@takosjp/yurucommu-core 3.0.0
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/LICENSE +16 -0
- package/README.md +82 -0
- package/migrations/0001_init.sql +495 -0
- package/migrations/0002_social_remote_actor_edges.sql +92 -0
- package/migrations/0003_activity_remote_object_edges.sql +68 -0
- package/migrations/0004_blocklist.sql +26 -0
- package/migrations/0005_story_community_scope.sql +13 -0
- package/migrations/0006_dm_community_read_status.sql +19 -0
- package/migrations/0007_moderation_reports.sql +22 -0
- package/migrations/0008_actor_fields_aka.sql +18 -0
- package/migrations/0009_object_tags.sql +13 -0
- package/migrations/0010_object_recipients_drop_actor_fk.sql +34 -0
- package/migrations/0011_drop_remote_actor_fks.sql +205 -0
- package/migrations/0012_objects_content_fts.sql +39 -0
- package/migrations/0013_efficiency_indexes.sql +13 -0
- package/migrations/0014_inbox_actor_created_idx.sql +15 -0
- package/migrations/0015_community_bans.sql +16 -0
- package/migrations/0016_namespace_takos_oidc_subject.sql +19 -0
- package/migrations/0017_mobile_push_registrations.sql +22 -0
- package/migrations/README.md +122 -0
- package/package.json +75 -0
- package/packages/api/LICENSE +16 -0
- package/packages/api/package.json +30 -0
- package/packages/api/src/index.ts +4 -0
- package/packages/api/src/lib/api/account.ts +20 -0
- package/packages/api/src/lib/api/actors.ts +149 -0
- package/packages/api/src/lib/api/auth.ts +46 -0
- package/packages/api/src/lib/api/communities.ts +329 -0
- package/packages/api/src/lib/api/dm.test.ts +67 -0
- package/packages/api/src/lib/api/dm.ts +236 -0
- package/packages/api/src/lib/api/fetch.ts +111 -0
- package/packages/api/src/lib/api/follow.ts +30 -0
- package/packages/api/src/lib/api/media.ts +100 -0
- package/packages/api/src/lib/api/moderation.ts +98 -0
- package/packages/api/src/lib/api/normalize.ts +71 -0
- package/packages/api/src/lib/api/notifications.test.ts +63 -0
- package/packages/api/src/lib/api/notifications.ts +61 -0
- package/packages/api/src/lib/api/posts.test.ts +110 -0
- package/packages/api/src/lib/api/posts.ts +181 -0
- package/packages/api/src/lib/api/recommendations.ts +22 -0
- package/packages/api/src/lib/api/search.ts +88 -0
- package/packages/api/src/lib/api/stories.ts +80 -0
- package/packages/api/src/lib/api.ts +15 -0
- package/packages/api/src/lib/fetch-with-timeout.ts +42 -0
- package/packages/api/src/lib/transport.ts +40 -0
- package/packages/api/src/social-server.ts +47 -0
- package/packages/api/src/types/index.ts +185 -0
- package/scripts/apply-takosumi-migrations.ts +621 -0
- package/src/backend/federation-helpers.ts +36 -0
- package/src/backend/index.ts +872 -0
- package/src/backend/lib/account-migration.ts +106 -0
- package/src/backend/lib/activitypub-actor-cache.ts +238 -0
- package/src/backend/lib/activitypub-helpers.ts +131 -0
- package/src/backend/lib/activitypub-validators.ts +323 -0
- package/src/backend/lib/ap-context.ts +16 -0
- package/src/backend/lib/ap-ids.ts +101 -0
- package/src/backend/lib/ap-response.ts +30 -0
- package/src/backend/lib/ap-signing.ts +87 -0
- package/src/backend/lib/ap-verify.ts +670 -0
- package/src/backend/lib/auth-lockout.ts +230 -0
- package/src/backend/lib/backend-paths.ts +34 -0
- package/src/backend/lib/base64.ts +30 -0
- package/src/backend/lib/blocklist-purge.ts +109 -0
- package/src/backend/lib/blocklist.ts +279 -0
- package/src/backend/lib/chunk.ts +33 -0
- package/src/backend/lib/client-ip.ts +169 -0
- package/src/backend/lib/community-visibility.ts +230 -0
- package/src/backend/lib/crypto.ts +424 -0
- package/src/backend/lib/delivery/circuit.ts +265 -0
- package/src/backend/lib/delivery/metrics.ts +30 -0
- package/src/backend/lib/delivery/planner.ts +190 -0
- package/src/backend/lib/delivery/queue-batching.ts +626 -0
- package/src/backend/lib/delivery/queue-delivery.ts +641 -0
- package/src/backend/lib/delivery/queue.ts +576 -0
- package/src/backend/lib/delivery/transformers.ts +56 -0
- package/src/backend/lib/delivery/types.ts +139 -0
- package/src/backend/lib/errors.ts +114 -0
- package/src/backend/lib/federation-fetch.ts +296 -0
- package/src/backend/lib/feed-cursor.ts +57 -0
- package/src/backend/lib/feed-exclude.ts +48 -0
- package/src/backend/lib/hex.ts +8 -0
- package/src/backend/lib/log-mask.ts +213 -0
- package/src/backend/lib/logger.ts +285 -0
- package/src/backend/lib/mobile-contract.ts +137 -0
- package/src/backend/lib/oauth-providers.ts +324 -0
- package/src/backend/lib/oauth-utils.ts +148 -0
- package/src/backend/lib/oidc-id-token.ts +151 -0
- package/src/backend/lib/parse-helpers.ts +31 -0
- package/src/backend/lib/post-visibility.ts +190 -0
- package/src/backend/lib/session-actor.ts +61 -0
- package/src/backend/lib/ssrf.ts +428 -0
- package/src/backend/lib/strip-image-metadata.ts +191 -0
- package/src/backend/middleware/bearer-auth.ts +70 -0
- package/src/backend/middleware/body-limit.ts +212 -0
- package/src/backend/middleware/cache.ts +429 -0
- package/src/backend/middleware/csrf.ts +130 -0
- package/src/backend/middleware/error-handler.ts +77 -0
- package/src/backend/middleware/rate-limit.ts +308 -0
- package/src/backend/public.ts +21 -0
- package/src/backend/routes/account-teardown.ts +430 -0
- package/src/backend/routes/activitypub/handlers/actor-inbox-handlers.ts +354 -0
- package/src/backend/routes/activitypub/handlers/inbound-timestamp.ts +29 -0
- package/src/backend/routes/activitypub/handlers/inbox-content-handlers.ts +1634 -0
- package/src/backend/routes/activitypub/handlers/inbox-follow-handlers.ts +547 -0
- package/src/backend/routes/activitypub/handlers/inbox-interaction-handlers.ts +497 -0
- package/src/backend/routes/activitypub/handlers/inbox-shared-helpers.ts +262 -0
- package/src/backend/routes/activitypub/handlers/user-inbox-handlers.ts +35 -0
- package/src/backend/routes/activitypub/inbox-types.ts +74 -0
- package/src/backend/routes/activitypub/inbox.ts +1191 -0
- package/src/backend/routes/activitypub/outbox.ts +0 -0
- package/src/backend/routes/activitypub/query-helpers.ts +227 -0
- package/src/backend/routes/activitypub.ts +616 -0
- package/src/backend/routes/actors-helpers.ts +487 -0
- package/src/backend/routes/actors.ts +1311 -0
- package/src/backend/routes/apps.ts +313 -0
- package/src/backend/routes/auth-helpers.ts +566 -0
- package/src/backend/routes/auth.ts +615 -0
- package/src/backend/routes/communities/membership-invites.ts +208 -0
- package/src/backend/routes/communities/membership-join.ts +335 -0
- package/src/backend/routes/communities/membership-members.ts +539 -0
- package/src/backend/routes/communities/membership-requests.ts +296 -0
- package/src/backend/routes/communities/membership-shared.ts +364 -0
- package/src/backend/routes/communities/messages.ts +479 -0
- package/src/backend/routes/communities/routes.ts +624 -0
- package/src/backend/routes/communities.ts +21 -0
- package/src/backend/routes/dm/contacts.ts +525 -0
- package/src/backend/routes/dm/conversations-helpers.ts +197 -0
- package/src/backend/routes/dm/conversations.ts +25 -0
- package/src/backend/routes/dm/messages.ts +658 -0
- package/src/backend/routes/dm/query-helpers.ts +85 -0
- package/src/backend/routes/dm/read-archive.ts +228 -0
- package/src/backend/routes/dm/requests.ts +222 -0
- package/src/backend/routes/dm/typing.ts +81 -0
- package/src/backend/routes/dm.ts +15 -0
- package/src/backend/routes/follow-helpers.ts +370 -0
- package/src/backend/routes/follow.ts +588 -0
- package/src/backend/routes/media.ts +692 -0
- package/src/backend/routes/mobile.ts +159 -0
- package/src/backend/routes/moderation.ts +373 -0
- package/src/backend/routes/notifications.ts +757 -0
- package/src/backend/routes/posts/delete-cascade.ts +330 -0
- package/src/backend/routes/posts/interactions.ts +795 -0
- package/src/backend/routes/posts/post-helpers.ts +847 -0
- package/src/backend/routes/posts/queries.ts +537 -0
- package/src/backend/routes/posts/routes.ts +865 -0
- package/src/backend/routes/posts/transformers.ts +161 -0
- package/src/backend/routes/posts.ts +17 -0
- package/src/backend/routes/recommendations.ts +88 -0
- package/src/backend/routes/search.ts +730 -0
- package/src/backend/routes/stories/interactions.ts +576 -0
- package/src/backend/routes/stories/query-helpers.ts +482 -0
- package/src/backend/routes/stories/routes.ts +906 -0
- package/src/backend/routes/stories.ts +13 -0
- package/src/backend/routes/takos-tools/dm.ts +249 -0
- package/src/backend/routes/takos-tools/follows.ts +225 -0
- package/src/backend/routes/takos-tools/posts.ts +292 -0
- package/src/backend/routes/takos-tools/search.ts +228 -0
- package/src/backend/routes/takos-tools/timeline.ts +132 -0
- package/src/backend/routes/takos-tools/types.ts +10 -0
- package/src/backend/routes/takos-tools-response.ts +178 -0
- package/src/backend/routes/takos-tools.ts +153 -0
- package/src/backend/routes/timeline.ts +755 -0
- package/src/backend/runtime/bun.ts +620 -0
- package/src/backend/runtime/cloudflare.ts +202 -0
- package/src/backend/runtime/compat-bun/types.ts +44 -0
- package/src/backend/runtime/memory-kv.ts +104 -0
- package/src/backend/runtime/shared.ts +142 -0
- package/src/backend/runtime/types.ts +205 -0
- package/src/backend/server.ts +636 -0
- package/src/backend/types.ts +143 -0
- package/src/db/index.ts +97 -0
- package/src/db/schema/actors.ts +129 -0
- package/src/db/schema/communities.ts +133 -0
- package/src/db/schema/date-utils.ts +17 -0
- package/src/db/schema/index.ts +17 -0
- package/src/db/schema/messaging.ts +241 -0
- package/src/db/schema/mobile.ts +37 -0
- package/src/db/schema/posts.ts +150 -0
- package/src/db/schema/relations.ts +266 -0
- package/src/db/schema/reports.ts +33 -0
- package/src/db/schema/social.ts +106 -0
- package/src/db/schema/stories.ts +70 -0
- package/src/db/schema.ts +15 -0
- package/src/plugin/public.ts +7 -0
- package/src/runtime/site-worker.ts +10 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { logger } from "../logger.ts";
|
|
2
|
+
|
|
3
|
+
type MetricPayload = {
|
|
4
|
+
metric: string;
|
|
5
|
+
value: number;
|
|
6
|
+
at: string;
|
|
7
|
+
tags?: Record<string, string | number | boolean | null | undefined>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const log = logger.child({ component: "delivery.metrics" });
|
|
11
|
+
|
|
12
|
+
export function emitMetric(
|
|
13
|
+
metric: string,
|
|
14
|
+
value: number,
|
|
15
|
+
tags?: MetricPayload["tags"],
|
|
16
|
+
): void {
|
|
17
|
+
const payload: MetricPayload = {
|
|
18
|
+
metric,
|
|
19
|
+
value,
|
|
20
|
+
at: new Date().toISOString(),
|
|
21
|
+
tags,
|
|
22
|
+
};
|
|
23
|
+
// Logs are the portable, zero-dependency metrics channel across Workers + local runtimes.
|
|
24
|
+
// Downstream (Cloudflare / log shipper) can derive p50/p95/p99 and rates from these points.
|
|
25
|
+
log.info("metric", {
|
|
26
|
+
event: "delivery.metric",
|
|
27
|
+
type: "metric",
|
|
28
|
+
...payload,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { Database } from "../../../db/index.ts";
|
|
2
|
+
import { inArray } from "drizzle-orm";
|
|
3
|
+
import { actorCache } from "../../../db/index.ts";
|
|
4
|
+
import { emitMetric } from "./metrics.ts";
|
|
5
|
+
import {
|
|
6
|
+
DELIVERY_ENDPOINT_CACHE_TTL_MS,
|
|
7
|
+
safeParseIsoTimeMs,
|
|
8
|
+
} from "./transformers.ts";
|
|
9
|
+
import { isSafeRemoteUrl } from "../../federation-helpers.ts";
|
|
10
|
+
import { filterBlockedActorApIds } from "../blocklist.ts";
|
|
11
|
+
|
|
12
|
+
export type PlannedEndpointGroup = {
|
|
13
|
+
endpoint: string;
|
|
14
|
+
recipientCount: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type PlanEndpointsResult = {
|
|
18
|
+
groups: PlannedEndpointGroup[];
|
|
19
|
+
unknownRecipients: string[];
|
|
20
|
+
totalRecipients: number;
|
|
21
|
+
sharedInboxRecipients: number;
|
|
22
|
+
/**
|
|
23
|
+
* Recipients dropped because the operator has blocked their actor AP-ID or
|
|
24
|
+
* hostname. These receive no outbound delivery and no resolve_actor enqueue
|
|
25
|
+
* (mirrors the inbound blocklist enforcement in the inbox handler).
|
|
26
|
+
*/
|
|
27
|
+
blockedRecipients: string[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type ActorCacheRow = {
|
|
31
|
+
apId: string;
|
|
32
|
+
inbox: string;
|
|
33
|
+
sharedInbox: string | null;
|
|
34
|
+
lastFetchedAt: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Max ids per IN(...) lookup. Cloudflare D1 caps a query at 100 bound
|
|
38
|
+
// parameters (libsql/better-sqlite3 — what the tests run on — allow ~32k, so an
|
|
39
|
+
// over-large chunk passes CI but throws "too many SQL variables" on production
|
|
40
|
+
// D1). Each chunk element binds one parameter, so keep this <=90: a large
|
|
41
|
+
// recipient set is loaded in chunks rather than throwing.
|
|
42
|
+
const PLANNER_IN_CHUNK = 90;
|
|
43
|
+
|
|
44
|
+
function isActorCacheFresh(row: ActorCacheRow, nowMs: number): boolean {
|
|
45
|
+
const lastFetched = safeParseIsoTimeMs(row.lastFetchedAt);
|
|
46
|
+
if (lastFetched === null) return false;
|
|
47
|
+
return nowMs - lastFetched < DELIVERY_ENDPOINT_CACHE_TTL_MS;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function chooseEndpoint(
|
|
51
|
+
row: ActorCacheRow,
|
|
52
|
+
): { endpoint: string; usedSharedInbox: boolean } | null {
|
|
53
|
+
const shared = row.sharedInbox;
|
|
54
|
+
if (shared && isSafeRemoteUrl(shared)) {
|
|
55
|
+
return { endpoint: shared, usedSharedInbox: true };
|
|
56
|
+
}
|
|
57
|
+
if (row.inbox && isSafeRemoteUrl(row.inbox)) {
|
|
58
|
+
return { endpoint: row.inbox, usedSharedInbox: false };
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function planEndpointsFromActorCache(
|
|
64
|
+
db: Database,
|
|
65
|
+
recipientActorApIds: string[],
|
|
66
|
+
options?: {
|
|
67
|
+
metricTags?: Record<string, string | number | boolean | null | undefined>;
|
|
68
|
+
},
|
|
69
|
+
): Promise<PlanEndpointsResult> {
|
|
70
|
+
const nowMs = Date.now();
|
|
71
|
+
const totalRecipients = recipientActorApIds.length;
|
|
72
|
+
if (totalRecipients === 0) {
|
|
73
|
+
return {
|
|
74
|
+
groups: [],
|
|
75
|
+
unknownRecipients: [],
|
|
76
|
+
totalRecipients: 0,
|
|
77
|
+
sharedInboxRecipients: 0,
|
|
78
|
+
blockedRecipients: [],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Enforce the operator blocklist on the OUTBOUND side too: drop any
|
|
83
|
+
// recipient whose actor AP-ID or hostname is defederated BEFORE grouping
|
|
84
|
+
// endpoints, so a blocked domain/actor never receives our posts or DMs.
|
|
85
|
+
// Uses the same isActorBlocked check (which also covers the hostname
|
|
86
|
+
// transitively) that the inbox handler uses inbound.
|
|
87
|
+
const allowedRecipients: string[] = [];
|
|
88
|
+
const blockedRecipients: string[] = [];
|
|
89
|
+
// Batched blocklist filter (2 queries) instead of a serial isActorBlocked
|
|
90
|
+
// per recipient (2 queries each → up to ~400 round-trips per fan-out page).
|
|
91
|
+
const blockedSet = await filterBlockedActorApIds(db, recipientActorApIds);
|
|
92
|
+
for (const apId of recipientActorApIds) {
|
|
93
|
+
if (blockedSet.has(apId)) {
|
|
94
|
+
blockedRecipients.push(apId);
|
|
95
|
+
} else {
|
|
96
|
+
allowedRecipients.push(apId);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// After blocklist filtering, nothing left to plan.
|
|
101
|
+
if (allowedRecipients.length === 0) {
|
|
102
|
+
emitMetric("delivery_shared_inbox_aggregation_ratio", 0, {
|
|
103
|
+
total_recipients: totalRecipients,
|
|
104
|
+
shared_inbox_recipients: 0,
|
|
105
|
+
endpoints: 0,
|
|
106
|
+
unknown_recipients: 0,
|
|
107
|
+
blocked_recipients: blockedRecipients.length,
|
|
108
|
+
...(options?.metricTags ?? {}),
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
groups: [],
|
|
112
|
+
unknownRecipients: [],
|
|
113
|
+
totalRecipients,
|
|
114
|
+
sharedInboxRecipients: 0,
|
|
115
|
+
blockedRecipients,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Batch-load actor_cache for the recipient set, chunked so a large fan-out
|
|
120
|
+
// (a big community's whole remote audience) can't exceed SQLite's bound-
|
|
121
|
+
// parameter ceiling and throw — an un-acked throw here would poison the fanout
|
|
122
|
+
// queue message into an infinite retry loop.
|
|
123
|
+
const rows: ActorCacheRow[] = [];
|
|
124
|
+
for (let i = 0; i < allowedRecipients.length; i += PLANNER_IN_CHUNK) {
|
|
125
|
+
const chunkRows = await db
|
|
126
|
+
.select({
|
|
127
|
+
apId: actorCache.apId,
|
|
128
|
+
inbox: actorCache.inbox,
|
|
129
|
+
sharedInbox: actorCache.sharedInbox,
|
|
130
|
+
lastFetchedAt: actorCache.lastFetchedAt,
|
|
131
|
+
})
|
|
132
|
+
.from(actorCache)
|
|
133
|
+
.where(
|
|
134
|
+
inArray(
|
|
135
|
+
actorCache.apId,
|
|
136
|
+
allowedRecipients.slice(i, i + PLANNER_IN_CHUNK),
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
rows.push(...(chunkRows as ActorCacheRow[]));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const byApId = new Map(rows.map((r) => [r.apId, r as ActorCacheRow]));
|
|
143
|
+
const unknownRecipients: string[] = [];
|
|
144
|
+
const endpointCounts = new Map<string, number>();
|
|
145
|
+
let sharedInboxRecipients = 0;
|
|
146
|
+
|
|
147
|
+
for (const apId of allowedRecipients) {
|
|
148
|
+
const row = byApId.get(apId);
|
|
149
|
+
// Treat missing, stale, or unresolvable actors as unknown for resolve_actor jobs.
|
|
150
|
+
const chosen =
|
|
151
|
+
row && isActorCacheFresh(row, nowMs) ? chooseEndpoint(row) : null;
|
|
152
|
+
if (!chosen) {
|
|
153
|
+
unknownRecipients.push(apId);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (chosen.usedSharedInbox) sharedInboxRecipients++;
|
|
158
|
+
endpointCounts.set(
|
|
159
|
+
chosen.endpoint,
|
|
160
|
+
(endpointCounts.get(chosen.endpoint) ?? 0) + 1,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const groups: PlannedEndpointGroup[] = Array.from(
|
|
165
|
+
endpointCounts.entries(),
|
|
166
|
+
).map(([endpoint, recipientCount]) => ({
|
|
167
|
+
endpoint,
|
|
168
|
+
recipientCount,
|
|
169
|
+
}));
|
|
170
|
+
|
|
171
|
+
// Observability: sharedInbox aggregation ratio.
|
|
172
|
+
const ratio =
|
|
173
|
+
totalRecipients > 0 ? sharedInboxRecipients / totalRecipients : 0;
|
|
174
|
+
emitMetric("delivery_shared_inbox_aggregation_ratio", ratio, {
|
|
175
|
+
total_recipients: totalRecipients,
|
|
176
|
+
shared_inbox_recipients: sharedInboxRecipients,
|
|
177
|
+
endpoints: groups.length,
|
|
178
|
+
unknown_recipients: unknownRecipients.length,
|
|
179
|
+
blocked_recipients: blockedRecipients.length,
|
|
180
|
+
...(options?.metricTags ?? {}),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
groups,
|
|
185
|
+
unknownRecipients,
|
|
186
|
+
totalRecipients,
|
|
187
|
+
sharedInboxRecipients,
|
|
188
|
+
blockedRecipients,
|
|
189
|
+
};
|
|
190
|
+
}
|