@geekity/plugin-wordpress 0.0.0-stage → 0.1.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/dist/import.js ADDED
@@ -0,0 +1,461 @@
1
+ import { createPrivateKey, createPublicKey } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { exportJwk } from '@fedify/fedify';
4
+ import { WORDPRESS_ACTIVITYPUB_BASE } from './records.js';
5
+ /**
6
+ * Bringing one person across from the WordPress ActivityPub plugin
7
+ * (decision-14, TASK-71).
8
+ *
9
+ * Three things a site leaving the plugin cannot regenerate, and this is where
10
+ * they arrive: the RSA key pair its followers have cached, the actor id its
11
+ * followers key the account by, and the followers themselves. Everything else
12
+ * about the move — the posts, their ids, the compatibility paths — comes in
13
+ * with the content or is this plugin.
14
+ *
15
+ * Nothing here is a screen, on purpose. A stored actor id is identity for the
16
+ * life of the account (doc-4), and identity is not something a form should be
17
+ * able to retype.
18
+ */
19
+ /** The algorithm the plugin signs with, and the only one it has a key for. */
20
+ const RSA = 'RSASSA-PKCS1-v1_5';
21
+ /**
22
+ * Thrown when the user already has a key pair and this run was not told to
23
+ * replace it.
24
+ *
25
+ * Its own class because the answer is a flag rather than a correction, and
26
+ * because losing a private key is the one mistake this CMS cannot help with:
27
+ * every follower's server has cached the public half, and a new key means none
28
+ * of them can verify this person again.
29
+ */
30
+ export class ExistingKeyPairError extends Error {
31
+ name = 'ExistingKeyPairError';
32
+ /** The file that is already there. */
33
+ file;
34
+ constructor(username, file) {
35
+ super(`${username} already has a different key pair, in ${file}. Importing over it would ` +
36
+ 'change their identity: every follower has cached the public half of the key that is ' +
37
+ 'there, and none of them could verify this person again. Pass --force only if you are ' +
38
+ 'sure the pair being imported is the one the followers hold.');
39
+ this.file = file;
40
+ }
41
+ }
42
+ /** Thrown when the PEM is not an RSA private key at all. */
43
+ export class UnusableKeyPemError extends Error {
44
+ name = 'UnusableKeyPemError';
45
+ }
46
+ /**
47
+ * Bring one WordPress actor's key pair across, and say what changed.
48
+ *
49
+ * Run twice with the same inputs it changes nothing and says so, which is what
50
+ * makes it safe to run again after a follower's server was down.
51
+ */
52
+ export async function importWordPressActor(options) {
53
+ const { site, records, username } = options;
54
+ // Everything that can be refused is decided before anything is written, so a
55
+ // run that is going to stop stops with the disk as it was.
56
+ assertIdentityFree(options);
57
+ const planned = await planKeyImport(options);
58
+ const actorIdChanged = await site.setActorId(username, options.actorId);
59
+ const numberChanged = await records.setNumber(username, options.wordpressActorId);
60
+ const identity = actorIdChanged || numberChanged ? 'set' : 'unchanged';
61
+ const keys = [applyKeyImport(site, username, planned)];
62
+ // The Ed25519 pair is minted here rather than left to the first request,
63
+ // because this is also what proves the file just written imports: Fedify
64
+ // swallows what a key pairs dispatcher throws, so an unusable key found at
65
+ // request time would be found by nobody.
66
+ const hadEd25519 = site.actorKey(username, 'Ed25519').jwk !== undefined;
67
+ await site.loadActorKeys(username);
68
+ keys.push({
69
+ file: site.actorKey(username, 'Ed25519').file,
70
+ algorithm: 'Ed25519',
71
+ state: hadEd25519 ? 'unchanged' : 'generated',
72
+ });
73
+ const followers = await importFollowers(options);
74
+ return {
75
+ username,
76
+ actorId: options.actorId,
77
+ wordpressActorId: options.wordpressActorId,
78
+ identity,
79
+ keys,
80
+ followers,
81
+ changed: identity === 'set' ||
82
+ keys.some((key) => key.state !== 'unchanged') ||
83
+ followers.added.length > 0 ||
84
+ followers.refreshed.length > 0,
85
+ };
86
+ }
87
+ /**
88
+ * Refuse a user the site does not have, and an id or a number somebody else
89
+ * already carries: two accounts answering to one identity is not something to
90
+ * pick a winner for.
91
+ */
92
+ function assertIdentityFree(options) {
93
+ const { site, records, username } = options;
94
+ const users = site.users();
95
+ if (!users.some((user) => user.username === username)) {
96
+ throw new Error(`This site has no user named "${username}".`);
97
+ }
98
+ const byActorId = users.find((user) => user.username !== username && user.actorId === options.actorId);
99
+ if (byActorId !== undefined) {
100
+ throw new Error(`The actor id ${options.actorId} already belongs to "${byActorId.username}".`);
101
+ }
102
+ for (const [other, number] of records.actors()) {
103
+ if (other !== username && number === options.wordpressActorId) {
104
+ throw new Error(`WordPress actor ${String(options.wordpressActorId)} already belongs to "${other}".`);
105
+ }
106
+ }
107
+ }
108
+ /**
109
+ * Bring the followers across: read the collection, work out who each item is,
110
+ * and put them in the user's `followers.json` and the index.
111
+ *
112
+ * The plugin's collection carries nothing but a `first` page, and the page's
113
+ * `orderedItems` are bare actor URLs, so almost every follower here costs one
114
+ * fetch. That is the one step of the import that is really a network
115
+ * operation, which is why it can be pointed at a file instead: an owner who
116
+ * saved the collection before the DNS moved has everything they need offline.
117
+ *
118
+ * The site's `addFollower` is what writes, so a follower already in the file
119
+ * keeps the place and the follow time it has — running this again after an
120
+ * instance came back adds the missing people and moves nobody.
121
+ */
122
+ async function importFollowers(options) {
123
+ const empty = {
124
+ source: undefined,
125
+ added: [],
126
+ unchanged: [],
127
+ refreshed: [],
128
+ failed: [],
129
+ };
130
+ if (options.followers === false)
131
+ return empty;
132
+ const { site, username } = options;
133
+ const source = options.followers ?? followersCollectionUrl(options);
134
+ const added = [];
135
+ const unchanged = [];
136
+ const refreshed = [];
137
+ const failed = [];
138
+ for (const item of await collectionItems(source)) {
139
+ const actor = uriOf(item) ?? String(item);
140
+ try {
141
+ const document = isRecord(item) && uriOf(item['inbox']) !== null ? item : await fetchAs(actor);
142
+ const follower = followerFrom(document);
143
+ const held = site.followers(username).find((entry) => entry.actorId === follower.actorId);
144
+ await site.addFollower(username, follower);
145
+ if (held === undefined)
146
+ added.push(follower.actorId);
147
+ else if (sameFollower(held, follower))
148
+ unchanged.push(follower.actorId);
149
+ else
150
+ refreshed.push(follower.actorId);
151
+ }
152
+ catch (error) {
153
+ failed.push({ actor, reason: messageOf(error) });
154
+ }
155
+ }
156
+ return { source, added, unchanged, refreshed, failed };
157
+ }
158
+ /**
159
+ * The plugin's public followers collection for this actor.
160
+ *
161
+ * Built from the stored actor id's own origin rather than from anything the
162
+ * command was told separately: the id is the one URL an owner is certain to
163
+ * have right, and it is the site the followers are still pointed at.
164
+ */
165
+ function followersCollectionUrl(options) {
166
+ const number = String(options.wordpressActorId);
167
+ return new URL(`${WORDPRESS_ACTIVITYPUB_BASE}/actors/${number}/followers`, options.actorId).href;
168
+ }
169
+ /**
170
+ * Everything a followers collection names, in the order it names them.
171
+ *
172
+ * A collection with no items of its own is followed through `first`, and a
173
+ * page through `next`, until there is nowhere left to go; either may be a URL
174
+ * to fetch or a page already embedded in the document. URLs already visited
175
+ * are not visited again, because a server whose `next` points back at itself
176
+ * would otherwise be an endless import.
177
+ */
178
+ async function collectionItems(source) {
179
+ const items = [];
180
+ const seen = new Set();
181
+ let document = isUrl(source) ? await fetchAs(source) : readJsonFile(source);
182
+ if (isUrl(source))
183
+ seen.add(source);
184
+ while (isRecord(document)) {
185
+ const listed = document['orderedItems'] ?? document['items'];
186
+ const here = Array.isArray(listed) ? listed : undefined;
187
+ if (here !== undefined)
188
+ items.push(...here);
189
+ // `next` on a page; `first` only when this document listed nothing itself,
190
+ // which is the plugin's outer collection.
191
+ const onward = document['next'] ?? (here === undefined ? document['first'] : undefined);
192
+ if (onward === undefined || onward === null)
193
+ break;
194
+ if (isRecord(onward)) {
195
+ document = onward;
196
+ continue;
197
+ }
198
+ const url = uriOf(onward);
199
+ if (url === null || seen.has(url))
200
+ break;
201
+ seen.add(url);
202
+ document = await fetchAs(url);
203
+ }
204
+ return items;
205
+ }
206
+ /** A saved collection, as the JSON it holds. */
207
+ function readJsonFile(file) {
208
+ let source;
209
+ try {
210
+ source = readFileSync(file, 'utf8');
211
+ }
212
+ catch {
213
+ throw new Error(`There is no followers collection at ${file}.`);
214
+ }
215
+ try {
216
+ return JSON.parse(source);
217
+ }
218
+ catch (error) {
219
+ throw new Error(`${file} could not be read as JSON: ${messageOf(error)}`, { cause: error });
220
+ }
221
+ }
222
+ /** Whether a `--followers` value is a URL to fetch rather than a file to read. */
223
+ function isUrl(source) {
224
+ const protocol = URL.parse(source)?.protocol;
225
+ return protocol === 'http:' || protocol === 'https:';
226
+ }
227
+ /** What a peer should ask a fediverse server for. */
228
+ const ACTIVITY_STREAMS_ACCEPT = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
229
+ /** One ActivityStreams document, or a thrown error naming the URL and the status. */
230
+ async function fetchAs(url) {
231
+ let response;
232
+ try {
233
+ response = await fetch(url, { headers: { accept: ACTIVITY_STREAMS_ACCEPT } });
234
+ }
235
+ catch (error) {
236
+ throw new Error(`${url} could not be fetched: ${messageOf(error)}`, { cause: error });
237
+ }
238
+ if (!response.ok) {
239
+ throw new Error(`${url} answered ${String(response.status)} ${response.statusText}`.trim());
240
+ }
241
+ try {
242
+ return await response.json();
243
+ }
244
+ catch (error) {
245
+ throw new Error(`${url} did not answer with JSON: ${messageOf(error)}`, { cause: error });
246
+ }
247
+ }
248
+ /**
249
+ * One actor document as the follower it stands for.
250
+ *
251
+ * Read straight out of the JSON rather than through Fedify's vocabulary,
252
+ * because a command has a plain document and no federation context to
253
+ * dereference with. The fields are the ones `followers.json` holds (doc-4):
254
+ * two to deliver with, the rest to show.
255
+ */
256
+ function followerFrom(document) {
257
+ if (!isRecord(document))
258
+ throw new Error('that is not an actor document');
259
+ const actorId = uriOf(document['id']);
260
+ const inboxId = uriOf(document['inbox']);
261
+ if (actorId === null)
262
+ throw new Error('that actor document has no id');
263
+ if (inboxId === null)
264
+ throw new Error(`${actorId} publishes no inbox to deliver to`);
265
+ const endpoints = document['endpoints'];
266
+ const icon = document['icon'];
267
+ return {
268
+ actorId,
269
+ inboxId,
270
+ sharedInboxId: isRecord(endpoints) ? uriOf(endpoints['sharedInbox']) : null,
271
+ handle: handleOf(document, actorId),
272
+ name: textOf(document['name']) ?? null,
273
+ iconUrl: uriOf(isRecord(icon) ? icon['url'] : icon),
274
+ url: uriOf(document['url']),
275
+ };
276
+ }
277
+ /**
278
+ * `@name@host` for an actor, from its `preferredUsername` where it has one.
279
+ *
280
+ * Falling back to {@link actorHandle}, which guesses from the URL, for the
281
+ * server that publishes no `preferredUsername` at all.
282
+ */
283
+ function handleOf(document, actorId) {
284
+ const preferred = textOf(document['preferredUsername']);
285
+ if (preferred === undefined || preferred === '')
286
+ return actorHandle(actorId) ?? null;
287
+ return `@${preferred.replace(/^@/, '')}@${new URL(actorId).host}`;
288
+ }
289
+ /** Whether a stored follower already says everything this one does. */
290
+ function sameFollower(held, incoming) {
291
+ return (held.inboxId === incoming.inboxId &&
292
+ held.sharedInboxId === incoming.sharedInboxId &&
293
+ held.handle === incoming.handle &&
294
+ held.name === incoming.name &&
295
+ held.iconUrl === incoming.iconUrl &&
296
+ held.url === incoming.url);
297
+ }
298
+ /** A JSON-LD string value, which may be wrapped in a language map. */
299
+ function textOf(value) {
300
+ if (Array.isArray(value)) {
301
+ for (const item of value) {
302
+ const found = textOf(item);
303
+ if (found !== undefined)
304
+ return found;
305
+ }
306
+ return undefined;
307
+ }
308
+ if (isRecord(value))
309
+ return textOf(value['@value']);
310
+ return typeof value === 'string' && value !== '' ? value : undefined;
311
+ }
312
+ /**
313
+ * The handle an actor URL implies, `@name@host` from its last path segment,
314
+ * or `undefined` when the segment is a number or missing.
315
+ */
316
+ function actorHandle(actorId) {
317
+ const url = new URL(actorId);
318
+ const last = url.pathname
319
+ .split('/')
320
+ .filter((segment) => segment !== '')
321
+ .pop();
322
+ if (last === undefined || /^\d+$/.test(last))
323
+ return undefined;
324
+ return `@${last.replace(/^@/, '')}@${url.host}`;
325
+ }
326
+ /**
327
+ * The URL a JSON-LD value names: a string, a node's `id`, a `Link`'s `href`,
328
+ * or the first of an array that names one.
329
+ */
330
+ function uriOf(value) {
331
+ if (Array.isArray(value)) {
332
+ for (const item of value) {
333
+ const found = uriOf(item);
334
+ if (found !== null)
335
+ return found;
336
+ }
337
+ return null;
338
+ }
339
+ if (isRecord(value))
340
+ return uriOf(value['id'] ?? value['@id'] ?? value['href']);
341
+ if (typeof value !== 'string')
342
+ return null;
343
+ return URL.parse(value)?.href ?? null;
344
+ }
345
+ function isRecord(value) {
346
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
347
+ }
348
+ /**
349
+ * Work out what the exported PEM means for the user's RSA key file, without
350
+ * touching it.
351
+ *
352
+ * A file already holding this very key is left alone and reported unchanged —
353
+ * that is what makes the whole command idempotent — and one holding a
354
+ * different key is refused unless the run was told to replace it.
355
+ */
356
+ async function planKeyImport(options) {
357
+ const { site, username } = options;
358
+ const privateKey = privateKeyFrom(options.privateKeyPem);
359
+ assertPairMatches(privateKey, options.publicKeyPem);
360
+ const jwk = `${JSON.stringify(await privateJwkOf(privateKey), undefined, 2)}\n`;
361
+ const { file, jwk: held } = site.actorKey(username, RSA);
362
+ if (held === undefined)
363
+ return { file, algorithm: RSA, state: 'imported', jwk };
364
+ if (sameKeyFile(held, jwk))
365
+ return { file, algorithm: RSA, state: 'unchanged', jwk: undefined };
366
+ if (options.force !== true)
367
+ throw new ExistingKeyPairError(username, file);
368
+ return { file, algorithm: RSA, state: 'replaced', jwk };
369
+ }
370
+ /** Carry out what {@link planKeyImport} decided. */
371
+ function applyKeyImport(site, username, planned) {
372
+ const { jwk, ...key } = planned;
373
+ if (jwk !== undefined)
374
+ site.writeActorKey(username, key.algorithm, jwk);
375
+ return key;
376
+ }
377
+ /**
378
+ * The PEM as a key object, whichever of the two encodings the export used.
379
+ *
380
+ * PHP writes `-----BEGIN PRIVATE KEY-----` (PKCS#8) on OpenSSL 3 and
381
+ * `-----BEGIN RSA PRIVATE KEY-----` (PKCS#1) on older builds, and an owner
382
+ * copying one out of `wp option get` has no reason to know which they have.
383
+ * `createPrivateKey` reads both.
384
+ */
385
+ function privateKeyFrom(pem) {
386
+ const text = pem.trim();
387
+ if (text === '') {
388
+ throw new UnusableKeyPemError('The private key is empty. Check what the export wrote.');
389
+ }
390
+ let key;
391
+ try {
392
+ key = createPrivateKey({ key: text, format: 'pem' });
393
+ }
394
+ catch (error) {
395
+ throw new UnusableKeyPemError(`That is not a PEM private key: ${messageOf(error)}. It should begin with ` +
396
+ '"-----BEGIN PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----".', { cause: error });
397
+ }
398
+ if (key.asymmetricKeyType !== 'rsa') {
399
+ throw new UnusableKeyPemError(`That is a ${String(key.asymmetricKeyType)} key, and the WordPress ActivityPub plugin ` +
400
+ 'signs with RSA. Check that the right key was exported.');
401
+ }
402
+ return key;
403
+ }
404
+ /** Refuse an export whose two halves are not halves of one pair. */
405
+ function assertPairMatches(privateKey, publicKeyPem) {
406
+ const text = (publicKeyPem ?? '').trim();
407
+ if (text === '')
408
+ return;
409
+ let given;
410
+ try {
411
+ given = createPublicKey({ key: text, format: 'pem' });
412
+ }
413
+ catch (error) {
414
+ throw new UnusableKeyPemError(`That is not a PEM public key: ${messageOf(error)}.`, {
415
+ cause: error,
416
+ });
417
+ }
418
+ const derived = createPublicKey(privateKey);
419
+ if (!given
420
+ .export({ type: 'spki', format: 'der' })
421
+ .equals(derived.export({ type: 'spki', format: 'der' }))) {
422
+ throw new UnusableKeyPemError('The public and private keys are not two halves of one pair. Export both again from the ' +
423
+ 'same WordPress user, or leave the public key off — it is derived from the private one.');
424
+ }
425
+ }
426
+ /**
427
+ * The private JWK the key file holds, produced the way a generated one is.
428
+ *
429
+ * The PEM goes to DER through node:crypto, into WebCrypto, and out through
430
+ * Fedify's `exportJwk` — the same call the site makes when it mints a pair.
431
+ * Going the long way round rather than asking node:crypto for the JWK directly
432
+ * is what guarantees an imported file and a generated one carry the
433
+ * same members in the same shape, `alg: "RS256"` included, which is what the
434
+ * loader's boot check insists on.
435
+ */
436
+ async function privateJwkOf(privateKey) {
437
+ const pkcs8 = privateKey.export({ type: 'pkcs8', format: 'der' });
438
+ const key = await crypto.subtle.importKey('pkcs8', pkcs8, { name: RSA, hash: 'SHA-256' }, true, [
439
+ 'sign',
440
+ ]);
441
+ return await exportJwk(key);
442
+ }
443
+ /**
444
+ * Whether a key file already holds the key being imported.
445
+ *
446
+ * Compared as the parsed JWK rather than as bytes, so a file somebody
447
+ * reindented is still the same key.
448
+ */
449
+ function sameKeyFile(held, incoming) {
450
+ try {
451
+ return (JSON.stringify(JSON.parse(held)) ===
452
+ JSON.stringify(JSON.parse(incoming)));
453
+ }
454
+ catch {
455
+ return false;
456
+ }
457
+ }
458
+ function messageOf(error) {
459
+ return error instanceof Error ? error.message : String(error);
460
+ }
461
+ //# sourceMappingURL=import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import.js","sourceRoot":"","sources":["../src/import.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * `@geekity/plugin-wordpress` (decision-33): what a site that moved from the
3
+ * WordPress ActivityPub plugin needs until its followers' servers have caught
4
+ * up. Its old inbox and collection paths, the record of when each was last
5
+ * asked for, and `geekity import wordpress-actor`.
6
+ *
7
+ * Stored actor and object ids, WebFinger aliases, the feed and archive layout
8
+ * and oEmbed are the site's own and stay in `@geekity/cms` (decision-14).
9
+ */
10
+ export { ExistingKeyPairError, importWordPressActor, UnusableKeyPemError } from './import.ts';
11
+ export type { FollowersImportReport, ImportedKey, ImportWordPressActorOptions, ImportWordPressActorReport, UnreachableFollower, } from './import.ts';
12
+ export { ACTORS_FILE, ConflictingWordPressIdError, REQUESTS_FILE, WORDPRESS_ACTIVITYPUB_BASE, wordPressRecords, wordPressRequestTarget, } from './records.ts';
13
+ export type { WordPressRecords, WordPressRequests, WordPressRequestTarget, WordPressRoute, } from './records.ts';
14
+ declare const _default: import("@geekity/cms/plugin").Plugin<"@geekity/plugin-wordpress", {}>;
15
+ export default _default;
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC9F,YAAY,EACV,qBAAqB,EACrB,WAAW,EACX,2BAA2B,EAC3B,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,WAAW,EACX,2BAA2B,EAC3B,aAAa,EACb,0BAA0B,EAC1B,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,cAAc,GACf,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `@geekity/plugin-wordpress` (decision-33): what a site that moved from the
3
+ * WordPress ActivityPub plugin needs until its followers' servers have caught
4
+ * up. Its old inbox and collection paths, the record of when each was last
5
+ * asked for, and `geekity import wordpress-actor`.
6
+ *
7
+ * Stored actor and object ids, WebFinger aliases, the feed and archive layout
8
+ * and oEmbed are the site's own and stay in `@geekity/cms` (decision-14).
9
+ */
10
+ import { definePlugin } from '@geekity/cms/plugin';
11
+ import { importCommand } from './command.js';
12
+ import { wordPressFederation } from './federation.js';
13
+ import { wordPressRecords } from './records.js';
14
+ import { wordPressScreen } from './screen.js';
15
+ import { VERSION } from './version.js';
16
+ export { ExistingKeyPairError, importWordPressActor, UnusableKeyPemError } from './import.js';
17
+ export { ACTORS_FILE, ConflictingWordPressIdError, REQUESTS_FILE, WORDPRESS_ACTIVITYPUB_BASE, wordPressRecords, wordPressRequestTarget, } from './records.js';
18
+ export default definePlugin({
19
+ name: '@geekity/plugin-wordpress',
20
+ version: VERSION,
21
+ label: 'WordPress',
22
+ description: 'Answers the inbox and collection paths the WordPress ActivityPub plugin published, ' +
23
+ 'for a site that moved here from it, until every follower has refetched the actor.',
24
+ hostApi: 1,
25
+ register(host) {
26
+ const records = wordPressRecords(host.data);
27
+ host.federation(wordPressFederation(records));
28
+ host.screen(wordPressScreen(records));
29
+ host.command(importCommand(records));
30
+ },
31
+ });
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAQ9F,OAAO,EACL,WAAW,EACX,2BAA2B,EAC3B,aAAa,EACb,0BAA0B,EAC1B,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAQtB,eAAe,YAAY,CAAC;IAC1B,IAAI,EAAE,2BAA2B;IACjC,OAAO,EAAE,OAAO;IAChB,KAAK,EAAE,WAAW;IAClB,WAAW,EACT,qFAAqF;QACrF,mFAAmF;IACrF,OAAO,EAAE,CAAC;IACV,QAAQ,CAAC,IAAI;QACX,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;QACtC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IACvC,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * What the plugin keeps in its data folder, `data/plugins/@geekity/plugin-wordpress/`:
3
+ * the number the WordPress ActivityPub plugin gave each person, and when each
4
+ * of its old paths was last asked for.
5
+ *
6
+ * Files rather than a table, because the database is a cache a site may delete
7
+ * (decision-9), and both of these are facts the site cannot work out again.
8
+ */
9
+ import type { PluginDataFolder, PluginSite } from '@geekity/cms/plugin';
10
+ /** Where the plugin's REST namespace lives, on the site's origin. */
11
+ export declare const WORDPRESS_ACTIVITYPUB_BASE = "/wp-json/activitypub/1.0";
12
+ /** The plugin's actor path. Its identifier is the WordPress user's number. */
13
+ export declare const WORDPRESS_ACTOR_PATH: "/wp-json/activitypub/1.0/actors/{identifier}";
14
+ /** The actor's inbox, outbox and two follow collections, as the plugin spells them. */
15
+ export declare const WORDPRESS_INBOX_PATH: "/wp-json/activitypub/1.0/actors/{identifier}/inbox";
16
+ export declare const WORDPRESS_OUTBOX_PATH: "/wp-json/activitypub/1.0/actors/{identifier}/outbox";
17
+ export declare const WORDPRESS_FOLLOWERS_PATH: "/wp-json/activitypub/1.0/actors/{identifier}/followers";
18
+ export declare const WORDPRESS_FOLLOWING_PATH: "/wp-json/activitypub/1.0/actors/{identifier}/following";
19
+ /** The plugin's instance-wide inbox, which its `sharedInbox` endpoint names. */
20
+ export declare const WORDPRESS_SHARED_INBOX_PATH: "/wp-json/activitypub/1.0/inbox";
21
+ /** The file mapping each username to its WordPress number. */
22
+ export declare const ACTORS_FILE = "actors.json";
23
+ /** The file holding when each path was last asked for. */
24
+ export declare const REQUESTS_FILE = "requests.json";
25
+ /** One of the plugin's paths, as the record of what was asked for names it. */
26
+ export type WordPressRoute = 'actor' | 'inbox' | 'outbox' | 'followers' | 'following';
27
+ /** Every per-person path, in the order the plugin's actor names them. */
28
+ export declare const WORDPRESS_ROUTES: readonly WordPressRoute[];
29
+ /** Which compatibility path a request is for, and whose. */
30
+ export interface WordPressRequestTarget {
31
+ /** The path it is, or `sharedInbox` for the instance-wide one. */
32
+ readonly route: WordPressRoute | 'sharedInbox';
33
+ /** The WordPress number in the path, or `undefined` for the shared inbox. */
34
+ readonly wordpressActorId?: string | undefined;
35
+ }
36
+ /**
37
+ * The compatibility path a request asks for, or `undefined` for anything else.
38
+ *
39
+ * Matched here rather than left to Fedify because the record of what was last
40
+ * asked for is written whether or not a user answers: a delivery to a number
41
+ * nobody carries is still a peer holding the old URL. Fedify's router still
42
+ * decides what is served.
43
+ */
44
+ export declare function wordPressRequestTarget(pathname: string): WordPressRequestTarget | undefined;
45
+ /** When each compatibility path was last asked for. */
46
+ export interface WordPressRequests {
47
+ /** When the instance-wide inbox was last delivered to, or `undefined`. */
48
+ readonly sharedInbox?: string | undefined;
49
+ /** When each of a user's own paths was last asked for, by username. */
50
+ readonly users: Readonly<Record<string, Readonly<Partial<Record<WordPressRoute, string>>>>>;
51
+ }
52
+ /** Thrown when a WordPress number already belongs to another user. */
53
+ export declare class ConflictingWordPressIdError extends Error {
54
+ readonly name = "ConflictingWordPressIdError";
55
+ /** The user who already has it. */
56
+ readonly username: string;
57
+ constructor(username: string, wordpressActorId: number);
58
+ }
59
+ /** The plugin's two files, read and written through its data folder. */
60
+ export interface WordPressRecords {
61
+ /** Each username's WordPress number. */
62
+ actors(): ReadonlyMap<string, number>;
63
+ /**
64
+ * The user the plugin numbered `identifier`, or `undefined`. The identifier
65
+ * is a path segment, so it is compared as the decimal the plugin writes:
66
+ * `02` and `2.0` name nobody, because neither is a URL it ever published.
67
+ * A number whose user the site no longer has names nobody either.
68
+ */
69
+ userByNumber(site: PluginSite, identifier: string): string | undefined;
70
+ /**
71
+ * Give a user their WordPress number. Returns whether anything changed;
72
+ * throws {@link ConflictingWordPressIdError} when another user has it.
73
+ */
74
+ setNumber(username: string, wordpressActorId: number): Promise<boolean>;
75
+ requests(): WordPressRequests;
76
+ /**
77
+ * Record that one path was just asked for. A path whose number names
78
+ * nobody is not recorded: the record is per user.
79
+ */
80
+ recordRequest(options: {
81
+ target: WordPressRequestTarget;
82
+ username: string | undefined;
83
+ at: Date;
84
+ }): Promise<void>;
85
+ }
86
+ export declare function wordPressRecords(data: PluginDataFolder): WordPressRecords;
87
+ //# sourceMappingURL=records.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAExE,qEAAqE;AACrE,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AAErE,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,gDAA+D,CAAC;AAEjG,uFAAuF;AACvF,eAAO,MAAM,oBAAoB,sDAA2C,CAAC;AAC7E,eAAO,MAAM,qBAAqB,uDAA4C,CAAC;AAC/E,eAAO,MAAM,wBAAwB,0DAA+C,CAAC;AACrF,eAAO,MAAM,wBAAwB,0DAA+C,CAAC;AAErF,gFAAgF;AAChF,eAAO,MAAM,2BAA2B,kCAAiD,CAAC;AAE1F,8DAA8D;AAC9D,eAAO,MAAM,WAAW,gBAAgB,CAAC;AAEzC,0DAA0D;AAC1D,eAAO,MAAM,aAAa,kBAAkB,CAAC;AAE7C,+EAA+E;AAC/E,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;AAEtF,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAMrD,CAAC;AAEF,4DAA4D;AAC5D,MAAM,WAAW,sBAAsB;IACrC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,aAAa,CAAC;IAC/C,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChD;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS,CAY3F;AAED,uDAAuD;AACvD,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC7F;AAED,sEAAsE;AACtE,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,SAAkB,IAAI,iCAAiC;IACvD,mCAAmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,YAAY,QAAQ,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAGrD;CACF;AAED,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC;;;;;OAKG;IACH,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACvE;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxE,QAAQ,IAAI,iBAAiB,CAAC;IAC9B;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE;QACrB,MAAM,EAAE,sBAAsB,CAAC;QAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;QAC7B,EAAE,EAAE,IAAI,CAAC;KACV,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,gBAAgB,CAwDzE"}