@takosjp/yurucommu-core 3.4.1 → 3.4.4
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/migrations/0023_delivery_resolution_outbox.sql +33 -0
- package/migrations/0024_delivery_fanout_outbox.sql +35 -0
- package/migrations/0025_delivery_endpoint_terminal_retention.sql +6 -0
- package/migrations/0026_remote_actor_fetch_failures.sql +29 -0
- package/migrations/0027_remote_actor_tombstones.sql +12 -0
- package/migrations/0028_remote_actor_delivery_fence.sql +30 -0
- package/migrations/0029_delivery_endpoint_recipients.sql +47 -0
- package/package.json +2 -1
- package/packages/api/package.json +1 -1
- package/packages/api/src/lib/api/communities.ts +2 -0
- package/packages/api/src/lib/api/fetch.ts +69 -13
- package/packages/api/src/lib/api/normalize.ts +32 -10
- package/packages/api/src/lib/api/notifications.ts +4 -1
- package/packages/api/src/lib/rtc-client.ts +127 -29
- package/src/backend/federation-helpers.ts +1 -0
- package/src/backend/index.ts +35 -3
- package/src/backend/lib/account-migration.ts +340 -0
- package/src/backend/lib/activity-delete-cascade.ts +193 -0
- package/src/backend/lib/activitypub-actor-cache.ts +615 -48
- package/src/backend/lib/activitypub-actor-identity-sql.ts +179 -0
- package/src/backend/lib/activitypub-actor-identity.ts +39 -0
- package/src/backend/lib/activitypub-validators.ts +62 -4
- package/src/backend/lib/ap-ids.ts +36 -6
- package/src/backend/lib/ap-verify.ts +7 -17
- package/src/backend/lib/blocklist-purge.ts +676 -42
- package/src/backend/lib/blocklist.ts +278 -39
- package/src/backend/lib/community-visibility.ts +47 -33
- package/src/backend/lib/delivery/fanout-outbox.ts +336 -0
- package/src/backend/lib/delivery/planner.ts +16 -12
- package/src/backend/lib/delivery/queue-batching.ts +389 -145
- package/src/backend/lib/delivery/queue-delivery.ts +47 -25
- package/src/backend/lib/delivery/queue.ts +479 -73
- package/src/backend/lib/delivery/resolution-outbox.ts +456 -0
- package/src/backend/lib/delivery/types.ts +31 -7
- package/src/backend/lib/feed-exclude.ts +40 -28
- package/src/backend/lib/follow-edge-mutations.ts +217 -0
- package/src/backend/lib/notification-eligibility.ts +15 -9
- package/src/backend/lib/notification-push.ts +2 -2
- package/src/backend/lib/oidc-id-token.ts +66 -0
- package/src/backend/lib/personal-actor-moderation.ts +239 -0
- package/src/backend/lib/post-visibility.ts +79 -16
- package/src/backend/lib/remote-activity-id.ts +61 -0
- package/src/backend/lib/unread-counts.ts +3 -0
- package/src/backend/public.ts +16 -0
- package/src/backend/retention.ts +115 -0
- package/src/backend/routes/account-teardown.ts +422 -156
- package/src/backend/routes/activitypub/handlers/actor-inbox-handlers.ts +104 -105
- package/src/backend/routes/activitypub/handlers/inbound-community-scope.ts +121 -0
- package/src/backend/routes/activitypub/handlers/inbound-object-identity.ts +54 -0
- package/src/backend/routes/activitypub/handlers/inbound-reply-target.ts +34 -0
- package/src/backend/routes/activitypub/handlers/inbound-story-projection.ts +438 -0
- package/src/backend/routes/activitypub/handlers/inbox-content-handlers.ts +1121 -806
- package/src/backend/routes/activitypub/handlers/inbox-follow-handlers.ts +118 -153
- package/src/backend/routes/activitypub/handlers/inbox-interaction-handlers.ts +185 -168
- package/src/backend/routes/activitypub/handlers/inbox-shared-helpers.ts +220 -32
- package/src/backend/routes/activitypub/inbound-activity-identity.ts +16 -0
- package/src/backend/routes/activitypub/inbound-activity-reference.ts +116 -0
- package/src/backend/routes/activitypub/inbound-addressing.ts +87 -0
- package/src/backend/routes/activitypub/inbox-addressing.ts +22 -19
- package/src/backend/routes/activitypub/inbox-types.ts +14 -2
- package/src/backend/routes/activitypub/inbox.ts +81 -105
- package/src/backend/routes/activitypub/outbox.ts +0 -0
- package/src/backend/routes/activitypub.ts +6 -5
- package/src/backend/routes/actors-helpers.ts +34 -8
- package/src/backend/routes/actors.ts +333 -169
- package/src/backend/routes/auth-helpers.ts +57 -9
- package/src/backend/routes/auth.ts +10 -2
- package/src/backend/routes/communities/membership-invites.ts +4 -1
- package/src/backend/routes/communities/membership-members.ts +201 -73
- package/src/backend/routes/communities/membership-requests.ts +141 -76
- package/src/backend/routes/communities/membership-shared.ts +228 -21
- package/src/backend/routes/communities/messages.ts +9 -2
- package/src/backend/routes/communities/routes.ts +48 -13
- package/src/backend/routes/dm/contacts.ts +29 -41
- package/src/backend/routes/dm/conversations-helpers.ts +9 -1
- package/src/backend/routes/dm/messages.ts +36 -42
- package/src/backend/routes/dm/read-archive.ts +4 -2
- package/src/backend/routes/dm/requests.ts +62 -54
- package/src/backend/routes/follow-helpers.ts +200 -60
- package/src/backend/routes/follow.ts +146 -140
- package/src/backend/routes/media.ts +20 -94
- package/src/backend/routes/moderation.ts +72 -4
- package/src/backend/routes/notes.ts +3 -4
- package/src/backend/routes/notifications.ts +209 -108
- package/src/backend/routes/posts/delete-cascade.ts +253 -89
- package/src/backend/routes/posts/federation.ts +373 -0
- package/src/backend/routes/posts/interactions.ts +160 -184
- package/src/backend/routes/posts/like-mutation.ts +240 -0
- package/src/backend/routes/posts/post-helpers.ts +112 -162
- package/src/backend/routes/posts/queries.ts +150 -54
- package/src/backend/routes/posts/routes.ts +169 -329
- package/src/backend/routes/posts/transformers.ts +61 -6
- package/src/backend/routes/recommendations.ts +37 -44
- package/src/backend/routes/rtc/index.ts +26 -6
- package/src/backend/routes/search.ts +39 -55
- package/src/backend/routes/stories/interactions.ts +22 -6
- package/src/backend/routes/stories/query-helpers.ts +28 -41
- package/src/backend/routes/stories/routes.ts +130 -115
- package/src/backend/routes/takos-tools/dm.ts +17 -17
- package/src/backend/routes/takos-tools/posts.ts +189 -106
- package/src/backend/routes/takos-tools/search.ts +13 -4
- package/src/backend/routes/takos-tools/timeline.ts +35 -27
- package/src/backend/routes/takos-tools-response.ts +6 -2
- package/src/backend/routes/timeline.ts +5 -5
- package/src/backend/runtime/call-signaling-do.ts +35 -4
- package/src/backend/runtime/one-time-ticket.ts +116 -0
- package/src/backend/runtime/realtime-stream-do.ts +5 -61
- package/src/backend/runtime/signaling-hub.ts +36 -3
- package/src/backend/server.ts +95 -80
- package/src/db/d1-write.ts +67 -43
- package/src/db/schema/federation.ts +42 -1
- package/src/db/schema/messaging.ts +110 -2
- package/src/db/schema.ts +1 -1
|
@@ -19,6 +19,8 @@ import type {
|
|
|
19
19
|
HubToClientFrame,
|
|
20
20
|
IceServerConfig,
|
|
21
21
|
} from "../types/call.ts";
|
|
22
|
+
import { apiPost } from "./api/fetch.ts";
|
|
23
|
+
import { getYurucommuApiTransport } from "./transport.ts";
|
|
22
24
|
|
|
23
25
|
export type CallUiState =
|
|
24
26
|
| "idle"
|
|
@@ -45,7 +47,7 @@ export interface CallClientEvents {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
export interface CallClientOptions {
|
|
48
|
-
/**
|
|
50
|
+
/** Explicit server-origin override; otherwise the configured API transport owns it. */
|
|
49
51
|
origin?: string;
|
|
50
52
|
/** WebSocket reconnect backoff ceiling (ms). */
|
|
51
53
|
maxBackoffMs?: number;
|
|
@@ -67,12 +69,15 @@ interface ActiveCall {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
const CANDIDATE_FLUSH_MS = 200;
|
|
72
|
+
const MAX_PENDING_SOCKET_FRAMES = 64;
|
|
70
73
|
|
|
71
74
|
export class CallClient {
|
|
72
75
|
private ws: WebSocket | null = null;
|
|
73
76
|
private wantConnected = false;
|
|
74
77
|
private backoff = 500;
|
|
75
78
|
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
79
|
+
private openingSocket = false;
|
|
80
|
+
private readonly pendingFrames: ClientToHubFrame[] = [];
|
|
76
81
|
private readonly listeners = new Map<keyof CallClientEvents, Set<Listener>>();
|
|
77
82
|
private call: ActiveCall | null = null;
|
|
78
83
|
private localStream: MediaStream | null = null;
|
|
@@ -114,16 +119,24 @@ export class CallClient {
|
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
// --- connection -----------------------------------------------------------
|
|
117
|
-
private
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
(typeof location !== "undefined" ? location.origin : "")
|
|
121
|
-
);
|
|
122
|
+
private apiUrl(path: string): string {
|
|
123
|
+
const origin = this.options.origin;
|
|
124
|
+
return origin ? new URL(path, `${origin}/`).toString() : path;
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
private socketUrl(): string {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
+
private socketUrl(actorApId: string, ticket: string): string {
|
|
128
|
+
const transport = getYurucommuApiTransport();
|
|
129
|
+
const resolved = transport.resolveUrl(this.apiUrl("/api/rtc/socket"));
|
|
130
|
+
const base =
|
|
131
|
+
this.options.origin ??
|
|
132
|
+
(typeof location !== "undefined" ? location.origin : "http://localhost");
|
|
133
|
+
const url = new URL(resolved, base);
|
|
134
|
+
if (url.protocol === "http:") url.protocol = "ws:";
|
|
135
|
+
else if (url.protocol === "https:") url.protocol = "wss:";
|
|
136
|
+
else throw new Error("call socket URL must use HTTP(S)");
|
|
137
|
+
url.searchParams.set("actor", actorApId);
|
|
138
|
+
url.searchParams.set("ticket", ticket);
|
|
139
|
+
return url.toString();
|
|
127
140
|
}
|
|
128
141
|
|
|
129
142
|
connect(): void {
|
|
@@ -137,13 +150,46 @@ export class CallClient {
|
|
|
137
150
|
this.reconnectTimer = null;
|
|
138
151
|
this.ws?.close();
|
|
139
152
|
this.ws = null;
|
|
153
|
+
this.pendingFrames.length = 0;
|
|
140
154
|
}
|
|
141
155
|
|
|
142
156
|
private openSocket(): void {
|
|
143
157
|
if (this.ws && this.ws.readyState <= WebSocket.OPEN) return;
|
|
158
|
+
if (this.openingSocket) return;
|
|
159
|
+
this.openingSocket = true;
|
|
160
|
+
void this.openSocketWithTicket().finally(() => {
|
|
161
|
+
this.openingSocket = false;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private async openSocketWithTicket(): Promise<void> {
|
|
166
|
+
let actorApId: string;
|
|
167
|
+
let ticket: string;
|
|
168
|
+
try {
|
|
169
|
+
const response = await apiPost(this.apiUrl("/api/rtc/ticket"));
|
|
170
|
+
if (!response.ok) throw new Error(`ticket ${response.status}`);
|
|
171
|
+
const body = (await response.json()) as {
|
|
172
|
+
actor_ap_id?: unknown;
|
|
173
|
+
ticket?: unknown;
|
|
174
|
+
};
|
|
175
|
+
if (
|
|
176
|
+
typeof body.actor_ap_id !== "string" ||
|
|
177
|
+
typeof body.ticket !== "string"
|
|
178
|
+
) {
|
|
179
|
+
throw new Error("bad ticket response");
|
|
180
|
+
}
|
|
181
|
+
actorApId = body.actor_ap_id;
|
|
182
|
+
ticket = body.ticket;
|
|
183
|
+
} catch (err) {
|
|
184
|
+
this.emit("error", "socket_ticket_failed", String(err));
|
|
185
|
+
this.scheduleReconnect();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!this.wantConnected) return;
|
|
189
|
+
|
|
144
190
|
let socket: WebSocket;
|
|
145
191
|
try {
|
|
146
|
-
socket = new WebSocket(this.socketUrl());
|
|
192
|
+
socket = new WebSocket(this.socketUrl(actorApId, ticket));
|
|
147
193
|
} catch (err) {
|
|
148
194
|
this.emit("error", "socket_open_failed", String(err));
|
|
149
195
|
this.scheduleReconnect();
|
|
@@ -152,8 +198,22 @@ export class CallClient {
|
|
|
152
198
|
this.ws = socket;
|
|
153
199
|
socket.onopen = () => {
|
|
154
200
|
this.backoff = 500;
|
|
155
|
-
|
|
156
|
-
|
|
201
|
+
socket.send(JSON.stringify({ t: "hello" } satisfies ClientToHubFrame));
|
|
202
|
+
const pending = this.pendingFrames.splice(0);
|
|
203
|
+
const hasInitialInvite =
|
|
204
|
+
this.call !== null &&
|
|
205
|
+
pending.some(
|
|
206
|
+
(frame) => frame.t === "invite" && frame.callId === this.call?.callId,
|
|
207
|
+
);
|
|
208
|
+
if (this.call && !hasInitialInvite) {
|
|
209
|
+
socket.send(
|
|
210
|
+
JSON.stringify({
|
|
211
|
+
t: "resume",
|
|
212
|
+
callId: this.call.callId,
|
|
213
|
+
} satisfies ClientToHubFrame),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
for (const frame of pending) socket.send(JSON.stringify(frame));
|
|
157
217
|
};
|
|
158
218
|
socket.onmessage = (ev) => {
|
|
159
219
|
void this.onFrame(ev.data);
|
|
@@ -181,7 +241,14 @@ export class CallClient {
|
|
|
181
241
|
private send(frame: ClientToHubFrame): void {
|
|
182
242
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
183
243
|
this.ws.send(JSON.stringify(frame));
|
|
244
|
+
return;
|
|
184
245
|
}
|
|
246
|
+
if (!this.wantConnected) return;
|
|
247
|
+
if (this.pendingFrames.length >= MAX_PENDING_SOCKET_FRAMES) {
|
|
248
|
+
this.emit("error", "socket_queue_full");
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
this.pendingFrames.push(frame);
|
|
185
252
|
}
|
|
186
253
|
|
|
187
254
|
// --- public call control --------------------------------------------------
|
|
@@ -189,11 +256,9 @@ export class CallClient {
|
|
|
189
256
|
/** Place an outgoing call. Fetches a callId + ICE, then rings the peer. */
|
|
190
257
|
async startCall(peer: string, media: CallMediaKind): Promise<void> {
|
|
191
258
|
if (this.call) throw new Error("already in a call");
|
|
192
|
-
const res = await
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
headers: { "Content-Type": "application/json" },
|
|
196
|
-
body: JSON.stringify({ to: peer, media }),
|
|
259
|
+
const res = await apiPost(this.apiUrl("/api/rtc/calls"), {
|
|
260
|
+
to: peer,
|
|
261
|
+
media,
|
|
197
262
|
});
|
|
198
263
|
if (!res.ok) {
|
|
199
264
|
const code = res.status === 403 ? "blocked" : "start_failed";
|
|
@@ -204,18 +269,19 @@ export class CallClient {
|
|
|
204
269
|
callId: string;
|
|
205
270
|
iceServers: IceServerConfig[];
|
|
206
271
|
};
|
|
207
|
-
|
|
272
|
+
const call = this.newCall(
|
|
208
273
|
data.callId,
|
|
209
274
|
peer,
|
|
210
275
|
media,
|
|
211
276
|
"caller",
|
|
212
277
|
data.iceServers,
|
|
213
278
|
);
|
|
279
|
+
this.call = call;
|
|
214
280
|
this.setState("calling");
|
|
215
281
|
this.connect();
|
|
216
282
|
this.send({ t: "invite", callId: data.callId, to: peer, media });
|
|
217
|
-
await this.setupMedia(
|
|
218
|
-
await this.makeOffer(
|
|
283
|
+
if (!(await this.setupMedia(call))) return;
|
|
284
|
+
await this.makeOffer(call);
|
|
219
285
|
}
|
|
220
286
|
|
|
221
287
|
/** Accept the current incoming call. */
|
|
@@ -224,11 +290,14 @@ export class CallClient {
|
|
|
224
290
|
if (!call || call.role !== "callee") return;
|
|
225
291
|
this.setState("connecting");
|
|
226
292
|
this.send({ t: "accept", callId: call.callId });
|
|
227
|
-
await this.setupMedia(call);
|
|
293
|
+
if (!(await this.setupMedia(call))) return;
|
|
228
294
|
// The offer was already applied on arrival; create + send the answer.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
await
|
|
295
|
+
const pc = call.pc;
|
|
296
|
+
if (pc && call.remoteDescriptionSet && this.isActiveCall(call)) {
|
|
297
|
+
const answer = await pc.createAnswer();
|
|
298
|
+
if (!this.isActivePeer(call, pc)) return;
|
|
299
|
+
await pc.setLocalDescription(answer);
|
|
300
|
+
if (!this.isActivePeer(call, pc)) return;
|
|
232
301
|
this.send({ t: "answer", callId: call.callId, sdp: answer.sdp ?? "" });
|
|
233
302
|
}
|
|
234
303
|
}
|
|
@@ -412,24 +481,45 @@ export class CallClient {
|
|
|
412
481
|
}
|
|
413
482
|
|
|
414
483
|
// --- media + peer connection ---------------------------------------------
|
|
415
|
-
private
|
|
484
|
+
private isActiveCall(call: ActiveCall): boolean {
|
|
485
|
+
return this.call === call;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private isActivePeer(call: ActiveCall, pc: RTCPeerConnection): boolean {
|
|
489
|
+
return this.isActiveCall(call) && call.pc === pc;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private async setupMedia(call: ActiveCall): Promise<boolean> {
|
|
493
|
+
if (!this.isActiveCall(call)) return false;
|
|
416
494
|
if (!this.localStream) {
|
|
495
|
+
let stream: MediaStream;
|
|
417
496
|
try {
|
|
418
|
-
|
|
497
|
+
stream = await navigator.mediaDevices.getUserMedia({
|
|
419
498
|
audio: call.media.audio,
|
|
420
499
|
video: call.media.video,
|
|
421
500
|
});
|
|
422
501
|
} catch (err) {
|
|
502
|
+
if (!this.isActiveCall(call)) return false;
|
|
423
503
|
this.emit("error", "media_denied", String(err));
|
|
424
504
|
this.hangup("media_denied");
|
|
425
|
-
return;
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
if (!this.isActiveCall(call)) {
|
|
508
|
+
// Permission prompts resolve asynchronously. If the user cancelled or
|
|
509
|
+
// another call replaced this one while the prompt was open, the newly
|
|
510
|
+
// acquired tracks have no owner and must be stopped immediately.
|
|
511
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
512
|
+
return false;
|
|
426
513
|
}
|
|
514
|
+
this.localStream = stream;
|
|
427
515
|
this.emit("localstream", this.localStream);
|
|
428
516
|
}
|
|
517
|
+
if (!this.isActiveCall(call)) return false;
|
|
429
518
|
if (!call.pc) this.buildPeerConnection(call);
|
|
430
519
|
for (const track of this.localStream.getTracks()) {
|
|
431
520
|
call.pc?.addTrack(track, this.localStream);
|
|
432
521
|
}
|
|
522
|
+
return this.isActiveCall(call);
|
|
433
523
|
}
|
|
434
524
|
|
|
435
525
|
private buildPeerConnection(call: ActiveCall): void {
|
|
@@ -441,13 +531,16 @@ export class CallClient {
|
|
|
441
531
|
})),
|
|
442
532
|
});
|
|
443
533
|
pc.onicecandidate = (ev) => {
|
|
534
|
+
if (!this.isActivePeer(call, pc)) return;
|
|
444
535
|
if (ev.candidate) this.bufferCandidate(call, ev.candidate.toJSON());
|
|
445
536
|
else this.flushCandidates(call);
|
|
446
537
|
};
|
|
447
538
|
pc.ontrack = (ev) => {
|
|
539
|
+
if (!this.isActivePeer(call, pc)) return;
|
|
448
540
|
this.emit("remotestream", ev.streams[0] ?? null);
|
|
449
541
|
};
|
|
450
542
|
pc.onconnectionstatechange = () => {
|
|
543
|
+
if (!this.isActivePeer(call, pc)) return;
|
|
451
544
|
if (pc.connectionState === "connected") this.setState("connected");
|
|
452
545
|
else if (
|
|
453
546
|
pc.connectionState === "failed" ||
|
|
@@ -460,9 +553,14 @@ export class CallClient {
|
|
|
460
553
|
}
|
|
461
554
|
|
|
462
555
|
private async makeOffer(call: ActiveCall): Promise<void> {
|
|
556
|
+
if (!this.isActiveCall(call)) return;
|
|
463
557
|
if (!call.pc) this.buildPeerConnection(call);
|
|
464
|
-
const
|
|
465
|
-
|
|
558
|
+
const pc = call.pc;
|
|
559
|
+
if (!pc) return;
|
|
560
|
+
const offer = await pc.createOffer();
|
|
561
|
+
if (!this.isActivePeer(call, pc)) return;
|
|
562
|
+
await pc.setLocalDescription(offer);
|
|
563
|
+
if (!this.isActivePeer(call, pc)) return;
|
|
466
564
|
this.send({ t: "offer", callId: call.callId, sdp: offer.sdp ?? "" });
|
|
467
565
|
}
|
|
468
566
|
|
package/src/backend/index.ts
CHANGED
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
import { logger } from "./lib/logger.ts";
|
|
48
48
|
|
|
49
49
|
const log = logger.child({ component: "backend.index" });
|
|
50
|
-
let
|
|
50
|
+
let lastOutboxRecoverySweep = 0;
|
|
51
51
|
import type { IQueueBatch } from "./runtime/queue.ts";
|
|
52
52
|
import type {
|
|
53
53
|
D1Database,
|
|
@@ -56,16 +56,21 @@ import type {
|
|
|
56
56
|
MessageBatch,
|
|
57
57
|
Queue,
|
|
58
58
|
R2Bucket,
|
|
59
|
+
ScheduledController,
|
|
59
60
|
} from "@cloudflare/workers-types";
|
|
60
61
|
import type {
|
|
61
62
|
DeliveryDlqMessageV1,
|
|
62
63
|
DeliveryQueueMessageV1,
|
|
63
64
|
} from "./lib/delivery/types.ts";
|
|
64
65
|
import {
|
|
66
|
+
enqueuePendingDeliveryEndpointJobs,
|
|
65
67
|
handleDeliveryDlqBatch,
|
|
66
68
|
handleDeliveryQueueBatch,
|
|
67
69
|
} from "./lib/delivery/queue.ts";
|
|
70
|
+
import { enqueuePendingDeliveryResolutionJobs } from "./lib/delivery/resolution-outbox.ts";
|
|
71
|
+
import { enqueuePendingDeliveryFanoutJobs } from "./lib/delivery/fanout-outbox.ts";
|
|
68
72
|
import { enqueuePendingNotificationPushJobs } from "./lib/notification-push.ts";
|
|
73
|
+
import { runYurucommuRetention } from "./retention.ts";
|
|
69
74
|
|
|
70
75
|
type YurucommuApp = Hono<{ Bindings: Env; Variables: Variables }>;
|
|
71
76
|
|
|
@@ -618,10 +623,20 @@ function applyGlobalMiddleware(
|
|
|
618
623
|
const method = c.req.method.toUpperCase();
|
|
619
624
|
const now = Date.now();
|
|
620
625
|
const mutating = !["GET", "HEAD", "OPTIONS"].includes(method);
|
|
621
|
-
const recoveryDue = now -
|
|
626
|
+
const recoveryDue = now - lastOutboxRecoverySweep >= 60_000;
|
|
622
627
|
if (mutating || recoveryDue) {
|
|
623
|
-
if (recoveryDue)
|
|
628
|
+
if (recoveryDue) lastOutboxRecoverySweep = now;
|
|
624
629
|
const sweep = (async () => {
|
|
630
|
+
try {
|
|
631
|
+
await enqueuePendingDeliveryFanoutJobs(c.env);
|
|
632
|
+
await enqueuePendingDeliveryEndpointJobs(c.env);
|
|
633
|
+
await enqueuePendingDeliveryResolutionJobs(c.env);
|
|
634
|
+
} catch (error) {
|
|
635
|
+
log.error("Failed to enqueue durable federation outbox", {
|
|
636
|
+
event: "delivery.outbox.enqueue_failed",
|
|
637
|
+
error,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
625
640
|
try {
|
|
626
641
|
await enqueuePendingNotificationPushJobs(c.env);
|
|
627
642
|
} catch (error) {
|
|
@@ -994,6 +1009,12 @@ type WorkerBindings = EnvVars & {
|
|
|
994
1009
|
REALTIME_STREAM?: DurableObjectNamespace;
|
|
995
1010
|
};
|
|
996
1011
|
|
|
1012
|
+
function isMaterializedRuntimeEnv(
|
|
1013
|
+
bindings: WorkerBindings | Env,
|
|
1014
|
+
): bindings is Env {
|
|
1015
|
+
return "DB_INSTANCE" in bindings && !!bindings.DB_INSTANCE;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
997
1018
|
export default {
|
|
998
1019
|
async fetch(
|
|
999
1020
|
request: Request,
|
|
@@ -1012,4 +1033,15 @@ export default {
|
|
|
1012
1033
|
wrapCloudflareBindings(bindings),
|
|
1013
1034
|
);
|
|
1014
1035
|
},
|
|
1036
|
+
|
|
1037
|
+
async scheduled(
|
|
1038
|
+
_controller: ScheduledController,
|
|
1039
|
+
bindings: WorkerBindings | Env,
|
|
1040
|
+
_ctx: ExecutionContext,
|
|
1041
|
+
): Promise<void> {
|
|
1042
|
+
const env = isMaterializedRuntimeEnv(bindings)
|
|
1043
|
+
? bindings
|
|
1044
|
+
: wrapCloudflareBindings(bindings);
|
|
1045
|
+
await runYurucommuRetention(env);
|
|
1046
|
+
},
|
|
1015
1047
|
};
|