@sockethub/platform-carddav 1.0.0-beta.4 → 1.0.0-beta.6
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 +41 -2
- package/dist/dav.d.ts +6 -1
- package/dist/index.d.ts +15 -0
- package/dist/index.js +289 -30
- package/dist/index.js.map +6 -6
- package/dist/schema.d.ts +342 -0
- package/dist/types.d.ts +10 -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,42 @@ 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
|
+
Send `"object": { "tokenOnly": true }` to get the address book's current
|
|
126
|
+
`syncToken` with empty `items` and `removed`, without listing every contact.
|
|
127
|
+
Read it before loading the contacts you need, then sync with it later. A
|
|
128
|
+
server that exposes no token returns `carddav:sync-unsupported`.
|
|
129
|
+
|
|
130
|
+
On `carddav:sync-token-invalid`, drop the stored token and sync again without
|
|
131
|
+
one. On `carddav:sync-unsupported`, the server does not offer WebDAV Sync; fall
|
|
132
|
+
back to `query`. Other sync errors are `carddav:sync-failed`. A server that
|
|
133
|
+
rejects a token without a `DAV:` precondition (a 400, 409, or 412) is
|
|
134
|
+
reported as `carddav:sync-token-invalid`, so a retry without a token reports
|
|
135
|
+
the real cause.
|
|
136
|
+
|
|
99
137
|
### Create a contact
|
|
100
138
|
|
|
101
139
|
```json
|
|
@@ -138,4 +176,5 @@ array removes them.
|
|
|
138
176
|
The initial implementation reads vCard 3.0 and 4.0 and writes vCard 4.0. Stable
|
|
139
177
|
errors use the `carddav:` prefix, including `authentication-failed`,
|
|
140
178
|
`unsupported-authentication`, `invalid-address-book`, `invalid-resource`,
|
|
141
|
-
`conflict`, `not-found`,
|
|
179
|
+
`conflict`, `not-found`, `unsupported-update`, `sync-token-invalid`,
|
|
180
|
+
`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,21 @@ 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
|
+
* With `object.tokenOnly: true`, returns the address book's current
|
|
26
|
+
* token and no contacts, so a client can start tracking changes
|
|
27
|
+
* without downloading every contact first.
|
|
28
|
+
*/
|
|
29
|
+
sync(job: ActivityStream, credentials: CardDavCredentials, done: PlatformCallback): void;
|
|
15
30
|
create(job: ActivityStream, credentials: CardDavCredentials, done: PlatformCallback): void;
|
|
16
31
|
update(job: ActivityStream, credentials: CardDavCredentials, done: PlatformCallback): void;
|
|
17
32
|
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,175 @@ 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 currentSyncToken(collection) {
|
|
42993
|
+
const response = await this.request(collection, {
|
|
42994
|
+
method: "PROPFIND",
|
|
42995
|
+
headers: {
|
|
42996
|
+
depth: "0",
|
|
42997
|
+
"content-type": "application/xml; charset=utf-8"
|
|
42998
|
+
},
|
|
42999
|
+
body: `<?xml version="1.0" encoding="utf-8"?>
|
|
43000
|
+
<d:propfind xmlns:d="DAV:"><d:prop><d:sync-token/></d:prop></d:propfind>`
|
|
43001
|
+
});
|
|
43002
|
+
if (response.status === 404)
|
|
43003
|
+
return rejectDavResponse(response, `${this.errorPrefix}:not-found`);
|
|
43004
|
+
if (response.status !== 207) {
|
|
43005
|
+
await response.body?.cancel().catch(() => {});
|
|
43006
|
+
throw new DavFailure(`${this.errorPrefix}:invalid-response`);
|
|
43007
|
+
}
|
|
43008
|
+
let responses;
|
|
43009
|
+
try {
|
|
43010
|
+
responses = asArray(parseDavXml(await response.text(), {
|
|
43011
|
+
parseTagValue: false
|
|
43012
|
+
}).multistatus?.response);
|
|
43013
|
+
} catch {
|
|
43014
|
+
throw new DavFailure(`${this.errorPrefix}:invalid-response`);
|
|
43015
|
+
}
|
|
43016
|
+
const token = successfulProps(responses[0] ?? {})["sync-token"];
|
|
43017
|
+
if (typeof token !== "string" || token.trim() === "")
|
|
43018
|
+
throw new DavFailure(`${this.errorPrefix}:sync-unsupported`);
|
|
43019
|
+
return token.trim();
|
|
43020
|
+
}
|
|
43021
|
+
async syncCollection(collection, prop, syncToken, namespaces = "") {
|
|
43022
|
+
const response = await this.request(collection, {
|
|
43023
|
+
method: "REPORT",
|
|
43024
|
+
passForbidden: true,
|
|
43025
|
+
headers: {
|
|
43026
|
+
depth: "0",
|
|
43027
|
+
"content-type": "application/xml; charset=utf-8"
|
|
43028
|
+
},
|
|
43029
|
+
body: `<?xml version="1.0" encoding="utf-8"?>
|
|
43030
|
+
<d:sync-collection xmlns:d="DAV:"${namespaces ? ` ${namespaces}` : ""}>
|
|
43031
|
+
${syncToken ? `<d:sync-token>${xmlEscape(syncToken)}</d:sync-token>` : "<d:sync-token/>"}
|
|
43032
|
+
<d:sync-level>1</d:sync-level>
|
|
43033
|
+
<d:prop>${prop}</d:prop>
|
|
43034
|
+
</d:sync-collection>`
|
|
43035
|
+
});
|
|
43036
|
+
if (response.status !== 207) {
|
|
43037
|
+
let body = "";
|
|
43038
|
+
try {
|
|
43039
|
+
body = await response.text();
|
|
43040
|
+
} catch {}
|
|
43041
|
+
const preconditions = davPreconditions(body);
|
|
43042
|
+
const prefix = this.errorPrefix;
|
|
43043
|
+
if (preconditions.includes("valid-sync-token"))
|
|
43044
|
+
throw new DavFailure(`${prefix}:sync-token-invalid`);
|
|
43045
|
+
if (preconditions.includes("supported-report"))
|
|
43046
|
+
throw new DavFailure(`${prefix}:sync-unsupported`);
|
|
43047
|
+
if (response.status === 404)
|
|
43048
|
+
throw new DavFailure(`${prefix}:not-found`);
|
|
43049
|
+
if ([400, 409, 412].includes(response.status) && syncToken)
|
|
43050
|
+
throw new DavFailure(`${prefix}:sync-token-invalid`);
|
|
43051
|
+
if (response.status === 403)
|
|
43052
|
+
throw new DavFailure(`${prefix}:authentication-failed`);
|
|
43053
|
+
if ([400, 405, 415, 501].includes(response.status))
|
|
43054
|
+
throw new DavFailure(`${prefix}:sync-unsupported`);
|
|
43055
|
+
throw new DavFailure(`${prefix}:sync-failed`);
|
|
43056
|
+
}
|
|
43057
|
+
let multistatus;
|
|
43058
|
+
try {
|
|
43059
|
+
const parsed = parseDavXml(await response.text(), {
|
|
43060
|
+
parseTagValue: false
|
|
43061
|
+
}).multistatus;
|
|
43062
|
+
if (!parsed || typeof parsed !== "object")
|
|
43063
|
+
throw new Error;
|
|
43064
|
+
multistatus = parsed;
|
|
43065
|
+
} catch {
|
|
43066
|
+
throw new DavFailure(`${this.errorPrefix}:invalid-response`);
|
|
43067
|
+
}
|
|
43068
|
+
const token = multistatus["sync-token"];
|
|
43069
|
+
if (typeof token !== "string" || token.trim() === "")
|
|
43070
|
+
throw new DavFailure(`${this.errorPrefix}:invalid-response`);
|
|
43071
|
+
const result = {
|
|
43072
|
+
changed: [],
|
|
43073
|
+
removed: [],
|
|
43074
|
+
syncToken: token.trim(),
|
|
43075
|
+
truncated: false
|
|
43076
|
+
};
|
|
43077
|
+
for (const item of asArray(multistatus.response)) {
|
|
43078
|
+
const href = davHref(item.href);
|
|
43079
|
+
if (!href)
|
|
43080
|
+
throw new DavFailure(`${this.errorPrefix}:invalid-response`);
|
|
43081
|
+
const id = new URL(href, collection).href;
|
|
43082
|
+
const status = item.status;
|
|
43083
|
+
if (id === collection.href || `${id}/` === collection.href) {
|
|
43084
|
+
if (isStatus(status, 507))
|
|
43085
|
+
result.truncated = true;
|
|
43086
|
+
continue;
|
|
43087
|
+
}
|
|
43088
|
+
if (!isDavCollectionChild(collection.href, id))
|
|
43089
|
+
throw new DavFailure(`${this.errorPrefix}:invalid-response`);
|
|
43090
|
+
if (isStatus(status, 404))
|
|
43091
|
+
result.removed.push(id);
|
|
43092
|
+
else
|
|
43093
|
+
result.changed.push({ id, props: successfulProps(item) });
|
|
43094
|
+
}
|
|
43095
|
+
return result;
|
|
43096
|
+
}
|
|
43097
|
+
async syncResources(collection, options, syncToken) {
|
|
43098
|
+
const property = options.data.slice(options.data.indexOf(":") + 1);
|
|
43099
|
+
const sync = await this.syncCollection(collection, `<d:getetag/><${options.data}/>`, syncToken, options.namespaces);
|
|
43100
|
+
const resources = [];
|
|
43101
|
+
const missing = new Map;
|
|
43102
|
+
const add = (id, value, etag) => {
|
|
43103
|
+
const data = value && typeof value === "object" ? value["#text"] : value;
|
|
43104
|
+
if (typeof data !== "string")
|
|
43105
|
+
return false;
|
|
43106
|
+
resources.push({
|
|
43107
|
+
id,
|
|
43108
|
+
data,
|
|
43109
|
+
...typeof etag === "string" ? { etag } : {}
|
|
43110
|
+
});
|
|
43111
|
+
return true;
|
|
43112
|
+
};
|
|
43113
|
+
for (const member of sync.changed)
|
|
43114
|
+
if (!add(member.id, member.props[property], member.props.getetag))
|
|
43115
|
+
missing.set(member.id, member.props.getetag);
|
|
43116
|
+
if (missing.size) {
|
|
43117
|
+
const hrefs = [...missing.keys()].map((id) => `<d:href>${xmlEscape(new URL(id).pathname)}</d:href>`).join("");
|
|
43118
|
+
const response = await this.request(collection, {
|
|
43119
|
+
method: "REPORT",
|
|
43120
|
+
headers: {
|
|
43121
|
+
depth: "1",
|
|
43122
|
+
"content-type": "application/xml; charset=utf-8"
|
|
43123
|
+
},
|
|
43124
|
+
body: `<?xml version="1.0" encoding="utf-8"?>
|
|
43125
|
+
<${options.multiget} xmlns:d="DAV:" ${options.namespaces}>
|
|
43126
|
+
<d:prop><d:getetag/><${options.data}/></d:prop>
|
|
43127
|
+
${hrefs}
|
|
43128
|
+
</${options.multiget}>`
|
|
43129
|
+
});
|
|
43130
|
+
if (response.status !== 207)
|
|
43131
|
+
return rejectDavResponse(response, `${this.errorPrefix}:sync-failed`);
|
|
43132
|
+
for (const item of await this.readMultistatus(response)) {
|
|
43133
|
+
const href = davHref(item.href);
|
|
43134
|
+
if (!href)
|
|
43135
|
+
continue;
|
|
43136
|
+
const id = new URL(href, collection).href;
|
|
43137
|
+
if (!missing.has(id))
|
|
43138
|
+
continue;
|
|
43139
|
+
if (isStatus(item.status, 404)) {
|
|
43140
|
+
sync.removed.push(id);
|
|
43141
|
+
missing.delete(id);
|
|
43142
|
+
continue;
|
|
43143
|
+
}
|
|
43144
|
+
const props = successfulProps(item);
|
|
43145
|
+
if (add(id, props[property], props.getetag ?? missing.get(id)))
|
|
43146
|
+
missing.delete(id);
|
|
43147
|
+
}
|
|
43148
|
+
if (missing.size)
|
|
43149
|
+
throw new DavFailure(`${this.errorPrefix}:invalid-response`);
|
|
43150
|
+
}
|
|
43151
|
+
return {
|
|
43152
|
+
resources,
|
|
43153
|
+
removed: sync.removed,
|
|
43154
|
+
syncToken: sync.syncToken,
|
|
43155
|
+
truncated: sync.truncated
|
|
43156
|
+
};
|
|
43157
|
+
}
|
|
43158
|
+
async readMultistatus(response) {
|
|
42978
43159
|
try {
|
|
42979
43160
|
const parsed = parseDavXml(await response.text());
|
|
42980
43161
|
return asArray(parsed.multistatus?.response);
|
|
@@ -43275,7 +43456,7 @@ var ActivityStreamSchema = {
|
|
|
43275
43456
|
var package_default = {
|
|
43276
43457
|
name: "@sockethub/schemas",
|
|
43277
43458
|
description: "JSON schema files for validating Sockethub Activity Streams",
|
|
43278
|
-
version: "3.0.0-beta.
|
|
43459
|
+
version: "3.0.0-beta.6",
|
|
43279
43460
|
type: "module",
|
|
43280
43461
|
private: false,
|
|
43281
43462
|
author: "Nick Jennings <nick@silverbucket.net>",
|
|
@@ -43345,7 +43526,7 @@ var package_default = {
|
|
|
43345
43526
|
"ajv-formats": "^2.1.1",
|
|
43346
43527
|
"ajv-formats-draft2019": "^1.6.1"
|
|
43347
43528
|
},
|
|
43348
|
-
gitHead: "
|
|
43529
|
+
gitHead: "604f3da8850a19a4fd5f5e5dcec10bf283e9085e",
|
|
43349
43530
|
types: "./dist/index.d.ts"
|
|
43350
43531
|
};
|
|
43351
43532
|
|
|
@@ -44306,6 +44487,14 @@ function queryBody(query) {
|
|
|
44306
44487
|
${filter}
|
|
44307
44488
|
</c:addressbook-query>`;
|
|
44308
44489
|
}
|
|
44490
|
+
function contact(data, id, etag) {
|
|
44491
|
+
try {
|
|
44492
|
+
return parseVCard(data, id, typeof etag === "string" ? etag : undefined);
|
|
44493
|
+
} catch {
|
|
44494
|
+
throw new DavFailure("carddav:invalid-response");
|
|
44495
|
+
}
|
|
44496
|
+
}
|
|
44497
|
+
|
|
44309
44498
|
class CardDavClient extends DavClient {
|
|
44310
44499
|
constructor(url, authentication, timeoutMs = 15000, networkOptions = {}) {
|
|
44311
44500
|
super(url, authentication, "carddav", timeoutMs, networkOptions);
|
|
@@ -44371,29 +44560,30 @@ class CardDavClient extends DavClient {
|
|
|
44371
44560
|
});
|
|
44372
44561
|
if (response.status !== 207)
|
|
44373
44562
|
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
44563
|
const contacts = [];
|
|
44381
|
-
for (const item of
|
|
44564
|
+
for (const item of await this.readMultistatus(response)) {
|
|
44382
44565
|
const props = successfulProps(item);
|
|
44383
44566
|
const itemHref = davHref(item.href);
|
|
44384
44567
|
const data = props["address-data"];
|
|
44385
44568
|
if (!itemHref || typeof data !== "string")
|
|
44386
44569
|
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
|
-
}
|
|
44570
|
+
contacts.push(contact(data, new URL(itemHref, book.id).href, props.getetag));
|
|
44392
44571
|
if (query.limit && contacts.length >= query.limit)
|
|
44393
44572
|
break;
|
|
44394
44573
|
}
|
|
44395
44574
|
return contacts;
|
|
44396
44575
|
}
|
|
44576
|
+
async sync(book, syncToken) {
|
|
44577
|
+
const { resources, ...result } = await this.syncResources(new URL(book.id), {
|
|
44578
|
+
namespaces: 'xmlns:c="urn:ietf:params:xml:ns:carddav"',
|
|
44579
|
+
data: "c:address-data",
|
|
44580
|
+
multiget: "c:addressbook-multiget"
|
|
44581
|
+
}, syncToken);
|
|
44582
|
+
return {
|
|
44583
|
+
...result,
|
|
44584
|
+
items: resources.map((resource) => contact(resource.data, resource.id, resource.etag))
|
|
44585
|
+
};
|
|
44586
|
+
}
|
|
44397
44587
|
async create(book, input) {
|
|
44398
44588
|
const card = buildVCard(input, []);
|
|
44399
44589
|
const resource = new URL(`${encodeURIComponent(card.uid)}.vcf`, book.id.endsWith("/") ? book.id : `${book.id}/`);
|
|
@@ -44486,7 +44676,7 @@ class CardDavClient extends DavClient {
|
|
|
44486
44676
|
var package_default2 = {
|
|
44487
44677
|
name: "@sockethub/platform-carddav",
|
|
44488
44678
|
description: "A Sockethub platform for CardDAV address books and contacts",
|
|
44489
|
-
version: "1.0.0-beta.
|
|
44679
|
+
version: "1.0.0-beta.6",
|
|
44490
44680
|
type: "module",
|
|
44491
44681
|
private: false,
|
|
44492
44682
|
license: "LGPL-3.0+",
|
|
@@ -44515,21 +44705,21 @@ var package_default2 = {
|
|
|
44515
44705
|
test: "bun test src/"
|
|
44516
44706
|
},
|
|
44517
44707
|
dependencies: {
|
|
44518
|
-
"@sockethub/dav": "^1.0.0-beta.
|
|
44519
|
-
"@sockethub/schemas": "^3.0.0-beta.
|
|
44708
|
+
"@sockethub/dav": "^1.0.0-beta.6",
|
|
44709
|
+
"@sockethub/schemas": "^3.0.0-beta.6"
|
|
44520
44710
|
},
|
|
44521
44711
|
devDependencies: {
|
|
44522
44712
|
"@types/bun": "latest"
|
|
44523
44713
|
},
|
|
44524
44714
|
peerDependencies: {
|
|
44525
|
-
"@sockethub/server": "^5.0.0-beta.
|
|
44715
|
+
"@sockethub/server": "^5.0.0-beta.6"
|
|
44526
44716
|
},
|
|
44527
44717
|
peerDependenciesMeta: {
|
|
44528
44718
|
"@sockethub/server": {
|
|
44529
44719
|
optional: true
|
|
44530
44720
|
}
|
|
44531
44721
|
},
|
|
44532
|
-
gitHead: "
|
|
44722
|
+
gitHead: "604f3da8850a19a4fd5f5e5dcec10bf283e9085e"
|
|
44533
44723
|
};
|
|
44534
44724
|
|
|
44535
44725
|
// src/schema.ts
|
|
@@ -44637,7 +44827,7 @@ var shared = {
|
|
|
44637
44827
|
vcardVersion: { enum: ["3.0", "4.0"] },
|
|
44638
44828
|
updateSupported: { type: "boolean" }
|
|
44639
44829
|
};
|
|
44640
|
-
var
|
|
44830
|
+
var contact2 = {
|
|
44641
44831
|
type: "object",
|
|
44642
44832
|
required: ["type", "name"],
|
|
44643
44833
|
additionalProperties: false,
|
|
@@ -44658,6 +44848,13 @@ var query = {
|
|
|
44658
44848
|
limit: { type: "integer", minimum: 1, maximum: 1000 }
|
|
44659
44849
|
}
|
|
44660
44850
|
};
|
|
44851
|
+
var syncToken = { type: "string", minLength: 1, maxLength: 4096 };
|
|
44852
|
+
var sync = {
|
|
44853
|
+
type: "object",
|
|
44854
|
+
additionalProperties: false,
|
|
44855
|
+
properties: { syncToken, tokenOnly: { const: true } },
|
|
44856
|
+
not: { required: ["syncToken", "tokenOnly"] }
|
|
44857
|
+
};
|
|
44661
44858
|
var deletion = {
|
|
44662
44859
|
type: "object",
|
|
44663
44860
|
required: ["id", "type", "etag"],
|
|
@@ -44665,11 +44862,11 @@ var deletion = {
|
|
|
44665
44862
|
properties: { id: shared.id, type: shared.type, etag: shared.etag }
|
|
44666
44863
|
};
|
|
44667
44864
|
var updateContact = {
|
|
44668
|
-
...
|
|
44865
|
+
...contact2,
|
|
44669
44866
|
required: ["type", "name", "id", "uid", "etag"]
|
|
44670
44867
|
};
|
|
44671
44868
|
var responseContact = {
|
|
44672
|
-
...
|
|
44869
|
+
...contact2,
|
|
44673
44870
|
required: ["id", "type", "uid", "name", "vcardVersion", "updateSupported"]
|
|
44674
44871
|
};
|
|
44675
44872
|
var mutation = {
|
|
@@ -44727,10 +44924,20 @@ var PlatformCardDavSchema = {
|
|
|
44727
44924
|
messages: {
|
|
44728
44925
|
required: ["type", "actor"],
|
|
44729
44926
|
properties: {
|
|
44730
|
-
type: {
|
|
44927
|
+
type: {
|
|
44928
|
+
enum: ["fetch", "query", "sync", "create", "update", "delete"]
|
|
44929
|
+
},
|
|
44731
44930
|
actor,
|
|
44732
44931
|
target,
|
|
44733
|
-
object: {
|
|
44932
|
+
object: {
|
|
44933
|
+
oneOf: [
|
|
44934
|
+
contact2,
|
|
44935
|
+
query,
|
|
44936
|
+
deletion,
|
|
44937
|
+
{ ...sync, required: ["syncToken"] },
|
|
44938
|
+
{ ...sync, required: ["tokenOnly"] }
|
|
44939
|
+
]
|
|
44940
|
+
}
|
|
44734
44941
|
}
|
|
44735
44942
|
},
|
|
44736
44943
|
messageConstraints: {
|
|
@@ -44747,7 +44954,11 @@ var PlatformCardDavSchema = {
|
|
|
44747
44954
|
required: ["type", "actor", "target"]
|
|
44748
44955
|
},
|
|
44749
44956
|
{
|
|
44750
|
-
properties: { type: { const: "
|
|
44957
|
+
properties: { type: { const: "sync" }, object: sync },
|
|
44958
|
+
required: ["type", "actor", "target"]
|
|
44959
|
+
},
|
|
44960
|
+
{
|
|
44961
|
+
properties: { type: { const: "create" }, object: contact2 },
|
|
44751
44962
|
required: ["type", "actor", "target", "object"]
|
|
44752
44963
|
},
|
|
44753
44964
|
{
|
|
@@ -44800,6 +45011,30 @@ var PlatformCardDavSchema = {
|
|
|
44800
45011
|
}
|
|
44801
45012
|
}
|
|
44802
45013
|
},
|
|
45014
|
+
{
|
|
45015
|
+
type: "object",
|
|
45016
|
+
required: [
|
|
45017
|
+
"@context",
|
|
45018
|
+
"type",
|
|
45019
|
+
"totalItems",
|
|
45020
|
+
"items",
|
|
45021
|
+
"removed",
|
|
45022
|
+
"syncToken",
|
|
45023
|
+
"truncated"
|
|
45024
|
+
],
|
|
45025
|
+
additionalProperties: false,
|
|
45026
|
+
properties: {
|
|
45027
|
+
"@context": { type: "array", items: { type: "string" } },
|
|
45028
|
+
id: { type: ["string", "null"] },
|
|
45029
|
+
type: { const: "collection" },
|
|
45030
|
+
summary: { type: "string" },
|
|
45031
|
+
totalItems: { type: "integer", minimum: 0 },
|
|
45032
|
+
items: { type: "array", items: responseContact },
|
|
45033
|
+
removed: { type: "array", items: shared.id },
|
|
45034
|
+
syncToken,
|
|
45035
|
+
truncated: { type: "boolean" }
|
|
45036
|
+
}
|
|
45037
|
+
},
|
|
44803
45038
|
{
|
|
44804
45039
|
type: "object",
|
|
44805
45040
|
required: ["@context", "type", "actor", "target", "object"],
|
|
@@ -44825,7 +45060,14 @@ class CardDav {
|
|
|
44825
45060
|
discovery = new DiscoveryCache;
|
|
44826
45061
|
config = {
|
|
44827
45062
|
persist: false,
|
|
44828
|
-
requireCredentials: [
|
|
45063
|
+
requireCredentials: [
|
|
45064
|
+
"fetch",
|
|
45065
|
+
"query",
|
|
45066
|
+
"sync",
|
|
45067
|
+
"create",
|
|
45068
|
+
"update",
|
|
45069
|
+
"delete"
|
|
45070
|
+
],
|
|
44829
45071
|
connectTimeoutMs: 15000,
|
|
44830
45072
|
allowPrivateAddresses: false,
|
|
44831
45073
|
allowInsecureHttp: false,
|
|
@@ -44866,6 +45108,23 @@ class CardDav {
|
|
|
44866
45108
|
items
|
|
44867
45109
|
})).catch((error) => this.fail(job, error, done, credentials)).finally(() => client.close().catch(() => {}));
|
|
44868
45110
|
}
|
|
45111
|
+
sync(job, credentials, done) {
|
|
45112
|
+
const client = this.client(credentials);
|
|
45113
|
+
const input = job.object ?? {};
|
|
45114
|
+
this.book(client, credentials, job.target?.id ?? "").then((book) => input.tokenOnly ? client.currentSyncToken(new URL(book.id)).then((syncToken) => ({
|
|
45115
|
+
items: [],
|
|
45116
|
+
removed: [],
|
|
45117
|
+
syncToken,
|
|
45118
|
+
truncated: false
|
|
45119
|
+
})) : client.sync(book, input.syncToken)).then((result) => done(null, {
|
|
45120
|
+
"@context": CONTEXT,
|
|
45121
|
+
...job.id ? { id: job.id } : {},
|
|
45122
|
+
type: "collection",
|
|
45123
|
+
summary: "CardDAV changes",
|
|
45124
|
+
totalItems: result.items.length,
|
|
45125
|
+
...result
|
|
45126
|
+
})).catch((error) => this.fail(job, error, done, credentials)).finally(() => client.close().catch(() => {}));
|
|
45127
|
+
}
|
|
44869
45128
|
create(job, credentials, done) {
|
|
44870
45129
|
const client = this.client(credentials);
|
|
44871
45130
|
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 +45205,4 @@ export {
|
|
|
44946
45205
|
parseVCard
|
|
44947
45206
|
};
|
|
44948
45207
|
|
|
44949
|
-
//# debugId=
|
|
45208
|
+
//# debugId=9916E0836EEA9D2664756E2164756E21
|