feat: implement pagination for fetching contacts and add maxObjectsInGet capability
This commit is contained in:
+68
-37
@@ -2066,6 +2066,11 @@ export class JMAPClient {
|
|||||||
return coreCapability?.maxCallsInRequest || 50;
|
return coreCapability?.maxCallsInRequest || 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMaxObjectsInGet(): number {
|
||||||
|
const coreCapability = this.capabilities["urn:ietf:params:jmap:core"] as { maxObjectsInGet?: number } | undefined;
|
||||||
|
return coreCapability?.maxObjectsInGet || 500;
|
||||||
|
}
|
||||||
|
|
||||||
getEventSourceUrl(): string | null {
|
getEventSourceUrl(): string | null {
|
||||||
if (!this.session) return null;
|
if (!this.session) return null;
|
||||||
|
|
||||||
@@ -2428,26 +2433,62 @@ export class JMAPClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getContacts(addressBookId?: string): Promise<ContactCard[]> {
|
private async fetchPaginatedContacts(
|
||||||
try {
|
accountId: string,
|
||||||
const accountId = this.getContactsAccountId();
|
filter?: Record<string, unknown>,
|
||||||
const queryArgs: Record<string, unknown> = { accountId, limit: 1000 };
|
): Promise<ContactCard[]> {
|
||||||
if (addressBookId) {
|
const batchSize = this.getMaxObjectsInGet();
|
||||||
queryArgs.filter = { inAddressBook: addressBookId };
|
const allIds: string[] = [];
|
||||||
|
let position = 0;
|
||||||
|
|
||||||
|
// Paginate ContactCard/query to collect all IDs
|
||||||
|
for (;;) {
|
||||||
|
const queryArgs: Record<string, unknown> = { accountId, position, limit: batchSize };
|
||||||
|
if (filter) {
|
||||||
|
queryArgs.filter = filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.request([
|
const response = await this.request([
|
||||||
["ContactCard/query", queryArgs, "0"],
|
["ContactCard/query", queryArgs, "q"],
|
||||||
["ContactCard/get", {
|
|
||||||
accountId,
|
|
||||||
"#ids": { resultOf: "0", name: "ContactCard/query", path: "/ids" },
|
|
||||||
}, "1"],
|
|
||||||
], this.contactUsing());
|
], this.contactUsing());
|
||||||
|
|
||||||
if (response.methodResponses?.[1]?.[0] === "ContactCard/get") {
|
const queryResult = response.methodResponses?.[0];
|
||||||
return (response.methodResponses[1][1].list || []) as ContactCard[];
|
if (queryResult?.[0] !== "ContactCard/query") break;
|
||||||
|
|
||||||
|
const ids: string[] = queryResult[1].ids || [];
|
||||||
|
allIds.push(...ids);
|
||||||
|
|
||||||
|
const total: number = queryResult[1].total ?? -1;
|
||||||
|
if (ids.length < batchSize || (total > 0 && allIds.length >= total)) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return [];
|
position += ids.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allIds.length === 0) return [];
|
||||||
|
|
||||||
|
// Batch ContactCard/get to respect maxObjectsInGet
|
||||||
|
const allContacts: ContactCard[] = [];
|
||||||
|
for (let i = 0; i < allIds.length; i += batchSize) {
|
||||||
|
const chunk = allIds.slice(i, i + batchSize);
|
||||||
|
const response = await this.request([
|
||||||
|
["ContactCard/get", { accountId, ids: chunk }, "g"],
|
||||||
|
], this.contactUsing());
|
||||||
|
|
||||||
|
if (response.methodResponses?.[0]?.[0] === "ContactCard/get") {
|
||||||
|
const list = (response.methodResponses[0][1].list || []) as ContactCard[];
|
||||||
|
allContacts.push(...list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allContacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getContacts(addressBookId?: string): Promise<ContactCard[]> {
|
||||||
|
try {
|
||||||
|
const accountId = this.getContactsAccountId();
|
||||||
|
const filter = addressBookId ? { inAddressBook: addressBookId } : undefined;
|
||||||
|
return await this.fetchPaginatedContacts(accountId, filter);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to get contacts:', error);
|
console.error('Failed to get contacts:', error);
|
||||||
return [];
|
return [];
|
||||||
@@ -2465,29 +2506,19 @@ export class JMAPClient {
|
|||||||
const account = this.accounts[accountId];
|
const account = this.accounts[accountId];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.request([
|
const rawContacts = await this.fetchPaginatedContacts(accountId);
|
||||||
["ContactCard/query", { accountId, limit: 1000 }, "0"],
|
const contacts = rawContacts.map((contact) => ({
|
||||||
["ContactCard/get", {
|
...contact,
|
||||||
accountId,
|
id: isPrimary ? contact.id : `${accountId}:${contact.id}`,
|
||||||
"#ids": { resultOf: "0", name: "ContactCard/query", path: "/ids" },
|
originalId: contact.id,
|
||||||
}, "1"],
|
addressBookIds: isPrimary ? contact.addressBookIds : (contact.addressBookIds ? Object.fromEntries(
|
||||||
], this.contactUsing());
|
Object.entries(contact.addressBookIds).map(([bookId, v]) => [`${accountId}:${bookId}`, v])
|
||||||
|
) : contact.addressBookIds),
|
||||||
if (response.methodResponses?.[1]?.[0] === "ContactCard/get") {
|
accountId,
|
||||||
const rawContacts = (response.methodResponses[1][1].list || []) as ContactCard[];
|
accountName: account?.name || (isPrimary ? this.username : accountId),
|
||||||
const contacts = rawContacts.map((contact) => ({
|
isShared: !isPrimary,
|
||||||
...contact,
|
}));
|
||||||
id: isPrimary ? contact.id : `${accountId}:${contact.id}`,
|
allContacts.push(...contacts);
|
||||||
originalId: contact.id,
|
|
||||||
addressBookIds: isPrimary ? contact.addressBookIds : (contact.addressBookIds ? Object.fromEntries(
|
|
||||||
Object.entries(contact.addressBookIds).map(([bookId, v]) => [`${accountId}:${bookId}`, v])
|
|
||||||
) : contact.addressBookIds),
|
|
||||||
accountId,
|
|
||||||
accountName: account?.name || (isPrimary ? this.username : accountId),
|
|
||||||
isShared: !isPrimary,
|
|
||||||
}));
|
|
||||||
allContacts.push(...contacts);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to fetch contacts for account ${accountId}:`, error);
|
console.error(`Failed to fetch contacts for account ${accountId}:`, error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user