@sockethub/platform-xmpp 5.0.0-alpha.9 → 5.0.0-beta.0

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/README.md CHANGED
@@ -8,63 +8,274 @@ This platform provides XMPP client functionality, allowing web applications to c
8
8
  XMPP servers, send messages, manage contacts, join chat rooms, and handle presence
9
9
  updates through ActivityStreams messages.
10
10
 
11
- ## Implemented Verbs (`@type`)
11
+ ## Implemented Verbs (`type`)
12
12
 
13
+ * **connect** - Establish a connection to the XMPP server
14
+ * **disconnect** - Close the XMPP connection
15
+ * **join** - Join an XMPP chat room (MUC)
16
+ * **leave** - Leave an XMPP chat room
13
17
  * **send** - Send messages to contacts or chat rooms
14
- * **join** - Join XMPP chat rooms (MUCs)
15
- * **observe** - Subscribe to presence updates
16
- * **request-friend** - Send contact/buddy requests
17
- * **remove-friend** - Remove contacts from roster
18
- * **make-friend** - Accept contact requests
19
18
  * **update** - Update presence status and information
19
+ * **request-friend** - Send a contact/buddy request
20
+ * **remove-friend** - Remove a contact from the roster
21
+ * **make-friend** - Accept a contact request
22
+ * **query** - Query room attendance and room information via service discovery
23
+
24
+ ## Authentication
25
+
26
+ Credentials are sent to Sockethub via a `credentials` message before the
27
+ `connect` verb. The XMPP platform accepts a single `password` field.
28
+
29
+ ### Credentials object
30
+
31
+ | Field | Type | Required | Description |
32
+ |---|---|---|---|
33
+ | `type` | string | yes | Must be `"credentials"`. |
34
+ | `userAddress` | string | yes | Bare JID, e.g. `user@jabber.net`. |
35
+ | `resource` | string | yes | XMPP resource identifier (e.g. `"phone"`). |
36
+ | `password` | string | yes | SASL password. Use this field for PLAIN-slot tokens too. |
37
+ | `server` | string | no | Overrides the hostname from `userAddress`. |
38
+ | `port` | number | no | Overrides the default port. |
39
+
40
+ ### Password authentication
41
+
42
+ Use a standard XMPP account password. The server negotiates the strongest
43
+ available SASL mechanism (typically SCRAM-SHA-1, falling back to PLAIN).
44
+ The examples below assume you already have an initialized Sockethub client
45
+ instance named `sc`.
46
+
47
+ ```javascript
48
+ {
49
+ type: "credentials",
50
+ "@context": sc.contextFor("xmpp"),
51
+ actor: { id: "user@jabber.net", type: "person" },
52
+ object: {
53
+ type: "credentials",
54
+ userAddress: "user@jabber.net",
55
+ password: "s3cret",
56
+ resource: "phone"
57
+ }
58
+ }
59
+ ```
60
+
61
+ If your deployment expects a bearer-style token in the SASL PLAIN password
62
+ slot, pass that token string as `password`. This is only a narrow
63
+ compatibility mode. Dedicated token SASL mechanisms such as ejabberd
64
+ `X-OAUTH2`, Prosody `OAUTHBEARER`, Prosody community `X-TOKEN`, and SASL2 FAST
65
+ are not implemented by the bundled `@xmpp/client`, which only supports
66
+ `SCRAM-SHA-1`, `PLAIN`, and `ANONYMOUS` and prefers `SCRAM-SHA-1` when both
67
+ SCRAM and PLAIN are offered.
68
+
69
+ A failed authentication with either a traditional password or a token string
70
+ surfaces the same `SASLError: not-authorized` and is treated as a
71
+ non-recoverable connection error.
20
72
 
21
73
  ## Usage
22
74
 
75
+ ### Connect Example
76
+
77
+ ```javascript
78
+ {
79
+ type: "connect",
80
+ "@context": sc.contextFor("xmpp"),
81
+ actor: {
82
+ id: "user@example.org/web",
83
+ type: "person"
84
+ }
85
+ }
86
+ ```
87
+
23
88
  ### Send Message Example
24
89
 
25
- ```json
90
+ ```javascript
91
+ {
92
+ type: "send",
93
+ "@context": sc.contextFor("xmpp"),
94
+ actor: {
95
+ id: "user@example.org",
96
+ type: "person"
97
+ },
98
+ target: {
99
+ id: "friend@jabber.net",
100
+ type: "person"
101
+ },
102
+ object: {
103
+ type: "message",
104
+ content: "Hello from Sockethub!"
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### Send Room Message Example
110
+
111
+ ```javascript
112
+ {
113
+ type: "send",
114
+ "@context": sc.contextFor("xmpp"),
115
+ actor: {
116
+ id: "user@example.org/web",
117
+ type: "person"
118
+ },
119
+ target: {
120
+ id: "room@conference.example.org",
121
+ type: "room"
122
+ },
123
+ object: {
124
+ type: "message",
125
+ content: "Hello room"
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### Update Presence Example
131
+
132
+ ```javascript
133
+ {
134
+ type: "update",
135
+ "@context": sc.contextFor("xmpp"),
136
+ actor: {
137
+ id: "user@example.org/web",
138
+ type: "person"
139
+ },
140
+ object: {
141
+ type: "presence",
142
+ presence: "away",
143
+ content: "Out for lunch"
144
+ }
145
+ }
146
+ ```
147
+
148
+ ### Query Room Attendance Response Example
149
+
150
+ ```javascript
151
+ {
152
+ type: "query",
153
+ "@context": sc.contextFor("xmpp"),
154
+ actor: {
155
+ id: "room@conference.example.org",
156
+ type: "room"
157
+ },
158
+ target: {
159
+ id: "user@example.org/web",
160
+ type: "person"
161
+ },
162
+ object: {
163
+ type: "attendance",
164
+ members: ["alice", "bob", "carol"]
165
+ }
166
+ }
167
+ ```
168
+
169
+ ### Query Room Attendance Example
170
+
171
+ ```javascript
172
+ {
173
+ type: "query",
174
+ "@context": sc.contextFor("xmpp"),
175
+ actor: {
176
+ id: "user@example.org/web",
177
+ type: "person"
178
+ },
179
+ target: {
180
+ id: "room@conference.example.org",
181
+ type: "room"
182
+ },
183
+ object: {
184
+ type: "attendance"
185
+ }
186
+ }
187
+ ```
188
+
189
+ ### Query Room Information Example
190
+
191
+ ```javascript
192
+ {
193
+ type: "query",
194
+ "@context": sc.contextFor("xmpp"),
195
+ actor: {
196
+ id: "user@example.org/web",
197
+ type: "person"
198
+ },
199
+ target: {
200
+ id: "room@conference.example.org",
201
+ type: "room"
202
+ },
203
+ object: {
204
+ type: "room-info"
205
+ }
206
+ }
207
+ ```
208
+
209
+ ### Query Room Information Response Example
210
+
211
+ ```javascript
26
212
  {
27
- "@type": "send",
28
- "context": "xmpp",
29
- "actor": {
30
- "@id": "user@example.org"
213
+ type: "query",
214
+ "@context": sc.contextFor("xmpp"),
215
+ actor: {
216
+ id: "room@conference.example.org",
217
+ type: "room",
218
+ name: "Room Display Name"
31
219
  },
32
- "target": {
33
- "@id": "friend@jabber.net"
220
+ target: {
221
+ id: "user@example.org/web",
222
+ type: "person"
34
223
  },
35
- "object": {
36
- "@type": "Note",
37
- "content": "Hello from Sockethub!"
224
+ object: {
225
+ type: "room-info",
226
+ features: ["http://jabber.org/protocol/muc", "muc_passwordprotected"],
227
+ identities: [{
228
+ category: "conference",
229
+ type: "text",
230
+ name: "Room Display Name"
231
+ }]
232
+ }
233
+ }
234
+ ```
235
+
236
+ ### Disconnect Example
237
+
238
+ ```javascript
239
+ {
240
+ type: "disconnect",
241
+ "@context": sc.contextFor("xmpp"),
242
+ actor: {
243
+ id: "user@example.org/web",
244
+ type: "person"
38
245
  }
39
246
  }
40
247
  ```
41
248
 
42
249
  ### Join Chat Room Example
43
250
 
44
- ```json
251
+ ```javascript
45
252
  {
46
- "@type": "join",
47
- "context": "xmpp",
48
- "actor": {
49
- "@id": "user@example.org"
253
+ type: "join",
254
+ "@context": sc.contextFor("xmpp"),
255
+ actor: {
256
+ id: "user@example.org",
257
+ type: "person"
50
258
  },
51
- "target": {
52
- "@id": "room@conference.example.org"
259
+ target: {
260
+ id: "room@conference.example.org",
261
+ type: "room"
53
262
  }
54
263
  }
55
264
  ```
56
265
 
57
266
  ### Request Friend Example
58
267
 
59
- ```json
268
+ ```javascript
60
269
  {
61
- "@type": "request-friend",
62
- "context": "xmpp",
63
- "actor": {
64
- "@id": "user@example.org"
270
+ type: "request-friend",
271
+ "@context": sc.contextFor("xmpp"),
272
+ actor: {
273
+ id: "user@example.org",
274
+ type: "person"
65
275
  },
66
- "target": {
67
- "@id": "friend@jabber.net"
276
+ target: {
277
+ id: "friend@jabber.net",
278
+ type: "person"
68
279
  }
69
280
  }
70
281
  ```
@@ -86,4 +297,5 @@ updates through ActivityStreams messages.
86
297
 
87
298
  ## API
88
299
 
89
- Detailed API documentation can be found [here](API.md)
300
+ API reference docs are generated from the source JSDoc and are not committed to this repository.
301
+ Run `bun run doc` in this package to generate them locally.
@@ -0,0 +1,25 @@
1
+ import type { XmppElement } from "@xmpp/client";
2
+ import type { XmppHandlerSession } from "./types.js";
3
+ export declare class IncomingHandlers {
4
+ /** @internal Exposed to allow test access for null-handling validation. */
5
+ session: XmppHandlerSession | undefined;
6
+ constructor(session?: XmppHandlerSession);
7
+ close(): void;
8
+ error(err: {
9
+ text?: string;
10
+ condition?: string;
11
+ toString(): string;
12
+ }): void;
13
+ presence(stanza: XmppElement): void;
14
+ subscribe(to: {
15
+ id: string;
16
+ type: string;
17
+ }, from: string, name?: string): void;
18
+ notifyChatMessage(stanza: XmppElement): void;
19
+ notifyError(stanza: XmppElement): void;
20
+ notifyRoomAttendance(stanza: XmppElement): void;
21
+ notifyRoomInfo(stanza: XmppElement): void;
22
+ notifyRoomInfoError(stanza: XmppElement): void;
23
+ online(): void;
24
+ stanza(stanza: XmppElement): void;
25
+ }
@@ -0,0 +1,183 @@
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
+ import type { ActivityStream, Logger, PersistentPlatformConfig, PersistentPlatformInterface, PlatformCallback, PlatformSchemaStruct, PlatformSendToClient } from "@sockethub/schemas";
19
+ import { type XmppClientInstance, type XmppClientOptions, type XmppElement } from "@xmpp/client";
20
+ import type { XmppCredentialsObject, XmppPlatformSession } from "./types.js";
21
+ export type { XmppCredentialsObject, XmppPlatformSession } from "./types.js";
22
+ /**
23
+ * xmpp.js never sets TLS SNI, and `@xmpp/resolve` connects to raw IPs from
24
+ * SRV/A lookups, so direct TLS (`xmpps://`) is validated against the IP and
25
+ * fails (ERR_TLS_CERT_ALTNAME_INVALID). Both connect paths call
26
+ * `Transport.prototype.socketParameters(uri)`, so wrapping the prototypes
27
+ * covers them.
28
+ *
29
+ * The transport classes are shared module-level prototypes, so the wrapper is
30
+ * installed once per prototype and keeps the active domain in prototype state.
31
+ * That state is refreshed on every call: a later client for a different JID
32
+ * domain must not reuse the first wrapper's servername, and an IP-literal
33
+ * domain must send no SNI at all. Remove once xmpp.js sets `servername`
34
+ * upstream (xmppjs/xmpp.js#1128) and `@xmpp/*` is bumped.
35
+ */
36
+ export declare function setTlsServername(client: XmppClientInstance, domain: string): void;
37
+ /**
38
+ * Handles all actions related to communication via. the XMPP protocol.
39
+ *
40
+ * Uses `xmpp.js` as a base tool for interacting with XMPP.
41
+ *
42
+ * {@link https://github.com/xmppjs/xmpp.js}
43
+ */
44
+ export default class XMPP implements PersistentPlatformInterface {
45
+ credentialsHash: string | undefined;
46
+ readonly config: PersistentPlatformConfig;
47
+ readonly log: Logger;
48
+ readonly sendToClient: PlatformSendToClient;
49
+ /** @internal Set of bare JIDs for rooms this client has joined. */
50
+ __knownRooms: Set<string>;
51
+ protected __clientConstructor: (options: XmppClientOptions) => XmppClientInstance;
52
+ protected __xml: (name: string, attrs?: Record<string, string | undefined>, ...children: (XmppElement | undefined)[]) => XmppElement;
53
+ private readonly id;
54
+ private __initialized;
55
+ /** @internal Exposed for test access and IncomingHandlers. */
56
+ __client: XmppClientInstance | undefined;
57
+ /**
58
+ * Constructor called from the Sockethub `Platform` instance, passing in a
59
+ * session object.
60
+ * @param session - {@link XmppPlatformSession}
61
+ */
62
+ constructor(session: XmppPlatformSession);
63
+ protected createClient(): void;
64
+ protected createXml(): void;
65
+ /**
66
+ * Mark the platform as disconnected and uninitialized
67
+ * @param stopReconnection - If true, stop automatic reconnection
68
+ */
69
+ private __markDisconnected;
70
+ /**
71
+ * Classify error to determine if reconnection should be attempted
72
+ * @param err - The error from XMPP client
73
+ * @returns 'RECOVERABLE' or 'NON_RECOVERABLE'
74
+ */
75
+ private __classifyError;
76
+ /**
77
+ * Check if the XMPP client is properly connected and can send messages
78
+ * @returns true if client is connected and operational
79
+ */
80
+ private __isClientConnected;
81
+ /**
82
+ * @description
83
+ * JSON schema defining the types this platform accepts.
84
+ *
85
+ * Deployments that accept a bearer-style token in the SASL PLAIN password
86
+ * slot should still pass that token string as `password`. Dedicated token
87
+ * SASL mechanisms such as ejabberd `X-OAUTH2`, Prosody `OAUTHBEARER`,
88
+ * Prosody community `X-TOKEN`, and SASL2 FAST are not implemented by this
89
+ * client. See the package README for canonical credentials and usage
90
+ * payload examples.
91
+ **/
92
+ get schema(): PlatformSchemaStruct;
93
+ /**
94
+ * Returns whether the platform is ready to handle jobs.
95
+ * For XMPP, this means we have successfully connected to the server.
96
+ * During temporary network interruptions with automatic reconnection,
97
+ * remains true to allow queued jobs to retry rather than fail.
98
+ * @returns true if ready to handle jobs
99
+ */
100
+ isInitialized(): boolean;
101
+ /**
102
+ * Connect to the XMPP server.
103
+ *
104
+ * @param job - activity streams object
105
+ * @param credentials - credentials object
106
+ * @param done - callback when job is done
107
+ */
108
+ connect(job: ActivityStream, credentials: XmppCredentialsObject, done: PlatformCallback): void;
109
+ /**
110
+ * Join a room, optionally defining a display name for that room.
111
+ *
112
+ * @param job - activity streams object
113
+ * @param done - callback when job is done
114
+ */
115
+ join(job: ActivityStream, done: PlatformCallback): Promise<void>;
116
+ /**
117
+ * Leave a room
118
+ *
119
+ * @param job - activity streams object
120
+ * @param done - callback when job is done
121
+ */
122
+ leave(job: ActivityStream, done: PlatformCallback): void;
123
+ /**
124
+ * Send a message to a room or private conversation.
125
+ *
126
+ * @param job - activity streams object
127
+ * @param done - callback when job is done
128
+ */
129
+ send(job: ActivityStream, done: PlatformCallback): void;
130
+ /**
131
+ * @description
132
+ * Indicate presence and status message.
133
+ * Valid presence values are "away", "chat", "dnd", "xa", "offline", "online".
134
+ *
135
+ * @param job - activity streams object
136
+ * @param done - callback when job is done
137
+ */
138
+ update(job: ActivityStream, done: PlatformCallback): void;
139
+ /**
140
+ * @description
141
+ * Send friend request
142
+ *
143
+ * @param job - activity streams object
144
+ * @param done - callback when job is done
145
+ */
146
+ "request-friend"(job: ActivityStream, done: PlatformCallback): void;
147
+ /**
148
+ * @description
149
+ * Send a remove friend request
150
+ *
151
+ * @param job - activity streams object
152
+ * @param done - callback when job is done
153
+ */
154
+ "remove-friend"(job: ActivityStream, done: PlatformCallback): void;
155
+ /**
156
+ * @description
157
+ * Confirm a friend request
158
+ *
159
+ * @param job - activity streams object
160
+ * @param done - callback when job is done
161
+ */
162
+ "make-friend"(job: ActivityStream, done: PlatformCallback): void;
163
+ /**
164
+ * Indicate an intent to query something (e.g. get room attendance or room information).
165
+ *
166
+ * @param job - activity streams object
167
+ * @param done - callback when job is done
168
+ */
169
+ query(job: ActivityStream, done: PlatformCallback): void;
170
+ /**
171
+ * Disconnect XMPP client
172
+ * @param _job - activity streams object
173
+ * @param done - callback when done
174
+ */
175
+ disconnect(_job: ActivityStream, done: PlatformCallback): void;
176
+ /**
177
+ * Called when it's time to close any connections or clean data before being wiped
178
+ * forcefully.
179
+ * @param done - callback when complete
180
+ */
181
+ cleanup(done: PlatformCallback): void;
182
+ private __registerHandlers;
183
+ }