@sockethub/server 5.0.0-alpha.3 → 5.0.0-alpha.6

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 (127) hide show
  1. package/README.md +54 -60
  2. package/bin/sockethub +4 -3
  3. package/package.json +42 -60
  4. package/res/socket.io.js +4908 -0
  5. package/res/sockethub-client.js +602 -0
  6. package/res/sockethub-client.min.js +19 -0
  7. package/sockethub.config.example.json +2 -3
  8. package/src/bootstrap/init.d.ts +20 -7
  9. package/src/bootstrap/init.test.ts +211 -0
  10. package/src/bootstrap/init.ts +152 -75
  11. package/src/bootstrap/load-platforms.ts +151 -0
  12. package/src/config.test.ts +27 -22
  13. package/src/config.ts +82 -78
  14. package/src/defaults.json +24 -16
  15. package/src/index.ts +67 -27
  16. package/src/janitor.test.ts +211 -0
  17. package/src/janitor.ts +145 -77
  18. package/src/listener.ts +151 -57
  19. package/src/middleware/create-activity-object.test.ts +28 -8
  20. package/src/middleware/create-activity-object.ts +17 -8
  21. package/src/middleware/expand-activity-stream.test.data.ts +332 -346
  22. package/src/middleware/expand-activity-stream.test.ts +65 -66
  23. package/src/middleware/expand-activity-stream.ts +29 -19
  24. package/src/middleware/store-credentials.test.ts +74 -62
  25. package/src/middleware/store-credentials.ts +15 -15
  26. package/src/middleware/validate.test.data.ts +240 -242
  27. package/src/middleware/validate.test.ts +39 -78
  28. package/src/middleware/validate.ts +63 -39
  29. package/src/middleware.test.ts +168 -138
  30. package/src/middleware.ts +62 -43
  31. package/src/platform-instance.test.ts +507 -213
  32. package/src/platform-instance.ts +337 -219
  33. package/src/platform.test.ts +375 -0
  34. package/src/platform.ts +306 -139
  35. package/src/process-manager.ts +75 -51
  36. package/src/routes.test.ts +43 -89
  37. package/src/routes.ts +40 -77
  38. package/src/sentry.test.ts +106 -0
  39. package/src/sentry.ts +19 -0
  40. package/src/sockethub.ts +186 -153
  41. package/src/util.ts +5 -0
  42. package/coverage/tmp/coverage-93126-1649152190997-0.json +0 -1
  43. package/dist/bootstrap/init.d.ts +0 -18
  44. package/dist/bootstrap/init.js +0 -63
  45. package/dist/bootstrap/init.js.map +0 -1
  46. package/dist/bootstrap/platforms.js +0 -75
  47. package/dist/common.d.ts +0 -3
  48. package/dist/common.js +0 -20
  49. package/dist/common.js.map +0 -1
  50. package/dist/config.d.ts +0 -6
  51. package/dist/config.js +0 -102
  52. package/dist/config.js.map +0 -1
  53. package/dist/crypto.d.ts +0 -10
  54. package/dist/crypto.js +0 -38
  55. package/dist/crypto.js.map +0 -1
  56. package/dist/defaults.json +0 -28
  57. package/dist/index.d.ts +0 -2
  58. package/dist/index.js +0 -25
  59. package/dist/index.js.map +0 -1
  60. package/dist/janitor.d.ts +0 -15
  61. package/dist/janitor.js +0 -89
  62. package/dist/janitor.js.map +0 -1
  63. package/dist/listener.d.ts +0 -28
  64. package/dist/listener.js +0 -91
  65. package/dist/listener.js.map +0 -1
  66. package/dist/middleware/create-activity-object.d.ts +0 -6
  67. package/dist/middleware/create-activity-object.js +0 -19
  68. package/dist/middleware/create-activity-object.js.map +0 -1
  69. package/dist/middleware/expand-activity-stream.d.ts +0 -2
  70. package/dist/middleware/expand-activity-stream.js +0 -33
  71. package/dist/middleware/expand-activity-stream.js.map +0 -1
  72. package/dist/middleware/expand-activity-stream.test.data.d.ts +0 -480
  73. package/dist/middleware/expand-activity-stream.test.data.js +0 -360
  74. package/dist/middleware/expand-activity-stream.test.data.js.map +0 -1
  75. package/dist/middleware/store-credentials.d.ts +0 -3
  76. package/dist/middleware/store-credentials.js +0 -19
  77. package/dist/middleware/store-credentials.js.map +0 -1
  78. package/dist/middleware/validate.d.ts +0 -2
  79. package/dist/middleware/validate.js +0 -58
  80. package/dist/middleware/validate.js.map +0 -1
  81. package/dist/middleware/validate.test.data.d.ts +0 -532
  82. package/dist/middleware/validate.test.data.js +0 -263
  83. package/dist/middleware/validate.test.data.js.map +0 -1
  84. package/dist/middleware.d.ts +0 -10
  85. package/dist/middleware.js +0 -54
  86. package/dist/middleware.js.map +0 -1
  87. package/dist/platform-instance.d.ts +0 -77
  88. package/dist/platform-instance.js +0 -211
  89. package/dist/platform-instance.js.map +0 -1
  90. package/dist/platform.d.ts +0 -6
  91. package/dist/platform.js +0 -187
  92. package/dist/platform.js.map +0 -1
  93. package/dist/process-manager.d.ts +0 -11
  94. package/dist/process-manager.js +0 -78
  95. package/dist/process-manager.js.map +0 -1
  96. package/dist/routes.d.ts +0 -13
  97. package/dist/routes.js +0 -83
  98. package/dist/routes.js.map +0 -1
  99. package/dist/sockethub.d.ts +0 -39
  100. package/dist/sockethub.js +0 -119
  101. package/dist/sockethub.js.map +0 -1
  102. package/dist/store.d.ts +0 -5
  103. package/dist/store.js +0 -17
  104. package/dist/store.js.map +0 -1
  105. package/src/bootstrap/platforms.js +0 -75
  106. package/src/common.test.ts +0 -54
  107. package/src/common.ts +0 -14
  108. package/src/config.d.ts +0 -2
  109. package/src/crypto.d.ts +0 -5
  110. package/src/crypto.test.ts +0 -41
  111. package/src/crypto.ts +0 -41
  112. package/src/janitor.d.ts +0 -8
  113. package/src/middleware/validate.d.ts +0 -1
  114. package/src/middleware.d.ts +0 -21
  115. package/src/sockethub.d.ts +0 -1
  116. package/src/store.test.ts +0 -28
  117. package/src/store.ts +0 -17
  118. package/test/init-suite.js +0 -41
  119. package/test/queue.functional.test.js +0 -0
  120. package/test/sockethub-suite.js +0 -25
  121. package/tsconfig.json +0 -18
  122. package/views/examples/dummy.ejs +0 -93
  123. package/views/examples/feeds.ejs +0 -90
  124. package/views/examples/irc.ejs +0 -239
  125. package/views/examples/shared.js +0 -72
  126. package/views/examples/xmpp.ejs +0 -191
  127. package/views/index.ejs +0 -17
package/src/platform.ts CHANGED
@@ -1,191 +1,358 @@
1
- import debug from 'debug';
2
- import hash from "object-hash";
3
- import Queue from 'bull';
4
- import { IActivityStream } from "@sockethub/schemas";
5
- import { getPlatformId, decryptJobData } from "./common";
6
- import { JobDataDecrypted, JobEncrypted } from "./sockethub";
7
- import { MessageFromParent } from './platform-instance';
8
- import { getSessionStore } from "./store";
1
+ /**
2
+ * This runs as a stand-alone separate process that handles:
3
+ * 1. Starting up an instance of a given platform
4
+ * 2. Connecting to the redis job queue
5
+ * 3. Sending the platform jobs
6
+ * 4. Handling the result by putting it in the outgoing queue for
7
+ * sockethub core to send back to the client.
8
+ *
9
+ * If an exception is thrown by the platform, this process will die along with
10
+ * it and sockethub will start up another process. This ensures memory safety.
11
+ */
12
+ import { crypto, getPlatformId } from "@sockethub/crypto";
13
+ import {
14
+ CredentialsStore,
15
+ type JobDataDecrypted,
16
+ JobWorker,
17
+ } from "@sockethub/data-layer";
18
+ import type { JobHandler } from "@sockethub/data-layer";
19
+ import type {
20
+ ActivityStream,
21
+ CredentialsObject,
22
+ PersistentPlatformInterface,
23
+ PlatformCallback,
24
+ PlatformInterface,
25
+ PlatformSession,
26
+ } from "@sockethub/schemas";
27
+ import debug from "debug";
28
+ import config from "./config";
9
29
 
10
30
  // command-line params
11
31
  const parentId = process.argv[2];
12
32
  const platformName = process.argv[3];
13
33
  let identifier = process.argv[4];
34
+ const redisUrl = process.env.REDIS_URL;
35
+
14
36
  const loggerPrefix = `sockethub:platform:${platformName}:${identifier}`;
15
37
  let logger = debug(loggerPrefix);
16
38
 
17
- const redisConfig = process.env.REDIS_URL ? process.env.REDIS_URL
18
- : { host: process.env.REDIS_HOST, port: process.env.REDIS_PORT };
19
- const PlatformModule = require(`@sockethub/platform-${platformName}`);
39
+ // conditionally initialize sentry
40
+ let sentry: { readonly reportError: (err: Error) => void } = {
41
+ reportError: (err: Error) => {},
42
+ };
43
+ (async () => {
44
+ if (config.get("sentry:dsn")) {
45
+ logger("initializing sentry");
46
+ sentry = await import("./sentry");
47
+ }
48
+ })();
49
+
50
+ let jobWorker: JobWorker;
51
+ let jobWorkerStarted = false;
52
+ let parentSecret1: string;
53
+ let parentSecret2: string;
20
54
 
21
- let queueStarted = false;
22
- let parentSecret1: string, parentSecret2: string;
55
+ logger(`platform handler initializing for ${platformName} ${identifier}`);
23
56
 
24
- logger(`platform handler initialized for ${platformName} ${identifier}`);
57
+ interface SecretInterface {
58
+ parentSecret1: string;
59
+ parentSecret2: string;
60
+ }
25
61
 
26
- export interface PlatformSession {
27
- debug(msg: string): void;
28
- sendToClient(msg: IActivityStream, special?: string): void;
29
- updateActor(credentials: object): void;
62
+ interface SecretFromParent extends Array<string | SecretInterface> {
63
+ 0: string;
64
+ 1: SecretInterface;
30
65
  }
31
66
 
32
67
  /**
33
- * Handle any uncaught errors from the platform by alerting the worker and shutting down.
68
+ * Initialize platform module
34
69
  */
35
- process.on('uncaughtException', (err) => {
36
- console.log('EXCEPTION IN PLATFORM');
37
- // eslint-disable-next-line security-node/detect-crlf
38
- console.log(err.stack);
39
- process.send(['error', err.toString()]);
40
- process.exit(1);
41
- });
70
+ const platformSession: PlatformSession = {
71
+ debug: debug(`sockethub:platform:${platformName}:${identifier}`),
72
+ sendToClient: getSendFunction("message"),
73
+ updateActor: updateActor,
74
+ };
75
+
76
+ const platform: PlatformInterface = await (async () => {
77
+ const PlatformModule = await import(`@sockethub/platform-${platformName}`);
78
+ const p = new PlatformModule.default(platformSession) as PlatformInterface;
79
+ logger(`platform handler loaded for ${platformName} ${identifier}`);
80
+ return p as PlatformInterface;
81
+ })();
42
82
 
43
83
  /**
44
- * Incoming messages from the worker to this platform. Data is an array, the first property is the
45
- * method to call, the rest are params.
84
+ * Safely send error message to parent process, handling IPC channel closure
46
85
  */
47
- process.on('message', (data: MessageFromParent) => {
48
- if (data[0] === 'secrets') {
49
- parentSecret1 = data[1].parentSecret1;
50
- parentSecret2 = data[1].parentSecret2;
51
- startQueueListener();
52
- }
53
- });
86
+ function safeProcessSend(message: [string, string]) {
87
+ if (process.send && process.connected) {
88
+ try {
89
+ process.send(message);
90
+ } catch (ipcErr) {
91
+ console.error(`Failed to report error via IPC: ${ipcErr.message}`);
92
+ }
93
+ } else {
94
+ console.error("Cannot report error: IPC channel not available");
95
+ }
96
+ }
54
97
 
98
+ /**
99
+ * Type guard to check if a platform is persistent and has credentialsHash.
100
+ */
101
+ function isPersistentPlatform(
102
+ platform: PlatformInterface,
103
+ ): platform is PersistentPlatformInterface {
104
+ return platform.config.persist === true;
105
+ }
55
106
 
56
107
  /**
57
- * Initialize platform module
108
+ * Handle any uncaught errors from the platform by alerting the worker and shutting down.
58
109
  */
59
- const platformSession: PlatformSession = {
60
- debug: debug(`sockethub:platform:${platformName}:${identifier}`),
61
- sendToClient: getSendFunction('message'),
62
- updateActor: updateActor
63
- };
64
- const platform = new PlatformModule(platformSession);
110
+ process.once("uncaughtException", (err: Error) => {
111
+ console.log("EXCEPTION IN PLATFORM");
112
+ sentry.reportError(err);
113
+ console.log("error:\n", err.stack);
114
+ safeProcessSend(["error", err.toString()]);
115
+ process.exit(1);
116
+ });
117
+
118
+ process.once("unhandledRejection", (err: Error) => {
119
+ console.log("EXCEPTION IN PLATFORM");
120
+ sentry.reportError(err);
121
+ console.log("error:\n", err.stack);
122
+ safeProcessSend(["error", err.toString()]);
123
+ process.exit(1);
124
+ });
65
125
 
66
126
  /**
67
- * Get the credentials stored for this user in this sessions store, if given the correct
68
- * sessionSecret.
69
- * @param actorId
70
- * @param sessionId
71
- * @param sessionSecret
72
- * @param cb
127
+ * In the case of a parent disconnect, terminate child process.
73
128
  */
74
- function getCredentials(actorId: string, sessionId: string, sessionSecret: string, cb: Function) {
75
- if (platform.config.noCredentials) { return cb(); }
76
- const store = getSessionStore(parentId, parentSecret1, sessionId, sessionSecret);
77
- store.get(actorId, (err, credentials) => {
78
- if (platform.config.persist) {
79
- // don't continue if we don't get credentials
80
- if (err) { return cb(err); }
81
- } else if (! credentials) {
82
- // also skip if this is a non-persist platform with no credentials
83
- return cb();
84
- }
129
+ // Detect parent death via IPC disconnect
130
+ process.on("disconnect", () => {
131
+ console.log(`Parent disconnected. Child ${process.pid} exiting.`);
132
+ process.exit(1);
133
+ });
85
134
 
86
- if (platform.credentialsHash) {
87
- if (platform.credentialsHash !== hash(credentials.object)) {
88
- return cb('provided credentials do not match existing platform instance for actor '
89
- + platform.actor.id);
90
- }
135
+ /**
136
+ * Incoming messages from the worker to this platform. Data is an array, the first property is the
137
+ * method to call, the rest are params.
138
+ */
139
+ process.on("message", async (data: SecretFromParent) => {
140
+ if (data[0] === "secrets") {
141
+ const { parentSecret2: parentSecret3, parentSecret1: parentSecret } =
142
+ data[1];
143
+ parentSecret1 = parentSecret;
144
+ parentSecret2 = parentSecret3;
145
+ await startQueueListener();
91
146
  } else {
92
- platform.credentialsHash = hash(credentials.object);
147
+ throw new Error("received unknown command from parent thread");
93
148
  }
94
- cb(undefined, credentials);
95
- });
96
- }
149
+ });
97
150
 
98
151
  /**
99
152
  * Returns a function used to handle completed jobs from the platform code (the `done` callback).
100
- * @param secret the secret used to decrypt credentials
101
153
  */
102
- function getJobHandler(secret: string) {
103
- return (job: JobEncrypted, done: Function) => {
104
- const jobData: JobDataDecrypted = decryptJobData(job, secret);
105
- const jobLog = debug(`${loggerPrefix}:${jobData.sessionId}`);
106
- jobLog(`received ${jobData.title} ${jobData.msg.type}`);
107
- const sessionSecret = jobData.msg.sessionSecret;
108
- delete jobData.msg.sessionSecret;
109
-
110
- return getCredentials(jobData.msg.actor.id, jobData.sessionId, sessionSecret,
111
- (err, credentials) => {
112
- if (err) { return done(new Error(err)); }
113
- let jobCallbackCalled = false;
114
- const doneCallback = (err, result) => {
115
- if (jobCallbackCalled) { return; }
116
- jobCallbackCalled = true;
117
- if (err) {
118
- jobLog(`errored ${jobData.title} ${jobData.msg.type}`);
119
- let errMsg;
120
- // some error objects (eg. TimeoutError) don't interoplate correctly to human-readable
121
- // so we have to do this little dance
122
- try {
123
- errMsg = err.toString();
124
- } catch (e) {
125
- errMsg = err;
154
+ function getJobHandler(): JobHandler {
155
+ return async (
156
+ job: JobDataDecrypted,
157
+ ): Promise<string | undefined | ActivityStream> => {
158
+ return new Promise((resolve, reject) => {
159
+ const jobLog = debug(`${loggerPrefix}:${job.sessionId}`);
160
+ jobLog(`received ${job.title} ${job.msg.type}`);
161
+ const credentialStore = new CredentialsStore(
162
+ parentId,
163
+ job.sessionId,
164
+ parentSecret1 + job.msg.sessionSecret,
165
+ {
166
+ url: redisUrl,
167
+ },
168
+ );
169
+ // biome-ignore lint/performance/noDelete: <explanation>
170
+ delete job.msg.sessionSecret;
171
+
172
+ let jobCallbackCalled = false;
173
+ const doneCallback: PlatformCallback = (
174
+ err: Error | null,
175
+ result: null | ActivityStream,
176
+ ): void => {
177
+ if (jobCallbackCalled) {
178
+ resolve(null);
179
+ return;
180
+ }
181
+ jobCallbackCalled = true;
182
+ if (err) {
183
+ jobLog(`failed ${job.title} ${job.msg.type}`);
184
+ let errMsg: string | Error;
185
+ // some error objects (e.g. TimeoutError) don't interpolate correctly
186
+ // to being human-readable, so we have to do this little dance
187
+ try {
188
+ errMsg = err.toString();
189
+ } catch (err) {
190
+ errMsg = err;
191
+ }
192
+ sentry.reportError(new Error(errMsg as string));
193
+ reject(new Error(errMsg as string));
194
+ } else {
195
+ jobLog(`completed ${job.title} ${job.msg.type}`);
196
+ resolve(result);
197
+ }
198
+ };
199
+
200
+ if (platform.config.requireCredentials?.includes(job.msg.type)) {
201
+ // This method requires credentials and should be called even if the platform is not
202
+ // yet initialized, because they need to authenticate before they are initialized.
203
+
204
+ // Get credentialsHash for validation.
205
+ // For persistent platforms: undefined (or empty string) initially, then we set to hash after first
206
+ // successful call.
207
+ // For stateless platforms: always undefined (no validation, credentials used once per request)
208
+ // CredentialsStore skips validation when credentialsHash is falsy (undefined or empty string)
209
+ const credentialsHash = isPersistentPlatform(platform)
210
+ ? platform.credentialsHash
211
+ : undefined;
212
+
213
+ credentialStore
214
+ .get(job.msg.actor.id, credentialsHash)
215
+ .then((credentials) => {
216
+ // Create wrapper callback that updates credentialsHash after successful call
217
+ const wrappedCallback: PlatformCallback = (
218
+ err: Error | null,
219
+ result: null | ActivityStream,
220
+ ): void => {
221
+ if (!err && isPersistentPlatform(platform)) {
222
+ // Update credentialsHash after successful platform call.
223
+ // Only persistent platforms track credential state across requests.
224
+ platform.credentialsHash = crypto.objectHash(
225
+ credentials.object,
226
+ );
227
+ }
228
+ doneCallback(err, result);
229
+ };
230
+
231
+ // Proceed with platform method call
232
+ platform[job.msg.type](
233
+ job.msg,
234
+ credentials,
235
+ wrappedCallback,
236
+ );
237
+ })
238
+ .catch((err) => {
239
+ // Credential store error (invalid/missing credentials)
240
+ jobLog(`credential error ${err.toString()}`);
241
+
242
+ /**
243
+ * Critical distinction: handle credential errors differently based on platform state.
244
+ *
245
+ * For INITIALIZED platforms (already running):
246
+ * - Reject ONLY this job via doneCallback(err, null)
247
+ * - Keep the platform process running
248
+ * - Why: Platform instances can be shared by multiple clients (sessions).
249
+ * Terminating on credential error would crash the platform for ALL users,
250
+ * including those with valid credentials. This would create a DoS vector
251
+ * where one user's mistake (browser refresh with wrong creds, mistyped
252
+ * password, expired token) would break the service for everyone sharing
253
+ * that platform instance.
254
+ * - The failing client receives an error message, while other clients
255
+ * continue operating normally.
256
+ *
257
+ * For UNINITIALIZED platforms (not yet started):
258
+ * - Terminate the platform process via reject(err)
259
+ * - Why: If the initial connection fails due to invalid credentials, there's
260
+ * no valid session to preserve. The platform instance was created specifically
261
+ * for this connection attempt and has no other users. Terminating allows
262
+ * proper cleanup and a fresh start on the next attempt.
263
+ * - Error is reported to Sentry for monitoring authentication issues.
264
+ */
265
+ if (platform.config.initialized) {
266
+ // Platform already running - reject job only, preserve platform instance
267
+ doneCallback(err, null);
268
+ } else {
269
+ // Platform not initialized - terminate platform process
270
+ sentry.reportError(err);
271
+ reject(err);
272
+ }
273
+ });
274
+ } else if (
275
+ platform.config.persist &&
276
+ !platform.config.initialized
277
+ ) {
278
+ reject(
279
+ new Error(
280
+ `${job.msg.type} called on uninitialized platform`,
281
+ ),
282
+ );
283
+ } else {
284
+ try {
285
+ platform[job.msg.type](job.msg, doneCallback);
286
+ } catch (err) {
287
+ jobLog(`platform call failed ${err.toString()}`);
288
+ sentry.reportError(err);
289
+ reject(err);
290
+ }
126
291
  }
127
- done(new Error(errMsg));
128
- } else {
129
- jobLog(`completed ${jobData.title} ${jobData.msg.type}`);
130
- done(null, result);
131
- }
132
- };
133
- if ((Array.isArray(platform.config.requireCredentials)) &&
134
- (platform.config.requireCredentials.includes(jobData.msg.type))) {
135
- // add the credentials object if this method requires it
136
- platform[jobData.msg.type](jobData.msg, credentials, doneCallback);
137
- } else if (platform.config.persist) {
138
- if (platform.initialized) {
139
- platform[jobData.msg.type](jobData.msg, doneCallback);
140
- } else {
141
- done(new Error(`${jobData.msg.type} called on uninitialized platform`));
142
- }
143
- } else {
144
- platform[jobData.msg.type](jobData.msg, doneCallback);
145
- }
146
- });
147
- };
292
+ });
293
+ };
148
294
  }
149
295
 
150
296
  /**
151
- * Get an function which sends a message to the parent thread (PlatformInstance). The platform
297
+ * Get a function which sends a message to the parent thread (PlatformInstance). The platform
152
298
  * can call that function to send messages back to the client.
153
299
  * @param command string containing the type of command to be sent. 'message' or 'close'
154
300
  */
155
301
  function getSendFunction(command: string) {
156
- return function (msg: IActivityStream, special?: string) {
157
- process.send([command, msg, special]);
158
- };
302
+ return (msg: ActivityStream, special?: string) => {
303
+ if (platform.config.persist) {
304
+ process.send([command, msg, special]);
305
+ } else {
306
+ logger(
307
+ "sendToClient called on non-persistent platform, rejecting.",
308
+ );
309
+ }
310
+ };
159
311
  }
160
312
 
161
313
  /**
162
- * When a user changes it's actor name, the channel identifier changes, we need to ensure that
314
+ * When a user changes its actor name, the channel identifier changes, we need to ensure that
163
315
  * both the queue thread (listening on the channel for jobs) and the logging object are updated.
164
316
  * @param credentials
165
317
  */
166
- function updateActor(credentials) {
167
- identifier = getPlatformId(platformName, credentials.actor.id);
168
- logger(`platform actor updated to ${credentials.actor.id} identifier ${identifier}`);
169
- logger = debug(`sockethub:platform:${identifier}`);
170
- platform.credentialsHash = hash(credentials.object);
171
- platform.debug = debug(`sockethub:platform:${platformName}:${identifier}`);
172
- process.send(['updateActor', undefined, identifier]);
173
- startQueueListener(true);
318
+ async function updateActor(credentials: CredentialsObject): Promise<void> {
319
+ identifier = getPlatformId(platformName, credentials.actor.id);
320
+ logger(
321
+ `platform actor updated to ${credentials.actor.id} identifier ${identifier}`,
322
+ );
323
+ logger = debug(`sockethub:platform:${identifier}`);
324
+
325
+ // Update credentialsHash for persistent platforms (tracks actor-specific state)
326
+ if (isPersistentPlatform(platform)) {
327
+ platform.credentialsHash = crypto.objectHash(credentials.object);
328
+ }
329
+
330
+ platform.debug = debug(`sockethub:platform:${platformName}:${identifier}`);
331
+ process.send(["updateActor", undefined, identifier]);
332
+ await startQueueListener(true);
174
333
  }
175
334
 
176
335
  /**
177
- * starts listening on the queue for incoming jobs
178
- * @param refresh boolean if the param is true, we re-init the queue.process
336
+ * Starts listening on the queue for incoming jobs
337
+ * @param refresh boolean if the param is true, we re-init the `queue.process`
179
338
  * (used when identifier changes)
180
339
  */
181
- function startQueueListener(refresh: boolean = false) {
182
- const secret = parentSecret1 + parentSecret2;
183
- if ((queueStarted) && (!refresh)) {
184
- logger('start queue called multiple times, skipping');
185
- return;
186
- }
187
- const queue = new Queue(parentId + identifier, { redis: redisConfig });
188
- queueStarted = true;
189
- logger('listening on the queue for incoming jobs');
190
- queue.process(getJobHandler(secret));
340
+ async function startQueueListener(refresh = false) {
341
+ if (jobWorkerStarted) {
342
+ if (refresh) {
343
+ await jobWorker.shutdown();
344
+ } else {
345
+ logger("start queue called multiple times, skipping");
346
+ return;
347
+ }
348
+ }
349
+ jobWorker = new JobWorker(
350
+ parentId,
351
+ identifier,
352
+ parentSecret1 + parentSecret2,
353
+ { url: redisUrl },
354
+ );
355
+ logger("listening on the queue for incoming jobs");
356
+ jobWorker.onJob(getJobHandler());
357
+ jobWorkerStarted = true;
191
358
  }
@@ -1,64 +1,88 @@
1
- import init from './bootstrap/init';
1
+ import { getPlatformId } from "@sockethub/crypto";
2
+
3
+ import type { IInitObject } from "./bootstrap/init.js";
2
4
  import PlatformInstance, {
3
- platformInstances, PlatformInstanceParams, MessageFromParent } from "./platform-instance";
4
- import { getPlatformId } from "./common";
5
+ platformInstances,
6
+ type PlatformInstanceParams,
7
+ type MessageFromParent,
8
+ } from "./platform-instance.js";
5
9
 
6
10
  class ProcessManager {
7
- private readonly parentId: string;
8
- private readonly parentSecret1: string;
9
- private readonly parentSecret2: string;
11
+ private readonly parentId: string;
12
+ private readonly parentSecret1: string;
13
+ private readonly parentSecret2: string;
14
+ private init: IInitObject;
10
15
 
11
- constructor(parentId: string, parentSecret1: string, parentSecret2: string) {
12
- this.parentId = parentId;
13
- this.parentSecret1 = parentSecret1;
14
- this.parentSecret2 = parentSecret2;
15
- }
16
+ constructor(
17
+ parentId: string,
18
+ parentSecret1: string,
19
+ parentSecret2: string,
20
+ init: IInitObject,
21
+ ) {
22
+ this.parentId = parentId;
23
+ this.parentSecret1 = parentSecret1;
24
+ this.parentSecret2 = parentSecret2;
25
+ this.init = init;
26
+ }
16
27
 
17
- get(platform: string, actorId: string, sessionId?: string): PlatformInstance {
18
- const platformDetails = init.platforms.get(platform);
19
- let pi;
28
+ get(
29
+ platform: string,
30
+ actorId: string,
31
+ sessionId?: string,
32
+ ): PlatformInstance {
33
+ const platformDetails = this.init.platforms.get(platform);
34
+ let pi: PlatformInstance;
20
35
 
21
- if (platformDetails.config.persist) {
22
- // ensure process is started - one for each actor
23
- pi = this.ensureProcess(platform, sessionId, actorId);
24
- } else {
25
- // ensure process is started - one for all jobs
26
- pi = this.ensureProcess(platform);
36
+ if (platformDetails.config.persist) {
37
+ // ensure process is started - one for each actor
38
+ pi = this.ensureProcess(platform, sessionId, actorId);
39
+ } else {
40
+ // ensure process is started - one for all jobs
41
+ pi = this.ensureProcess(platform);
42
+ }
43
+ pi.config = platformDetails.config;
44
+ return pi;
27
45
  }
28
- pi.config = platformDetails.config;
29
- return pi;
30
- }
31
46
 
32
- private createPlatformInstance(identifier: string, platform: string,
33
- actor?: string): PlatformInstance {
34
- const secrets: MessageFromParent = [
35
- 'secrets', {
36
- parentSecret1: this.parentSecret1,
37
- parentSecret2: this.parentSecret2
38
- }
39
- ];
40
- const platformInstanceConfig: PlatformInstanceParams = {
41
- identifier: identifier,
42
- platform: platform,
43
- parentId: this.parentId,
44
- actor: actor
45
- };
46
- const platformInstance = new PlatformInstance(platformInstanceConfig);
47
- platformInstance.initQueue(this.parentSecret1 + this.parentSecret2);
48
- platformInstance.process.send(secrets);
49
- return platformInstance;
50
- }
47
+ private createPlatformInstance(
48
+ identifier: string,
49
+ platform: string,
50
+ actor?: string,
51
+ ): PlatformInstance {
52
+ const secrets: MessageFromParent = [
53
+ "secrets",
54
+ {
55
+ parentSecret1: this.parentSecret1,
56
+ parentSecret2: this.parentSecret2,
57
+ },
58
+ ];
59
+ const platformInstanceConfig: PlatformInstanceParams = {
60
+ identifier: identifier,
61
+ platform: platform,
62
+ parentId: this.parentId,
63
+ actor: actor,
64
+ };
65
+ const platformInstance = new PlatformInstance(platformInstanceConfig);
66
+ platformInstance.initQueue(this.parentSecret1 + this.parentSecret2);
67
+ platformInstance.process.send(secrets);
68
+ return platformInstance;
69
+ }
51
70
 
52
- private ensureProcess(platform: string, sessionId?: string, actor?: string): PlatformInstance {
53
- const identifier = getPlatformId(platform, actor);
54
- const platformInstance = platformInstances.get(identifier) ||
55
- this.createPlatformInstance(identifier, platform, actor);
56
- if (sessionId) {
57
- platformInstance.registerSession(sessionId);
71
+ private ensureProcess(
72
+ platform: string,
73
+ sessionId?: string,
74
+ actor?: string,
75
+ ): PlatformInstance {
76
+ const identifier = getPlatformId(platform, actor);
77
+ const platformInstance =
78
+ platformInstances.get(identifier) ||
79
+ this.createPlatformInstance(identifier, platform, actor);
80
+ if (sessionId) {
81
+ platformInstance.registerSession(sessionId);
82
+ }
83
+ platformInstances.set(identifier, platformInstance);
84
+ return platformInstance;
58
85
  }
59
- platformInstances.set(identifier, platformInstance);
60
- return platformInstance;
61
- }
62
86
  }
63
87
 
64
88
  export default ProcessManager;