@sockethub/platform-xmpp 5.0.0-alpha.11 → 5.0.0-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.test.js DELETED
@@ -1,631 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "bun:test";
2
- import sinon from "sinon";
3
-
4
- import XMPP from "./index.js";
5
-
6
- const actor = {
7
- type: "person",
8
- id: "testingham@jabber.net",
9
- name: "testing ham",
10
- };
11
-
12
- const credentials = {
13
- actor: actor,
14
- object: {
15
- type: "credentials",
16
- userAddress: "testingham@jabber.net",
17
- password: "foobar",
18
- resource: "home",
19
- },
20
- };
21
-
22
- const target = {
23
- mrfoobar: {
24
- type: "person",
25
- id: "mrfoobar@jabber.net",
26
- name: "Mr FooBar",
27
- },
28
- partyroom: {
29
- type: "room",
30
- id: "partyroom@jabber.net",
31
- },
32
- roomuser: {
33
- type: "room",
34
- id: "partyroom@jabber.net/ms tut",
35
- },
36
- };
37
-
38
- const job = {
39
- connect: {
40
- context: "xmpp",
41
- type: "connect",
42
- actor: {
43
- id: "slvrbckt@jabber.net/Home",
44
- type: "person",
45
- name: "Nick Jennings",
46
- userName: "slvrbckt",
47
- },
48
- },
49
- join: {
50
- actor: actor,
51
- object: {
52
- type: "update",
53
- name: "Frank",
54
- },
55
- target: target.partyroom,
56
- },
57
- leave: {
58
- actor: actor,
59
- target: target.partyroom,
60
- },
61
- send: {
62
- chat: {
63
- actor: actor,
64
- object: {
65
- type: "message",
66
- id: "hc-1234abcd",
67
- content: "hello",
68
- },
69
- target: target.mrfoobar,
70
- },
71
- groupchat: {
72
- actor: actor,
73
- object: {
74
- type: "message",
75
- id: "hc-1234abcd",
76
- content: "hi all",
77
- },
78
- target: target.roomuser,
79
- },
80
- correction: {
81
- actor: actor,
82
- object: {
83
- type: "message",
84
- id: "hc-1234abcd",
85
- content: "hi yall",
86
- "xmpp:replace": { id: "hc-234bcde" },
87
- },
88
- target: target.roomuser,
89
- },
90
- },
91
- update: {
92
- presenceOnline: {
93
- actor: actor,
94
- object: {
95
- type: "presence",
96
- presence: "online",
97
- content: "ready to chat",
98
- },
99
- },
100
- presenceUnavailable: {
101
- actor: actor,
102
- object: {
103
- type: "presence",
104
- presence: "away",
105
- content: "eating popcorn",
106
- },
107
- },
108
- presenceOffline: {
109
- actor: actor,
110
- object: {
111
- type: "presence",
112
- presence: "offline",
113
- content: "",
114
- },
115
- },
116
- },
117
- "request-friend": {
118
- actor: actor,
119
- target: target.mrfoobar,
120
- },
121
- "remove-friend": {
122
- actor: actor,
123
- target: target.mrfoobar,
124
- },
125
- "make-friend": {
126
- actor: actor,
127
- target: target.mrfoobar,
128
- },
129
- query: {
130
- actor: actor,
131
- target: target.partyroom,
132
- object: {
133
- type: "attendance",
134
- },
135
- },
136
- };
137
-
138
- describe("XMPP", () => {
139
- let clientFake, xmlFake, clientObjectFake, xp;
140
-
141
- beforeEach(() => {
142
- clientObjectFake = {
143
- on: sinon.fake(),
144
- start: sinon.fake.resolves(),
145
- send: sinon.fake.resolves(),
146
- join: sinon.fake.resolves(),
147
- stop: sinon.fake.resolves()
148
- };
149
- clientFake = sinon.fake.returns(clientObjectFake);
150
-
151
- // Mock XML object with chainable .c() method for building stanzas
152
- const mockXmlElement = {
153
- name: "presence",
154
- attrs: {},
155
- children: [],
156
- c: sinon.fake.returns({
157
- name: "x",
158
- attrs: { xmlns: "http://jabber.org/protocol/muc" },
159
- parent: null
160
- }),
161
- getChild: sinon.fake((name, xmlns) => {
162
- if (name === "x" && xmlns === "http://jabber.org/protocol/muc") {
163
- return { attrs: { xmlns: "http://jabber.org/protocol/muc" } };
164
- }
165
- return null;
166
- })
167
- };
168
-
169
- // Create a smart fake that returns complex object for presence, simple for others
170
- xmlFake = sinon.fake((elementName) => {
171
- if (elementName === "presence") {
172
- return mockXmlElement;
173
- }
174
- return undefined; // Default return for other elements
175
- });
176
-
177
- class TestXMPP extends XMPP {
178
- createClient() {
179
- this.__clientConstructor = clientFake;
180
- }
181
- createXml() {
182
- this.__xml = xmlFake;
183
- }
184
- }
185
-
186
- xp = new TestXMPP({
187
- id: actor,
188
- log: {
189
- error: sinon.fake(),
190
- warn: sinon.fake(),
191
- info: sinon.fake(),
192
- debug: sinon.fake(),
193
- },
194
- sendToClient: sinon.fake(),
195
- });
196
- });
197
-
198
- afterEach(() => {
199
- sinon.restore();
200
- });
201
-
202
- describe("Successful initialization", () => {
203
- it("works as intended", (done) => {
204
- xp.connect(job.connect, credentials, () => {
205
- sinon.assert.calledOnce(clientFake);
206
- expect(xp.__client.on).toBeDefined();
207
- expect(xp.__client.start).toBeDefined();
208
- expect(xp.__client.send).toBeDefined();
209
- expect(xp.__client.send.callCount).toEqual(0);
210
- sinon.assert.calledOnce(clientObjectFake.start);
211
- sinon.assert.notCalled(xp.sendToClient);
212
- done();
213
- });
214
- });
215
- });
216
-
217
- describe("Bad initialization", () => {
218
- it("returns the existing __client object", (done) => {
219
- const dummyClient = {
220
- foo: "bar",
221
- socket: {
222
- writable: true
223
- },
224
- status: "online"
225
- };
226
- xp.__client = dummyClient
227
- xp.connect(job.connect, credentials, (d) => {
228
- console.log('result: ', d);
229
- expect(xp.__client).toEqual(dummyClient);
230
- sinon.assert.notCalled(clientFake);
231
- sinon.assert.notCalled(xp.sendToClient);
232
- // sinon.assert.calledOnce(clientFake);
233
- // sinon.assert.calledOnce(xp.sendToClient);
234
- done();
235
- });
236
- });
237
-
238
- it("deletes the __client property on failed connect", (done) => {
239
- clientObjectFake.start = sinon.fake.rejects("foo");
240
- xp.connect(job.connect, credentials, () => {
241
- expect(xp.__client).toBeUndefined();
242
- sinon.assert.notCalled(xp.sendToClient);
243
- done();
244
- });
245
- });
246
- });
247
-
248
- describe("Error handling", () => {
249
- let processExitStub;
250
-
251
- beforeEach(() => {
252
- // Stub process.exit to prevent test process from exiting
253
- processExitStub = sinon.stub(process, "exit");
254
- });
255
-
256
- afterEach(() => {
257
- processExitStub.restore();
258
- });
259
-
260
- it("terminates process on TypeError (internal code error)", (done) => {
261
- xp.connect(job.connect, credentials, () => {
262
- // Get the error event handler that was registered
263
- const errorHandler = clientObjectFake.on.getCalls().find(
264
- (call) => call.args[0] === "error",
265
- ).args[1];
266
-
267
- // Simulate a TypeError (internal code error)
268
- const typeError = new TypeError("this.session.debug is not a function");
269
- errorHandler(typeError);
270
-
271
- // Verify process.exit(1) was called
272
- sinon.assert.calledOnce(processExitStub);
273
- sinon.assert.calledWith(processExitStub, 1);
274
-
275
- // Verify error was logged
276
- sinon.assert.called(xp.log.error);
277
- expect(
278
- xp.log.error
279
- .getCalls()
280
- .some((call) =>
281
- call.args[0].includes("FATAL: Internal code error"),
282
- ),
283
- ).toBeTrue();
284
-
285
- done();
286
- });
287
- });
288
-
289
- it("terminates process on ReferenceError (internal code error)", (done) => {
290
- xp.connect(job.connect, credentials, () => {
291
- const errorHandler = clientObjectFake.on.getCalls().find(
292
- (call) => call.args[0] === "error",
293
- ).args[1];
294
-
295
- const refError = new ReferenceError("foo is not defined");
296
- errorHandler(refError);
297
-
298
- sinon.assert.calledOnce(processExitStub);
299
- sinon.assert.calledWith(processExitStub, 1);
300
- sinon.assert.called(xp.log.error);
301
-
302
- done();
303
- });
304
- });
305
-
306
- it("does NOT terminate process on network errors (recoverable)", (done) => {
307
- xp.connect(job.connect, credentials, () => {
308
- const errorHandler = clientObjectFake.on.getCalls().find(
309
- (call) => call.args[0] === "error",
310
- ).args[1];
311
-
312
- // Simulate a network error
313
- const networkError = new Error("ECONNRESET");
314
- networkError.code = "ECONNRESET";
315
- errorHandler(networkError);
316
-
317
- // Should NOT call process.exit for network errors
318
- sinon.assert.notCalled(processExitStub);
319
-
320
- // Should send error to client instead
321
- sinon.assert.called(xp.sendToClient);
322
-
323
- done();
324
- });
325
- });
326
-
327
- it("does NOT terminate process on XMPP protocol errors (non-recoverable)", (done) => {
328
- xp.connect(job.connect, credentials, () => {
329
- const errorHandler = clientObjectFake.on.getCalls().find(
330
- (call) => call.args[0] === "error",
331
- ).args[1];
332
-
333
- // Simulate an XMPP protocol error (e.g., auth failure)
334
- const authError = new Error("not-authorized");
335
- authError.condition = "not-authorized";
336
- errorHandler(authError);
337
-
338
- // Should NOT call process.exit for XMPP errors
339
- sinon.assert.notCalled(processExitStub);
340
-
341
- // Should send error to client and mark disconnected
342
- sinon.assert.called(xp.sendToClient);
343
-
344
- done();
345
- });
346
- });
347
- });
348
-
349
- describe("Platform functionality", () => {
350
- beforeEach((done) => {
351
- xp.connect(job.join, credentials, () => done());
352
- });
353
-
354
- describe("#join", () => {
355
- it("calls xmpp.js correctly", (done) => {
356
- expect(xp.__client.send).toBeInstanceOf(Function);
357
- xp.join(job.join, () => {
358
- sinon.assert.calledOnce(xp.__client.send);
359
-
360
- // Verify MUC <x> element was created with correct namespace
361
- sinon.assert.calledWith(xmlFake, "x", { xmlns: "http://jabber.org/protocol/muc" });
362
-
363
- // Verify presence stanza was created with correct attributes
364
- sinon.assert.calledWith(xmlFake, "presence", {
365
- from: "testingham@jabber.net",
366
- to: "partyroom@jabber.net/testing ham",
367
- });
368
-
369
- done();
370
- });
371
- });
372
- });
373
-
374
- describe("#leave", () => {
375
- it("calls xmpp.js correctly", (done) => {
376
- expect(xp.__client).toBeDefined();
377
- expect(xp.__client.send).toBeInstanceOf(Function);
378
- xp.leave(job.leave, () => {
379
- sinon.assert.calledOnce(xp.__client.send);
380
- sinon.assert.calledWith(xmlFake, "presence", {
381
- from: "testingham@jabber.net",
382
- to: "partyroom@jabber.net/testing ham",
383
- type: "unavailable",
384
- });
385
- done();
386
- });
387
- });
388
- });
389
-
390
- describe("#send", () => {
391
- it("calls xmpp.js correctly", (done) => {
392
- expect(xp.__client).toBeDefined();
393
- expect(xp.__client.send).toBeInstanceOf(Function);
394
- xp.send(job.send.chat, () => {
395
- sinon.assert.calledOnce(xp.__client.send);
396
- expect(xmlFake.getCall(0).args).toEqual([
397
- "body",
398
- {},
399
- job.send.chat.object.content,
400
- ]);
401
- expect(xmlFake.getCall(1).args).toEqual([
402
- "message",
403
- {
404
- type: "chat",
405
- to: job.send.chat.target.id,
406
- id: job.send.chat.object.id,
407
- },
408
- undefined,
409
- undefined,
410
- ]);
411
- done();
412
- });
413
- });
414
-
415
- it("calls xmpp.js correctly for a groupchat", (done) => {
416
- xp.send(job.send.groupchat, () => {
417
- sinon.assert.calledOnce(xp.__client.send);
418
- expect(xmlFake.getCall(0).args).toEqual([
419
- "body",
420
- {},
421
- job.send.groupchat.object.content,
422
- ]);
423
- expect(xmlFake.getCall(1).args).toEqual([
424
- "message",
425
- {
426
- type: "groupchat",
427
- to: job.send.groupchat.target.id,
428
- id: job.send.groupchat.object.id,
429
- },
430
- undefined,
431
- undefined,
432
- ]);
433
- done();
434
- });
435
- });
436
-
437
- it("calls xmpp.js correctly for a message correction", (done) => {
438
- xp.send(job.send.correction, () => {
439
- sinon.assert.calledOnce(xp.__client.send);
440
- expect(xmlFake.getCall(0).args).toEqual([
441
- "body",
442
- {},
443
- job.send.correction.object.content,
444
- ]);
445
- expect(xmlFake.getCall(1).args).toEqual([
446
- "replace",
447
- {
448
- id: job.send.correction.object["xmpp:replace"].id,
449
- xmlns: "urn:xmpp:message-correct:0",
450
- },
451
- ]);
452
- expect(xmlFake.getCall(2).args).toEqual([
453
- "message",
454
- {
455
- type: "groupchat",
456
- to: job.send.correction.target.id,
457
- id: job.send.correction.object.id,
458
- },
459
- undefined,
460
- undefined,
461
- ]);
462
- done();
463
- });
464
- });
465
- });
466
-
467
- describe("#update", () => {
468
- it("calls xml() correctly for available", (done) => {
469
- xp.update(job.update.presenceOnline, () => {
470
- sinon.assert.calledOnce(xp.__client.send);
471
- expect(xmlFake.getCall(0).args).toEqual([
472
- "presence",
473
- {},
474
- {},
475
- { status: "ready to chat" },
476
- ]);
477
- done();
478
- });
479
- });
480
- it("calls xml() correctly for unavailable", (done) => {
481
- xp.update(job.update.presenceUnavailable, () => {
482
- sinon.assert.calledOnce(xp.__client.send);
483
- expect(xmlFake.getCall(0).args).toEqual([
484
- "presence",
485
- {},
486
- { show: "away" },
487
- { status: "eating popcorn" },
488
- ]);
489
- done();
490
- });
491
- });
492
- it("calls xml() correctly for offline", (done) => {
493
- xp.update(job.update.presenceOffline, () => {
494
- sinon.assert.calledOnce(xp.__client.send);
495
- expect(xmlFake.getCall(0).args).toEqual([
496
- "presence",
497
- { type: "unavailable" },
498
- {},
499
- {},
500
- ]);
501
- done();
502
- });
503
- });
504
- });
505
-
506
- describe("#request-friend", () => {
507
- it("calls xmpp.js correctly", (done) => {
508
- xp["request-friend"](job["request-friend"], () => {
509
- sinon.assert.calledOnce(xp.__client.send);
510
- expect(xmlFake.getCall(0).args).toEqual([
511
- "presence",
512
- {
513
- type: "subscribe",
514
- to: job["request-friend"].target["id"],
515
- },
516
- ]);
517
- done();
518
- });
519
- });
520
- });
521
-
522
- describe("#remove-friend", () => {
523
- it("calls xmpp.js correctly", (done) => {
524
- xp["remove-friend"](job["remove-friend"], () => {
525
- sinon.assert.calledOnce(xp.__client.send);
526
- expect(xmlFake.getCall(0).args).toEqual([
527
- "presence",
528
- {
529
- type: "unsubscribe",
530
- to: job["remove-friend"].target["id"],
531
- },
532
- ]);
533
- done();
534
- });
535
- });
536
- });
537
-
538
- describe("#make-friend", () => {
539
- it("calls xmpp.js correctly", (done) => {
540
- xp["remove-friend"](job["remove-friend"], () => {
541
- sinon.assert.calledOnce(xp.__client.send);
542
- expect(xmlFake.getCall(0).args).toEqual([
543
- "presence",
544
- {
545
- type: "unsubscribe",
546
- to: job["make-friend"].target["id"],
547
- },
548
- ]);
549
- done();
550
- });
551
- });
552
- });
553
-
554
- describe("#query", () => {
555
- it("calls xmpp.js correctly", (done) => {
556
- xp.query(job.query, () => {
557
- sinon.assert.calledOnce(xp.__client.send);
558
- expect(xmlFake.getCall(0).args).toEqual([
559
- "query",
560
- { xmlns: "http://jabber.org/protocol/disco#items" },
561
- ]);
562
- expect(xmlFake.getCall(1).args).toEqual([
563
- "iq",
564
- {
565
- id: "muc_id",
566
- type: "get",
567
- from: "testingham@jabber.net",
568
- to: "partyroom@jabber.net",
569
- },
570
- undefined,
571
- ]);
572
- done();
573
- });
574
- });
575
- });
576
-
577
- describe("#disconnect", () => {
578
- it("calls cleanup", (done) => {
579
- let cleanupCalled = false;
580
- xp.cleanup = (done) => {
581
- cleanupCalled = true;
582
- done();
583
- }
584
- xp.disconnect(job, () => {
585
- expect(cleanupCalled).toEqual(true);
586
- done()
587
- });
588
- })
589
- });
590
-
591
- describe("#cleanup", () => {
592
- it("calls client.stop", (done) => {
593
- expect(xp.isInitialized()).toEqual(true);
594
- xp.cleanup(() => {
595
- expect(xp.isInitialized()).toEqual(false);
596
- sinon.assert.calledOnce(xp.__client.stop);
597
- done()
598
- });
599
- })
600
- });
601
-
602
- describe("#join", () => {
603
- it("creates correct MUC presence stanza with namespace", (done) => {
604
- const joinJob = {
605
- actor: {
606
- id: "testingham@jabber.net",
607
- name: "Testing Ham"
608
- },
609
- target: {
610
- id: "testroom@conference.jabber.net"
611
- }
612
- };
613
-
614
- xp.join(joinJob, () => {
615
- sinon.assert.calledOnce(xp.__client.send);
616
-
617
- // Verify MUC <x> element was created with correct namespace
618
- sinon.assert.calledWith(xmlFake, "x", { xmlns: "http://jabber.org/protocol/muc" });
619
-
620
- // Verify presence stanza was created with correct attributes
621
- sinon.assert.calledWith(xmlFake, "presence", {
622
- from: "testingham@jabber.net",
623
- to: "testroom@conference.jabber.net/Testing Ham",
624
- });
625
-
626
- done();
627
- });
628
- });
629
- });
630
- });
631
- });
package/src/schema.js DELETED
@@ -1,61 +0,0 @@
1
- import PackageJSON from "../package.json" with { type: "json" };
2
-
3
- export const PlatformSchema = {
4
- name: "xmpp",
5
- version: PackageJSON.version,
6
- messages: {
7
- required: ["type"],
8
- properties: {
9
- type: {
10
- enum: [
11
- "connect",
12
- "join",
13
- "leave",
14
- "send",
15
- "update",
16
- "request-friend",
17
- "remove-friend",
18
- "make-friend",
19
- "query",
20
- "disconnect",
21
- ],
22
- },
23
- },
24
- },
25
- credentials: {
26
- required: ["object"],
27
- properties: {
28
- // TODO platforms shouldn't have to define the actor property if
29
- // they don't want to, just credential specifics
30
- actor: {
31
- type: "object",
32
- required: ["id"],
33
- },
34
- object: {
35
- type: "object",
36
- required: ["type", "userAddress", "password", "resource"],
37
- additionalProperties: false,
38
- properties: {
39
- type: {
40
- type: "string",
41
- },
42
- userAddress: {
43
- type: "string",
44
- },
45
- password: {
46
- type: "string",
47
- },
48
- server: {
49
- type: "string",
50
- },
51
- port: {
52
- type: "number",
53
- },
54
- resource: {
55
- type: "string",
56
- },
57
- },
58
- },
59
- },
60
- },
61
- };