@sockethub/client 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/README.md +128 -31
- package/dist/sockethub-client.browser.js +17452 -288
- package/dist/sockethub-client.js +17456 -275
- package/dist/sockethub-client.min.js +32 -4
- package/package.json +4 -4
- package/src/sockethub-client.test.ts +433 -25
- package/src/sockethub-client.ts +832 -75
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sockethub/client",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.12",
|
|
4
4
|
"description": "A JavaScript client for the Sockethub protocol gateway",
|
|
5
5
|
"main": "./dist/sockethub-client.js",
|
|
6
6
|
"exports": {
|
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
"test:browser": "wtr dist/**/*.test.js --node-resolve --puppeteer"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
+
"@sockethub/activity-streams": "^4.4.0-alpha.12",
|
|
51
|
+
"@sockethub/schemas": "^3.0.0-alpha.12",
|
|
50
52
|
"eventemitter3": "^5.0.4"
|
|
51
53
|
},
|
|
52
54
|
"devDependencies": {
|
|
53
|
-
"@sockethub/activity-streams": "^4.4.0-alpha.11",
|
|
54
|
-
"@sockethub/schemas": "^3.0.0-alpha.11",
|
|
55
55
|
"@types/bun": "latest",
|
|
56
56
|
"@types/sinon": "^17.0.4",
|
|
57
57
|
"@web/test-runner": "^0.19.0",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"sinon": "^17.0.2",
|
|
60
60
|
"socket.io-client": "^4.8.3"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "f039dab3c3f67cbbf204476fc397532e973f82a8"
|
|
63
63
|
}
|
|
@@ -5,6 +5,40 @@ import { createSandbox, restore } from "sinon";
|
|
|
5
5
|
|
|
6
6
|
import SockethubClient from "./sockethub-client";
|
|
7
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
|
+
|
|
8
42
|
describe("SockethubClient bad initialization", () => {
|
|
9
43
|
it("no socket.io instance", () => {
|
|
10
44
|
class TestSockethubClient extends SockethubClient {
|
|
@@ -33,7 +67,22 @@ describe("SockethubClient", () => {
|
|
|
33
67
|
asInstance = new EventEmitter();
|
|
34
68
|
sandbox.spy(asInstance, "on");
|
|
35
69
|
sandbox.spy(asInstance, "emit");
|
|
36
|
-
asInstance.Stream = sandbox.stub().
|
|
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
|
+
});
|
|
37
86
|
asInstance.Object = {
|
|
38
87
|
create: sandbox.stub(),
|
|
39
88
|
};
|
|
@@ -73,12 +122,163 @@ describe("SockethubClient", () => {
|
|
|
73
122
|
});
|
|
74
123
|
|
|
75
124
|
it("registers a listeners for socket events", () => {
|
|
76
|
-
expect(socket.on.callCount).to.equal(
|
|
125
|
+
expect(socket.on.callCount).to.equal(6);
|
|
77
126
|
expect(socket.on.calledWithMatch("activity-object")).to.be.true;
|
|
78
127
|
expect(socket.on.calledWithMatch("connect")).to.be.true;
|
|
79
128
|
expect(socket.on.calledWithMatch("connect_error")).to.be.true;
|
|
80
129
|
expect(socket.on.calledWithMatch("disconnect")).to.be.true;
|
|
81
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
|
+
});
|
|
82
282
|
});
|
|
83
283
|
|
|
84
284
|
describe("event handling", () => {
|
|
@@ -93,9 +293,12 @@ describe("SockethubClient", () => {
|
|
|
93
293
|
});
|
|
94
294
|
|
|
95
295
|
it("activity-object-create", (done) => {
|
|
296
|
+
sc.socket.connected = true;
|
|
297
|
+
sc._socket.connected = true;
|
|
298
|
+
socket.emit("schemas", TEST_REGISTRY);
|
|
96
299
|
asInstance.emit("activity-object-create", { foo: "bar" });
|
|
97
300
|
setTimeout(() => {
|
|
98
|
-
expect(socket.emit.callCount).to.
|
|
301
|
+
expect(socket.emit.callCount).to.be.greaterThanOrEqual(2);
|
|
99
302
|
expect(
|
|
100
303
|
socket.emit.calledWithMatch("activity-object", {
|
|
101
304
|
foo: "bar",
|
|
@@ -105,13 +308,73 @@ describe("SockethubClient", () => {
|
|
|
105
308
|
}, 0);
|
|
106
309
|
});
|
|
107
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
|
+
|
|
108
354
|
it("connect", (done) => {
|
|
109
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
|
+
});
|
|
110
368
|
sc.socket.on("connect", () => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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);
|
|
115
378
|
});
|
|
116
379
|
socket.emit("connect");
|
|
117
380
|
});
|
|
@@ -136,6 +399,40 @@ describe("SockethubClient", () => {
|
|
|
136
399
|
socket.emit("connect_error");
|
|
137
400
|
});
|
|
138
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
|
+
|
|
139
436
|
it("message", (done) => {
|
|
140
437
|
sc.socket.on("message", () => {
|
|
141
438
|
expect(sc.socket._emit.callCount).to.equal(1);
|
|
@@ -147,20 +444,54 @@ describe("SockethubClient", () => {
|
|
|
147
444
|
});
|
|
148
445
|
|
|
149
446
|
describe("event emitting", () => {
|
|
150
|
-
|
|
447
|
+
beforeEach(() => {
|
|
151
448
|
sc._socket.connected = true;
|
|
152
|
-
|
|
153
|
-
|
|
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
|
+
|
|
154
470
|
expect(() => {
|
|
155
|
-
sc.socket.emit(
|
|
156
|
-
|
|
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);
|
|
157
485
|
});
|
|
158
486
|
|
|
159
487
|
it("message", (done) => {
|
|
160
488
|
sc.socket.connected = true;
|
|
161
489
|
const callback = () => {};
|
|
162
490
|
socket.once("message", (data: any, cb: any) => {
|
|
163
|
-
expect(data).to.be.eql({
|
|
491
|
+
expect(data).to.be.eql({
|
|
492
|
+
actor: { id: "bar", type: "person" },
|
|
493
|
+
type: "bar",
|
|
494
|
+
});
|
|
164
495
|
expect(cb).to.be.eql(callback);
|
|
165
496
|
done();
|
|
166
497
|
});
|
|
@@ -171,7 +502,10 @@ describe("SockethubClient", () => {
|
|
|
171
502
|
sc.socket.connected = true;
|
|
172
503
|
const callback = () => {};
|
|
173
504
|
socket.once("message", (data: any, cb: any) => {
|
|
174
|
-
expect(data).to.be.eql({
|
|
505
|
+
expect(data).to.be.eql({
|
|
506
|
+
actor: { id: "bar", type: "person" },
|
|
507
|
+
type: "join",
|
|
508
|
+
});
|
|
175
509
|
expect(cb).to.be.eql(callback);
|
|
176
510
|
done();
|
|
177
511
|
});
|
|
@@ -182,7 +516,10 @@ describe("SockethubClient", () => {
|
|
|
182
516
|
sc.socket.connected = true;
|
|
183
517
|
const callback = () => {};
|
|
184
518
|
socket.once("message", (data: any, cb: any) => {
|
|
185
|
-
expect(data).to.be.eql({
|
|
519
|
+
expect(data).to.be.eql({
|
|
520
|
+
actor: { id: "bar", type: "person" },
|
|
521
|
+
type: "leave",
|
|
522
|
+
});
|
|
186
523
|
expect(cb).to.be.eql(callback);
|
|
187
524
|
done();
|
|
188
525
|
});
|
|
@@ -197,7 +534,10 @@ describe("SockethubClient", () => {
|
|
|
197
534
|
sc.socket.connected = true;
|
|
198
535
|
const callback = () => {};
|
|
199
536
|
socket.once("message", (data: any, cb: any) => {
|
|
200
|
-
expect(data).to.be.eql({
|
|
537
|
+
expect(data).to.be.eql({
|
|
538
|
+
actor: { id: "bar", type: "person" },
|
|
539
|
+
type: "connect",
|
|
540
|
+
});
|
|
201
541
|
expect(cb).to.be.eql(callback);
|
|
202
542
|
done();
|
|
203
543
|
});
|
|
@@ -212,7 +552,10 @@ describe("SockethubClient", () => {
|
|
|
212
552
|
sc.socket.connected = true;
|
|
213
553
|
const callback = () => {};
|
|
214
554
|
socket.once("message", (data: any, cb: any) => {
|
|
215
|
-
expect(data).to.be.eql({
|
|
555
|
+
expect(data).to.be.eql({
|
|
556
|
+
actor: { id: "bar", type: "person" },
|
|
557
|
+
type: "disconnect",
|
|
558
|
+
});
|
|
216
559
|
expect(cb).to.be.eql(callback);
|
|
217
560
|
done();
|
|
218
561
|
});
|
|
@@ -225,21 +568,29 @@ describe("SockethubClient", () => {
|
|
|
225
568
|
|
|
226
569
|
it("message (offline)", (done) => {
|
|
227
570
|
sc.socket.connected = false;
|
|
571
|
+
sc._socket.connected = false;
|
|
572
|
+
socket.emit("disconnect");
|
|
228
573
|
const callback = () => {};
|
|
229
|
-
socket.once("message", (data: any, cb: any) => {
|
|
230
|
-
expect(data).to.be.eql({ actor: "bar" });
|
|
231
|
-
expect(cb).to.be.eql(callback);
|
|
232
|
-
done();
|
|
233
|
-
});
|
|
234
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);
|
|
235
583
|
});
|
|
236
584
|
|
|
237
585
|
it("activity-object", (done) => {
|
|
238
586
|
sc.socket.connected = true;
|
|
239
|
-
const callback = ()
|
|
587
|
+
const callback = sandbox.spy();
|
|
240
588
|
socket.once("activity-object", (data: any, cb: any) => {
|
|
241
589
|
expect(data).to.be.eql({ actor: "bar" });
|
|
242
|
-
|
|
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;
|
|
243
594
|
done();
|
|
244
595
|
});
|
|
245
596
|
sc.socket.emit("activity-object", { actor: "bar" }, callback);
|
|
@@ -249,12 +600,58 @@ describe("SockethubClient", () => {
|
|
|
249
600
|
sc.socket.connected = true;
|
|
250
601
|
const callback = () => {};
|
|
251
602
|
socket.once("credentials", (data: any, cb: any) => {
|
|
252
|
-
expect(data).to.be.eql({
|
|
603
|
+
expect(data).to.be.eql({
|
|
604
|
+
type: "credentials",
|
|
605
|
+
actor: { id: "bar", type: "person" },
|
|
606
|
+
});
|
|
253
607
|
expect(cb).to.be.eql(callback);
|
|
254
608
|
done();
|
|
255
609
|
});
|
|
256
610
|
sc.socket.emit("credentials", { actor: "bar" }, callback);
|
|
257
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
|
+
});
|
|
258
655
|
});
|
|
259
656
|
|
|
260
657
|
describe("replay functionality", () => {
|
|
@@ -266,6 +663,7 @@ describe("SockethubClient", () => {
|
|
|
266
663
|
// Reset socket spy and trigger replay
|
|
267
664
|
socket.emit.resetHistory();
|
|
268
665
|
socket.emit("connect");
|
|
666
|
+
socket.emit("schemas", TEST_REGISTRY);
|
|
269
667
|
|
|
270
668
|
setTimeout(() => {
|
|
271
669
|
const replayCalls = socket.emit.getCalls().filter(call => call.args[0] === "activity-object");
|
|
@@ -293,6 +691,7 @@ describe("SockethubClient", () => {
|
|
|
293
691
|
|
|
294
692
|
// Trigger reconnect which calls replay
|
|
295
693
|
socket.emit("connect");
|
|
694
|
+
socket.emit("schemas", TEST_REGISTRY);
|
|
296
695
|
|
|
297
696
|
setTimeout(() => {
|
|
298
697
|
// Stream() should NOT be called for activity-objects
|
|
@@ -327,6 +726,7 @@ describe("SockethubClient", () => {
|
|
|
327
726
|
|
|
328
727
|
// Trigger reconnect
|
|
329
728
|
socket.emit("connect");
|
|
729
|
+
socket.emit("schemas", TEST_REGISTRY);
|
|
330
730
|
|
|
331
731
|
setTimeout(() => {
|
|
332
732
|
// Stream() SHOULD be called for credentials
|
|
@@ -344,6 +744,13 @@ describe("SockethubClient", () => {
|
|
|
344
744
|
});
|
|
345
745
|
|
|
346
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
|
+
|
|
347
754
|
it("clears stored credentials", () => {
|
|
348
755
|
// Store some credentials
|
|
349
756
|
sc.socket.emit("credentials", {
|
|
@@ -378,6 +785,7 @@ describe("SockethubClient", () => {
|
|
|
378
785
|
});
|
|
379
786
|
|
|
380
787
|
socket.emit("connect");
|
|
788
|
+
socket.emit("schemas", TEST_REGISTRY);
|
|
381
789
|
|
|
382
790
|
setTimeout(() => {
|
|
383
791
|
// No credentials should have been replayed
|