@sockethub/platform-xmpp 5.0.0-alpha.12 → 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.
@@ -0,0 +1,2 @@
1
+ import type { PlatformSchemaStruct } from "@sockethub/schemas";
2
+ export declare const PlatformSchema: PlatformSchemaStruct;
@@ -0,0 +1,33 @@
1
+ import type { ActivityActor, CredentialsObject, Logger, PlatformSendToClient, PlatformSession } from "@sockethub/schemas";
2
+ export interface XmppPlatformSession extends PlatformSession {
3
+ id: string;
4
+ }
5
+ export interface XmppCredentialsObject extends CredentialsObject {
6
+ object: {
7
+ type: string;
8
+ userAddress: string;
9
+ password: string;
10
+ server?: string;
11
+ port?: number;
12
+ resource: string;
13
+ };
14
+ }
15
+ export interface XmppBuiltCredentials {
16
+ service: string;
17
+ username: string;
18
+ password: string;
19
+ resource?: string;
20
+ }
21
+ /**
22
+ * Narrow interface for what IncomingHandlers uses from the XMPP platform instance.
23
+ * Avoids a circular import between incoming-handlers.ts and index.ts.
24
+ */
25
+ export interface XmppHandlerSession {
26
+ log: Logger;
27
+ sendToClient: PlatformSendToClient;
28
+ __knownRooms: Set<string>;
29
+ actor?: ActivityActor;
30
+ connection?: {
31
+ disconnect?: () => void;
32
+ };
33
+ }
@@ -0,0 +1,19 @@
1
+ import type { XmppElement } from "@xmpp/client";
2
+ import type { XmppBuiltCredentials, XmppCredentialsObject } from "./types.js";
3
+ interface XDataFieldResult {
4
+ var: string;
5
+ field: {
6
+ type: string;
7
+ label?: string;
8
+ value: string | number | boolean | string[] | null;
9
+ options?: Array<{
10
+ label: string;
11
+ value: string;
12
+ }>;
13
+ };
14
+ }
15
+ export declare const utils: {
16
+ buildXmppCredentials: (credentials: XmppCredentialsObject) => XmppBuiltCredentials;
17
+ parseXDataField: (field: XmppElement) => XDataFieldResult | null;
18
+ };
19
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sockethub/platform-xmpp",
3
3
  "description": "A sockethub platform module implementing XMPP functionality",
4
- "version": "5.0.0-alpha.12",
4
+ "version": "5.0.0-alpha.13",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "author": "Nick Jennings <nick@silverbucket.net>",
@@ -12,17 +12,16 @@
12
12
  "main": "dist/index.js",
13
13
  "exports": {
14
14
  ".": {
15
- "bun": "./src/index.js",
15
+ "types": "./dist/index.d.ts",
16
16
  "import": "./dist/index.js",
17
17
  "default": "./dist/index.js"
18
18
  }
19
19
  },
20
20
  "files": [
21
- "src/",
22
21
  "dist/"
23
22
  ],
24
23
  "engines": {
25
- "bun": ">=1.2"
24
+ "node": ">=20"
26
25
  },
27
26
  "keywords": [
28
27
  "sockethub",
@@ -40,27 +39,31 @@
40
39
  },
41
40
  "homepage": "https://github.com/sockethub/sockethub/tree/master/packages/platform-xmpp",
42
41
  "scripts": {
43
- "build": "bun build src/index.js --outdir dist --target node --format esm --sourcemap=external",
42
+ "build": "bun build src/index.ts --outdir dist --target node --format esm --sourcemap=external",
44
43
  "clean": "rm -rf dist",
45
44
  "clean:deps": "rm -rf node_modules",
46
- "doc": "jsdoc2md --no-gfm --heading-depth 1 src/index.js > API.md"
45
+ "doc": "typedoc --options typedoc.json"
47
46
  },
48
47
  "dependencies": {
48
+ "@sockethub/util": "^1.0.0-alpha.1",
49
49
  "@xmpp/client": "^0.13.6"
50
50
  },
51
51
  "devDependencies": {
52
- "@sockethub/schemas": "^3.0.0-alpha.12",
52
+ "@sockethub/schemas": "^3.0.0-alpha.13",
53
+ "@types/bun": "latest",
53
54
  "@xmpp/xml": "^0.13.2",
54
- "jsdoc-to-markdown": "^8.0.3",
55
- "sinon": "^17.0.2"
55
+ "ajv": "^8.17.1",
56
+ "sinon": "^17.0.2",
57
+ "typedoc": "^0.28.14"
56
58
  },
57
59
  "peerDependencies": {
58
- "@sockethub/server": "^5.0.0-alpha.12"
60
+ "@sockethub/server": "^5.0.0-alpha.13"
59
61
  },
60
62
  "peerDependenciesMeta": {
61
63
  "@sockethub/server": {
62
64
  "optional": true
63
65
  }
64
66
  },
65
- "gitHead": "f039dab3c3f67cbbf204476fc397532e973f82a8"
67
+ "gitHead": "864733e5b34449ef39542eceb4ff11aa308081bc",
68
+ "types": "./dist/index.d.ts"
66
69
  }
@@ -1,355 +0,0 @@
1
- import { buildCanonicalContext } from "@sockethub/schemas";
2
- import { PlatformSchema } from "./schema.js";
3
-
4
- const XMPP_CONTEXT = buildCanonicalContext(PlatformSchema.contextUrl);
5
-
6
- function getMessageBody(stanza) {
7
- for (const elem of stanza.children) {
8
- if (elem.name === "body") {
9
- return elem.children.join(" ");
10
- }
11
- }
12
- }
13
-
14
- function getMessageTimestamp(stanza) {
15
- try {
16
- const delay = stanza.children.find((c) => c.name === "delay");
17
- return delay.attrs.stamp;
18
- } catch (_e) {
19
- // no timestamp
20
- return null;
21
- }
22
- }
23
-
24
- function getMessageId(stanza) {
25
- try {
26
- return stanza.attrs.id;
27
- } catch (_e) {
28
- // no message id
29
- return null;
30
- }
31
- }
32
-
33
- function getMessageStanzaId(stanza) {
34
- try {
35
- const stanzaId = stanza.children.find((c) => c.name === "stanza-id");
36
- return stanzaId.attrs.id;
37
- } catch (_e) {
38
- // no stanza id
39
- return null;
40
- }
41
- }
42
-
43
- function getMessageReplaceId(stanza) {
44
- try {
45
- const replaceEl = stanza.children.find((c) => c.name === "replace");
46
- return replaceEl.attrs.id;
47
- } catch (_e) {
48
- // no origin id
49
- return null;
50
- }
51
- }
52
-
53
- function getPresence(stanza) {
54
- if (stanza.getChild("show")) {
55
- return stanza.getChild("show").getText();
56
- }
57
- return stanza.attrs.type === "unavailable" ? "offline" : "online";
58
- }
59
-
60
- export class IncomingHandlers {
61
- constructor(session) {
62
- this.session = session;
63
- }
64
-
65
- close() {
66
- if (!this.session) {
67
- console.debug("close event received but session is undefined");
68
- return;
69
- }
70
-
71
- this.session.log.debug(
72
- "received close event with no handler specified",
73
- );
74
- if (this.session.actor && this.session.sendToClient) {
75
- this.session.sendToClient({
76
- "@context": XMPP_CONTEXT,
77
- type: "close",
78
- actor: this.session.actor,
79
- target: this.session.actor,
80
- });
81
- this.session.log.debug(
82
- `**** xmpp this.session.for ${this.session.actor.id} closed`,
83
- );
84
- }
85
- if (
86
- this.session.connection &&
87
- typeof this.session.connection.disconnect === "function"
88
- ) {
89
- this.session.connection.disconnect();
90
- }
91
- }
92
-
93
- error(err) {
94
- try {
95
- this.session.sendToClient({
96
- "@context": XMPP_CONTEXT,
97
- type: "error",
98
- error: err.text || err.toString(),
99
- object: {
100
- type: "message",
101
- condition: err.condition || "unknown",
102
- },
103
- });
104
- } catch (e) {
105
- this.session.log.debug("*** XMPP ERROR (rl catch): ", e);
106
- }
107
- }
108
-
109
- presence(stanza) {
110
- const obj = {
111
- "@context": XMPP_CONTEXT,
112
- type: "update",
113
- actor: {
114
- type: "person",
115
- id: stanza.attrs.from,
116
- },
117
- object: {
118
- type: "presence",
119
- },
120
- };
121
- if (stanza.getChildText("status")) {
122
- obj.object.content = stanza.getChildText("status");
123
- }
124
- obj.object.presence = getPresence(stanza);
125
- if (stanza.attrs.to) {
126
- obj.target = { id: stanza.attrs.to, type: "person" };
127
- } else {
128
- obj.actor.name = stanza.attrs.from.split("/")[1];
129
- }
130
- this.session.log.debug(
131
- `received contact presence update from ${stanza.attrs.from}`,
132
- );
133
- this.session.sendToClient(obj);
134
- }
135
-
136
- subscribe(to, from, name) {
137
- this.session.log.debug(`received subscribe request from ${from}`);
138
- const actor = { id: from, type: "person" };
139
- if (name) {
140
- actor.name = name;
141
- }
142
- this.session.sendToClient({
143
- "@context": XMPP_CONTEXT,
144
- type: "request-friend",
145
- actor: actor,
146
- target: to,
147
- });
148
- }
149
-
150
- // unsubscribe(from) {
151
- // this.session.log.debug('received unsubscribe request from ' + from);
152
- // this.session.sendToClient({
153
- // type: "remove-friend",
154
- // actor: { id: from },
155
- // target: this.session.actor
156
- // });
157
- // }
158
-
159
- notifyChatMessage(stanza) {
160
- const message = getMessageBody(stanza);
161
- if (!message) {
162
- return;
163
- }
164
- const from = stanza.attrs.from;
165
- const timestamp = getMessageTimestamp(stanza);
166
- const messageId = getMessageId(stanza);
167
- const type = stanza.attrs.type === "groupchat" ? "room" : "person";
168
-
169
- const activity = {
170
- "@context": XMPP_CONTEXT,
171
- type: "send",
172
- actor: {
173
- type: "person",
174
- id: from,
175
- },
176
- target: {
177
- type: type,
178
- id: stanza.attrs.to,
179
- },
180
- object: {
181
- type: "message",
182
- id: messageId,
183
- content: message,
184
- },
185
- };
186
-
187
- const messageStanzaId = getMessageStanzaId(stanza);
188
- if (messageStanzaId) {
189
- activity.object["xmpp:stanza-id"] = messageStanzaId;
190
- }
191
-
192
- const messageReplaceId = getMessageReplaceId(stanza);
193
- if (messageReplaceId) {
194
- activity.object["xmpp:replace"] = { id: messageReplaceId };
195
- }
196
-
197
- if (type === "room") {
198
- [activity.target.id, activity.actor.name] = from.split("/");
199
- }
200
-
201
- if (timestamp) {
202
- activity.published = new Date(timestamp).toISOString();
203
- }
204
-
205
- this.session.sendToClient(activity);
206
- }
207
-
208
- notifyError(stanza) {
209
- const error = stanza.getChild("error");
210
- let message = stanza.toString();
211
- let type = "message";
212
- if (stanza.is("presence")) {
213
- type = "update";
214
- }
215
-
216
- if (error) {
217
- message = error.toString();
218
- if (error.getChild("remote-server-not-found")) {
219
- // when we get this.session.type of return message, we know it was a response from a join
220
- type = "join";
221
- message = `remote server not found ${stanza.attrs.from}`;
222
- }
223
- }
224
-
225
- this.session.sendToClient({
226
- "@context": XMPP_CONTEXT,
227
- type: type,
228
- actor: {
229
- id: stanza.attrs.from,
230
- type: "room",
231
- },
232
- error: message,
233
- target: {
234
- id: stanza.attrs.to,
235
- type: "person",
236
- },
237
- });
238
- }
239
-
240
- notifyRoomAttendance(stanza) {
241
- const query = stanza.getChild("query");
242
- if (query) {
243
- const members = [];
244
- const entries = query.getChildren("item");
245
- for (const e in entries) {
246
- if (!Object.hasOwn(entries, e)) {
247
- continue;
248
- }
249
- members.push(entries[e].attrs.name);
250
- }
251
-
252
- this.session.sendToClient({
253
- "@context": XMPP_CONTEXT,
254
- type: "query",
255
- actor: {
256
- id: stanza.attrs.from,
257
- type: "room",
258
- },
259
- target: {
260
- id: stanza.attrs.to,
261
- type: "person",
262
- },
263
- object: {
264
- type: "attendance",
265
- members: members,
266
- },
267
- });
268
- }
269
- }
270
-
271
- online() {
272
- this.session.log.debug("online");
273
- }
274
-
275
- /**
276
- * Handles all unknown conditions that we don't have an explicit handler for
277
- **/
278
- stanza(stanza) {
279
- // console.log("incoming stanza ", stanza);
280
- if (stanza.attrs.type === "error") {
281
- this.notifyError(stanza);
282
- } else if (stanza.is("message")) {
283
- this.notifyChatMessage(stanza);
284
- } else if (stanza.is("presence")) {
285
- this.presence(stanza);
286
- } else if (stanza.is("iq")) {
287
- if (
288
- stanza.attrs.id === "muc_id" &&
289
- stanza.attrs.type === "result"
290
- ) {
291
- this.session.log.debug("got room attendance list");
292
- return this.notifyRoomAttendance(stanza);
293
- }
294
-
295
- // todo: clean up this area, unsure of what these are
296
- const query = stanza.getChild("query");
297
- if (query) {
298
- const entries = query.getChildren("item");
299
- for (const e in entries) {
300
- if (!entries.hasOwn(e)) {
301
- continue;
302
- }
303
- this.session.log.debug("STANZA ATTRS: ", entries[e].attrs);
304
- if (entries[e].attrs.subscription === "both") {
305
- this.session.sendToClient({
306
- "@context": XMPP_CONTEXT,
307
- type: "update",
308
- actor: {
309
- id: entries[e].attrs.jid,
310
- name: entries[e].attrs.name,
311
- },
312
- target: this.session.actor,
313
- object: {
314
- type: "presence",
315
- status: "",
316
- presence: getPresence(entries[e]),
317
- },
318
- });
319
- } else if (
320
- entries[e].attrs.subscription === "from" &&
321
- entries[e].attrs.ask &&
322
- entries[e].attrs.ask === "subscribe"
323
- ) {
324
- this.session.sendToClient({
325
- "@context": XMPP_CONTEXT,
326
- type: "update",
327
- actor: {
328
- id: entries[e].attrs.jid,
329
- name: entries[e].attrs.name,
330
- },
331
- target: this.session.actor,
332
- object: {
333
- type: "presence",
334
- statusText: "",
335
- presence: "notauthorized",
336
- },
337
- });
338
- } else {
339
- /**
340
- * can't figure out how to know if one of these query stanzas are from
341
- * added contacts or pending requests
342
- */
343
- this.subscribe(
344
- this.session.actor,
345
- entries[e].attrs.jid,
346
- entries[e].attrs.name,
347
- );
348
- }
349
- }
350
- }
351
- } else {
352
- this.session.log.debug(`got XMPP unknown stanza... ${stanza}`);
353
- }
354
- }
355
- }