sockethub 4.1.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/bin/sockethub +23 -20
- package/coverage/tmp/coverage-15699-1646422276150-0.json +1 -0
- package/dist/bootstrap/init.js +1 -1
- package/dist/bootstrap/init.js.map +1 -1
- package/dist/bootstrap/platforms.js +14 -18
- package/dist/janitor.js +11 -5
- package/dist/janitor.js.map +1 -1
- package/dist/middleware/create-activity-object.js.map +1 -1
- 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 +1 -1
- package/dist/middleware/store-credentials.js.map +1 -1
- package/dist/middleware/validate.js +30 -104
- package/dist/middleware/validate.js.map +1 -1
- package/dist/middleware/validate.test.data.js +151 -111
- package/dist/middleware/validate.test.data.js.map +1 -1
- package/dist/middleware.js +47 -77
- package/dist/middleware.js.map +1 -1
- package/dist/platform-instance.js +47 -54
- package/dist/platform-instance.js.map +1 -1
- package/dist/platform.js +29 -10
- package/dist/platform.js.map +1 -1
- package/dist/process-manager.js +5 -2
- package/dist/process-manager.js.map +1 -1
- package/dist/routes.js +3 -5
- package/dist/routes.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 +67 -36
- package/dist/sockethub.js.map +1 -1
- package/package.json +40 -36
- package/src/bootstrap/init.ts +6 -2
- package/src/bootstrap/platforms.js +14 -18
- package/src/crypto.ts +3 -3
- package/src/janitor.ts +18 -10
- package/src/middleware/create-activity-object.ts +1 -2
- 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 +12 -12
- package/src/middleware/store-credentials.ts +4 -4
- package/src/middleware/validate.test.data.ts +154 -120
- package/src/middleware/validate.test.ts +7 -10
- package/src/middleware/validate.ts +30 -123
- package/src/middleware.test.ts +59 -26
- package/src/middleware.ts +44 -76
- package/src/platform-instance.test.ts +8 -10
- package/src/platform-instance.ts +58 -57
- package/src/platform.ts +30 -14
- package/src/process-manager.ts +7 -4
- package/src/routes.ts +3 -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 +75 -68
- package/views/examples/dummy.ejs +6 -6
- package/views/examples/feeds.ejs +8 -8
- package/views/examples/irc.ejs +64 -58
- package/views/examples/shared.js +31 -29
- package/views/examples/xmpp.ejs +48 -57
- package/webpack.minified.config.js +14 -0
- package/webpack.normal.config.js +14 -0
- package/coverage/tmp/coverage-24546-1630615250805-0.json +0 -1
- package/dist/client/sockethub-client.js +0 -171
- package/dist/client/sockethub-client.js.map +0 -1
- package/src/client/sockethub-client.js +0 -178
- package/test/middleware-suite.js +0 -101
package/src/middleware.test.ts
CHANGED
|
@@ -1,27 +1,43 @@
|
|
|
1
1
|
import { expect } from 'chai';
|
|
2
2
|
import * as sinon from 'sinon';
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import middleware, { MiddlewareChain } from "./middleware";
|
|
5
5
|
|
|
6
6
|
describe("Middleware", () => {
|
|
7
|
-
it("
|
|
8
|
-
expect(typeof
|
|
7
|
+
it("middleware() is a function", () => {
|
|
8
|
+
expect(typeof middleware).to.be.equal('function');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('returns a MiddlewareChain instance', () => {
|
|
12
|
+
const mw = middleware('testa');
|
|
13
|
+
expect(mw).to.be.instanceof(MiddlewareChain);
|
|
14
|
+
const mwc = new MiddlewareChain('testa');
|
|
15
|
+
expect(mw.name).to.be.eql(mwc.name);
|
|
9
16
|
});
|
|
10
17
|
|
|
11
18
|
it("only accepts functions", () => {
|
|
12
|
-
const mw =
|
|
19
|
+
const mw = middleware('test');
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
expect(()=>{mw.use('foobar');}).to.throw(
|
|
22
|
+
'middleware use() can only take a function as an argument');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("only accepts functions that expect 2 or 3 params", () => {
|
|
26
|
+
const mw = middleware('test');
|
|
13
27
|
// @ts-ignore
|
|
14
|
-
expect(()=>{mw(()=>{}
|
|
15
|
-
'middleware
|
|
28
|
+
expect(()=>{mw.use((one, two, three, four) => {});}).to.throw(
|
|
29
|
+
'middleware function provided with incorrect number of params: 4');
|
|
16
30
|
});
|
|
17
31
|
|
|
18
32
|
it("calls each member of call list", (done) => {
|
|
19
33
|
const callback = (data, cb) => { cb(data); };
|
|
20
34
|
const funcs = [ sinon.spy(callback), sinon.spy(callback), sinon.spy(callback) ];
|
|
21
|
-
const mw =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
const mw = middleware('test');
|
|
36
|
+
for (let func of funcs) {
|
|
37
|
+
mw.use(func);
|
|
38
|
+
}
|
|
39
|
+
mw.done()('some data', (data) => {
|
|
40
|
+
expect(data).to.eql('some data');
|
|
25
41
|
funcs.unshift(callback);
|
|
26
42
|
for (let i = 1; i < funcs.length; i++) {
|
|
27
43
|
expect(funcs[i].calledOnce).to.be.true;
|
|
@@ -33,16 +49,22 @@ describe("Middleware", () => {
|
|
|
33
49
|
|
|
34
50
|
it("does not throw exception on error with no callback provided", (done) => {
|
|
35
51
|
let errorHandlerCalled = false;
|
|
36
|
-
const callbackError = (data, cb) => {
|
|
52
|
+
const callbackError = (data, cb) => {
|
|
53
|
+
cb(new Error('some error')); };
|
|
37
54
|
const funcs = [ sinon.spy(callbackError) ];
|
|
38
|
-
const mw =
|
|
55
|
+
const mw = middleware('test');
|
|
56
|
+
for (let func of funcs) {
|
|
57
|
+
const entry = mw.use(func);
|
|
58
|
+
}
|
|
59
|
+
mw.use((err, data, next) => {
|
|
39
60
|
expect(err.toString()).to.equal('Error: some error');
|
|
40
61
|
errorHandlerCalled = true;
|
|
62
|
+
next(err);
|
|
63
|
+
});
|
|
64
|
+
mw.done()('foobar', () => {
|
|
65
|
+
expect(errorHandlerCalled).to.be.true;
|
|
66
|
+
done();
|
|
41
67
|
});
|
|
42
|
-
const entry = mw(...funcs);
|
|
43
|
-
entry('foobar');
|
|
44
|
-
expect(errorHandlerCalled).to.be.true;
|
|
45
|
-
done();
|
|
46
68
|
});
|
|
47
69
|
|
|
48
70
|
it("aborts call stack on error - calls error handler, and callback", (done) => {
|
|
@@ -50,14 +72,18 @@ describe("Middleware", () => {
|
|
|
50
72
|
const callback = (data, cb) => { cb(data); };
|
|
51
73
|
const callbackError = (data, cb) => { cb(new Error('some error')); };
|
|
52
74
|
const funcs = [ sinon.spy(callback), sinon.spy(callbackError), sinon.spy(callback) ];
|
|
53
|
-
const mw =
|
|
75
|
+
const mw = middleware('test');
|
|
76
|
+
for (let func of funcs) {
|
|
77
|
+
mw.use(func);
|
|
78
|
+
}
|
|
79
|
+
mw.use((err, data, next) => {
|
|
54
80
|
expect(err.toString()).to.equal('Error: some error');
|
|
55
81
|
errorHandlerCalled = true;
|
|
82
|
+
next(err);
|
|
56
83
|
});
|
|
57
|
-
|
|
58
|
-
entry('foobar', (err) => {
|
|
84
|
+
mw.done()('foobar', (data) => {
|
|
59
85
|
// FIXME -- We need to also handle socket.io callbacks!
|
|
60
|
-
expect(
|
|
86
|
+
expect(data instanceof Error).to.be.true;
|
|
61
87
|
expect(funcs[0].calledOnce).to.be.true;
|
|
62
88
|
expect(funcs[0].calledWith('foobar', callback));
|
|
63
89
|
expect(funcs[1].calledOnce).to.be.true;
|
|
@@ -73,7 +99,11 @@ describe("Middleware", () => {
|
|
|
73
99
|
const callback = (data, cb) => { cb(data); };
|
|
74
100
|
const callbackError = (data, cb) => { cb(new Error('some error')); };
|
|
75
101
|
const funcs = [ sinon.spy(callback), sinon.spy(callback), sinon.spy(callbackError) ];
|
|
76
|
-
const mw =
|
|
102
|
+
const mw = middleware('test');
|
|
103
|
+
for (let func of funcs) {
|
|
104
|
+
mw.use(func);
|
|
105
|
+
}
|
|
106
|
+
mw.use((err, data, next) => {
|
|
77
107
|
expect(err instanceof Error).to.be.true;
|
|
78
108
|
expect(err.toString()).to.equal('Error: some error');
|
|
79
109
|
errorHandlerCalled = true;
|
|
@@ -86,8 +116,7 @@ describe("Middleware", () => {
|
|
|
86
116
|
expect(errorHandlerCalled).to.be.true;
|
|
87
117
|
setTimeout(done, 0);
|
|
88
118
|
});
|
|
89
|
-
|
|
90
|
-
entry('foobar');
|
|
119
|
+
mw.done()('foobar', () => {});
|
|
91
120
|
});
|
|
92
121
|
|
|
93
122
|
it("calls each member of chain (50)", (done) => {
|
|
@@ -97,12 +126,16 @@ describe("Middleware", () => {
|
|
|
97
126
|
for (let i = 0; i <= 50; i++) {
|
|
98
127
|
funcs.push(sinon.spy(callback));
|
|
99
128
|
}
|
|
100
|
-
const mw =
|
|
129
|
+
const mw = middleware('test');
|
|
130
|
+
for (let func of funcs) {
|
|
131
|
+
mw.use(func);
|
|
132
|
+
}
|
|
133
|
+
mw.use((err, next, data) => {
|
|
101
134
|
expect(err.toString()).to.equal('Error: some error');
|
|
102
135
|
errorHandlerCalled = true;
|
|
103
136
|
});
|
|
104
|
-
mw(
|
|
105
|
-
expect(
|
|
137
|
+
mw.done()('some data', (data) => {
|
|
138
|
+
expect(data).to.equal('some data');
|
|
106
139
|
funcs.unshift(callback);
|
|
107
140
|
for (let i = 1; i < funcs.length; i++) {
|
|
108
141
|
expect(funcs[i].calledOnce).to.be.true;
|
package/src/middleware.ts
CHANGED
|
@@ -1,84 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* @return {function} chains a series of functions to be called in sequence
|
|
6
|
-
*/
|
|
7
|
-
export default function createMiddleware(errorHandler: Function) {
|
|
8
|
-
return function chain(...chain: Array<Function>) {
|
|
9
|
-
for (let func of chain) {
|
|
10
|
-
if (typeof func !== 'function') {
|
|
11
|
-
throw new Error('middleware chain can only take other functions as arguments.');
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return getMiddlewareInstance(chain, errorHandler);
|
|
15
|
-
};
|
|
1
|
+
import { debug } from 'debug';
|
|
2
|
+
|
|
3
|
+
export default function middleware(name: string): MiddlewareChain {
|
|
4
|
+
return new MiddlewareChain(name);
|
|
16
5
|
}
|
|
17
6
|
|
|
7
|
+
export class MiddlewareChain {
|
|
8
|
+
public name: string;
|
|
9
|
+
private chain: Array<Function> = [];
|
|
10
|
+
private errHandler: Function = (err: Error) => { throw err; };
|
|
11
|
+
private logger: Function;
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
let position = 0;
|
|
13
|
+
constructor(name: string) {
|
|
14
|
+
this.name = name;
|
|
15
|
+
this.logger = debug(`sockethub:middleware:${name}`);
|
|
16
|
+
}
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
use(func: Function): this {
|
|
19
|
+
if (typeof func !== 'function') {
|
|
20
|
+
throw new Error('middleware use() can only take a function as an argument');
|
|
28
21
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
params.
|
|
36
|
-
chain[position++](...params);
|
|
37
|
-
} else {
|
|
38
|
-
callback(null);
|
|
39
|
-
}
|
|
22
|
+
if (func.length === 3) {
|
|
23
|
+
this.errHandler = func;
|
|
24
|
+
} else if (func.length === 2) {
|
|
25
|
+
this.chain.push(func);
|
|
26
|
+
} else {
|
|
27
|
+
throw new Error(
|
|
28
|
+
'middleware function provided with incorrect number of params: ' + func.length);
|
|
40
29
|
}
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
41
32
|
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
done() {
|
|
34
|
+
return (data: any, callback: Function) => {
|
|
35
|
+
let position = 0;
|
|
36
|
+
if (typeof callback !== 'function') {
|
|
37
|
+
callback = () => {};
|
|
38
|
+
}
|
|
39
|
+
const next = (_data: any) => {
|
|
40
|
+
if (_data instanceof Error) {
|
|
41
|
+
this.logger(_data);
|
|
42
|
+
this.errHandler(_data, data, callback);
|
|
43
|
+
} else if (typeof this.chain[position] === 'function') {
|
|
44
|
+
this.chain[position++](_data, next);
|
|
45
|
+
} else {
|
|
46
|
+
callback(_data);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
next(data);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
44
52
|
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* When calling the middleware instance function, pass in a list of functions to call in order.
|
|
48
|
-
* Each function can expect any number of params, but the final param should expect a callback.
|
|
49
|
-
*
|
|
50
|
-
* After all functions are completed, it will call the callback function. If any middleware calls
|
|
51
|
-
* the callback with the first parameter an instance of Error, then the call stack will be aborted,
|
|
52
|
-
* and the final callback (callback of first input) will be called with the Error as the first i
|
|
53
|
-
* parameter.
|
|
54
|
-
* Additionally, if provided during the `createMiddleware` call, the errorHandler will be called,
|
|
55
|
-
* with the error object as the first parameter along with the remaining parameters.
|
|
56
|
-
*
|
|
57
|
-
* @param {function} middleware Any number of functions, each expecting a callback as the final
|
|
58
|
-
* parameter.
|
|
59
|
-
* @return {function} entry The entry function, which will start calling functions defined in the
|
|
60
|
-
* chain.
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
*
|
|
64
|
-
* const entry = middleware.chain(
|
|
65
|
-
* (data, cb) => {
|
|
66
|
-
* //... do something with data
|
|
67
|
-
* cb(data);
|
|
68
|
-
* },
|
|
69
|
-
* (data, cb) => {
|
|
70
|
-
* //... do more with data
|
|
71
|
-
* cb(data);
|
|
72
|
-
* },
|
|
73
|
-
* (data, cb) => {
|
|
74
|
-
* //... do last stuff with data
|
|
75
|
-
* cb();
|
|
76
|
-
* }
|
|
77
|
-
* );
|
|
78
|
-
*
|
|
79
|
-
* entry(initialData, (err) => {
|
|
80
|
-
* // this function is called when complete or an error occurs anywhere on the chain
|
|
81
|
-
* // in which case execution is aborted.
|
|
82
|
-
* });
|
|
83
|
-
*
|
|
84
|
-
*/
|
|
@@ -126,10 +126,8 @@ describe("PlatformInstance", () => {
|
|
|
126
126
|
pi.destroy = sandbox.stub();
|
|
127
127
|
expect(pi.sessions.size).to.be.equal(0);
|
|
128
128
|
});
|
|
129
|
-
|
|
130
129
|
});
|
|
131
130
|
|
|
132
|
-
|
|
133
131
|
it('initializes the job queue', () => {
|
|
134
132
|
expect(pi.queue).to.be.undefined;
|
|
135
133
|
pi.initQueue('a secret');
|
|
@@ -153,7 +151,7 @@ describe("PlatformInstance", () => {
|
|
|
153
151
|
});
|
|
154
152
|
|
|
155
153
|
it('sends messages to client using socket session id', async () => {
|
|
156
|
-
await pi.sendToClient('my session id',
|
|
154
|
+
await pi.sendToClient('my session id',
|
|
157
155
|
{foo: 'this is a message object', sessionSecret: 'private data'});
|
|
158
156
|
expect(getSocketFake.callCount).to.equal(1);
|
|
159
157
|
sandbox.assert.calledOnce(getSocketFake);
|
|
@@ -186,19 +184,19 @@ describe("PlatformInstance", () => {
|
|
|
186
184
|
});
|
|
187
185
|
|
|
188
186
|
it('appends completed result message when present', async () => {
|
|
189
|
-
await pi.handleJobResult('completed', {msg: {foo: 'bar'}},
|
|
187
|
+
await pi.handleJobResult('completed', {sessionId: 'a session id', msg: {foo: 'bar'}},
|
|
190
188
|
'a good result message');
|
|
191
189
|
expect(pi.broadcastToSharedPeers.callCount).to.equal(1);
|
|
192
|
-
sandbox.assert.calledWith(pi.sendToClient,
|
|
193
|
-
{foo: 'bar'
|
|
190
|
+
sandbox.assert.calledWith(pi.sendToClient, 'a session id',
|
|
191
|
+
{foo: 'bar'});
|
|
194
192
|
});
|
|
195
193
|
|
|
196
194
|
it('appends failed result message when present', async () => {
|
|
197
|
-
await pi.handleJobResult('failed', {msg: {foo: 'bar'}},
|
|
195
|
+
await pi.handleJobResult('failed', {sessionId: 'a session id', msg: {foo: 'bar'}},
|
|
198
196
|
'a bad result message');
|
|
199
197
|
expect(pi.broadcastToSharedPeers.callCount).to.equal(1);
|
|
200
|
-
sandbox.assert.calledWith(pi.sendToClient,
|
|
201
|
-
{foo: 'bar',
|
|
198
|
+
sandbox.assert.calledWith(pi.sendToClient, 'a session id',
|
|
199
|
+
{foo: 'bar', error: 'a bad result message'});
|
|
202
200
|
});
|
|
203
201
|
});
|
|
204
202
|
|
|
@@ -232,7 +230,7 @@ describe("PlatformInstance", () => {
|
|
|
232
230
|
const message = pi.callbackFunction('message', 'my session id');
|
|
233
231
|
message(['blah', {foo: 'bar'}]);
|
|
234
232
|
sandbox.assert.calledWith(pi.sendToClient,
|
|
235
|
-
'my session id',
|
|
233
|
+
'my session id', {foo:'bar'});
|
|
236
234
|
});
|
|
237
235
|
});
|
|
238
236
|
});
|
package/src/platform-instance.ts
CHANGED
|
@@ -4,12 +4,12 @@ import { debug, Debugger } from 'debug';
|
|
|
4
4
|
import Queue from 'bull';
|
|
5
5
|
|
|
6
6
|
import config from "./config";
|
|
7
|
-
import {
|
|
7
|
+
import { ActivityStream, JobDataDecrypted, JobEncrypted } from "./sockethub";
|
|
8
8
|
import { getSocket } from "./serve";
|
|
9
|
-
import {decryptJobData} from "./common";
|
|
9
|
+
import { decryptJobData } from "./common";
|
|
10
10
|
|
|
11
11
|
// collection of platform instances, stored by `id`
|
|
12
|
-
export const platformInstances = new Map();
|
|
12
|
+
export const platformInstances = new Map<string, PlatformInstance>();
|
|
13
13
|
|
|
14
14
|
export interface PlatformInstanceParams {
|
|
15
15
|
identifier: string;
|
|
@@ -18,26 +18,32 @@ export interface PlatformInstanceParams {
|
|
|
18
18
|
actor?: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
interface MessageFromPlatform extends Array<string|
|
|
22
|
-
0: string, 1:
|
|
21
|
+
interface MessageFromPlatform extends Array<string|ActivityStream>{
|
|
22
|
+
0: string, 1: ActivityStream, 2: string}
|
|
23
23
|
export interface MessageFromParent extends Array<string|any>{0: string, 1: any}
|
|
24
24
|
|
|
25
|
+
interface PlatformConfig {
|
|
26
|
+
persist?: boolean;
|
|
27
|
+
requireCredentials?: Array<string>;
|
|
28
|
+
}
|
|
25
29
|
|
|
26
30
|
export default class PlatformInstance {
|
|
27
31
|
flaggedForTermination: boolean = false;
|
|
28
32
|
id: string;
|
|
29
33
|
queue: Queue;
|
|
34
|
+
completedJobHandlers: Map<string, Function> = new Map();
|
|
35
|
+
config: PlatformConfig = {};
|
|
30
36
|
readonly name: string;
|
|
31
37
|
readonly process: ChildProcess;
|
|
32
38
|
readonly debug: Debugger;
|
|
33
39
|
readonly parentId: string;
|
|
34
40
|
readonly sessions: Set<string> = new Set();
|
|
35
|
-
|
|
36
|
-
private readonly actor?: string;
|
|
37
|
-
private readonly sessionCallbacks: object = {
|
|
41
|
+
readonly sessionCallbacks: object = {
|
|
38
42
|
'close': (() => new Map())(),
|
|
39
|
-
'message': (() => new Map())()
|
|
43
|
+
'message': (() => new Map())()
|
|
40
44
|
};
|
|
45
|
+
public readonly global?: boolean = false;
|
|
46
|
+
private readonly actor?: string;
|
|
41
47
|
|
|
42
48
|
constructor(params: PlatformInstanceParams) {
|
|
43
49
|
this.id = params.identifier;
|
|
@@ -58,29 +64,21 @@ export default class PlatformInstance {
|
|
|
58
64
|
* Destroys all references to this platform instance, internal listeners and controlled processes
|
|
59
65
|
*/
|
|
60
66
|
public async destroy() {
|
|
67
|
+
this.debug(`cleaning up`);
|
|
61
68
|
this.flaggedForTermination = true;
|
|
62
|
-
platformInstances.delete(this.id);
|
|
63
|
-
try {
|
|
64
|
-
await this.queue.empty();
|
|
65
|
-
} catch (e) { }
|
|
66
|
-
try {
|
|
67
|
-
await this.queue.clean(0);
|
|
68
|
-
} catch (e) { }
|
|
69
69
|
try {
|
|
70
|
-
await this.queue.
|
|
71
|
-
} catch (e) { }
|
|
72
|
-
try {
|
|
73
|
-
this.queue.removeAllListeners();
|
|
70
|
+
await this.queue.removeAllListeners();
|
|
74
71
|
} catch (e) { }
|
|
75
72
|
try {
|
|
76
73
|
await this.queue.obliterate({ force: true });
|
|
77
74
|
} catch (e) { }
|
|
78
75
|
try {
|
|
79
76
|
delete this.queue;
|
|
80
|
-
this.process.removeAllListeners('close');
|
|
81
|
-
this.process.unref();
|
|
82
|
-
this.process.kill();
|
|
77
|
+
await this.process.removeAllListeners('close');
|
|
78
|
+
await this.process.unref();
|
|
79
|
+
await this.process.kill();
|
|
83
80
|
} catch (e) { }
|
|
81
|
+
platformInstances.delete(this.id);
|
|
84
82
|
}
|
|
85
83
|
|
|
86
84
|
/**
|
|
@@ -88,20 +86,23 @@ export default class PlatformInstance {
|
|
|
88
86
|
*/
|
|
89
87
|
public initQueue(secret: string) {
|
|
90
88
|
this.queue = new Queue(this.parentId + this.id, { redis: config.get('redis') });
|
|
89
|
+
|
|
91
90
|
this.queue.on('global:completed', (jobId, resultString) => {
|
|
92
91
|
const result = resultString ? JSON.parse(resultString) : "";
|
|
93
92
|
this.queue.getJob(jobId).then(async (job: JobEncrypted) => {
|
|
94
93
|
await this.handleJobResult('completed', decryptJobData(job, secret), result);
|
|
95
|
-
job.remove();
|
|
94
|
+
await job.remove();
|
|
96
95
|
});
|
|
97
96
|
});
|
|
97
|
+
|
|
98
98
|
this.queue.on('global:error', (jobId, result) => {
|
|
99
99
|
this.debug("unknown queue error", jobId, result);
|
|
100
100
|
});
|
|
101
|
+
|
|
101
102
|
this.queue.on('global:failed', (jobId, result) => {
|
|
102
103
|
this.queue.getJob(jobId).then(async (job: JobEncrypted) => {
|
|
103
104
|
await this.handleJobResult('failed', decryptJobData(job, secret), result);
|
|
104
|
-
job.remove();
|
|
105
|
+
await job.remove();
|
|
105
106
|
});
|
|
106
107
|
});
|
|
107
108
|
}
|
|
@@ -125,55 +126,58 @@ export default class PlatformInstance {
|
|
|
125
126
|
* Sends a message to client (user), can be registered with an event emitted from the platform
|
|
126
127
|
* process.
|
|
127
128
|
* @param sessionId ID of the socket connection to send the message to
|
|
128
|
-
* @param type type of message to emit. 'message', 'completed', 'failed'
|
|
129
129
|
* @param msg ActivityStream object to send to client
|
|
130
130
|
*/
|
|
131
|
-
public sendToClient(sessionId: string,
|
|
131
|
+
public sendToClient(sessionId: string, msg: ActivityStream) {
|
|
132
132
|
getSocket(sessionId).then((socket) => {
|
|
133
133
|
try {
|
|
134
134
|
// this property should never be exposed externally
|
|
135
135
|
delete msg.sessionSecret;
|
|
136
136
|
} catch (e) {}
|
|
137
137
|
msg.context = this.name;
|
|
138
|
-
if ((msg
|
|
138
|
+
if ((msg.type === 'error') && (typeof msg.actor === 'undefined') && (this.actor)) {
|
|
139
139
|
// ensure an actor is present if not otherwise defined
|
|
140
|
-
msg.actor = this.actor;
|
|
140
|
+
msg.actor = { id: this.actor };
|
|
141
141
|
}
|
|
142
|
-
socket.emit(
|
|
142
|
+
socket.emit('message', msg);
|
|
143
143
|
}, (err) => this.debug(`sendToClient ${err}`));
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// send message to every connected socket associated with this platform instance.
|
|
147
|
-
private async broadcastToSharedPeers(sessionId: string, msg:
|
|
147
|
+
private async broadcastToSharedPeers(sessionId: string, msg: ActivityStream) {
|
|
148
148
|
for (let sid of this.sessions.values()) {
|
|
149
149
|
if (sid !== sessionId) {
|
|
150
150
|
this.debug(`broadcasting message to ${sid}`);
|
|
151
|
-
await this.sendToClient(sid,
|
|
151
|
+
await this.sendToClient(sid, msg);
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
// handle job results coming in on the queue from platform instances
|
|
157
157
|
private async handleJobResult(type: string, jobData: JobDataDecrypted, result) {
|
|
158
|
-
this.debug(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
content: result ? result : "job failed for unknown reason"
|
|
168
|
-
};
|
|
158
|
+
this.debug(`${type} job ${jobData.title}`);
|
|
159
|
+
delete jobData.msg.sessionSecret;
|
|
160
|
+
let msg = jobData.msg;
|
|
161
|
+
if (type === 'failed') {
|
|
162
|
+
msg.error = result ? result : "job failed for unknown reason";
|
|
163
|
+
if ((this.config.persist) && (this.config.requireCredentials.includes(jobData.msg.type))) {
|
|
164
|
+
this.debug(`critical job type ${jobData.msg.type} failed, terminating platform instance`);
|
|
165
|
+
await this.destroy();
|
|
166
|
+
}
|
|
169
167
|
}
|
|
170
168
|
|
|
171
|
-
// send
|
|
172
|
-
|
|
169
|
+
// send result to client
|
|
170
|
+
const callback = this.completedJobHandlers.get(jobData.title);
|
|
171
|
+
if (callback) {
|
|
172
|
+
callback(msg);
|
|
173
|
+
this.completedJobHandlers.delete(jobData.title);
|
|
174
|
+
} else {
|
|
175
|
+
await this.sendToClient(jobData.sessionId, msg);
|
|
176
|
+
}
|
|
173
177
|
|
|
174
178
|
// let all related peers know of result as an independent message
|
|
175
179
|
// (not as part of a job completion, or failure)
|
|
176
|
-
await this.broadcastToSharedPeers(jobData.sessionId,
|
|
180
|
+
await this.broadcastToSharedPeers(jobData.sessionId, msg);
|
|
177
181
|
}
|
|
178
182
|
|
|
179
183
|
/**
|
|
@@ -181,19 +185,16 @@ export default class PlatformInstance {
|
|
|
181
185
|
* @param sessionId
|
|
182
186
|
* @param errorMessage
|
|
183
187
|
*/
|
|
184
|
-
private reportError(sessionId: string, errorMessage: any) {
|
|
185
|
-
const errorObject:
|
|
188
|
+
private async reportError(sessionId: string, errorMessage: any) {
|
|
189
|
+
const errorObject: ActivityStream = {
|
|
186
190
|
context: this.name,
|
|
187
|
-
|
|
188
|
-
actor: this.actor,
|
|
189
|
-
|
|
190
|
-
'@type': 'error',
|
|
191
|
-
content: errorMessage
|
|
192
|
-
}
|
|
191
|
+
type: 'error',
|
|
192
|
+
actor: { id: this.actor },
|
|
193
|
+
error: errorMessage
|
|
193
194
|
};
|
|
194
|
-
this.sendToClient(sessionId,
|
|
195
|
+
this.sendToClient(sessionId, errorObject);
|
|
195
196
|
this.sessions.clear();
|
|
196
|
-
this.destroy();
|
|
197
|
+
await this.destroy();
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
/**
|
|
@@ -226,7 +227,7 @@ export default class PlatformInstance {
|
|
|
226
227
|
this.reportError(sessionId, data[1]);
|
|
227
228
|
} else {
|
|
228
229
|
// treat like a message to clients
|
|
229
|
-
this.sendToClient(sessionId,
|
|
230
|
+
this.sendToClient(sessionId, data[1]);
|
|
230
231
|
}
|
|
231
232
|
}
|
|
232
233
|
};
|
package/src/platform.ts
CHANGED
|
@@ -3,9 +3,9 @@ import hash from "object-hash";
|
|
|
3
3
|
import config from './config';
|
|
4
4
|
import Queue from 'bull';
|
|
5
5
|
import { getPlatformId, decryptJobData } from "./common";
|
|
6
|
-
import {
|
|
6
|
+
import { ActivityStream, JobDataDecrypted, JobEncrypted } from "./sockethub";
|
|
7
7
|
import { MessageFromParent } from './platform-instance';
|
|
8
|
-
import {getSessionStore} from "./store";
|
|
8
|
+
import { getSessionStore } from "./store";
|
|
9
9
|
|
|
10
10
|
// command-line params
|
|
11
11
|
const parentId = process.argv[2];
|
|
@@ -23,7 +23,7 @@ logger(`platform handler initialized for ${platformName} ${identifier}`);
|
|
|
23
23
|
|
|
24
24
|
export interface PlatformSession {
|
|
25
25
|
debug(msg: string): void;
|
|
26
|
-
sendToClient(msg:
|
|
26
|
+
sendToClient(msg: ActivityStream, special?: string): void;
|
|
27
27
|
updateActor(credentials: object): void;
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -31,7 +31,7 @@ export interface PlatformSession {
|
|
|
31
31
|
* Handle any uncaught errors from the platform by alerting the worker and shutting down.
|
|
32
32
|
*/
|
|
33
33
|
process.on('uncaughtException', (err) => {
|
|
34
|
-
console.log('
|
|
34
|
+
console.log('EXCEPTION IN PLATFORM');
|
|
35
35
|
// eslint-disable-next-line security-node/detect-crlf
|
|
36
36
|
console.log(err.stack);
|
|
37
37
|
process.send(['error', err.toString()]);
|
|
@@ -84,7 +84,7 @@ function getCredentials(actorId: string, sessionId: string, sessionSecret: strin
|
|
|
84
84
|
if (platform.credentialsHash) {
|
|
85
85
|
if (platform.credentialsHash !== hash(credentials.object)) {
|
|
86
86
|
return cb('provided credentials do not match existing platform instance for actor '
|
|
87
|
-
+ platform.actor
|
|
87
|
+
+ platform.actor.id);
|
|
88
88
|
}
|
|
89
89
|
} else {
|
|
90
90
|
platform.credentialsHash = hash(credentials.object);
|
|
@@ -101,11 +101,11 @@ function getJobHandler(secret: string) {
|
|
|
101
101
|
return (job: JobEncrypted, done: Function) => {
|
|
102
102
|
const jobData: JobDataDecrypted = decryptJobData(job, secret);
|
|
103
103
|
const jobLog = debug(`${loggerPrefix}:${jobData.sessionId}`);
|
|
104
|
-
jobLog(`
|
|
104
|
+
jobLog(`received ${jobData.title} ${jobData.msg.type}`);
|
|
105
105
|
const sessionSecret = jobData.msg.sessionSecret;
|
|
106
106
|
delete jobData.msg.sessionSecret;
|
|
107
107
|
|
|
108
|
-
return getCredentials(jobData.msg.actor
|
|
108
|
+
return getCredentials(jobData.msg.actor.id, jobData.sessionId, sessionSecret,
|
|
109
109
|
(err, credentials) => {
|
|
110
110
|
if (err) { return done(new Error(err)); }
|
|
111
111
|
let jobCallbackCalled = false;
|
|
@@ -113,17 +113,33 @@ function getJobHandler(secret: string) {
|
|
|
113
113
|
if (jobCallbackCalled) { return; }
|
|
114
114
|
jobCallbackCalled = true;
|
|
115
115
|
if (err) {
|
|
116
|
-
|
|
116
|
+
jobLog(`errored ${jobData.title} ${jobData.msg.type}`);
|
|
117
|
+
let errMsg;
|
|
118
|
+
// some error objects (eg. TimeoutError) don't interoplate correctly to human-readable
|
|
119
|
+
// so we have to do this little dance
|
|
120
|
+
try {
|
|
121
|
+
errMsg = err.toString();
|
|
122
|
+
} catch (e) {
|
|
123
|
+
errMsg = err;
|
|
124
|
+
}
|
|
125
|
+
done(new Error(errMsg));
|
|
117
126
|
} else {
|
|
127
|
+
jobLog(`completed ${jobData.title} ${jobData.msg.type}`);
|
|
118
128
|
done(null, result);
|
|
119
129
|
}
|
|
120
130
|
};
|
|
121
131
|
if ((Array.isArray(platform.config.requireCredentials)) &&
|
|
122
|
-
(platform.config.requireCredentials.includes(jobData.msg
|
|
132
|
+
(platform.config.requireCredentials.includes(jobData.msg.type))) {
|
|
123
133
|
// add the credentials object if this method requires it
|
|
124
|
-
platform[jobData.msg
|
|
134
|
+
platform[jobData.msg.type](jobData.msg, credentials, doneCallback);
|
|
135
|
+
} else if (platform.config.persist) {
|
|
136
|
+
if (platform.initialized) {
|
|
137
|
+
platform[jobData.msg.type](jobData.msg, doneCallback);
|
|
138
|
+
} else {
|
|
139
|
+
done(new Error(`${jobData.msg.type} called on uninitialized platform`));
|
|
140
|
+
}
|
|
125
141
|
} else {
|
|
126
|
-
platform[jobData.msg
|
|
142
|
+
platform[jobData.msg.type](jobData.msg, doneCallback);
|
|
127
143
|
}
|
|
128
144
|
});
|
|
129
145
|
};
|
|
@@ -135,7 +151,7 @@ function getJobHandler(secret: string) {
|
|
|
135
151
|
* @param command string containing the type of command to be sent. 'message' or 'close'
|
|
136
152
|
*/
|
|
137
153
|
function getSendFunction(command: string) {
|
|
138
|
-
return function (msg:
|
|
154
|
+
return function (msg: ActivityStream, special?: string) {
|
|
139
155
|
process.send([command, msg, special]);
|
|
140
156
|
};
|
|
141
157
|
}
|
|
@@ -146,8 +162,8 @@ function getSendFunction(command: string) {
|
|
|
146
162
|
* @param credentials
|
|
147
163
|
*/
|
|
148
164
|
function updateActor(credentials) {
|
|
149
|
-
identifier = getPlatformId(platformName, credentials.actor
|
|
150
|
-
logger(`platform actor updated to ${credentials.actor
|
|
165
|
+
identifier = getPlatformId(platformName, credentials.actor.id);
|
|
166
|
+
logger(`platform actor updated to ${credentials.actor.id} identifier ${identifier}`);
|
|
151
167
|
logger = debug(`sockethub:platform:${identifier}`);
|
|
152
168
|
platform.credentialsHash = hash(credentials.object);
|
|
153
169
|
platform.debug = debug(`sockethub:platform:${platformName}:${identifier}`);
|