@sockethub/client 5.0.0-alpha.12 → 5.0.0-alpha.14
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/README.md +47 -21
- package/dist/sockethub-client.browser.js +404 -728
- package/dist/sockethub-client.d.ts +280 -0
- package/dist/sockethub-client.js +398 -715
- package/dist/sockethub-client.min.js +21 -36
- package/package.json +6 -7
- package/src/sockethub-client.test.ts +0 -797
- package/src/sockethub-client.ts +0 -1095
|
@@ -1,797 +0,0 @@
|
|
|
1
|
-
import type { ASManager } from "@sockethub/activity-streams";
|
|
2
|
-
import { expect } from "chai";
|
|
3
|
-
import EventEmitter from "eventemitter3";
|
|
4
|
-
import { createSandbox, restore } from "sinon";
|
|
5
|
-
|
|
6
|
-
import SockethubClient from "./sockethub-client";
|
|
7
|
-
|
|
8
|
-
const TEST_REGISTRY = {
|
|
9
|
-
version: "5.0.0-alpha.11",
|
|
10
|
-
contexts: {
|
|
11
|
-
as: "https://example.com/as2",
|
|
12
|
-
sockethub: "https://example.com/sh",
|
|
13
|
-
},
|
|
14
|
-
platforms: [
|
|
15
|
-
{
|
|
16
|
-
id: "xmpp",
|
|
17
|
-
version: "1.0.0",
|
|
18
|
-
contextUrl: "https://example.com/context/platform/xmpp/v9.jsonld",
|
|
19
|
-
contextVersion: "9",
|
|
20
|
-
schemaVersion: "9",
|
|
21
|
-
types: ["connect", "send", "join", "leave", "disconnect"],
|
|
22
|
-
schemas: {
|
|
23
|
-
messages: {},
|
|
24
|
-
credentials: {},
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
id: "dummy",
|
|
29
|
-
version: "1.0.0",
|
|
30
|
-
contextUrl: "https://example.com/context/platform/dummy/v1.jsonld",
|
|
31
|
-
contextVersion: "1",
|
|
32
|
-
schemaVersion: "1",
|
|
33
|
-
types: ["echo"],
|
|
34
|
-
schemas: {
|
|
35
|
-
messages: {},
|
|
36
|
-
credentials: {},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
describe("SockethubClient bad initialization", () => {
|
|
43
|
-
it("no socket.io instance", () => {
|
|
44
|
-
class TestSockethubClient extends SockethubClient {
|
|
45
|
-
initActivityStreams() {
|
|
46
|
-
this.ActivityStreams = {} as ASManager;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
expect(() => {
|
|
50
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
51
|
-
// @ts-ignore
|
|
52
|
-
new TestSockethubClient();
|
|
53
|
-
}).to.throw("SockethubClient requires a socket.io instance");
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe("SockethubClient", () => {
|
|
58
|
-
let asInstance: any, socket: any, sc: any, sandbox: any;
|
|
59
|
-
|
|
60
|
-
beforeEach(() => {
|
|
61
|
-
sandbox = sandbox = createSandbox();
|
|
62
|
-
socket = new EventEmitter();
|
|
63
|
-
socket.connected = false;
|
|
64
|
-
socket.__instance = "socketio"; // used to uniquely identify the object we're passing in
|
|
65
|
-
sandbox.spy(socket, "on");
|
|
66
|
-
sandbox.spy(socket, "emit");
|
|
67
|
-
asInstance = new EventEmitter();
|
|
68
|
-
sandbox.spy(asInstance, "on");
|
|
69
|
-
sandbox.spy(asInstance, "emit");
|
|
70
|
-
asInstance.Stream = sandbox.stub().callsFake((stream: any) => {
|
|
71
|
-
if (!stream || typeof stream !== "object") {
|
|
72
|
-
return stream;
|
|
73
|
-
}
|
|
74
|
-
const next = { ...stream };
|
|
75
|
-
if (typeof next.actor === "string") {
|
|
76
|
-
next.actor = { id: next.actor };
|
|
77
|
-
}
|
|
78
|
-
if (typeof next.target === "string") {
|
|
79
|
-
next.target = { id: next.target };
|
|
80
|
-
}
|
|
81
|
-
if (typeof next.object === "string") {
|
|
82
|
-
next.object = { content: next.object };
|
|
83
|
-
}
|
|
84
|
-
return next;
|
|
85
|
-
});
|
|
86
|
-
asInstance.Object = {
|
|
87
|
-
create: sandbox.stub(),
|
|
88
|
-
};
|
|
89
|
-
class TestSockethubClient extends SockethubClient {
|
|
90
|
-
initActivityStreams() {
|
|
91
|
-
this.ActivityStreams = asInstance as ASManager;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
sc = new TestSockethubClient(socket);
|
|
95
|
-
sandbox.spy(sc.socket, "on");
|
|
96
|
-
sandbox.spy(sc.socket, "emit");
|
|
97
|
-
sandbox.spy(sc.socket, "_emit");
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
afterEach(() => {
|
|
101
|
-
restore();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("contains the ActivityStreams property", () => {
|
|
105
|
-
expect(asInstance).to.be.eql(sc.ActivityStreams);
|
|
106
|
-
expect(typeof asInstance.Stream).to.equal("function");
|
|
107
|
-
expect(typeof sc.ActivityStreams.Object.create).to.equal("function");
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("contains the socket property", () => {
|
|
111
|
-
expect(sc.socket instanceof EventEmitter).to.be.true;
|
|
112
|
-
// the object we passed in should not be the publicly available one
|
|
113
|
-
expect(sc.socket.__instance).to.not.equal("socketio");
|
|
114
|
-
expect(sc.debug).to.be.true;
|
|
115
|
-
expect(sc.socket.connected).to.be.false;
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("registers listeners for ActivityStream events", () => {
|
|
119
|
-
expect(asInstance.on.callCount).to.equal(1);
|
|
120
|
-
expect(asInstance.on.calledWithMatch("activity-object-create")).to.be
|
|
121
|
-
.true;
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("registers a listeners for socket events", () => {
|
|
125
|
-
expect(socket.on.callCount).to.equal(6);
|
|
126
|
-
expect(socket.on.calledWithMatch("activity-object")).to.be.true;
|
|
127
|
-
expect(socket.on.calledWithMatch("connect")).to.be.true;
|
|
128
|
-
expect(socket.on.calledWithMatch("connect_error")).to.be.true;
|
|
129
|
-
expect(socket.on.calledWithMatch("disconnect")).to.be.true;
|
|
130
|
-
expect(socket.on.calledWithMatch("message")).to.be.true;
|
|
131
|
-
expect(socket.on.calledWithMatch("schemas")).to.be.true;
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
describe("contextFor", () => {
|
|
135
|
-
it("tracks schema readiness", () => {
|
|
136
|
-
expect(sc.isSchemasReady()).to.equal(false);
|
|
137
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
138
|
-
expect(sc.isSchemasReady()).to.equal(true);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("waitForSchemas resolves once registry arrives", async () => {
|
|
142
|
-
socket.io = {};
|
|
143
|
-
socket.connected = true;
|
|
144
|
-
sc.socket.connected = true;
|
|
145
|
-
socket.on("schemas", (ack: any) => {
|
|
146
|
-
if (typeof ack === "function") {
|
|
147
|
-
ack(TEST_REGISTRY);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
const registry = await sc.waitForSchemas();
|
|
151
|
-
expect(registry.contexts).to.eql({
|
|
152
|
-
as: "https://example.com/as2",
|
|
153
|
-
sockethub: "https://example.com/sh",
|
|
154
|
-
});
|
|
155
|
-
expect(registry.platforms?.[0]?.id).to.equal("xmpp");
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("throws before schema registry is loaded", () => {
|
|
159
|
-
expect(() => sc.contextFor("xmpp")).to.throw(
|
|
160
|
-
"Schema registry not loaded yet",
|
|
161
|
-
);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it("throws when platform is missing", () => {
|
|
165
|
-
expect(() => sc.contextFor("")).to.throw(
|
|
166
|
-
"requires a non-empty platform string",
|
|
167
|
-
);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it("uses server-provided contexts and platform context URL", () => {
|
|
171
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
172
|
-
|
|
173
|
-
expect(sc.contextFor("xmpp")).to.eql([
|
|
174
|
-
"https://example.com/as2",
|
|
175
|
-
"https://example.com/sh",
|
|
176
|
-
"https://example.com/context/platform/xmpp/v9.jsonld",
|
|
177
|
-
]);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it("throws for unknown platform when registry is loaded", () => {
|
|
181
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
182
|
-
|
|
183
|
-
expect(() => sc.contextFor("irc")).to.throw(
|
|
184
|
-
"unknown platform 'irc'",
|
|
185
|
-
);
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
describe("initialization API", () => {
|
|
190
|
-
it("ready() resolves with ClientReadyInfo after server emits schemas", async () => {
|
|
191
|
-
socket.io = {};
|
|
192
|
-
socket.connected = true;
|
|
193
|
-
sc.socket.connected = true;
|
|
194
|
-
socket.on("schemas", (ack: any) => {
|
|
195
|
-
if (typeof ack === "function") {
|
|
196
|
-
ack(TEST_REGISTRY);
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
const info = await sc.ready(2000);
|
|
200
|
-
expect(info.state).to.equal("ready");
|
|
201
|
-
expect(info.reason).to.be.a("string");
|
|
202
|
-
expect(info.sockethubVersion).to.equal("5.0.0-alpha.11");
|
|
203
|
-
expect(info.platforms).to.be.an("array").with.length.greaterThan(0);
|
|
204
|
-
expect(info.contexts.as).to.equal("https://example.com/as2");
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it("ready() resolves immediately when already ready", async () => {
|
|
208
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
209
|
-
const info = await sc.ready();
|
|
210
|
-
expect(info.state).to.equal("ready");
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it("ready() rejects on timeout when schemas never arrive", async () => {
|
|
214
|
-
const timeoutSocket = new EventEmitter();
|
|
215
|
-
timeoutSocket.connected = true;
|
|
216
|
-
timeoutSocket.__instance = "socketio";
|
|
217
|
-
timeoutSocket.io = {};
|
|
218
|
-
sandbox.spy(timeoutSocket, "on");
|
|
219
|
-
sandbox.spy(timeoutSocket, "emit");
|
|
220
|
-
|
|
221
|
-
class TimeoutClient extends SockethubClient {
|
|
222
|
-
initActivityStreams() {
|
|
223
|
-
this.ActivityStreams = asInstance as ASManager;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
const client = new TimeoutClient(timeoutSocket);
|
|
227
|
-
client.socket.connected = true;
|
|
228
|
-
try {
|
|
229
|
-
await client.ready(50);
|
|
230
|
-
expect.fail("should have rejected");
|
|
231
|
-
} catch (err: any) {
|
|
232
|
-
expect(err.message).to.contain("timed out");
|
|
233
|
-
}
|
|
234
|
-
timeoutSocket.emit("disconnect");
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it("emits ready payload including sockethub and platform versions", (done) => {
|
|
238
|
-
sc.socket.on("ready", (info: any) => {
|
|
239
|
-
expect(info.state).to.equal("ready");
|
|
240
|
-
expect(info.sockethubVersion).to.equal("5.0.0-alpha.11");
|
|
241
|
-
expect(info.platforms[0]).to.include.keys([
|
|
242
|
-
"id",
|
|
243
|
-
"version",
|
|
244
|
-
"contextVersion",
|
|
245
|
-
"schemaVersion",
|
|
246
|
-
]);
|
|
247
|
-
done();
|
|
248
|
-
});
|
|
249
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it("emits init_error and timeout warning when schemas never arrive", (done) => {
|
|
253
|
-
const timeoutSocket = new EventEmitter();
|
|
254
|
-
timeoutSocket.connected = false;
|
|
255
|
-
timeoutSocket.__instance = "socketio";
|
|
256
|
-
sandbox.spy(timeoutSocket, "on");
|
|
257
|
-
sandbox.spy(timeoutSocket, "emit");
|
|
258
|
-
const warnStub = sandbox.stub(console, "warn");
|
|
259
|
-
|
|
260
|
-
class TimeoutSockethubClient extends SockethubClient {
|
|
261
|
-
initActivityStreams() {
|
|
262
|
-
this.ActivityStreams = asInstance as ASManager;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
const timeoutClient = new TimeoutSockethubClient(timeoutSocket, {
|
|
266
|
-
initTimeoutMs: 10,
|
|
267
|
-
});
|
|
268
|
-
timeoutClient.socket.on("init_error", (err: any) => {
|
|
269
|
-
expect(err.phase).to.equal("timeout");
|
|
270
|
-
expect(err.error).to.contain("timed out");
|
|
271
|
-
expect(
|
|
272
|
-
warnStub.calledWithMatch(
|
|
273
|
-
"Initialization timed out after 10ms",
|
|
274
|
-
),
|
|
275
|
-
).to.equal(true);
|
|
276
|
-
timeoutSocket.emit("disconnect");
|
|
277
|
-
done();
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
timeoutSocket.emit("connect");
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
describe("event handling", () => {
|
|
285
|
-
it("activity-object", (done) => {
|
|
286
|
-
socket.emit("activity-object", { foo: "bar" });
|
|
287
|
-
setTimeout(() => {
|
|
288
|
-
sandbox.assert.calledWith(asInstance.Object.create, {
|
|
289
|
-
foo: "bar",
|
|
290
|
-
});
|
|
291
|
-
done();
|
|
292
|
-
}, 0);
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it("activity-object-create", (done) => {
|
|
296
|
-
sc.socket.connected = true;
|
|
297
|
-
sc._socket.connected = true;
|
|
298
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
299
|
-
asInstance.emit("activity-object-create", { foo: "bar" });
|
|
300
|
-
setTimeout(() => {
|
|
301
|
-
expect(socket.emit.callCount).to.be.greaterThanOrEqual(2);
|
|
302
|
-
expect(
|
|
303
|
-
socket.emit.calledWithMatch("activity-object", {
|
|
304
|
-
foo: "bar",
|
|
305
|
-
}),
|
|
306
|
-
).to.be.true;
|
|
307
|
-
done();
|
|
308
|
-
}, 0);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it("activity-object-create stores object only after successful ACK", (done) => {
|
|
312
|
-
sc.socket.connected = true;
|
|
313
|
-
sc._socket.connected = true;
|
|
314
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
315
|
-
// Intercept the emit to capture the ACK callback
|
|
316
|
-
socket.on("activity-object", (_data: any, ackCb: any) => {
|
|
317
|
-
// Simulate successful ACK (no error)
|
|
318
|
-
if (typeof ackCb === "function") {
|
|
319
|
-
ackCb();
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
asInstance.emit("activity-object-create", {
|
|
323
|
-
id: "good-obj",
|
|
324
|
-
foo: "bar",
|
|
325
|
-
});
|
|
326
|
-
setTimeout(() => {
|
|
327
|
-
expect(sc.events["activity-object"].has("good-obj")).to.be.true;
|
|
328
|
-
done();
|
|
329
|
-
}, 0);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
it("activity-object-create does not store object on ACK error", (done) => {
|
|
333
|
-
sc.socket.connected = true;
|
|
334
|
-
sc._socket.connected = true;
|
|
335
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
336
|
-
// Intercept the emit to capture the ACK callback
|
|
337
|
-
socket.on("activity-object", (_data: any, ackCb: any) => {
|
|
338
|
-
// Simulate error ACK
|
|
339
|
-
if (typeof ackCb === "function") {
|
|
340
|
-
ackCb({ error: "rejected by server" });
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
asInstance.emit("activity-object-create", {
|
|
344
|
-
id: "bad-obj",
|
|
345
|
-
foo: "bar",
|
|
346
|
-
});
|
|
347
|
-
setTimeout(() => {
|
|
348
|
-
expect(sc.events["activity-object"].has("bad-obj")).to.be
|
|
349
|
-
.false;
|
|
350
|
-
done();
|
|
351
|
-
}, 0);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
it("connect", (done) => {
|
|
355
|
-
expect(sc.socket.connected).to.be.false;
|
|
356
|
-
socket.io = {};
|
|
357
|
-
socket.on("schemas", (ack: any) => {
|
|
358
|
-
if (typeof ack === "function") {
|
|
359
|
-
ack({
|
|
360
|
-
contexts: {
|
|
361
|
-
as: "https://example.com/as2",
|
|
362
|
-
sockethub: "https://example.com/sh",
|
|
363
|
-
},
|
|
364
|
-
platforms: [],
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
sc.socket.on("connect", () => {
|
|
369
|
-
setTimeout(() => {
|
|
370
|
-
expect(sc.socket.connected).to.be.true;
|
|
371
|
-
expect(sc.socket._emit.callCount).to.be.greaterThanOrEqual(
|
|
372
|
-
1,
|
|
373
|
-
);
|
|
374
|
-
expect(sc.socket._emit.calledWithMatch("connect"));
|
|
375
|
-
expect(socket.emit.calledWithMatch("schemas")).to.be.true;
|
|
376
|
-
done();
|
|
377
|
-
}, 0);
|
|
378
|
-
});
|
|
379
|
-
socket.emit("connect");
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
it("disconnect", (done) => {
|
|
383
|
-
sc.socket.connected = true;
|
|
384
|
-
sc.socket.on("disconnect", () => {
|
|
385
|
-
expect(sc.socket.connected).to.be.false;
|
|
386
|
-
expect(sc.socket._emit.callCount).to.equal(1);
|
|
387
|
-
expect(sc.socket._emit.calledWithMatch("disconnect"));
|
|
388
|
-
done();
|
|
389
|
-
});
|
|
390
|
-
socket.emit("disconnect");
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
it("connect_error", (done) => {
|
|
394
|
-
sc.socket.on("connect_error", () => {
|
|
395
|
-
expect(sc.socket._emit.callCount).to.equal(1);
|
|
396
|
-
expect(sc.socket._emit.calledWithMatch("connect_error"));
|
|
397
|
-
done();
|
|
398
|
-
});
|
|
399
|
-
socket.emit("connect_error");
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
it("schemas", (done) => {
|
|
403
|
-
sc.socket.on("schemas", (registry: any) => {
|
|
404
|
-
expect(registry.contexts).to.eql({
|
|
405
|
-
as: "https://example.com/as2",
|
|
406
|
-
sockethub: "https://example.com/sh",
|
|
407
|
-
});
|
|
408
|
-
expect(registry.platforms).to.be.an("array");
|
|
409
|
-
expect(registry.platforms[0]?.id).to.equal("xmpp");
|
|
410
|
-
done();
|
|
411
|
-
});
|
|
412
|
-
socket.emit("schemas", {
|
|
413
|
-
version: "5.0.0-alpha.11",
|
|
414
|
-
contexts: {
|
|
415
|
-
as: "https://example.com/as2",
|
|
416
|
-
sockethub: "https://example.com/sh",
|
|
417
|
-
},
|
|
418
|
-
platforms: [
|
|
419
|
-
{
|
|
420
|
-
id: "xmpp",
|
|
421
|
-
version: "1.0.0",
|
|
422
|
-
contextUrl:
|
|
423
|
-
"https://example.com/context/platform/xmpp/v9.jsonld",
|
|
424
|
-
contextVersion: "9",
|
|
425
|
-
schemaVersion: "9",
|
|
426
|
-
types: ["connect"],
|
|
427
|
-
schemas: {
|
|
428
|
-
messages: {},
|
|
429
|
-
credentials: {},
|
|
430
|
-
},
|
|
431
|
-
},
|
|
432
|
-
],
|
|
433
|
-
});
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it("message", (done) => {
|
|
437
|
-
sc.socket.on("message", () => {
|
|
438
|
-
expect(sc.socket._emit.callCount).to.equal(1);
|
|
439
|
-
expect(sc.socket._emit.calledWithMatch("message"));
|
|
440
|
-
done();
|
|
441
|
-
});
|
|
442
|
-
socket.emit("message");
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
describe("event emitting", () => {
|
|
447
|
-
beforeEach(() => {
|
|
448
|
-
sc._socket.connected = true;
|
|
449
|
-
sc.socket.connected = true;
|
|
450
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
451
|
-
sandbox.stub(sc, "validateActivity").returns("");
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
it("message (no actor) returns callback error", () => {
|
|
455
|
-
const callback = sandbox.spy();
|
|
456
|
-
sc.socket.emit("message", { foo: "bar" }, callback);
|
|
457
|
-
expect(callback.calledOnce).to.equal(true);
|
|
458
|
-
expect(callback.firstCall.args[0]?.error).to.contain(
|
|
459
|
-
"actor property not present",
|
|
460
|
-
);
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
it("returns validation error via callback when registry is loaded", () => {
|
|
464
|
-
(sc.validateActivity as any).returns(
|
|
465
|
-
"[xmpp] /actor: invalid actor for this activity",
|
|
466
|
-
);
|
|
467
|
-
const callback = sandbox.spy();
|
|
468
|
-
socket.emit.resetHistory();
|
|
469
|
-
|
|
470
|
-
expect(() => {
|
|
471
|
-
sc.socket.emit(
|
|
472
|
-
"message",
|
|
473
|
-
{ actor: "bar", type: "send" },
|
|
474
|
-
callback,
|
|
475
|
-
);
|
|
476
|
-
}).to.not.throw();
|
|
477
|
-
|
|
478
|
-
expect(sc.validateActivity.calledOnce).to.equal(true);
|
|
479
|
-
expect(callback.calledOnce).to.equal(true);
|
|
480
|
-
expect(callback.firstCall.args[0]).to.eql({
|
|
481
|
-
error:
|
|
482
|
-
"SockethubClient validation failed: [xmpp] /actor: invalid actor for this activity",
|
|
483
|
-
});
|
|
484
|
-
expect(socket.emit.calledWithMatch("message")).to.equal(false);
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
it("message", (done) => {
|
|
488
|
-
sc.socket.connected = true;
|
|
489
|
-
const callback = () => {};
|
|
490
|
-
socket.once("message", (data: any, cb: any) => {
|
|
491
|
-
expect(data).to.be.eql({
|
|
492
|
-
actor: { id: "bar", type: "person" },
|
|
493
|
-
type: "bar",
|
|
494
|
-
});
|
|
495
|
-
expect(cb).to.be.eql(callback);
|
|
496
|
-
done();
|
|
497
|
-
});
|
|
498
|
-
sc.socket.emit("message", { actor: "bar", type: "bar" }, callback);
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
it("message (join)", (done) => {
|
|
502
|
-
sc.socket.connected = true;
|
|
503
|
-
const callback = () => {};
|
|
504
|
-
socket.once("message", (data: any, cb: any) => {
|
|
505
|
-
expect(data).to.be.eql({
|
|
506
|
-
actor: { id: "bar", type: "person" },
|
|
507
|
-
type: "join",
|
|
508
|
-
});
|
|
509
|
-
expect(cb).to.be.eql(callback);
|
|
510
|
-
done();
|
|
511
|
-
});
|
|
512
|
-
sc.socket.emit("message", { actor: "bar", type: "join" }, callback);
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
it("message (leave)", (done) => {
|
|
516
|
-
sc.socket.connected = true;
|
|
517
|
-
const callback = () => {};
|
|
518
|
-
socket.once("message", (data: any, cb: any) => {
|
|
519
|
-
expect(data).to.be.eql({
|
|
520
|
-
actor: { id: "bar", type: "person" },
|
|
521
|
-
type: "leave",
|
|
522
|
-
});
|
|
523
|
-
expect(cb).to.be.eql(callback);
|
|
524
|
-
done();
|
|
525
|
-
});
|
|
526
|
-
sc.socket.emit(
|
|
527
|
-
"message",
|
|
528
|
-
{ actor: "bar", type: "leave" },
|
|
529
|
-
callback,
|
|
530
|
-
);
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
it("message (connect)", (done) => {
|
|
534
|
-
sc.socket.connected = true;
|
|
535
|
-
const callback = () => {};
|
|
536
|
-
socket.once("message", (data: any, cb: any) => {
|
|
537
|
-
expect(data).to.be.eql({
|
|
538
|
-
actor: { id: "bar", type: "person" },
|
|
539
|
-
type: "connect",
|
|
540
|
-
});
|
|
541
|
-
expect(cb).to.be.eql(callback);
|
|
542
|
-
done();
|
|
543
|
-
});
|
|
544
|
-
sc.socket.emit(
|
|
545
|
-
"message",
|
|
546
|
-
{ actor: "bar", type: "connect" },
|
|
547
|
-
callback,
|
|
548
|
-
);
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
it("message (disconnect)", (done) => {
|
|
552
|
-
sc.socket.connected = true;
|
|
553
|
-
const callback = () => {};
|
|
554
|
-
socket.once("message", (data: any, cb: any) => {
|
|
555
|
-
expect(data).to.be.eql({
|
|
556
|
-
actor: { id: "bar", type: "person" },
|
|
557
|
-
type: "disconnect",
|
|
558
|
-
});
|
|
559
|
-
expect(cb).to.be.eql(callback);
|
|
560
|
-
done();
|
|
561
|
-
});
|
|
562
|
-
sc.socket.emit(
|
|
563
|
-
"message",
|
|
564
|
-
{ actor: "bar", type: "disconnect" },
|
|
565
|
-
callback,
|
|
566
|
-
);
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
it("message (offline)", (done) => {
|
|
570
|
-
sc.socket.connected = false;
|
|
571
|
-
sc._socket.connected = false;
|
|
572
|
-
socket.emit("disconnect");
|
|
573
|
-
const callback = () => {};
|
|
574
|
-
sc.socket.emit("message", { actor: "bar" }, callback);
|
|
575
|
-
setTimeout(() => {
|
|
576
|
-
expect(
|
|
577
|
-
socket.emit
|
|
578
|
-
.getCalls()
|
|
579
|
-
.some((call: any) => call.args[0] === "message"),
|
|
580
|
-
).to.equal(false);
|
|
581
|
-
done();
|
|
582
|
-
}, 0);
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
it("activity-object", (done) => {
|
|
586
|
-
sc.socket.connected = true;
|
|
587
|
-
const callback = sandbox.spy();
|
|
588
|
-
socket.once("activity-object", (data: any, cb: any) => {
|
|
589
|
-
expect(data).to.be.eql({ actor: "bar" });
|
|
590
|
-
// Callback is wrapped to defer persistence until ACK
|
|
591
|
-
expect(typeof cb).to.equal("function");
|
|
592
|
-
cb(); // simulate successful ACK
|
|
593
|
-
expect(callback.calledOnce).to.be.true;
|
|
594
|
-
done();
|
|
595
|
-
});
|
|
596
|
-
sc.socket.emit("activity-object", { actor: "bar" }, callback);
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
it("credentials", (done) => {
|
|
600
|
-
sc.socket.connected = true;
|
|
601
|
-
const callback = () => {};
|
|
602
|
-
socket.once("credentials", (data: any, cb: any) => {
|
|
603
|
-
expect(data).to.be.eql({
|
|
604
|
-
type: "credentials",
|
|
605
|
-
actor: { id: "bar", type: "person" },
|
|
606
|
-
});
|
|
607
|
-
expect(cb).to.be.eql(callback);
|
|
608
|
-
done();
|
|
609
|
-
});
|
|
610
|
-
sc.socket.emit("credentials", { actor: "bar" }, callback);
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
it("queues outbound messages before ready and flushes after schemas", (done) => {
|
|
614
|
-
const preReadySocket = new EventEmitter();
|
|
615
|
-
preReadySocket.connected = false;
|
|
616
|
-
preReadySocket.__instance = "socketio";
|
|
617
|
-
sandbox.spy(preReadySocket, "on");
|
|
618
|
-
sandbox.spy(preReadySocket, "emit");
|
|
619
|
-
|
|
620
|
-
class TestSockethubClient extends SockethubClient {
|
|
621
|
-
initActivityStreams() {
|
|
622
|
-
this.ActivityStreams = asInstance as ASManager;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
const preReadyClient = new TestSockethubClient(preReadySocket);
|
|
626
|
-
sandbox.spy(preReadyClient.socket, "_emit");
|
|
627
|
-
sandbox.stub(preReadyClient, "validateActivity").returns("");
|
|
628
|
-
|
|
629
|
-
const callback = sandbox.spy();
|
|
630
|
-
preReadyClient.socket.emit(
|
|
631
|
-
"message",
|
|
632
|
-
{ actor: "bar", type: "join" },
|
|
633
|
-
callback,
|
|
634
|
-
);
|
|
635
|
-
expect(
|
|
636
|
-
preReadySocket.emit
|
|
637
|
-
.getCalls()
|
|
638
|
-
.some((call: any) => call.args[0] === "message"),
|
|
639
|
-
).to.equal(false);
|
|
640
|
-
|
|
641
|
-
preReadySocket.connected = true;
|
|
642
|
-
preReadyClient.socket.connected = true;
|
|
643
|
-
preReadySocket.emit("schemas", TEST_REGISTRY);
|
|
644
|
-
|
|
645
|
-
setTimeout(() => {
|
|
646
|
-
expect(
|
|
647
|
-
preReadySocket.emit
|
|
648
|
-
.getCalls()
|
|
649
|
-
.some((call: any) => call.args[0] === "message"),
|
|
650
|
-
).to.equal(true);
|
|
651
|
-
expect(callback.called).to.equal(false);
|
|
652
|
-
done();
|
|
653
|
-
}, 0);
|
|
654
|
-
});
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
describe("replay functionality", () => {
|
|
658
|
-
it("replays map values, not [key, val] pairs", (done) => {
|
|
659
|
-
// Directly populate the activity-object map to test replay
|
|
660
|
-
const testObj = { id: "test1", type: "like", content: "test" };
|
|
661
|
-
sc.events["activity-object"].set("test1", testObj);
|
|
662
|
-
|
|
663
|
-
// Reset socket spy and trigger replay
|
|
664
|
-
socket.emit.resetHistory();
|
|
665
|
-
socket.emit("connect");
|
|
666
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
667
|
-
|
|
668
|
-
setTimeout(() => {
|
|
669
|
-
const replayCalls = socket.emit.getCalls().filter(call => call.args[0] === "activity-object");
|
|
670
|
-
|
|
671
|
-
expect(replayCalls).to.have.length(1);
|
|
672
|
-
expect(replayCalls[0].args[1]).to.deep.equal(testObj);
|
|
673
|
-
expect(Array.isArray(replayCalls[0].args[1])).to.be.false;
|
|
674
|
-
|
|
675
|
-
done();
|
|
676
|
-
}, 0);
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
it("does not call ActivityStreams.Stream() on activity-objects", (done) => {
|
|
680
|
-
// Store an activity-object directly
|
|
681
|
-
const activityObject = {
|
|
682
|
-
id: "test-actor@example.com",
|
|
683
|
-
type: "person",
|
|
684
|
-
name: "Test Actor",
|
|
685
|
-
};
|
|
686
|
-
sc.events["activity-object"].set(activityObject.id, activityObject);
|
|
687
|
-
|
|
688
|
-
// Reset spies
|
|
689
|
-
socket.emit.resetHistory();
|
|
690
|
-
asInstance.Stream.resetHistory();
|
|
691
|
-
|
|
692
|
-
// Trigger reconnect which calls replay
|
|
693
|
-
socket.emit("connect");
|
|
694
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
695
|
-
|
|
696
|
-
setTimeout(() => {
|
|
697
|
-
// Stream() should NOT be called for activity-objects
|
|
698
|
-
const streamCalls = asInstance.Stream.getCalls();
|
|
699
|
-
const activityObjectStreamCalls = streamCalls.filter(
|
|
700
|
-
(call: any) => call.args[0]?.id === "test-actor@example.com",
|
|
701
|
-
);
|
|
702
|
-
expect(activityObjectStreamCalls).to.have.length(0);
|
|
703
|
-
|
|
704
|
-
// But the activity-object should still be emitted
|
|
705
|
-
const replayCalls = socket.emit
|
|
706
|
-
.getCalls()
|
|
707
|
-
.filter((call: any) => call.args[0] === "activity-object");
|
|
708
|
-
expect(replayCalls).to.have.length(1);
|
|
709
|
-
expect(replayCalls[0].args[1]).to.deep.equal(activityObject);
|
|
710
|
-
|
|
711
|
-
done();
|
|
712
|
-
}, 0);
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
it("calls ActivityStreams.Stream() on credentials during replay", (done) => {
|
|
716
|
-
// Store credentials
|
|
717
|
-
const credentials = {
|
|
718
|
-
actor: { id: "user@example.com", type: "person" },
|
|
719
|
-
object: { type: "credentials", password: "secret" },
|
|
720
|
-
};
|
|
721
|
-
sc.events.credentials.set(credentials.actor.id, credentials);
|
|
722
|
-
|
|
723
|
-
// Reset spies
|
|
724
|
-
socket.emit.resetHistory();
|
|
725
|
-
asInstance.Stream.resetHistory();
|
|
726
|
-
|
|
727
|
-
// Trigger reconnect
|
|
728
|
-
socket.emit("connect");
|
|
729
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
730
|
-
|
|
731
|
-
setTimeout(() => {
|
|
732
|
-
// Stream() SHOULD be called for credentials
|
|
733
|
-
expect(asInstance.Stream.called).to.be.true;
|
|
734
|
-
|
|
735
|
-
// Credentials should be emitted
|
|
736
|
-
const replayCalls = socket.emit
|
|
737
|
-
.getCalls()
|
|
738
|
-
.filter((call: any) => call.args[0] === "credentials");
|
|
739
|
-
expect(replayCalls).to.have.length(1);
|
|
740
|
-
|
|
741
|
-
done();
|
|
742
|
-
}, 0);
|
|
743
|
-
});
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
describe("clearCredentials", () => {
|
|
747
|
-
beforeEach(() => {
|
|
748
|
-
sc.socket.connected = true;
|
|
749
|
-
sc._socket.connected = true;
|
|
750
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
751
|
-
sandbox.stub(sc, "validateActivity").returns("");
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
it("clears stored credentials", () => {
|
|
755
|
-
// Store some credentials
|
|
756
|
-
sc.socket.emit("credentials", {
|
|
757
|
-
actor: { id: "user@example.com" },
|
|
758
|
-
object: { type: "credentials", username: "user", password: "pass" },
|
|
759
|
-
});
|
|
760
|
-
|
|
761
|
-
// Verify credentials are stored
|
|
762
|
-
expect(sc.events.credentials.size).to.equal(1);
|
|
763
|
-
|
|
764
|
-
// Clear credentials
|
|
765
|
-
sc.clearCredentials();
|
|
766
|
-
|
|
767
|
-
// Verify credentials are cleared
|
|
768
|
-
expect(sc.events.credentials.size).to.equal(0);
|
|
769
|
-
});
|
|
770
|
-
|
|
771
|
-
it("prevents credential replay after clearing", (done) => {
|
|
772
|
-
// Store credentials
|
|
773
|
-
sc.socket.emit("credentials", {
|
|
774
|
-
actor: { id: "user@example.com" },
|
|
775
|
-
object: { type: "credentials", username: "user", password: "pass" },
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
// Clear credentials
|
|
779
|
-
sc.clearCredentials();
|
|
780
|
-
|
|
781
|
-
// Trigger reconnect
|
|
782
|
-
let replayAttempts = 0;
|
|
783
|
-
socket.on("credentials", () => {
|
|
784
|
-
replayAttempts++;
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
socket.emit("connect");
|
|
788
|
-
socket.emit("schemas", TEST_REGISTRY);
|
|
789
|
-
|
|
790
|
-
setTimeout(() => {
|
|
791
|
-
// No credentials should have been replayed
|
|
792
|
-
expect(replayAttempts).to.equal(0);
|
|
793
|
-
done();
|
|
794
|
-
}, 10);
|
|
795
|
-
});
|
|
796
|
-
});
|
|
797
|
-
});
|