@upyo/jmap 0.4.0-dev.73 → 0.4.0-dev.77
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/dist/index.cjs +153 -13
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +153 -13
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -114,10 +114,10 @@ function createJmapConfig(config) {
|
|
|
114
114
|
basicAuth: config.basicAuth ?? null,
|
|
115
115
|
accountId: config.accountId ?? null,
|
|
116
116
|
identityId: config.identityId ?? null,
|
|
117
|
-
timeout: config.timeout
|
|
118
|
-
retries: config.retries
|
|
117
|
+
timeout: config.timeout ?? 3e4,
|
|
118
|
+
retries: config.retries ?? 3,
|
|
119
119
|
headers: config.headers ?? {},
|
|
120
|
-
sessionCacheTtl: config.sessionCacheTtl
|
|
120
|
+
sessionCacheTtl: config.sessionCacheTtl ?? 3e5,
|
|
121
121
|
baseUrl: config.baseUrl ?? null
|
|
122
122
|
};
|
|
123
123
|
}
|
|
@@ -207,12 +207,18 @@ var JmapHttpClient = class {
|
|
|
207
207
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
208
208
|
let combinedSignal = controller.signal;
|
|
209
209
|
if (signal) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
const AbortSignalAny = AbortSignal.any;
|
|
211
|
+
if (typeof AbortSignalAny === "function") combinedSignal = AbortSignalAny([signal, controller.signal]);
|
|
212
|
+
else {
|
|
213
|
+
if (signal.aborted) {
|
|
214
|
+
clearTimeout(timeoutId);
|
|
215
|
+
signal.throwIfAborted();
|
|
216
|
+
}
|
|
217
|
+
signal.addEventListener("abort", () => {
|
|
218
|
+
controller.abort(signal.reason);
|
|
219
|
+
});
|
|
220
|
+
combinedSignal = controller.signal;
|
|
213
221
|
}
|
|
214
|
-
signal.addEventListener("abort", () => controller.abort());
|
|
215
|
-
combinedSignal = controller.signal;
|
|
216
222
|
}
|
|
217
223
|
try {
|
|
218
224
|
return await globalThis.fetch(url, {
|
|
@@ -516,14 +522,96 @@ var JmapTransport = class {
|
|
|
516
522
|
}
|
|
517
523
|
}
|
|
518
524
|
/**
|
|
519
|
-
* Sends multiple messages
|
|
525
|
+
* Sends multiple messages in a single batched JMAP request.
|
|
520
526
|
* @param messages The messages to send.
|
|
521
527
|
* @param options Optional transport options.
|
|
522
528
|
* @yields Receipts for each message.
|
|
523
529
|
* @since 0.4.0
|
|
524
530
|
*/
|
|
525
531
|
async *sendMany(messages, options) {
|
|
526
|
-
|
|
532
|
+
const signal = options?.signal;
|
|
533
|
+
const messageArray = [];
|
|
534
|
+
for await (const message of messages) messageArray.push(message);
|
|
535
|
+
if (messageArray.length === 0) return;
|
|
536
|
+
let processingStage = "initialization";
|
|
537
|
+
let attachmentsUploadedCount = 0;
|
|
538
|
+
try {
|
|
539
|
+
signal?.throwIfAborted();
|
|
540
|
+
processingStage = "session fetch";
|
|
541
|
+
const session = await this.getSession(signal);
|
|
542
|
+
signal?.throwIfAborted();
|
|
543
|
+
processingStage = "account discovery";
|
|
544
|
+
const accountId = this.config.accountId ?? findMailAccount(session);
|
|
545
|
+
if (!accountId) {
|
|
546
|
+
for (let i = 0; i < messageArray.length; i++) yield {
|
|
547
|
+
successful: false,
|
|
548
|
+
errorMessages: ["No mail-capable account found in JMAP session"]
|
|
549
|
+
};
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
processingStage = "mailbox discovery";
|
|
553
|
+
const draftsMailboxId = await this.getDraftsMailboxId(session, accountId, signal);
|
|
554
|
+
signal?.throwIfAborted();
|
|
555
|
+
processingStage = "identity resolution";
|
|
556
|
+
const identityMap = await this.getIdentityMap(session, accountId, signal);
|
|
557
|
+
signal?.throwIfAborted();
|
|
558
|
+
processingStage = "attachment upload";
|
|
559
|
+
const allUploadedBlobs = /* @__PURE__ */ new Map();
|
|
560
|
+
for (let i = 0; i < messageArray.length; i++) {
|
|
561
|
+
const message = messageArray[i];
|
|
562
|
+
const uploadedBlobs = await this.uploadAttachments(session, accountId, message.attachments, signal);
|
|
563
|
+
allUploadedBlobs.set(i, uploadedBlobs);
|
|
564
|
+
attachmentsUploadedCount = i + 1;
|
|
565
|
+
signal?.throwIfAborted();
|
|
566
|
+
}
|
|
567
|
+
processingStage = "message conversion";
|
|
568
|
+
const emailCreates = {};
|
|
569
|
+
const submissionCreates = {};
|
|
570
|
+
for (let i = 0; i < messageArray.length; i++) {
|
|
571
|
+
const message = messageArray[i];
|
|
572
|
+
const uploadedBlobs = allUploadedBlobs.get(i);
|
|
573
|
+
const emailCreate = convertMessage(message, draftsMailboxId, uploadedBlobs);
|
|
574
|
+
const senderEmail = message.sender.address.toLowerCase();
|
|
575
|
+
const identityId = identityMap.get(senderEmail) ?? identityMap.values().next().value;
|
|
576
|
+
emailCreates[`draft${i}`] = emailCreate;
|
|
577
|
+
submissionCreates[`sub${i}`] = {
|
|
578
|
+
identityId,
|
|
579
|
+
emailId: `#draft${i}`
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
processingStage = "batch request execution";
|
|
583
|
+
const response = await this.httpClient.executeRequest(session.apiUrl, {
|
|
584
|
+
using: [
|
|
585
|
+
JMAP_CAPABILITIES.core,
|
|
586
|
+
JMAP_CAPABILITIES.mail,
|
|
587
|
+
JMAP_CAPABILITIES.submission
|
|
588
|
+
],
|
|
589
|
+
methodCalls: [[
|
|
590
|
+
"Email/set",
|
|
591
|
+
{
|
|
592
|
+
accountId,
|
|
593
|
+
create: emailCreates
|
|
594
|
+
},
|
|
595
|
+
"c0"
|
|
596
|
+
], [
|
|
597
|
+
"EmailSubmission/set",
|
|
598
|
+
{
|
|
599
|
+
accountId,
|
|
600
|
+
create: submissionCreates
|
|
601
|
+
},
|
|
602
|
+
"c1"
|
|
603
|
+
]]
|
|
604
|
+
}, signal);
|
|
605
|
+
for (let i = 0; i < messageArray.length; i++) yield this.parseBatchResponseForIndex(response, i);
|
|
606
|
+
} catch (error) {
|
|
607
|
+
const baseMessage = error instanceof Error && error.name === "AbortError" ? `Request aborted: ${error.message}` : error instanceof JmapApiError ? error.message : error instanceof Error ? error.message : String(error);
|
|
608
|
+
let detailedMessage = `Failed during ${processingStage}: ${baseMessage}`;
|
|
609
|
+
if (processingStage === "attachment upload" && attachmentsUploadedCount > 0) detailedMessage += ` (${attachmentsUploadedCount}/${messageArray.length} messages had attachments uploaded before failure)`;
|
|
610
|
+
for (let i = 0; i < messageArray.length; i++) yield {
|
|
611
|
+
successful: false,
|
|
612
|
+
errorMessages: [detailedMessage]
|
|
613
|
+
};
|
|
614
|
+
}
|
|
527
615
|
}
|
|
528
616
|
/**
|
|
529
617
|
* Gets or refreshes the JMAP session.
|
|
@@ -609,6 +697,21 @@ var JmapTransport = class {
|
|
|
609
697
|
*/
|
|
610
698
|
async getIdentityId(session, accountId, senderEmail, signal) {
|
|
611
699
|
if (this.config.identityId) return this.config.identityId;
|
|
700
|
+
const identityMap = await this.getIdentityMap(session, accountId, signal);
|
|
701
|
+
const matching = identityMap.get(senderEmail.toLowerCase());
|
|
702
|
+
if (matching) return matching;
|
|
703
|
+
return identityMap.values().next().value;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Gets all identities and builds a map of email to identity ID.
|
|
707
|
+
* @param session The JMAP session.
|
|
708
|
+
* @param accountId The account ID.
|
|
709
|
+
* @param signal Optional abort signal.
|
|
710
|
+
* @returns Map of lowercase email to identity ID.
|
|
711
|
+
* @since 0.4.0
|
|
712
|
+
*/
|
|
713
|
+
async getIdentityMap(session, accountId, signal) {
|
|
714
|
+
if (this.config.identityId) return new Map([["*", this.config.identityId]]);
|
|
612
715
|
const response = await this.httpClient.executeRequest(session.apiUrl, {
|
|
613
716
|
using: [JMAP_CAPABILITIES.core, JMAP_CAPABILITIES.submission],
|
|
614
717
|
methodCalls: [[
|
|
@@ -621,9 +724,9 @@ var JmapTransport = class {
|
|
|
621
724
|
if (!identityResponse) throw new JmapApiError("No Identity/get response received");
|
|
622
725
|
const identities = identityResponse[1].list;
|
|
623
726
|
if (!identities || identities.length === 0) throw new JmapApiError("No identities found");
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
return
|
|
727
|
+
const identityMap = /* @__PURE__ */ new Map();
|
|
728
|
+
for (const identity of identities) identityMap.set(identity.email.toLowerCase(), identity.id);
|
|
729
|
+
return identityMap;
|
|
627
730
|
}
|
|
628
731
|
/**
|
|
629
732
|
* Uploads all attachments and returns a map of contentId to blobId.
|
|
@@ -674,6 +777,43 @@ var JmapTransport = class {
|
|
|
674
777
|
errorMessages: errors
|
|
675
778
|
};
|
|
676
779
|
}
|
|
780
|
+
/**
|
|
781
|
+
* Parses the JMAP batch response to extract receipt for a specific index.
|
|
782
|
+
* @param response The JMAP response.
|
|
783
|
+
* @param index The message index in the batch.
|
|
784
|
+
* @returns A receipt indicating success or failure for that message.
|
|
785
|
+
* @since 0.4.0
|
|
786
|
+
*/
|
|
787
|
+
parseBatchResponseForIndex(response, index) {
|
|
788
|
+
const errors = [];
|
|
789
|
+
const draftKey = `draft${index}`;
|
|
790
|
+
const subKey = `sub${index}`;
|
|
791
|
+
const emailResponse = response.methodResponses.find((r) => r[0] === "Email/set");
|
|
792
|
+
if (emailResponse) {
|
|
793
|
+
const emailResult = emailResponse[1];
|
|
794
|
+
if (emailResult.notCreated?.[draftKey]) {
|
|
795
|
+
const error = emailResult.notCreated[draftKey];
|
|
796
|
+
errors.push(`Email creation failed: ${error.type}${error.description ? ` - ${error.description}` : ""}`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
const submissionResponse = response.methodResponses.find((r) => r[0] === "EmailSubmission/set");
|
|
800
|
+
if (submissionResponse) {
|
|
801
|
+
const submissionResult = submissionResponse[1];
|
|
802
|
+
if (submissionResult.notCreated?.[subKey]) {
|
|
803
|
+
const error = submissionResult.notCreated[subKey];
|
|
804
|
+
errors.push(`Email submission failed: ${error.type}${error.description ? ` - ${error.description}` : ""}`);
|
|
805
|
+
}
|
|
806
|
+
if (submissionResult.created?.[subKey]) return {
|
|
807
|
+
successful: true,
|
|
808
|
+
messageId: submissionResult.created[subKey].id
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
if (errors.length === 0) errors.push("Unknown error: No submission result received");
|
|
812
|
+
return {
|
|
813
|
+
successful: false,
|
|
814
|
+
errorMessages: errors
|
|
815
|
+
};
|
|
816
|
+
}
|
|
677
817
|
};
|
|
678
818
|
|
|
679
819
|
//#endregion
|
package/dist/index.d.cts
CHANGED
|
@@ -122,7 +122,7 @@ declare class JmapTransport implements Transport {
|
|
|
122
122
|
*/
|
|
123
123
|
send(message: Message, options?: TransportOptions): Promise<Receipt>;
|
|
124
124
|
/**
|
|
125
|
-
* Sends multiple messages
|
|
125
|
+
* Sends multiple messages in a single batched JMAP request.
|
|
126
126
|
* @param messages The messages to send.
|
|
127
127
|
* @param options Optional transport options.
|
|
128
128
|
* @yields Receipts for each message.
|
|
@@ -162,6 +162,15 @@ declare class JmapTransport implements Transport {
|
|
|
162
162
|
* @since 0.4.0
|
|
163
163
|
*/
|
|
164
164
|
private getIdentityId;
|
|
165
|
+
/**
|
|
166
|
+
* Gets all identities and builds a map of email to identity ID.
|
|
167
|
+
* @param session The JMAP session.
|
|
168
|
+
* @param accountId The account ID.
|
|
169
|
+
* @param signal Optional abort signal.
|
|
170
|
+
* @returns Map of lowercase email to identity ID.
|
|
171
|
+
* @since 0.4.0
|
|
172
|
+
*/
|
|
173
|
+
private getIdentityMap;
|
|
165
174
|
/**
|
|
166
175
|
* Uploads all attachments and returns a map of contentId to blobId.
|
|
167
176
|
* @param session The JMAP session.
|
|
@@ -179,6 +188,14 @@ declare class JmapTransport implements Transport {
|
|
|
179
188
|
* @since 0.4.0
|
|
180
189
|
*/
|
|
181
190
|
private parseResponse;
|
|
191
|
+
/**
|
|
192
|
+
* Parses the JMAP batch response to extract receipt for a specific index.
|
|
193
|
+
* @param response The JMAP response.
|
|
194
|
+
* @param index The message index in the batch.
|
|
195
|
+
* @returns A receipt indicating success or failure for that message.
|
|
196
|
+
* @since 0.4.0
|
|
197
|
+
*/
|
|
198
|
+
private parseBatchResponseForIndex;
|
|
182
199
|
}
|
|
183
200
|
//#endregion
|
|
184
201
|
//#region src/errors.d.ts
|
package/dist/index.d.ts
CHANGED
|
@@ -122,7 +122,7 @@ declare class JmapTransport implements Transport {
|
|
|
122
122
|
*/
|
|
123
123
|
send(message: Message, options?: TransportOptions): Promise<Receipt>;
|
|
124
124
|
/**
|
|
125
|
-
* Sends multiple messages
|
|
125
|
+
* Sends multiple messages in a single batched JMAP request.
|
|
126
126
|
* @param messages The messages to send.
|
|
127
127
|
* @param options Optional transport options.
|
|
128
128
|
* @yields Receipts for each message.
|
|
@@ -162,6 +162,15 @@ declare class JmapTransport implements Transport {
|
|
|
162
162
|
* @since 0.4.0
|
|
163
163
|
*/
|
|
164
164
|
private getIdentityId;
|
|
165
|
+
/**
|
|
166
|
+
* Gets all identities and builds a map of email to identity ID.
|
|
167
|
+
* @param session The JMAP session.
|
|
168
|
+
* @param accountId The account ID.
|
|
169
|
+
* @param signal Optional abort signal.
|
|
170
|
+
* @returns Map of lowercase email to identity ID.
|
|
171
|
+
* @since 0.4.0
|
|
172
|
+
*/
|
|
173
|
+
private getIdentityMap;
|
|
165
174
|
/**
|
|
166
175
|
* Uploads all attachments and returns a map of contentId to blobId.
|
|
167
176
|
* @param session The JMAP session.
|
|
@@ -179,6 +188,14 @@ declare class JmapTransport implements Transport {
|
|
|
179
188
|
* @since 0.4.0
|
|
180
189
|
*/
|
|
181
190
|
private parseResponse;
|
|
191
|
+
/**
|
|
192
|
+
* Parses the JMAP batch response to extract receipt for a specific index.
|
|
193
|
+
* @param response The JMAP response.
|
|
194
|
+
* @param index The message index in the batch.
|
|
195
|
+
* @returns A receipt indicating success or failure for that message.
|
|
196
|
+
* @since 0.4.0
|
|
197
|
+
*/
|
|
198
|
+
private parseBatchResponseForIndex;
|
|
182
199
|
}
|
|
183
200
|
//#endregion
|
|
184
201
|
//#region src/errors.d.ts
|
package/dist/index.js
CHANGED
|
@@ -113,10 +113,10 @@ function createJmapConfig(config) {
|
|
|
113
113
|
basicAuth: config.basicAuth ?? null,
|
|
114
114
|
accountId: config.accountId ?? null,
|
|
115
115
|
identityId: config.identityId ?? null,
|
|
116
|
-
timeout: config.timeout
|
|
117
|
-
retries: config.retries
|
|
116
|
+
timeout: config.timeout ?? 3e4,
|
|
117
|
+
retries: config.retries ?? 3,
|
|
118
118
|
headers: config.headers ?? {},
|
|
119
|
-
sessionCacheTtl: config.sessionCacheTtl
|
|
119
|
+
sessionCacheTtl: config.sessionCacheTtl ?? 3e5,
|
|
120
120
|
baseUrl: config.baseUrl ?? null
|
|
121
121
|
};
|
|
122
122
|
}
|
|
@@ -206,12 +206,18 @@ var JmapHttpClient = class {
|
|
|
206
206
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
207
207
|
let combinedSignal = controller.signal;
|
|
208
208
|
if (signal) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
209
|
+
const AbortSignalAny = AbortSignal.any;
|
|
210
|
+
if (typeof AbortSignalAny === "function") combinedSignal = AbortSignalAny([signal, controller.signal]);
|
|
211
|
+
else {
|
|
212
|
+
if (signal.aborted) {
|
|
213
|
+
clearTimeout(timeoutId);
|
|
214
|
+
signal.throwIfAborted();
|
|
215
|
+
}
|
|
216
|
+
signal.addEventListener("abort", () => {
|
|
217
|
+
controller.abort(signal.reason);
|
|
218
|
+
});
|
|
219
|
+
combinedSignal = controller.signal;
|
|
212
220
|
}
|
|
213
|
-
signal.addEventListener("abort", () => controller.abort());
|
|
214
|
-
combinedSignal = controller.signal;
|
|
215
221
|
}
|
|
216
222
|
try {
|
|
217
223
|
return await globalThis.fetch(url, {
|
|
@@ -515,14 +521,96 @@ var JmapTransport = class {
|
|
|
515
521
|
}
|
|
516
522
|
}
|
|
517
523
|
/**
|
|
518
|
-
* Sends multiple messages
|
|
524
|
+
* Sends multiple messages in a single batched JMAP request.
|
|
519
525
|
* @param messages The messages to send.
|
|
520
526
|
* @param options Optional transport options.
|
|
521
527
|
* @yields Receipts for each message.
|
|
522
528
|
* @since 0.4.0
|
|
523
529
|
*/
|
|
524
530
|
async *sendMany(messages, options) {
|
|
525
|
-
|
|
531
|
+
const signal = options?.signal;
|
|
532
|
+
const messageArray = [];
|
|
533
|
+
for await (const message of messages) messageArray.push(message);
|
|
534
|
+
if (messageArray.length === 0) return;
|
|
535
|
+
let processingStage = "initialization";
|
|
536
|
+
let attachmentsUploadedCount = 0;
|
|
537
|
+
try {
|
|
538
|
+
signal?.throwIfAborted();
|
|
539
|
+
processingStage = "session fetch";
|
|
540
|
+
const session = await this.getSession(signal);
|
|
541
|
+
signal?.throwIfAborted();
|
|
542
|
+
processingStage = "account discovery";
|
|
543
|
+
const accountId = this.config.accountId ?? findMailAccount(session);
|
|
544
|
+
if (!accountId) {
|
|
545
|
+
for (let i = 0; i < messageArray.length; i++) yield {
|
|
546
|
+
successful: false,
|
|
547
|
+
errorMessages: ["No mail-capable account found in JMAP session"]
|
|
548
|
+
};
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
processingStage = "mailbox discovery";
|
|
552
|
+
const draftsMailboxId = await this.getDraftsMailboxId(session, accountId, signal);
|
|
553
|
+
signal?.throwIfAborted();
|
|
554
|
+
processingStage = "identity resolution";
|
|
555
|
+
const identityMap = await this.getIdentityMap(session, accountId, signal);
|
|
556
|
+
signal?.throwIfAborted();
|
|
557
|
+
processingStage = "attachment upload";
|
|
558
|
+
const allUploadedBlobs = /* @__PURE__ */ new Map();
|
|
559
|
+
for (let i = 0; i < messageArray.length; i++) {
|
|
560
|
+
const message = messageArray[i];
|
|
561
|
+
const uploadedBlobs = await this.uploadAttachments(session, accountId, message.attachments, signal);
|
|
562
|
+
allUploadedBlobs.set(i, uploadedBlobs);
|
|
563
|
+
attachmentsUploadedCount = i + 1;
|
|
564
|
+
signal?.throwIfAborted();
|
|
565
|
+
}
|
|
566
|
+
processingStage = "message conversion";
|
|
567
|
+
const emailCreates = {};
|
|
568
|
+
const submissionCreates = {};
|
|
569
|
+
for (let i = 0; i < messageArray.length; i++) {
|
|
570
|
+
const message = messageArray[i];
|
|
571
|
+
const uploadedBlobs = allUploadedBlobs.get(i);
|
|
572
|
+
const emailCreate = convertMessage(message, draftsMailboxId, uploadedBlobs);
|
|
573
|
+
const senderEmail = message.sender.address.toLowerCase();
|
|
574
|
+
const identityId = identityMap.get(senderEmail) ?? identityMap.values().next().value;
|
|
575
|
+
emailCreates[`draft${i}`] = emailCreate;
|
|
576
|
+
submissionCreates[`sub${i}`] = {
|
|
577
|
+
identityId,
|
|
578
|
+
emailId: `#draft${i}`
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
processingStage = "batch request execution";
|
|
582
|
+
const response = await this.httpClient.executeRequest(session.apiUrl, {
|
|
583
|
+
using: [
|
|
584
|
+
JMAP_CAPABILITIES.core,
|
|
585
|
+
JMAP_CAPABILITIES.mail,
|
|
586
|
+
JMAP_CAPABILITIES.submission
|
|
587
|
+
],
|
|
588
|
+
methodCalls: [[
|
|
589
|
+
"Email/set",
|
|
590
|
+
{
|
|
591
|
+
accountId,
|
|
592
|
+
create: emailCreates
|
|
593
|
+
},
|
|
594
|
+
"c0"
|
|
595
|
+
], [
|
|
596
|
+
"EmailSubmission/set",
|
|
597
|
+
{
|
|
598
|
+
accountId,
|
|
599
|
+
create: submissionCreates
|
|
600
|
+
},
|
|
601
|
+
"c1"
|
|
602
|
+
]]
|
|
603
|
+
}, signal);
|
|
604
|
+
for (let i = 0; i < messageArray.length; i++) yield this.parseBatchResponseForIndex(response, i);
|
|
605
|
+
} catch (error) {
|
|
606
|
+
const baseMessage = error instanceof Error && error.name === "AbortError" ? `Request aborted: ${error.message}` : error instanceof JmapApiError ? error.message : error instanceof Error ? error.message : String(error);
|
|
607
|
+
let detailedMessage = `Failed during ${processingStage}: ${baseMessage}`;
|
|
608
|
+
if (processingStage === "attachment upload" && attachmentsUploadedCount > 0) detailedMessage += ` (${attachmentsUploadedCount}/${messageArray.length} messages had attachments uploaded before failure)`;
|
|
609
|
+
for (let i = 0; i < messageArray.length; i++) yield {
|
|
610
|
+
successful: false,
|
|
611
|
+
errorMessages: [detailedMessage]
|
|
612
|
+
};
|
|
613
|
+
}
|
|
526
614
|
}
|
|
527
615
|
/**
|
|
528
616
|
* Gets or refreshes the JMAP session.
|
|
@@ -608,6 +696,21 @@ var JmapTransport = class {
|
|
|
608
696
|
*/
|
|
609
697
|
async getIdentityId(session, accountId, senderEmail, signal) {
|
|
610
698
|
if (this.config.identityId) return this.config.identityId;
|
|
699
|
+
const identityMap = await this.getIdentityMap(session, accountId, signal);
|
|
700
|
+
const matching = identityMap.get(senderEmail.toLowerCase());
|
|
701
|
+
if (matching) return matching;
|
|
702
|
+
return identityMap.values().next().value;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Gets all identities and builds a map of email to identity ID.
|
|
706
|
+
* @param session The JMAP session.
|
|
707
|
+
* @param accountId The account ID.
|
|
708
|
+
* @param signal Optional abort signal.
|
|
709
|
+
* @returns Map of lowercase email to identity ID.
|
|
710
|
+
* @since 0.4.0
|
|
711
|
+
*/
|
|
712
|
+
async getIdentityMap(session, accountId, signal) {
|
|
713
|
+
if (this.config.identityId) return new Map([["*", this.config.identityId]]);
|
|
611
714
|
const response = await this.httpClient.executeRequest(session.apiUrl, {
|
|
612
715
|
using: [JMAP_CAPABILITIES.core, JMAP_CAPABILITIES.submission],
|
|
613
716
|
methodCalls: [[
|
|
@@ -620,9 +723,9 @@ var JmapTransport = class {
|
|
|
620
723
|
if (!identityResponse) throw new JmapApiError("No Identity/get response received");
|
|
621
724
|
const identities = identityResponse[1].list;
|
|
622
725
|
if (!identities || identities.length === 0) throw new JmapApiError("No identities found");
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
return
|
|
726
|
+
const identityMap = /* @__PURE__ */ new Map();
|
|
727
|
+
for (const identity of identities) identityMap.set(identity.email.toLowerCase(), identity.id);
|
|
728
|
+
return identityMap;
|
|
626
729
|
}
|
|
627
730
|
/**
|
|
628
731
|
* Uploads all attachments and returns a map of contentId to blobId.
|
|
@@ -673,6 +776,43 @@ var JmapTransport = class {
|
|
|
673
776
|
errorMessages: errors
|
|
674
777
|
};
|
|
675
778
|
}
|
|
779
|
+
/**
|
|
780
|
+
* Parses the JMAP batch response to extract receipt for a specific index.
|
|
781
|
+
* @param response The JMAP response.
|
|
782
|
+
* @param index The message index in the batch.
|
|
783
|
+
* @returns A receipt indicating success or failure for that message.
|
|
784
|
+
* @since 0.4.0
|
|
785
|
+
*/
|
|
786
|
+
parseBatchResponseForIndex(response, index) {
|
|
787
|
+
const errors = [];
|
|
788
|
+
const draftKey = `draft${index}`;
|
|
789
|
+
const subKey = `sub${index}`;
|
|
790
|
+
const emailResponse = response.methodResponses.find((r) => r[0] === "Email/set");
|
|
791
|
+
if (emailResponse) {
|
|
792
|
+
const emailResult = emailResponse[1];
|
|
793
|
+
if (emailResult.notCreated?.[draftKey]) {
|
|
794
|
+
const error = emailResult.notCreated[draftKey];
|
|
795
|
+
errors.push(`Email creation failed: ${error.type}${error.description ? ` - ${error.description}` : ""}`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const submissionResponse = response.methodResponses.find((r) => r[0] === "EmailSubmission/set");
|
|
799
|
+
if (submissionResponse) {
|
|
800
|
+
const submissionResult = submissionResponse[1];
|
|
801
|
+
if (submissionResult.notCreated?.[subKey]) {
|
|
802
|
+
const error = submissionResult.notCreated[subKey];
|
|
803
|
+
errors.push(`Email submission failed: ${error.type}${error.description ? ` - ${error.description}` : ""}`);
|
|
804
|
+
}
|
|
805
|
+
if (submissionResult.created?.[subKey]) return {
|
|
806
|
+
successful: true,
|
|
807
|
+
messageId: submissionResult.created[subKey].id
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
if (errors.length === 0) errors.push("Unknown error: No submission result received");
|
|
811
|
+
return {
|
|
812
|
+
successful: false,
|
|
813
|
+
errorMessages: errors
|
|
814
|
+
};
|
|
815
|
+
}
|
|
676
816
|
};
|
|
677
817
|
|
|
678
818
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upyo/jmap",
|
|
3
|
-
"version": "0.4.0-dev.
|
|
3
|
+
"version": "0.4.0-dev.77+40049302",
|
|
4
4
|
"description": "JMAP transport for Upyo email library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"sideEffects": false,
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@upyo/core": "0.4.0-dev.
|
|
56
|
+
"@upyo/core": "0.4.0-dev.77+40049302"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@dotenvx/dotenvx": "^1.47.3",
|