@wacando/agent 2.1.0 → 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.
@@ -1,7 +1,9 @@
1
1
  import { OnboardingService } from '../onboarding/service.mjs';
2
- import { CredentialStore, SafeError } from '../onboarding/storage.mjs';
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
- constructor({ host = 'cli', origin, store = new CredentialStore(), report, request, connect, onStanza } = {}) {
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
- client = new ConversationClient({ runtime: this, agent, session });
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,7 +1,7 @@
1
- // Embedded from lbframe/wacando@9f82881d4ee6cfd655727a08883877230917c955 by scripts/embed-wacando.mjs. Do not edit; see provenance.json.
1
+ // Embedded from lbframe/wacando@618922ecaf2213da18949a8b27d343c8767b64c5 by scripts/embed-wacando.mjs. Do not edit; see provenance.json.
2
2
  import {
3
3
  XMPP_DOMAIN
4
- } from "./chunk-EIGFLZ2Y.mjs";
4
+ } from "./chunk-PZDQXGA6.mjs";
5
5
 
6
6
  // packages/xmpp/src/muc.ts
7
7
  import xml from "@xmpp/xml";
@@ -408,6 +408,7 @@ function isItemNotFound(err) {
408
408
  }
409
409
 
410
410
  export {
411
+ defaultTimeoutMs,
411
412
  waitForStanza,
412
413
  MucJoinError,
413
414
  joinRoom,
@@ -1,7 +1,7 @@
1
- // Embedded from lbframe/wacando@9f82881d4ee6cfd655727a08883877230917c955 by scripts/embed-wacando.mjs. Do not edit; see provenance.json.
1
+ // Embedded from lbframe/wacando@618922ecaf2213da18949a8b27d343c8767b64c5 by scripts/embed-wacando.mjs. Do not edit; see provenance.json.
2
2
  import {
3
3
  DomainError
4
- } from "./chunk-EIGFLZ2Y.mjs";
4
+ } from "./chunk-PZDQXGA6.mjs";
5
5
 
6
6
  // packages/omemo/src/ns.ts
7
7
  var NS_OMEMO = "eu.siacs.conversations.axolotl";
@@ -0,0 +1,74 @@
1
+ // Embedded from lbframe/wacando@618922ecaf2213da18949a8b27d343c8767b64c5 by scripts/embed-wacando.mjs. Do not edit; see provenance.json.
2
+
3
+ // packages/contracts/src/errors.ts
4
+ var DomainError = class extends Error {
5
+ code;
6
+ constructor(code, message) {
7
+ super(message);
8
+ this.name = "DomainError";
9
+ this.code = code;
10
+ }
11
+ };
12
+
13
+ // packages/contracts/src/files.ts
14
+ var ALLOWED_MEDIA_TYPES = [
15
+ "text/plain",
16
+ "text/markdown",
17
+ "application/pdf",
18
+ "application/zip",
19
+ "application/gzip"
20
+ ];
21
+ var MAX_FILE_BYTES = 10 * 1024 * 1024;
22
+ var MIN_FILE_BYTES = 16;
23
+ var TEXT_HEAD_MIN_BYTES = 32;
24
+ function startsWith(bytes, sig) {
25
+ return bytes.length >= sig.length && sig.every((b, i) => bytes[i] === b);
26
+ }
27
+ function isGzipHeader(bytes) {
28
+ if (bytes.length < 10 || !startsWith(bytes, [31, 139, 8])) return false;
29
+ const flg = bytes[3];
30
+ const xfl = bytes[8];
31
+ const os = bytes[9];
32
+ return (flg & 224) === 0 && [0, 2, 4].includes(xfl) && (os <= 13 || os === 255);
33
+ }
34
+ function isTextHead(bytes) {
35
+ const head = bytes.subarray(0, Math.min(bytes.length, 512));
36
+ if (head.length < TEXT_HEAD_MIN_BYTES) return false;
37
+ const cuts = head.length < bytes.length ? [0, 1, 2, 3] : [0];
38
+ for (const cut of cuts) {
39
+ let text;
40
+ try {
41
+ text = new TextDecoder("utf-8", { fatal: true }).decode(head.subarray(0, head.length - cut));
42
+ } catch {
43
+ continue;
44
+ }
45
+ return !/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(text);
46
+ }
47
+ return false;
48
+ }
49
+ function looksLikePlaintext(bytes) {
50
+ return startsWith(bytes, [37, 80, 68, 70, 45]) || // %PDF-
51
+ startsWith(bytes, [80, 75, 3, 4]) || // zip local file header
52
+ startsWith(bytes, [80, 75, 5, 6]) || // empty zip
53
+ isGzipHeader(bytes) || isTextHead(bytes);
54
+ }
55
+
56
+ // packages/contracts/src/internal.ts
57
+ function xmppDomain() {
58
+ const env = globalThis.process?.env;
59
+ return env?.XMPP_DOMAIN ?? "wacando.localhost";
60
+ }
61
+ var XMPP_DOMAIN = xmppDomain();
62
+ var XMPP_MUC_DOMAIN = `conference.${XMPP_DOMAIN}`;
63
+
64
+ // packages/contracts/src/message.ts
65
+ var MAX_MESSAGE_PLAINTEXT_BYTES = 64 * 1024;
66
+
67
+ export {
68
+ DomainError,
69
+ ALLOWED_MEDIA_TYPES,
70
+ MAX_FILE_BYTES,
71
+ MIN_FILE_BYTES,
72
+ looksLikePlaintext,
73
+ XMPP_DOMAIN
74
+ };
@@ -1,4 +1,4 @@
1
- // Embedded from lbframe/wacando@9f82881d4ee6cfd655727a08883877230917c955 by scripts/embed-wacando.mjs. Do not edit; see provenance.json.
1
+ // Embedded from lbframe/wacando@618922ecaf2213da18949a8b27d343c8767b64c5 by scripts/embed-wacando.mjs. Do not edit; see provenance.json.
2
2
  import {
3
3
  BODY_MARKER,
4
4
  GCM_IV_BYTES,
@@ -29,8 +29,8 @@ import {
29
29
  signerJid,
30
30
  signerRole,
31
31
  targetJid
32
- } from "./chunks/chunk-OI3RF7TS.mjs";
33
- import "./chunks/chunk-EIGFLZ2Y.mjs";
32
+ } from "./chunks/chunk-PHGOH4AU.mjs";
33
+ import "./chunks/chunk-PZDQXGA6.mjs";
34
34
 
35
35
  // packages/omemo/src/policy-wire.ts
36
36
  import xml from "@xmpp/xml";