@fluxmail/provider-gmail 0.1.0
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/errors.d.ts +12 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +79 -0
- package/dist/errors.js.map +1 -0
- package/dist/gmailProvider.d.ts +58 -0
- package/dist/gmailProvider.d.ts.map +1 -0
- package/dist/gmailProvider.js +453 -0
- package/dist/gmailProvider.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/mime.d.ts +8 -0
- package/dist/mime.d.ts.map +1 -0
- package/dist/mime.js +61 -0
- package/dist/mime.js.map +1 -0
- package/dist/parse.d.ts +39 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +190 -0
- package/dist/parse.js.map +1 -0
- package/dist/query.d.ts +13 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +73 -0
- package/dist/query.js.map +1 -0
- package/package.json +36 -0
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { EmailError } from '@fluxmail/core';
|
|
2
|
+
export declare function isRetryable(err: unknown): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* A non-idempotent request can be committed before a 5xx reaches the client,
|
|
5
|
+
* so retrying can duplicate its effect. Retry only responses that reject the
|
|
6
|
+
* request before processing it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isRetryableForNonIdempotentRequest(err: unknown): boolean;
|
|
9
|
+
/** Map googleapis errors onto the normalized EmailError codes. */
|
|
10
|
+
export declare function toEmailError(err: unknown): EmailError;
|
|
11
|
+
export declare function withRetry<T>(fn: () => Promise<T>, maxRetries?: number, retryable?: (err: unknown) => boolean): Promise<T>;
|
|
12
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AA4B5C,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAWjD;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAIxE;AAED,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CA2BrD;AAED,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,UAAU,SAAI,EACd,SAAS,GAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAqB,GACjD,OAAO,CAAC,CAAC,CAAC,CAYZ"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { EmailError } from '@fluxmail/core';
|
|
2
|
+
function statusOf(err) {
|
|
3
|
+
if (typeof err.code === 'number')
|
|
4
|
+
return err.code;
|
|
5
|
+
if (typeof err.code === 'string' && /^\d+$/.test(err.code))
|
|
6
|
+
return Number(err.code);
|
|
7
|
+
return err.response?.status;
|
|
8
|
+
}
|
|
9
|
+
function reasonsOf(err) {
|
|
10
|
+
return [...(err.errors ?? []), ...(err.response?.data?.error?.errors ?? [])]
|
|
11
|
+
.map((item) => item.reason)
|
|
12
|
+
.filter((reason) => !!reason);
|
|
13
|
+
}
|
|
14
|
+
function isRateLimitReason(reason) {
|
|
15
|
+
return /^(?:user)?rateLimitExceeded$/i.test(reason);
|
|
16
|
+
}
|
|
17
|
+
export function isRetryable(err) {
|
|
18
|
+
const e = err;
|
|
19
|
+
const status = statusOf(e);
|
|
20
|
+
return (status === 429 ||
|
|
21
|
+
status === 500 ||
|
|
22
|
+
status === 502 ||
|
|
23
|
+
status === 503 ||
|
|
24
|
+
status === 504 ||
|
|
25
|
+
(status === 403 && reasonsOf(e).some(isRateLimitReason)));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A non-idempotent request can be committed before a 5xx reaches the client,
|
|
29
|
+
* so retrying can duplicate its effect. Retry only responses that reject the
|
|
30
|
+
* request before processing it.
|
|
31
|
+
*/
|
|
32
|
+
export function isRetryableForNonIdempotentRequest(err) {
|
|
33
|
+
const e = err;
|
|
34
|
+
const status = statusOf(e);
|
|
35
|
+
return status === 429 || (status === 403 && reasonsOf(e).some(isRateLimitReason));
|
|
36
|
+
}
|
|
37
|
+
/** Map googleapis errors onto the normalized EmailError codes. */
|
|
38
|
+
export function toEmailError(err) {
|
|
39
|
+
if (err instanceof EmailError)
|
|
40
|
+
return err;
|
|
41
|
+
const e = err;
|
|
42
|
+
const status = statusOf(e);
|
|
43
|
+
const message = e.message ?? e.response?.data?.error?.message ?? 'Gmail API error';
|
|
44
|
+
const reasons = reasonsOf(e);
|
|
45
|
+
// invalid_grant = revoked/expired refresh token; comes back as a 400.
|
|
46
|
+
if (status === 401 || /invalid_grant/i.test(message)) {
|
|
47
|
+
return new EmailError('auth_expired', `Gmail authorization expired or revoked: ${message}`);
|
|
48
|
+
}
|
|
49
|
+
if (status === 429 ||
|
|
50
|
+
reasons.some((reason) => isRateLimitReason(reason) || /^(?:dailyLimit|quota)Exceeded$/i.test(reason))) {
|
|
51
|
+
return new EmailError('rate_limited', `Gmail rate limit hit: ${message}`);
|
|
52
|
+
}
|
|
53
|
+
if (status === 403) {
|
|
54
|
+
return new EmailError('provider_unavailable', `Gmail permission denied: ${message}`);
|
|
55
|
+
}
|
|
56
|
+
if (status === 404) {
|
|
57
|
+
return new EmailError('not_found', `Not found in Gmail: ${message}`);
|
|
58
|
+
}
|
|
59
|
+
if (status === 400) {
|
|
60
|
+
return new EmailError('invalid_request', message);
|
|
61
|
+
}
|
|
62
|
+
return new EmailError('provider_unavailable', `Gmail API failure: ${message}`);
|
|
63
|
+
}
|
|
64
|
+
export async function withRetry(fn, maxRetries = 3, retryable = isRetryable) {
|
|
65
|
+
let attempt = 0;
|
|
66
|
+
for (;;) {
|
|
67
|
+
try {
|
|
68
|
+
return await fn();
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
if (attempt >= maxRetries || !retryable(err))
|
|
72
|
+
throw toEmailError(err);
|
|
73
|
+
const delayMs = 500 * 2 ** attempt + Math.floor(Math.random() * 250);
|
|
74
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
75
|
+
attempt++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { OAuth2Client } from 'googleapis-common';
|
|
2
|
+
import { type AttachmentMeta, type Capabilities, type DraftInput, type EmailProvider, type EmailQuery, type Folder, type GetMessageOpts, type Message, type ModifyAction, type Page, type PageOpts, type SendResult, type Thread } from '@fluxmail/core';
|
|
3
|
+
export interface GmailProviderOptions {
|
|
4
|
+
accountId: string;
|
|
5
|
+
email: string;
|
|
6
|
+
/** Stored sender name used when Gmail settings and the current Google profile have no name. */
|
|
7
|
+
displayName?: string;
|
|
8
|
+
auth: OAuth2Client;
|
|
9
|
+
}
|
|
10
|
+
export declare const GMAIL_CAPABILITIES: Capabilities;
|
|
11
|
+
export declare class GmailProvider implements EmailProvider {
|
|
12
|
+
readonly capabilities: Capabilities;
|
|
13
|
+
private readonly gmail;
|
|
14
|
+
private readonly accountId;
|
|
15
|
+
private readonly email;
|
|
16
|
+
private readonly displayName;
|
|
17
|
+
private readonly auth;
|
|
18
|
+
private labelCache;
|
|
19
|
+
/** undefined = not resolved yet; null = no name available. */
|
|
20
|
+
private senderName;
|
|
21
|
+
constructor(opts: GmailProviderOptions);
|
|
22
|
+
/**
|
|
23
|
+
* Sender name for the From header, matching what the Gmail UI would use:
|
|
24
|
+
* the primary send-as displayName when set, else the current Google profile
|
|
25
|
+
* name, with the stored account name as an offline fallback.
|
|
26
|
+
*/
|
|
27
|
+
private resolveSenderName;
|
|
28
|
+
testConnection(): Promise<void>;
|
|
29
|
+
private labels;
|
|
30
|
+
private labelNameMap;
|
|
31
|
+
/** Gmail messages carry the DRAFT label, but only Draft resources expose the draft id. */
|
|
32
|
+
private attachDraftIds;
|
|
33
|
+
private resolveLabelId;
|
|
34
|
+
listMessages(q: EmailQuery, page?: PageOpts): Promise<Page<Message>>;
|
|
35
|
+
getMessage(id: string, opts?: GetMessageOpts): Promise<Message>;
|
|
36
|
+
getThread(threadId: string): Promise<Thread>;
|
|
37
|
+
private parseFullMessage;
|
|
38
|
+
listFolders(): Promise<Folder[]>;
|
|
39
|
+
/** Resolve reply threading (headers + Gmail thread id) from the message being replied to. */
|
|
40
|
+
private resolveReply;
|
|
41
|
+
/** Threading already present on an existing draft, so content updates keep a reply on its thread. */
|
|
42
|
+
private resolveExistingDraftThreading;
|
|
43
|
+
private composeRaw;
|
|
44
|
+
private draftToMessage;
|
|
45
|
+
createDraft(d: DraftInput): Promise<Message>;
|
|
46
|
+
getDraft(draftId: string): Promise<Message>;
|
|
47
|
+
updateDraft(draftId: string, d: DraftInput): Promise<Message>;
|
|
48
|
+
deleteDraft(draftId: string): Promise<void>;
|
|
49
|
+
send(input: DraftInput | {
|
|
50
|
+
draftId: string;
|
|
51
|
+
}): Promise<SendResult>;
|
|
52
|
+
modify(ids: string[], action: ModifyAction): Promise<void>;
|
|
53
|
+
getAttachment(messageId: string, attachmentId: string): Promise<{
|
|
54
|
+
meta: AttachmentMeta;
|
|
55
|
+
content: Buffer;
|
|
56
|
+
}>;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=gmailProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gmailProvider.d.ts","sourceRoot":"","sources":["../src/gmailProvider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,MAAM,EAEX,KAAK,cAAc,EACnB,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,KAAK,IAAI,EACT,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,MAAM,EACZ,MAAM,gBAAgB,CAAC;AAgCxB,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,eAAO,MAAM,kBAAkB,EAAE,YAKhC,CAAC;AAEF,qBAAa,aAAc,YAAW,aAAa;IACjD,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAsB;IAEzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,UAAU,CAAuE;IACzF,8DAA8D;IAC9D,OAAO,CAAC,UAAU,CAA4B;gBAElC,IAAI,EAAE,oBAAoB;IAQtC;;;;OAIG;YACW,iBAAiB;IAuCzB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;YAIvB,MAAM;YAUN,YAAY;IAK1B,0FAA0F;YAC5E,cAAc;YA0Bd,cAAc;IAoBtB,YAAY,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAuDpE,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ/D,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAgBpC,gBAAgB;IA+BxB,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAkBtC,6FAA6F;YAC/E,YAAY;IAsB1B,qGAAqG;YACvF,6BAA6B;YAmB7B,UAAU;YAwBV,cAAc;IAQtB,WAAW,CAAC,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;IAc5C,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK3C,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;IAe7D,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC;IAyBlE,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAgE1D,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAejH"}
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import { EmailError, isEmailError, replySubject, } from '@fluxmail/core';
|
|
3
|
+
import { toGmailQuery, ROLE_TO_LABEL } from './query.js';
|
|
4
|
+
import { decodeBase64Url, decodeTextPart, encodeBase64Url, findAttachment, parseGmailMessage, parseGmailMessageWithParts, } from './parse.js';
|
|
5
|
+
import { buildRawMessage } from './mime.js';
|
|
6
|
+
import { isRetryableForNonIdempotentRequest, withRetry } from './errors.js';
|
|
7
|
+
const LABEL_TO_ROLE = {
|
|
8
|
+
INBOX: 'inbox',
|
|
9
|
+
SENT: 'sent',
|
|
10
|
+
DRAFT: 'drafts',
|
|
11
|
+
TRASH: 'trash',
|
|
12
|
+
SPAM: 'spam',
|
|
13
|
+
STARRED: 'starred',
|
|
14
|
+
};
|
|
15
|
+
const METADATA_HEADERS = ['From', 'To', 'Cc', 'Bcc', 'Reply-To', 'Subject', 'Date', 'Message-ID', 'References', 'In-Reply-To'];
|
|
16
|
+
const LABEL_CACHE_TTL_MS = 60_000;
|
|
17
|
+
const HYDRATE_CONCURRENCY = 10;
|
|
18
|
+
const DEFAULT_PAGE_SIZE = 25;
|
|
19
|
+
const MAX_PAGE_SIZE = 100;
|
|
20
|
+
const NON_IDEMPOTENT_MAX_RETRIES = 3;
|
|
21
|
+
const BATCH_MODIFY_MAX_IDS = 1_000;
|
|
22
|
+
const GOOGLE_USERINFO_URL = 'https://openidconnect.googleapis.com/v1/userinfo';
|
|
23
|
+
const IMMUTABLE_SYSTEM_LABELS = new Set(['sent', 'draft', 'drafts']);
|
|
24
|
+
export const GMAIL_CAPABILITIES = {
|
|
25
|
+
labels: true,
|
|
26
|
+
serverThreads: true,
|
|
27
|
+
serverSearch: 'rich',
|
|
28
|
+
snippets: true,
|
|
29
|
+
};
|
|
30
|
+
export class GmailProvider {
|
|
31
|
+
capabilities = GMAIL_CAPABILITIES;
|
|
32
|
+
gmail;
|
|
33
|
+
accountId;
|
|
34
|
+
email;
|
|
35
|
+
displayName;
|
|
36
|
+
auth;
|
|
37
|
+
labelCache = null;
|
|
38
|
+
/** undefined = not resolved yet; null = no name available. */
|
|
39
|
+
senderName;
|
|
40
|
+
constructor(opts) {
|
|
41
|
+
this.accountId = opts.accountId;
|
|
42
|
+
this.email = opts.email;
|
|
43
|
+
this.displayName = opts.displayName;
|
|
44
|
+
this.auth = opts.auth;
|
|
45
|
+
this.gmail = google.gmail({ version: 'v1', auth: opts.auth });
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Sender name for the From header, matching what the Gmail UI would use:
|
|
49
|
+
* the primary send-as displayName when set, else the current Google profile
|
|
50
|
+
* name, with the stored account name as an offline fallback.
|
|
51
|
+
*/
|
|
52
|
+
async resolveSenderName() {
|
|
53
|
+
if (this.senderName !== undefined)
|
|
54
|
+
return this.senderName;
|
|
55
|
+
let lookupFailed = false;
|
|
56
|
+
let sendAsName;
|
|
57
|
+
try {
|
|
58
|
+
const res = await withRetry(() => this.gmail.users.settings.sendAs.list({ userId: 'me' }));
|
|
59
|
+
const primary = res.data.sendAs?.find((s) => s.isPrimary) ??
|
|
60
|
+
res.data.sendAs?.find((s) => s.sendAsEmail === this.email);
|
|
61
|
+
sendAsName = primary?.displayName?.trim() || undefined;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Fall through to the profile and stored account name.
|
|
65
|
+
lookupFailed = true;
|
|
66
|
+
}
|
|
67
|
+
if (sendAsName) {
|
|
68
|
+
this.senderName = sendAsName;
|
|
69
|
+
return this.senderName;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const res = await withRetry(() => this.auth.request({ url: GOOGLE_USERINFO_URL }));
|
|
73
|
+
const profileName = res.data.name?.trim();
|
|
74
|
+
if (profileName) {
|
|
75
|
+
this.senderName = profileName;
|
|
76
|
+
return this.senderName;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Profile lookup is optional; sending should still work without it.
|
|
81
|
+
lookupFailed = true;
|
|
82
|
+
}
|
|
83
|
+
const fallback = this.displayName?.trim() || null;
|
|
84
|
+
// Cache "no name configured", but not a transient lookup failure: the next
|
|
85
|
+
// send should try Gmail again rather than pin the offline fallback.
|
|
86
|
+
if (!lookupFailed)
|
|
87
|
+
this.senderName = fallback;
|
|
88
|
+
return fallback;
|
|
89
|
+
}
|
|
90
|
+
async testConnection() {
|
|
91
|
+
await withRetry(() => this.gmail.users.getProfile({ userId: 'me' }));
|
|
92
|
+
}
|
|
93
|
+
async labels(forceRefresh = false) {
|
|
94
|
+
if (!forceRefresh && this.labelCache && Date.now() - this.labelCache.fetchedAt < LABEL_CACHE_TTL_MS) {
|
|
95
|
+
return this.labelCache.labels;
|
|
96
|
+
}
|
|
97
|
+
const res = await withRetry(() => this.gmail.users.labels.list({ userId: 'me' }));
|
|
98
|
+
const labels = res.data.labels ?? [];
|
|
99
|
+
this.labelCache = { fetchedAt: Date.now(), labels };
|
|
100
|
+
return labels;
|
|
101
|
+
}
|
|
102
|
+
async labelNameMap() {
|
|
103
|
+
const labels = await this.labels();
|
|
104
|
+
return new Map(labels.filter((l) => l.id && l.name).map((l) => [l.id, l.name]));
|
|
105
|
+
}
|
|
106
|
+
/** Gmail messages carry the DRAFT label, but only Draft resources expose the draft id. */
|
|
107
|
+
async attachDraftIds(messages) {
|
|
108
|
+
const unresolved = new Map(messages.filter((message) => message.flags.draft).map((message) => [message.id, message]));
|
|
109
|
+
if (!unresolved.size)
|
|
110
|
+
return;
|
|
111
|
+
let pageToken;
|
|
112
|
+
do {
|
|
113
|
+
const res = await withRetry(() => this.gmail.users.drafts.list({
|
|
114
|
+
userId: 'me',
|
|
115
|
+
maxResults: 500,
|
|
116
|
+
...(pageToken ? { pageToken } : {}),
|
|
117
|
+
}));
|
|
118
|
+
for (const draft of res.data.drafts ?? []) {
|
|
119
|
+
const message = draft.message?.id ? unresolved.get(draft.message.id) : undefined;
|
|
120
|
+
if (message && draft.id) {
|
|
121
|
+
message.draftId = draft.id;
|
|
122
|
+
unresolved.delete(message.id);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
pageToken = res.data.nextPageToken ?? undefined;
|
|
126
|
+
} while (unresolved.size && pageToken);
|
|
127
|
+
}
|
|
128
|
+
async resolveLabelId(folder, createIfMissing = false) {
|
|
129
|
+
const role = folder.toLowerCase();
|
|
130
|
+
if (IMMUTABLE_SYSTEM_LABELS.has(role)) {
|
|
131
|
+
throw new EmailError('invalid_request', `Gmail does not allow changing the "${role}" system label`);
|
|
132
|
+
}
|
|
133
|
+
if (ROLE_TO_LABEL[role])
|
|
134
|
+
return ROLE_TO_LABEL[role];
|
|
135
|
+
const labels = await this.labels();
|
|
136
|
+
const match = labels.find((l) => l.id === folder || l.name?.toLowerCase() === folder.toLowerCase());
|
|
137
|
+
if (match?.id)
|
|
138
|
+
return match.id;
|
|
139
|
+
if (!createIfMissing) {
|
|
140
|
+
throw new EmailError('not_found', `No Gmail label or folder named "${folder}"`);
|
|
141
|
+
}
|
|
142
|
+
const created = await withRetry(() => this.gmail.users.labels.create({ userId: 'me', requestBody: { name: folder } }));
|
|
143
|
+
this.labelCache = null;
|
|
144
|
+
if (!created.data.id)
|
|
145
|
+
throw new EmailError('provider_unavailable', 'Gmail did not return a label id');
|
|
146
|
+
return created.data.id;
|
|
147
|
+
}
|
|
148
|
+
async listMessages(q, page) {
|
|
149
|
+
const labels = await this.labels();
|
|
150
|
+
const gq = toGmailQuery(q, (folder) => {
|
|
151
|
+
const match = labels.find((l) => l.id === folder || l.name?.toLowerCase() === folder.toLowerCase());
|
|
152
|
+
return match?.id ?? null;
|
|
153
|
+
});
|
|
154
|
+
const pageSize = Math.min(page?.pageSize ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
|
|
155
|
+
const res = await withRetry(() => this.gmail.users.messages.list({
|
|
156
|
+
userId: 'me',
|
|
157
|
+
maxResults: pageSize,
|
|
158
|
+
...(page?.pageToken ? { pageToken: page.pageToken } : {}),
|
|
159
|
+
...(gq.q ? { q: gq.q } : {}),
|
|
160
|
+
...(gq.labelIds ? { labelIds: gq.labelIds } : {}),
|
|
161
|
+
...(gq.includeSpamTrash ? { includeSpamTrash: true } : {}),
|
|
162
|
+
}));
|
|
163
|
+
const ids = (res.data.messages ?? []).map((m) => m.id).filter(Boolean);
|
|
164
|
+
const labelNames = await this.labelNameMap();
|
|
165
|
+
const items = [];
|
|
166
|
+
for (let i = 0; i < ids.length; i += HYDRATE_CONCURRENCY) {
|
|
167
|
+
const chunk = ids.slice(i, i + HYDRATE_CONCURRENCY);
|
|
168
|
+
const fetched = await Promise.allSettled(chunk.map((id) => withRetry(() => this.gmail.users.messages.get({
|
|
169
|
+
userId: 'me',
|
|
170
|
+
id,
|
|
171
|
+
format: 'metadata',
|
|
172
|
+
metadataHeaders: METADATA_HEADERS,
|
|
173
|
+
}))));
|
|
174
|
+
for (const result of fetched) {
|
|
175
|
+
if (result.status === 'rejected') {
|
|
176
|
+
if (isEmailError(result.reason) && result.reason.code === 'not_found')
|
|
177
|
+
continue;
|
|
178
|
+
throw result.reason;
|
|
179
|
+
}
|
|
180
|
+
items.push(parseGmailMessage(result.value.data, {
|
|
181
|
+
accountId: this.accountId,
|
|
182
|
+
labelNames,
|
|
183
|
+
includeBody: false,
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
await this.attachDraftIds(items);
|
|
188
|
+
const out = { items };
|
|
189
|
+
if (res.data.nextPageToken)
|
|
190
|
+
out.nextPageToken = res.data.nextPageToken;
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
async getMessage(id, opts) {
|
|
194
|
+
const res = await withRetry(() => this.gmail.users.messages.get({ userId: 'me', id, format: 'full' }));
|
|
195
|
+
const labelNames = await this.labelNameMap();
|
|
196
|
+
const message = await this.parseFullMessage(res.data, labelNames, opts?.includeHeaders ?? false);
|
|
197
|
+
await this.attachDraftIds([message]);
|
|
198
|
+
return message;
|
|
199
|
+
}
|
|
200
|
+
async getThread(threadId) {
|
|
201
|
+
const res = await withRetry(() => this.gmail.users.threads.get({ userId: 'me', id: threadId, format: 'full' }));
|
|
202
|
+
const labelNames = await this.labelNameMap();
|
|
203
|
+
const messages = await Promise.all((res.data.messages ?? []).map((message) => this.parseFullMessage(message, labelNames)));
|
|
204
|
+
await this.attachDraftIds(messages);
|
|
205
|
+
return {
|
|
206
|
+
id: res.data.id ?? threadId,
|
|
207
|
+
subject: messages[0]?.subject ?? '',
|
|
208
|
+
messages,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
async parseFullMessage(raw, labelNames, includeHeaders = false) {
|
|
212
|
+
const { message, externalBodyParts } = parseGmailMessageWithParts(raw, {
|
|
213
|
+
accountId: this.accountId,
|
|
214
|
+
labelNames,
|
|
215
|
+
includeBody: true,
|
|
216
|
+
includeAttachments: true,
|
|
217
|
+
includeHeaders,
|
|
218
|
+
});
|
|
219
|
+
for (const field of ['text', 'html']) {
|
|
220
|
+
const external = externalBodyParts[field];
|
|
221
|
+
if (!external)
|
|
222
|
+
continue;
|
|
223
|
+
const res = await withRetry(() => this.gmail.users.messages.attachments.get({
|
|
224
|
+
userId: 'me',
|
|
225
|
+
messageId: raw.id ?? message.id,
|
|
226
|
+
id: external.attachmentId,
|
|
227
|
+
}));
|
|
228
|
+
if (res.data.data == null) {
|
|
229
|
+
throw new EmailError('provider_unavailable', `Gmail returned no ${field} body data`);
|
|
230
|
+
}
|
|
231
|
+
message.body ??= {};
|
|
232
|
+
message.body[field] = decodeTextPart(external.part, res.data.data);
|
|
233
|
+
}
|
|
234
|
+
return message;
|
|
235
|
+
}
|
|
236
|
+
async listFolders() {
|
|
237
|
+
const labels = await this.labels(true);
|
|
238
|
+
const folders = [];
|
|
239
|
+
for (const label of labels) {
|
|
240
|
+
if (!label.id || !label.name)
|
|
241
|
+
continue;
|
|
242
|
+
// Hide Gmail-internal labels that aren't useful as folders.
|
|
243
|
+
if (label.id.startsWith('CATEGORY_') || label.id === 'CHAT' || label.id === 'UNREAD' || label.id === 'IMPORTANT') {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const folder = { id: label.id, name: label.name };
|
|
247
|
+
const role = LABEL_TO_ROLE[label.id];
|
|
248
|
+
if (role)
|
|
249
|
+
folder.role = role;
|
|
250
|
+
if (typeof label.messagesUnread === 'number')
|
|
251
|
+
folder.unreadCount = label.messagesUnread;
|
|
252
|
+
folders.push(folder);
|
|
253
|
+
}
|
|
254
|
+
return folders;
|
|
255
|
+
}
|
|
256
|
+
/** Resolve reply threading (headers + Gmail thread id) from the message being replied to. */
|
|
257
|
+
async resolveReply(replyToMessageId) {
|
|
258
|
+
const res = await withRetry(() => this.gmail.users.messages.get({
|
|
259
|
+
userId: 'me',
|
|
260
|
+
id: replyToMessageId,
|
|
261
|
+
format: 'metadata',
|
|
262
|
+
metadataHeaders: ['Message-ID', 'References', 'Subject'],
|
|
263
|
+
}));
|
|
264
|
+
const headers = res.data.payload?.headers ?? [];
|
|
265
|
+
const get = (name) => headers.find((h) => h.name?.toLowerCase() === name.toLowerCase())?.value ?? '';
|
|
266
|
+
const messageId = get('Message-ID');
|
|
267
|
+
const references = [get('References'), messageId].filter(Boolean).join(' ');
|
|
268
|
+
const out = {
|
|
269
|
+
threadId: res.data.threadId ?? '',
|
|
270
|
+
subject: get('Subject'),
|
|
271
|
+
};
|
|
272
|
+
if (messageId)
|
|
273
|
+
out.inReplyTo = messageId;
|
|
274
|
+
if (references)
|
|
275
|
+
out.references = references;
|
|
276
|
+
return out;
|
|
277
|
+
}
|
|
278
|
+
/** Threading already present on an existing draft, so content updates keep a reply on its thread. */
|
|
279
|
+
async resolveExistingDraftThreading(draftId) {
|
|
280
|
+
const res = await withRetry(() => this.gmail.users.drafts.get({ userId: 'me', id: draftId, format: 'metadata' }));
|
|
281
|
+
const message = res.data.message;
|
|
282
|
+
if (!message)
|
|
283
|
+
return undefined;
|
|
284
|
+
const headers = message.payload?.headers ?? [];
|
|
285
|
+
const get = (name) => headers.find((h) => h.name?.toLowerCase() === name.toLowerCase())?.value ?? '';
|
|
286
|
+
const out = {};
|
|
287
|
+
if (message.threadId)
|
|
288
|
+
out.threadId = message.threadId;
|
|
289
|
+
const inReplyTo = get('In-Reply-To');
|
|
290
|
+
if (inReplyTo)
|
|
291
|
+
out.inReplyTo = inReplyTo;
|
|
292
|
+
const references = get('References');
|
|
293
|
+
if (references)
|
|
294
|
+
out.references = references;
|
|
295
|
+
return out;
|
|
296
|
+
}
|
|
297
|
+
async composeRaw(d, existingThreading) {
|
|
298
|
+
let threading;
|
|
299
|
+
let threadId;
|
|
300
|
+
const draft = { ...d };
|
|
301
|
+
if (d.replyToMessageId) {
|
|
302
|
+
const reply = await this.resolveReply(d.replyToMessageId);
|
|
303
|
+
threading = reply;
|
|
304
|
+
threadId = reply.threadId || undefined;
|
|
305
|
+
if (!draft.subject)
|
|
306
|
+
draft.subject = replySubject(reply.subject);
|
|
307
|
+
}
|
|
308
|
+
else if (existingThreading) {
|
|
309
|
+
threading = existingThreading;
|
|
310
|
+
threadId = existingThreading.threadId;
|
|
311
|
+
}
|
|
312
|
+
const senderName = await this.resolveSenderName();
|
|
313
|
+
const from = senderName ? { name: senderName, email: this.email } : { email: this.email };
|
|
314
|
+
const raw = await buildRawMessage(draft, from, threading);
|
|
315
|
+
const out = { raw: encodeBase64Url(raw) };
|
|
316
|
+
if (threadId)
|
|
317
|
+
out.threadId = threadId;
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
async draftToMessage(draft) {
|
|
321
|
+
const messageId = draft.message?.id;
|
|
322
|
+
if (!messageId)
|
|
323
|
+
throw new EmailError('provider_unavailable', 'Gmail draft has no message id');
|
|
324
|
+
const message = await this.getMessage(messageId);
|
|
325
|
+
if (draft.id)
|
|
326
|
+
message.draftId = draft.id;
|
|
327
|
+
return message;
|
|
328
|
+
}
|
|
329
|
+
async createDraft(d) {
|
|
330
|
+
const { raw, threadId } = await this.composeRaw(d);
|
|
331
|
+
const res = await withRetry(() => this.gmail.users.drafts.create({
|
|
332
|
+
userId: 'me',
|
|
333
|
+
requestBody: { message: { raw, ...(threadId ? { threadId } : {}) } },
|
|
334
|
+
}), NON_IDEMPOTENT_MAX_RETRIES, isRetryableForNonIdempotentRequest);
|
|
335
|
+
return this.draftToMessage(res.data);
|
|
336
|
+
}
|
|
337
|
+
async getDraft(draftId) {
|
|
338
|
+
const res = await withRetry(() => this.gmail.users.drafts.get({ userId: 'me', id: draftId }));
|
|
339
|
+
return this.draftToMessage(res.data);
|
|
340
|
+
}
|
|
341
|
+
async updateDraft(draftId, d) {
|
|
342
|
+
// Replacing a reply draft's content must not detach it from its thread:
|
|
343
|
+
// carry the draft's threading over unless the caller re-derives it.
|
|
344
|
+
const existing = d.replyToMessageId ? undefined : await this.resolveExistingDraftThreading(draftId);
|
|
345
|
+
const { raw, threadId } = await this.composeRaw(d, existing);
|
|
346
|
+
const res = await withRetry(() => this.gmail.users.drafts.update({
|
|
347
|
+
userId: 'me',
|
|
348
|
+
id: draftId,
|
|
349
|
+
requestBody: { message: { raw, ...(threadId ? { threadId } : {}) } },
|
|
350
|
+
}));
|
|
351
|
+
return this.draftToMessage(res.data);
|
|
352
|
+
}
|
|
353
|
+
async deleteDraft(draftId) {
|
|
354
|
+
await withRetry(() => this.gmail.users.drafts.delete({ userId: 'me', id: draftId }));
|
|
355
|
+
}
|
|
356
|
+
async send(input) {
|
|
357
|
+
if ('draftId' in input) {
|
|
358
|
+
const res = await withRetry(() => this.gmail.users.drafts.send({ userId: 'me', requestBody: { id: input.draftId } }), NON_IDEMPOTENT_MAX_RETRIES, isRetryableForNonIdempotentRequest);
|
|
359
|
+
return { id: res.data.id ?? '', threadId: res.data.threadId ?? res.data.id ?? '' };
|
|
360
|
+
}
|
|
361
|
+
if (!input.to?.length && !input.cc?.length && !input.bcc?.length) {
|
|
362
|
+
throw new EmailError('invalid_request', 'Cannot send a message with no recipients');
|
|
363
|
+
}
|
|
364
|
+
const { raw, threadId } = await this.composeRaw(input);
|
|
365
|
+
const res = await withRetry(() => this.gmail.users.messages.send({
|
|
366
|
+
userId: 'me',
|
|
367
|
+
requestBody: { raw, ...(threadId ? { threadId } : {}) },
|
|
368
|
+
}), NON_IDEMPOTENT_MAX_RETRIES, isRetryableForNonIdempotentRequest);
|
|
369
|
+
return { id: res.data.id ?? '', threadId: res.data.threadId ?? res.data.id ?? '' };
|
|
370
|
+
}
|
|
371
|
+
async modify(ids, action) {
|
|
372
|
+
if (!ids.length)
|
|
373
|
+
return;
|
|
374
|
+
if (action === 'trash' || action === 'untrash' || action === 'delete') {
|
|
375
|
+
for (const id of ids) {
|
|
376
|
+
if (action === 'trash')
|
|
377
|
+
await withRetry(() => this.gmail.users.messages.trash({ userId: 'me', id }));
|
|
378
|
+
else if (action === 'untrash')
|
|
379
|
+
await withRetry(() => this.gmail.users.messages.untrash({ userId: 'me', id }));
|
|
380
|
+
else
|
|
381
|
+
await withRetry(() => this.gmail.users.messages.delete({ userId: 'me', id }));
|
|
382
|
+
}
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
let addLabelIds = [];
|
|
386
|
+
let removeLabelIds = [];
|
|
387
|
+
if (action === 'markRead')
|
|
388
|
+
removeLabelIds = ['UNREAD'];
|
|
389
|
+
else if (action === 'markUnread')
|
|
390
|
+
addLabelIds = ['UNREAD'];
|
|
391
|
+
else if (action === 'star')
|
|
392
|
+
addLabelIds = ['STARRED'];
|
|
393
|
+
else if (action === 'unstar')
|
|
394
|
+
removeLabelIds = ['STARRED'];
|
|
395
|
+
else if (action === 'archive')
|
|
396
|
+
removeLabelIds = ['INBOX'];
|
|
397
|
+
else if ('move' in action) {
|
|
398
|
+
const role = action.move.toLowerCase();
|
|
399
|
+
if (role === 'all' || role === 'starred') {
|
|
400
|
+
throw new EmailError('invalid_request', `Cannot move messages to "${role}"; it is a view, not a destination`);
|
|
401
|
+
}
|
|
402
|
+
if (role === 'archive') {
|
|
403
|
+
// Gmail has no archive label: archiving is just leaving the inbox.
|
|
404
|
+
removeLabelIds = ['INBOX'];
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
const target = await this.resolveLabelId(action.move, true);
|
|
408
|
+
addLabelIds = [target];
|
|
409
|
+
if (target !== 'INBOX')
|
|
410
|
+
removeLabelIds = ['INBOX'];
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
else if ('addLabels' in action) {
|
|
414
|
+
const labels = [...new Set(action.addLabels)];
|
|
415
|
+
if (labels.length > 100) {
|
|
416
|
+
throw new EmailError('invalid_request', 'Gmail allows at most 100 labels in one modification');
|
|
417
|
+
}
|
|
418
|
+
addLabelIds = await Promise.all(labels.map((label) => this.resolveLabelId(label, true)));
|
|
419
|
+
}
|
|
420
|
+
else if ('removeLabels' in action) {
|
|
421
|
+
const labels = [...new Set(action.removeLabels)];
|
|
422
|
+
if (labels.length > 100) {
|
|
423
|
+
throw new EmailError('invalid_request', 'Gmail allows at most 100 labels in one modification');
|
|
424
|
+
}
|
|
425
|
+
removeLabelIds = await Promise.all(labels.map((label) => this.resolveLabelId(label)));
|
|
426
|
+
}
|
|
427
|
+
for (let offset = 0; offset < ids.length; offset += BATCH_MODIFY_MAX_IDS) {
|
|
428
|
+
const chunk = ids.slice(offset, offset + BATCH_MODIFY_MAX_IDS);
|
|
429
|
+
await withRetry(() => this.gmail.users.messages.batchModify({
|
|
430
|
+
userId: 'me',
|
|
431
|
+
requestBody: {
|
|
432
|
+
ids: chunk,
|
|
433
|
+
...(addLabelIds.length ? { addLabelIds } : {}),
|
|
434
|
+
...(removeLabelIds.length ? { removeLabelIds } : {}),
|
|
435
|
+
},
|
|
436
|
+
}));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async getAttachment(messageId, attachmentId) {
|
|
440
|
+
const msg = await withRetry(() => this.gmail.users.messages.get({ userId: 'me', id: messageId, format: 'full' }));
|
|
441
|
+
const attachment = findAttachment(msg.data.payload, attachmentId);
|
|
442
|
+
if (!attachment)
|
|
443
|
+
throw new EmailError('not_found', `Attachment ${attachmentId} not found on message ${messageId}`);
|
|
444
|
+
if (attachment.content !== undefined) {
|
|
445
|
+
return { meta: attachment.meta, content: attachment.content };
|
|
446
|
+
}
|
|
447
|
+
const res = await withRetry(() => this.gmail.users.messages.attachments.get({ userId: 'me', messageId, id: attachmentId }));
|
|
448
|
+
if (res.data.data == null)
|
|
449
|
+
throw new EmailError('provider_unavailable', 'Gmail returned no attachment data');
|
|
450
|
+
return { meta: attachment.meta, content: decodeBase64Url(res.data.data) };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
//# sourceMappingURL=gmailProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gmailProvider.js","sourceRoot":"","sources":["../src/gmailProvider.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { GmailProvider, GMAIL_CAPABILITIES, type GmailProviderOptions } from './gmailProvider.js';
|
|
2
|
+
export { toGmailQuery } from './query.js';
|
|
3
|
+
export { parseGmailMessage, walkParts } from './parse.js';
|
|
4
|
+
export { toEmailError, withRetry } from './errors.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAClG,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAA6B,MAAM,oBAAoB,CAAC;AAClG,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/mime.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type DraftInput, type EmailAddress } from '@fluxmail/core';
|
|
2
|
+
export interface ThreadingHeaders {
|
|
3
|
+
inReplyTo?: string;
|
|
4
|
+
references?: string;
|
|
5
|
+
}
|
|
6
|
+
/** Build a raw RFC 5322 message from a DraftInput. */
|
|
7
|
+
export declare function buildRawMessage(draft: DraftInput, from: EmailAddress, threading?: ThreadingHeaders): Promise<Buffer>;
|
|
8
|
+
//# sourceMappingURL=mime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.d.ts","sourceRoot":"","sources":["../src/mime.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEhF,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAuBD,sDAAsD;AACtD,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,YAAY,EAClB,SAAS,CAAC,EAAE,gBAAgB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAgCjB"}
|
package/dist/mime.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import MailComposer from 'nodemailer/lib/mail-composer/index.js';
|
|
2
|
+
import { EmailError } from '@fluxmail/core';
|
|
3
|
+
function toMailAddresses(addrs) {
|
|
4
|
+
if (!addrs?.length)
|
|
5
|
+
return undefined;
|
|
6
|
+
return addrs.map((a) => ({ name: a.name ?? '', address: a.email }));
|
|
7
|
+
}
|
|
8
|
+
function decodeAttachmentContent(rawContent) {
|
|
9
|
+
// RFC 2045 base64 may be wrapped in whitespace (e.g. `base64` CLI output).
|
|
10
|
+
const content = rawContent.replace(/\s+/g, '');
|
|
11
|
+
if (content === '')
|
|
12
|
+
return Buffer.alloc(0);
|
|
13
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(content) || content.length % 4 === 1) {
|
|
14
|
+
throw new EmailError('invalid_request', 'Attachment content must be valid base64');
|
|
15
|
+
}
|
|
16
|
+
const unpadded = content.replace(/=+$/, '');
|
|
17
|
+
const padded = unpadded + '='.repeat((4 - (unpadded.length % 4)) % 4);
|
|
18
|
+
const decoded = Buffer.from(padded, 'base64');
|
|
19
|
+
if (decoded.toString('base64').replace(/=+$/, '') !== unpadded) {
|
|
20
|
+
throw new EmailError('invalid_request', 'Attachment content must be valid base64');
|
|
21
|
+
}
|
|
22
|
+
return decoded;
|
|
23
|
+
}
|
|
24
|
+
/** Build a raw RFC 5322 message from a DraftInput. */
|
|
25
|
+
export async function buildRawMessage(draft, from, threading) {
|
|
26
|
+
const options = {
|
|
27
|
+
from: { name: from.name ?? '', address: from.email },
|
|
28
|
+
to: toMailAddresses(draft.to),
|
|
29
|
+
cc: toMailAddresses(draft.cc),
|
|
30
|
+
bcc: toMailAddresses(draft.bcc),
|
|
31
|
+
subject: draft.subject ?? '',
|
|
32
|
+
text: draft.body.text,
|
|
33
|
+
html: draft.body.html,
|
|
34
|
+
attachments: draft.attachments?.map((a) => ({
|
|
35
|
+
filename: a.filename,
|
|
36
|
+
content: decodeAttachmentContent(a.content),
|
|
37
|
+
contentType: a.mimeType,
|
|
38
|
+
...(a.contentId ? { cid: a.contentId } : {}),
|
|
39
|
+
...(a.disposition ? { contentDisposition: a.disposition } : {}),
|
|
40
|
+
})),
|
|
41
|
+
};
|
|
42
|
+
if (threading?.inReplyTo)
|
|
43
|
+
options.inReplyTo = threading.inReplyTo;
|
|
44
|
+
if (threading?.references)
|
|
45
|
+
options.references = threading.references;
|
|
46
|
+
const composer = new MailComposer(options);
|
|
47
|
+
const compiled = composer.compile();
|
|
48
|
+
// Gmail derives recipients from the raw headers; without this the Bcc header
|
|
49
|
+
// (which MailComposer strips by default) never reaches Gmail and bcc
|
|
50
|
+
// recipients silently get nothing. Gmail removes it before delivery.
|
|
51
|
+
compiled.keepBcc = true;
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
compiled.build((err, message) => {
|
|
54
|
+
if (err)
|
|
55
|
+
reject(err);
|
|
56
|
+
else
|
|
57
|
+
resolve(message);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=mime.js.map
|
package/dist/mime.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.js","sourceRoot":"","sources":["../src/mime.ts"],"names":[],"mappings":"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"}
|
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { gmail_v1 } from 'googleapis';
|
|
2
|
+
import { type AttachmentMeta, type Message, type MessageBody } from '@fluxmail/core';
|
|
3
|
+
export declare function decodeBase64Url(data: string): Buffer;
|
|
4
|
+
export declare function encodeBase64Url(data: Buffer): string;
|
|
5
|
+
export declare function decodeTextPart(part: gmail_v1.Schema$MessagePart, encoded?: string): string;
|
|
6
|
+
type BodyField = 'text' | 'html';
|
|
7
|
+
export interface ExternalBodyPart {
|
|
8
|
+
attachmentId: string;
|
|
9
|
+
part: gmail_v1.Schema$MessagePart;
|
|
10
|
+
}
|
|
11
|
+
export type ExternalBodyParts = Partial<Record<BodyField, ExternalBodyPart>>;
|
|
12
|
+
interface WalkedParts {
|
|
13
|
+
body: MessageBody;
|
|
14
|
+
attachments: AttachmentMeta[];
|
|
15
|
+
externalBodyParts: ExternalBodyParts;
|
|
16
|
+
}
|
|
17
|
+
interface ParsedAttachment {
|
|
18
|
+
meta: AttachmentMeta;
|
|
19
|
+
content?: Buffer;
|
|
20
|
+
}
|
|
21
|
+
/** Walk the MIME tree collecting the preferred text/html bodies and attachment metadata. */
|
|
22
|
+
export declare function walkParts(payload: gmail_v1.Schema$MessagePart | undefined): WalkedParts;
|
|
23
|
+
export declare function findAttachment(payload: gmail_v1.Schema$MessagePart | undefined, attachmentId: string): ParsedAttachment | undefined;
|
|
24
|
+
export interface ParseContext {
|
|
25
|
+
accountId: string;
|
|
26
|
+
/** Gmail label id -> display name, for translating labelIds. */
|
|
27
|
+
labelNames: Map<string, string>;
|
|
28
|
+
includeBody: boolean;
|
|
29
|
+
/** Set only when the Gmail response includes the MIME payload. */
|
|
30
|
+
includeAttachments?: boolean;
|
|
31
|
+
includeHeaders?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare function parseGmailMessage(msg: gmail_v1.Schema$Message, ctx: ParseContext): Message;
|
|
34
|
+
export declare function parseGmailMessageWithParts(msg: gmail_v1.Schema$Message, ctx: ParseContext): {
|
|
35
|
+
message: Message;
|
|
36
|
+
externalBodyParts: ExternalBodyParts;
|
|
37
|
+
};
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAEL,KAAK,cAAc,EAGnB,KAAK,OAAO,EACZ,KAAK,WAAW,EACjB,MAAM,gBAAgB,CAAC;AAExB,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAMD,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,EAAE,OAAO,SAAwB,GAAG,MAAM,CAMzG;AAED,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAEjC,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,QAAQ,CAAC,kBAAkB,CAAC;CACnC;AAED,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAE7E,UAAU,WAAW;IACnB,IAAI,EAAE,WAAW,CAAC;IAClB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,iBAAiB,EAAE,iBAAiB,CAAC;CACtC;AAED,UAAU,gBAAgB;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAsDD,4FAA4F;AAC5F,wBAAgB,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,kBAAkB,GAAG,SAAS,GAAG,WAAW,CA2BvF;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,QAAQ,CAAC,kBAAkB,GAAG,SAAS,EAChD,YAAY,EAAE,MAAM,GACnB,gBAAgB,GAAG,SAAS,CAa9B;AAuBD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,kEAAkE;IAClE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAE1F;AAED,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,QAAQ,CAAC,cAAc,EAC5B,GAAG,EAAE,YAAY,GAChB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,iBAAiB,EAAE,iBAAiB,CAAA;CAAE,CAqD5D"}
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import iconv from 'iconv-lite';
|
|
2
|
+
import { parseAddressList, } from '@fluxmail/core';
|
|
3
|
+
export function decodeBase64Url(data) {
|
|
4
|
+
return Buffer.from(data.replace(/-/g, '+').replace(/_/g, '/'), 'base64');
|
|
5
|
+
}
|
|
6
|
+
export function encodeBase64Url(data) {
|
|
7
|
+
return data.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
8
|
+
}
|
|
9
|
+
function headerValue(payload, name) {
|
|
10
|
+
return payload?.headers?.find((h) => h.name?.toLowerCase() === name.toLowerCase())?.value ?? undefined;
|
|
11
|
+
}
|
|
12
|
+
export function decodeTextPart(part, encoded = part.body?.data ?? '') {
|
|
13
|
+
const data = decodeBase64Url(encoded);
|
|
14
|
+
const contentType = headerValue(part, 'Content-Type') ?? '';
|
|
15
|
+
const charset = contentType.match(/charset\s*=\s*(?:"([^"]+)"|'([^']+)'|([^;\s]+))/i);
|
|
16
|
+
const encoding = charset?.[1] ?? charset?.[2] ?? charset?.[3] ?? 'utf-8';
|
|
17
|
+
return iconv.decode(data, iconv.encodingExists(encoding) ? encoding : 'utf-8');
|
|
18
|
+
}
|
|
19
|
+
function parseAttachment(part, path) {
|
|
20
|
+
const body = part.body;
|
|
21
|
+
const rawContentId = headerValue(part, 'Content-ID')?.trim();
|
|
22
|
+
const contentId = rawContentId?.replace(/^<|>$/g, '');
|
|
23
|
+
const rawDisposition = headerValue(part, 'Content-Disposition')?.split(';', 1)[0]?.trim().toLowerCase();
|
|
24
|
+
const isMessageBody = part.mimeType === 'text/plain' || part.mimeType === 'text/html';
|
|
25
|
+
const hasContent = body?.attachmentId != null || body?.data != null;
|
|
26
|
+
const isAttachment = !!part.filename ||
|
|
27
|
+
rawDisposition === 'attachment' ||
|
|
28
|
+
(!isMessageBody && !part.mimeType?.startsWith('multipart/') && (hasContent || !!contentId || rawDisposition === 'inline'));
|
|
29
|
+
if (!isAttachment)
|
|
30
|
+
return undefined;
|
|
31
|
+
const disposition = rawDisposition === 'inline' || rawDisposition === 'attachment'
|
|
32
|
+
? rawDisposition
|
|
33
|
+
: contentId
|
|
34
|
+
? 'inline'
|
|
35
|
+
: undefined;
|
|
36
|
+
const metadata = {
|
|
37
|
+
...(contentId ? { contentId } : {}),
|
|
38
|
+
...(disposition ? { disposition } : {}),
|
|
39
|
+
};
|
|
40
|
+
const fallbackId = part.partId ?? path.join('.');
|
|
41
|
+
const filename = part.filename || `${disposition === 'inline' ? 'inline' : 'attachment'}-${fallbackId}`;
|
|
42
|
+
if (body?.attachmentId) {
|
|
43
|
+
return {
|
|
44
|
+
meta: {
|
|
45
|
+
id: body.attachmentId,
|
|
46
|
+
filename,
|
|
47
|
+
mimeType: part.mimeType ?? '',
|
|
48
|
+
sizeBytes: body.size ?? 0,
|
|
49
|
+
...metadata,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (body?.data != null) {
|
|
54
|
+
const content = decodeBase64Url(body.data);
|
|
55
|
+
return {
|
|
56
|
+
meta: {
|
|
57
|
+
id: `inline:${part.partId ?? path.join('.')}`,
|
|
58
|
+
filename,
|
|
59
|
+
mimeType: part.mimeType ?? '',
|
|
60
|
+
sizeBytes: body.size ?? content.length,
|
|
61
|
+
...metadata,
|
|
62
|
+
},
|
|
63
|
+
content,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
/** Walk the MIME tree collecting the preferred text/html bodies and attachment metadata. */
|
|
69
|
+
export function walkParts(payload) {
|
|
70
|
+
const result = { body: {}, attachments: [], externalBodyParts: {} };
|
|
71
|
+
if (!payload)
|
|
72
|
+
return result;
|
|
73
|
+
const visit = (part, partPath) => {
|
|
74
|
+
const mimeType = part.mimeType ?? '';
|
|
75
|
+
const attachment = parseAttachment(part, partPath);
|
|
76
|
+
if (attachment) {
|
|
77
|
+
result.attachments.push(attachment.meta);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const bodyField = mimeType === 'text/plain' ? 'text' : mimeType === 'text/html' ? 'html' : undefined;
|
|
81
|
+
if (bodyField && result.body[bodyField] === undefined && result.externalBodyParts[bodyField] === undefined) {
|
|
82
|
+
if (part.body?.data != null) {
|
|
83
|
+
result.body[bodyField] = decodeTextPart(part);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (part.body?.attachmentId) {
|
|
87
|
+
result.externalBodyParts[bodyField] = { attachmentId: part.body.attachmentId, part };
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const [index, child] of (part.parts ?? []).entries())
|
|
92
|
+
visit(child, [...partPath, index]);
|
|
93
|
+
};
|
|
94
|
+
visit(payload, [0]);
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
export function findAttachment(payload, attachmentId) {
|
|
98
|
+
if (!payload)
|
|
99
|
+
return undefined;
|
|
100
|
+
const visit = (part, partPath) => {
|
|
101
|
+
const attachment = parseAttachment(part, partPath);
|
|
102
|
+
if (attachment?.meta.id === attachmentId)
|
|
103
|
+
return attachment;
|
|
104
|
+
for (const [index, child] of (part.parts ?? []).entries()) {
|
|
105
|
+
const found = visit(child, [...partPath, index]);
|
|
106
|
+
if (found)
|
|
107
|
+
return found;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
};
|
|
111
|
+
return visit(payload, [0]);
|
|
112
|
+
}
|
|
113
|
+
/** System labels that pin a message's canonical location, most specific first. */
|
|
114
|
+
const LOCATION_LABEL_ROLES = [
|
|
115
|
+
['TRASH', 'trash'],
|
|
116
|
+
['SPAM', 'spam'],
|
|
117
|
+
['DRAFT', 'drafts'],
|
|
118
|
+
['INBOX', 'inbox'],
|
|
119
|
+
['SENT', 'sent'],
|
|
120
|
+
];
|
|
121
|
+
/** Gmail has no archive label: archived mail is whatever carries no location label. */
|
|
122
|
+
function deriveFolder(labelIds, labelNames) {
|
|
123
|
+
for (const [labelId, role] of LOCATION_LABEL_ROLES) {
|
|
124
|
+
if (labelIds.includes(labelId)) {
|
|
125
|
+
return { id: labelId, name: labelNames.get(labelId) ?? labelId, role };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { id: 'archive', name: 'Archive', role: 'archive' };
|
|
129
|
+
}
|
|
130
|
+
const INTERESTING_HEADERS = ['message-id', 'in-reply-to', 'references', 'list-unsubscribe'];
|
|
131
|
+
export function parseGmailMessage(msg, ctx) {
|
|
132
|
+
return parseGmailMessageWithParts(msg, ctx).message;
|
|
133
|
+
}
|
|
134
|
+
export function parseGmailMessageWithParts(msg, ctx) {
|
|
135
|
+
const payload = msg.payload;
|
|
136
|
+
const labelIds = msg.labelIds ?? [];
|
|
137
|
+
const from = parseAddressList(headerValue(payload, 'From'))[0];
|
|
138
|
+
const dateHeader = headerValue(payload, 'Date');
|
|
139
|
+
const dateMs = msg.internalDate ? Number(msg.internalDate) : dateHeader ? Date.parse(dateHeader) : Date.now();
|
|
140
|
+
const message = {
|
|
141
|
+
id: msg.id ?? '',
|
|
142
|
+
threadId: msg.threadId ?? msg.id ?? '',
|
|
143
|
+
accountId: ctx.accountId,
|
|
144
|
+
folder: deriveFolder(labelIds, ctx.labelNames),
|
|
145
|
+
labels: labelIds
|
|
146
|
+
.filter((id) => !id.startsWith('CATEGORY_'))
|
|
147
|
+
.map((id) => ctx.labelNames.get(id) ?? id),
|
|
148
|
+
to: parseAddressList(headerValue(payload, 'To')),
|
|
149
|
+
subject: headerValue(payload, 'Subject') ?? '',
|
|
150
|
+
date: new Date(dateMs).toISOString(),
|
|
151
|
+
flags: {
|
|
152
|
+
read: !labelIds.includes('UNREAD'),
|
|
153
|
+
starred: labelIds.includes('STARRED'),
|
|
154
|
+
draft: labelIds.includes('DRAFT'),
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
if (from)
|
|
158
|
+
message.from = from;
|
|
159
|
+
const cc = parseAddressList(headerValue(payload, 'Cc'));
|
|
160
|
+
if (cc.length)
|
|
161
|
+
message.cc = cc;
|
|
162
|
+
const bcc = parseAddressList(headerValue(payload, 'Bcc'));
|
|
163
|
+
if (bcc.length)
|
|
164
|
+
message.bcc = bcc;
|
|
165
|
+
const replyTo = parseAddressList(headerValue(payload, 'Reply-To'));
|
|
166
|
+
if (replyTo.length)
|
|
167
|
+
message.replyTo = replyTo;
|
|
168
|
+
if (msg.snippet)
|
|
169
|
+
message.snippet = msg.snippet;
|
|
170
|
+
let externalBodyParts = {};
|
|
171
|
+
if (ctx.includeBody || ctx.includeAttachments) {
|
|
172
|
+
const walked = walkParts(payload);
|
|
173
|
+
if (ctx.includeAttachments)
|
|
174
|
+
message.attachments = walked.attachments;
|
|
175
|
+
if (ctx.includeBody)
|
|
176
|
+
message.body = walked.body;
|
|
177
|
+
externalBodyParts = walked.externalBodyParts;
|
|
178
|
+
}
|
|
179
|
+
if (ctx.includeHeaders) {
|
|
180
|
+
const headers = {};
|
|
181
|
+
for (const h of payload?.headers ?? []) {
|
|
182
|
+
if (h.name && h.value && INTERESTING_HEADERS.includes(h.name.toLowerCase())) {
|
|
183
|
+
headers[h.name] = h.value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
message.headers = headers;
|
|
187
|
+
}
|
|
188
|
+
return { message, externalBodyParts };
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"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"}
|
package/dist/query.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type EmailQuery } from '@fluxmail/core';
|
|
2
|
+
export declare const ROLE_TO_LABEL: Record<string, string>;
|
|
3
|
+
export interface GmailQuery {
|
|
4
|
+
q?: string;
|
|
5
|
+
labelIds?: string[];
|
|
6
|
+
includeSpamTrash?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Translate the unified EmailQuery into Gmail's q= syntax + labelIds.
|
|
10
|
+
* `resolveLabelId` maps a user folder name/id to a Gmail label id (null if unknown).
|
|
11
|
+
*/
|
|
12
|
+
export declare function toGmailQuery(q: EmailQuery, resolveLabelId: (folder: string) => string | null): GmailQuery;
|
|
13
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE7D,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAOhD,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAcD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,CAAC,EAAE,UAAU,EACb,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAChD,UAAU,CAkCZ"}
|
package/dist/query.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { EmailError } from '@fluxmail/core';
|
|
2
|
+
export const ROLE_TO_LABEL = {
|
|
3
|
+
inbox: 'INBOX',
|
|
4
|
+
sent: 'SENT',
|
|
5
|
+
drafts: 'DRAFT',
|
|
6
|
+
trash: 'TRASH',
|
|
7
|
+
spam: 'SPAM',
|
|
8
|
+
starred: 'STARRED',
|
|
9
|
+
};
|
|
10
|
+
function quote(value) {
|
|
11
|
+
return /[\s"]/.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value;
|
|
12
|
+
}
|
|
13
|
+
function epochSeconds(iso, field) {
|
|
14
|
+
const millis = new Date(iso).getTime();
|
|
15
|
+
if (!Number.isFinite(millis)) {
|
|
16
|
+
throw new EmailError('invalid_request', `${field} must be a valid ISO date, got "${iso}"`);
|
|
17
|
+
}
|
|
18
|
+
return Math.floor(millis / 1000);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Translate the unified EmailQuery into Gmail's q= syntax + labelIds.
|
|
22
|
+
* `resolveLabelId` maps a user folder name/id to a Gmail label id (null if unknown).
|
|
23
|
+
*/
|
|
24
|
+
export function toGmailQuery(q, resolveLabelId) {
|
|
25
|
+
const parts = [];
|
|
26
|
+
const out = {};
|
|
27
|
+
if (q.folder) {
|
|
28
|
+
const role = q.folder.toLowerCase();
|
|
29
|
+
if (role === 'archive') {
|
|
30
|
+
parts.push('in:archive');
|
|
31
|
+
}
|
|
32
|
+
else if (role === 'all') {
|
|
33
|
+
parts.push('in:anywhere');
|
|
34
|
+
out.includeSpamTrash = true;
|
|
35
|
+
}
|
|
36
|
+
else if (ROLE_TO_LABEL[role]) {
|
|
37
|
+
out.labelIds = [ROLE_TO_LABEL[role]];
|
|
38
|
+
if (role === 'trash' || role === 'spam')
|
|
39
|
+
out.includeSpamTrash = true;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const labelId = resolveLabelId(q.folder);
|
|
43
|
+
if (labelId)
|
|
44
|
+
out.labelIds = [labelId];
|
|
45
|
+
else
|
|
46
|
+
parts.push(`label:${quote(q.folder)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (q.text)
|
|
50
|
+
parts.push(q.text);
|
|
51
|
+
if (q.from)
|
|
52
|
+
parts.push(`from:${quote(q.from)}`);
|
|
53
|
+
if (q.to)
|
|
54
|
+
parts.push(`to:${quote(q.to)}`);
|
|
55
|
+
if (q.subject)
|
|
56
|
+
parts.push(`subject:${quote(q.subject)}`);
|
|
57
|
+
if (q.unreadOnly)
|
|
58
|
+
parts.push('is:unread');
|
|
59
|
+
if (q.starredOnly)
|
|
60
|
+
parts.push('is:starred');
|
|
61
|
+
if (q.hasAttachment)
|
|
62
|
+
parts.push('has:attachment');
|
|
63
|
+
if (q.after)
|
|
64
|
+
parts.push(`after:${epochSeconds(q.after, 'after')}`);
|
|
65
|
+
if (q.before)
|
|
66
|
+
parts.push(`before:${epochSeconds(q.before, 'before')}`);
|
|
67
|
+
if (q.rawProviderQuery)
|
|
68
|
+
parts.push(q.rawProviderQuery);
|
|
69
|
+
if (parts.length)
|
|
70
|
+
out.q = parts.join(' ');
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fluxmail/provider-gmail",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Gmail adapter for the Fluxmail unified email API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"googleapis": "^173.0.0",
|
|
22
|
+
"googleapis-common": "8.0.2",
|
|
23
|
+
"iconv-lite": "^0.7.3",
|
|
24
|
+
"nodemailer": "^9.0.3",
|
|
25
|
+
"@fluxmail/core": "0.1.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^22.15.0",
|
|
29
|
+
"@types/nodemailer": "^8.0.1"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.json",
|
|
33
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
34
|
+
"test": "vitest run"
|
|
35
|
+
}
|
|
36
|
+
}
|