@sockethub/platform-carddav 1.0.0-beta.4 → 1.0.0-beta.5
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 +36 -2
- package/dist/dav.d.ts +6 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.js +253 -30
- package/dist/index.js.map +6 -6
- package/dist/schema.d.ts +330 -0
- package/dist/types.d.ts +9 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ through the standard Sockethub credentials flow.
|
|
|
14
14
|
|
|
15
15
|
## Discovery cache
|
|
16
16
|
|
|
17
|
-
Each `query`, `create`, `update`, and `delete` needs the account's address book list to
|
|
17
|
+
Each `query`, `sync`, `create`, `update`, and `delete` needs the account's address book list to
|
|
18
18
|
check the target. The platform caches that discovery result in memory for
|
|
19
19
|
`packageConfig.discoveryCacheTtlMs` (default `300000`, 5 minutes; `0`
|
|
20
20
|
disables the cache). Entries are keyed by an HMAC of the protocol, service URL,
|
|
@@ -52,6 +52,8 @@ that endpoint.
|
|
|
52
52
|
|
|
53
53
|
- `fetch`: discover address books.
|
|
54
54
|
- `query`: list or search contacts.
|
|
55
|
+
- `sync`: report what changed in one address book since a sync token
|
|
56
|
+
(RFC 6578 WebDAV Sync).
|
|
55
57
|
- `create`: create a vCard with collision protection.
|
|
56
58
|
- `update`: update a vCard only when its ETag still matches.
|
|
57
59
|
- `delete`: delete a vCard only when its ETag still matches.
|
|
@@ -96,6 +98,37 @@ Use `sc.contextFor("carddav")` as the request context.
|
|
|
96
98
|
}
|
|
97
99
|
```
|
|
98
100
|
|
|
101
|
+
### Sync changes
|
|
102
|
+
|
|
103
|
+
`sync` reports changes without listing the whole address book each time. Send
|
|
104
|
+
it without an `object` for the initial sync, which returns every contact and a
|
|
105
|
+
`syncToken`:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"type": "sync",
|
|
110
|
+
"actor": { "id": "carddav:alice", "type": "person" },
|
|
111
|
+
"target": {
|
|
112
|
+
"id": "https://contacts.example/addressbooks/alice/personal/",
|
|
113
|
+
"type": "addressBook"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The response is a collection with `items` (contacts in the same shape as
|
|
119
|
+
`query` results), `removed` (ids of contacts deleted since the token),
|
|
120
|
+
`syncToken`, and `truncated`. Store the token as an opaque string per address
|
|
121
|
+
book and send it back as `"object": { "syncToken": "..." }` to receive only
|
|
122
|
+
what changed since then. When `truncated` is `true`, store the new token and
|
|
123
|
+
sync again until it is `false`.
|
|
124
|
+
|
|
125
|
+
On `carddav:sync-token-invalid`, drop the stored token and sync again without
|
|
126
|
+
one. On `carddav:sync-unsupported`, the server does not offer WebDAV Sync; fall
|
|
127
|
+
back to `query`. Other sync errors are `carddav:sync-failed`. A server that
|
|
128
|
+
rejects a token without a `DAV:` precondition (a 400, 409, or 412) is
|
|
129
|
+
reported as `carddav:sync-token-invalid`, so a retry without a token reports
|
|
130
|
+
the real cause.
|
|
131
|
+
|
|
99
132
|
### Create a contact
|
|
100
133
|
|
|
101
134
|
```json
|
|
@@ -138,4 +171,5 @@ array removes them.
|
|
|
138
171
|
The initial implementation reads vCard 3.0 and 4.0 and writes vCard 4.0. Stable
|
|
139
172
|
errors use the `carddav:` prefix, including `authentication-failed`,
|
|
140
173
|
`unsupported-authentication`, `invalid-address-book`, `invalid-resource`,
|
|
141
|
-
`conflict`, `not-found`,
|
|
174
|
+
`conflict`, `not-found`, `unsupported-update`, `sync-token-invalid`,
|
|
175
|
+
`sync-unsupported`, and `sync-failed`.
|
package/dist/dav.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { type DavAuthentication, DavClient, DavFailure, type DavNetworkOptions } from "@sockethub/dav";
|
|
2
|
-
import type { AddressBookDescription, Contact, ContactInput, ContactQuery } from "./types.js";
|
|
2
|
+
import type { AddressBookDescription, Contact, ContactInput, ContactQuery, ContactSyncResult } from "./types.js";
|
|
3
3
|
export { DavFailure as CardDavFailure };
|
|
4
4
|
export declare class CardDavClient extends DavClient {
|
|
5
5
|
constructor(url: string, authentication: DavAuthentication, timeoutMs?: number, networkOptions?: DavNetworkOptions);
|
|
6
6
|
discoverAddressBooks(): Promise<AddressBookDescription[]>;
|
|
7
7
|
private discoverFrom;
|
|
8
8
|
query(book: AddressBookDescription, query?: ContactQuery): Promise<Contact[]>;
|
|
9
|
+
/**
|
|
10
|
+
* Incremental sync (RFC 6578). Without `syncToken`, lists every contact
|
|
11
|
+
* in the address book; with one, lists only what changed since then.
|
|
12
|
+
*/
|
|
13
|
+
sync(book: AddressBookDescription, syncToken?: string): Promise<ContactSyncResult>;
|
|
9
14
|
create(book: AddressBookDescription, input: ContactInput): Promise<{
|
|
10
15
|
id: string;
|
|
11
16
|
uid: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,17 @@ export default class CardDav implements PlatformInterface {
|
|
|
12
12
|
cleanup(done: PlatformCallback): void;
|
|
13
13
|
fetch(job: ActivityStream, credentials: CardDavCredentials, done: PlatformCallback): void;
|
|
14
14
|
query(job: ActivityStream, credentials: CardDavCredentials, done: PlatformCallback): void;
|
|
15
|
+
/**
|
|
16
|
+
* Incremental address book sync (RFC 6578 WebDAV Sync). Without
|
|
17
|
+
* `object.syncToken`, returns every contact plus a token; with one,
|
|
18
|
+
* returns only contacts changed and ids removed since that token. Store
|
|
19
|
+
* the returned `syncToken` opaquely and send it next time. When
|
|
20
|
+
* `truncated` is true, sync again with the new token before relying on
|
|
21
|
+
* the result being complete. `carddav:sync-token-invalid` means start
|
|
22
|
+
* over without a token; `carddav:sync-unsupported` means fall back to
|
|
23
|
+
* `query`.
|
|
24
|
+
*/
|
|
25
|
+
sync(job: ActivityStream, credentials: CardDavCredentials, done: PlatformCallback): void;
|
|
15
26
|
create(job: ActivityStream, credentials: CardDavCredentials, done: PlatformCallback): void;
|
|
16
27
|
update(job: ActivityStream, credentials: CardDavCredentials, done: PlatformCallback): void;
|
|
17
28
|
delete(job: ActivityStream, credentials: CardDavCredentials, done: PlatformCallback): void;
|
package/dist/index.js
CHANGED
|
@@ -42683,9 +42683,10 @@ function xmlText(value) {
|
|
|
42683
42683
|
return predefined[entity.toLowerCase()] ?? reference;
|
|
42684
42684
|
});
|
|
42685
42685
|
}
|
|
42686
|
-
function parseDavXml(xml) {
|
|
42686
|
+
function parseDavXml(xml, options = {}) {
|
|
42687
42687
|
return new XMLParser({
|
|
42688
42688
|
ignoreAttributes: false,
|
|
42689
|
+
parseTagValue: options.parseTagValue ?? true,
|
|
42689
42690
|
removeNSPrefix: true,
|
|
42690
42691
|
processEntities: false,
|
|
42691
42692
|
tagValueProcessor: (_tagName, value) => xmlText(value),
|
|
@@ -42864,6 +42865,16 @@ function digestChallenge(header) {
|
|
|
42864
42865
|
return;
|
|
42865
42866
|
}
|
|
42866
42867
|
var quote = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}"`;
|
|
42868
|
+
var isStatus = (value, code) => new RegExp(`\\s${code}(?:\\s|$)`).test(String(value ?? ""));
|
|
42869
|
+
function davPreconditions(xml) {
|
|
42870
|
+
try {
|
|
42871
|
+
const error = parseDavXml(xml).error;
|
|
42872
|
+
return error && typeof error === "object" ? Object.keys(error) : [];
|
|
42873
|
+
} catch {
|
|
42874
|
+
return [];
|
|
42875
|
+
}
|
|
42876
|
+
}
|
|
42877
|
+
var xmlEscape = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
42867
42878
|
|
|
42868
42879
|
class DavClient {
|
|
42869
42880
|
authentication;
|
|
@@ -42908,9 +42919,10 @@ class DavClient {
|
|
|
42908
42919
|
this.assertAllowed(url);
|
|
42909
42920
|
let response;
|
|
42910
42921
|
try {
|
|
42922
|
+
const { passForbidden: _, ...fetchInit } = init;
|
|
42911
42923
|
const authorization = this.authorization(url, init);
|
|
42912
42924
|
response = await this.fetchImpl(url, {
|
|
42913
|
-
...
|
|
42925
|
+
...fetchInit,
|
|
42914
42926
|
redirect: "manual",
|
|
42915
42927
|
signal,
|
|
42916
42928
|
headers: {
|
|
@@ -42954,7 +42966,7 @@ class DavClient {
|
|
|
42954
42966
|
}
|
|
42955
42967
|
throw new DavFailure(`${this.errorPrefix}:${authAttempts > 0 ? "authentication-failed" : "unsupported-authentication"}`);
|
|
42956
42968
|
}
|
|
42957
|
-
if (response.status === 401 || response.status === 403) {
|
|
42969
|
+
if (response.status === 401 || response.status === 403 && !init.passForbidden) {
|
|
42958
42970
|
await response.body?.cancel().catch(() => {});
|
|
42959
42971
|
throw new DavFailure(`${this.errorPrefix}:authentication-failed`);
|
|
42960
42972
|
}
|
|
@@ -42975,6 +42987,146 @@ class DavClient {
|
|
|
42975
42987
|
await response.body?.cancel().catch(() => {});
|
|
42976
42988
|
throw new DavFailure(`${this.errorPrefix}:invalid-response`);
|
|
42977
42989
|
}
|
|
42990
|
+
return this.readMultistatus(response);
|
|
42991
|
+
}
|
|
42992
|
+
async syncCollection(collection, prop, syncToken, namespaces = "") {
|
|
42993
|
+
const response = await this.request(collection, {
|
|
42994
|
+
method: "REPORT",
|
|
42995
|
+
passForbidden: true,
|
|
42996
|
+
headers: {
|
|
42997
|
+
depth: "0",
|
|
42998
|
+
"content-type": "application/xml; charset=utf-8"
|
|
42999
|
+
},
|
|
43000
|
+
body: `<?xml version="1.0" encoding="utf-8"?>
|
|
43001
|
+
<d:sync-collection xmlns:d="DAV:"${namespaces ? ` ${namespaces}` : ""}>
|
|
43002
|
+
${syncToken ? `<d:sync-token>${xmlEscape(syncToken)}</d:sync-token>` : "<d:sync-token/>"}
|
|
43003
|
+
<d:sync-level>1</d:sync-level>
|
|
43004
|
+
<d:prop>${prop}</d:prop>
|
|
43005
|
+
</d:sync-collection>`
|
|
43006
|
+
});
|
|
43007
|
+
if (response.status !== 207) {
|
|
43008
|
+
let body = "";
|
|
43009
|
+
try {
|
|
43010
|
+
body = await response.text();
|
|
43011
|
+
} catch {}
|
|
43012
|
+
const preconditions = davPreconditions(body);
|
|
43013
|
+
const prefix = this.errorPrefix;
|
|
43014
|
+
if (preconditions.includes("valid-sync-token"))
|
|
43015
|
+
throw new DavFailure(`${prefix}:sync-token-invalid`);
|
|
43016
|
+
if (preconditions.includes("supported-report"))
|
|
43017
|
+
throw new DavFailure(`${prefix}:sync-unsupported`);
|
|
43018
|
+
if (response.status === 404)
|
|
43019
|
+
throw new DavFailure(`${prefix}:not-found`);
|
|
43020
|
+
if ([400, 409, 412].includes(response.status) && syncToken)
|
|
43021
|
+
throw new DavFailure(`${prefix}:sync-token-invalid`);
|
|
43022
|
+
if (response.status === 403)
|
|
43023
|
+
throw new DavFailure(`${prefix}:authentication-failed`);
|
|
43024
|
+
if ([400, 405, 415, 501].includes(response.status))
|
|
43025
|
+
throw new DavFailure(`${prefix}:sync-unsupported`);
|
|
43026
|
+
throw new DavFailure(`${prefix}:sync-failed`);
|
|
43027
|
+
}
|
|
43028
|
+
let multistatus;
|
|
43029
|
+
try {
|
|
43030
|
+
const parsed = parseDavXml(await response.text(), {
|
|
43031
|
+
parseTagValue: false
|
|
43032
|
+
}).multistatus;
|
|
43033
|
+
if (!parsed || typeof parsed !== "object")
|
|
43034
|
+
throw new Error;
|
|
43035
|
+
multistatus = parsed;
|
|
43036
|
+
} catch {
|
|
43037
|
+
throw new DavFailure(`${this.errorPrefix}:invalid-response`);
|
|
43038
|
+
}
|
|
43039
|
+
const token = multistatus["sync-token"];
|
|
43040
|
+
if (typeof token !== "string" || token.trim() === "")
|
|
43041
|
+
throw new DavFailure(`${this.errorPrefix}:invalid-response`);
|
|
43042
|
+
const result = {
|
|
43043
|
+
changed: [],
|
|
43044
|
+
removed: [],
|
|
43045
|
+
syncToken: token.trim(),
|
|
43046
|
+
truncated: false
|
|
43047
|
+
};
|
|
43048
|
+
for (const item of asArray(multistatus.response)) {
|
|
43049
|
+
const href = davHref(item.href);
|
|
43050
|
+
if (!href)
|
|
43051
|
+
throw new DavFailure(`${this.errorPrefix}:invalid-response`);
|
|
43052
|
+
const id = new URL(href, collection).href;
|
|
43053
|
+
const status = item.status;
|
|
43054
|
+
if (id === collection.href || `${id}/` === collection.href) {
|
|
43055
|
+
if (isStatus(status, 507))
|
|
43056
|
+
result.truncated = true;
|
|
43057
|
+
continue;
|
|
43058
|
+
}
|
|
43059
|
+
if (!isDavCollectionChild(collection.href, id))
|
|
43060
|
+
throw new DavFailure(`${this.errorPrefix}:invalid-response`);
|
|
43061
|
+
if (isStatus(status, 404))
|
|
43062
|
+
result.removed.push(id);
|
|
43063
|
+
else
|
|
43064
|
+
result.changed.push({ id, props: successfulProps(item) });
|
|
43065
|
+
}
|
|
43066
|
+
return result;
|
|
43067
|
+
}
|
|
43068
|
+
async syncResources(collection, options, syncToken) {
|
|
43069
|
+
const property = options.data.slice(options.data.indexOf(":") + 1);
|
|
43070
|
+
const sync = await this.syncCollection(collection, `<d:getetag/><${options.data}/>`, syncToken, options.namespaces);
|
|
43071
|
+
const resources = [];
|
|
43072
|
+
const missing = new Map;
|
|
43073
|
+
const add = (id, value, etag) => {
|
|
43074
|
+
const data = value && typeof value === "object" ? value["#text"] : value;
|
|
43075
|
+
if (typeof data !== "string")
|
|
43076
|
+
return false;
|
|
43077
|
+
resources.push({
|
|
43078
|
+
id,
|
|
43079
|
+
data,
|
|
43080
|
+
...typeof etag === "string" ? { etag } : {}
|
|
43081
|
+
});
|
|
43082
|
+
return true;
|
|
43083
|
+
};
|
|
43084
|
+
for (const member of sync.changed)
|
|
43085
|
+
if (!add(member.id, member.props[property], member.props.getetag))
|
|
43086
|
+
missing.set(member.id, member.props.getetag);
|
|
43087
|
+
if (missing.size) {
|
|
43088
|
+
const hrefs = [...missing.keys()].map((id) => `<d:href>${xmlEscape(new URL(id).pathname)}</d:href>`).join("");
|
|
43089
|
+
const response = await this.request(collection, {
|
|
43090
|
+
method: "REPORT",
|
|
43091
|
+
headers: {
|
|
43092
|
+
depth: "1",
|
|
43093
|
+
"content-type": "application/xml; charset=utf-8"
|
|
43094
|
+
},
|
|
43095
|
+
body: `<?xml version="1.0" encoding="utf-8"?>
|
|
43096
|
+
<${options.multiget} xmlns:d="DAV:" ${options.namespaces}>
|
|
43097
|
+
<d:prop><d:getetag/><${options.data}/></d:prop>
|
|
43098
|
+
${hrefs}
|
|
43099
|
+
</${options.multiget}>`
|
|
43100
|
+
});
|
|
43101
|
+
if (response.status !== 207)
|
|
43102
|
+
return rejectDavResponse(response, `${this.errorPrefix}:sync-failed`);
|
|
43103
|
+
for (const item of await this.readMultistatus(response)) {
|
|
43104
|
+
const href = davHref(item.href);
|
|
43105
|
+
if (!href)
|
|
43106
|
+
continue;
|
|
43107
|
+
const id = new URL(href, collection).href;
|
|
43108
|
+
if (!missing.has(id))
|
|
43109
|
+
continue;
|
|
43110
|
+
if (isStatus(item.status, 404)) {
|
|
43111
|
+
sync.removed.push(id);
|
|
43112
|
+
missing.delete(id);
|
|
43113
|
+
continue;
|
|
43114
|
+
}
|
|
43115
|
+
const props = successfulProps(item);
|
|
43116
|
+
if (add(id, props[property], props.getetag ?? missing.get(id)))
|
|
43117
|
+
missing.delete(id);
|
|
43118
|
+
}
|
|
43119
|
+
if (missing.size)
|
|
43120
|
+
throw new DavFailure(`${this.errorPrefix}:invalid-response`);
|
|
43121
|
+
}
|
|
43122
|
+
return {
|
|
43123
|
+
resources,
|
|
43124
|
+
removed: sync.removed,
|
|
43125
|
+
syncToken: sync.syncToken,
|
|
43126
|
+
truncated: sync.truncated
|
|
43127
|
+
};
|
|
43128
|
+
}
|
|
43129
|
+
async readMultistatus(response) {
|
|
42978
43130
|
try {
|
|
42979
43131
|
const parsed = parseDavXml(await response.text());
|
|
42980
43132
|
return asArray(parsed.multistatus?.response);
|
|
@@ -43275,7 +43427,7 @@ var ActivityStreamSchema = {
|
|
|
43275
43427
|
var package_default = {
|
|
43276
43428
|
name: "@sockethub/schemas",
|
|
43277
43429
|
description: "JSON schema files for validating Sockethub Activity Streams",
|
|
43278
|
-
version: "3.0.0-beta.
|
|
43430
|
+
version: "3.0.0-beta.5",
|
|
43279
43431
|
type: "module",
|
|
43280
43432
|
private: false,
|
|
43281
43433
|
author: "Nick Jennings <nick@silverbucket.net>",
|
|
@@ -43345,7 +43497,7 @@ var package_default = {
|
|
|
43345
43497
|
"ajv-formats": "^2.1.1",
|
|
43346
43498
|
"ajv-formats-draft2019": "^1.6.1"
|
|
43347
43499
|
},
|
|
43348
|
-
gitHead: "
|
|
43500
|
+
gitHead: "cc4ffdd9714ebbd8372a88c4f2d795974e04029e",
|
|
43349
43501
|
types: "./dist/index.d.ts"
|
|
43350
43502
|
};
|
|
43351
43503
|
|
|
@@ -44306,6 +44458,14 @@ function queryBody(query) {
|
|
|
44306
44458
|
${filter}
|
|
44307
44459
|
</c:addressbook-query>`;
|
|
44308
44460
|
}
|
|
44461
|
+
function contact(data, id, etag) {
|
|
44462
|
+
try {
|
|
44463
|
+
return parseVCard(data, id, typeof etag === "string" ? etag : undefined);
|
|
44464
|
+
} catch {
|
|
44465
|
+
throw new DavFailure("carddav:invalid-response");
|
|
44466
|
+
}
|
|
44467
|
+
}
|
|
44468
|
+
|
|
44309
44469
|
class CardDavClient extends DavClient {
|
|
44310
44470
|
constructor(url, authentication, timeoutMs = 15000, networkOptions = {}) {
|
|
44311
44471
|
super(url, authentication, "carddav", timeoutMs, networkOptions);
|
|
@@ -44371,29 +44531,30 @@ class CardDavClient extends DavClient {
|
|
|
44371
44531
|
});
|
|
44372
44532
|
if (response.status !== 207)
|
|
44373
44533
|
return rejectDavResponse(response, "carddav:query-failed");
|
|
44374
|
-
let parsed;
|
|
44375
|
-
try {
|
|
44376
|
-
parsed = parseDavXml(await response.text());
|
|
44377
|
-
} catch {
|
|
44378
|
-
throw new DavFailure("carddav:invalid-response");
|
|
44379
|
-
}
|
|
44380
44534
|
const contacts = [];
|
|
44381
|
-
for (const item of
|
|
44535
|
+
for (const item of await this.readMultistatus(response)) {
|
|
44382
44536
|
const props = successfulProps(item);
|
|
44383
44537
|
const itemHref = davHref(item.href);
|
|
44384
44538
|
const data = props["address-data"];
|
|
44385
44539
|
if (!itemHref || typeof data !== "string")
|
|
44386
44540
|
continue;
|
|
44387
|
-
|
|
44388
|
-
contacts.push(parseVCard(data, new URL(itemHref, book.id).href, typeof props.getetag === "string" ? props.getetag : undefined));
|
|
44389
|
-
} catch {
|
|
44390
|
-
throw new DavFailure("carddav:invalid-response");
|
|
44391
|
-
}
|
|
44541
|
+
contacts.push(contact(data, new URL(itemHref, book.id).href, props.getetag));
|
|
44392
44542
|
if (query.limit && contacts.length >= query.limit)
|
|
44393
44543
|
break;
|
|
44394
44544
|
}
|
|
44395
44545
|
return contacts;
|
|
44396
44546
|
}
|
|
44547
|
+
async sync(book, syncToken) {
|
|
44548
|
+
const { resources, ...result } = await this.syncResources(new URL(book.id), {
|
|
44549
|
+
namespaces: 'xmlns:c="urn:ietf:params:xml:ns:carddav"',
|
|
44550
|
+
data: "c:address-data",
|
|
44551
|
+
multiget: "c:addressbook-multiget"
|
|
44552
|
+
}, syncToken);
|
|
44553
|
+
return {
|
|
44554
|
+
...result,
|
|
44555
|
+
items: resources.map((resource) => contact(resource.data, resource.id, resource.etag))
|
|
44556
|
+
};
|
|
44557
|
+
}
|
|
44397
44558
|
async create(book, input) {
|
|
44398
44559
|
const card = buildVCard(input, []);
|
|
44399
44560
|
const resource = new URL(`${encodeURIComponent(card.uid)}.vcf`, book.id.endsWith("/") ? book.id : `${book.id}/`);
|
|
@@ -44486,7 +44647,7 @@ class CardDavClient extends DavClient {
|
|
|
44486
44647
|
var package_default2 = {
|
|
44487
44648
|
name: "@sockethub/platform-carddav",
|
|
44488
44649
|
description: "A Sockethub platform for CardDAV address books and contacts",
|
|
44489
|
-
version: "1.0.0-beta.
|
|
44650
|
+
version: "1.0.0-beta.5",
|
|
44490
44651
|
type: "module",
|
|
44491
44652
|
private: false,
|
|
44492
44653
|
license: "LGPL-3.0+",
|
|
@@ -44515,21 +44676,21 @@ var package_default2 = {
|
|
|
44515
44676
|
test: "bun test src/"
|
|
44516
44677
|
},
|
|
44517
44678
|
dependencies: {
|
|
44518
|
-
"@sockethub/dav": "^1.0.0-beta.
|
|
44519
|
-
"@sockethub/schemas": "^3.0.0-beta.
|
|
44679
|
+
"@sockethub/dav": "^1.0.0-beta.5",
|
|
44680
|
+
"@sockethub/schemas": "^3.0.0-beta.5"
|
|
44520
44681
|
},
|
|
44521
44682
|
devDependencies: {
|
|
44522
44683
|
"@types/bun": "latest"
|
|
44523
44684
|
},
|
|
44524
44685
|
peerDependencies: {
|
|
44525
|
-
"@sockethub/server": "^5.0.0-beta.
|
|
44686
|
+
"@sockethub/server": "^5.0.0-beta.5"
|
|
44526
44687
|
},
|
|
44527
44688
|
peerDependenciesMeta: {
|
|
44528
44689
|
"@sockethub/server": {
|
|
44529
44690
|
optional: true
|
|
44530
44691
|
}
|
|
44531
44692
|
},
|
|
44532
|
-
gitHead: "
|
|
44693
|
+
gitHead: "cc4ffdd9714ebbd8372a88c4f2d795974e04029e"
|
|
44533
44694
|
};
|
|
44534
44695
|
|
|
44535
44696
|
// src/schema.ts
|
|
@@ -44637,7 +44798,7 @@ var shared = {
|
|
|
44637
44798
|
vcardVersion: { enum: ["3.0", "4.0"] },
|
|
44638
44799
|
updateSupported: { type: "boolean" }
|
|
44639
44800
|
};
|
|
44640
|
-
var
|
|
44801
|
+
var contact2 = {
|
|
44641
44802
|
type: "object",
|
|
44642
44803
|
required: ["type", "name"],
|
|
44643
44804
|
additionalProperties: false,
|
|
@@ -44658,6 +44819,12 @@ var query = {
|
|
|
44658
44819
|
limit: { type: "integer", minimum: 1, maximum: 1000 }
|
|
44659
44820
|
}
|
|
44660
44821
|
};
|
|
44822
|
+
var syncToken = { type: "string", minLength: 1, maxLength: 4096 };
|
|
44823
|
+
var sync = {
|
|
44824
|
+
type: "object",
|
|
44825
|
+
additionalProperties: false,
|
|
44826
|
+
properties: { syncToken }
|
|
44827
|
+
};
|
|
44661
44828
|
var deletion = {
|
|
44662
44829
|
type: "object",
|
|
44663
44830
|
required: ["id", "type", "etag"],
|
|
@@ -44665,11 +44832,11 @@ var deletion = {
|
|
|
44665
44832
|
properties: { id: shared.id, type: shared.type, etag: shared.etag }
|
|
44666
44833
|
};
|
|
44667
44834
|
var updateContact = {
|
|
44668
|
-
...
|
|
44835
|
+
...contact2,
|
|
44669
44836
|
required: ["type", "name", "id", "uid", "etag"]
|
|
44670
44837
|
};
|
|
44671
44838
|
var responseContact = {
|
|
44672
|
-
...
|
|
44839
|
+
...contact2,
|
|
44673
44840
|
required: ["id", "type", "uid", "name", "vcardVersion", "updateSupported"]
|
|
44674
44841
|
};
|
|
44675
44842
|
var mutation = {
|
|
@@ -44727,10 +44894,19 @@ var PlatformCardDavSchema = {
|
|
|
44727
44894
|
messages: {
|
|
44728
44895
|
required: ["type", "actor"],
|
|
44729
44896
|
properties: {
|
|
44730
|
-
type: {
|
|
44897
|
+
type: {
|
|
44898
|
+
enum: ["fetch", "query", "sync", "create", "update", "delete"]
|
|
44899
|
+
},
|
|
44731
44900
|
actor,
|
|
44732
44901
|
target,
|
|
44733
|
-
object: {
|
|
44902
|
+
object: {
|
|
44903
|
+
oneOf: [
|
|
44904
|
+
contact2,
|
|
44905
|
+
query,
|
|
44906
|
+
deletion,
|
|
44907
|
+
{ ...sync, required: ["syncToken"] }
|
|
44908
|
+
]
|
|
44909
|
+
}
|
|
44734
44910
|
}
|
|
44735
44911
|
},
|
|
44736
44912
|
messageConstraints: {
|
|
@@ -44747,7 +44923,11 @@ var PlatformCardDavSchema = {
|
|
|
44747
44923
|
required: ["type", "actor", "target"]
|
|
44748
44924
|
},
|
|
44749
44925
|
{
|
|
44750
|
-
properties: { type: { const: "
|
|
44926
|
+
properties: { type: { const: "sync" }, object: sync },
|
|
44927
|
+
required: ["type", "actor", "target"]
|
|
44928
|
+
},
|
|
44929
|
+
{
|
|
44930
|
+
properties: { type: { const: "create" }, object: contact2 },
|
|
44751
44931
|
required: ["type", "actor", "target", "object"]
|
|
44752
44932
|
},
|
|
44753
44933
|
{
|
|
@@ -44800,6 +44980,30 @@ var PlatformCardDavSchema = {
|
|
|
44800
44980
|
}
|
|
44801
44981
|
}
|
|
44802
44982
|
},
|
|
44983
|
+
{
|
|
44984
|
+
type: "object",
|
|
44985
|
+
required: [
|
|
44986
|
+
"@context",
|
|
44987
|
+
"type",
|
|
44988
|
+
"totalItems",
|
|
44989
|
+
"items",
|
|
44990
|
+
"removed",
|
|
44991
|
+
"syncToken",
|
|
44992
|
+
"truncated"
|
|
44993
|
+
],
|
|
44994
|
+
additionalProperties: false,
|
|
44995
|
+
properties: {
|
|
44996
|
+
"@context": { type: "array", items: { type: "string" } },
|
|
44997
|
+
id: { type: ["string", "null"] },
|
|
44998
|
+
type: { const: "collection" },
|
|
44999
|
+
summary: { type: "string" },
|
|
45000
|
+
totalItems: { type: "integer", minimum: 0 },
|
|
45001
|
+
items: { type: "array", items: responseContact },
|
|
45002
|
+
removed: { type: "array", items: shared.id },
|
|
45003
|
+
syncToken,
|
|
45004
|
+
truncated: { type: "boolean" }
|
|
45005
|
+
}
|
|
45006
|
+
},
|
|
44803
45007
|
{
|
|
44804
45008
|
type: "object",
|
|
44805
45009
|
required: ["@context", "type", "actor", "target", "object"],
|
|
@@ -44825,7 +45029,14 @@ class CardDav {
|
|
|
44825
45029
|
discovery = new DiscoveryCache;
|
|
44826
45030
|
config = {
|
|
44827
45031
|
persist: false,
|
|
44828
|
-
requireCredentials: [
|
|
45032
|
+
requireCredentials: [
|
|
45033
|
+
"fetch",
|
|
45034
|
+
"query",
|
|
45035
|
+
"sync",
|
|
45036
|
+
"create",
|
|
45037
|
+
"update",
|
|
45038
|
+
"delete"
|
|
45039
|
+
],
|
|
44829
45040
|
connectTimeoutMs: 15000,
|
|
44830
45041
|
allowPrivateAddresses: false,
|
|
44831
45042
|
allowInsecureHttp: false,
|
|
@@ -44866,6 +45077,18 @@ class CardDav {
|
|
|
44866
45077
|
items
|
|
44867
45078
|
})).catch((error) => this.fail(job, error, done, credentials)).finally(() => client.close().catch(() => {}));
|
|
44868
45079
|
}
|
|
45080
|
+
sync(job, credentials, done) {
|
|
45081
|
+
const client = this.client(credentials);
|
|
45082
|
+
const input = job.object ?? {};
|
|
45083
|
+
this.book(client, credentials, job.target?.id ?? "").then((book) => client.sync(book, input.syncToken)).then((result) => done(null, {
|
|
45084
|
+
"@context": CONTEXT,
|
|
45085
|
+
...job.id ? { id: job.id } : {},
|
|
45086
|
+
type: "collection",
|
|
45087
|
+
summary: "CardDAV changes",
|
|
45088
|
+
totalItems: result.items.length,
|
|
45089
|
+
...result
|
|
45090
|
+
})).catch((error) => this.fail(job, error, done, credentials)).finally(() => client.close().catch(() => {}));
|
|
45091
|
+
}
|
|
44869
45092
|
create(job, credentials, done) {
|
|
44870
45093
|
const client = this.client(credentials);
|
|
44871
45094
|
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(() => {}));
|
|
@@ -44946,4 +45169,4 @@ export {
|
|
|
44946
45169
|
parseVCard
|
|
44947
45170
|
};
|
|
44948
45171
|
|
|
44949
|
-
//# debugId=
|
|
45172
|
+
//# debugId=A470017A427BECB364756E2164756E21
|