@sockethub/platform-carddav 1.0.0-beta.0 → 1.0.0-beta.2

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
@@ -5,8 +5,25 @@ creating, updating, and deleting vCard contacts.
5
5
 
6
6
  The platform accepts public HTTPS services by default. Administrators may opt
7
7
  in to HTTP or private-network services with `allowInsecureHttp` and
8
- `allowPrivateAddresses`. Authentication supports Basic, Digest, or a
9
- caller-provided Bearer token through the standard Sockethub credentials flow.
8
+ `allowPrivateAddresses`. Address checks use the shared outbound URL guard,
9
+ including IPv6 embeddings of private ranges and the local-use NAT64 prefix
10
+ `64:ff9b:1::/48`. See
11
+ [Outbound URL guard](../../docs/configuration.md#outbound-url-guard).
12
+ Authentication supports Basic, Digest, or a caller-provided Bearer token
13
+ through the standard Sockethub credentials flow.
14
+
15
+ ## Discovery cache
16
+
17
+ Each `query`, `create`, `update`, and `delete` needs the account's address book list to
18
+ check the target. The platform caches that discovery result in memory for
19
+ `packageConfig.discoveryCacheTtlMs` (default `300000`, 5 minutes; `0`
20
+ disables the cache). Entries are keyed by an HMAC of the protocol, service URL,
21
+ and credentials. The HMAC uses a per-process random secret, and only
22
+ collection metadata is stored. A `fetch` always discovers fresh. Within the TTL,
23
+ an address book added on the server is not visible to targeted actions, and a removed
24
+ one is still accepted as a target (the server then rejects the request). An
25
+ authentication, not-found, or unknown-address-book error drops the entry, so the next
26
+ request rediscovers. The cache is lost when the platform process restarts.
10
27
 
11
28
  ## Authentication
12
29
 
package/dist/index.d.ts CHANGED
@@ -2,7 +2,10 @@ import type { ActivityStream, PlatformCallback, PlatformInterface, PlatformSchem
2
2
  import type { CardDavCredentials } from "./types.js";
3
3
  export default class CardDav implements PlatformInterface {
4
4
  private readonly log;
5
- config: StatelessPlatformConfig;
5
+ private readonly discovery;
6
+ config: StatelessPlatformConfig & {
7
+ discoveryCacheTtlMs: number;
8
+ };
6
9
  constructor(session: PlatformSession);
7
10
  get schema(): PlatformSchemaStruct;
8
11
  isInitialized(): boolean;
@@ -13,6 +16,7 @@ export default class CardDav implements PlatformInterface {
13
16
  update(job: ActivityStream, credentials: CardDavCredentials, done: PlatformCallback): void;
14
17
  delete(job: ActivityStream, credentials: CardDavCredentials, done: PlatformCallback): void;
15
18
  private client;
19
+ private discoveryKey;
16
20
  private book;
17
21
  private mutation;
18
22
  private fail;
package/dist/index.js CHANGED
@@ -15762,7 +15762,7 @@ var require_ajv_formats_draft2019 = __commonJS(function(exports, module) {
15762
15762
 
15763
15763
  // ../dav/dist/index.js
15764
15764
  import { createRequire as createRequire2 } from "node:module";
15765
- import { createHash, randomBytes } from "node:crypto";
15765
+ import { createHash, createHmac, randomBytes } from "node:crypto";
15766
15766
  import { lookup as dnsLookup } from "node:dns";
15767
15767
  import { isIP } from "node:net";
15768
15768
  var __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
@@ -39185,7 +39185,7 @@ function createGuardedLookup(allowPrivateAddresses) {
39185
39185
  if (!allowPrivateAddresses) {
39186
39186
  for (const { address } of addresses) {
39187
39187
  if (isBlockedAddress(address)) {
39188
- callback(new Error(`blocked non-public address ${address} for ${hostname}`));
39188
+ callback(new Error(`blocked non-public address for ${hostname}`));
39189
39189
  return;
39190
39190
  }
39191
39191
  }
@@ -42746,6 +42746,82 @@ function isDavCollectionChild(collectionId, resourceId) {
42746
42746
  return false;
42747
42747
  }
42748
42748
  }
42749
+
42750
+ class DiscoveryCache {
42751
+ maxEntries;
42752
+ secret = randomBytes(32);
42753
+ entries = new Map;
42754
+ pending = new Map;
42755
+ timer;
42756
+ timerAt = Number.POSITIVE_INFINITY;
42757
+ constructor(maxEntries = 1000) {
42758
+ this.maxEntries = maxEntries;
42759
+ }
42760
+ key(...identity) {
42761
+ return createHmac("sha256", this.secret).update(JSON.stringify(identity)).digest("hex");
42762
+ }
42763
+ async get(key, ttlMs, discover) {
42764
+ if (!(ttlMs > 0))
42765
+ return discover();
42766
+ const hit = this.entries.get(key);
42767
+ if (hit && hit.expires > Date.now())
42768
+ return hit.value;
42769
+ const inFlight = this.pending.get(key);
42770
+ if (inFlight)
42771
+ return inFlight;
42772
+ const promise = discover();
42773
+ this.pending.set(key, promise);
42774
+ try {
42775
+ const value = await promise;
42776
+ if (this.pending.get(key) === promise)
42777
+ this.store(key, value, Date.now() + ttlMs);
42778
+ return value;
42779
+ } finally {
42780
+ if (this.pending.get(key) === promise)
42781
+ this.pending.delete(key);
42782
+ }
42783
+ }
42784
+ delete(key) {
42785
+ this.entries.delete(key);
42786
+ this.pending.delete(key);
42787
+ }
42788
+ get size() {
42789
+ return this.entries.size;
42790
+ }
42791
+ store(key, value, expires) {
42792
+ this.entries.delete(key);
42793
+ if (this.entries.size >= this.maxEntries) {
42794
+ this.sweep();
42795
+ if (this.entries.size >= this.maxEntries)
42796
+ this.entries.delete(this.entries.keys().next().value);
42797
+ }
42798
+ this.entries.set(key, { expires, value });
42799
+ this.schedule(expires);
42800
+ }
42801
+ sweep() {
42802
+ const now = Date.now();
42803
+ for (const [key, entry] of this.entries)
42804
+ if (entry.expires <= now)
42805
+ this.entries.delete(key);
42806
+ }
42807
+ schedule(at) {
42808
+ if (this.timer && at >= this.timerAt)
42809
+ return;
42810
+ clearTimeout(this.timer);
42811
+ this.timerAt = at;
42812
+ this.timer = setTimeout(() => {
42813
+ this.timer = undefined;
42814
+ this.timerAt = Number.POSITIVE_INFINITY;
42815
+ this.sweep();
42816
+ let next = Number.POSITIVE_INFINITY;
42817
+ for (const entry of this.entries.values())
42818
+ next = Math.min(next, entry.expires);
42819
+ if (next !== Number.POSITIVE_INFINITY)
42820
+ this.schedule(next);
42821
+ }, Math.min(Math.max(0, at - Date.now()), 2147483647));
42822
+ this.timer.unref?.();
42823
+ }
42824
+ }
42749
42825
  function unquote(value) {
42750
42826
  return value.startsWith('"') ? value.slice(1, -1).replace(/\\(.)/g, "$1") : value;
42751
42827
  }
@@ -43199,7 +43275,7 @@ var ActivityStreamSchema = {
43199
43275
  var package_default = {
43200
43276
  name: "@sockethub/schemas",
43201
43277
  description: "JSON schema files for validating Sockethub Activity Streams",
43202
- version: "3.0.0-beta.0",
43278
+ version: "3.0.0-beta.2",
43203
43279
  type: "module",
43204
43280
  private: false,
43205
43281
  author: "Nick Jennings <nick@silverbucket.net>",
@@ -43269,7 +43345,7 @@ var package_default = {
43269
43345
  "ajv-formats": "^2.1.1",
43270
43346
  "ajv-formats-draft2019": "^1.6.1"
43271
43347
  },
43272
- gitHead: "75002ae8f15d7679bd58a9327f5a1d7eeaffa237",
43348
+ gitHead: "1f74d4e07311496f0acc021855137bc197d79eab",
43273
43349
  types: "./dist/index.d.ts"
43274
43350
  };
43275
43351
 
@@ -43374,8 +43450,11 @@ var SockethubConfigSchema = {
43374
43450
  "@sockethub/platform-caldav",
43375
43451
  "@sockethub/platform-dummy",
43376
43452
  "@sockethub/platform-feeds",
43453
+ "@sockethub/platform-email",
43377
43454
  "@sockethub/platform-irc",
43378
43455
  "@sockethub/platform-metadata",
43456
+ "@sockethub/platform-messenger",
43457
+ "@sockethub/platform-instagram",
43379
43458
  "@sockethub/platform-xmpp"
43380
43459
  ]
43381
43460
  },
@@ -43391,7 +43470,8 @@ var SockethubConfigSchema = {
43391
43470
  connectTimeoutMs: { type: "integer", minimum: 1 },
43392
43471
  allowPrivateAddresses: { type: "boolean" },
43393
43472
  allowInsecureHttp: { type: "boolean" },
43394
- concurrency: { type: "integer", minimum: 1 }
43473
+ concurrency: { type: "integer", minimum: 1 },
43474
+ discoveryCacheTtlMs: { type: "integer", minimum: 0 }
43395
43475
  }
43396
43476
  },
43397
43477
  "@sockethub/platform-caldav": {
@@ -43401,7 +43481,20 @@ var SockethubConfigSchema = {
43401
43481
  connectTimeoutMs: { type: "integer", minimum: 1 },
43402
43482
  allowPrivateAddresses: { type: "boolean" },
43403
43483
  allowInsecureHttp: { type: "boolean" },
43404
- concurrency: { type: "integer", minimum: 1 }
43484
+ concurrency: { type: "integer", minimum: 1 },
43485
+ discoveryCacheTtlMs: { type: "integer", minimum: 0 }
43486
+ }
43487
+ },
43488
+ "@sockethub/platform-email": {
43489
+ type: "object",
43490
+ additionalProperties: false,
43491
+ properties: {
43492
+ connectTimeoutMs: {
43493
+ type: "integer",
43494
+ minimum: 5000,
43495
+ maximum: 120000
43496
+ },
43497
+ allowPrivateAddresses: { type: "boolean" }
43405
43498
  }
43406
43499
  },
43407
43500
  "@sockethub/platform-dummy": {
@@ -43424,14 +43517,16 @@ var SockethubConfigSchema = {
43424
43517
  type: "object",
43425
43518
  additionalProperties: false,
43426
43519
  properties: {
43427
- connectTimeoutMs: { type: "number" }
43520
+ connectTimeoutMs: { type: "number" },
43521
+ allowPrivateAddresses: { type: "boolean" }
43428
43522
  }
43429
43523
  },
43430
43524
  "@sockethub/platform-xmpp": {
43431
43525
  type: "object",
43432
43526
  additionalProperties: false,
43433
43527
  properties: {
43434
- connectTimeoutMs: { type: "number" }
43528
+ connectTimeoutMs: { type: "number" },
43529
+ allowPrivateAddresses: { type: "boolean" }
43435
43530
  }
43436
43531
  },
43437
43532
  "@sockethub/platform-metadata": {
@@ -43521,11 +43616,29 @@ var SockethubConfigSchema = {
43521
43616
  type: "object",
43522
43617
  additionalProperties: false,
43523
43618
  properties: {
43619
+ maxInstancesPerSession: {
43620
+ type: "integer",
43621
+ minimum: 0,
43622
+ default: 10,
43623
+ description: "Maximum persistent instances per socket session. 0 disables the cap."
43624
+ },
43625
+ maxInstancesPerIp: {
43626
+ type: "integer",
43627
+ minimum: 0,
43628
+ default: 20,
43629
+ description: "Maximum persistent instances per client IP, including HTTP actions. 0 disables the cap."
43630
+ },
43631
+ maxStartsPerIpPerMinute: {
43632
+ type: "integer",
43633
+ minimum: 0,
43634
+ default: 60,
43635
+ description: "Maximum new persistent instances a client IP may start per minute, " + "including HTTP actions. Bounds fork/teardown churn that the " + "concurrent caps allow. Reusing a running instance does not count. " + "0 disables the cap."
43636
+ },
43524
43637
  maxPlatformInstances: {
43525
43638
  type: "number",
43526
43639
  minimum: 0,
43527
43640
  default: 100,
43528
- description: "Upper bound on concurrently running platform instances " + "(child processes). Each persistent-platform actor forks " + "its own process, so an unbounded count is a resource " + "exhaustion risk on public instances. 0 disables the cap."
43641
+ description: "Upper bound on concurrently running persistent platform instances " + "(child processes). Each persistent-platform actor forks " + "its own process, so an unbounded count is a resource " + "exhaustion risk on public instances. 0 disables the cap."
43529
43642
  }
43530
43643
  }
43531
43644
  },
@@ -43706,33 +43819,66 @@ var SockethubConfigSchema = {
43706
43819
  };
43707
43820
  var SockethubConfigSchemaId = versionedSchemaId(SockethubConfigSchema.$id, package_default.version);
43708
43821
 
43709
- // ../schemas/src/service-descriptor.ts
43710
- var import_ajv = __toESM(require_ajv(), 1);
43711
- var ajv = new import_ajv.default({ strictTypes: false, allErrors: true });
43822
+ // ../schemas/src/helpers/codegen.ts
43823
+ var available;
43824
+ var warned = false;
43825
+ function isCodegenAvailable() {
43826
+ if (available === undefined) {
43827
+ try {
43828
+ new Function("return true");
43829
+ available = true;
43830
+ } catch {
43831
+ available = false;
43832
+ }
43833
+ }
43834
+ return available;
43835
+ }
43836
+ function warnCodegenUnavailable() {
43837
+ if (warned) {
43838
+ return;
43839
+ }
43840
+ warned = true;
43841
+ console.warn("[@sockethub/schemas] Schema validation is disabled: this environment's Content Security Policy blocks `new Function`, which Ajv needs to compile schemas. The Sockethub server still validates every message.");
43842
+ }
43712
43843
 
43713
43844
  // ../schemas/src/validator.ts
43714
- var import_ajv2 = __toESM(require_ajv(), 1);
43845
+ var import_ajv = __toESM(require_ajv(), 1);
43715
43846
  var import_ajv_formats = __toESM(require_dist(), 1);
43716
43847
  var import_ajv_formats_draft2019 = __toESM(require_ajv_formats_draft2019(), 1);
43717
- var ajv2 = new import_ajv2.default({ strictTypes: false, allErrors: true });
43718
- import_ajv_formats.default(ajv2);
43719
- import_ajv_formats_draft2019.default(ajv2);
43720
43848
  var schemaURL = "https://sockethub.org/schemas/v0";
43721
43849
  var schemas = {};
43722
43850
  var systemContextsRegistered = false;
43723
43851
  schemas[`${schemaURL}/activity-stream`] = ActivityStreamSchema;
43724
- for (const uri in schemas) {
43725
- ajv2.addSchema(schemas[uri], uri);
43852
+ var ajv;
43853
+ function getAjv() {
43854
+ if (!isCodegenAvailable()) {
43855
+ warnCodegenUnavailable();
43856
+ return;
43857
+ }
43858
+ if (!ajv) {
43859
+ const instance = new import_ajv.default({ strictTypes: false, allErrors: true });
43860
+ import_ajv_formats.default(instance);
43861
+ import_ajv_formats_draft2019.default(instance);
43862
+ for (const uri in schemas) {
43863
+ instance.addSchema(schemas[uri], uri);
43864
+ }
43865
+ ajv = instance;
43866
+ }
43867
+ return ajv;
43726
43868
  }
43727
43869
  function registerSystemContext(platformId, contextUrl) {
43728
43870
  registerPlatformContext(platformId, contextUrl, `${schemaURL}/context/${platformId}/messages`, `${schemaURL}/context/${platformId}/credentials`);
43871
+ const instance = getAjv();
43872
+ if (!instance) {
43873
+ return;
43874
+ }
43729
43875
  const messageSchemaRef = `${schemaURL}/context/${platformId}/messages`;
43730
- if (!ajv2.getSchema(messageSchemaRef)) {
43731
- ajv2.addSchema({}, messageSchemaRef);
43876
+ if (!instance.getSchema(messageSchemaRef)) {
43877
+ instance.addSchema({}, messageSchemaRef);
43732
43878
  }
43733
43879
  const credentialsSchemaRef = `${schemaURL}/context/${platformId}/credentials`;
43734
- if (!ajv2.getSchema(credentialsSchemaRef)) {
43735
- ajv2.addSchema({}, credentialsSchemaRef);
43880
+ if (!instance.getSchema(credentialsSchemaRef)) {
43881
+ instance.addSchema({}, credentialsSchemaRef);
43736
43882
  }
43737
43883
  }
43738
43884
  function registerSystemPlatformContexts() {
@@ -43796,8 +43942,10 @@ function splitEscaped(value, separator) {
43796
43942
  result.push(current);
43797
43943
  return result;
43798
43944
  }
43799
- var unescapeText = (value) => value.replace(/\\n/gi, `
43800
- `).replace(/\\,/g, ",").replace(/\\;/g, ";").replace(/\\\\/g, "\\");
43945
+ function unescapeText(value) {
43946
+ return value.replaceAll(/\\([nN,;\\])/g, (_match, character) => character === "n" || character === "N" ? `
43947
+ ` : character);
43948
+ }
43801
43949
  var escapeText = (value) => value.replace(/\r\n?/g, `
43802
43950
  `).replaceAll("\\", "\\\\").replaceAll(`
43803
43951
  `, "\\n").replaceAll(";", "\\;").replaceAll(",", "\\,");
@@ -44033,20 +44181,20 @@ function params(value) {
44033
44181
  }
44034
44182
  function fold(line) {
44035
44183
  const chunks = [];
44036
- let rest = line;
44037
- while (Buffer.byteLength(rest, "utf8") > 75) {
44038
- let end = Math.min(75, rest.length);
44039
- while (Buffer.byteLength(rest.slice(0, end), "utf8") > 75)
44040
- end -= 1;
44041
- const preceding = rest.charCodeAt(end - 1);
44042
- if (preceding >= 55296 && preceding <= 56319)
44043
- end -= 1;
44044
- if (end <= 0)
44045
- throw new Error("unable to fold vCard content line");
44046
- chunks.push(rest.slice(0, end));
44047
- rest = rest.slice(end);
44048
- }
44049
- chunks.push(rest);
44184
+ let start = 0;
44185
+ let end = 0;
44186
+ let octets = 0;
44187
+ for (const char of line) {
44188
+ const size = Buffer.byteLength(char, "utf8");
44189
+ if (octets + size > (chunks.length ? 74 : 75)) {
44190
+ chunks.push(line.slice(start, end));
44191
+ start = end;
44192
+ octets = 0;
44193
+ }
44194
+ octets += size;
44195
+ end += char.length;
44196
+ }
44197
+ chunks.push(line.slice(start));
44050
44198
  return chunks.join(`\r
44051
44199
  `);
44052
44200
  }
@@ -44337,7 +44485,7 @@ class CardDavClient extends DavClient {
44337
44485
  var package_default2 = {
44338
44486
  name: "@sockethub/platform-carddav",
44339
44487
  description: "A Sockethub platform for CardDAV address books and contacts",
44340
- version: "1.0.0-beta.0",
44488
+ version: "1.0.0-beta.2",
44341
44489
  type: "module",
44342
44490
  private: false,
44343
44491
  license: "LGPL-3.0+",
@@ -44366,21 +44514,21 @@ var package_default2 = {
44366
44514
  test: "bun test src/"
44367
44515
  },
44368
44516
  dependencies: {
44369
- "@sockethub/dav": "^1.0.0-beta.0",
44370
- "@sockethub/schemas": "^3.0.0-beta.0"
44517
+ "@sockethub/dav": "^1.0.0-beta.2",
44518
+ "@sockethub/schemas": "^3.0.0-beta.2"
44371
44519
  },
44372
44520
  devDependencies: {
44373
44521
  "@types/bun": "latest"
44374
44522
  },
44375
44523
  peerDependencies: {
44376
- "@sockethub/server": "^5.0.0-beta.0"
44524
+ "@sockethub/server": "^5.0.0-beta.2"
44377
44525
  },
44378
44526
  peerDependenciesMeta: {
44379
44527
  "@sockethub/server": {
44380
44528
  optional: true
44381
44529
  }
44382
44530
  },
44383
- gitHead: "75002ae8f15d7679bd58a9327f5a1d7eeaffa237"
44531
+ gitHead: "1f74d4e07311496f0acc021855137bc197d79eab"
44384
44532
  };
44385
44533
 
44386
44534
  // src/schema.ts
@@ -44673,13 +44821,15 @@ var CONTEXT = buildCanonicalContext(PlatformCardDavSchema.contextUrl);
44673
44821
 
44674
44822
  class CardDav {
44675
44823
  log;
44824
+ discovery = new DiscoveryCache;
44676
44825
  config = {
44677
44826
  persist: false,
44678
44827
  requireCredentials: ["fetch", "query", "create", "update", "delete"],
44679
44828
  connectTimeoutMs: 15000,
44680
44829
  allowPrivateAddresses: false,
44681
44830
  allowInsecureHttp: false,
44682
- concurrency: 10
44831
+ concurrency: 10,
44832
+ discoveryCacheTtlMs: 300000
44683
44833
  };
44684
44834
  constructor(session) {
44685
44835
  this.log = session.log;
@@ -44702,40 +44852,40 @@ class CardDav {
44702
44852
  summary: "CardDAV address books",
44703
44853
  totalItems: items.length,
44704
44854
  items
44705
- })).catch((error) => this.fail(job, error, done)).finally(() => client.close().catch(() => {}));
44855
+ })).catch((error) => this.fail(job, error, done, credentials)).finally(() => client.close().catch(() => {}));
44706
44856
  }
44707
44857
  query(job, credentials, done) {
44708
44858
  const client = this.client(credentials);
44709
- this.book(client, job.target?.id ?? "").then((book) => client.query(book, job.object ?? {})).then((items) => done(null, {
44859
+ this.book(client, credentials, job.target?.id ?? "").then((book) => client.query(book, job.object ?? {})).then((items) => done(null, {
44710
44860
  "@context": CONTEXT,
44711
44861
  ...job.id ? { id: job.id } : {},
44712
44862
  type: "collection",
44713
44863
  summary: "CardDAV contacts",
44714
44864
  totalItems: items.length,
44715
44865
  items
44716
- })).catch((error) => this.fail(job, error, done)).finally(() => client.close().catch(() => {}));
44866
+ })).catch((error) => this.fail(job, error, done, credentials)).finally(() => client.close().catch(() => {}));
44717
44867
  }
44718
44868
  create(job, credentials, done) {
44719
44869
  const client = this.client(credentials);
44720
- this.book(client, job.target?.id ?? "").then((book) => client.create(book, job.object)).then((result) => done(null, this.mutation(job, "create", result))).catch((error) => this.fail(job, error, done)).finally(() => client.close().catch(() => {}));
44870
+ this.book(client, credentials, job.target?.id ?? "").then((book) => client.create(book, job.object)).then((result) => done(null, this.mutation(job, "create", result))).catch((error) => this.fail(job, error, done, credentials)).finally(() => client.close().catch(() => {}));
44721
44871
  }
44722
44872
  update(job, credentials, done) {
44723
44873
  const client = this.client(credentials);
44724
44874
  const input = job.object;
44725
- this.book(client, job.target?.id ?? "").then((book) => {
44875
+ this.book(client, credentials, job.target?.id ?? "").then((book) => {
44726
44876
  if (!isDavCollectionChild(book.id, input.id))
44727
44877
  throw new DavFailure("carddav:invalid-resource");
44728
44878
  return client.update(input);
44729
- }).then((result) => done(null, this.mutation(job, "update", result))).catch((error) => this.fail(job, error, done)).finally(() => client.close().catch(() => {}));
44879
+ }).then((result) => done(null, this.mutation(job, "update", result))).catch((error) => this.fail(job, error, done, credentials)).finally(() => client.close().catch(() => {}));
44730
44880
  }
44731
44881
  delete(job, credentials, done) {
44732
44882
  const client = this.client(credentials);
44733
44883
  const input = job.object;
44734
- this.book(client, job.target?.id ?? "").then((book) => {
44884
+ this.book(client, credentials, job.target?.id ?? "").then((book) => {
44735
44885
  if (!isDavCollectionChild(book.id, input.id))
44736
44886
  throw new DavFailure("carddav:invalid-resource");
44737
44887
  return client.delete(input.id, input.etag);
44738
- }).then(() => done(null, this.mutation(job, "delete", { id: input.id }))).catch((error) => this.fail(job, error, done)).finally(() => client.close().catch(() => {}));
44888
+ }).then(() => done(null, this.mutation(job, "delete", { id: input.id }))).catch((error) => this.fail(job, error, done, credentials)).finally(() => client.close().catch(() => {}));
44739
44889
  }
44740
44890
  client(credentials) {
44741
44891
  const { url, ...authentication } = credentials.object;
@@ -44744,14 +44894,18 @@ class CardDav {
44744
44894
  allowInsecureHttp: this.config.allowInsecureHttp
44745
44895
  });
44746
44896
  }
44747
- async book(client, id) {
44897
+ discoveryKey(credentials) {
44898
+ return this.discovery.key("carddav", credentials.object);
44899
+ }
44900
+ async book(client, credentials, id) {
44748
44901
  let normalized;
44749
44902
  try {
44750
44903
  normalized = new URL(id).href;
44751
44904
  } catch {
44752
44905
  throw new DavFailure("carddav:invalid-address-book");
44753
44906
  }
44754
- const book = (await client.discoverAddressBooks()).find((item) => item.id === normalized);
44907
+ const books = await this.discovery.get(this.discoveryKey(credentials), this.config.discoveryCacheTtlMs, () => client.discoverAddressBooks());
44908
+ const book = books.find((item) => item.id === normalized);
44755
44909
  if (!book)
44756
44910
  throw new DavFailure("carddav:invalid-address-book");
44757
44911
  return book;
@@ -44771,7 +44925,9 @@ class CardDav {
44771
44925
  }
44772
44926
  };
44773
44927
  }
44774
- fail(job, error, done) {
44928
+ fail(job, error, done, credentials) {
44929
+ if (credentials && error instanceof DavFailure && /:(authentication-failed|not-found|invalid-address-book)$/.test(error.code))
44930
+ this.discovery.delete(this.discoveryKey(credentials));
44775
44931
  const code = error instanceof DavFailure ? error.code : `carddav:invalid-${job.type}`;
44776
44932
  this.log.error(`CardDAV ${job.type} failed for actor ${job.actor.id}`, {
44777
44933
  code,
@@ -44789,4 +44945,4 @@ export {
44789
44945
  parseVCard
44790
44946
  };
44791
44947
 
44792
- //# debugId=9B429FF2EF86262D64756E2164756E21
44948
+ //# debugId=DCD67AD60F21618364756E2164756E21