@wacando/agent 2.0.2 → 2.2.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 +6 -2
- package/STATUS.md +20 -2
- package/THIRD-PARTY-NOTICES.txt +0 -1113
- package/bin/wacando-agent.mjs +48 -3
- package/docs/AGENT_TOOLS.md +87 -5
- package/docs/MIGRATION-2.0.md +3 -2
- package/docs/ONBOARDING.md +1 -1
- package/docs/PLUGIN_HANDOFF_FOLLOWUP.md +1 -1
- package/docs/RELEASE.md +10 -6
- package/docs/WACANDO_PACKAGES.md +76 -22
- package/package.json +19 -7
- package/packages/mcp/agent-tools.mjs +15 -0
- package/packages/mcp/server.mjs +1 -1
- package/packages/onboarding/version.mjs +1 -1
- package/packages/runtime/conversations.mjs +43 -10
- package/packages/runtime/device.mjs +1 -1
- package/packages/runtime/files.mjs +626 -0
- package/packages/runtime/recovery.mjs +5 -4
- package/packages/runtime/runtime.mjs +21 -3
- package/packages/runtime/session.mjs +1 -1
- package/packages/wacando/chunks/chunk-53E7SSTF.mjs +423 -0
- package/packages/wacando/chunks/chunk-PHGOH4AU.mjs +421 -0
- package/packages/wacando/chunks/chunk-PZDQXGA6.mjs +74 -0
- package/packages/wacando/omemo-wire.mjs +192 -0
- package/packages/wacando/omemo.mjs +3045 -0
- package/packages/wacando/xmpp.mjs +86 -0
- package/plugins/wacando/.codex-plugin/plugin.json +1 -1
- package/plugins/wacando/skills/wacando/SKILL.md +1 -0
- package/plugins/wacando-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/wacando-claude/skills/open/SKILL.md +1 -0
- package/plugins/wacando-cursor/.cursor-plugin/plugin.json +1 -1
- package/plugins/wacando-cursor/skills/wacando/SKILL.md +1 -0
- package/provenance.json +129 -266
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { OnboardingService } from '../onboarding/service.mjs';
|
|
2
|
-
import {
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { CredentialStore, digest, SafeError } from '../onboarding/storage.mjs';
|
|
3
4
|
import { configuredOrigin, get, post } from '../onboarding/network.mjs';
|
|
4
5
|
import { ConversationClient } from './conversations.mjs';
|
|
6
|
+
import { FileTransfers, FileVault, uploadPin } from './files.mjs';
|
|
5
7
|
import { claim, pairingErrors, redeem } from './pairing.mjs';
|
|
6
8
|
|
|
7
9
|
const BUFFER_LIMIT = 200;
|
|
@@ -27,10 +29,16 @@ export function publicIdentity(agent, state) {
|
|
|
27
29
|
* and stop on revocation.
|
|
28
30
|
*/
|
|
29
31
|
export class AgentRuntime {
|
|
30
|
-
|
|
32
|
+
/**
|
|
33
|
+
* `files`: `{ uploadOrigin, quarantineDir }` (defaults: WACANDO_UPLOAD_ORIGIN
|
|
34
|
+
* or the API origin; WACANDO_QUARANTINE_DIR or a directory inside the
|
|
35
|
+
* protected runtime state) plus test hooks (`now`, `sleep`, `pollMs`, `fetch`).
|
|
36
|
+
*/
|
|
37
|
+
constructor({ host = 'cli', origin, store = new CredentialStore(), report, request, connect, onStanza, files = {} } = {}) {
|
|
31
38
|
if (!/^[a-z0-9][a-z0-9_-]{0,31}$/.test(host)) throw new SafeError('Invalid runtime host name.');
|
|
32
39
|
this.host = host;
|
|
33
40
|
this.originSetting = origin;
|
|
41
|
+
this.fileOptions = files;
|
|
34
42
|
this.store = store;
|
|
35
43
|
this.listeners = new Set();
|
|
36
44
|
this.clients = new Map();
|
|
@@ -120,7 +128,8 @@ export class AgentRuntime {
|
|
|
120
128
|
let client = this.clients.get(agent.agentId);
|
|
121
129
|
if (client && client.session !== session) this.rebind(agent.agentId, session);
|
|
122
130
|
if (!client) {
|
|
123
|
-
|
|
131
|
+
const files = await this.fileTransfersFor(agent);
|
|
132
|
+
client = new ConversationClient({ runtime: this, agent, session, files });
|
|
124
133
|
this.clients.set(agent.agentId, client);
|
|
125
134
|
for (const stanza of this.buffered.get(agent.agentId) ?? []) client.handle(stanza);
|
|
126
135
|
this.buffered.delete(agent.agentId);
|
|
@@ -191,6 +200,15 @@ export class AgentRuntime {
|
|
|
191
200
|
return { paired: true, ...publicIdentity(agent) };
|
|
192
201
|
}
|
|
193
202
|
|
|
203
|
+
/** File transfer state of one identity: private directories, pinned upload origin. */
|
|
204
|
+
async fileTransfersFor(agent) {
|
|
205
|
+
const { uploadOrigin, quarantineDir = process.env.WACANDO_QUARANTINE_DIR, ...hooks } = this.fileOptions;
|
|
206
|
+
const pin = uploadPin(agent.origin, uploadOrigin ?? process.env.WACANDO_UPLOAD_ORIGIN);
|
|
207
|
+
const base = join(await this.store.directory(agent.origin, this.host), `files-${digest(agent.agentId)}`);
|
|
208
|
+
const vault = new FileVault(base, quarantineDir ? join(quarantineDir, digest(`${agent.origin}\n${agent.agentId}`).slice(0, 32)) : undefined);
|
|
209
|
+
return client => new FileTransfers(client, { vault, pin, ...hooks });
|
|
210
|
+
}
|
|
211
|
+
|
|
194
212
|
/** Subscribe to every stanza of every connected identity. Returns an unsubscribe function. */
|
|
195
213
|
onStanza(listener) {
|
|
196
214
|
this.listeners.add(listener);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { client } from '@xmpp/client';
|
|
2
|
-
import { announceAvailable, refuseSubscriptionRequests, xml } from '
|
|
2
|
+
import { announceAvailable, refuseSubscriptionRequests, xml } from '#wacando/xmpp';
|
|
3
3
|
import { SafeError } from '../onboarding/storage.mjs';
|
|
4
4
|
import { loadDevice } from './device.mjs';
|
|
5
5
|
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
// Embedded from lbframe/wacando@618922ecaf2213da18949a8b27d343c8767b64c5 by scripts/embed-wacando.mjs. Do not edit; see provenance.json.
|
|
2
|
+
import {
|
|
3
|
+
XMPP_DOMAIN
|
|
4
|
+
} from "./chunk-PZDQXGA6.mjs";
|
|
5
|
+
|
|
6
|
+
// packages/xmpp/src/muc.ts
|
|
7
|
+
import xml from "@xmpp/xml";
|
|
8
|
+
|
|
9
|
+
// packages/xmpp/src/jid.ts
|
|
10
|
+
var COMPONENT_JID = `policy.${XMPP_DOMAIN}`;
|
|
11
|
+
function occupantJid(room, nick) {
|
|
12
|
+
if (nick.length === 0) {
|
|
13
|
+
throw new Error("occupant nick must not be empty");
|
|
14
|
+
}
|
|
15
|
+
return `${room}/${nick}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// packages/xmpp/src/timeouts.ts
|
|
19
|
+
var BASE_TIMEOUT_MS = 1e4;
|
|
20
|
+
var scale = 1;
|
|
21
|
+
function scaledTimeoutMs(ms) {
|
|
22
|
+
return Math.round(ms * scale);
|
|
23
|
+
}
|
|
24
|
+
function defaultTimeoutMs() {
|
|
25
|
+
return scaledTimeoutMs(BASE_TIMEOUT_MS);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// packages/xmpp/src/stanza.ts
|
|
29
|
+
var StanzaWaitError = class extends Error {
|
|
30
|
+
constructor(reason, message, stanza) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.reason = reason;
|
|
33
|
+
this.stanza = stanza;
|
|
34
|
+
this.name = "StanzaWaitError";
|
|
35
|
+
}
|
|
36
|
+
reason;
|
|
37
|
+
stanza;
|
|
38
|
+
};
|
|
39
|
+
function waitForElement(entity, event, predicate, timeoutMs = defaultTimeoutMs()) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const seen = [];
|
|
42
|
+
const timer = setTimeout(() => {
|
|
43
|
+
cleanup();
|
|
44
|
+
const trail = seen.length ? `; last seen: ${seen.join(", ")}` : "; nothing arrived";
|
|
45
|
+
const status = entity.status;
|
|
46
|
+
const state = typeof status === "string" ? `; entity status: ${status}` : "";
|
|
47
|
+
reject(
|
|
48
|
+
new StanzaWaitError(
|
|
49
|
+
"timeout",
|
|
50
|
+
`no matching ${event} within ${timeoutMs}ms${trail}${state}`
|
|
51
|
+
)
|
|
52
|
+
);
|
|
53
|
+
}, timeoutMs);
|
|
54
|
+
const onElement = (el) => {
|
|
55
|
+
seen.push(describeElement(el));
|
|
56
|
+
if (seen.length > 8) seen.shift();
|
|
57
|
+
try {
|
|
58
|
+
if (predicate(el)) {
|
|
59
|
+
cleanup();
|
|
60
|
+
resolve(el);
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
cleanup();
|
|
64
|
+
reject(err);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const cleanup = () => {
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
entity.off(event, onElement);
|
|
70
|
+
};
|
|
71
|
+
entity.on(event, onElement);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function waitForStanza(entity, predicate, timeoutMs = defaultTimeoutMs()) {
|
|
75
|
+
return waitForElement(entity, "stanza", predicate, timeoutMs);
|
|
76
|
+
}
|
|
77
|
+
function describeElement(el) {
|
|
78
|
+
const type = el.getAttr("type");
|
|
79
|
+
const from = el.getAttr("from");
|
|
80
|
+
const error = type === "error" ? ` error=${stanzaErrorCondition(el)}` : "";
|
|
81
|
+
return `<${el.name}${type ? ` type=${type}` : ""}${from ? ` from=${from}` : ""}${error}>`;
|
|
82
|
+
}
|
|
83
|
+
function stanzaErrorCondition(stanza) {
|
|
84
|
+
const error = stanza.getChild("error");
|
|
85
|
+
const condition = error?.children.find((c) => typeof c !== "string");
|
|
86
|
+
return condition?.getName() ?? "unknown";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// packages/xmpp/src/muc.ts
|
|
90
|
+
var NS_MUC = "http://jabber.org/protocol/muc";
|
|
91
|
+
var NS_MUC_USER = `${NS_MUC}#user`;
|
|
92
|
+
var NS_MUC_ADMIN = `${NS_MUC}#admin`;
|
|
93
|
+
var NS_MUC_OWNER = `${NS_MUC}#owner`;
|
|
94
|
+
var NS_DATA_FORMS = "jabber:x:data";
|
|
95
|
+
var MucError = class extends Error {
|
|
96
|
+
constructor(message, condition = "unknown", stanza) {
|
|
97
|
+
super(message);
|
|
98
|
+
this.condition = condition;
|
|
99
|
+
this.stanza = stanza;
|
|
100
|
+
this.name = "MucError";
|
|
101
|
+
}
|
|
102
|
+
condition;
|
|
103
|
+
stanza;
|
|
104
|
+
};
|
|
105
|
+
var MucJoinError = class extends MucError {
|
|
106
|
+
constructor(condition, stanza, room, nick) {
|
|
107
|
+
super(`join denied for ${occupantJid(room, nick)}: ${condition}`, condition, stanza);
|
|
108
|
+
this.room = room;
|
|
109
|
+
this.nick = nick;
|
|
110
|
+
this.name = "MucJoinError";
|
|
111
|
+
}
|
|
112
|
+
room;
|
|
113
|
+
nick;
|
|
114
|
+
};
|
|
115
|
+
function joinPresenceEl(room, nick) {
|
|
116
|
+
return xml("presence", { to: occupantJid(room, nick) }, xml("x", { xmlns: NS_MUC }));
|
|
117
|
+
}
|
|
118
|
+
async function joinRoom(entity, room, nick, timeoutMs = defaultTimeoutMs()) {
|
|
119
|
+
const self = occupantJid(room, nick);
|
|
120
|
+
const wait = waitForStanza(
|
|
121
|
+
entity,
|
|
122
|
+
(s) => s.is("presence") && s.getAttr("from") === self,
|
|
123
|
+
timeoutMs
|
|
124
|
+
);
|
|
125
|
+
try {
|
|
126
|
+
await entity.send(joinPresenceEl(room, nick));
|
|
127
|
+
} catch (err) {
|
|
128
|
+
void wait.catch(() => {
|
|
129
|
+
});
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
const presence = await wait;
|
|
133
|
+
if (presence.getAttr("type") === "error") {
|
|
134
|
+
throw new MucJoinError(stanzaErrorCondition(presence), presence, room, nick);
|
|
135
|
+
}
|
|
136
|
+
return presence;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// packages/xmpp/src/origin.ts
|
|
140
|
+
import { jid } from "@xmpp/jid";
|
|
141
|
+
var NS_MUC_USER2 = "http://jabber.org/protocol/muc#user";
|
|
142
|
+
var NS_FORWARD = "urn:xmpp:forward:0";
|
|
143
|
+
var NS_WACANDO_POLICY = "urn:wacando:policy:0";
|
|
144
|
+
var origins = /* @__PURE__ */ new WeakMap();
|
|
145
|
+
function authenticatedMessageOrigin(stanza) {
|
|
146
|
+
return origins.get(stanza);
|
|
147
|
+
}
|
|
148
|
+
function itemJid(stanza) {
|
|
149
|
+
const xs = stanza.getChildren("x", NS_MUC_USER2);
|
|
150
|
+
if (xs.length !== 1) return;
|
|
151
|
+
const items = xs[0]?.getChildren("item");
|
|
152
|
+
if (items?.length !== 1) return;
|
|
153
|
+
const value = items[0]?.getAttr("jid");
|
|
154
|
+
return typeof value === "string" ? value : void 0;
|
|
155
|
+
}
|
|
156
|
+
function userBareJid(value) {
|
|
157
|
+
if (value === void 0) return;
|
|
158
|
+
try {
|
|
159
|
+
const parsed = jid(value);
|
|
160
|
+
return parsed.getLocal() ? parsed.bare().toString() : void 0;
|
|
161
|
+
} catch {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function isTrustedRelay(value, trustedRelayJid) {
|
|
166
|
+
if (value === void 0 || trustedRelayJid === void 0) return false;
|
|
167
|
+
try {
|
|
168
|
+
const parsed = jid(value);
|
|
169
|
+
return !parsed.getLocal() && parsed.getDomain() === trustedRelayJid;
|
|
170
|
+
} catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function relaySender(stanza) {
|
|
175
|
+
const relays = stanza.getChildren("relay", NS_WACANDO_POLICY);
|
|
176
|
+
if (relays.length !== 1) return;
|
|
177
|
+
return userBareJid(relays[0]?.getAttr("jid"));
|
|
178
|
+
}
|
|
179
|
+
function recordMamOrigin(stanza, room, trustedRelayJid) {
|
|
180
|
+
if (!stanza.is("message") || stanza.getAttr("type") !== "groupchat" || !stanza.getAttr("from")?.startsWith(`${room}/`))
|
|
181
|
+
return;
|
|
182
|
+
const item = itemJid(stanza);
|
|
183
|
+
const senderBareJid = isTrustedRelay(item, trustedRelayJid) ? relaySender(stanza) : userBareJid(item);
|
|
184
|
+
if (senderBareJid) origins.set(stanza, { roomJid: room, senderBareJid });
|
|
185
|
+
}
|
|
186
|
+
function unwrapPolicyDelivery(stanza, policyJid) {
|
|
187
|
+
if (!stanza.is("message") || stanza.getAttr("type") === "error") return;
|
|
188
|
+
const from = stanza.getAttr("from");
|
|
189
|
+
if (typeof from !== "string" || !isTrustedRelay(from, policyJid)) return;
|
|
190
|
+
const deliveries = stanza.getChildren("delivery", NS_WACANDO_POLICY);
|
|
191
|
+
if (deliveries.length !== 1) return;
|
|
192
|
+
const delivery = deliveries[0];
|
|
193
|
+
const room = delivery?.getAttr("room");
|
|
194
|
+
const senderBareJid = userBareJid(delivery?.getAttr("sender"));
|
|
195
|
+
const inner = delivery?.getChild("forwarded", NS_FORWARD)?.getChild("message");
|
|
196
|
+
if (typeof room !== "string" || !senderBareJid || !inner) return;
|
|
197
|
+
if (inner.getAttr("type") !== "groupchat" || !inner.getAttr("from")?.startsWith(`${room}/`))
|
|
198
|
+
return;
|
|
199
|
+
origins.set(inner, { roomJid: room, senderBareJid });
|
|
200
|
+
return inner;
|
|
201
|
+
}
|
|
202
|
+
var MucOriginTracker = class {
|
|
203
|
+
/**
|
|
204
|
+
* `trustedRelayJid`: the policy component domain. When set, groupchat from
|
|
205
|
+
* an occupant whose real JID is exactly that domain is attributed to its
|
|
206
|
+
* `<relay jid>` (#39). Without it, relays carry no origin (fail closed).
|
|
207
|
+
*/
|
|
208
|
+
constructor(entity, options = {}) {
|
|
209
|
+
this.entity = entity;
|
|
210
|
+
this.options = options;
|
|
211
|
+
entity.on("stanza", this.onStanza);
|
|
212
|
+
entity.on("offline", this.reset);
|
|
213
|
+
entity.on("disconnect", this.reset);
|
|
214
|
+
}
|
|
215
|
+
entity;
|
|
216
|
+
options;
|
|
217
|
+
rooms = /* @__PURE__ */ new Set();
|
|
218
|
+
occupants = /* @__PURE__ */ new Map();
|
|
219
|
+
/** Occupant full JIDs whose authenticated real JID is the policy component. */
|
|
220
|
+
relays = /* @__PURE__ */ new Set();
|
|
221
|
+
trackRoom(room) {
|
|
222
|
+
this.rooms.add(room);
|
|
223
|
+
}
|
|
224
|
+
reset = () => {
|
|
225
|
+
this.occupants.clear();
|
|
226
|
+
this.relays.clear();
|
|
227
|
+
};
|
|
228
|
+
onStanza = (stanza) => {
|
|
229
|
+
const from = stanza.getAttr("from");
|
|
230
|
+
if (typeof from !== "string") return;
|
|
231
|
+
const slash = from.indexOf("/");
|
|
232
|
+
if (slash < 1 || !this.rooms.has(from.slice(0, slash))) return;
|
|
233
|
+
if (stanza.is("presence")) {
|
|
234
|
+
const type = stanza.getAttr("type");
|
|
235
|
+
if (type === "unavailable" || type === "error") {
|
|
236
|
+
this.occupants.delete(from);
|
|
237
|
+
this.relays.delete(from);
|
|
238
|
+
if (stanza.getChild("x", NS_MUC_USER2)?.getChildren("status").some((s) => s.getAttr("code") === "110")) {
|
|
239
|
+
const prefix = `${from.slice(0, slash)}/`;
|
|
240
|
+
for (const occupant of this.occupants.keys()) {
|
|
241
|
+
if (occupant.startsWith(prefix)) this.occupants.delete(occupant);
|
|
242
|
+
}
|
|
243
|
+
for (const relay of [...this.relays]) {
|
|
244
|
+
if (relay.startsWith(prefix)) this.relays.delete(relay);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} else if (!type) {
|
|
248
|
+
const item = itemJid(stanza);
|
|
249
|
+
const sender = userBareJid(item);
|
|
250
|
+
if (sender) this.occupants.set(from, sender);
|
|
251
|
+
else this.occupants.delete(from);
|
|
252
|
+
if (isTrustedRelay(item, this.options.trustedRelayJid)) this.relays.add(from);
|
|
253
|
+
else this.relays.delete(from);
|
|
254
|
+
}
|
|
255
|
+
} else if (stanza.is("message") && stanza.getAttr("type") === "groupchat" && !stanza.getChild("delay", "urn:xmpp:delay")) {
|
|
256
|
+
const senderBareJid = this.relays.has(from) ? relaySender(stanza) : this.occupants.get(from);
|
|
257
|
+
if (senderBareJid) origins.set(stanza, { roomJid: from.slice(0, slash), senderBareJid });
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
close() {
|
|
261
|
+
this.entity.off("stanza", this.onStanza);
|
|
262
|
+
this.entity.off("offline", this.reset);
|
|
263
|
+
this.entity.off("disconnect", this.reset);
|
|
264
|
+
this.reset();
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// packages/xmpp/src/mam.ts
|
|
269
|
+
import xml2 from "@xmpp/xml";
|
|
270
|
+
var NS_MAM = "urn:xmpp:mam:2";
|
|
271
|
+
var NS_FORWARD2 = "urn:xmpp:forward:0";
|
|
272
|
+
var NS_RSM = "http://jabber.org/protocol/rsm";
|
|
273
|
+
var queryCounter = 0;
|
|
274
|
+
function filterField(varName, value) {
|
|
275
|
+
return xml2("field", { var: varName }, xml2("value", {}, value));
|
|
276
|
+
}
|
|
277
|
+
function mamQueryEl(options) {
|
|
278
|
+
const fields = [xml2("field", { var: "FORM_TYPE", type: "hidden" }, xml2("value", {}, NS_MAM))];
|
|
279
|
+
if (options.withJid !== void 0) fields.push(filterField("with", options.withJid));
|
|
280
|
+
if (options.start !== void 0) fields.push(filterField("start", options.start));
|
|
281
|
+
if (options.end !== void 0) fields.push(filterField("end", options.end));
|
|
282
|
+
const children = [xml2("x", { xmlns: NS_DATA_FORMS, type: "submit" }, ...fields)];
|
|
283
|
+
if (options.max !== void 0) {
|
|
284
|
+
children.push(xml2("set", { xmlns: NS_RSM }, xml2("max", {}, String(options.max))));
|
|
285
|
+
}
|
|
286
|
+
return xml2("query", { xmlns: NS_MAM, queryid: options.queryId }, ...children);
|
|
287
|
+
}
|
|
288
|
+
async function queryMam(entity, options, timeoutMs = defaultTimeoutMs()) {
|
|
289
|
+
const queryId = options.queryId ?? `mam${++queryCounter}`;
|
|
290
|
+
const forwarded = [];
|
|
291
|
+
const onStanza = (stanza) => {
|
|
292
|
+
if (!stanza.is("message") || stanza.getAttr("from") !== options.to) return;
|
|
293
|
+
const result = stanza.getChild("result", NS_MAM);
|
|
294
|
+
if (!result || result.getAttr("queryid") !== queryId) return;
|
|
295
|
+
const fwd = result.getChild("forwarded", NS_FORWARD2);
|
|
296
|
+
if (fwd) {
|
|
297
|
+
const inner = forwardedStanza(fwd);
|
|
298
|
+
if (inner) recordMamOrigin(inner, options.to, options.trustedRelayJid);
|
|
299
|
+
forwarded.push(fwd);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
entity.on("stanza", onStanza);
|
|
303
|
+
try {
|
|
304
|
+
const response = await entity.iqCaller.set(
|
|
305
|
+
mamQueryEl({ ...options, queryId }),
|
|
306
|
+
options.to,
|
|
307
|
+
timeoutMs
|
|
308
|
+
);
|
|
309
|
+
const fin = response?.getChild("fin", NS_MAM);
|
|
310
|
+
const rsmLast = fin?.getChild("set", NS_RSM)?.getChildText("last");
|
|
311
|
+
return {
|
|
312
|
+
queryId,
|
|
313
|
+
complete: fin?.getAttr("complete") === "true",
|
|
314
|
+
lastId: typeof rsmLast === "string" ? rsmLast : void 0,
|
|
315
|
+
messages: forwarded
|
|
316
|
+
};
|
|
317
|
+
} finally {
|
|
318
|
+
entity.off("stanza", onStanza);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function forwardedStanza(forwarded) {
|
|
322
|
+
return forwarded.children.find(
|
|
323
|
+
(c) => typeof c !== "string" && c.getName() !== "delay"
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// packages/xmpp/src/roster.ts
|
|
328
|
+
import { jid as parseJid } from "@xmpp/jid";
|
|
329
|
+
import xml3 from "@xmpp/xml";
|
|
330
|
+
function subscriptionRefusal(stanza) {
|
|
331
|
+
if (!stanza.is("presence") || stanza.getAttr("type") !== "subscribe") return null;
|
|
332
|
+
const from = stanza.getAttr("from");
|
|
333
|
+
if (typeof from !== "string" || from === "") return null;
|
|
334
|
+
let bare;
|
|
335
|
+
try {
|
|
336
|
+
bare = parseJid(from).bare().toString();
|
|
337
|
+
} catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
return xml3("presence", { to: bare, type: "unsubscribed" });
|
|
341
|
+
}
|
|
342
|
+
function refuseSubscriptionRequests(entity) {
|
|
343
|
+
const onStanza = (stanza) => {
|
|
344
|
+
const refusal = subscriptionRefusal(stanza);
|
|
345
|
+
if (refusal) void entity.send(refusal).catch(() => {
|
|
346
|
+
});
|
|
347
|
+
};
|
|
348
|
+
entity.on("stanza", onStanza);
|
|
349
|
+
return () => entity.off("stanza", onStanza);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// packages/xmpp/src/presence.ts
|
|
353
|
+
var UNKNOWN_PRESENCE = Object.freeze({
|
|
354
|
+
connection: "unknown",
|
|
355
|
+
availability: null
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// packages/xmpp/src/pubsub.ts
|
|
359
|
+
import xml4 from "@xmpp/xml";
|
|
360
|
+
var NS_PUBSUB = "http://jabber.org/protocol/pubsub";
|
|
361
|
+
var NS_PUBSUB_PUBLISH_OPTIONS = "http://jabber.org/protocol/pubsub#publish-options";
|
|
362
|
+
function pepPublishEl(node, itemId, payload, accessModel = "open") {
|
|
363
|
+
return xml4(
|
|
364
|
+
"pubsub",
|
|
365
|
+
{ xmlns: NS_PUBSUB },
|
|
366
|
+
xml4("publish", { node }, xml4("item", { id: itemId }, payload)),
|
|
367
|
+
xml4(
|
|
368
|
+
"publish-options",
|
|
369
|
+
{},
|
|
370
|
+
xml4(
|
|
371
|
+
"x",
|
|
372
|
+
{ xmlns: NS_DATA_FORMS, type: "submit" },
|
|
373
|
+
xml4(
|
|
374
|
+
"field",
|
|
375
|
+
{ var: "FORM_TYPE", type: "hidden" },
|
|
376
|
+
xml4("value", {}, NS_PUBSUB_PUBLISH_OPTIONS)
|
|
377
|
+
),
|
|
378
|
+
xml4("field", { var: "pubsub#access_model" }, xml4("value", {}, accessModel))
|
|
379
|
+
)
|
|
380
|
+
)
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
function pubsubItemsEl(node, maxItems) {
|
|
384
|
+
const attrs = { node };
|
|
385
|
+
if (maxItems !== void 0) attrs.max_items = String(maxItems);
|
|
386
|
+
return xml4("pubsub", { xmlns: NS_PUBSUB }, xml4("items", attrs));
|
|
387
|
+
}
|
|
388
|
+
async function pepPublish(entity, to, node, itemId, payload, timeoutMs = defaultTimeoutMs()) {
|
|
389
|
+
await entity.iqCaller.set(pepPublishEl(node, itemId, payload), to, timeoutMs);
|
|
390
|
+
}
|
|
391
|
+
async function pepFetchItems(entity, to, node, timeoutMs = defaultTimeoutMs()) {
|
|
392
|
+
try {
|
|
393
|
+
const result = await entity.iqCaller.get(pubsubItemsEl(node), to, timeoutMs);
|
|
394
|
+
const pubsub = result?.getName() === "pubsub" ? result : result?.getChild("pubsub", NS_PUBSUB);
|
|
395
|
+
const items = pubsub?.getChild("items");
|
|
396
|
+
if (!items) return [];
|
|
397
|
+
return items.getChildren("item").map((item) => item.children.find((c) => typeof c !== "string")).filter((el) => el !== void 0);
|
|
398
|
+
} catch (err) {
|
|
399
|
+
if (isItemNotFound(err)) return [];
|
|
400
|
+
throw err;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function isItemNotFound(err) {
|
|
404
|
+
const cond = err?.condition;
|
|
405
|
+
if (cond === "item-not-found") return true;
|
|
406
|
+
const stanza = err?.stanza;
|
|
407
|
+
return stanza?.getChild("error")?.getChild("item-not-found") !== void 0;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export {
|
|
411
|
+
defaultTimeoutMs,
|
|
412
|
+
waitForStanza,
|
|
413
|
+
MucJoinError,
|
|
414
|
+
joinRoom,
|
|
415
|
+
authenticatedMessageOrigin,
|
|
416
|
+
unwrapPolicyDelivery,
|
|
417
|
+
MucOriginTracker,
|
|
418
|
+
queryMam,
|
|
419
|
+
forwardedStanza,
|
|
420
|
+
pepPublish,
|
|
421
|
+
pepFetchItems,
|
|
422
|
+
refuseSubscriptionRequests
|
|
423
|
+
};
|