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.
Files changed (71) hide show
  1. package/bin/sockethub +23 -20
  2. package/coverage/tmp/coverage-15699-1646422276150-0.json +1 -0
  3. package/dist/bootstrap/init.js +1 -1
  4. package/dist/bootstrap/init.js.map +1 -1
  5. package/dist/bootstrap/platforms.js +14 -18
  6. package/dist/janitor.js +11 -5
  7. package/dist/janitor.js.map +1 -1
  8. package/dist/middleware/create-activity-object.js.map +1 -1
  9. package/dist/middleware/expand-activity-stream.js +33 -0
  10. package/dist/middleware/expand-activity-stream.js.map +1 -0
  11. package/dist/middleware/expand-activity-stream.test.data.js +360 -0
  12. package/dist/middleware/expand-activity-stream.test.data.js.map +1 -0
  13. package/dist/middleware/store-credentials.js +1 -1
  14. package/dist/middleware/store-credentials.js.map +1 -1
  15. package/dist/middleware/validate.js +30 -104
  16. package/dist/middleware/validate.js.map +1 -1
  17. package/dist/middleware/validate.test.data.js +151 -111
  18. package/dist/middleware/validate.test.data.js.map +1 -1
  19. package/dist/middleware.js +47 -77
  20. package/dist/middleware.js.map +1 -1
  21. package/dist/platform-instance.js +47 -54
  22. package/dist/platform-instance.js.map +1 -1
  23. package/dist/platform.js +29 -10
  24. package/dist/platform.js.map +1 -1
  25. package/dist/process-manager.js +5 -2
  26. package/dist/process-manager.js.map +1 -1
  27. package/dist/routes.js +3 -5
  28. package/dist/routes.js.map +1 -1
  29. package/dist/sockethub-client.js +2604 -0
  30. package/dist/sockethub-client.js.map +1 -0
  31. package/dist/sockethub-client.min.js +2 -0
  32. package/dist/sockethub-client.min.js.LICENSE.txt +24 -0
  33. package/dist/sockethub.js +67 -36
  34. package/dist/sockethub.js.map +1 -1
  35. package/package.json +40 -36
  36. package/src/bootstrap/init.ts +6 -2
  37. package/src/bootstrap/platforms.js +14 -18
  38. package/src/crypto.ts +3 -3
  39. package/src/janitor.ts +18 -10
  40. package/src/middleware/create-activity-object.ts +1 -2
  41. package/src/middleware/expand-activity-stream.test.data.ts +365 -0
  42. package/src/middleware/expand-activity-stream.test.ts +78 -0
  43. package/src/middleware/expand-activity-stream.ts +27 -0
  44. package/src/middleware/store-credentials.test.ts +12 -12
  45. package/src/middleware/store-credentials.ts +4 -4
  46. package/src/middleware/validate.test.data.ts +154 -120
  47. package/src/middleware/validate.test.ts +7 -10
  48. package/src/middleware/validate.ts +30 -123
  49. package/src/middleware.test.ts +59 -26
  50. package/src/middleware.ts +44 -76
  51. package/src/platform-instance.test.ts +8 -10
  52. package/src/platform-instance.ts +58 -57
  53. package/src/platform.ts +30 -14
  54. package/src/process-manager.ts +7 -4
  55. package/src/routes.ts +3 -6
  56. package/src/serve.ts +1 -1
  57. package/src/sockethub-client.test.ts +235 -0
  58. package/src/sockethub-client.ts +164 -0
  59. package/src/sockethub.ts +75 -68
  60. package/views/examples/dummy.ejs +6 -6
  61. package/views/examples/feeds.ejs +8 -8
  62. package/views/examples/irc.ejs +64 -58
  63. package/views/examples/shared.js +31 -29
  64. package/views/examples/xmpp.ejs +48 -57
  65. package/webpack.minified.config.js +14 -0
  66. package/webpack.normal.config.js +14 -0
  67. package/coverage/tmp/coverage-24546-1630615250805-0.json +0 -1
  68. package/dist/client/sockethub-client.js +0 -171
  69. package/dist/client/sockethub-client.js.map +0 -1
  70. package/src/client/sockethub-client.js +0 -178
  71. package/test/middleware-suite.js +0 -101
@@ -1,27 +1,43 @@
1
1
  import { expect } from 'chai';
2
2
  import * as sinon from 'sinon';
3
3
 
4
- import createMiddleware from "./middleware";
4
+ import middleware, { MiddlewareChain } from "./middleware";
5
5
 
6
6
  describe("Middleware", () => {
7
- it("createMiddleware() is a function", () => {
8
- expect(typeof createMiddleware).to.be.equal('function');
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 = createMiddleware(()=>{});
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(()=>{}, 'foobar');}).to.throw(
15
- 'middleware chain can only take other functions as arguments.');
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 = createMiddleware(()=>{});
22
- const entry = mw(...funcs);
23
- entry('foobar', (err) => {
24
- expect(err).to.be.null;
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) => { cb(new Error('some error')); };
52
+ const callbackError = (data, cb) => {
53
+ cb(new Error('some error')); };
37
54
  const funcs = [ sinon.spy(callbackError) ];
38
- const mw = createMiddleware((err)=>{
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 = createMiddleware((err, msg) => {
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
- const entry = mw(...funcs);
58
- entry('foobar', (err) => {
84
+ mw.done()('foobar', (data) => {
59
85
  // FIXME -- We need to also handle socket.io callbacks!
60
- expect(err instanceof Error).to.be.true;
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 = createMiddleware((err, msg) => {
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
- const entry = mw(...funcs);
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 = createMiddleware((err, msg) => {
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(...funcs)('foo', (err) => {
105
- expect(err).to.be.null;
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
- * Returns a function which accepts a list of middleware functions to call, in order.
3
- * The errorHandler will be called whenever an error occurs.
4
- * @param {function} errorHandler
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
- function getMiddlewareInstance(chain: Array<Function>, errorHandler: Function) {
20
- return (...initialParams) => {
21
- // placeholder callback in case one is not provided
22
- let callback = (err: Error|null) => { if (err) {} };
23
- let position = 0;
13
+ constructor(name: string) {
14
+ this.name = name;
15
+ this.logger = debug(`sockethub:middleware:${name}`);
16
+ }
24
17
 
25
- if (typeof initialParams[initialParams.length - 1] === 'function') {
26
- // callback has been provided
27
- callback = initialParams.pop();
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
- function callFunc(...params) {
31
- if (params[0] instanceof Error) {
32
- errorHandler(...params);
33
- callback(params[0]);
34
- } else if (typeof chain[position] === 'function') {
35
- params.push(callFunc);
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
- callFunc(...initialParams);
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', 'message',
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, undefined, 'completed',
193
- {foo: 'bar', object: {'@type': 'result', content: 'a good result message'}});
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, undefined, 'failed',
201
- {foo: 'bar', object: {'@type': 'error', content: 'a bad result message'}});
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', 'message', {foo:'bar'});
233
+ 'my session id', {foo:'bar'});
236
234
  });
237
235
  });
238
236
  });
@@ -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 { ActivityObject, JobDataDecrypted, JobEncrypted } from "./sockethub";
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|ActivityObject>{
22
- 0: string, 1: ActivityObject, 2: string}
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
- public readonly global?: boolean = false;
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.close();
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, type: string, msg: ActivityObject) {
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['@type'] === 'error') && (typeof msg.actor === 'undefined') && (this.actor)) {
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(type, msg);
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: ActivityObject) {
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, 'message', msg);
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(`job ${jobData.title}: ${type}`);
159
- if ((type === 'completed') && (result)) {
160
- jobData.msg.object = {
161
- '@type': 'result',
162
- content: result
163
- };
164
- } else if (type === 'failed') {
165
- jobData.msg.object = {
166
- '@type': 'error',
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 message to client as completed for failed job
172
- await this.sendToClient(jobData.sessionId, type, jobData.msg);
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, jobData.msg);
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: ActivityObject = {
188
+ private async reportError(sessionId: string, errorMessage: any) {
189
+ const errorObject: ActivityStream = {
186
190
  context: this.name,
187
- '@type': 'error',
188
- actor: this.actor,
189
- object: {
190
- '@type': 'error',
191
- content: errorMessage
192
- }
191
+ type: 'error',
192
+ actor: { id: this.actor },
193
+ error: errorMessage
193
194
  };
194
- this.sendToClient(sessionId, 'message', errorObject);
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, 'message', data[1]);
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 { ActivityObject, JobDataDecrypted, JobEncrypted } from "./sockethub";
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: ActivityObject, special?: string): void;
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('UNCAUGHT EXCEPTION IN PLATFORM');
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['@id']);
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(`job ${jobData.title}: ${jobData.msg['@type']}`);
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['@id'], jobData.sessionId, sessionSecret,
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
- done(err instanceof Error ? err : new Error(err));
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['@type']))) {
132
+ (platform.config.requireCredentials.includes(jobData.msg.type))) {
123
133
  // add the credentials object if this method requires it
124
- platform[jobData.msg['@type']](jobData.msg, credentials, doneCallback);
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['@type']](jobData.msg, doneCallback);
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: ActivityObject, special?: string) {
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['@id']);
150
- logger(`platform actor updated to ${credentials.actor['@id']} identifier ${identifier}`);
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}`);