@geekity/plugin-wordpress 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +151 -2
- package/dist/bundle/index.js +160334 -0
- package/dist/bundle/plugin.json +8 -0
- package/dist/command.d.ts +17 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/command.js +194 -0
- package/dist/command.js.map +1 -0
- package/dist/federation.d.ts +30 -0
- package/dist/federation.d.ts.map +1 -0
- package/dist/federation.js +162 -0
- package/dist/federation.js.map +1 -0
- package/dist/import.d.ts +123 -0
- package/dist/import.d.ts.map +1 -0
- package/dist/import.js +461 -0
- package/dist/import.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/records.d.ts +87 -0
- package/dist/records.d.ts.map +1 -0
- package/dist/records.js +176 -0
- package/dist/records.js.map +1 -0
- package/dist/screen.d.ts +14 -0
- package/dist/screen.d.ts.map +1 -0
- package/dist/screen.js +78 -0
- package/dist/screen.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +54 -3
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PluginCommand } from '@geekity/cms/plugin';
|
|
2
|
+
import type { WordPressRecords } from './records.ts';
|
|
3
|
+
/**
|
|
4
|
+
* `geekity import wordpress-actor <username>`: the cutover, for one person.
|
|
5
|
+
*
|
|
6
|
+
* Everything the WordPress ActivityPub plugin holds that the site cannot mint
|
|
7
|
+
* for itself arrives here (decision-14): the RSA pair its followers have
|
|
8
|
+
* cached, the actor id they key the account by, the number its paths are
|
|
9
|
+
* built from, and the followers themselves. It is a command rather than a
|
|
10
|
+
* screen because a stored actor id is identity for the life of the account,
|
|
11
|
+
* and it is idempotent because the cutover is a thing an owner runs twice:
|
|
12
|
+
* once before the DNS moves and once after an instance that was down comes
|
|
13
|
+
* back. It runs whether or not the plugin is enabled, because the import
|
|
14
|
+
* comes first.
|
|
15
|
+
*/
|
|
16
|
+
export declare function importCommand(records: WordPressRecords): PluginCommand;
|
|
17
|
+
//# sourceMappingURL=command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,qBAAqB,CAAC;AAK/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,aAAa,CAkDtE"}
|
package/dist/command.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { importWordPressActor } from './import.js';
|
|
4
|
+
import { WORDPRESS_ACTIVITYPUB_BASE } from './records.js';
|
|
5
|
+
/**
|
|
6
|
+
* `geekity import wordpress-actor <username>`: the cutover, for one person.
|
|
7
|
+
*
|
|
8
|
+
* Everything the WordPress ActivityPub plugin holds that the site cannot mint
|
|
9
|
+
* for itself arrives here (decision-14): the RSA pair its followers have
|
|
10
|
+
* cached, the actor id they key the account by, the number its paths are
|
|
11
|
+
* built from, and the followers themselves. It is a command rather than a
|
|
12
|
+
* screen because a stored actor id is identity for the life of the account,
|
|
13
|
+
* and it is idempotent because the cutover is a thing an owner runs twice:
|
|
14
|
+
* once before the DNS moves and once after an instance that was down comes
|
|
15
|
+
* back. It runs whether or not the plugin is enabled, because the import
|
|
16
|
+
* comes first.
|
|
17
|
+
*/
|
|
18
|
+
export function importCommand(records) {
|
|
19
|
+
return {
|
|
20
|
+
words: ['import', 'wordpress-actor'],
|
|
21
|
+
usage: '<username> --actor-id <url> --wordpress-id <n> (--keypair <file> | --private-key <file> [--public-key <file>]) [--followers <url|file|none>] [--force]',
|
|
22
|
+
summary: "Bring one person across from the WordPress ActivityPub plugin: their key pair, the actor id their followers hold, the plugin's numeric actor id, and their followers.",
|
|
23
|
+
options: [
|
|
24
|
+
{
|
|
25
|
+
name: 'actor-id',
|
|
26
|
+
value: '<url>',
|
|
27
|
+
description: 'The actor id WordPress published, query string and all, for example https://example.com/?author=2',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'wordpress-id',
|
|
31
|
+
value: '<n>',
|
|
32
|
+
description: `The WordPress user id, which is the number in the plugin's paths (${WORDPRESS_ACTIVITYPUB_BASE}/actors/<n>/inbox).`,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'keypair',
|
|
36
|
+
value: '<file>',
|
|
37
|
+
description: 'The JSON `wp option get activitypub_keypair_for_<login> --format=json` prints: {"private_key": …, "public_key": …}.',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'private-key',
|
|
41
|
+
value: '<file>',
|
|
42
|
+
description: 'The private key as PEM, instead of --keypair.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'public-key',
|
|
46
|
+
value: '<file>',
|
|
47
|
+
description: 'The public key as PEM. Optional, and only checked against the private half; it is derived, never stored.',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'followers',
|
|
51
|
+
value: '<url|file|none>',
|
|
52
|
+
description: "Where the followers come from. Left off, the plugin's own public followers collection on the actor id's origin.",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'force',
|
|
56
|
+
description: 'Import over a key pair the user already has. Do this only when you are certain the pair being imported is the one the followers hold: a new key means none of them can verify this person again.',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
run: (context) => run(context, records),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function run(context, records) {
|
|
63
|
+
const { args, options } = context;
|
|
64
|
+
const username = args[0];
|
|
65
|
+
if (username === undefined) {
|
|
66
|
+
throw new Error('geekity import wordpress-actor <username> needs the account on this site the ' +
|
|
67
|
+
"plugin's actor becomes.");
|
|
68
|
+
}
|
|
69
|
+
const actorId = text(options, 'actor-id');
|
|
70
|
+
if (actorId === undefined) {
|
|
71
|
+
throw new Error('--actor-id is the id WordPress published this person under, query string and all, ' +
|
|
72
|
+
'for example --actor-id "https://example.com/?author=2". Its followers key the ' +
|
|
73
|
+
'account by it, so it is the one thing the import cannot work out for itself.');
|
|
74
|
+
}
|
|
75
|
+
if (!isAbsoluteHttpUrl(actorId)) {
|
|
76
|
+
throw new Error(`--actor-id must be an http or https URL; "${actorId}" is not one.`);
|
|
77
|
+
}
|
|
78
|
+
const number = text(options, 'wordpress-id');
|
|
79
|
+
if (number === undefined || !/^[1-9][0-9]*$/.test(number)) {
|
|
80
|
+
throw new Error('--wordpress-id is the WordPress user id, a whole positive number: it is what the ' +
|
|
81
|
+
`plugin's paths are built from, as in ${WORDPRESS_ACTIVITYPUB_BASE}/actors/2/inbox.`);
|
|
82
|
+
}
|
|
83
|
+
const { privateKeyPem, publicKeyPem } = await readKeyPair(context);
|
|
84
|
+
const report = await importWordPressActor({
|
|
85
|
+
site: context.site,
|
|
86
|
+
records,
|
|
87
|
+
username,
|
|
88
|
+
actorId,
|
|
89
|
+
wordpressActorId: Number(number),
|
|
90
|
+
privateKeyPem,
|
|
91
|
+
publicKeyPem,
|
|
92
|
+
followers: followersSource(context),
|
|
93
|
+
force: options['force'] === true,
|
|
94
|
+
});
|
|
95
|
+
context.write(importSummary(report));
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The key pair, from whichever of the two exports the owner had to hand.
|
|
100
|
+
*
|
|
101
|
+
* `--keypair` is the option the current plugin stores, one option holding
|
|
102
|
+
* both halves, and the two PEM files are what `wp user meta get` prints for a
|
|
103
|
+
* site old enough to still be on the legacy meta keys. The README says which
|
|
104
|
+
* command produces which.
|
|
105
|
+
*/
|
|
106
|
+
async function readKeyPair(context) {
|
|
107
|
+
const { options, cwd } = context;
|
|
108
|
+
const keypair = text(options, 'keypair');
|
|
109
|
+
const privateKey = text(options, 'private-key');
|
|
110
|
+
const publicKey = text(options, 'public-key');
|
|
111
|
+
if (keypair !== undefined) {
|
|
112
|
+
if (privateKey !== undefined) {
|
|
113
|
+
throw new Error('Pass either --keypair or --private-key, not both.');
|
|
114
|
+
}
|
|
115
|
+
let parsed;
|
|
116
|
+
try {
|
|
117
|
+
parsed = JSON.parse(await readFile(path.resolve(cwd, keypair), 'utf8'));
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
throw new Error(`${keypair} could not be read as the JSON "wp option get … --format=json" prints: ` +
|
|
121
|
+
`${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
122
|
+
}
|
|
123
|
+
const held = parsed;
|
|
124
|
+
if (typeof held?.private_key !== 'string') {
|
|
125
|
+
throw new Error(`${keypair} has no "private_key". Check what the export wrote.`);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
privateKeyPem: held.private_key,
|
|
129
|
+
publicKeyPem: typeof held.public_key === 'string' ? held.public_key : undefined,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (privateKey === undefined) {
|
|
133
|
+
throw new Error('The key pair is missing: pass --keypair with the JSON WordPress stores, or ' +
|
|
134
|
+
'--private-key with the PEM. Without it this person could not sign anything, and ' +
|
|
135
|
+
'every follower holds the public half of the key that signs it.');
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
privateKeyPem: await readFile(path.resolve(cwd, privateKey), 'utf8'),
|
|
139
|
+
publicKeyPem: publicKey === undefined ? undefined : await readFile(path.resolve(cwd, publicKey), 'utf8'),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* What `--followers` means: a URL, a file resolved against the working
|
|
144
|
+
* directory, `none`, or the plugin's own collection when it was left off.
|
|
145
|
+
*/
|
|
146
|
+
function followersSource(context) {
|
|
147
|
+
const given = text(context.options, 'followers');
|
|
148
|
+
if (given === undefined)
|
|
149
|
+
return undefined;
|
|
150
|
+
if (given === 'none')
|
|
151
|
+
return false;
|
|
152
|
+
return isAbsoluteHttpUrl(given) ? given : path.resolve(context.cwd, given);
|
|
153
|
+
}
|
|
154
|
+
/** What the run did, as the operator reads it. */
|
|
155
|
+
function importSummary(report) {
|
|
156
|
+
const lines = [];
|
|
157
|
+
const { followers } = report;
|
|
158
|
+
lines.push(report.changed
|
|
159
|
+
? `Imported ${report.username} from ${report.actorId}`
|
|
160
|
+
: `Nothing to change: ${report.username} is already ${report.actorId}`);
|
|
161
|
+
lines.push(` ids ${report.identity === 'set' ? 'written to' : 'already in'} ` +
|
|
162
|
+
`data/users.json and the plugin's actors.json (WordPress actor ${String(report.wordpressActorId)})`);
|
|
163
|
+
for (const key of report.keys)
|
|
164
|
+
lines.push(` key ${key.state} ${key.file}`);
|
|
165
|
+
if (followers.source === undefined) {
|
|
166
|
+
lines.push(' followers skipped');
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
lines.push(` followers ${String(followers.added.length)} added, ` +
|
|
170
|
+
`${String(followers.refreshed.length)} refreshed, ` +
|
|
171
|
+
`${String(followers.unchanged.length)} unchanged, ` +
|
|
172
|
+
`${String(followers.failed.length)} could not be fetched ` +
|
|
173
|
+
`(${followers.source})`);
|
|
174
|
+
for (const failure of followers.failed) {
|
|
175
|
+
lines.push(` skipped ${failure.actor}: ${failure.reason}`);
|
|
176
|
+
}
|
|
177
|
+
if (followers.failed.length > 0) {
|
|
178
|
+
lines.push(' Those followers were left out. Run this again once their servers answer; ' +
|
|
179
|
+
'nothing already imported is touched twice.');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return `${lines.join('\n')}\n`;
|
|
183
|
+
}
|
|
184
|
+
/** One option as the text it carries, or `undefined` when it was not given. */
|
|
185
|
+
function text(options, name) {
|
|
186
|
+
const value = options[name];
|
|
187
|
+
return typeof value === 'string' ? value : undefined;
|
|
188
|
+
}
|
|
189
|
+
/** Whether a value is a URL a peer could fetch. */
|
|
190
|
+
function isAbsoluteHttpUrl(value) {
|
|
191
|
+
const protocol = URL.parse(value)?.protocol;
|
|
192
|
+
return protocol === 'http:' || protocol === 'https:';
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { PluginFederationMiddleware } from '@geekity/cms/plugin';
|
|
2
|
+
import type { WordPressRecords } from './records.ts';
|
|
3
|
+
/**
|
|
4
|
+
* The WordPress ActivityPub plugin's paths, served while this plugin is
|
|
5
|
+
* enabled (TASK-70, TASK-282).
|
|
6
|
+
*
|
|
7
|
+
* A follower's server delivers to the inbox URL it cached from the actor
|
|
8
|
+
* document, and replaces it only when it next refetches the actor. A site that
|
|
9
|
+
* moved here from the plugin therefore goes on being delivered to at
|
|
10
|
+
* `/wp-json/activitypub/1.0/actors/2/inbox` and `/wp-json/activitypub/1.0/inbox`
|
|
11
|
+
* for as long as those caches last, and decision-14 calls that **cache rather
|
|
12
|
+
* than identity**: the site carries the plugin's layout until the caches have
|
|
13
|
+
* moved on, and then the plugin is disabled.
|
|
14
|
+
*
|
|
15
|
+
* Everything a peer reads back is the canonical identity. The actor served at
|
|
16
|
+
* `…/actors/2` is the same `Person` the author URL serves, same `id`, same
|
|
17
|
+
* canonical `inbox`, so a peer that refetches here learns the new endpoints.
|
|
18
|
+
*
|
|
19
|
+
* A Fedify federation of the plugin's own is how a second inbox path gets
|
|
20
|
+
* signature verification at all: one federation has exactly one pair of inbox
|
|
21
|
+
* listeners (doc-8). It shares the site federation's KV store and is set to
|
|
22
|
+
* `per-origin` idempotence, as the canonical inbox is, so one `Follow`
|
|
23
|
+
* redelivered to `andrew`'s inbox and to `2`'s is handled once.
|
|
24
|
+
*
|
|
25
|
+
* Every document crosses from the site as JSON-LD and is parsed here with
|
|
26
|
+
* this plugin's own vocabulary, because a bundled plugin carries its own copy
|
|
27
|
+
* of Fedify and the site's objects are not instances of this copy's classes.
|
|
28
|
+
*/
|
|
29
|
+
export declare function wordPressFederation(records: WordPressRecords): PluginFederationMiddleware;
|
|
30
|
+
//# sourceMappingURL=federation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"federation.d.ts","sourceRoot":"","sources":["../src/federation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAGV,0BAA0B,EAC3B,MAAM,qBAAqB,CAAC;AAW7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,GAAG,0BAA0B,CA8BzF"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { createFederation } from '@fedify/fedify';
|
|
2
|
+
import { Activity, Create, Person } from '@fedify/vocab';
|
|
3
|
+
import { WORDPRESS_ACTOR_PATH, WORDPRESS_FOLLOWERS_PATH, WORDPRESS_FOLLOWING_PATH, WORDPRESS_INBOX_PATH, WORDPRESS_OUTBOX_PATH, WORDPRESS_SHARED_INBOX_PATH, wordPressRequestTarget, } from './records.js';
|
|
4
|
+
/**
|
|
5
|
+
* The WordPress ActivityPub plugin's paths, served while this plugin is
|
|
6
|
+
* enabled (TASK-70, TASK-282).
|
|
7
|
+
*
|
|
8
|
+
* A follower's server delivers to the inbox URL it cached from the actor
|
|
9
|
+
* document, and replaces it only when it next refetches the actor. A site that
|
|
10
|
+
* moved here from the plugin therefore goes on being delivered to at
|
|
11
|
+
* `/wp-json/activitypub/1.0/actors/2/inbox` and `/wp-json/activitypub/1.0/inbox`
|
|
12
|
+
* for as long as those caches last, and decision-14 calls that **cache rather
|
|
13
|
+
* than identity**: the site carries the plugin's layout until the caches have
|
|
14
|
+
* moved on, and then the plugin is disabled.
|
|
15
|
+
*
|
|
16
|
+
* Everything a peer reads back is the canonical identity. The actor served at
|
|
17
|
+
* `…/actors/2` is the same `Person` the author URL serves, same `id`, same
|
|
18
|
+
* canonical `inbox`, so a peer that refetches here learns the new endpoints.
|
|
19
|
+
*
|
|
20
|
+
* A Fedify federation of the plugin's own is how a second inbox path gets
|
|
21
|
+
* signature verification at all: one federation has exactly one pair of inbox
|
|
22
|
+
* listeners (doc-8). It shares the site federation's KV store and is set to
|
|
23
|
+
* `per-origin` idempotence, as the canonical inbox is, so one `Follow`
|
|
24
|
+
* redelivered to `andrew`'s inbox and to `2`'s is handled once.
|
|
25
|
+
*
|
|
26
|
+
* Every document crosses from the site as JSON-LD and is parsed here with
|
|
27
|
+
* this plugin's own vocabulary, because a bundled plugin carries its own copy
|
|
28
|
+
* of Fedify and the site's objects are not instances of this copy's classes.
|
|
29
|
+
*/
|
|
30
|
+
export function wordPressFederation(records) {
|
|
31
|
+
let federation;
|
|
32
|
+
return async (context, next) => {
|
|
33
|
+
const target = wordPressRequestTarget(new URL(context.request.url).pathname);
|
|
34
|
+
if (target === undefined)
|
|
35
|
+
return await next();
|
|
36
|
+
await records.recordRequest({
|
|
37
|
+
target,
|
|
38
|
+
username: target.wordpressActorId === undefined
|
|
39
|
+
? undefined
|
|
40
|
+
: records.userByNumber(context.site, target.wordpressActorId),
|
|
41
|
+
at: context.now(),
|
|
42
|
+
});
|
|
43
|
+
federation ??= build(context, records);
|
|
44
|
+
return await federation.fetch(context.request, {
|
|
45
|
+
contextData: context,
|
|
46
|
+
onNotFound: next,
|
|
47
|
+
async onNotAcceptable() {
|
|
48
|
+
const response = await next();
|
|
49
|
+
if (response.status !== 404)
|
|
50
|
+
return response;
|
|
51
|
+
return new Response('Not acceptable', {
|
|
52
|
+
status: 406,
|
|
53
|
+
headers: { 'content-type': 'text/plain', vary: 'Accept' },
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The compatibility federation: the plugin's inbox and collections, mapped to
|
|
61
|
+
* users by the number WordPress gave them. Nothing else is registered:
|
|
62
|
+
* WebFinger, NodeInfo and the actor's real endpoints belong to the site.
|
|
63
|
+
*/
|
|
64
|
+
function build(first, records) {
|
|
65
|
+
const federation = createFederation({
|
|
66
|
+
kv: first.kv,
|
|
67
|
+
origin: new URL(first.site.baseUrl).origin,
|
|
68
|
+
allowPrivateAddress: first.allowPrivateAddress,
|
|
69
|
+
});
|
|
70
|
+
/**
|
|
71
|
+
* The username behind the identifier in a path Fedify matched, which on the
|
|
72
|
+
* plugin's paths is the number WordPress gave the user.
|
|
73
|
+
*/
|
|
74
|
+
function usernameOfWordPressNumber(context, wordpressNumber) {
|
|
75
|
+
return records.userByNumber(context.data.site, wordpressNumber);
|
|
76
|
+
}
|
|
77
|
+
/** What parsing a document from the site with this copy's vocabulary needs. */
|
|
78
|
+
function loaders(context) {
|
|
79
|
+
return { documentLoader: context.documentLoader, contextLoader: context.contextLoader };
|
|
80
|
+
}
|
|
81
|
+
federation
|
|
82
|
+
.setActorDispatcher(WORDPRESS_ACTOR_PATH, async (context, identifier) => {
|
|
83
|
+
const username = usernameOfWordPressNumber(context, identifier);
|
|
84
|
+
if (username === undefined)
|
|
85
|
+
return null;
|
|
86
|
+
const actor = await context.data.actor(username);
|
|
87
|
+
return actor === undefined ? null : await Person.fromJsonLd(actor, loaders(context));
|
|
88
|
+
})
|
|
89
|
+
.setKeyPairsDispatcher(async (context, identifier) => {
|
|
90
|
+
const username = usernameOfWordPressNumber(context, identifier);
|
|
91
|
+
return username === undefined ? [] : await context.data.keyPairs(username);
|
|
92
|
+
});
|
|
93
|
+
federation
|
|
94
|
+
.setOutboxDispatcher(WORDPRESS_OUTBOX_PATH, async (context, identifier, cursor) => {
|
|
95
|
+
const username = usernameOfWordPressNumber(context, identifier);
|
|
96
|
+
const outbox = username === undefined ? undefined : context.data.outbox(username);
|
|
97
|
+
if (outbox === undefined)
|
|
98
|
+
return null;
|
|
99
|
+
const page = await outbox.page(cursor);
|
|
100
|
+
return {
|
|
101
|
+
items: await Promise.all(page.items.map((item) => Create.fromJsonLd(item, loaders(context)))),
|
|
102
|
+
nextCursor: page.nextCursor,
|
|
103
|
+
prevCursor: page.prevCursor,
|
|
104
|
+
};
|
|
105
|
+
})
|
|
106
|
+
.setCounter((context, identifier) => {
|
|
107
|
+
const username = usernameOfWordPressNumber(context, identifier);
|
|
108
|
+
return username === undefined ? null : (context.data.outbox(username)?.totalItems ?? null);
|
|
109
|
+
})
|
|
110
|
+
.setFirstCursor((context, identifier) => {
|
|
111
|
+
const username = usernameOfWordPressNumber(context, identifier);
|
|
112
|
+
return username === undefined ? null : (context.data.outbox(username)?.firstCursor ?? null);
|
|
113
|
+
})
|
|
114
|
+
.setLastCursor((context, identifier) => {
|
|
115
|
+
const username = usernameOfWordPressNumber(context, identifier);
|
|
116
|
+
return username === undefined ? null : (context.data.outbox(username)?.lastCursor ?? null);
|
|
117
|
+
});
|
|
118
|
+
federation
|
|
119
|
+
.setFollowersDispatcher(WORDPRESS_FOLLOWERS_PATH, async (context, identifier, cursor) => {
|
|
120
|
+
const username = usernameOfWordPressNumber(context, identifier);
|
|
121
|
+
const followers = username === undefined ? undefined : context.data.followers(username);
|
|
122
|
+
if (followers === undefined)
|
|
123
|
+
return null;
|
|
124
|
+
const page = await followers.page(cursor);
|
|
125
|
+
return {
|
|
126
|
+
items: page.items.map((recipient) => ({
|
|
127
|
+
id: new URL(recipient.id),
|
|
128
|
+
inboxId: new URL(recipient.inboxId),
|
|
129
|
+
endpoints: {
|
|
130
|
+
sharedInbox: recipient.sharedInboxId === null ? null : new URL(recipient.sharedInboxId),
|
|
131
|
+
},
|
|
132
|
+
})),
|
|
133
|
+
nextCursor: page.nextCursor,
|
|
134
|
+
prevCursor: page.prevCursor,
|
|
135
|
+
};
|
|
136
|
+
})
|
|
137
|
+
.setCounter((context, identifier) => {
|
|
138
|
+
const username = usernameOfWordPressNumber(context, identifier);
|
|
139
|
+
return username === undefined ? null : (context.data.followers(username)?.totalItems ?? null);
|
|
140
|
+
})
|
|
141
|
+
.setFirstCursor((context, identifier) => {
|
|
142
|
+
const username = usernameOfWordPressNumber(context, identifier);
|
|
143
|
+
return username === undefined
|
|
144
|
+
? null
|
|
145
|
+
: (context.data.followers(username)?.firstCursor ?? null);
|
|
146
|
+
})
|
|
147
|
+
.setLastCursor((context, identifier) => {
|
|
148
|
+
const username = usernameOfWordPressNumber(context, identifier);
|
|
149
|
+
return username === undefined ? null : (context.data.followers(username)?.lastCursor ?? null);
|
|
150
|
+
});
|
|
151
|
+
federation.setFollowingDispatcher(WORDPRESS_FOLLOWING_PATH, (context, identifier) => usernameOfWordPressNumber(context, identifier) === undefined ? null : { items: [] });
|
|
152
|
+
federation
|
|
153
|
+
.setInboxListeners(WORDPRESS_INBOX_PATH, WORDPRESS_SHARED_INBOX_PATH)
|
|
154
|
+
.withIdempotency('per-origin')
|
|
155
|
+
.on(Activity, async (context, activity) => {
|
|
156
|
+
const wordpressNumber = context.recipient;
|
|
157
|
+
const recipient = wordpressNumber === null ? undefined : usernameOfWordPressNumber(context, wordpressNumber);
|
|
158
|
+
await context.data.receive((await activity.toJsonLd()), recipient ?? null);
|
|
159
|
+
});
|
|
160
|
+
return federation;
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=federation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"federation.js","sourceRoot":"","sources":["../src/federation.ts"],"names":[],"mappings":"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"}
|
package/dist/import.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { PluginKeyAlgorithm, PluginSite } from '@geekity/cms/plugin';
|
|
2
|
+
import type { WordPressRecords } from './records.ts';
|
|
3
|
+
/** What {@link importWordPressActor} is given. */
|
|
4
|
+
export interface ImportWordPressActorOptions {
|
|
5
|
+
/** The site the person arrives on. */
|
|
6
|
+
readonly site: PluginSite;
|
|
7
|
+
/** Where the plugin keeps each person's WordPress number. */
|
|
8
|
+
readonly records: WordPressRecords;
|
|
9
|
+
/** Which account on this site the plugin's actor becomes. */
|
|
10
|
+
readonly username: string;
|
|
11
|
+
/**
|
|
12
|
+
* The actor's id exactly as WordPress published it, query string and all:
|
|
13
|
+
* `https://example.com/?author=2`.
|
|
14
|
+
*/
|
|
15
|
+
readonly actorId: string;
|
|
16
|
+
/** The WordPress user id, which is the number in every one of its paths. */
|
|
17
|
+
readonly wordpressActorId: number;
|
|
18
|
+
/** The private key as PEM, PKCS#1 or PKCS#8, as `wp` printed it. */
|
|
19
|
+
readonly privateKeyPem: string;
|
|
20
|
+
/**
|
|
21
|
+
* The public key as PEM, when the export carried one.
|
|
22
|
+
*
|
|
23
|
+
* Checked against the private half rather than stored: the public key is
|
|
24
|
+
* derivable, so the only thing a second file can tell us is whether the two
|
|
25
|
+
* halves of the export belong together — and a pair that does not is an
|
|
26
|
+
* export somebody got wrong, which is worth finding out now rather than
|
|
27
|
+
* after the DNS has moved.
|
|
28
|
+
*/
|
|
29
|
+
readonly publicKeyPem?: string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Where the followers come from.
|
|
32
|
+
*
|
|
33
|
+
* Left off, the plugin's public followers collection on the actor id's own
|
|
34
|
+
* origin, which is the whole point of running this before the DNS moves. A
|
|
35
|
+
* URL asks a different one; a path reads a saved collection out of a file,
|
|
36
|
+
* for a site that is already gone; `false` skips the step, for a run that is
|
|
37
|
+
* only there to put the key and the ids in place.
|
|
38
|
+
*/
|
|
39
|
+
readonly followers?: string | false | undefined;
|
|
40
|
+
/** Replace a key pair this user already has. Refused without it. */
|
|
41
|
+
readonly force?: boolean | undefined;
|
|
42
|
+
}
|
|
43
|
+
/** What happened to one key file. */
|
|
44
|
+
export interface ImportedKey {
|
|
45
|
+
/** The file, absolute. */
|
|
46
|
+
readonly file: string;
|
|
47
|
+
/** Which pair it holds. */
|
|
48
|
+
readonly algorithm: PluginKeyAlgorithm;
|
|
49
|
+
/** What the run did with it. */
|
|
50
|
+
readonly state: 'imported' | 'generated' | 'unchanged' | 'replaced';
|
|
51
|
+
}
|
|
52
|
+
/** One follower the collection named and nothing could be made of. */
|
|
53
|
+
export interface UnreachableFollower {
|
|
54
|
+
/** The actor the collection named. */
|
|
55
|
+
readonly actor: string;
|
|
56
|
+
/** Why it was skipped, for the report. */
|
|
57
|
+
readonly reason: string;
|
|
58
|
+
}
|
|
59
|
+
/** What the followers step did. */
|
|
60
|
+
export interface FollowersImportReport {
|
|
61
|
+
/** The collection it read, or `undefined` when the step was skipped. */
|
|
62
|
+
readonly source: string | undefined;
|
|
63
|
+
/** Actors written into the file for the first time. */
|
|
64
|
+
readonly added: readonly string[];
|
|
65
|
+
/** Actors the file already held, with nothing about them changed. */
|
|
66
|
+
readonly unchanged: readonly string[];
|
|
67
|
+
/** Actors whose profile the file held and this run refreshed. */
|
|
68
|
+
readonly refreshed: readonly string[];
|
|
69
|
+
/**
|
|
70
|
+
* Actors that could not be turned into followers, and why.
|
|
71
|
+
*
|
|
72
|
+
* Reported rather than fatal: an instance that is down, or an account that
|
|
73
|
+
* has been deleted, must not stop the other hundred followers from coming
|
|
74
|
+
* across, and the owner can run the command again once it is back.
|
|
75
|
+
*/
|
|
76
|
+
readonly failed: readonly UnreachableFollower[];
|
|
77
|
+
}
|
|
78
|
+
/** What the run did. */
|
|
79
|
+
export interface ImportWordPressActorReport {
|
|
80
|
+
/** Who was imported. */
|
|
81
|
+
readonly username: string;
|
|
82
|
+
/** The id their followers hold. */
|
|
83
|
+
readonly actorId: string;
|
|
84
|
+
/** The number the plugin's paths are built from. */
|
|
85
|
+
readonly wordpressActorId: number;
|
|
86
|
+
/**
|
|
87
|
+
* Whether the two ids had to be written, or the record already said so.
|
|
88
|
+
*/
|
|
89
|
+
readonly identity: 'set' | 'unchanged';
|
|
90
|
+
/** One entry per key file, RSA first, as the key loader orders them. */
|
|
91
|
+
readonly keys: readonly ImportedKey[];
|
|
92
|
+
/** What came of the followers collection. */
|
|
93
|
+
readonly followers: FollowersImportReport;
|
|
94
|
+
/** Whether anything on disk is different because of this run. */
|
|
95
|
+
readonly changed: boolean;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Thrown when the user already has a key pair and this run was not told to
|
|
99
|
+
* replace it.
|
|
100
|
+
*
|
|
101
|
+
* Its own class because the answer is a flag rather than a correction, and
|
|
102
|
+
* because losing a private key is the one mistake this CMS cannot help with:
|
|
103
|
+
* every follower's server has cached the public half, and a new key means none
|
|
104
|
+
* of them can verify this person again.
|
|
105
|
+
*/
|
|
106
|
+
export declare class ExistingKeyPairError extends Error {
|
|
107
|
+
readonly name = "ExistingKeyPairError";
|
|
108
|
+
/** The file that is already there. */
|
|
109
|
+
readonly file: string;
|
|
110
|
+
constructor(username: string, file: string);
|
|
111
|
+
}
|
|
112
|
+
/** Thrown when the PEM is not an RSA private key at all. */
|
|
113
|
+
export declare class UnusableKeyPemError extends Error {
|
|
114
|
+
readonly name = "UnusableKeyPemError";
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Bring one WordPress actor's key pair across, and say what changed.
|
|
118
|
+
*
|
|
119
|
+
* Run twice with the same inputs it changes nothing and says so, which is what
|
|
120
|
+
* makes it safe to run again after a follower's server was down.
|
|
121
|
+
*/
|
|
122
|
+
export declare function importWordPressActor(options: ImportWordPressActorOptions): Promise<ImportWordPressActorReport>;
|
|
123
|
+
//# sourceMappingURL=import.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../src/import.ts"],"names":[],"mappings":"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"}
|