@cello-protocol/daemon 0.0.231 → 0.0.233
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/channel-collect-tick.d.ts +50 -4
- package/dist/channel-collect-tick.d.ts.map +1 -1
- package/dist/channel-collect-tick.js +103 -7
- package/dist/channel-collect-tick.js.map +1 -1
- package/dist/channel-join-exchange.d.ts +25 -1
- package/dist/channel-join-exchange.d.ts.map +1 -1
- package/dist/channel-join-exchange.js +11 -4
- package/dist/channel-join-exchange.js.map +1 -1
- package/dist/channel-membership-wiring.d.ts +17 -1
- package/dist/channel-membership-wiring.d.ts.map +1 -1
- package/dist/channel-membership-wiring.js +113 -14
- package/dist/channel-membership-wiring.js.map +1 -1
- package/dist/channel-publish-handlers.d.ts +8 -0
- package/dist/channel-publish-handlers.d.ts.map +1 -1
- package/dist/channel-publish-handlers.js +3 -0
- package/dist/channel-publish-handlers.js.map +1 -1
- package/dist/channel-publish-wiring.d.ts +10 -0
- package/dist/channel-publish-wiring.d.ts.map +1 -1
- package/dist/channel-publish-wiring.js +18 -1
- package/dist/channel-publish-wiring.js.map +1 -1
- package/dist/channel-subscribe.d.ts +86 -0
- package/dist/channel-subscribe.d.ts.map +1 -0
- package/dist/channel-subscribe.js +123 -0
- package/dist/channel-subscribe.js.map +1 -0
- package/dist/channel-wake-sender.d.ts +35 -0
- package/dist/channel-wake-sender.d.ts.map +1 -0
- package/dist/channel-wake-sender.js +61 -0
- package/dist/channel-wake-sender.js.map +1 -0
- package/dist/daemon.js +39 -1
- package/dist/daemon.js.map +1 -1
- package/dist/register-handler.d.ts.map +1 -1
- package/dist/register-handler.js +18 -1
- package/dist/register-handler.js.map +1 -1
- package/dist/registration-manager.d.ts +6 -0
- package/dist/registration-manager.d.ts.map +1 -1
- package/dist/registration-manager.js +9 -1
- package/dist/registration-manager.js.map +1 -1
- package/dist/signaling-wiring.d.ts +5 -0
- package/dist/signaling-wiring.d.ts.map +1 -1
- package/dist/signaling-wiring.js +14 -0
- package/dist/signaling-wiring.js.map +1 -1
- package/package.json +5 -5
|
@@ -1,12 +1,48 @@
|
|
|
1
1
|
import type { Logger } from "./types.js";
|
|
2
2
|
import type { ChannelCollector } from "./channel-collector.js";
|
|
3
3
|
import type { ChannelSubscriptionStore } from "./channel-subscription-store.js";
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* ⚠️ **THIS IS A BACKSTOP, NOT THE DELIVERY MECHANISM — 021-WAKE changed what it is FOR.**
|
|
6
|
+
*
|
|
7
|
+
* At five minutes this timer WAS how a post reached anyone, and the feature was email with a
|
|
8
|
+
* five-minute inbox check. Delivery is now the wake (021): the publisher asks a directory to poke
|
|
9
|
+
* the members and they fetch at once.
|
|
10
|
+
*
|
|
11
|
+
* The timer keeps a job because push is best-effort, and it is the only thing that covers a post
|
|
12
|
+
* sitting on a relay with nobody told: the publisher's daemon never sent the wake, the frame was
|
|
13
|
+
* lost while the signaling stream rotated, or the peer forward never reached this subscriber's
|
|
14
|
+
* node. It does not go to zero, because the case it cannot otherwise repair is a channel that goes
|
|
15
|
+
* QUIET right after the message you missed — which is exactly the "relays going down now" post.
|
|
16
|
+
*/
|
|
5
17
|
export declare const COLLECT_TICK_INTERVAL_MS: number;
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The widest the pubkey-derived offset can push a channel's tick — **a proportion of the interval,
|
|
20
|
+
* which is why it is written as one.** A fixed 60 seconds was right for a 5-minute tick and is a
|
|
21
|
+
* narrow band inside an hour: the fleet would clump into the same minute every hour, which is the
|
|
22
|
+
* thundering herd this exists to prevent, arriving less often and so harder to notice.
|
|
23
|
+
*/
|
|
24
|
+
export declare const COLLECT_JITTER_MS: number;
|
|
25
|
+
/**
|
|
26
|
+
* A failing channel is backed off to at most this, then stays there until it succeeds.
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ **IT HAS TO EXCEED THE INTERVAL OR IT IS NOT A BACKOFF.** At 30 minutes against an hourly
|
|
29
|
+
* poll, a FAILING channel became due sooner than a healthy one — the first failure computed
|
|
30
|
+
* `min(30min, 60min×2)` = 30 minutes, and every subsequent one stayed there. This number was sized
|
|
31
|
+
* for a five-minute interval and not re-derived when the interval moved, which is the whole
|
|
32
|
+
* mistake: a constant that only makes sense relative to another constant should be written
|
|
33
|
+
* relative to it.
|
|
34
|
+
*/
|
|
9
35
|
export declare const COLLECT_MAX_BACKOFF_MS: number;
|
|
36
|
+
/**
|
|
37
|
+
* Randomness added to a RETRY, on top of the stable per-channel jitter.
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ **THE STEADY-STATE TIMER IS NOT THE STAMPEDE RISK — RECOVERY IS.** When a relay returns after
|
|
40
|
+
* an outage, every daemon that has been failing is on the retry path, not the timer, and
|
|
41
|
+
* `jitterForChannel` is stable per (agent, channel) so it does not stagger the returning WAVE: all
|
|
42
|
+
* of them back off to the same ceiling and come due on the same pass. This is the one place a
|
|
43
|
+
* re-rolled random belongs, because the thing being spread is a moment rather than a schedule.
|
|
44
|
+
*/
|
|
45
|
+
export declare const COLLECT_RETRY_SPREAD_MS: number;
|
|
10
46
|
export interface ChannelCollectTickDeps {
|
|
11
47
|
logger: Logger;
|
|
12
48
|
collector: ChannelCollector;
|
|
@@ -19,12 +55,22 @@ export interface ChannelCollectTickDeps {
|
|
|
19
55
|
intervalMs?: number;
|
|
20
56
|
jitterMs?: number;
|
|
21
57
|
maxBackoffMs?: number;
|
|
58
|
+
/** M16 021-WAKE: random spread on the RETRY path only. Set to 0 in a test that needs determinism. */
|
|
59
|
+
retrySpreadMs?: number;
|
|
22
60
|
}
|
|
23
61
|
export interface ChannelCollectTicker {
|
|
24
62
|
/** Begin collecting. Idempotent: a second call does not stack a second timer. */
|
|
25
63
|
start: () => void;
|
|
26
64
|
/** One pass over every active subscription. Exposed so a test drives it without a clock. */
|
|
27
65
|
collectAllDue: (now: number) => Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* M16 021-WAKE: the doorbell rang — collect this agent's channels NOW.
|
|
68
|
+
*
|
|
69
|
+
* ⚠️ **EVERY channel this agent follows, because the wake names none.** The frame is deliberately
|
|
70
|
+
* empty so that channel→member never travels a second wire; the daemon already knows what it
|
|
71
|
+
* subscribes to, and fetching all of them is what makes the empty frame sufficient.
|
|
72
|
+
*/
|
|
73
|
+
collectNow: (agentId: string) => Promise<void>;
|
|
28
74
|
stop: () => void;
|
|
29
75
|
}
|
|
30
76
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-collect-tick.d.ts","sourceRoot":"","sources":["../src/channel-collect-tick.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAGhF
|
|
1
|
+
{"version":3,"file":"channel-collect-tick.d.ts","sourceRoot":"","sources":["../src/channel-collect-tick.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAGhF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,QAAc,CAAC;AACpD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,QAA+B,CAAC;AAC9D;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,QAA+B,CAAC;AAEnE;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,QAAc,CAAC;AAEnD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,gBAAgB,CAAC;IAC5B,aAAa,EAAE,wBAAwB,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qGAAqG;IACrG,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,iFAAiF;IACjF,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,4FAA4F;IAC5F,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C;;;;;;OAMG;IACH,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI9F;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,sBAAsB,GAAG,oBAAoB,CAyI7F"}
|
|
@@ -32,12 +32,48 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import { createHash } from "node:crypto";
|
|
34
34
|
import { extractErrorMessage } from "./error-message.js";
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
/**
|
|
36
|
+
* ⚠️ **THIS IS A BACKSTOP, NOT THE DELIVERY MECHANISM — 021-WAKE changed what it is FOR.**
|
|
37
|
+
*
|
|
38
|
+
* At five minutes this timer WAS how a post reached anyone, and the feature was email with a
|
|
39
|
+
* five-minute inbox check. Delivery is now the wake (021): the publisher asks a directory to poke
|
|
40
|
+
* the members and they fetch at once.
|
|
41
|
+
*
|
|
42
|
+
* The timer keeps a job because push is best-effort, and it is the only thing that covers a post
|
|
43
|
+
* sitting on a relay with nobody told: the publisher's daemon never sent the wake, the frame was
|
|
44
|
+
* lost while the signaling stream rotated, or the peer forward never reached this subscriber's
|
|
45
|
+
* node. It does not go to zero, because the case it cannot otherwise repair is a channel that goes
|
|
46
|
+
* QUIET right after the message you missed — which is exactly the "relays going down now" post.
|
|
47
|
+
*/
|
|
48
|
+
export const COLLECT_TICK_INTERVAL_MS = 60 * 60_000;
|
|
49
|
+
/**
|
|
50
|
+
* The widest the pubkey-derived offset can push a channel's tick — **a proportion of the interval,
|
|
51
|
+
* which is why it is written as one.** A fixed 60 seconds was right for a 5-minute tick and is a
|
|
52
|
+
* narrow band inside an hour: the fleet would clump into the same minute every hour, which is the
|
|
53
|
+
* thundering herd this exists to prevent, arriving less often and so harder to notice.
|
|
54
|
+
*/
|
|
55
|
+
export const COLLECT_JITTER_MS = COLLECT_TICK_INTERVAL_MS / 2;
|
|
56
|
+
/**
|
|
57
|
+
* A failing channel is backed off to at most this, then stays there until it succeeds.
|
|
58
|
+
*
|
|
59
|
+
* ⚠️ **IT HAS TO EXCEED THE INTERVAL OR IT IS NOT A BACKOFF.** At 30 minutes against an hourly
|
|
60
|
+
* poll, a FAILING channel became due sooner than a healthy one — the first failure computed
|
|
61
|
+
* `min(30min, 60min×2)` = 30 minutes, and every subsequent one stayed there. This number was sized
|
|
62
|
+
* for a five-minute interval and not re-derived when the interval moved, which is the whole
|
|
63
|
+
* mistake: a constant that only makes sense relative to another constant should be written
|
|
64
|
+
* relative to it.
|
|
65
|
+
*/
|
|
66
|
+
export const COLLECT_MAX_BACKOFF_MS = 4 * COLLECT_TICK_INTERVAL_MS;
|
|
67
|
+
/**
|
|
68
|
+
* Randomness added to a RETRY, on top of the stable per-channel jitter.
|
|
69
|
+
*
|
|
70
|
+
* ⚠️ **THE STEADY-STATE TIMER IS NOT THE STAMPEDE RISK — RECOVERY IS.** When a relay returns after
|
|
71
|
+
* an outage, every daemon that has been failing is on the retry path, not the timer, and
|
|
72
|
+
* `jitterForChannel` is stable per (agent, channel) so it does not stagger the returning WAVE: all
|
|
73
|
+
* of them back off to the same ceiling and come due on the same pass. This is the one place a
|
|
74
|
+
* re-rolled random belongs, because the thing being spread is a moment rather than a schedule.
|
|
75
|
+
*/
|
|
76
|
+
export const COLLECT_RETRY_SPREAD_MS = 10 * 60_000;
|
|
41
77
|
/**
|
|
42
78
|
* A stable offset in [0, jitterMs) for a channel, from its public key. Same channel, same
|
|
43
79
|
* subscriber, same offset every time — see the header for why that beats a fresh random each tick.
|
|
@@ -53,6 +89,7 @@ export function createChannelCollectTicker(deps) {
|
|
|
53
89
|
const intervalMs = deps.intervalMs ?? COLLECT_TICK_INTERVAL_MS;
|
|
54
90
|
const jitterMs = deps.jitterMs ?? COLLECT_JITTER_MS;
|
|
55
91
|
const maxBackoffMs = deps.maxBackoffMs ?? COLLECT_MAX_BACKOFF_MS;
|
|
92
|
+
const retrySpreadMs = deps.retrySpreadMs ?? COLLECT_RETRY_SPREAD_MS;
|
|
56
93
|
/** When each subscription is next due, and how far it has been backed off. */
|
|
57
94
|
const nextDue = new Map();
|
|
58
95
|
const backoff = new Map();
|
|
@@ -95,7 +132,12 @@ export function createChannelCollectTicker(deps) {
|
|
|
95
132
|
// and one channel's bad relay must not stop every other channel on this daemon.
|
|
96
133
|
const next = Math.min(maxBackoffMs, (backoff.get(key) ?? intervalMs) * 2);
|
|
97
134
|
backoff.set(key, next);
|
|
98
|
-
|
|
135
|
+
// Stable jitter spreads channels across subscribers; the RANDOM part spreads the moment a
|
|
136
|
+
// whole fleet returns after an outage, which stable jitter cannot do because every failing
|
|
137
|
+
// channel lands on the same ceiling.
|
|
138
|
+
nextDue.set(key, now + next
|
|
139
|
+
+ jitterForChannel(sub.channel_pubkey, sub.agent_id, jitterMs)
|
|
140
|
+
+ Math.floor(Math.random() * retrySpreadMs));
|
|
99
141
|
logger.warn("channel.collect.tick_failed", {
|
|
100
142
|
channel_pubkey: sub.channel_pubkey,
|
|
101
143
|
reason: extractErrorMessage(err),
|
|
@@ -107,10 +149,64 @@ export function createChannelCollectTicker(deps) {
|
|
|
107
149
|
}
|
|
108
150
|
}
|
|
109
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* M16 021-WAKE — a doorbell arrived for this agent. Fetch its channels at once.
|
|
154
|
+
*
|
|
155
|
+
* ⚠️ **IT DOES NOT TOUCH `nextDue`, and that is deliberate.** A wake that reset the schedule would
|
|
156
|
+
* let a chatty channel keep pushing the backstop out for every OTHER channel on this agent — and
|
|
157
|
+
* the backstop is the only thing that repairs a wake that never arrived.
|
|
158
|
+
*
|
|
159
|
+
* ⚠️ The online check is repeated here. The kill switch has to hold on this path too: collecting
|
|
160
|
+
* for an agent the operator switched off is the switch failing to switch something off, and a
|
|
161
|
+
* doorbell must not become the way around it.
|
|
162
|
+
*/
|
|
163
|
+
async function collectNow(agentId) {
|
|
164
|
+
if (!isAgentOnline(agentId)) {
|
|
165
|
+
logger.debug("channel.collect.wake_ignored", { reason: "agent_offline" });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
for (const sub of subscriptions.active()) {
|
|
169
|
+
if (sub.agent_id !== agentId)
|
|
170
|
+
continue;
|
|
171
|
+
const key = keyOf(sub.agent_id, sub.channel_pubkey);
|
|
172
|
+
if (inFlight.has(key))
|
|
173
|
+
continue;
|
|
174
|
+
inFlight.add(key);
|
|
175
|
+
try {
|
|
176
|
+
await collector.collectOnce(sub.agent_id, sub.channel_pubkey);
|
|
177
|
+
await collector.repairGaps(sub.agent_id, sub.channel_pubkey);
|
|
178
|
+
backoff.delete(key);
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
// One channel's bad relay must not stop the rest of this agent's channels collecting. The
|
|
182
|
+
// timer's own backoff is untouched: this pass is extra, not a replacement for it.
|
|
183
|
+
logger.warn("channel.collect.wake_failed", {
|
|
184
|
+
channel_pubkey: sub.channel_pubkey, reason: extractErrorMessage(err),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
inFlight.delete(key);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
110
192
|
return {
|
|
193
|
+
collectNow,
|
|
111
194
|
start() {
|
|
112
195
|
if (timer !== null)
|
|
113
196
|
return;
|
|
197
|
+
/**
|
|
198
|
+
* ⚠️ **ONE PASS NOW, BEFORE THE TIMER — and leaving this out made the order's own worst case
|
|
199
|
+
* twelve times worse.** At a five-minute interval a daemon that had just started waited five
|
|
200
|
+
* minutes for its first collection; at sixty it waits an hour. Your laptop was shut
|
|
201
|
+
* overnight, you open it, the daemon reconnects — and nothing is fetched until the next hour.
|
|
202
|
+
* The offline subscriber is exactly the case the backstop exists for.
|
|
203
|
+
*
|
|
204
|
+
* It is also what makes a NEW subscription arrive promptly: `collectAllDue` treats first
|
|
205
|
+
* sight as due immediately, but only a pass can notice it.
|
|
206
|
+
*/
|
|
207
|
+
void collectAllDue(Date.now()).catch((err) => {
|
|
208
|
+
logger.warn("channel.collect.pass_failed", { reason: extractErrorMessage(err) });
|
|
209
|
+
});
|
|
114
210
|
// The pass runs every interval and decides per subscription what is due; the jitter lives in
|
|
115
211
|
// the due times rather than in the timer, so one slow channel cannot drag the others.
|
|
116
212
|
timer = setInterval(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-collect-tick.js","sourceRoot":"","sources":["../src/channel-collect-tick.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD
|
|
1
|
+
{"version":3,"file":"channel-collect-tick.js","sourceRoot":"","sources":["../src/channel-collect-tick.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,6 +35,20 @@ export interface LocalChannelAdmin {
|
|
|
35
35
|
channelKeyProvider: KeyProvider;
|
|
36
36
|
adminKeyProvider: KeyProvider;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* M16 021-WAKE item 21: the admin, or WHY there isn't one.
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ The refusal is unchanged — anything other than `ok` leaves the join refused, exactly as the
|
|
42
|
+
* bare `null` did. What is new is that the cause travels with it, so `admin_unresolved` stops being
|
|
43
|
+
* a word an operator can do nothing with.
|
|
44
|
+
*/
|
|
45
|
+
export type AdminLookupOutcome = {
|
|
46
|
+
ok: true;
|
|
47
|
+
adminPubkeyHex: string;
|
|
48
|
+
} | {
|
|
49
|
+
ok: false;
|
|
50
|
+
reason: string;
|
|
51
|
+
};
|
|
38
52
|
export interface ChannelJoinExchangeDeps {
|
|
39
53
|
logger: Logger;
|
|
40
54
|
members: ChannelMembershipStore;
|
|
@@ -46,7 +60,7 @@ export interface ChannelJoinExchangeDeps {
|
|
|
46
60
|
* The channel's admin AS THE DIRECTORY REPORTS IT. `null` means the lookup could not be resolved,
|
|
47
61
|
* which FAILS CLOSED — an unreachable directory must not become "whoever answered is the admin".
|
|
48
62
|
*/
|
|
49
|
-
profileAdminPubkey: (channelHex: string, agentId: string) => Promise<
|
|
63
|
+
profileAdminPubkey: (channelHex: string, agentId: string) => Promise<AdminLookupOutcome>;
|
|
50
64
|
keyProviderFor: (agentId: string) => KeyProvider | null;
|
|
51
65
|
raiseNotice: (event: string, channelHex: string, subscriberHex: string) => void;
|
|
52
66
|
now?: () => number;
|
|
@@ -58,6 +72,16 @@ export type SubscriberJoinResult = {
|
|
|
58
72
|
} | {
|
|
59
73
|
ok: false;
|
|
60
74
|
reason: "not_a_join_frame" | "malformed" | "not_admin_of_channel" | "admin_unresolved" | "key_unwrap_failed" | "no_key_provider";
|
|
75
|
+
/**
|
|
76
|
+
* M16 021-WAKE item 21: WHY, when the reason alone cannot say.
|
|
77
|
+
*
|
|
78
|
+
* ⚠️ `admin_unresolved` is an exit-point label. A dead signaling stream, a ten-second timeout
|
|
79
|
+
* against a directory that has not been rolled, a channel the directory has never heard of
|
|
80
|
+
* and a database fault all arrive at that one word, and the operator cannot tell which. The
|
|
81
|
+
* cause survived only in a log line one step upstream, which is not where anyone looks when a
|
|
82
|
+
* join is refused.
|
|
83
|
+
*/
|
|
84
|
+
detail?: string;
|
|
61
85
|
};
|
|
62
86
|
export interface ChannelJoinExchange {
|
|
63
87
|
/** An inbound frame on the ADMIN's daemon. `consumed: false` means it was not a join frame. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-join-exchange.d.ts","sourceRoot":"","sources":["../src/channel-join-exchange.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAEgD,kBAAkB,EAGxE,MAAM,gCAAgC,CAAC;AAIxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAGhF,sGAAsG;AACtG,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,WAAW,CAAC;IAChC,gBAAgB,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,sBAAsB,CAAC;IAChC,aAAa,EAAE,wBAAwB,CAAC;IACxC,gGAAgG;IAChG,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,iBAAiB,GAAG,IAAI,CAAC;IACpE;;;OAGG;IACH,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"channel-join-exchange.d.ts","sourceRoot":"","sources":["../src/channel-join-exchange.ts"],"names":[],"mappings":"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"}
|
|
@@ -208,11 +208,18 @@ export function createChannelJoinExchange(deps) {
|
|
|
208
208
|
* authenticated directory stream rather than any connection that happens to be open.
|
|
209
209
|
*/
|
|
210
210
|
const storedAdmin = rekeyFrame ? (subscriptions.get(agentId, channelHex)?.admin_pubkey ?? null) : null;
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
211
|
+
const looked = storedAdmin !== null
|
|
212
|
+
? { ok: true, adminPubkeyHex: storedAdmin }
|
|
213
|
+
: await deps.profileAdminPubkey(channelHex, agentId);
|
|
214
|
+
if (!looked.ok) {
|
|
215
|
+
// The cause travels with the refusal now. It used to live only in a log line one step
|
|
216
|
+
// upstream, which is not where anyone looks when a join is refused.
|
|
217
|
+
logger.warn("channel.join.refused", {
|
|
218
|
+
channel_pubkey: channelHex, reason: "admin_unresolved", detail: looked.reason,
|
|
219
|
+
});
|
|
220
|
+
return { ok: false, reason: "admin_unresolved", detail: looked.reason };
|
|
215
221
|
}
|
|
222
|
+
const profileAdmin = looked.adminPubkeyHex;
|
|
216
223
|
if (profileAdmin.toLowerCase() !== counterpartyHex.toLowerCase()) {
|
|
217
224
|
logger.warn("channel.join.refused", {
|
|
218
225
|
channel_pubkey: channelHex, reason: "not_admin_of_channel",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-join-exchange.js","sourceRoot":"","sources":["../src/channel-join-exchange.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EACL,wBAAwB,EAAE,yBAAyB,EAAE,kBAAkB,EACvE,yBAAyB,EAAE,wBAAwB,EAAE,kBAAkB,EACvE,kBAAkB,GAEnB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,gBAAgB,EAAE,eAAe,EAAE,cAAc,GAClD,MAAM,wBAAwB,CAAC;AAKhC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"channel-join-exchange.js","sourceRoot":"","sources":["../src/channel-join-exchange.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@ import type { Logger } from "./types.js";
|
|
|
15
15
|
import type { DaemonDatabase } from "./sqlcipher-db.js";
|
|
16
16
|
import type { KeyProvider } from "@cello-protocol/crypto";
|
|
17
17
|
import { ChannelMembershipStore } from "./channel-membership-store.js";
|
|
18
|
+
import { type AdminLookupOutcome } from "./channel-join-exchange.js";
|
|
18
19
|
import { type ChannelAdminOutcome, type SignalingLike } from "./channel-admin-lookup.js";
|
|
19
20
|
type Handler = (params: Record<string, unknown> | undefined, connectionId: string) => Promise<unknown>;
|
|
20
21
|
export interface ChannelMembershipWiringDeps {
|
|
@@ -48,6 +49,14 @@ export interface ChannelMembershipWiringDeps {
|
|
|
48
49
|
* the relay's copy of it.
|
|
49
50
|
*/
|
|
50
51
|
signalingFor: (agentName: string) => SignalingLike | null;
|
|
52
|
+
/**
|
|
53
|
+
* M16 022: open a session as this agent, without an IPC connection. The same path
|
|
54
|
+
* `cello_initiate_session` takes — `join` needs it because a subscriber has never spoken to the
|
|
55
|
+
* channel's administrator.
|
|
56
|
+
*/
|
|
57
|
+
openSessionFor: (agentName: string, opts: {
|
|
58
|
+
targetPubkey: string;
|
|
59
|
+
}) => Promise<unknown>;
|
|
51
60
|
}
|
|
52
61
|
/**
|
|
53
62
|
* M16 020-CHANADMIN — where the subscriber's admin key comes from.
|
|
@@ -72,7 +81,7 @@ export declare function createProfileAdminPubkey(deps: {
|
|
|
72
81
|
members: ChannelMembershipStore;
|
|
73
82
|
lookup: (agentId: string, channelHex: string) => Promise<ChannelAdminOutcome>;
|
|
74
83
|
logger: Logger;
|
|
75
|
-
}): (channelHex: string, agentId: string) => Promise<
|
|
84
|
+
}): (channelHex: string, agentId: string) => Promise<AdminLookupOutcome>;
|
|
76
85
|
export interface ChannelMembershipWiring {
|
|
77
86
|
/**
|
|
78
87
|
* The channel's CURRENT fetch key, signed, or undefined when this daemon holds no group key for
|
|
@@ -84,6 +93,13 @@ export interface ChannelMembershipWiring {
|
|
|
84
93
|
time_ms: number;
|
|
85
94
|
signature: Uint8Array;
|
|
86
95
|
} | undefined>;
|
|
96
|
+
/**
|
|
97
|
+
* M16 021-WAKE: the channel's ACTIVE members, which is who a post's doorbell is rung for. Pending
|
|
98
|
+
* and ejected rows are excluded by the store — waking a pending request would tell somebody who
|
|
99
|
+
* has not been admitted that a post exists, and waking an ejected member is the thing the
|
|
100
|
+
* ejection undid.
|
|
101
|
+
*/
|
|
102
|
+
activeMembers: (channelHex: string) => string[];
|
|
87
103
|
}
|
|
88
104
|
export declare function wireChannelMembership(deps: ChannelMembershipWiringDeps): ChannelMembershipWiring;
|
|
89
105
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-membership-wiring.d.ts","sourceRoot":"","sources":["../src/channel-membership-wiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAK1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"channel-membership-wiring.d.ts","sourceRoot":"","sources":["../src/channel-membership-wiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAK1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEvE,OAAO,EAC8C,KAAK,kBAAkB,EAC3E,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACqB,KAAK,mBAAmB,EAAE,KAAK,aAAa,EACvE,MAAM,2BAA2B,CAAC;AAKnC,KAAK,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEvG,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,2EAA2E;IAC3E,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5F,yFAAyF;IACzF,qBAAqB,EAAE,CACrB,EAAE,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,KACnI,IAAI,CAAC;IACV,qGAAqG;IACrG,YAAY,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;IACxF,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACvC,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9C,mBAAmB,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACrF,sFAAsF;IACtF,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,qBAAqB,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtG;;;;;OAKG;IACH,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,aAAa,GAAG,IAAI,CAAC;IAC1D;;;;OAIG;IACH,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACzF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,sBAAsB,CAAC;IAChC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC9E,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAyBvE;AA0BD,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE,UAAU,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,UAAU,CAAA;KAAE,GAAG,SAAS,CAAC,CAAC;IAC7H;;;;;OAKG;IACH,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;CACjD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,2BAA2B,GAAG,uBAAuB,CA6ZhG"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { channelJoinFrameType, encodeChannelRekey, buildChannelFetchKeyTbs, JOIN_REQUEST_TYPE, } from "@cello-protocol/protocol-types";
|
|
2
|
-
import { generateGroupKey, wrapGroupKeyFor, deriveFetchKey } from "@cello-protocol/crypto";
|
|
2
|
+
import { generateGroupKey, wrapGroupKeyFor, deriveFetchKey, decryptBody } from "@cello-protocol/crypto";
|
|
3
3
|
import { ChannelMembershipStore } from "./channel-membership-store.js";
|
|
4
4
|
import { ChannelSubscriptionStore } from "./channel-subscription-store.js";
|
|
5
|
-
import { createChannelJoinExchange } from "./channel-join-exchange.js";
|
|
5
|
+
import { createChannelJoinExchange, } from "./channel-join-exchange.js";
|
|
6
6
|
import { createChannelAdminLookup, } from "./channel-admin-lookup.js";
|
|
7
|
+
import { createChannelSubscribe } from "./channel-subscribe.js";
|
|
8
|
+
import { ChannelInboxStore } from "./channel-inbox-store.js";
|
|
7
9
|
import { extractErrorMessage } from "./error-message.js";
|
|
8
10
|
/**
|
|
9
11
|
* M16 020-CHANADMIN — where the subscriber's admin key comes from.
|
|
@@ -29,25 +31,25 @@ export function createProfileAdminPubkey(deps) {
|
|
|
29
31
|
// The ADMIN this daemon recorded, not the channel's own key — the two are different agents, and
|
|
30
32
|
// comparing a key with itself is what the first version of this did.
|
|
31
33
|
const settings = deps.members.settings(channelHex);
|
|
32
|
-
if (settings && settings.admin_pubkey.length > 0)
|
|
33
|
-
return settings.admin_pubkey;
|
|
34
|
+
if (settings && settings.admin_pubkey.length > 0) {
|
|
35
|
+
return { ok: true, adminPubkeyHex: settings.admin_pubkey };
|
|
36
|
+
}
|
|
34
37
|
try {
|
|
35
38
|
const outcome = await deps.lookup(agentId, channelHex);
|
|
36
39
|
if (outcome.kind === "admin")
|
|
37
|
-
return outcome.adminPubkeyHex;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
});
|
|
42
|
-
return
|
|
40
|
+
return { ok: true, adminPubkeyHex: outcome.adminPubkeyHex };
|
|
41
|
+
// M16 021-WAKE item 21: the reason goes BACK, not just into a log. `admin_unresolved` alone
|
|
42
|
+
// cannot tell a dead stream from an unrolled directory from a channel that does not exist.
|
|
43
|
+
const reason = outcome.kind === "not_a_channel" ? "not_a_channel" : outcome.reason;
|
|
44
|
+
deps.logger.info("channel.join.admin_unresolved", { channel_pubkey: channelHex, reason });
|
|
45
|
+
return { ok: false, reason };
|
|
43
46
|
}
|
|
44
47
|
catch (err) {
|
|
45
48
|
// This runs inside the inbound content path. An exception escaping would surface as a broken
|
|
46
49
|
// session rather than a refused join, which is a worse answer to the same question.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
return null;
|
|
50
|
+
const reason = extractErrorMessage(err);
|
|
51
|
+
deps.logger.warn("channel.join.admin_unresolved", { channel_pubkey: channelHex, reason });
|
|
52
|
+
return { ok: false, reason };
|
|
51
53
|
}
|
|
52
54
|
};
|
|
53
55
|
}
|
|
@@ -179,6 +181,11 @@ export function wireChannelMembership(deps) {
|
|
|
179
181
|
logger.warn("channel.join.refused", {
|
|
180
182
|
...(correlationId !== undefined ? { correlationId } : {}),
|
|
181
183
|
reason: asSubscriber.reason, sender: senderPubkey, frame_type: kind,
|
|
184
|
+
// ⚠️ THE LINE THE OPERATOR ACTUALLY READS — this is the one carrying correlationId, and
|
|
185
|
+
// the join path is fire-and-forget so there is no response to inspect either. Dropping
|
|
186
|
+
// `detail` here left `admin_unresolved` as bare as it was before item 21 fixed it, with
|
|
187
|
+
// the cause visible only on a second line that shares this event name.
|
|
188
|
+
...(asSubscriber.detail !== undefined ? { detail: asSubscriber.detail } : {}),
|
|
182
189
|
});
|
|
183
190
|
}
|
|
184
191
|
})().catch((err) => {
|
|
@@ -191,6 +198,97 @@ export function wireChannelMembership(deps) {
|
|
|
191
198
|
return { consumed: true };
|
|
192
199
|
});
|
|
193
200
|
// ─── Operator verbs ───────────────────────────────────────────────────────────────────────────
|
|
201
|
+
/**
|
|
202
|
+
* M16 022-SUBSCRIBE — the three verbs that make the other eleven mean anything. Until this, no
|
|
203
|
+
* production code sent a join request and nothing read a post back out.
|
|
204
|
+
*/
|
|
205
|
+
const subscribe = createChannelSubscribe({
|
|
206
|
+
logger,
|
|
207
|
+
subscriptions,
|
|
208
|
+
inbox: new ChannelInboxStore(deps.getDb(), logger),
|
|
209
|
+
/**
|
|
210
|
+
* ⚠️ **THE SAME SOURCE THE ADMIN CHECK USES, and the first version used a different one.** It
|
|
211
|
+
* went straight to the directory, so joining a channel THIS daemon administers answered
|
|
212
|
+
* `unavailable` — while the check that runs on the answer resolves it locally. Two sources for
|
|
213
|
+
* one fact is how they drift.
|
|
214
|
+
*/
|
|
215
|
+
lookupAdmin: async (agentId, channelHex) => {
|
|
216
|
+
const found = await profileAdminPubkey(channelHex, agentId);
|
|
217
|
+
if (found.ok)
|
|
218
|
+
return { kind: "admin", adminPubkeyHex: found.adminPubkeyHex };
|
|
219
|
+
return found.reason === "not_a_channel"
|
|
220
|
+
? { kind: "not_a_channel" }
|
|
221
|
+
: { kind: "unavailable", reason: found.reason };
|
|
222
|
+
},
|
|
223
|
+
/**
|
|
224
|
+
* An existing session with the admin, or a new one.
|
|
225
|
+
*
|
|
226
|
+
* ⚠️ **IT OPENS ONE IF THERE IS NONE, and that is what makes `join` a single command.** A
|
|
227
|
+
* subscriber has no reason to already hold a session with a channel's administrator — they have
|
|
228
|
+
* never spoken. Requiring `cello initiate-session` first would make the verb a two-step dance
|
|
229
|
+
* whose first step nothing tells you to take.
|
|
230
|
+
*
|
|
231
|
+
* It calls the daemon's OWN initiate handler rather than a second path to the same thing: the
|
|
232
|
+
* brokering, key-binding checks and refusals all belong to that handler and must not be
|
|
233
|
+
* reimplemented here.
|
|
234
|
+
*/
|
|
235
|
+
sessionWith: async (agentName, counterpartyHex) => {
|
|
236
|
+
const existing = openSessionWith(agentName, counterpartyHex);
|
|
237
|
+
if (existing !== null)
|
|
238
|
+
return { ok: true, sessionId: existing };
|
|
239
|
+
/**
|
|
240
|
+
* ⚠️ **`openSessionFor`, NOT the handlers map — and the first version got BOTH field names
|
|
241
|
+
* wrong.** It passed `target` where the negotiator reads `target_pubkey`, and read back
|
|
242
|
+
* `session_id` where the handler returns `sessionId`. So every join refused with
|
|
243
|
+
* `no_session`, pointing the operator at the counterparty and the network for a bug that was
|
|
244
|
+
* a field name in this file. The handler's own header names this exact trap.
|
|
245
|
+
*
|
|
246
|
+
* `openSessionFor` is the seam built for callers with no IPC connection, and the document
|
|
247
|
+
* layer already uses it. Going through the handler map also meant inventing a fake
|
|
248
|
+
* connectionId, which was a seam that proved nothing.
|
|
249
|
+
*/
|
|
250
|
+
const res = await deps.openSessionFor(agentName, { targetPubkey: counterpartyHex });
|
|
251
|
+
if (res.ok === true && typeof res.sessionId === "string")
|
|
252
|
+
return { ok: true, sessionId: res.sessionId };
|
|
253
|
+
// The real refusal travels. Discarding it is what made a payload bug read as a network fault.
|
|
254
|
+
return { ok: false, reason: res.reason ?? "session_open_failed", guidance: res.guidance };
|
|
255
|
+
},
|
|
256
|
+
sendInSession: (agentName, sessionId, content) => deps.sendInSession(agentName, sessionId, content),
|
|
257
|
+
agentPubkey: (agentName) => deps.loadedAgents.find((a) => a.name === agentName)?.pubkey ?? null,
|
|
258
|
+
decrypt: (agentId, channelHex, seq, body) => {
|
|
259
|
+
const keys = subscriptions.keysFor(agentId, channelHex);
|
|
260
|
+
const out = decryptBody(keys, new Uint8Array(Buffer.from(channelHex, "hex")), seq, body);
|
|
261
|
+
return Promise.resolve(out.ok ? out.plaintext : null);
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
handlers.set("cello_channel_info", async (params, connectionId) => {
|
|
265
|
+
const agent = needAgent(deps, params, connectionId);
|
|
266
|
+
if (!agent.ok)
|
|
267
|
+
return agent.answer;
|
|
268
|
+
const channel = needChannel(params);
|
|
269
|
+
if (!channel.ok)
|
|
270
|
+
return channel.answer;
|
|
271
|
+
return subscribe.info(deps.resolveAgentId(agent.agentName), channel.channelHex);
|
|
272
|
+
});
|
|
273
|
+
handlers.set("cello_channel_join", async (params, connectionId) => {
|
|
274
|
+
const agent = needAgent(deps, params, connectionId);
|
|
275
|
+
if (!agent.ok)
|
|
276
|
+
return agent.answer;
|
|
277
|
+
const channel = needChannel(params);
|
|
278
|
+
if (!channel.ok)
|
|
279
|
+
return channel.answer;
|
|
280
|
+
const note = typeof params?.["note"] === "string" ? (params["note"]) : undefined;
|
|
281
|
+
return subscribe.join(agent.agentName, deps.resolveAgentId(agent.agentName), channel.channelHex, note);
|
|
282
|
+
});
|
|
283
|
+
handlers.set("cello_channel_read", async (params, connectionId) => {
|
|
284
|
+
const agent = needAgent(deps, params, connectionId);
|
|
285
|
+
if (!agent.ok)
|
|
286
|
+
return agent.answer;
|
|
287
|
+
const channel = needChannel(params);
|
|
288
|
+
if (!channel.ok)
|
|
289
|
+
return channel.answer;
|
|
290
|
+
return subscribe.read(deps.resolveAgentId(agent.agentName), channel.channelHex, params?.["all"] === true);
|
|
291
|
+
});
|
|
194
292
|
handlers.set("cello_channels", async (params, connectionId) => {
|
|
195
293
|
const agent = needAgent(deps, params, connectionId);
|
|
196
294
|
if (!agent.ok)
|
|
@@ -363,6 +461,7 @@ export function wireChannelMembership(deps) {
|
|
|
363
461
|
return result.ok ? { ok: true, channel: channel.channelHex } : { ok: false, reason: result.reason };
|
|
364
462
|
});
|
|
365
463
|
return {
|
|
464
|
+
activeMembers: (channelHex) => members.activeMembers(channelHex),
|
|
366
465
|
currentFetchKey: async (channelHex) => {
|
|
367
466
|
const admin = localChannelAdmin(channelHex);
|
|
368
467
|
if (!admin)
|