@kubun/connector-google-mail 0.17.2 → 0.17.3
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/lib/api.d.ts +2 -1
- package/lib/api.js +39 -102
- package/lib/model.js +5 -21
- package/lib/provider.js +48 -76
- package/package.json +4 -4
package/lib/api.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type EntityRecord, type WriteAttachment } from '@kubun/connector';
|
|
2
|
+
import { type GoogleAPIClient } from '@kubun/provider-google';
|
|
2
3
|
export declare const MAIL_BASE_URL = "https://gmail.googleapis.com/gmail/v1";
|
|
3
|
-
export declare function
|
|
4
|
+
export declare function mailAPIClient(accessToken: string, fetch?: typeof globalThis.fetch): GoogleAPIClient;
|
|
4
5
|
export declare function decodeBase64Url(data: string): string;
|
|
5
6
|
export declare function decodeBase64UrlBytes(data: string): Uint8Array;
|
|
6
7
|
export declare function encodeBase64Url(text: string): string;
|
package/lib/api.js
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
import { connectorHTTPError, markWriteSent } from '@kubun/connector';
|
|
2
|
+
import { createGoogleAPIClient } from '@kubun/provider-google';
|
|
2
3
|
import { b64uFromUTF, b64uToUTF, fromB64U } from '@sozai/codec';
|
|
3
4
|
export const MAIL_BASE_URL = 'https://gmail.googleapis.com/gmail/v1';
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
// Build the error thrown by a Gmail call, tagged with the HTTP status and its
|
|
13
|
-
// outcome classification (`kind`, see `classifyHTTPStatus`) — a definite
|
|
14
|
-
// rejection dead-letters a write instead of re-driving it. The status is also
|
|
15
|
-
// the seam fetchEntity/reconcile (provider.ts) use to turn a 404 into a
|
|
16
|
-
// `present: false` result (deletion detected) instead of a throw.
|
|
17
|
-
async function googleMailAPIError(response) {
|
|
18
|
-
const detail = await parseGoogleError(response);
|
|
19
|
-
return connectorHTTPError(`Google Mail API error: ${detail}`, response.status);
|
|
5
|
+
export function mailAPIClient(accessToken, fetch) {
|
|
6
|
+
return createGoogleAPIClient({
|
|
7
|
+
api: 'Google Mail API',
|
|
8
|
+
accessToken,
|
|
9
|
+
fetch,
|
|
10
|
+
toError: ({ httpStatus, message })=>connectorHTTPError(message, httpStatus)
|
|
11
|
+
});
|
|
20
12
|
}
|
|
21
13
|
export function decodeBase64Url(data) {
|
|
22
14
|
return b64uToUTF(data);
|
|
@@ -147,52 +139,34 @@ export function mapMessageToEntity(message) {
|
|
|
147
139
|
};
|
|
148
140
|
}
|
|
149
141
|
// Targeted single-message fetch (anti-entropy repair verification / write-back
|
|
150
|
-
// recovery's fetchEntity+reconcile). A 404 here is the deletion signal
|
|
151
|
-
//
|
|
142
|
+
// recovery's fetchEntity+reconcile). A 404 here is the deletion signal:
|
|
143
|
+
// fetchOrAbsent turns it into a `present: false` result instead of a throw.
|
|
152
144
|
export async function getMessage(config) {
|
|
153
|
-
|
|
154
|
-
const response = await fetchFn(buildMessageGetURL({
|
|
145
|
+
return mailAPIClient(config.accessToken, config.fetch).get(buildMessageGetURL({
|
|
155
146
|
messageID: config.messageID
|
|
156
147
|
}), {
|
|
157
|
-
headers: {
|
|
158
|
-
Authorization: `Bearer ${config.accessToken}`
|
|
159
|
-
},
|
|
160
148
|
signal: config.signal
|
|
161
149
|
});
|
|
162
|
-
if (!response.ok) throw await googleMailAPIError(response);
|
|
163
|
-
return await response.json();
|
|
164
150
|
}
|
|
165
151
|
// Targeted single-thread fetch — a real threads.get(format=full) read, mapped
|
|
166
152
|
// from its own resource (id + constituent messages), never synthesized from a
|
|
167
153
|
// bulk message listing's locally-scanned subject.
|
|
168
154
|
export async function getThread(config) {
|
|
169
|
-
|
|
170
|
-
const response = await fetchFn(buildThreadGetURL({
|
|
155
|
+
return mailAPIClient(config.accessToken, config.fetch).get(buildThreadGetURL({
|
|
171
156
|
threadID: config.threadID
|
|
172
157
|
}), {
|
|
173
|
-
headers: {
|
|
174
|
-
Authorization: `Bearer ${config.accessToken}`
|
|
175
|
-
},
|
|
176
158
|
signal: config.signal
|
|
177
159
|
});
|
|
178
|
-
if (!response.ok) throw await googleMailAPIError(response);
|
|
179
|
-
return await response.json();
|
|
180
160
|
}
|
|
181
161
|
// Targeted single-label fetch. Unlike labels.list (buildLabelsListURL, used by
|
|
182
162
|
// the bulk labelBatch), a direct labels.get returns the full resource
|
|
183
163
|
// including `color` — the list endpoint omits it.
|
|
184
164
|
export async function getLabel(config) {
|
|
185
|
-
|
|
186
|
-
const response = await fetchFn(buildLabelGetURL({
|
|
165
|
+
return mailAPIClient(config.accessToken, config.fetch).get(buildLabelGetURL({
|
|
187
166
|
labelID: config.labelID
|
|
188
167
|
}), {
|
|
189
|
-
headers: {
|
|
190
|
-
Authorization: `Bearer ${config.accessToken}`
|
|
191
|
-
},
|
|
192
168
|
signal: config.signal
|
|
193
169
|
});
|
|
194
|
-
if (!response.ok) throw await googleMailAPIError(response);
|
|
195
|
-
return await response.json();
|
|
196
170
|
}
|
|
197
171
|
function streamOfBytes(bytes) {
|
|
198
172
|
return new ReadableStream({
|
|
@@ -215,17 +189,10 @@ export async function importMessageAttachment(config) {
|
|
|
215
189
|
if (target == null) {
|
|
216
190
|
throw new Error(`Google Mail: no attachment with providerRef "${config.providerRef}"`);
|
|
217
191
|
}
|
|
218
|
-
const
|
|
219
|
-
const response = await fetchFn(buildAttachmentGetURL({
|
|
192
|
+
const body = await mailAPIClient(config.accessToken, config.fetch).get(buildAttachmentGetURL({
|
|
220
193
|
messageID: config.messageID,
|
|
221
194
|
attachmentID: config.providerRef
|
|
222
|
-
})
|
|
223
|
-
headers: {
|
|
224
|
-
Authorization: `Bearer ${config.accessToken}`
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
if (!response.ok) throw await googleMailAPIError(response);
|
|
228
|
-
const body = await response.json();
|
|
195
|
+
}));
|
|
229
196
|
if (body.data == null) throw new Error('Google Mail: attachment response has no data');
|
|
230
197
|
const bytes = decodeBase64UrlBytes(body.data);
|
|
231
198
|
const attachmentID = await config.writeAttachment(streamOfBytes(bytes), {
|
|
@@ -301,22 +268,16 @@ function requireString(params, key) {
|
|
|
301
268
|
async function readBackWrittenMessage(config) {
|
|
302
269
|
try {
|
|
303
270
|
const messageID = config.messageIDOf(await config.written.json());
|
|
304
|
-
const
|
|
305
|
-
const response = await fetchFn(buildMessageGetURL({
|
|
271
|
+
const message = await mailAPIClient(config.accessToken, config.fetch).get(buildMessageGetURL({
|
|
306
272
|
messageID
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
Authorization: `Bearer ${config.accessToken}`
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
if (!response.ok) throw await googleMailAPIError(response);
|
|
313
|
-
return mapMessageToEntity(await response.json());
|
|
273
|
+
}));
|
|
274
|
+
return mapMessageToEntity(message);
|
|
314
275
|
} catch (err) {
|
|
315
276
|
throw markWriteSent(err);
|
|
316
277
|
}
|
|
317
278
|
}
|
|
318
279
|
export async function sendMessage(config) {
|
|
319
|
-
const
|
|
280
|
+
const client = mailAPIClient(config.accessToken, config.fetch);
|
|
320
281
|
const raw = buildRawMessage({
|
|
321
282
|
to: config.params.to,
|
|
322
283
|
cc: config.params.cc,
|
|
@@ -324,17 +285,13 @@ export async function sendMessage(config) {
|
|
|
324
285
|
subject: requireString(config.params, 'subject'),
|
|
325
286
|
body: requireString(config.params, 'body')
|
|
326
287
|
});
|
|
327
|
-
const response = await
|
|
288
|
+
const response = await client.request(`${MAIL_BASE_URL}/users/me/messages/send`, {
|
|
328
289
|
method: 'POST',
|
|
329
|
-
|
|
330
|
-
Authorization: `Bearer ${config.accessToken}`,
|
|
331
|
-
'Content-Type': 'application/json'
|
|
332
|
-
},
|
|
333
|
-
body: JSON.stringify({
|
|
290
|
+
body: {
|
|
334
291
|
raw
|
|
335
|
-
}
|
|
292
|
+
}
|
|
336
293
|
});
|
|
337
|
-
if (!response.ok)
|
|
294
|
+
if (!response.ok) return client.fail(response);
|
|
338
295
|
return readBackWrittenMessage({
|
|
339
296
|
accessToken: config.accessToken,
|
|
340
297
|
written: response,
|
|
@@ -343,7 +300,7 @@ export async function sendMessage(config) {
|
|
|
343
300
|
});
|
|
344
301
|
}
|
|
345
302
|
export async function createDraft(config) {
|
|
346
|
-
const
|
|
303
|
+
const client = mailAPIClient(config.accessToken, config.fetch);
|
|
347
304
|
const raw = buildRawMessage({
|
|
348
305
|
to: config.params.to,
|
|
349
306
|
cc: config.params.cc,
|
|
@@ -351,19 +308,15 @@ export async function createDraft(config) {
|
|
|
351
308
|
subject: requireString(config.params, 'subject'),
|
|
352
309
|
body: requireString(config.params, 'body')
|
|
353
310
|
});
|
|
354
|
-
const response = await
|
|
311
|
+
const response = await client.request(`${MAIL_BASE_URL}/users/me/drafts`, {
|
|
355
312
|
method: 'POST',
|
|
356
|
-
|
|
357
|
-
Authorization: `Bearer ${config.accessToken}`,
|
|
358
|
-
'Content-Type': 'application/json'
|
|
359
|
-
},
|
|
360
|
-
body: JSON.stringify({
|
|
313
|
+
body: {
|
|
361
314
|
message: {
|
|
362
315
|
raw
|
|
363
316
|
}
|
|
364
|
-
}
|
|
317
|
+
}
|
|
365
318
|
});
|
|
366
|
-
if (!response.ok)
|
|
319
|
+
if (!response.ok) return client.fail(response);
|
|
367
320
|
return readBackWrittenMessage({
|
|
368
321
|
accessToken: config.accessToken,
|
|
369
322
|
written: response,
|
|
@@ -372,20 +325,16 @@ export async function createDraft(config) {
|
|
|
372
325
|
});
|
|
373
326
|
}
|
|
374
327
|
export async function modifyLabels(config) {
|
|
375
|
-
const
|
|
328
|
+
const client = mailAPIClient(config.accessToken, config.fetch);
|
|
376
329
|
const url = `${MAIL_BASE_URL}/users/me/messages/${encodeURIComponent(config.messageID)}/modify`;
|
|
377
|
-
const response = await
|
|
330
|
+
const response = await client.request(url, {
|
|
378
331
|
method: 'POST',
|
|
379
|
-
|
|
380
|
-
Authorization: `Bearer ${config.accessToken}`,
|
|
381
|
-
'Content-Type': 'application/json'
|
|
382
|
-
},
|
|
383
|
-
body: JSON.stringify({
|
|
332
|
+
body: {
|
|
384
333
|
addLabelIds: config.addLabelIds ?? [],
|
|
385
334
|
removeLabelIds: config.removeLabelIds ?? []
|
|
386
|
-
}
|
|
335
|
+
}
|
|
387
336
|
});
|
|
388
|
-
if (!response.ok)
|
|
337
|
+
if (!response.ok) return client.fail(response);
|
|
389
338
|
return readBackWrittenMessage({
|
|
390
339
|
accessToken: config.accessToken,
|
|
391
340
|
written: response,
|
|
@@ -414,31 +363,19 @@ function buildLabelBody(params) {
|
|
|
414
363
|
return body;
|
|
415
364
|
}
|
|
416
365
|
export async function createLabel(config) {
|
|
417
|
-
const fetchFn = config.fetch ?? globalThis.fetch;
|
|
418
366
|
const body = buildLabelBody(config.params);
|
|
419
367
|
if (typeof body.name !== 'string') throw new Error('Google Mail: "name" is required');
|
|
420
|
-
const
|
|
368
|
+
const label = await mailAPIClient(config.accessToken, config.fetch).send(buildLabelsListURL(), {
|
|
421
369
|
method: 'POST',
|
|
422
|
-
|
|
423
|
-
Authorization: `Bearer ${config.accessToken}`,
|
|
424
|
-
'Content-Type': 'application/json'
|
|
425
|
-
},
|
|
426
|
-
body: JSON.stringify(body)
|
|
370
|
+
body
|
|
427
371
|
});
|
|
428
|
-
|
|
429
|
-
return mapLabelToEntity(await response.json());
|
|
372
|
+
return mapLabelToEntity(label);
|
|
430
373
|
}
|
|
431
374
|
export async function updateLabel(config) {
|
|
432
|
-
const fetchFn = config.fetch ?? globalThis.fetch;
|
|
433
375
|
const url = `${MAIL_BASE_URL}/users/me/labels/${encodeURIComponent(config.labelID)}`;
|
|
434
|
-
const
|
|
376
|
+
const label = await mailAPIClient(config.accessToken, config.fetch).send(url, {
|
|
435
377
|
method: 'PATCH',
|
|
436
|
-
|
|
437
|
-
Authorization: `Bearer ${config.accessToken}`,
|
|
438
|
-
'Content-Type': 'application/json'
|
|
439
|
-
},
|
|
440
|
-
body: JSON.stringify(buildLabelBody(config.params))
|
|
378
|
+
body: buildLabelBody(config.params)
|
|
441
379
|
});
|
|
442
|
-
|
|
443
|
-
return mapLabelToEntity(await response.json());
|
|
380
|
+
return mapLabelToEntity(label);
|
|
444
381
|
}
|
package/lib/model.js
CHANGED
|
@@ -1,23 +1,7 @@
|
|
|
1
|
-
import { connectorClusters, connectorModelIDs } from '@kubun/connector';
|
|
1
|
+
import { connectorClusters, connectorModelIDs, externalEntityDefinition } from '@kubun/connector';
|
|
2
2
|
import { modelIDs, modelsClusters } from '@kubun/models';
|
|
3
3
|
import { GraphBuilder } from '@kubun/protocol';
|
|
4
|
-
import {
|
|
5
|
-
const commonDefinitions = {
|
|
6
|
-
SourceService: {
|
|
7
|
-
type: 'string',
|
|
8
|
-
title: 'SourceService',
|
|
9
|
-
minLength: 1,
|
|
10
|
-
maxLength: 50
|
|
11
|
-
},
|
|
12
|
-
SourceID: {
|
|
13
|
-
type: 'string',
|
|
14
|
-
title: 'SourceID',
|
|
15
|
-
minLength: 1,
|
|
16
|
-
maxLength: 500
|
|
17
|
-
},
|
|
18
|
-
SourceURL: urlSchema,
|
|
19
|
-
DateTime: dateTimeSchema
|
|
20
|
-
};
|
|
4
|
+
import { docIDSchema } from '@kubun/scalars';
|
|
21
5
|
export const googleMailThreadDefinition = {
|
|
22
6
|
name: 'GoogleMailThread',
|
|
23
7
|
behavior: 'default',
|
|
@@ -52,7 +36,7 @@ export const googleMailThreadDefinition = {
|
|
|
52
36
|
],
|
|
53
37
|
additionalProperties: false,
|
|
54
38
|
definitions: {
|
|
55
|
-
...
|
|
39
|
+
...externalEntityDefinition.schema.definitions
|
|
56
40
|
}
|
|
57
41
|
},
|
|
58
42
|
fieldsMeta: {},
|
|
@@ -155,7 +139,7 @@ export const googleMailMessageDefinition = {
|
|
|
155
139
|
],
|
|
156
140
|
additionalProperties: false,
|
|
157
141
|
definitions: {
|
|
158
|
-
...
|
|
142
|
+
...externalEntityDefinition.schema.definitions,
|
|
159
143
|
ThreadDocID: docIDSchema,
|
|
160
144
|
Attachment: {
|
|
161
145
|
type: 'object',
|
|
@@ -274,7 +258,7 @@ export const googleMailLabelDefinition = {
|
|
|
274
258
|
],
|
|
275
259
|
additionalProperties: false,
|
|
276
260
|
definitions: {
|
|
277
|
-
...
|
|
261
|
+
...externalEntityDefinition.schema.definitions
|
|
278
262
|
}
|
|
279
263
|
},
|
|
280
264
|
fieldsMeta: {},
|
package/lib/provider.js
CHANGED
|
@@ -1,32 +1,18 @@
|
|
|
1
|
-
import { resolveModelID } from '@kubun/connector';
|
|
2
|
-
import { buildHistoryListURL, buildLabelsListURL, buildMessagesListURL, buildProfileURL, getHeader, getLabel, getMessage, getThread, mapLabelToEntity, mapMessageToEntity, mapThreadToEntity
|
|
1
|
+
import { fetchOrAbsent, parseMaxAgeDays, resolveModelID } from '@kubun/connector';
|
|
2
|
+
import { buildHistoryListURL, buildLabelsListURL, buildMessagesListURL, buildProfileURL, getHeader, getLabel, getMessage, getThread, mailAPIClient, mapLabelToEntity, mapMessageToEntity, mapThreadToEntity } from './api.js';
|
|
3
3
|
import { googleMailClusters } from './model.js';
|
|
4
4
|
export const DEFAULT_QUERY = 'in:inbox';
|
|
5
5
|
const THREAD_MODEL_ID = resolveModelID(googleMailClusters, 'GoogleMailThread');
|
|
6
6
|
const MESSAGE_MODEL_ID = resolveModelID(googleMailClusters, 'GoogleMailMessage');
|
|
7
7
|
const LABEL_MODEL_ID = resolveModelID(googleMailClusters, 'GoogleMailLabel');
|
|
8
|
-
async function fetchJSON(url, credential, fetchFn, signal) {
|
|
9
|
-
const response = await fetchFn(url, {
|
|
10
|
-
headers: {
|
|
11
|
-
Authorization: `Bearer ${credential.accessToken}`
|
|
12
|
-
},
|
|
13
|
-
signal
|
|
14
|
-
});
|
|
15
|
-
if (!response.ok) {
|
|
16
|
-
throw new Error(`Google Mail API error: ${await parseGoogleError(response)}`);
|
|
17
|
-
}
|
|
18
|
-
return await response.json();
|
|
19
|
-
}
|
|
20
8
|
/**
|
|
21
9
|
* The absolute lower bound (epoch seconds) for a full message listing: a window
|
|
22
10
|
* already frozen in the checkpoint, else `now − maxAge` computed once. Null when
|
|
23
11
|
* the boundary carries no `maxAge` (no floor).
|
|
24
12
|
*/ function windowStartSeconds(boundary, frozen) {
|
|
25
13
|
if (frozen != null) return frozen;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (match?.[1] == null) return null;
|
|
29
|
-
const days = Number.parseInt(match[1], 10);
|
|
14
|
+
const days = parseMaxAgeDays(boundary);
|
|
15
|
+
if (days == null) return null;
|
|
30
16
|
return Math.floor((Date.now() - days * 86_400_000) / 1000);
|
|
31
17
|
}
|
|
32
18
|
/** Appends an absolute `after:<epoch seconds>` floor to a Gmail search query. */ function applyAfter(query, windowStart) {
|
|
@@ -44,21 +30,18 @@ function maxHistoryId(messages) {
|
|
|
44
30
|
}
|
|
45
31
|
export function createGoogleMailProvider(config) {
|
|
46
32
|
const fetchFn = config.fetch ?? globalThis.fetch;
|
|
33
|
+
const client = mailAPIClient(config.credential.accessToken, fetchFn);
|
|
47
34
|
const mintFetchHLC = config.mintFetchHLC;
|
|
48
|
-
// getMessage/getThread/getLabel (api.ts) throw a plain Error tagged with
|
|
49
|
-
// `.httpStatus` — this is the seam fetchEntity/reconcile use to turn a 404
|
|
50
|
-
// into a `present: false` result instead of a throw.
|
|
51
|
-
function isNotFound(err) {
|
|
52
|
-
return err?.httpStatus === 404;
|
|
53
|
-
}
|
|
54
35
|
async function listMessageIDs(query, signal) {
|
|
55
36
|
const ids = [];
|
|
56
37
|
let pageToken;
|
|
57
38
|
do {
|
|
58
|
-
const data = await
|
|
39
|
+
const data = await client.get(buildMessagesListURL({
|
|
59
40
|
query,
|
|
60
41
|
pageToken
|
|
61
|
-
}),
|
|
42
|
+
}), {
|
|
43
|
+
signal
|
|
44
|
+
});
|
|
62
45
|
for (const m of data.messages ?? [])ids.push(m);
|
|
63
46
|
pageToken = data.nextPageToken;
|
|
64
47
|
}while (pageToken != null)
|
|
@@ -76,14 +59,18 @@ export function createGoogleMailProvider(config) {
|
|
|
76
59
|
});
|
|
77
60
|
}
|
|
78
61
|
async function profileHistoryId(signal) {
|
|
79
|
-
const profile = await
|
|
62
|
+
const profile = await client.get(buildProfileURL(), {
|
|
63
|
+
signal
|
|
64
|
+
});
|
|
80
65
|
return profile.historyId ?? '0';
|
|
81
66
|
}
|
|
82
67
|
// Labels have no incremental history API, so every sync re-lists them in full.
|
|
83
68
|
// The list endpoint is a single cheap call; it omits color (captured only on
|
|
84
69
|
// create/edit) but carries id, name, type, and the visibility fields.
|
|
85
70
|
async function labelBatch(signal) {
|
|
86
|
-
const data = await
|
|
71
|
+
const data = await client.get(buildLabelsListURL(), {
|
|
72
|
+
signal
|
|
73
|
+
});
|
|
87
74
|
const sourceFetchHLC = mintFetchHLC();
|
|
88
75
|
return {
|
|
89
76
|
modelID: LABEL_MODEL_ID,
|
|
@@ -157,22 +144,18 @@ export function createGoogleMailProvider(config) {
|
|
|
157
144
|
let latestHistoryId = checkpoint.historyId;
|
|
158
145
|
let pageToken;
|
|
159
146
|
do {
|
|
160
|
-
const response = await
|
|
147
|
+
const response = await client.request(buildHistoryListURL({
|
|
161
148
|
startHistoryId: checkpoint.historyId,
|
|
162
149
|
pageToken
|
|
163
150
|
}), {
|
|
164
|
-
|
|
165
|
-
Authorization: `Bearer ${config.credential.accessToken}`
|
|
166
|
-
},
|
|
151
|
+
method: 'GET',
|
|
167
152
|
signal: params.signal
|
|
168
153
|
});
|
|
169
154
|
if (response.status === 404) {
|
|
170
155
|
yield* fetchAllEntities(params);
|
|
171
156
|
return;
|
|
172
157
|
}
|
|
173
|
-
if (!response.ok)
|
|
174
|
-
throw new Error(`Google Mail API error: ${await parseGoogleError(response)}`);
|
|
175
|
-
}
|
|
158
|
+
if (!response.ok) await client.fail(response);
|
|
176
159
|
const data = await response.json();
|
|
177
160
|
for (const record of data.history ?? []){
|
|
178
161
|
for (const ref of [
|
|
@@ -247,53 +230,42 @@ export function createGoogleMailProvider(config) {
|
|
|
247
230
|
// the list endpoint labelBatch uses).
|
|
248
231
|
async function fetchEntity(model, sourceID) {
|
|
249
232
|
const sourceFetchHLC = mintFetchHLC();
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
233
|
+
return fetchOrAbsent({
|
|
234
|
+
sourceID,
|
|
235
|
+
sourceFetchHLC,
|
|
236
|
+
read: async ()=>{
|
|
237
|
+
if (model === THREAD_MODEL_ID) {
|
|
238
|
+
const thread = await getThread({
|
|
239
|
+
accessToken: config.credential.accessToken,
|
|
240
|
+
threadID: sourceID,
|
|
241
|
+
fetch: fetchFn
|
|
242
|
+
});
|
|
243
|
+
const first = thread.messages?.[0];
|
|
244
|
+
const subject = first != null ? getHeader(first.payload?.headers, 'Subject') : undefined;
|
|
245
|
+
return {
|
|
246
|
+
record: mapThreadToEntity(thread.id, subject)
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (model === LABEL_MODEL_ID) {
|
|
250
|
+
const label = await getLabel({
|
|
251
|
+
accessToken: config.credential.accessToken,
|
|
252
|
+
labelID: sourceID,
|
|
253
|
+
fetch: fetchFn
|
|
254
|
+
});
|
|
255
|
+
return {
|
|
256
|
+
record: mapLabelToEntity(label)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const message = await getMessage({
|
|
267
260
|
accessToken: config.credential.accessToken,
|
|
268
|
-
|
|
261
|
+
messageID: sourceID,
|
|
269
262
|
fetch: fetchFn
|
|
270
263
|
});
|
|
271
264
|
return {
|
|
272
|
-
|
|
273
|
-
record: mapLabelToEntity(label),
|
|
274
|
-
sourceFetchHLC
|
|
265
|
+
record: mapMessageToEntity(message)
|
|
275
266
|
};
|
|
276
267
|
}
|
|
277
|
-
|
|
278
|
-
accessToken: config.credential.accessToken,
|
|
279
|
-
messageID: sourceID,
|
|
280
|
-
fetch: fetchFn
|
|
281
|
-
});
|
|
282
|
-
return {
|
|
283
|
-
present: true,
|
|
284
|
-
record: mapMessageToEntity(message),
|
|
285
|
-
sourceFetchHLC
|
|
286
|
-
};
|
|
287
|
-
} catch (err) {
|
|
288
|
-
if (isNotFound(err)) {
|
|
289
|
-
return {
|
|
290
|
-
present: false,
|
|
291
|
-
sourceID,
|
|
292
|
-
sourceFetchHLC
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
throw err;
|
|
296
|
-
}
|
|
268
|
+
});
|
|
297
269
|
}
|
|
298
270
|
// Provider-authoritative re-read for write-back recovery. Gmail's
|
|
299
271
|
// resources carry no dependable ETag on a direct get, so this mirrors
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/connector-google-mail",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.3",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"license": "see LICENSE.md",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
"LICENSE.md"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@kubun/connector": "^0.17.
|
|
18
|
+
"@kubun/connector": "^0.17.3",
|
|
19
19
|
"@kubun/credential-types": "^0.17.2",
|
|
20
20
|
"@kubun/hlc": "^0.17.0",
|
|
21
21
|
"@kubun/models": "^0.17.0",
|
|
22
|
-
"@kubun/protocol": "^0.17.
|
|
23
|
-
"@kubun/provider-google": "^0.17.
|
|
22
|
+
"@kubun/protocol": "^0.17.2",
|
|
23
|
+
"@kubun/provider-google": "^0.17.1",
|
|
24
24
|
"@kubun/scalars": "^0.17.0",
|
|
25
25
|
"@sozai/codec": "^0.4.0"
|
|
26
26
|
},
|