@sockethub/server 5.0.0-alpha.10

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 (46) hide show
  1. package/LICENSE +165 -0
  2. package/README.md +130 -0
  3. package/bin/sockethub +4 -0
  4. package/dist/defaults.json +36 -0
  5. package/dist/index.js +166465 -0
  6. package/dist/index.js.map +1877 -0
  7. package/dist/platform.js +103625 -0
  8. package/dist/platform.js.map +1435 -0
  9. package/package.json +100 -0
  10. package/res/socket.io.js +4908 -0
  11. package/res/sockethub-client.js +631 -0
  12. package/res/sockethub-client.min.js +19 -0
  13. package/src/bootstrap/init.d.ts +21 -0
  14. package/src/bootstrap/init.test.ts +211 -0
  15. package/src/bootstrap/init.ts +160 -0
  16. package/src/bootstrap/load-platforms.ts +151 -0
  17. package/src/config.test.ts +33 -0
  18. package/src/config.ts +98 -0
  19. package/src/defaults.json +36 -0
  20. package/src/index.ts +68 -0
  21. package/src/janitor.test.ts +211 -0
  22. package/src/janitor.ts +157 -0
  23. package/src/listener.ts +173 -0
  24. package/src/middleware/create-activity-object.test.ts +30 -0
  25. package/src/middleware/create-activity-object.ts +22 -0
  26. package/src/middleware/expand-activity-stream.test.data.ts +351 -0
  27. package/src/middleware/expand-activity-stream.test.ts +77 -0
  28. package/src/middleware/expand-activity-stream.ts +37 -0
  29. package/src/middleware/store-credentials.test.ts +85 -0
  30. package/src/middleware/store-credentials.ts +16 -0
  31. package/src/middleware/validate.test.data.ts +259 -0
  32. package/src/middleware/validate.test.ts +44 -0
  33. package/src/middleware/validate.ts +73 -0
  34. package/src/middleware.test.ts +184 -0
  35. package/src/middleware.ts +71 -0
  36. package/src/platform-instance.test.ts +531 -0
  37. package/src/platform-instance.ts +360 -0
  38. package/src/platform.test.ts +375 -0
  39. package/src/platform.ts +358 -0
  40. package/src/process-manager.ts +88 -0
  41. package/src/routes.test.ts +54 -0
  42. package/src/routes.ts +61 -0
  43. package/src/sentry.test.ts +106 -0
  44. package/src/sentry.ts +19 -0
  45. package/src/sockethub.ts +198 -0
  46. package/src/util.ts +5 -0
@@ -0,0 +1,375 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
2
+ import * as sinon from "sinon";
3
+ import { crypto } from "@sockethub/crypto";
4
+ import type {
5
+ ActivityStream,
6
+ CredentialsObject,
7
+ PlatformCallback,
8
+ PlatformInterface,
9
+ } from "@sockethub/schemas";
10
+ import type { JobDataDecrypted } from "@sockethub/data-layer";
11
+
12
+ /**
13
+ * Tests for platform.ts credential handling logic
14
+ *
15
+ * Since platform.ts runs as a separate process and uses process.argv,
16
+ * we test the core credential handling behavior by simulating the
17
+ * getJobHandler logic.
18
+ */
19
+ describe("platform.ts credential handling", () => {
20
+ let sandbox: sinon.SinonSandbox;
21
+ let mockPlatform: Partial<PlatformInterface>;
22
+ let mockCredentialStore: any;
23
+ let mockJob: JobDataDecrypted;
24
+ let validCredentials: CredentialsObject;
25
+
26
+ beforeEach(() => {
27
+ sandbox = sinon.createSandbox();
28
+
29
+ validCredentials = {
30
+ type: "credentials",
31
+ context: "xmpp",
32
+ actor: {
33
+ id: "testuser@localhost",
34
+ type: "person",
35
+ name: "Test User",
36
+ },
37
+ object: {
38
+ type: "credentials",
39
+ userAddress: "testuser@localhost",
40
+ password: "testpassword",
41
+ server: "xmpp://localhost:5222",
42
+ },
43
+ };
44
+
45
+ mockPlatform = {
46
+ config: {
47
+ persist: true,
48
+ initialized: false,
49
+ requireCredentials: ["connect"],
50
+ },
51
+ credentialsHash: undefined,
52
+ connect: sandbox.stub(),
53
+ };
54
+
55
+ mockCredentialStore = {
56
+ get: sandbox.stub().resolves(validCredentials),
57
+ };
58
+
59
+ mockJob = {
60
+ sessionId: "test-session-123",
61
+ title: "xmpp-job-1",
62
+ msg: {
63
+ type: "connect",
64
+ context: "xmpp",
65
+ actor: { id: "testuser@localhost", type: "person" },
66
+ sessionSecret: "secret123",
67
+ },
68
+ } as JobDataDecrypted;
69
+ });
70
+
71
+ afterEach(() => {
72
+ sandbox.restore();
73
+ });
74
+
75
+ describe("credentialsHash updates after successful platform calls", () => {
76
+ it("should set credentialsHash after successful connect on first call", async () => {
77
+ // Initially no hash
78
+ expect(mockPlatform.credentialsHash).toBeUndefined();
79
+
80
+ // Simulate the platform.ts credential handling flow
81
+ const credentials = await mockCredentialStore.get(
82
+ mockJob.msg.actor.id,
83
+ mockPlatform.credentialsHash,
84
+ );
85
+
86
+ // Create the wrapper callback that platform.ts uses
87
+ let capturedErr: Error | null = null;
88
+ let capturedResult: ActivityStream | null = null;
89
+ const doneCallback: PlatformCallback = (err, result) => {
90
+ capturedErr = err;
91
+ capturedResult = result;
92
+ };
93
+
94
+ const wrappedCallback: PlatformCallback = (err, result) => {
95
+ if (!err) {
96
+ // This is what platform.ts does
97
+ mockPlatform.credentialsHash = crypto.objectHash(
98
+ credentials.object,
99
+ );
100
+ }
101
+ doneCallback(err, result);
102
+ };
103
+
104
+ // Simulate platform connect call succeeding
105
+ (mockPlatform.connect as sinon.SinonStub).callsFake(
106
+ (_msg, _creds, callback) => {
107
+ callback(null, { type: "success" });
108
+ },
109
+ );
110
+
111
+ // Call platform method with wrapped callback
112
+ mockPlatform.connect(mockJob.msg, credentials, wrappedCallback);
113
+
114
+ // Verify credentialsHash was set
115
+ expect(mockPlatform.credentialsHash).toBeDefined();
116
+ expect(mockPlatform.credentialsHash).toBe(
117
+ crypto.objectHash(credentials.object),
118
+ );
119
+ expect(capturedErr).toBeNull();
120
+ expect(capturedResult).toEqual({ type: "success" });
121
+ });
122
+
123
+ it("should NOT update credentialsHash when platform call fails", async () => {
124
+ const initialHash = crypto.objectHash(validCredentials.object);
125
+ mockPlatform.credentialsHash = initialHash;
126
+
127
+ const credentials = await mockCredentialStore.get(
128
+ mockJob.msg.actor.id,
129
+ mockPlatform.credentialsHash,
130
+ );
131
+
132
+ let capturedErr: Error | null = null;
133
+ const doneCallback: PlatformCallback = (err, result) => {
134
+ capturedErr = err;
135
+ };
136
+
137
+ const wrappedCallback: PlatformCallback = (err, result) => {
138
+ if (!err) {
139
+ mockPlatform.credentialsHash = crypto.objectHash(
140
+ credentials.object,
141
+ );
142
+ }
143
+ doneCallback(err, result);
144
+ };
145
+
146
+ // Simulate platform connect call failing
147
+ (mockPlatform.connect as sinon.SinonStub).callsFake(
148
+ (_msg, _creds, callback) => {
149
+ callback(new Error("connection failed"), null);
150
+ },
151
+ );
152
+
153
+ mockPlatform.connect(mockJob.msg, credentials, wrappedCallback);
154
+
155
+ // Verify credentialsHash was NOT changed
156
+ expect(mockPlatform.credentialsHash).toBe(initialHash);
157
+ expect(capturedErr).toBeDefined();
158
+ expect(capturedErr?.message).toBe("connection failed");
159
+ });
160
+
161
+ it("should update credentialsHash on subsequent successful calls", async () => {
162
+ // First call sets hash
163
+ const firstHash = crypto.objectHash(validCredentials.object);
164
+ mockPlatform.credentialsHash = firstHash;
165
+
166
+ // Second call with same credentials
167
+ const credentials = await mockCredentialStore.get(
168
+ mockJob.msg.actor.id,
169
+ mockPlatform.credentialsHash,
170
+ );
171
+
172
+ const wrappedCallback: PlatformCallback = (err, result) => {
173
+ if (!err) {
174
+ mockPlatform.credentialsHash = crypto.objectHash(
175
+ credentials.object,
176
+ );
177
+ }
178
+ };
179
+
180
+ (mockPlatform.connect as sinon.SinonStub).callsFake(
181
+ (_msg, _creds, callback) => {
182
+ callback(null, { type: "success" });
183
+ },
184
+ );
185
+
186
+ mockPlatform.connect(mockJob.msg, credentials, wrappedCallback);
187
+
188
+ // Hash should still be the same (same credentials)
189
+ expect(mockPlatform.credentialsHash).toBe(firstHash);
190
+ });
191
+ });
192
+
193
+ describe("CredentialsStore.get() validation behavior", () => {
194
+ it("should pass credentialsHash to CredentialsStore.get() for validation", async () => {
195
+ const existingHash = crypto.objectHash(validCredentials.object);
196
+ mockPlatform.credentialsHash = existingHash;
197
+
198
+ await mockCredentialStore.get(
199
+ mockJob.msg.actor.id,
200
+ mockPlatform.credentialsHash,
201
+ );
202
+
203
+ sinon.assert.calledOnce(mockCredentialStore.get);
204
+ sinon.assert.calledWith(
205
+ mockCredentialStore.get,
206
+ mockJob.msg.actor.id,
207
+ existingHash,
208
+ );
209
+ });
210
+
211
+ it("should pass undefined when no credentialsHash exists", async () => {
212
+ mockPlatform.credentialsHash = undefined;
213
+
214
+ await mockCredentialStore.get(
215
+ mockJob.msg.actor.id,
216
+ mockPlatform.credentialsHash,
217
+ );
218
+
219
+ sinon.assert.calledWith(
220
+ mockCredentialStore.get,
221
+ mockJob.msg.actor.id,
222
+ undefined,
223
+ );
224
+ });
225
+
226
+ it("should handle CredentialsStore.get() rejection", async () => {
227
+ mockCredentialStore.get.rejects(
228
+ new Error("invalid credentials for testuser@localhost"),
229
+ );
230
+
231
+ try {
232
+ await mockCredentialStore.get(
233
+ mockJob.msg.actor.id,
234
+ mockPlatform.credentialsHash,
235
+ );
236
+ expect.unreachable("Should have thrown");
237
+ } catch (err) {
238
+ expect(err.message).toContain("invalid credentials");
239
+ }
240
+ });
241
+ });
242
+
243
+ describe("Wrapper callback behavior", () => {
244
+ it("should call doneCallback after updating credentialsHash", async () => {
245
+ const credentials = await mockCredentialStore.get(
246
+ mockJob.msg.actor.id,
247
+ mockPlatform.credentialsHash,
248
+ );
249
+
250
+ const doneCallbackSpy = sandbox.spy();
251
+ let hashSetBeforeDone = false;
252
+
253
+ const wrappedCallback: PlatformCallback = (err, result) => {
254
+ if (!err) {
255
+ mockPlatform.credentialsHash = crypto.objectHash(
256
+ credentials.object,
257
+ );
258
+ hashSetBeforeDone = mockPlatform.credentialsHash !==
259
+ undefined;
260
+ }
261
+ doneCallbackSpy(err, result);
262
+ };
263
+
264
+ (mockPlatform.connect as sinon.SinonStub).callsFake(
265
+ (_msg, _creds, callback) => {
266
+ callback(null, { type: "success" });
267
+ },
268
+ );
269
+
270
+ mockPlatform.connect(mockJob.msg, credentials, wrappedCallback);
271
+
272
+ // Verify order: hash set, then done called
273
+ expect(hashSetBeforeDone).toBeTrue();
274
+ sinon.assert.calledOnce(doneCallbackSpy);
275
+ sinon.assert.calledWith(doneCallbackSpy, null, { type: "success" });
276
+ });
277
+
278
+ it("should pass through errors without updating hash", async () => {
279
+ const initialHash = crypto.objectHash(validCredentials.object);
280
+ mockPlatform.credentialsHash = initialHash;
281
+
282
+ const credentials = await mockCredentialStore.get(
283
+ mockJob.msg.actor.id,
284
+ mockPlatform.credentialsHash,
285
+ );
286
+
287
+ const doneCallbackSpy = sandbox.spy();
288
+ const testError = new Error("platform error");
289
+
290
+ const wrappedCallback: PlatformCallback = (err, result) => {
291
+ if (!err) {
292
+ mockPlatform.credentialsHash = crypto.objectHash(
293
+ credentials.object,
294
+ );
295
+ }
296
+ doneCallbackSpy(err, result);
297
+ };
298
+
299
+ (mockPlatform.connect as sinon.SinonStub).callsFake(
300
+ (_msg, _creds, callback) => {
301
+ callback(testError, null);
302
+ },
303
+ );
304
+
305
+ mockPlatform.connect(mockJob.msg, credentials, wrappedCallback);
306
+
307
+ // Hash unchanged
308
+ expect(mockPlatform.credentialsHash).toBe(initialHash);
309
+ // Error passed through
310
+ sinon.assert.calledOnce(doneCallbackSpy);
311
+ sinon.assert.calledWith(doneCallbackSpy, testError, null);
312
+ });
313
+ });
314
+
315
+ describe("Integration scenarios", () => {
316
+ it("should handle complete flow: fetch credentials -> call platform -> update hash -> callback", async () => {
317
+ const flowLog: string[] = [];
318
+
319
+ // Step 1: Fetch credentials
320
+ flowLog.push("fetch-start");
321
+ const credentials = await mockCredentialStore.get(
322
+ mockJob.msg.actor.id,
323
+ mockPlatform.credentialsHash,
324
+ );
325
+ flowLog.push("fetch-success");
326
+
327
+ // Step 2: Create wrapped callback
328
+ const doneCallback: PlatformCallback = (err, result) => {
329
+ flowLog.push("done-callback");
330
+ };
331
+
332
+ const wrappedCallback: PlatformCallback = (err, result) => {
333
+ flowLog.push("wrapped-callback-start");
334
+ if (!err) {
335
+ mockPlatform.credentialsHash = crypto.objectHash(
336
+ credentials.object,
337
+ );
338
+ flowLog.push("hash-updated");
339
+ }
340
+ doneCallback(err, result);
341
+ flowLog.push("wrapped-callback-end");
342
+ };
343
+
344
+ // Step 3: Call platform method
345
+ (mockPlatform.connect as sinon.SinonStub).callsFake(
346
+ (_msg, _creds, callback) => {
347
+ flowLog.push("platform-method-executing");
348
+ callback(null, { type: "success" });
349
+ flowLog.push("platform-method-callback-done");
350
+ },
351
+ );
352
+
353
+ flowLog.push("call-platform-start");
354
+ mockPlatform.connect(mockJob.msg, credentials, wrappedCallback);
355
+ flowLog.push("call-platform-end");
356
+
357
+ // Verify flow order
358
+ expect(flowLog).toEqual([
359
+ "fetch-start",
360
+ "fetch-success",
361
+ "call-platform-start",
362
+ "platform-method-executing",
363
+ "wrapped-callback-start",
364
+ "hash-updated",
365
+ "done-callback",
366
+ "wrapped-callback-end",
367
+ "platform-method-callback-done",
368
+ "call-platform-end",
369
+ ]);
370
+
371
+ // Verify final state
372
+ expect(mockPlatform.credentialsHash).toBeDefined();
373
+ });
374
+ });
375
+ });