@sockethub/platform-xmpp 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.
- package/LICENSE +165 -0
- package/README.md +89 -0
- package/dist/index.js +13029 -0
- package/dist/index.js.map +131 -0
- package/package.json +65 -0
- package/src/incoming-handlers.js +348 -0
- package/src/incoming-handlers.test.data.js +260 -0
- package/src/incoming-handlers.test.js +92 -0
- package/src/index.js +726 -0
- package/src/index.test.js +525 -0
- package/src/schema.js +61 -0
- package/src/utils.js +21 -0
- package/src/utils.test.js +58 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is a platform for Sockethub implementing XMPP functionality.
|
|
3
|
+
*
|
|
4
|
+
* Developed by Nick Jennings (https://github.com/silverbucket)
|
|
5
|
+
*
|
|
6
|
+
* Sockethub is licensed under the LGPLv3.
|
|
7
|
+
* See the LICENSE file for details.
|
|
8
|
+
*
|
|
9
|
+
* The latest version of this module can be found here:
|
|
10
|
+
* git://github.com/sockethub/sockethub.git
|
|
11
|
+
*
|
|
12
|
+
* For more information about Sockethub visit http://sockethub.org/.
|
|
13
|
+
*
|
|
14
|
+
* This program is distributed in the hope that it will be useful,
|
|
15
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { client, xml } from "@xmpp/client";
|
|
20
|
+
|
|
21
|
+
import { IncomingHandlers } from "./incoming-handlers.js";
|
|
22
|
+
import { PlatformSchema } from "./schema.js";
|
|
23
|
+
import { utils } from "./utils.js";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Handles all actions related to communication via. the XMPP protocol.
|
|
27
|
+
*
|
|
28
|
+
* Uses `xmpp.js` as a base tool for interacting with XMPP.
|
|
29
|
+
*
|
|
30
|
+
* {@link https://github.com/xmppjs/xmpp.js}
|
|
31
|
+
*/
|
|
32
|
+
export default class XMPP {
|
|
33
|
+
/**
|
|
34
|
+
* Constructor called from the Sockethub `Platform` instance, passing in a
|
|
35
|
+
* session object.
|
|
36
|
+
* @param {object} session - {@link Sockethub.Platform.PlatformSession#object}
|
|
37
|
+
*/
|
|
38
|
+
constructor(session) {
|
|
39
|
+
this.id = session.id; // actor
|
|
40
|
+
this.config = {
|
|
41
|
+
connectTimeoutMs: 10000,
|
|
42
|
+
persist: true,
|
|
43
|
+
initialized: false,
|
|
44
|
+
requireCredentials: ["connect"],
|
|
45
|
+
};
|
|
46
|
+
this.debug = session.debug;
|
|
47
|
+
this.sendToClient = session.sendToClient;
|
|
48
|
+
this.createClient();
|
|
49
|
+
this.createXml();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
createClient() {
|
|
53
|
+
this.__clientConstructor = client;
|
|
54
|
+
}
|
|
55
|
+
createXml() {
|
|
56
|
+
this.__xml = xml;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Mark the platform as disconnected and uninitialized
|
|
61
|
+
* @param {boolean} stopReconnection - If true, stop automatic reconnection
|
|
62
|
+
*/
|
|
63
|
+
__markDisconnected(stopReconnection = false) {
|
|
64
|
+
this.debug(`marking client as disconnected for ${this.id}`);
|
|
65
|
+
|
|
66
|
+
if (stopReconnection && this.__client) {
|
|
67
|
+
this.debug(`stopping automatic reconnection for ${this.id}`);
|
|
68
|
+
this.__client.stop();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.__client = undefined;
|
|
72
|
+
this.config.initialized = false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Classify error to determine if reconnection should be attempted
|
|
77
|
+
* @param {Error} err - The error from XMPP client
|
|
78
|
+
* @returns {string} 'RECOVERABLE' or 'NON_RECOVERABLE'
|
|
79
|
+
*/
|
|
80
|
+
__classifyError(err) {
|
|
81
|
+
const errorString = err.toString();
|
|
82
|
+
const condition = err.condition;
|
|
83
|
+
|
|
84
|
+
// ONLY these errors are safe to reconnect on
|
|
85
|
+
const recoverableErrors = [
|
|
86
|
+
"ECONNRESET", // Network connection reset
|
|
87
|
+
"ECONNREFUSED", // Connection refused (server down)
|
|
88
|
+
"ETIMEDOUT", // Network timeout
|
|
89
|
+
"ENOTFOUND", // DNS resolution failed
|
|
90
|
+
"EHOSTUNREACH", // Host unreachable
|
|
91
|
+
"ENETUNREACH", // Network unreachable
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
// Check if this is explicitly a recoverable network error
|
|
95
|
+
if (
|
|
96
|
+
recoverableErrors.some((pattern) => errorString.includes(pattern))
|
|
97
|
+
) {
|
|
98
|
+
return "RECOVERABLE";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Also check for specific network-level error codes
|
|
102
|
+
if (err.code && recoverableErrors.includes(err.code)) {
|
|
103
|
+
return "RECOVERABLE";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// DEFAULT: Everything else is non-recoverable
|
|
107
|
+
// This includes:
|
|
108
|
+
// - StreamError: conflict
|
|
109
|
+
// - SASLError: not-authorized
|
|
110
|
+
// - StreamError: policy-violation
|
|
111
|
+
// - Any unknown XMPP protocol errors
|
|
112
|
+
// - Any authentication failures
|
|
113
|
+
// - Any server policy violations
|
|
114
|
+
// - Any new error types we haven't seen before
|
|
115
|
+
return "NON_RECOVERABLE";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Check if the XMPP client is properly connected and can send messages
|
|
120
|
+
* @returns {boolean} true if client is connected and operational
|
|
121
|
+
*/
|
|
122
|
+
__isClientConnected() {
|
|
123
|
+
if (!this.__client) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check if the client has a socket and it's writable
|
|
128
|
+
try {
|
|
129
|
+
return (
|
|
130
|
+
this.__client.socket &&
|
|
131
|
+
this.__client.socket.writable !== false &&
|
|
132
|
+
this.__client.status === "online"
|
|
133
|
+
);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
this.debug("Error checking client connection status:", err);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @description
|
|
142
|
+
* JSON schema defining the types this platform accepts.
|
|
143
|
+
*
|
|
144
|
+
* Actual handling of incoming 'set' commands are handled by dispatcher,
|
|
145
|
+
* but the dispatcher uses this defined schema to validate credentials
|
|
146
|
+
* received, so that when a @context type is called, it can fetch the
|
|
147
|
+
* credentials (`session.getConfig()`), knowing they will have already been
|
|
148
|
+
* validated against this schema.
|
|
149
|
+
*
|
|
150
|
+
*
|
|
151
|
+
* In the below example, Sockethub will validate the incoming credentials object
|
|
152
|
+
* against whatever is defined in the `credentials` portion of the schema
|
|
153
|
+
* object.
|
|
154
|
+
*
|
|
155
|
+
*
|
|
156
|
+
* It will also check if the incoming AS object uses a type which exists in the
|
|
157
|
+
* `types` portion of the schema object (should be an array of type names).
|
|
158
|
+
*
|
|
159
|
+
* **NOTE**: For more information on using the credentials object from a client,
|
|
160
|
+
* see [Sockethub Client](https://github.com/sockethub/sockethub/wiki/Sockethub-Client)
|
|
161
|
+
*
|
|
162
|
+
* Valid AS object for setting XMPP credentials:
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
*
|
|
166
|
+
* {
|
|
167
|
+
* type: 'credentials',
|
|
168
|
+
* context: 'xmpp',
|
|
169
|
+
* actor: {
|
|
170
|
+
* id: 'testuser@jabber.net',
|
|
171
|
+
* type: 'person',
|
|
172
|
+
* name: 'Mr. Test User'
|
|
173
|
+
* },
|
|
174
|
+
* object: {
|
|
175
|
+
* type: 'credentials',
|
|
176
|
+
* userAddress: 'testuser@jabber.net',
|
|
177
|
+
* password: 'asdasdasdasd',
|
|
178
|
+
* resource: 'phone'
|
|
179
|
+
* }
|
|
180
|
+
* }
|
|
181
|
+
**/
|
|
182
|
+
get schema() {
|
|
183
|
+
return PlatformSchema;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Connect to the XMPP server.
|
|
188
|
+
*
|
|
189
|
+
* @param {object} job activity streams object
|
|
190
|
+
* @param {object} credentials credentials object
|
|
191
|
+
* @param {object} done callback when job is done
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
*
|
|
195
|
+
* {
|
|
196
|
+
* context: 'xmpp',
|
|
197
|
+
* type: 'connect',
|
|
198
|
+
* actor: {
|
|
199
|
+
* id: 'slvrbckt@jabber.net/Home',
|
|
200
|
+
* type: 'person',
|
|
201
|
+
* name: 'Nick Jennings',
|
|
202
|
+
* userName: 'slvrbckt'
|
|
203
|
+
* }
|
|
204
|
+
* }
|
|
205
|
+
*/
|
|
206
|
+
connect(job, credentials, done) {
|
|
207
|
+
if (this.__isClientConnected()) {
|
|
208
|
+
this.debug(`client connection already exists for ${job.actor.id}`);
|
|
209
|
+
this.config.initialized = true;
|
|
210
|
+
return done();
|
|
211
|
+
}
|
|
212
|
+
this.debug(`connect() called for ${job.actor.id}`);
|
|
213
|
+
|
|
214
|
+
// Log credential processing
|
|
215
|
+
const xmppCreds = utils.buildXmppCredentials(credentials);
|
|
216
|
+
this.debug(
|
|
217
|
+
`building XMPP credentials for ${job.actor.id}:`,
|
|
218
|
+
JSON.stringify({
|
|
219
|
+
service: xmppCreds.service,
|
|
220
|
+
username: xmppCreds.username,
|
|
221
|
+
resource: xmppCreds.resource,
|
|
222
|
+
timeout: this.config.connectTimeoutMs,
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Log before client creation
|
|
227
|
+
this.debug(`creating XMPP client for ${job.actor.id}`);
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
this.__client = this.__clientConstructor({
|
|
231
|
+
...xmppCreds,
|
|
232
|
+
...{ timeout: this.config.connectTimeoutMs, tls: false },
|
|
233
|
+
});
|
|
234
|
+
this.debug(`XMPP client created successfully for ${job.actor.id}`);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
this.debug(`XMPP client creation failed for ${job.actor.id}:`, err);
|
|
237
|
+
return done(`client creation failed: ${err.message}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
this.__client.on("offline", () => {
|
|
241
|
+
this.debug(`offline event received for ${job.actor.id}`);
|
|
242
|
+
this.__markDisconnected();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
this.__client.on("error", (err) => {
|
|
246
|
+
this.debug(
|
|
247
|
+
`network error event for ${job.actor.id}:${err.toString()}`,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const errorType = this.__classifyError(err);
|
|
251
|
+
|
|
252
|
+
const as = {
|
|
253
|
+
context: "xmpp",
|
|
254
|
+
type: "connect",
|
|
255
|
+
actor: { id: job.actor.id },
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
if (errorType === "RECOVERABLE") {
|
|
259
|
+
// Clean up state but allow reconnection
|
|
260
|
+
this.__markDisconnected(false);
|
|
261
|
+
|
|
262
|
+
as.error = `Connection lost: ${err.toString()}. Attempting automatic reconnection...`;
|
|
263
|
+
as.object = {
|
|
264
|
+
type: "connect",
|
|
265
|
+
status: "reconnecting",
|
|
266
|
+
condition: err.condition || "network",
|
|
267
|
+
};
|
|
268
|
+
} else {
|
|
269
|
+
// Clean up state and stop reconnection
|
|
270
|
+
this.__markDisconnected(true);
|
|
271
|
+
|
|
272
|
+
as.error = `Connection failed: ${err.toString()}. Manual reconnection required.`;
|
|
273
|
+
as.object = {
|
|
274
|
+
type: "connect",
|
|
275
|
+
status: "failed",
|
|
276
|
+
condition: err.condition || "protocol",
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
this.sendToClient(as);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
this.__client.on("online", () => {
|
|
283
|
+
this.debug(`online event received for ${job.actor.id}`);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
this.debug(`starting XMPP client connection for ${job.actor.id}`);
|
|
287
|
+
const startTime = Date.now();
|
|
288
|
+
|
|
289
|
+
this.__client
|
|
290
|
+
.start()
|
|
291
|
+
.then(() => {
|
|
292
|
+
// connected
|
|
293
|
+
const duration = Date.now() - startTime;
|
|
294
|
+
this.debug(
|
|
295
|
+
`connection successful for ${job.actor.id} after ${duration}ms`,
|
|
296
|
+
);
|
|
297
|
+
this.config.initialized = true;
|
|
298
|
+
this.__registerHandlers();
|
|
299
|
+
return done();
|
|
300
|
+
})
|
|
301
|
+
.catch((err) => {
|
|
302
|
+
const duration = Date.now() - startTime;
|
|
303
|
+
this.debug(
|
|
304
|
+
`connection failed for ${job.actor.id} after ${duration}ms:`,
|
|
305
|
+
{
|
|
306
|
+
error: err,
|
|
307
|
+
message: err?.message,
|
|
308
|
+
code: err?.code,
|
|
309
|
+
stack: err?.stack,
|
|
310
|
+
},
|
|
311
|
+
);
|
|
312
|
+
this.__client = undefined;
|
|
313
|
+
return done(
|
|
314
|
+
`connection failed: ${err?.message || err}. (service: ${xmppCreds.service})`,
|
|
315
|
+
);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Join a room, optionally defining a display name for that room.
|
|
321
|
+
*
|
|
322
|
+
* @param {object} job activity streams object
|
|
323
|
+
* @param {object} done callback when job is done
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
*
|
|
327
|
+
* {
|
|
328
|
+
* context: 'xmpp',
|
|
329
|
+
* type: 'join',
|
|
330
|
+
* actor: {
|
|
331
|
+
* type: 'person',
|
|
332
|
+
* id: 'slvrbckt@jabber.net/Home',
|
|
333
|
+
* name: 'Mr. Pimp'
|
|
334
|
+
* },
|
|
335
|
+
* target: {
|
|
336
|
+
* type: 'room',
|
|
337
|
+
* id: 'PartyChatRoom@muc.jabber.net'
|
|
338
|
+
* }
|
|
339
|
+
* }
|
|
340
|
+
*/
|
|
341
|
+
async join(job, done) {
|
|
342
|
+
this.debug(
|
|
343
|
+
`sending join from ${job.actor.id} to ` +
|
|
344
|
+
`${job.target.id}/${job.actor.name}`,
|
|
345
|
+
);
|
|
346
|
+
// TODO optional passwords not handled for now
|
|
347
|
+
// TODO investigate implementation reserved nickname discovery
|
|
348
|
+
const id = job.target.id.split("/")[0];
|
|
349
|
+
|
|
350
|
+
const presence = this.__xml(
|
|
351
|
+
"presence",
|
|
352
|
+
{
|
|
353
|
+
from: job.actor.id,
|
|
354
|
+
to: `${job.target.id}/${job.actor.name || id}`,
|
|
355
|
+
},
|
|
356
|
+
this.__xml("x", { xmlns: "http://jabber.org/protocol/muc" }),
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
return this.__client.send(presence).then(done).catch(done);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Leave a room
|
|
364
|
+
*
|
|
365
|
+
* @param {object} job activity streams object
|
|
366
|
+
* @param {object} done callback when job is done
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
*
|
|
370
|
+
* {
|
|
371
|
+
* context: 'xmpp',
|
|
372
|
+
* type: 'leave',
|
|
373
|
+
* actor: {
|
|
374
|
+
* type: 'person',
|
|
375
|
+
* id: 'slvrbckt@jabber.net/Home',
|
|
376
|
+
* name: 'slvrbckt'
|
|
377
|
+
* },
|
|
378
|
+
* target: {
|
|
379
|
+
* type: 'room'
|
|
380
|
+
* id: 'PartyChatRoom@muc.jabber.net',
|
|
381
|
+
* }
|
|
382
|
+
* }
|
|
383
|
+
*/
|
|
384
|
+
leave(job, done) {
|
|
385
|
+
this.debug(
|
|
386
|
+
`sending leave from ${job.actor.id} to ` +
|
|
387
|
+
`${job.target.id}/${job.actor.name}`,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const id = job.target.id.split("/")[0];
|
|
391
|
+
|
|
392
|
+
this.__client
|
|
393
|
+
.send(
|
|
394
|
+
this.__xml("presence", {
|
|
395
|
+
from: job.actor.id,
|
|
396
|
+
to:
|
|
397
|
+
job.target?.id && job.actor?.name
|
|
398
|
+
? `${job.target.id}/${job.actor.name}`
|
|
399
|
+
: id,
|
|
400
|
+
type: "unavailable",
|
|
401
|
+
}),
|
|
402
|
+
)
|
|
403
|
+
.then(done);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Send a message to a room or private conversation.
|
|
408
|
+
*
|
|
409
|
+
* @param {object} job activity streams object
|
|
410
|
+
* @param {object} done callback when job is done
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
*
|
|
414
|
+
* {
|
|
415
|
+
* context: 'xmpp',
|
|
416
|
+
* type: 'send',
|
|
417
|
+
* actor: {
|
|
418
|
+
* id: 'slvrbckt@jabber.net/Home',
|
|
419
|
+
* type: 'person',
|
|
420
|
+
* name: 'Nick Jennings',
|
|
421
|
+
* userName: 'slvrbckt'
|
|
422
|
+
* },
|
|
423
|
+
* target: {
|
|
424
|
+
* id: 'homer@jabber.net/Home',
|
|
425
|
+
* type: 'user',
|
|
426
|
+
* name: 'Homer'
|
|
427
|
+
* },
|
|
428
|
+
* object: {
|
|
429
|
+
* type: 'message',
|
|
430
|
+
* content: 'Hello from Sockethub!'
|
|
431
|
+
* }
|
|
432
|
+
* }
|
|
433
|
+
*
|
|
434
|
+
* {
|
|
435
|
+
* context: 'xmpp',
|
|
436
|
+
* type: 'send',
|
|
437
|
+
* actor: {
|
|
438
|
+
* id: 'slvrbckt@jabber.net/Home',
|
|
439
|
+
* type: 'person',
|
|
440
|
+
* name: 'Nick Jennings',
|
|
441
|
+
* userName: 'slvrbckt'
|
|
442
|
+
* },
|
|
443
|
+
* target: {
|
|
444
|
+
* id: 'party-room@jabber.net',
|
|
445
|
+
* type: 'room'
|
|
446
|
+
* },
|
|
447
|
+
* object: {
|
|
448
|
+
* type: 'message',
|
|
449
|
+
* content: 'Hello from Sockethub!'
|
|
450
|
+
* }
|
|
451
|
+
* }
|
|
452
|
+
*
|
|
453
|
+
*/
|
|
454
|
+
send(job, done) {
|
|
455
|
+
this.debug(`send() called for ${job.actor.id}`);
|
|
456
|
+
// send message
|
|
457
|
+
const message = this.__xml(
|
|
458
|
+
"message",
|
|
459
|
+
{
|
|
460
|
+
type: job.target.type === "room" ? "groupchat" : "chat",
|
|
461
|
+
to: job.target.id,
|
|
462
|
+
id: job.object.id,
|
|
463
|
+
},
|
|
464
|
+
this.__xml("body", {}, job.object.content),
|
|
465
|
+
job.object["xmpp:replace"]
|
|
466
|
+
? this.__xml("replace", {
|
|
467
|
+
id: job.object["xmpp:replace"].id,
|
|
468
|
+
xmlns: "urn:xmpp:message-correct:0",
|
|
469
|
+
})
|
|
470
|
+
: undefined,
|
|
471
|
+
);
|
|
472
|
+
this.__client.send(message).then(done);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* @description
|
|
477
|
+
* Indicate presence and status message.
|
|
478
|
+
* Valid presence values are "away", "chat", "dnd", "xa", "offline", "online".
|
|
479
|
+
*
|
|
480
|
+
* @param {object} job activity streams object
|
|
481
|
+
* @param {object} done callback when job is done
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
*
|
|
485
|
+
* {
|
|
486
|
+
* context: 'xmpp',
|
|
487
|
+
* type: 'update',
|
|
488
|
+
* actor: {
|
|
489
|
+
* id: 'user@host.org/Home'
|
|
490
|
+
* },
|
|
491
|
+
* object: {
|
|
492
|
+
* type: 'presence'
|
|
493
|
+
* presence: 'away',
|
|
494
|
+
* content: '...clever saying goes here...'
|
|
495
|
+
* }
|
|
496
|
+
* }
|
|
497
|
+
*/
|
|
498
|
+
update(job, done) {
|
|
499
|
+
this.debug(`update() called for ${job.actor.id}`);
|
|
500
|
+
const props = {};
|
|
501
|
+
const show = {};
|
|
502
|
+
const status = {};
|
|
503
|
+
if (job.object.type === "presence") {
|
|
504
|
+
if (job.object.presence === "offline") {
|
|
505
|
+
props.type = "unavailable";
|
|
506
|
+
} else if (job.object.presence !== "online") {
|
|
507
|
+
show.show = job.object.presence;
|
|
508
|
+
}
|
|
509
|
+
if (job.object.content) {
|
|
510
|
+
status.status = job.object.content;
|
|
511
|
+
}
|
|
512
|
+
// setting presence
|
|
513
|
+
this.debug(`setting presence: ${job.object.presence}`);
|
|
514
|
+
this.__client
|
|
515
|
+
.send(this.__xml("presence", props, show, status))
|
|
516
|
+
.then(done);
|
|
517
|
+
} else {
|
|
518
|
+
done(`unknown update object type: ${job.object.type}`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* @description
|
|
524
|
+
* Send friend request
|
|
525
|
+
*
|
|
526
|
+
* @param {object} job activity streams object
|
|
527
|
+
* @param {object} done callback when job is done
|
|
528
|
+
*
|
|
529
|
+
* @example
|
|
530
|
+
*
|
|
531
|
+
* {
|
|
532
|
+
* context: 'xmpp',
|
|
533
|
+
* type: 'request-friend',
|
|
534
|
+
* actor: {
|
|
535
|
+
* id: 'user@host.org/Home'
|
|
536
|
+
* },
|
|
537
|
+
* target: {
|
|
538
|
+
* id: 'homer@jabber.net/Home',
|
|
539
|
+
* }
|
|
540
|
+
* }
|
|
541
|
+
*/
|
|
542
|
+
"request-friend"(job, done) {
|
|
543
|
+
this.debug(`request-friend() called for ${job.actor.id}`);
|
|
544
|
+
this.__client
|
|
545
|
+
.send(
|
|
546
|
+
this.__xml("presence", {
|
|
547
|
+
type: "subscribe",
|
|
548
|
+
to: job.target.id,
|
|
549
|
+
}),
|
|
550
|
+
)
|
|
551
|
+
.then(done);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* @description
|
|
556
|
+
* Send a remove friend request
|
|
557
|
+
*
|
|
558
|
+
* @param {object} job activity streams object
|
|
559
|
+
* @param {object} done callback when job is done
|
|
560
|
+
*
|
|
561
|
+
* @example
|
|
562
|
+
*
|
|
563
|
+
* {
|
|
564
|
+
* context: 'xmpp',
|
|
565
|
+
* type: 'remove-friend',
|
|
566
|
+
* actor: {
|
|
567
|
+
* id: 'user@host.org/Home'
|
|
568
|
+
* },
|
|
569
|
+
* target: {
|
|
570
|
+
* id: 'homer@jabber.net/Home',
|
|
571
|
+
* }
|
|
572
|
+
* }
|
|
573
|
+
*/
|
|
574
|
+
"remove-friend"(job, done) {
|
|
575
|
+
this.debug(`remove-friend() called for ${job.actor.id}`);
|
|
576
|
+
this.__client
|
|
577
|
+
.send(
|
|
578
|
+
this.__xml("presence", {
|
|
579
|
+
type: "unsubscribe",
|
|
580
|
+
to: job.target.id,
|
|
581
|
+
}),
|
|
582
|
+
)
|
|
583
|
+
.then(done);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* @description
|
|
588
|
+
* Confirm a friend request
|
|
589
|
+
*
|
|
590
|
+
* @param {object} job activity streams object
|
|
591
|
+
* @param {object} done callback when job is done
|
|
592
|
+
*
|
|
593
|
+
* @example
|
|
594
|
+
*
|
|
595
|
+
* {
|
|
596
|
+
* context: 'xmpp',
|
|
597
|
+
* type: 'make-friend',
|
|
598
|
+
* actor: {
|
|
599
|
+
* id: 'user@host.org/Home'
|
|
600
|
+
* },
|
|
601
|
+
* target: {
|
|
602
|
+
* id: 'homer@jabber.net/Home',
|
|
603
|
+
* }
|
|
604
|
+
* }
|
|
605
|
+
*/
|
|
606
|
+
"make-friend"(job, done) {
|
|
607
|
+
this.debug(`make-friend() called for ${job.actor.id}`);
|
|
608
|
+
this.__client
|
|
609
|
+
.send(
|
|
610
|
+
this.__xml("presence", {
|
|
611
|
+
type: "subscribe",
|
|
612
|
+
to: job.target.id,
|
|
613
|
+
}),
|
|
614
|
+
)
|
|
615
|
+
.then(done);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Indicate an intent to query something (i.e. get a list of users in a room).
|
|
620
|
+
*
|
|
621
|
+
* @param {object} job activity streams object
|
|
622
|
+
* @param {object} done callback when job is done
|
|
623
|
+
*
|
|
624
|
+
* @example
|
|
625
|
+
*
|
|
626
|
+
* {
|
|
627
|
+
* context: 'xmpp',
|
|
628
|
+
* type: 'query',
|
|
629
|
+
* actor: {
|
|
630
|
+
* id: 'slvrbckt@jabber.net/Home',
|
|
631
|
+
* type: 'person'
|
|
632
|
+
* },
|
|
633
|
+
* target: {
|
|
634
|
+
* id: 'PartyChatRoom@muc.jabber.net',
|
|
635
|
+
* type: 'room'
|
|
636
|
+
* },
|
|
637
|
+
* object: {
|
|
638
|
+
* type: 'attendance'
|
|
639
|
+
* }
|
|
640
|
+
* }
|
|
641
|
+
*
|
|
642
|
+
* // The above object might return:
|
|
643
|
+
* {
|
|
644
|
+
* context: 'xmpp',
|
|
645
|
+
* type: 'query',
|
|
646
|
+
* actor: {
|
|
647
|
+
* id: 'PartyChatRoom@muc.jabber.net',
|
|
648
|
+
* type: 'room'
|
|
649
|
+
* },
|
|
650
|
+
* target: {
|
|
651
|
+
* id: 'slvrbckt@jabber.net/Home',
|
|
652
|
+
* type: 'person'
|
|
653
|
+
* },
|
|
654
|
+
* object: {
|
|
655
|
+
* type: 'attendance'
|
|
656
|
+
* members: [
|
|
657
|
+
* 'RyanGosling',
|
|
658
|
+
* 'PeeWeeHerman',
|
|
659
|
+
* 'Commando',
|
|
660
|
+
* 'Smoochie',
|
|
661
|
+
* 'neo'
|
|
662
|
+
* ]
|
|
663
|
+
* }
|
|
664
|
+
* }
|
|
665
|
+
*/
|
|
666
|
+
query(job, done) {
|
|
667
|
+
this.debug(`sending query from ${job.actor.id} for ${job.target.id}`);
|
|
668
|
+
this.__client
|
|
669
|
+
.send(
|
|
670
|
+
this.__xml(
|
|
671
|
+
"iq",
|
|
672
|
+
{
|
|
673
|
+
id: "muc_id",
|
|
674
|
+
type: "get",
|
|
675
|
+
from: job.actor.id,
|
|
676
|
+
to: job.target.id,
|
|
677
|
+
},
|
|
678
|
+
this.__xml("query", {
|
|
679
|
+
xmlns: "http://jabber.org/protocol/disco#items",
|
|
680
|
+
}),
|
|
681
|
+
),
|
|
682
|
+
)
|
|
683
|
+
.then(done);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Disconnect XMPP client
|
|
688
|
+
* @param {object} job activity streams object
|
|
689
|
+
* @param done
|
|
690
|
+
*
|
|
691
|
+
* @example
|
|
692
|
+
*
|
|
693
|
+
* {
|
|
694
|
+
* context: 'xmpp',
|
|
695
|
+
* type: 'disconnect',
|
|
696
|
+
* actor: {
|
|
697
|
+
* id: 'slvrbckt@jabber.net/Home',
|
|
698
|
+
* type: 'person'
|
|
699
|
+
* }
|
|
700
|
+
* }
|
|
701
|
+
*/
|
|
702
|
+
disconnect(job, done) {
|
|
703
|
+
this.debug("disconnecting");
|
|
704
|
+
this.cleanup(done);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Called when it's time to close any connections or clean data before being wiped
|
|
709
|
+
* forcefully.
|
|
710
|
+
* @param {function} done - callback when complete
|
|
711
|
+
*/
|
|
712
|
+
cleanup(done) {
|
|
713
|
+
this.debug("cleanup");
|
|
714
|
+
this.config.initialized = false;
|
|
715
|
+
this.__client.stop();
|
|
716
|
+
done();
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
__registerHandlers() {
|
|
720
|
+
const ih = new IncomingHandlers(this);
|
|
721
|
+
this.__client.on("close", ih.close.bind(ih));
|
|
722
|
+
this.__client.on("error", ih.error.bind(ih));
|
|
723
|
+
this.__client.on("online", ih.online.bind(ih));
|
|
724
|
+
this.__client.on("stanza", ih.stanza.bind(ih));
|
|
725
|
+
}
|
|
726
|
+
}
|