@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
@@ -1,242 +1,360 @@
1
- import { ChildProcess, fork } from 'child_process';
2
- import { join } from 'path';
3
- import { debug, Debugger } from 'debug';
4
- import Queue from 'bull';
5
- import { IActivityStream } from '@sockethub/schemas';
1
+ import { type ChildProcess, fork } from "node:child_process";
2
+ import { join } from "node:path";
3
+ import debug from "debug";
6
4
 
7
- import config from "./config";
8
- import { JobDataDecrypted, JobEncrypted } from "./sockethub";
9
- import { getSocket } from "./listener";
10
- import { decryptJobData } from "./common";
5
+ import { type JobDataDecrypted, JobQueue } from "@sockethub/data-layer";
6
+ import type {
7
+ ActivityStream,
8
+ CompletedJobHandler,
9
+ InternalActivityStream,
10
+ Logger,
11
+ PlatformConfig,
12
+ } from "@sockethub/schemas";
13
+ import type { Socket } from "socket.io";
14
+
15
+ import config from "./config.js";
16
+ import { getSocket } from "./listener.js";
17
+ import { __dirname } from "./util.js";
11
18
 
12
19
  // collection of platform instances, stored by `id`
13
20
  export const platformInstances = new Map<string, PlatformInstance>();
14
21
 
15
22
  export interface PlatformInstanceParams {
16
- identifier: string;
17
- platform: string;
18
- parentId?: string;
19
- actor?: string;
23
+ identifier: string;
24
+ platform: string;
25
+ parentId?: string;
26
+ actor?: string;
20
27
  }
21
28
 
22
- interface MessageFromPlatform extends Array<string | IActivityStream>{
23
- 0: string, 1: IActivityStream, 2: string}
24
- export interface MessageFromParent extends Array<string|any>{0: string, 1: any}
29
+ type EnvFormat = {
30
+ DEBUG?: string;
31
+ REDIS_URL: string;
32
+ };
33
+
34
+ interface MessageFromPlatform extends Array<string | ActivityStream> {
35
+ 0: string;
36
+ 1: ActivityStream;
37
+ 2: string;
38
+ }
25
39
 
26
- interface PlatformConfig {
27
- persist?: boolean;
28
- requireCredentials?: Array<string>;
40
+ export interface MessageFromParent extends Array<string | unknown> {
41
+ 0: string;
42
+ 1: unknown;
29
43
  }
30
44
 
31
45
  export default class PlatformInstance {
32
- flaggedForTermination: boolean = false;
33
- id: string;
34
- queue: Queue;
35
- completedJobHandlers: Map<string, Function> = new Map();
36
- config: PlatformConfig = {};
37
- readonly name: string;
38
- readonly process: ChildProcess;
39
- readonly debug: Debugger;
40
- readonly parentId: string;
41
- readonly sessions: Set<string> = new Set();
42
- readonly sessionCallbacks: object = {
43
- 'close': (() => new Map())(),
44
- 'message': (() => new Map())()
45
- };
46
- public readonly global?: boolean = false;
47
- private readonly actor?: string;
48
-
49
- constructor(params: PlatformInstanceParams) {
50
- this.id = params.identifier;
51
- this.name = params.platform;
52
- this.parentId = params.parentId;
53
- if (params.actor) {
54
- this.actor = params.actor;
55
- } else {
56
- this.global = true;
46
+ id: string;
47
+ flaggedForTermination = false;
48
+ queue: JobQueue;
49
+ JobQueue: typeof JobQueue;
50
+ getSocket: typeof getSocket;
51
+ readonly global: boolean = false;
52
+ readonly completedJobHandlers: Map<string, CompletedJobHandler> = new Map();
53
+ config: PlatformConfig;
54
+ readonly name: string;
55
+ process: ChildProcess;
56
+ readonly debug: Logger;
57
+ readonly parentId: string;
58
+ readonly sessions: Set<string> = new Set();
59
+ readonly sessionCallbacks: object = {
60
+ close: (() => new Map())(),
61
+ message: (() => new Map())(),
62
+ };
63
+ private readonly actor?: string;
64
+
65
+ constructor(params: PlatformInstanceParams) {
66
+ this.id = params.identifier;
67
+ this.name = params.platform;
68
+ this.parentId = params.parentId;
69
+ if (params.actor) {
70
+ this.actor = params.actor;
71
+ } else {
72
+ this.global = true;
73
+ }
74
+
75
+ this.debug = debug(`sockethub:server:platform-instance:${this.id}`);
76
+ const env: EnvFormat = {
77
+ REDIS_URL: config.get("redis:url") as string,
78
+ };
79
+ if (process.env.DEBUG) {
80
+ env.DEBUG = process.env.DEBUG;
81
+ }
82
+
83
+ this.createQueue();
84
+ this.initProcess(this.parentId, this.name, this.id, env);
85
+ this.createGetSocket();
57
86
  }
58
87
 
59
- this.debug = debug(`sockethub:platform-instance:${this.id}`);
60
- // spin off a process
61
- const env = config.get('redis:url') ? { REDIS_URL: config.get('redis:url') }
62
- : { REDIS_HOST: config.get('redis:host'), REDIS_PORT: config.get('redis:port') };
63
- this.process = fork(
64
- join(__dirname, 'platform.js'),
65
- [this.parentId, this.name, this.id],
66
- { env: env });
67
- }
68
-
69
- /**
70
- * Destroys all references to this platform instance, internal listeners and controlled processes
71
- */
72
- public async destroy() {
73
- this.debug(`cleaning up`);
74
- this.flaggedForTermination = true;
75
- try {
76
- await this.queue.removeAllListeners();
77
- } catch (e) { }
78
- try {
79
- await this.queue.obliterate({ force: true });
80
- } catch (e) { }
81
- try {
82
- delete this.queue;
83
- await this.process.removeAllListeners('close');
84
- await this.process.unref();
85
- await this.process.kill();
86
- } catch (e) { }
87
- platformInstances.delete(this.id);
88
- }
89
-
90
- /**
91
- * When jobs are completed or failed, we prepare the results and send them to the client socket
92
- */
93
- public initQueue(secret: string) {
94
- this.queue = new Queue(this.parentId + this.id, { redis: config.get('redis') });
95
-
96
- this.queue.on('global:completed', (jobId, resultString) => {
97
- const result = resultString ? JSON.parse(resultString) : "";
98
- this.queue.getJob(jobId).then(async (job: JobEncrypted) => {
99
- await this.handleJobResult('completed', decryptJobData(job, secret), result);
100
- await job.remove();
101
- });
102
- });
103
-
104
- this.queue.on('global:error', (jobId, result) => {
105
- this.debug("unknown queue error", jobId, result);
106
- });
107
-
108
- this.queue.on('global:failed', (jobId, result) => {
109
- this.queue.getJob(jobId).then(async (job: JobEncrypted) => {
110
- await this.handleJobResult('failed', decryptJobData(job, secret), result);
111
- await job.remove();
112
- });
113
- });
114
- }
115
-
116
- /**
117
- * Register listener to be called when the process emits a message.
118
- * @param sessionId ID of socket connection that will receive messages from platform emits
119
- */
120
- public registerSession(sessionId: string) {
121
- if (! this.sessions.has(sessionId)) {
122
- this.sessions.add(sessionId);
123
- for (let type of Object.keys(this.sessionCallbacks)) {
124
- const cb = this.callbackFunction(type, sessionId);
125
- this.process.on(type, cb);
126
- this.sessionCallbacks[type].set(sessionId, cb);
127
- }
88
+ createQueue() {
89
+ this.JobQueue = JobQueue;
128
90
  }
129
- }
130
-
131
- /**
132
- * Sends a message to client (user), can be registered with an event emitted from the platform
133
- * process.
134
- * @param sessionId ID of the socket connection to send the message to
135
- * @param msg ActivityStream object to send to client
136
- */
137
- public sendToClient(sessionId: string, msg: IActivityStream) {
138
- getSocket(sessionId).then((socket) => {
139
- try {
140
- // this property should never be exposed externally
141
- delete msg.sessionSecret;
142
- } catch (e) {}
143
- msg.context = this.name;
144
- if ((msg.type === 'error') && (typeof msg.actor === 'undefined') && (this.actor)) {
145
- // ensure an actor is present if not otherwise defined
146
- msg.actor = { id: this.actor, type: 'unknown' };
147
- }
148
- socket.emit('message', msg);
149
- }, (err) => this.debug(`sendToClient ${err}`));
150
- }
151
-
152
- // send message to every connected socket associated with this platform instance.
153
- private async broadcastToSharedPeers(sessionId: string, msg: IActivityStream) {
154
- for (let sid of this.sessions.values()) {
155
- if (sid !== sessionId) {
156
- this.debug(`broadcasting message to ${sid}`);
157
- await this.sendToClient(sid, msg);
158
- }
91
+
92
+ initProcess(parentId: string, name: string, id: string, env: EnvFormat) {
93
+ // spin off a process
94
+ this.process = fork(
95
+ join(__dirname, "platform.js"),
96
+ [parentId, name, id],
97
+ { env: env },
98
+ );
159
99
  }
160
- }
161
-
162
- // handle job results coming in on the queue from platform instances
163
- private async handleJobResult(type: string, jobData: JobDataDecrypted, result) {
164
- this.debug(`${type} job ${jobData.title}`);
165
- delete jobData.msg.sessionSecret;
166
- let msg = jobData.msg;
167
- if (type === 'failed') {
168
- msg.error = result ? result : "job failed for unknown reason";
169
- if ((this.config.persist) && (this.config.requireCredentials.includes(jobData.msg.type))) {
170
- this.debug(`critical job type ${jobData.msg.type} failed, terminating platform instance`);
171
- await this.destroy();
172
- }
100
+
101
+ createGetSocket() {
102
+ this.getSocket = getSocket;
173
103
  }
174
104
 
175
- // send result to client
176
- const callback = this.completedJobHandlers.get(jobData.title);
177
- if (callback) {
178
- callback(msg);
179
- this.completedJobHandlers.delete(jobData.title);
180
- } else {
181
- await this.sendToClient(jobData.sessionId, msg);
105
+ /**
106
+ * Destroys all references to this platform instance, internal listeners and controlled processes
107
+ */
108
+ public async shutdown() {
109
+ this.debug("platform process shutdown");
110
+ this.flaggedForTermination = true;
111
+
112
+ try {
113
+ this.process.removeAllListeners("close");
114
+ this.process.unref();
115
+ this.process.kill();
116
+ } catch (e) {
117
+ // needs to happen
118
+ }
119
+
120
+ try {
121
+ await this.queue.shutdown();
122
+ this.queue = undefined;
123
+ } catch (e) {
124
+ // this needs to happen
125
+ }
126
+
127
+ try {
128
+ platformInstances.delete(this.id);
129
+ } catch (e) {
130
+ // this needs to happen
131
+ }
182
132
  }
183
133
 
184
- // let all related peers know of result as an independent message
185
- // (not as part of a job completion, or failure)
186
- await this.broadcastToSharedPeers(jobData.sessionId, msg);
187
- }
188
-
189
- /**
190
- * Sends error message to client and clears all references to this class.
191
- * @param sessionId
192
- * @param errorMessage
193
- */
194
- private async reportError(sessionId: string, errorMessage: any) {
195
- const errorObject: IActivityStream = {
196
- context: this.name,
197
- type: 'error',
198
- actor: { id: this.actor, type: 'unknown' },
199
- error: errorMessage
200
- };
201
- this.sendToClient(sessionId, errorObject);
202
- this.sessions.clear();
203
- await this.destroy();
204
- }
205
-
206
- /**
207
- * Updates the instance with a new identifier, updating the platformInstances mapping as well.
208
- * @param identifier
209
- */
210
- private updateIdentifier(identifier: string) {
211
- platformInstances.delete(this.id);
212
- this.id = identifier;
213
- platformInstances.set(this.id, this);
214
- }
215
-
216
- /**
217
- * Generates a function tied to a given client session (socket connection), the generated
218
- * function will be called for each session ID registered, for every platform emit.
219
- * @param listener
220
- * @param sessionId
221
- */
222
- private callbackFunction(listener: string, sessionId: string) {
223
- const funcs = {
224
- 'close': (e: object) => {
225
- this.debug(`close even triggered ${this.id}: ${e}`);
226
- this.reportError(sessionId, `Error: session thread closed unexpectedly: ${e}`);
227
- },
228
- 'message': (data: MessageFromPlatform) => {
229
- if (data[0] === 'updateActor') {
230
- // We need to update the key to the store in order to find it in the future.
231
- this.updateIdentifier(data[2]);
232
- } else if (data[0] === 'error') {
233
- this.reportError(sessionId, data[1]);
134
+ /**
135
+ * When jobs are completed or failed, we prepare the results and send them to the client socket
136
+ */
137
+ public initQueue(secret: string) {
138
+ this.queue = new this.JobQueue(
139
+ this.parentId,
140
+ this.id,
141
+ secret,
142
+ config.get("redis"),
143
+ );
144
+
145
+ this.queue.on(
146
+ "completed",
147
+ async (
148
+ job: JobDataDecrypted,
149
+ result: ActivityStream | undefined,
150
+ ) => {
151
+ await this.handleJobResult("completed", job, result);
152
+ },
153
+ );
154
+
155
+ this.queue.on(
156
+ "failed",
157
+ async (
158
+ job: JobDataDecrypted,
159
+ result: ActivityStream | undefined,
160
+ ) => {
161
+ await this.handleJobResult("failed", job, result);
162
+ },
163
+ );
164
+ }
165
+
166
+ /**
167
+ * Register listener to be called when the process emits a message.
168
+ * @param sessionId ID of socket connection that will receive messages from platform emits
169
+ */
170
+ public registerSession(sessionId: string) {
171
+ if (!this.sessions.has(sessionId)) {
172
+ this.sessions.add(sessionId);
173
+ for (const type of Object.keys(this.sessionCallbacks)) {
174
+ const cb = this.callbackFunction(type, sessionId);
175
+ this.process.on(type, cb);
176
+ this.sessionCallbacks[type].set(sessionId, cb);
177
+ }
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Sends a message to client (user), can be registered with an event emitted from the platform
183
+ * process.
184
+ * @param sessionId ID of the socket connection to send the message to
185
+ * @param msg ActivityStream object to send to client
186
+ */
187
+ public async sendToClient(sessionId: string, msg: InternalActivityStream) {
188
+ return this.getSocket(sessionId).then(
189
+ (socket: Socket) => {
190
+ try {
191
+ // this property should never be exposed externally
192
+ // biome-ignore lint/performance/noDelete: <explanation>
193
+ delete msg.sessionSecret;
194
+ } finally {
195
+ msg.context = this.name;
196
+ if (
197
+ msg.type === "error" &&
198
+ typeof msg.actor === "undefined" &&
199
+ this.actor
200
+ ) {
201
+ // ensure an actor is present if not otherwise defined
202
+ msg.actor = { id: this.actor, type: "unknown" };
203
+ }
204
+ socket.emit("message", msg as ActivityStream);
205
+ }
206
+ },
207
+ (err) => this.debug(`sendToClient ${err}`),
208
+ );
209
+ }
210
+
211
+ // send message to every connected socket associated with this platform instance.
212
+ private broadcastToSharedPeers(sessionId: string, msg: ActivityStream) {
213
+ for (const sid of this.sessions.values()) {
214
+ if (sid !== sessionId) {
215
+ this.debug(`broadcasting message to ${sid}`);
216
+ this.sendToClient(sid, msg);
217
+ }
218
+ }
219
+ }
220
+
221
+ // handle job results coming in on the queue from platform instances
222
+ private async handleJobResult(
223
+ state: string,
224
+ job: JobDataDecrypted,
225
+ result: ActivityStream | undefined,
226
+ ) {
227
+ let payload = result; // some platforms return new AS objects as result
228
+ if (state === "failed") {
229
+ payload = job.msg; // failures always use original AS job object
230
+ payload.error = result
231
+ ? result.toString()
232
+ : "job failed for unknown reason";
233
+ }
234
+ this.debug(
235
+ `${job.title} ${state}${payload?.error ? `: ${payload.error}` : ""}`,
236
+ );
237
+
238
+ if (!payload || typeof payload === "string") {
239
+ payload = job.msg;
240
+ }
241
+
242
+ // send result to client
243
+ const callback = this.completedJobHandlers.get(job.title);
244
+ if (callback) {
245
+ callback(payload);
246
+ this.completedJobHandlers.delete(job.title);
234
247
  } else {
235
- // treat like a message to clients
236
- this.sendToClient(sessionId, data[1]);
248
+ await this.sendToClient(job.sessionId, payload);
237
249
  }
238
- }
239
- };
240
- return funcs[listener];
241
- }
250
+
251
+ if (payload) {
252
+ // let all related peers know of result as an independent message
253
+ // (not as part of a job completion, or failure)
254
+ this.broadcastToSharedPeers(job.sessionId, payload);
255
+ }
256
+
257
+ // persistent
258
+ if (
259
+ this.config.persist &&
260
+ this.config.requireCredentials?.includes(job.msg.type)
261
+ ) {
262
+ if (state === "failed") {
263
+ // Only terminate if platform is not yet initialized
264
+ // If already initialized, credential failures are non-fatal (wrong session credentials)
265
+ if (!this.config.initialized) {
266
+ this.debug(
267
+ `critical job type ${job.msg.type} failed during initialization, flagging for termination`,
268
+ );
269
+ await this.queue.pause();
270
+ this.config.initialized = false;
271
+ this.flaggedForTermination = true;
272
+ } else {
273
+ this.debug(
274
+ `credential job ${job.msg.type} failed on initialized platform, not flagged for termination`,
275
+ );
276
+ // Platform stays alive - error sent to client via sendToClient above
277
+ }
278
+ } else {
279
+ this.debug("persistent platform initialized");
280
+ await this.queue.resume();
281
+ this.config.initialized = true;
282
+ this.flaggedForTermination = false;
283
+ }
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Sends error message to client and clears all references to this class.
289
+ * @param sessionId
290
+ * @param errorMessage
291
+ */
292
+ private async reportError(sessionId: string, errorMessage: string) {
293
+ const errorObject: ActivityStream = {
294
+ context: this.name,
295
+ type: "error",
296
+ actor: { id: this.actor, type: "unknown" },
297
+ error: errorMessage,
298
+ };
299
+
300
+ // Only attempt to send to client if we have a valid session
301
+ try {
302
+ if (sessionId && this.sessions.has(sessionId)) {
303
+ await this.sendToClient(sessionId, errorObject);
304
+ }
305
+ } catch (err) {
306
+ this.debug(`Failed to send error to client: ${err.message}`);
307
+ }
308
+
309
+ this.sessions.clear();
310
+ await this.shutdown();
311
+ }
312
+
313
+ /**
314
+ * Updates the instance with a new identifier, updating the platformInstances mapping as well.
315
+ * @param identifier
316
+ */
317
+ private updateIdentifier(identifier: string) {
318
+ platformInstances.delete(this.id);
319
+ this.id = identifier;
320
+ platformInstances.set(this.id, this);
321
+ }
322
+
323
+ /**
324
+ * Generates a function tied to a given client session (socket connection), the generated
325
+ * function will be called for each session ID registered, for every platform emit.
326
+ * @param listener
327
+ * @param sessionId
328
+ */
329
+ private callbackFunction(listener: string, sessionId: string) {
330
+ const funcs = {
331
+ close: async (e: object) => {
332
+ this.debug(`close event triggered ${this.id}: ${e}`);
333
+ // Check if process is still connected before attempting error reporting
334
+ if (this.process?.connected && !this.flaggedForTermination) {
335
+ await this.reportError(
336
+ sessionId,
337
+ `Error: session thread closed unexpectedly: ${e}`,
338
+ );
339
+ } else {
340
+ this.debug(
341
+ "Process already disconnected or flagged for termination, skipping error report",
342
+ );
343
+ await this.shutdown();
344
+ }
345
+ },
346
+ message: async ([first, second, third]: MessageFromPlatform) => {
347
+ if (first === "updateActor") {
348
+ // We need to update the key to the store in order to find it in the future.
349
+ this.updateIdentifier(third);
350
+ } else if (first === "error" && typeof second === "string") {
351
+ await this.reportError(sessionId, second);
352
+ } else {
353
+ // treat like a message to clients
354
+ await this.sendToClient(sessionId, second);
355
+ }
356
+ },
357
+ };
358
+ return funcs[listener];
359
+ }
242
360
  }