@sockethub/server 5.0.0-alpha.11 → 5.0.0-alpha.12
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/defaults.json +4 -0
- package/dist/index.js +46734 -46237
- package/dist/index.js.map +198 -196
- package/dist/platform.js +17926 -1483
- package/dist/platform.js.map +180 -31
- package/package.json +14 -14
- package/res/sockethub-client.js +17452 -288
- package/res/sockethub-client.min.js +32 -4
- package/src/bootstrap/init.test-helpers.ts +75 -0
- package/src/bootstrap/init.test.ts +70 -106
- package/src/bootstrap/init.ts +45 -27
- package/src/bootstrap/load-platforms.ts +39 -8
- package/src/config.ts +12 -1
- package/src/defaults.json +4 -0
- package/src/index.ts +9 -7
- package/src/janitor.ts +3 -1
- package/src/listener.ts +8 -9
- package/src/middleware/create-activity-object.ts +1 -1
- package/src/middleware/expand-activity-stream.test.data.ts +30 -23
- package/src/middleware/expand-activity-stream.ts +11 -8
- package/src/middleware/store-credentials.test.ts +5 -1
- package/src/middleware/store-credentials.ts +11 -5
- package/src/middleware/validate.test.data.ts +132 -16
- package/src/middleware/validate.test.ts +6 -2
- package/src/middleware/validate.ts +137 -30
- package/src/middleware.ts +26 -22
- package/src/platform-instance.test.ts +41 -6
- package/src/platform-instance.ts +164 -25
- package/src/platform.test.ts +11 -2
- package/src/platform.ts +135 -19
- package/src/process-manager.ts +30 -4
- package/src/rate-limiter.ts +5 -1
- package/src/routes.ts +16 -8
- package/src/sockethub.ts +192 -83
|
@@ -4,22 +4,89 @@
|
|
|
4
4
|
|
|
5
5
|
import { createLogger } from "@sockethub/logger";
|
|
6
6
|
import {
|
|
7
|
+
type ActivityObject,
|
|
7
8
|
type ActivityStream,
|
|
8
|
-
|
|
9
|
+
AS2_BASE_CONTEXT_URL,
|
|
10
|
+
buildCanonicalContext,
|
|
11
|
+
resolvePlatformId,
|
|
12
|
+
SOCKETHUB_BASE_CONTEXT_URL,
|
|
9
13
|
validateActivityObject,
|
|
10
14
|
validateActivityStream,
|
|
11
15
|
validateCredentials,
|
|
12
16
|
} from "@sockethub/schemas";
|
|
13
17
|
|
|
14
18
|
import getInitObject, { type IInitObject } from "../bootstrap/init.js";
|
|
19
|
+
import type { MiddlewareChainInterface } from "../middleware.js";
|
|
15
20
|
|
|
16
21
|
// called when registered with the middleware function, define the type of validation
|
|
17
22
|
// that will be called when the middleware eventually does.
|
|
23
|
+
export default function validate<T extends ActivityObject>(
|
|
24
|
+
type: "activity-object",
|
|
25
|
+
sockethubId: string,
|
|
26
|
+
passedInitObj?: IInitObject,
|
|
27
|
+
): (msg: T, done: MiddlewareChainInterface<T>) => void;
|
|
28
|
+
export default function validate<T extends ActivityStream>(
|
|
29
|
+
type: "credentials" | "message",
|
|
30
|
+
sockethubId: string,
|
|
31
|
+
passedInitObj?: IInitObject,
|
|
32
|
+
): (msg: T, done: MiddlewareChainInterface<T>) => void;
|
|
18
33
|
export default function validate(
|
|
19
34
|
type: string,
|
|
20
35
|
sockethubId: string,
|
|
21
36
|
passedInitObj?: IInitObject,
|
|
22
37
|
) {
|
|
38
|
+
const baseContextUrls = new Set([
|
|
39
|
+
AS2_BASE_CONTEXT_URL,
|
|
40
|
+
SOCKETHUB_BASE_CONTEXT_URL,
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const getLegacyContext = (stream: ActivityStream): string | undefined => {
|
|
44
|
+
const legacyContext = (stream as ActivityStream & { context?: unknown })
|
|
45
|
+
.context;
|
|
46
|
+
return typeof legacyContext === "string" ? legacyContext : undefined;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getContextValues = (stream: ActivityStream): Array<string> => {
|
|
50
|
+
if (!Array.isArray(stream["@context"])) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
return stream["@context"].filter(
|
|
54
|
+
(value): value is string => typeof value === "string",
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const getPlatformContextCandidates = (
|
|
59
|
+
stream: ActivityStream,
|
|
60
|
+
): Array<string> => {
|
|
61
|
+
return getContextValues(stream).filter(
|
|
62
|
+
(value) => !baseContextUrls.has(value),
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const normalizeLegacyContext = (
|
|
67
|
+
stream: ActivityStream,
|
|
68
|
+
initObj: IInitObject,
|
|
69
|
+
) => {
|
|
70
|
+
if (resolvePlatformId(stream)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const legacyContext = getLegacyContext(stream);
|
|
74
|
+
if (!legacyContext) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const platformMeta = initObj.platforms.get(legacyContext);
|
|
78
|
+
if (!platformMeta) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const platformContextUrl =
|
|
82
|
+
platformMeta.contextUrl ||
|
|
83
|
+
`https://sockethub.org/ns/context/platform/${legacyContext}/v1.jsonld`;
|
|
84
|
+
// Intentionally mutate the incoming stream so legacy `context` payloads
|
|
85
|
+
// continue through canonical @context/platform validation paths.
|
|
86
|
+
stream["@context"] = buildCanonicalContext(platformContextUrl);
|
|
87
|
+
stream.platform = legacyContext;
|
|
88
|
+
};
|
|
89
|
+
|
|
23
90
|
let initObj = passedInitObj;
|
|
24
91
|
if (!passedInitObj) {
|
|
25
92
|
getInitObject().then((init) => {
|
|
@@ -28,45 +95,85 @@ export default function validate(
|
|
|
28
95
|
}
|
|
29
96
|
|
|
30
97
|
const sessionLog = createLogger(`server:validate:${sockethubId}`);
|
|
31
|
-
return (
|
|
98
|
+
return (
|
|
99
|
+
msg: ActivityStream | ActivityObject,
|
|
100
|
+
done: MiddlewareChainInterface<ActivityStream | ActivityObject>,
|
|
101
|
+
) => {
|
|
32
102
|
sessionLog.debug(`applying schema validation for ${type}`);
|
|
33
103
|
if (type === "activity-object") {
|
|
34
|
-
const err = validateActivityObject(msg);
|
|
35
|
-
if (err) {
|
|
36
|
-
done(new Error(err));
|
|
37
|
-
} else {
|
|
38
|
-
done(msg);
|
|
39
|
-
}
|
|
40
|
-
} else if (!initObj.platforms.has(msg.context)) {
|
|
41
|
-
return done(
|
|
42
|
-
new Error(
|
|
43
|
-
`platform context ${msg.context} not registered with this Sockethub instance.`,
|
|
44
|
-
),
|
|
45
|
-
);
|
|
46
|
-
} else if (type === "credentials") {
|
|
47
|
-
const err = validateCredentials(msg);
|
|
104
|
+
const err = validateActivityObject(msg as ActivityObject);
|
|
48
105
|
if (err) {
|
|
49
106
|
done(new Error(err));
|
|
50
107
|
} else {
|
|
51
108
|
done(msg);
|
|
52
109
|
}
|
|
53
110
|
} else {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
111
|
+
if (!initObj) {
|
|
112
|
+
return done(
|
|
113
|
+
new Error(
|
|
114
|
+
"Sockethub platforms not initialized for validation.",
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const stream = msg as ActivityStream;
|
|
119
|
+
// Intentional mutation for backward compatibility with legacy `context`.
|
|
120
|
+
normalizeLegacyContext(stream, initObj);
|
|
121
|
+
const platformId = resolvePlatformId(stream);
|
|
122
|
+
if (!platformId) {
|
|
123
|
+
const platformContextCandidates =
|
|
124
|
+
getPlatformContextCandidates(stream);
|
|
125
|
+
const contextDetails =
|
|
126
|
+
platformContextCandidates.length === 0
|
|
127
|
+
? " No platform @context values were provided."
|
|
128
|
+
: platformContextCandidates.length > 1
|
|
129
|
+
? ` Multiple platform @context values were provided: ${platformContextCandidates.join(", ")}.`
|
|
130
|
+
: ` Unregistered platform @context value: ${platformContextCandidates[0]}`;
|
|
131
|
+
return done(
|
|
132
|
+
new Error(
|
|
133
|
+
`platform context URL not registered with this Sockethub instance.${contextDetails}`,
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (!initObj.platforms.has(platformId)) {
|
|
138
|
+
return done(
|
|
139
|
+
new Error(
|
|
140
|
+
`platform ${platformId} resolved from @context is not enabled in this Sockethub instance.`,
|
|
141
|
+
),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
stream.platform = platformId;
|
|
145
|
+
if (type === "credentials") {
|
|
146
|
+
const err = validateCredentials(stream);
|
|
147
|
+
if (err) {
|
|
148
|
+
done(new Error(err));
|
|
149
|
+
} else {
|
|
150
|
+
done(stream);
|
|
151
|
+
}
|
|
57
152
|
} else {
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
153
|
+
const err = validateActivityStream(stream);
|
|
154
|
+
if (err) {
|
|
155
|
+
done(new Error(err));
|
|
156
|
+
} else {
|
|
157
|
+
const platformMeta = initObj.platforms.get(platformId);
|
|
158
|
+
if (!platformMeta) {
|
|
159
|
+
return done(
|
|
160
|
+
new Error(
|
|
161
|
+
`platform ${platformId} not registered with this Sockethub instance.`,
|
|
162
|
+
),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (!platformMeta.types.includes(stream.type)) {
|
|
166
|
+
return done(
|
|
167
|
+
new Error(
|
|
168
|
+
`platform type ${stream.type} not supported by ${platformId} ` +
|
|
169
|
+
`platform. (types: ${platformMeta.types.join(
|
|
170
|
+
", ",
|
|
171
|
+
)})`,
|
|
172
|
+
),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
done(stream);
|
|
68
176
|
}
|
|
69
|
-
done(msg);
|
|
70
177
|
}
|
|
71
178
|
}
|
|
72
179
|
};
|
package/src/middleware.ts
CHANGED
|
@@ -5,24 +5,27 @@ import type {
|
|
|
5
5
|
Logger,
|
|
6
6
|
} from "@sockethub/schemas";
|
|
7
7
|
|
|
8
|
-
export default function middleware(name: string): MiddlewareChain {
|
|
9
|
-
return new MiddlewareChain(name);
|
|
8
|
+
export default function middleware<T>(name: string): MiddlewareChain<T> {
|
|
9
|
+
return new MiddlewareChain<T>(name);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export type
|
|
13
|
-
error: ActivityStream | ActivityObject | Error,
|
|
14
|
-
data?: ActivityStream | ActivityObject | MiddlewareChainInterface,
|
|
15
|
-
next?: MiddlewareChainInterface,
|
|
16
|
-
) => void;
|
|
12
|
+
export type MiddlewareNext<T> = (data?: T | Error) => void;
|
|
17
13
|
|
|
18
|
-
type
|
|
14
|
+
export type MiddlewareHandler<T> = (data: T, next: MiddlewareNext<T>) => void;
|
|
15
|
+
export type MiddlewareErrorHandler<T> = (
|
|
16
|
+
err: Error,
|
|
17
|
+
data: T,
|
|
18
|
+
next: MiddlewareNext<T>,
|
|
19
|
+
) => void;
|
|
19
20
|
|
|
20
21
|
export type LogErrorInterface = (msg: Error) => void;
|
|
22
|
+
export type MiddlewareChainInterface<T = ActivityStream | ActivityObject> =
|
|
23
|
+
MiddlewareNext<T>;
|
|
21
24
|
|
|
22
|
-
export class MiddlewareChain {
|
|
25
|
+
export class MiddlewareChain<T> {
|
|
23
26
|
public name: string;
|
|
24
|
-
private chain: Array<
|
|
25
|
-
private errHandler:
|
|
27
|
+
private chain: Array<MiddlewareHandler<T>> = [];
|
|
28
|
+
private errHandler: MiddlewareErrorHandler<T> = (err: Error) => {
|
|
26
29
|
throw err;
|
|
27
30
|
};
|
|
28
31
|
private readonly logger: Logger;
|
|
@@ -32,16 +35,16 @@ export class MiddlewareChain {
|
|
|
32
35
|
this.logger = createLogger(`middleware:${name}`);
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
use(func:
|
|
38
|
+
use(func: MiddlewareErrorHandler<T> | MiddlewareHandler<T>): this {
|
|
36
39
|
if (typeof func !== "function") {
|
|
37
40
|
throw new Error(
|
|
38
41
|
"middleware use() can only take a function as an argument",
|
|
39
42
|
);
|
|
40
43
|
}
|
|
41
44
|
if (func.length === 3) {
|
|
42
|
-
this.errHandler = func as
|
|
45
|
+
this.errHandler = func as MiddlewareErrorHandler<T>;
|
|
43
46
|
} else if (func.length === 2) {
|
|
44
|
-
this.chain.push(func as
|
|
47
|
+
this.chain.push(func as MiddlewareHandler<T>);
|
|
45
48
|
} else {
|
|
46
49
|
throw new Error(
|
|
47
50
|
`middleware function provided with incorrect number of params: ${func.length}`,
|
|
@@ -51,22 +54,23 @@ export class MiddlewareChain {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
done() {
|
|
54
|
-
return (data:
|
|
55
|
-
let cb = callbackFunc
|
|
57
|
+
return (data: T, callbackFunc: MiddlewareNext<T>) => {
|
|
58
|
+
let cb = callbackFunc as MiddlewareNext<T>;
|
|
56
59
|
let position = 0;
|
|
57
60
|
if (typeof callbackFunc !== "function") {
|
|
58
61
|
cb = () => {
|
|
59
62
|
// ensure we have a callback function
|
|
60
63
|
};
|
|
61
64
|
}
|
|
62
|
-
const next = (_data
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.
|
|
65
|
+
const next: MiddlewareNext<T> = (_data) => {
|
|
66
|
+
const nextData = _data === undefined ? data : _data;
|
|
67
|
+
if (nextData instanceof Error) {
|
|
68
|
+
this.logger.error(nextData.toString());
|
|
69
|
+
this.errHandler(nextData, data, cb);
|
|
66
70
|
} else if (typeof this.chain[position] === "function") {
|
|
67
|
-
this.chain[position++](
|
|
71
|
+
this.chain[position++](nextData as T, next);
|
|
68
72
|
} else {
|
|
69
|
-
cb(
|
|
73
|
+
cb(nextData as T);
|
|
70
74
|
}
|
|
71
75
|
};
|
|
72
76
|
next(data);
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
2
|
import * as sinon from "sinon";
|
|
3
|
+
import {
|
|
4
|
+
buildCanonicalContext,
|
|
5
|
+
INTERNAL_PLATFORM_CONTEXT_URL,
|
|
6
|
+
} from "@sockethub/schemas";
|
|
3
7
|
import { __dirname } from "./util.js";
|
|
4
8
|
const FORK_PATH = __dirname + "/platform.js";
|
|
5
9
|
|
|
@@ -173,8 +177,37 @@ describe("PlatformInstance", () => {
|
|
|
173
177
|
sandbox.assert.calledOnce(socketMock.emit);
|
|
174
178
|
sandbox.assert.calledWith(socketMock.emit, "message", {
|
|
175
179
|
foo: "this is a message object",
|
|
176
|
-
|
|
180
|
+
platform: "a platform name",
|
|
181
|
+
"@context": buildCanonicalContext(
|
|
182
|
+
INTERNAL_PLATFORM_CONTEXT_URL,
|
|
183
|
+
),
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("injects platform-specific @context when contextUrl is set", async () => {
|
|
188
|
+
const testContextUrl =
|
|
189
|
+
"https://sockethub.org/ns/context/platform/dummy/v1.jsonld";
|
|
190
|
+
pi.contextUrl = testContextUrl;
|
|
191
|
+
await pi.sendToClient("my session id", {
|
|
192
|
+
type: "echo",
|
|
193
|
+
actor: { id: "test@dummy", type: "person" },
|
|
194
|
+
});
|
|
195
|
+
sandbox.assert.calledOnce(socketMock.emit);
|
|
196
|
+
const emittedMsg = socketMock.emit.firstCall.args[1];
|
|
197
|
+
expect(emittedMsg["@context"]).toEqual(
|
|
198
|
+
buildCanonicalContext(testContextUrl),
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("strips legacy context field from outbound payloads", async () => {
|
|
203
|
+
await pi.sendToClient("my session id", {
|
|
204
|
+
type: "echo",
|
|
205
|
+
context: "dummy",
|
|
206
|
+
actor: { id: "test@dummy", type: "person" },
|
|
177
207
|
});
|
|
208
|
+
const emittedMsg = socketMock.emit.firstCall.args[1];
|
|
209
|
+
expect(emittedMsg.context).toBeUndefined();
|
|
210
|
+
expect(emittedMsg["@context"]).toBeDefined();
|
|
178
211
|
});
|
|
179
212
|
|
|
180
213
|
it("broadcasts to peers", async () => {
|
|
@@ -212,6 +245,7 @@ describe("PlatformInstance", () => {
|
|
|
212
245
|
expect(pi.broadcastToSharedPeers.callCount).toEqual(1);
|
|
213
246
|
sandbox.assert.calledWith(pi.sendToClient, "a session id", {
|
|
214
247
|
foo: "bar",
|
|
248
|
+
platform: "a platform name",
|
|
215
249
|
});
|
|
216
250
|
});
|
|
217
251
|
|
|
@@ -225,6 +259,7 @@ describe("PlatformInstance", () => {
|
|
|
225
259
|
sandbox.assert.calledWith(pi.sendToClient, "a session id", {
|
|
226
260
|
foo: "bar",
|
|
227
261
|
error: "a bad result message",
|
|
262
|
+
platform: "a platform name",
|
|
228
263
|
});
|
|
229
264
|
});
|
|
230
265
|
});
|
|
@@ -353,7 +388,7 @@ describe("PlatformInstance", () => {
|
|
|
353
388
|
sessionId: "session123",
|
|
354
389
|
msg: {
|
|
355
390
|
type: "connect",
|
|
356
|
-
|
|
391
|
+
platform: "xmpp",
|
|
357
392
|
actor: { id: "testuser@localhost", type: "person" },
|
|
358
393
|
},
|
|
359
394
|
title: "xmpp-1",
|
|
@@ -405,7 +440,7 @@ describe("PlatformInstance", () => {
|
|
|
405
440
|
sessionId: "session123",
|
|
406
441
|
msg: {
|
|
407
442
|
type: "connect",
|
|
408
|
-
|
|
443
|
+
platform: "xmpp",
|
|
409
444
|
actor: { id: "testuser@localhost", type: "person" },
|
|
410
445
|
},
|
|
411
446
|
title: "xmpp-1",
|
|
@@ -424,7 +459,7 @@ describe("PlatformInstance", () => {
|
|
|
424
459
|
sessionId: "session456",
|
|
425
460
|
msg: {
|
|
426
461
|
type: "send",
|
|
427
|
-
|
|
462
|
+
platform: "xmpp",
|
|
428
463
|
actor: { id: "testuser@localhost", type: "person" },
|
|
429
464
|
},
|
|
430
465
|
title: "xmpp-2",
|
|
@@ -465,7 +500,7 @@ describe("PlatformInstance", () => {
|
|
|
465
500
|
sessionId: "session123",
|
|
466
501
|
msg: {
|
|
467
502
|
type: "connect",
|
|
468
|
-
|
|
503
|
+
platform: "xmpp",
|
|
469
504
|
actor: { id: "testuser@localhost", type: "person" },
|
|
470
505
|
},
|
|
471
506
|
title: "xmpp-1",
|
|
@@ -514,7 +549,7 @@ describe("PlatformInstance", () => {
|
|
|
514
549
|
sessionId: "session123",
|
|
515
550
|
msg: {
|
|
516
551
|
type: "connect",
|
|
517
|
-
|
|
552
|
+
platform: "xmpp",
|
|
518
553
|
actor: { id: "testuser@localhost", type: "person" },
|
|
519
554
|
},
|
|
520
555
|
title: "xmpp-1",
|