sockethub 4.0.0 → 5.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/sockethub +23 -19
- package/coverage/tmp/coverage-15699-1646422276150-0.json +1 -0
- package/dist/bootstrap/init.js +14 -4
- package/dist/bootstrap/init.js.map +1 -1
- package/dist/bootstrap/platforms.js +81 -69
- package/dist/common.js +10 -12
- package/dist/common.js.map +1 -1
- package/dist/config.js +4 -22
- package/dist/config.js.map +1 -1
- package/dist/crypto.js +7 -8
- package/dist/crypto.js.map +1 -1
- package/dist/janitor.js +14 -9
- package/dist/janitor.js.map +1 -1
- package/dist/middleware/create-activity-object.js +19 -0
- package/dist/middleware/create-activity-object.js.map +1 -0
- package/dist/middleware/expand-activity-stream.js +33 -0
- package/dist/middleware/expand-activity-stream.js.map +1 -0
- package/dist/middleware/expand-activity-stream.test.data.js +360 -0
- package/dist/middleware/expand-activity-stream.test.data.js.map +1 -0
- package/dist/middleware/store-credentials.js +19 -0
- package/dist/middleware/store-credentials.js.map +1 -0
- package/dist/middleware/validate.js +77 -0
- package/dist/middleware/validate.js.map +1 -0
- package/dist/middleware/validate.test.data.js +321 -0
- package/dist/middleware/validate.test.data.js.map +1 -0
- package/dist/middleware.js +47 -49
- package/dist/middleware.js.map +1 -1
- package/dist/platform-instance.js +84 -66
- package/dist/platform-instance.js.map +1 -1
- package/dist/platform.js +50 -25
- package/dist/platform.js.map +1 -1
- package/dist/process-manager.js +7 -4
- package/dist/process-manager.js.map +1 -1
- package/dist/routes.js +9 -7
- package/dist/routes.js.map +1 -1
- package/dist/serve.js +3 -3
- package/dist/serve.js.map +1 -1
- package/dist/sockethub-client.js +2604 -0
- package/dist/sockethub-client.js.map +1 -0
- package/dist/sockethub-client.min.js +2 -0
- package/dist/sockethub-client.min.js.LICENSE.txt +24 -0
- package/dist/sockethub.js +75 -58
- package/dist/sockethub.js.map +1 -1
- package/dist/store.js +17 -0
- package/dist/store.js.map +1 -0
- package/package.json +48 -44
- package/src/bootstrap/init.ts +16 -2
- package/src/bootstrap/platforms.js +14 -18
- package/src/common.test.ts +44 -33
- package/src/common.ts +9 -17
- package/src/config.test.ts +16 -38
- package/src/config.ts +1 -23
- package/src/crypto.test.ts +15 -17
- package/src/crypto.ts +4 -5
- package/src/janitor.ts +19 -12
- package/src/middleware/create-activity-object.test.ts +10 -0
- package/src/middleware/create-activity-object.ts +13 -0
- package/src/middleware/expand-activity-stream.test.data.ts +365 -0
- package/src/middleware/expand-activity-stream.test.ts +78 -0
- package/src/middleware/expand-activity-stream.ts +27 -0
- package/src/middleware/store-credentials.test.ts +72 -0
- package/src/middleware/store-credentials.ts +16 -0
- package/src/{validate.d.ts → middleware/validate.d.ts} +0 -0
- package/src/middleware/validate.test.data.ts +320 -0
- package/src/middleware/validate.test.ts +47 -0
- package/src/middleware/validate.ts +49 -0
- package/src/middleware.test.ts +148 -0
- package/src/middleware.ts +46 -51
- package/src/platform-instance.test.ts +224 -196
- package/src/platform-instance.ts +74 -58
- package/src/platform.ts +44 -24
- package/src/process-manager.ts +7 -4
- package/src/routes.test.ts +32 -17
- package/src/routes.ts +8 -6
- package/src/serve.ts +1 -1
- package/src/sockethub-client.test.ts +235 -0
- package/src/sockethub-client.ts +164 -0
- package/src/sockethub.ts +96 -93
- package/src/store.test.ts +26 -0
- package/src/store.ts +17 -0
- package/tsconfig.json +8 -8
- package/views/examples/dummy.ejs +7 -7
- package/views/examples/feeds.ejs +10 -10
- package/views/examples/irc.ejs +65 -59
- package/views/examples/shared.js +31 -29
- package/views/examples/xmpp.ejs +49 -58
- package/webpack.minified.config.js +14 -0
- package/webpack.normal.config.js +14 -0
- package/coverage/clover.xml +0 -190
- package/coverage/coverage-final.json +0 -6
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -79
- package/coverage/lcov-report/common.ts.html +0 -143
- package/coverage/lcov-report/config.ts.html +0 -359
- package/coverage/lcov-report/crypto.ts.html +0 -203
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -171
- package/coverage/lcov-report/platform-instance.ts.html +0 -740
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/routes.ts.html +0 -353
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -170
- package/coverage/lcov-report/src/common.ts.html +0 -143
- package/coverage/lcov-report/src/config.ts.html +0 -359
- package/coverage/lcov-report/src/crypto.ts.html +0 -182
- package/coverage/lcov-report/src/index.html +0 -156
- package/coverage/lcov-report/src/platform-instance.ts.html +0 -740
- package/coverage/lcov-report/src/routes/base.ts.html +0 -359
- package/coverage/lcov-report/src/routes/examples.ts.html +0 -311
- package/coverage/lcov-report/src/routes/index.html +0 -111
- package/coverage/lcov-report/src/services/http.ts.html +0 -239
- package/coverage/lcov-report/src/services/index.html +0 -111
- package/coverage/lcov-report/src/services/redis.ts.html +0 -140
- package/coverage/lcov-report/src/shared-resources.ts.html +0 -104
- package/coverage/lcov.info +0 -336
- package/coverage/tmp/coverage-70996-1620314182345-0.json +0 -1
- package/dist/bootstrap/platforms.js.map +0 -1
- package/dist/js/client.js +0 -177
- package/dist/js/client.js.map +0 -1
- package/dist/resource-manager.js +0 -66
- package/dist/resource-manager.js.map +0 -1
- package/dist/routes/base.js +0 -92
- package/dist/routes/base.js.map +0 -1
- package/dist/routes/examples.js +0 -93
- package/dist/routes/examples.js.map +0 -1
- package/dist/services/http.js +0 -68
- package/dist/services/http.js.map +0 -1
- package/dist/services/redis.js +0 -1
- package/dist/services/redis.js.map +0 -1
- package/dist/shared-resources.js +0 -11
- package/dist/shared-resources.js.map +0 -1
- package/dist/validate.js +0 -157
- package/dist/validate.js.map +0 -1
- package/jest.config.js +0 -18
- package/src/js/client.js +0 -190
- package/src/validate.ts +0 -147
- package/test/middleware-suite.js +0 -101
- package/test/validate-suite.js +0 -338
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import proxyquire from 'proxyquire';
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
import * as sinon from 'sinon';
|
|
4
|
+
import { EventEmitter2 } from 'eventemitter2';
|
|
5
|
+
|
|
6
|
+
proxyquire.noPreserveCache();
|
|
7
|
+
proxyquire.noCallThru();
|
|
8
|
+
|
|
9
|
+
describe("SockethubClient bad initialization", () => {
|
|
10
|
+
it("no socket.io instance", () => {
|
|
11
|
+
const SockethubClient = proxyquire('./sockethub-client', {
|
|
12
|
+
'activity-streams': (config: any) => {}
|
|
13
|
+
});
|
|
14
|
+
expect(() => {
|
|
15
|
+
const junk = new SockethubClient();
|
|
16
|
+
}).to.throw("SockethubClient requires a socket.io instance");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("SockethubClient", () => {
|
|
21
|
+
let ASManager, socket, sc, sandbox;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
sandbox = sandbox = sinon.createSandbox();
|
|
25
|
+
socket = new EventEmitter2();
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
socket.__instance = 'socketio'; // used to uniquely identify the object we're passing in
|
|
28
|
+
sandbox.spy(socket, 'on');
|
|
29
|
+
sandbox.spy(socket, 'emit');
|
|
30
|
+
ASManager = new EventEmitter2();
|
|
31
|
+
sandbox.spy(ASManager, 'on');
|
|
32
|
+
sandbox.spy(ASManager, 'emit');
|
|
33
|
+
ASManager.Stream = sandbox.stub();
|
|
34
|
+
ASManager.Object = {
|
|
35
|
+
create: sandbox.stub()
|
|
36
|
+
};
|
|
37
|
+
const SockethubClient = proxyquire('./sockethub-client', {
|
|
38
|
+
'activity-streams': (config: any) => {
|
|
39
|
+
return ASManager;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
sc = new SockethubClient(socket);
|
|
43
|
+
sandbox.spy(sc.socket, 'on');
|
|
44
|
+
sandbox.spy(sc.socket, '_on');
|
|
45
|
+
sandbox.spy(sc.socket, 'emit');
|
|
46
|
+
sandbox.spy(sc.socket, '_emit');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
sinon.restore();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("contains the ActivityStreams property", () => {
|
|
54
|
+
expect(sc.ActivityStreams).to.be.eql(ASManager);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("contains the socket property", () => {
|
|
58
|
+
expect(sc.socket instanceof EventEmitter2).to.be.true;
|
|
59
|
+
// the object we passed in should not be the publically available one
|
|
60
|
+
expect(sc.socket.__instance).to.not.equal('socketio');
|
|
61
|
+
expect(sc.debug).to.be.true;
|
|
62
|
+
expect(sc.online).to.be.false;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("registers listeners for ActivityStream events", () => {
|
|
66
|
+
expect(ASManager.on.callCount).to.equal(1);
|
|
67
|
+
expect(ASManager.on.calledWithMatch('activity-object-create')).to.be.true;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("registers a listeners for socket events", () => {
|
|
71
|
+
expect(socket.on.callCount).to.equal(5);
|
|
72
|
+
expect(socket.on.calledWithMatch('activity-object')).to.be.true;
|
|
73
|
+
expect(socket.on.calledWithMatch('connect')).to.be.true;
|
|
74
|
+
expect(socket.on.calledWithMatch('connect_error')).to.be.true;
|
|
75
|
+
expect(socket.on.calledWithMatch('disconnect')).to.be.true;
|
|
76
|
+
expect(socket.on.calledWithMatch('message')).to.be.true;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("event handling", () => {
|
|
80
|
+
it("activity-object", (done) => {
|
|
81
|
+
socket.emit('activity-object', {foo:"bar"});
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
sandbox.assert.calledWith(ASManager.Object.create, {foo:"bar"});
|
|
84
|
+
done();
|
|
85
|
+
}, 0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("activity-object-create", (done) => {
|
|
89
|
+
ASManager.emit('activity-object-create', {foo:"bar"});
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
expect(socket.emit.callCount).to.equal(1);
|
|
92
|
+
expect(socket.emit.calledWithMatch('activity-object', {foo:"bar"})).to.be.true;
|
|
93
|
+
done();
|
|
94
|
+
}, 0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("connect", (done) => {
|
|
98
|
+
expect(sc.online).to.be.false;
|
|
99
|
+
sc.socket.on("connect", () => {
|
|
100
|
+
expect(sc.online).to.be.true;
|
|
101
|
+
expect(sc.socket._emit.callCount).to.equal(1);
|
|
102
|
+
expect(sc.socket._emit.calledWithMatch('connect'));
|
|
103
|
+
done();
|
|
104
|
+
});
|
|
105
|
+
socket.emit("connect");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("disconnect", (done) => {
|
|
109
|
+
sc.online = true;
|
|
110
|
+
sc.socket.on("disconnect", () => {
|
|
111
|
+
expect(sc.online).to.be.false;
|
|
112
|
+
expect(sc.socket._emit.callCount).to.equal(1);
|
|
113
|
+
expect(sc.socket._emit.calledWithMatch('disconnect'));
|
|
114
|
+
done();
|
|
115
|
+
});
|
|
116
|
+
socket.emit("disconnect");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("connect_error", (done) => {
|
|
120
|
+
sc.socket.on("connect_error", () => {
|
|
121
|
+
expect(sc.socket._emit.callCount).to.equal(1);
|
|
122
|
+
expect(sc.socket._emit.calledWithMatch('connect_error'));
|
|
123
|
+
done();
|
|
124
|
+
});
|
|
125
|
+
socket.emit("connect_error");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("message", (done) => {
|
|
129
|
+
sc.socket.on("message", () => {
|
|
130
|
+
expect(sc.socket._emit.callCount).to.equal(1);
|
|
131
|
+
expect(sc.socket._emit.calledWithMatch('message'));
|
|
132
|
+
done();
|
|
133
|
+
});
|
|
134
|
+
socket.emit("message");
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("event emitting", () => {
|
|
139
|
+
it("message (no actor)", () => {
|
|
140
|
+
sc.online = true;
|
|
141
|
+
const callback = (res) => {};
|
|
142
|
+
expect(() => {
|
|
143
|
+
sc.socket.emit("message", {foo:"bar"}, callback);
|
|
144
|
+
}).to.throw("actor property not present");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("message", (done) => {
|
|
148
|
+
sc.online = true;
|
|
149
|
+
const callback = (res) => {};
|
|
150
|
+
socket.once("message", (data, cb) => {
|
|
151
|
+
expect(data).to.be.eql({actor: "bar", type: "bar"});
|
|
152
|
+
expect(cb).to.be.eql(callback);
|
|
153
|
+
done();
|
|
154
|
+
});
|
|
155
|
+
sc.socket.emit("message", {actor:"bar", type: "bar"}, callback);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("message (join)", (done) => {
|
|
159
|
+
sc.online = true;
|
|
160
|
+
const callback = (res) => {};
|
|
161
|
+
socket.once("message", (data, cb) => {
|
|
162
|
+
expect(data).to.be.eql({actor: "bar", type: "join"});
|
|
163
|
+
expect(cb).to.be.eql(callback);
|
|
164
|
+
done();
|
|
165
|
+
});
|
|
166
|
+
sc.socket.emit("message", {actor:"bar", type: "join"}, callback);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("message (leave)", (done) => {
|
|
170
|
+
sc.online = true;
|
|
171
|
+
const callback = (res) => {};
|
|
172
|
+
socket.once("message", (data, cb) => {
|
|
173
|
+
expect(data).to.be.eql({actor: "bar", type: "leave"});
|
|
174
|
+
expect(cb).to.be.eql(callback);
|
|
175
|
+
done();
|
|
176
|
+
});
|
|
177
|
+
sc.socket.emit("message", {actor:"bar", type: "leave"}, callback);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("message (connect)", (done) => {
|
|
181
|
+
sc.online = true;
|
|
182
|
+
const callback = (res) => {};
|
|
183
|
+
socket.once("message", (data, cb) => {
|
|
184
|
+
expect(data).to.be.eql({actor: "bar", type: "connect"});
|
|
185
|
+
expect(cb).to.be.eql(callback);
|
|
186
|
+
done();
|
|
187
|
+
});
|
|
188
|
+
sc.socket.emit("message", {actor:"bar", type: "connect"}, callback);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("message (disconnect)", (done) => {
|
|
192
|
+
sc.online = true;
|
|
193
|
+
const callback = (res) => {};
|
|
194
|
+
socket.once("message", (data, cb) => {
|
|
195
|
+
expect(data).to.be.eql({actor: "bar", type: "disconnect"});
|
|
196
|
+
expect(cb).to.be.eql(callback);
|
|
197
|
+
done();
|
|
198
|
+
});
|
|
199
|
+
sc.socket.emit("message", {actor:"bar", type: "disconnect"}, callback);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("message (offline)", (done) => {
|
|
203
|
+
sc.online = false;
|
|
204
|
+
const callback = (res) => {};
|
|
205
|
+
socket.once("message", (data, cb) => {
|
|
206
|
+
expect(data).to.be.eql({actor: "bar"});
|
|
207
|
+
expect(cb).to.be.eql(callback);
|
|
208
|
+
done();
|
|
209
|
+
});
|
|
210
|
+
sc.socket.emit("message", {actor:"bar"}, callback);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("activity-object", (done) => {
|
|
214
|
+
sc.online = true;
|
|
215
|
+
const callback = (res) => {};
|
|
216
|
+
socket.once("activity-object", (data, cb) => {
|
|
217
|
+
expect(data).to.be.eql({actor: "bar"});
|
|
218
|
+
expect(cb).to.be.eql(callback);
|
|
219
|
+
done();
|
|
220
|
+
});
|
|
221
|
+
sc.socket.emit("activity-object", {actor:"bar"}, callback);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("credentials", (done) => {
|
|
225
|
+
sc.online = true;
|
|
226
|
+
const callback = (res) => {};
|
|
227
|
+
socket.once("credentials", (data, cb) => {
|
|
228
|
+
expect(data).to.be.eql({actor: "bar"});
|
|
229
|
+
expect(cb).to.be.eql(callback);
|
|
230
|
+
done();
|
|
231
|
+
});
|
|
232
|
+
sc.socket.emit("credentials", {actor:"bar"}, callback);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { EventEmitter2 } from 'eventemitter2';
|
|
2
|
+
import ASFactory from 'activity-streams';
|
|
3
|
+
|
|
4
|
+
export interface ActivityObjectManager {
|
|
5
|
+
create(obj: any): any;
|
|
6
|
+
delete(id: string): boolean;
|
|
7
|
+
list(): Array<string>,
|
|
8
|
+
get(id: string, expand: boolean): any;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ASManager {
|
|
12
|
+
Stream(meta: any): any,
|
|
13
|
+
Object: ActivityObjectManager,
|
|
14
|
+
on(event, func): void;
|
|
15
|
+
once(event, func): void;
|
|
16
|
+
off(event, funcName): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class SockethubClient {
|
|
20
|
+
private _socket;
|
|
21
|
+
private events = {
|
|
22
|
+
'credentials': new Map(),
|
|
23
|
+
'activity-object': new Map(),
|
|
24
|
+
'connect': new Map(),
|
|
25
|
+
'join': new Map()
|
|
26
|
+
};
|
|
27
|
+
public socket;
|
|
28
|
+
public ActivityStreams: ASManager;
|
|
29
|
+
public online = false;
|
|
30
|
+
public debug = true;
|
|
31
|
+
|
|
32
|
+
constructor(socket) {
|
|
33
|
+
if (! socket) { throw new Error('SockethubClient requires a socket.io instance'); }
|
|
34
|
+
this._socket = socket;
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
this.ActivityStreams = ASFactory({specialObjs: ['credentials']});
|
|
37
|
+
|
|
38
|
+
this.socket = this.createPublicEmitter();
|
|
39
|
+
this.registerSocketIOHandlers();
|
|
40
|
+
|
|
41
|
+
this.ActivityStreams.on('activity-object-create', (obj) => {
|
|
42
|
+
socket.emit('activity-object', obj, (err) => {
|
|
43
|
+
if (err) { console.error('failed to create activity-object ', err); }
|
|
44
|
+
else { this.eventActivityObject(obj); }
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
socket.on('activity-object', (obj) => {
|
|
49
|
+
this.ActivityStreams.Object.create(obj);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private createPublicEmitter(): EventEmitter2 {
|
|
54
|
+
let socket = new EventEmitter2({
|
|
55
|
+
wildcard: true,
|
|
56
|
+
verboseMemoryLeak: false
|
|
57
|
+
});
|
|
58
|
+
// @ts-ignore
|
|
59
|
+
socket._emit = socket.emit;
|
|
60
|
+
socket.emit = (event, content, callback): any => {
|
|
61
|
+
if (event === 'credentials') {
|
|
62
|
+
this.eventCredentials(content);
|
|
63
|
+
} else if (event === 'activity-object') {
|
|
64
|
+
this.eventActivityObject(content);
|
|
65
|
+
} else if (event === 'message') {
|
|
66
|
+
this.eventMessage(content);
|
|
67
|
+
}
|
|
68
|
+
this._socket.emit(event, content, callback);
|
|
69
|
+
};
|
|
70
|
+
return socket;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private eventActivityObject(content: any) {
|
|
74
|
+
if (content.id) {
|
|
75
|
+
this.events['activity-object'].set(content.id, content);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private eventCredentials(content: any) {
|
|
80
|
+
if ((content.object) && (content.object.type === 'credentials')) {
|
|
81
|
+
this.events['credentials'].set(content.actor.id || content.actor, content);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private eventMessage(content: any) {
|
|
86
|
+
if (! this.online) { return; }
|
|
87
|
+
// either store or delete the specified content onto the storedJoins map,
|
|
88
|
+
// for reply once we're back online.
|
|
89
|
+
const key = SockethubClient.getKey(content);
|
|
90
|
+
if (content.type === 'join' || content.type === 'connect') {
|
|
91
|
+
this.events[content.type].set(key, content);
|
|
92
|
+
} else if (content.type === 'leave') {
|
|
93
|
+
this.events['join'].delete(key);
|
|
94
|
+
} else if (content.type === 'disconnect') {
|
|
95
|
+
this.events['connect'].delete(key);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private static getKey(content: any) {
|
|
100
|
+
let actor = content.actor?.id || content.actor;
|
|
101
|
+
if (! actor) {
|
|
102
|
+
throw new Error("actor property not present for message type: " + content?.type);
|
|
103
|
+
}
|
|
104
|
+
let target = content.target ? content.target.id || content.target : '';
|
|
105
|
+
return actor + '-' + target;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private log(msg: string, obj?: any) {
|
|
109
|
+
if (this.debug) {
|
|
110
|
+
// eslint-disable-next-line security-node/detect-crlf
|
|
111
|
+
console.log(msg, obj);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private registerSocketIOHandlers() {
|
|
116
|
+
// middleware for events which don't deal in AS objects
|
|
117
|
+
const callHandler = (event: string) => {
|
|
118
|
+
return (obj, cb) => {
|
|
119
|
+
if (event === 'connect') {
|
|
120
|
+
this.online = true;
|
|
121
|
+
this.replay('activity-object', this.events['activity-object']);
|
|
122
|
+
this.replay('credentials', this.events['credentials']);
|
|
123
|
+
this.replay('message', this.events['connect']);
|
|
124
|
+
this.replay('message', this.events['join']);
|
|
125
|
+
} else if (event === 'disconnect') {
|
|
126
|
+
this.online = false;
|
|
127
|
+
}
|
|
128
|
+
this.socket._emit(event, obj, cb);
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// register for events that give us information on connection status
|
|
133
|
+
this._socket.on('connect', callHandler('connect'));
|
|
134
|
+
this._socket.on('connect_error', callHandler('connect_error'));
|
|
135
|
+
this._socket.on('disconnect', callHandler('disconnect'));
|
|
136
|
+
|
|
137
|
+
// use as a middleware to receive incoming Sockethub messages and unpack them
|
|
138
|
+
// using the ActivityStreams library before passing them along to the app.
|
|
139
|
+
this._socket.on('message', (obj, cb) => {
|
|
140
|
+
this.socket._emit('message', this.ActivityStreams.Stream(obj), cb);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private replay(name: string, asMap: any) {
|
|
145
|
+
asMap.forEach((obj) => {
|
|
146
|
+
this.log(`replaying ${name}`, obj);
|
|
147
|
+
this._socket.emit(name, obj);
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (typeof module === 'object' && module.exports) {
|
|
153
|
+
module.exports = SockethubClient;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (typeof exports === 'object') {
|
|
157
|
+
exports = SockethubClient; // lgtm [js/useless-assignment-to-local]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// @ts-ignore
|
|
161
|
+
if (typeof window === 'object') {
|
|
162
|
+
// @ts-ignore
|
|
163
|
+
window.SockethubClient = SockethubClient;
|
|
164
|
+
}
|
package/src/sockethub.ts
CHANGED
|
@@ -1,64 +1,72 @@
|
|
|
1
1
|
import debug from 'debug';
|
|
2
|
-
import ActivityStreams from 'activity-streams';
|
|
3
2
|
import { Socket } from "socket.io";
|
|
4
3
|
|
|
5
|
-
import config from './config';
|
|
6
4
|
import crypto from './crypto';
|
|
7
5
|
import init from './bootstrap/init';
|
|
8
|
-
import
|
|
6
|
+
import middleware from './middleware';
|
|
7
|
+
import createActivityObject from "./middleware/create-activity-object";
|
|
8
|
+
import expandActivityStream from "./middleware/expand-activity-stream";
|
|
9
|
+
import storeCredentials from "./middleware/store-credentials";
|
|
10
|
+
import validate from "./middleware/validate";
|
|
9
11
|
import janitor from './janitor';
|
|
10
12
|
import serve from './serve';
|
|
11
|
-
import validate from './validate';
|
|
12
13
|
import ProcessManager from "./process-manager";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
14
|
+
import PlatformInstance, { platformInstances } from "./platform-instance";
|
|
15
|
+
import { getSessionStore } from "./store";
|
|
15
16
|
|
|
16
|
-
const log = debug('sockethub:core')
|
|
17
|
-
activity = ActivityStreams(config.get('activity-streams:opts'));
|
|
17
|
+
const log = debug('sockethub:core');
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
export interface
|
|
20
|
+
export interface JobDataDecrypted {
|
|
21
21
|
title?: string;
|
|
22
|
-
msg:
|
|
22
|
+
msg: ActivityStream;
|
|
23
23
|
sessionId: string;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export interface
|
|
27
|
-
|
|
26
|
+
export interface JobDataEncrypted {
|
|
27
|
+
title?: string;
|
|
28
|
+
msg: string;
|
|
29
|
+
sessionId: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface JobDecrypted {
|
|
33
|
+
data: JobDataDecrypted,
|
|
28
34
|
remove?: Function;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
export interface
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
export interface JobEncrypted {
|
|
38
|
+
data: JobDataEncrypted,
|
|
39
|
+
remove?: Function;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ActivityStream {
|
|
43
|
+
type: string;
|
|
44
|
+
context: string;
|
|
45
|
+
actor: {
|
|
46
|
+
id: string;
|
|
47
|
+
type?: string;
|
|
48
|
+
name?: string;
|
|
36
49
|
},
|
|
37
50
|
object?: {
|
|
38
|
-
|
|
51
|
+
type: string;
|
|
39
52
|
content?: any;
|
|
40
53
|
},
|
|
41
|
-
target?:
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
target?: {
|
|
55
|
+
type: string;
|
|
56
|
+
id: string;
|
|
57
|
+
name?: string;
|
|
44
58
|
},
|
|
45
|
-
|
|
46
|
-
error?: any;
|
|
59
|
+
error?: string;
|
|
47
60
|
sessionSecret?: string;
|
|
48
61
|
}
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
msg.error = err;
|
|
59
|
-
// send failure
|
|
60
|
-
socket.emit('failure', msg);
|
|
61
|
-
});
|
|
63
|
+
function attachError(err, msg) {
|
|
64
|
+
if (typeof msg !== 'object') {
|
|
65
|
+
msg = { context: 'error' };
|
|
66
|
+
}
|
|
67
|
+
msg.error = err.toString();
|
|
68
|
+
delete msg.sessionSecret;
|
|
69
|
+
return msg;
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
class Sockethub {
|
|
@@ -97,84 +105,79 @@ class Sockethub {
|
|
|
97
105
|
janitor.clean(); // start cleanup cycle
|
|
98
106
|
serve.start(); // start external services
|
|
99
107
|
log('registering handlers');
|
|
100
|
-
serve.io.on('connection', this.
|
|
108
|
+
serve.io.on('connection', this.handleIncomingConnection.bind(this));
|
|
101
109
|
}
|
|
102
110
|
|
|
103
|
-
removeAllPlatformInstances() {
|
|
104
|
-
for (let platform of platformInstances
|
|
105
|
-
platform.destroy();
|
|
111
|
+
async removeAllPlatformInstances() {
|
|
112
|
+
for (let platform of platformInstances) {
|
|
113
|
+
await platform[1].destroy();
|
|
106
114
|
}
|
|
107
115
|
}
|
|
108
116
|
|
|
109
|
-
private
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
title: title,
|
|
116
|
-
sessionId: socket.id,
|
|
117
|
-
msg: crypto.encrypt(msg, this.parentSecret1 + this.parentSecret2)
|
|
118
|
-
};
|
|
119
|
-
platformInstance.queue.add(job);
|
|
120
|
-
};
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
private handleStoreCredentials(store: Store, sessionLog: Function) {
|
|
124
|
-
return (creds: ActivityObject) => {
|
|
125
|
-
store.save(creds.actor['@id'], creds, (err) => {
|
|
126
|
-
if (err) {
|
|
127
|
-
sessionLog('error saving credentials to store ' + err);
|
|
128
|
-
} else {
|
|
129
|
-
sessionLog('credentials encrypted and saved');
|
|
130
|
-
}
|
|
131
|
-
});
|
|
117
|
+
private createJob(socketId: string, platformInstance: PlatformInstance, msg): JobDataEncrypted {
|
|
118
|
+
const title = `${msg.context}-${(msg.id) ? msg.id : this.counter++}`;
|
|
119
|
+
const job: JobDataEncrypted = {
|
|
120
|
+
title: title,
|
|
121
|
+
sessionId: socketId,
|
|
122
|
+
msg: crypto.encrypt(msg, this.parentSecret1 + this.parentSecret2)
|
|
132
123
|
};
|
|
124
|
+
return job;
|
|
133
125
|
};
|
|
134
126
|
|
|
135
|
-
private
|
|
127
|
+
private handleIncomingConnection(socket: Socket) {
|
|
136
128
|
const sessionLog = debug('sockethub:core:' + socket.id), // session-specific debug messages
|
|
137
129
|
sessionSecret = crypto.randToken(16),
|
|
138
130
|
// store instance is session-specific
|
|
139
|
-
store = getSessionStore(this.parentId, this.parentSecret1, socket.id, sessionSecret)
|
|
140
|
-
middleware = getMiddleware(socket, sessionLog);
|
|
131
|
+
store = getSessionStore(this.parentId, this.parentSecret1, socket.id, sessionSecret);
|
|
141
132
|
|
|
142
133
|
sessionLog(`socket.io connection`);
|
|
143
134
|
|
|
144
135
|
socket.on('disconnect', () => {
|
|
145
|
-
sessionLog('disconnect received from client
|
|
136
|
+
sessionLog('disconnect received from client');
|
|
146
137
|
});
|
|
147
138
|
|
|
148
|
-
socket.on(
|
|
149
|
-
'credentials'
|
|
150
|
-
|
|
151
|
-
validate('credentials', socket.id)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
(next, msg) => {
|
|
159
|
-
// middleware which attaches the sessionSecret to the message. The platform thread
|
|
160
|
-
// must find the credentials on their own using the given sessionSecret, which indicates
|
|
161
|
-
// that this specific session (socket connection) has provided credentials.
|
|
162
|
-
msg.sessionSecret = sessionSecret;
|
|
163
|
-
next(true, msg);
|
|
164
|
-
},
|
|
165
|
-
this.handleIncomingMessage(socket, sessionLog)
|
|
166
|
-
)
|
|
167
|
-
);
|
|
139
|
+
socket.on('credentials',
|
|
140
|
+
middleware('credentials')
|
|
141
|
+
.use(expandActivityStream)
|
|
142
|
+
.use(validate('credentials', socket.id))
|
|
143
|
+
.use(storeCredentials(store, sessionLog))
|
|
144
|
+
.use((err, data, next) => {
|
|
145
|
+
// error handler
|
|
146
|
+
next(attachError(err, data));
|
|
147
|
+
}).use((data, next) => { next(); })
|
|
148
|
+
.done());
|
|
168
149
|
|
|
169
150
|
// when new activity objects are created on the client side, an event is
|
|
170
151
|
// fired and we receive a copy on the server side.
|
|
171
|
-
socket.on(
|
|
172
|
-
'activity-object'
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
152
|
+
socket.on('activity-object',
|
|
153
|
+
middleware('activity-object')
|
|
154
|
+
.use(validate('activity-object', socket.id))
|
|
155
|
+
.use(createActivityObject)
|
|
156
|
+
.use((err, data, next) => {
|
|
157
|
+
next(attachError(err, data));
|
|
158
|
+
}).use((data, next) => { next(); })
|
|
159
|
+
.done());
|
|
160
|
+
|
|
161
|
+
socket.on('message',
|
|
162
|
+
middleware('message')
|
|
163
|
+
.use(expandActivityStream)
|
|
164
|
+
.use(validate('message', socket.id))
|
|
165
|
+
.use((msg, next) => {
|
|
166
|
+
// The platform thread must find the credentials on their own using the given
|
|
167
|
+
// sessionSecret, which indicates that this specific session (socket
|
|
168
|
+
// connection) has provided credentials.
|
|
169
|
+
msg.sessionSecret = sessionSecret;
|
|
170
|
+
next(msg);
|
|
171
|
+
}).use((err, data, next) => {
|
|
172
|
+
next(attachError(err, data));
|
|
173
|
+
}).use((msg, next) => {
|
|
174
|
+
const platformInstance = this.processManager.get(msg.context, msg.actor.id, socket.id);
|
|
175
|
+
sessionLog(`queued to channel ${platformInstance.id}`);
|
|
176
|
+
const job = this.createJob(socket.id, platformInstance, msg);
|
|
177
|
+
// job validated and queued, store socket.io callback for when job is completed
|
|
178
|
+
platformInstance.completedJobHandlers.set(job.title, next);
|
|
179
|
+
platformInstance.queue.add(job);
|
|
180
|
+
}).done());
|
|
178
181
|
}
|
|
179
182
|
}
|
|
180
183
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import proxyquire from 'proxyquire';
|
|
2
|
+
import * as sinon from 'sinon';
|
|
3
|
+
|
|
4
|
+
proxyquire.noPreserveCache();
|
|
5
|
+
proxyquire.noCallThru();
|
|
6
|
+
|
|
7
|
+
const MockSecureStore = sinon.fake();
|
|
8
|
+
|
|
9
|
+
const StoreMod = proxyquire('./store', {
|
|
10
|
+
'secure-store-redis': MockSecureStore
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const getSessionStore = StoreMod.getSessionStore;
|
|
14
|
+
|
|
15
|
+
describe('getSessionStore', () => {
|
|
16
|
+
it('returns a valid Store object', () => {
|
|
17
|
+
const store = getSessionStore('a parent id', 'a parent secret',
|
|
18
|
+
'a session id', 'a session secret');
|
|
19
|
+
sinon.assert.calledOnce(MockSecureStore);
|
|
20
|
+
sinon.assert.calledWith(MockSecureStore, {
|
|
21
|
+
namespace: 'sockethub:a parent id:session:a session id:store',
|
|
22
|
+
secret: 'a parent secreta session secret',
|
|
23
|
+
redis: { host: '127.0.0.1', port: 6379 }
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
});
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import SecureStore from 'secure-store-redis';
|
|
2
|
+
|
|
3
|
+
import config from "./config";
|
|
4
|
+
|
|
5
|
+
export interface ISecureStoreInstance {
|
|
6
|
+
save(id: string, obj: any, cb: Function);
|
|
7
|
+
get(id: string, cb: Function);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getSessionStore(parentId: string, parentSecret: string,
|
|
11
|
+
sessionId: string, sessionSecret: string): ISecureStoreInstance {
|
|
12
|
+
return new SecureStore({
|
|
13
|
+
namespace: 'sockethub:' + parentId + ':session:' + sessionId + ':store',
|
|
14
|
+
secret: parentSecret + sessionSecret,
|
|
15
|
+
redis: config.get('redis')
|
|
16
|
+
});
|
|
17
|
+
}
|