@kubun/connector-google-mail 0.12.0 → 0.13.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/lib/actions.d.ts +2 -1
- package/lib/actions.js +18 -2
- package/lib/api.d.ts +14 -1
- package/lib/api.js +59 -3
- package/lib/connector.js +7 -2
- package/lib/model.d.ts +31 -5
- package/lib/model.js +15 -1
- package/package.json +6 -5
package/lib/actions.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { ActionHandler } from '@kubun/connector';
|
|
1
|
+
import type { ActionHandler, WriteAttachment } from '@kubun/connector';
|
|
2
2
|
export type GoogleMailActionHandlerConfig = {
|
|
3
3
|
credential: {
|
|
4
4
|
accessToken: string;
|
|
5
5
|
scopes: Array<string>;
|
|
6
6
|
};
|
|
7
7
|
fetch?: typeof globalThis.fetch;
|
|
8
|
+
writeAttachment?: WriteAttachment;
|
|
8
9
|
};
|
|
9
10
|
export declare function createGoogleMailActionHandler(config: GoogleMailActionHandlerConfig): ActionHandler;
|
package/lib/actions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createDraft, createLabel, modifyLabels, sendMessage, updateLabel } from './api.js';
|
|
1
|
+
import { createDraft, createLabel, importMessageAttachment, modifyLabels, sendMessage, updateLabel } from './api.js';
|
|
2
2
|
export function createGoogleMailActionHandler(config) {
|
|
3
3
|
return {
|
|
4
4
|
async create (model, params) {
|
|
@@ -22,7 +22,7 @@ export function createGoogleMailActionHandler(config) {
|
|
|
22
22
|
fetch: config.fetch
|
|
23
23
|
});
|
|
24
24
|
},
|
|
25
|
-
async update (model, sourceID, params) {
|
|
25
|
+
async update (model, sourceID, params, existing) {
|
|
26
26
|
if (model === 'GoogleMailLabel') {
|
|
27
27
|
return updateLabel({
|
|
28
28
|
accessToken: config.credential.accessToken,
|
|
@@ -31,6 +31,22 @@ export function createGoogleMailActionHandler(config) {
|
|
|
31
31
|
fetch: config.fetch
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
+
// On GoogleMailMessage, a providerRef selects an attachment to import: its
|
|
35
|
+
// bytes are downloaded and written to the blob store, filling that entry's
|
|
36
|
+
// blob field. Without it, the update modifies labels as before.
|
|
37
|
+
if (typeof params.providerRef === 'string') {
|
|
38
|
+
if (config.writeAttachment == null) {
|
|
39
|
+
throw new Error('Google Mail: cannot import attachment, no blob plugin installed');
|
|
40
|
+
}
|
|
41
|
+
return importMessageAttachment({
|
|
42
|
+
accessToken: config.credential.accessToken,
|
|
43
|
+
messageID: sourceID,
|
|
44
|
+
providerRef: params.providerRef,
|
|
45
|
+
existing,
|
|
46
|
+
writeAttachment: config.writeAttachment,
|
|
47
|
+
fetch: config.fetch
|
|
48
|
+
});
|
|
49
|
+
}
|
|
34
50
|
return modifyLabels({
|
|
35
51
|
accessToken: config.credential.accessToken,
|
|
36
52
|
messageID: sourceID,
|
package/lib/api.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { EntityRecord } from '@kubun/connector';
|
|
1
|
+
import type { EntityRecord, WriteAttachment } from '@kubun/connector';
|
|
2
2
|
export declare const MAIL_BASE_URL = "https://gmail.googleapis.com/gmail/v1";
|
|
3
3
|
export declare function parseGoogleError(response: Response): Promise<string>;
|
|
4
4
|
export declare function decodeBase64Url(data: string): string;
|
|
5
|
+
export declare function decodeBase64UrlBytes(data: string): Uint8Array;
|
|
5
6
|
export declare function encodeBase64Url(text: string): string;
|
|
6
7
|
export declare function buildMessagesListURL(params: {
|
|
7
8
|
query: string;
|
|
@@ -10,6 +11,10 @@ export declare function buildMessagesListURL(params: {
|
|
|
10
11
|
export declare function buildMessageGetURL(params: {
|
|
11
12
|
messageID: string;
|
|
12
13
|
}): string;
|
|
14
|
+
export declare function buildAttachmentGetURL(params: {
|
|
15
|
+
messageID: string;
|
|
16
|
+
attachmentID: string;
|
|
17
|
+
}): string;
|
|
13
18
|
export declare function buildHistoryListURL(params: {
|
|
14
19
|
startHistoryId: string;
|
|
15
20
|
pageToken?: string;
|
|
@@ -87,6 +92,14 @@ export declare function getHeader(headers: Array<{
|
|
|
87
92
|
export declare function extractEmail(headerValue: string): string;
|
|
88
93
|
export declare function extractEmails(headerValue: string | undefined): Array<string>;
|
|
89
94
|
export declare function mapMessageToEntity(message: GmailMessageResource): EntityRecord;
|
|
95
|
+
export declare function importMessageAttachment(config: {
|
|
96
|
+
accessToken: string;
|
|
97
|
+
messageID: string;
|
|
98
|
+
providerRef: string;
|
|
99
|
+
existing: Record<string, unknown> | undefined;
|
|
100
|
+
writeAttachment: WriteAttachment;
|
|
101
|
+
fetch?: typeof globalThis.fetch;
|
|
102
|
+
}): Promise<EntityRecord>;
|
|
90
103
|
export declare function mapThreadToEntity(threadID: string, subject: string | undefined): EntityRecord;
|
|
91
104
|
export declare function buildRawMessage(params: {
|
|
92
105
|
to: string | Array<string>;
|
package/lib/api.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { b64uFromUTF, b64uToUTF, fromB64U } from '@sozai/codec';
|
|
1
2
|
export const MAIL_BASE_URL = 'https://gmail.googleapis.com/gmail/v1';
|
|
2
3
|
export async function parseGoogleError(response) {
|
|
3
4
|
try {
|
|
@@ -8,10 +9,13 @@ export async function parseGoogleError(response) {
|
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
11
|
export function decodeBase64Url(data) {
|
|
11
|
-
return
|
|
12
|
+
return b64uToUTF(data);
|
|
13
|
+
}
|
|
14
|
+
export function decodeBase64UrlBytes(data) {
|
|
15
|
+
return fromB64U(data);
|
|
12
16
|
}
|
|
13
17
|
export function encodeBase64Url(text) {
|
|
14
|
-
return
|
|
18
|
+
return b64uFromUTF(text);
|
|
15
19
|
}
|
|
16
20
|
export function buildMessagesListURL(params) {
|
|
17
21
|
const url = new URL(`${MAIL_BASE_URL}/users/me/messages`);
|
|
@@ -24,6 +28,9 @@ export function buildMessageGetURL(params) {
|
|
|
24
28
|
url.searchParams.set('format', 'full');
|
|
25
29
|
return url.toString();
|
|
26
30
|
}
|
|
31
|
+
export function buildAttachmentGetURL(params) {
|
|
32
|
+
return `${MAIL_BASE_URL}/users/me/messages/${encodeURIComponent(params.messageID)}/attachments/${encodeURIComponent(params.attachmentID)}`;
|
|
33
|
+
}
|
|
27
34
|
export function buildHistoryListURL(params) {
|
|
28
35
|
const url = new URL(`${MAIL_BASE_URL}/users/me/history`);
|
|
29
36
|
url.searchParams.set('startHistoryId', params.startHistoryId);
|
|
@@ -82,9 +89,13 @@ function extractAttachments(payload) {
|
|
|
82
89
|
const out = [];
|
|
83
90
|
function walk(part) {
|
|
84
91
|
if (part.filename != null && part.filename !== '' && part.body?.attachmentId != null) {
|
|
92
|
+
// providerRef is the Gmail part id used later to fetch bytes on demand
|
|
93
|
+
// (users.messages.attachments.get); the blob field stays absent until import.
|
|
85
94
|
out.push({
|
|
86
95
|
filename: part.filename,
|
|
87
|
-
mimeType: part.mimeType ?? 'application/octet-stream'
|
|
96
|
+
mimeType: part.mimeType ?? 'application/octet-stream',
|
|
97
|
+
providerRef: part.body.attachmentId,
|
|
98
|
+
size: part.body.size ?? 0
|
|
88
99
|
});
|
|
89
100
|
}
|
|
90
101
|
for (const child of part.parts ?? [])walk(child);
|
|
@@ -117,6 +128,51 @@ export function mapMessageToEntity(message) {
|
|
|
117
128
|
}
|
|
118
129
|
};
|
|
119
130
|
}
|
|
131
|
+
function streamOfBytes(bytes) {
|
|
132
|
+
return new ReadableStream({
|
|
133
|
+
start (controller) {
|
|
134
|
+
controller.enqueue(bytes);
|
|
135
|
+
controller.close();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// Download one attachment's bytes from Gmail, write them to the blob store, and
|
|
140
|
+
// return the message entity with that attachment's `blob` set. Other attachments'
|
|
141
|
+
// already-imported blobs are preserved from `existing`. Gmail returns the whole
|
|
142
|
+
// attachment as a single base64url `data` field, so the bytes are buffered here.
|
|
143
|
+
export async function importMessageAttachment(config) {
|
|
144
|
+
const attachments = config.existing?.attachments ?? [];
|
|
145
|
+
const target = attachments.find((a)=>a.providerRef === config.providerRef);
|
|
146
|
+
if (target == null) {
|
|
147
|
+
throw new Error(`Google Mail: no attachment with providerRef "${config.providerRef}"`);
|
|
148
|
+
}
|
|
149
|
+
const fetchFn = config.fetch ?? globalThis.fetch;
|
|
150
|
+
const response = await fetchFn(buildAttachmentGetURL({
|
|
151
|
+
messageID: config.messageID,
|
|
152
|
+
attachmentID: config.providerRef
|
|
153
|
+
}), {
|
|
154
|
+
headers: {
|
|
155
|
+
Authorization: `Bearer ${config.accessToken}`
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
if (!response.ok) throw new Error(`Google Mail API error: ${await parseGoogleError(response)}`);
|
|
159
|
+
const body = await response.json();
|
|
160
|
+
if (body.data == null) throw new Error('Google Mail: attachment response has no data');
|
|
161
|
+
const bytes = decodeBase64UrlBytes(body.data);
|
|
162
|
+
const attachmentID = await config.writeAttachment(streamOfBytes(bytes), {
|
|
163
|
+
mimeType: target.mimeType ?? 'application/octet-stream'
|
|
164
|
+
});
|
|
165
|
+
const updated = attachments.map((a)=>a.providerRef === config.providerRef ? {
|
|
166
|
+
...a,
|
|
167
|
+
blob: attachmentID
|
|
168
|
+
} : a);
|
|
169
|
+
return {
|
|
170
|
+
...config.existing ?? {},
|
|
171
|
+
sourceService: 'google-mail',
|
|
172
|
+
sourceID: config.messageID,
|
|
173
|
+
attachments: updated
|
|
174
|
+
};
|
|
175
|
+
}
|
|
120
176
|
export function mapThreadToEntity(threadID, subject) {
|
|
121
177
|
return {
|
|
122
178
|
sourceService: 'google-mail',
|
package/lib/connector.js
CHANGED
|
@@ -66,7 +66,7 @@ export function createGoogleMailConnector(options) {
|
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
name: 'update',
|
|
69
|
-
description: 'On GoogleMailMessage: add/remove labels. On GoogleMailLabel: rename or restyle the label.',
|
|
69
|
+
description: 'On GoogleMailMessage: add/remove labels, or import an attachment when providerRef is set. On GoogleMailLabel: rename or restyle the label.',
|
|
70
70
|
input: {
|
|
71
71
|
type: 'object',
|
|
72
72
|
properties: {
|
|
@@ -83,6 +83,10 @@ export function createGoogleMailConnector(options) {
|
|
|
83
83
|
type: 'string'
|
|
84
84
|
}
|
|
85
85
|
},
|
|
86
|
+
// Import the attachment identified by this Gmail part id into the blob store.
|
|
87
|
+
providerRef: {
|
|
88
|
+
type: 'string'
|
|
89
|
+
},
|
|
86
90
|
// GoogleMailLabel
|
|
87
91
|
name: {
|
|
88
92
|
type: 'string'
|
|
@@ -107,7 +111,8 @@ export function createGoogleMailConnector(options) {
|
|
|
107
111
|
actionHandler: (config)=>{
|
|
108
112
|
return createGoogleMailActionHandler({
|
|
109
113
|
credential: config.credential,
|
|
110
|
-
fetch: options?.fetch
|
|
114
|
+
fetch: options?.fetch,
|
|
115
|
+
writeAttachment: config.writeAttachment
|
|
111
116
|
});
|
|
112
117
|
},
|
|
113
118
|
auth: {
|
package/lib/model.d.ts
CHANGED
|
@@ -37,7 +37,11 @@ export declare const googleMailThreadDefinition: {
|
|
|
37
37
|
readonly minLength: 1;
|
|
38
38
|
readonly maxLength: 500;
|
|
39
39
|
};
|
|
40
|
-
readonly SourceURL:
|
|
40
|
+
readonly SourceURL: {
|
|
41
|
+
readonly type: "string";
|
|
42
|
+
readonly format: "uri";
|
|
43
|
+
readonly title: "URL";
|
|
44
|
+
};
|
|
41
45
|
readonly DateTime: import("json-schema-typed").JSONSchema.String;
|
|
42
46
|
};
|
|
43
47
|
};
|
|
@@ -120,7 +124,11 @@ export declare const googleMailMessageDefinition: {
|
|
|
120
124
|
readonly required: ["body", "sender", "threadID", "thread", "sourceService", "sourceID", "lastSyncedAt"];
|
|
121
125
|
readonly additionalProperties: false;
|
|
122
126
|
readonly definitions: {
|
|
123
|
-
readonly ThreadDocID:
|
|
127
|
+
readonly ThreadDocID: {
|
|
128
|
+
readonly type: "string";
|
|
129
|
+
readonly title: "DocID";
|
|
130
|
+
readonly pattern: "^k[0-9a-z]{10,120}$";
|
|
131
|
+
};
|
|
124
132
|
readonly Attachment: {
|
|
125
133
|
readonly type: "object";
|
|
126
134
|
readonly title: "Attachment";
|
|
@@ -131,8 +139,18 @@ export declare const googleMailMessageDefinition: {
|
|
|
131
139
|
readonly mimeType: {
|
|
132
140
|
readonly type: "string";
|
|
133
141
|
};
|
|
142
|
+
readonly providerRef: {
|
|
143
|
+
readonly type: "string";
|
|
144
|
+
};
|
|
145
|
+
readonly size: {
|
|
146
|
+
readonly type: "number";
|
|
147
|
+
};
|
|
148
|
+
readonly blob: {
|
|
149
|
+
readonly type: "string";
|
|
150
|
+
readonly title: "attachmentid";
|
|
151
|
+
};
|
|
134
152
|
};
|
|
135
|
-
readonly required: ["filename", "mimeType"];
|
|
153
|
+
readonly required: ["filename", "mimeType", "providerRef", "size"];
|
|
136
154
|
readonly additionalProperties: false;
|
|
137
155
|
};
|
|
138
156
|
readonly SourceService: {
|
|
@@ -147,7 +165,11 @@ export declare const googleMailMessageDefinition: {
|
|
|
147
165
|
readonly minLength: 1;
|
|
148
166
|
readonly maxLength: 500;
|
|
149
167
|
};
|
|
150
|
-
readonly SourceURL:
|
|
168
|
+
readonly SourceURL: {
|
|
169
|
+
readonly type: "string";
|
|
170
|
+
readonly format: "uri";
|
|
171
|
+
readonly title: "URL";
|
|
172
|
+
};
|
|
151
173
|
readonly DateTime: import("json-schema-typed").JSONSchema.String;
|
|
152
174
|
};
|
|
153
175
|
};
|
|
@@ -218,7 +240,11 @@ export declare const googleMailLabelDefinition: {
|
|
|
218
240
|
readonly minLength: 1;
|
|
219
241
|
readonly maxLength: 500;
|
|
220
242
|
};
|
|
221
|
-
readonly SourceURL:
|
|
243
|
+
readonly SourceURL: {
|
|
244
|
+
readonly type: "string";
|
|
245
|
+
readonly format: "uri";
|
|
246
|
+
readonly title: "URL";
|
|
247
|
+
};
|
|
222
248
|
readonly DateTime: import("json-schema-typed").JSONSchema.String;
|
|
223
249
|
};
|
|
224
250
|
};
|
package/lib/model.js
CHANGED
|
@@ -156,11 +156,25 @@ export const googleMailMessageDefinition = {
|
|
|
156
156
|
},
|
|
157
157
|
mimeType: {
|
|
158
158
|
type: 'string'
|
|
159
|
+
},
|
|
160
|
+
// Gmail part id; the stable selector an import action uses to fetch bytes.
|
|
161
|
+
providerRef: {
|
|
162
|
+
type: 'string'
|
|
163
|
+
},
|
|
164
|
+
size: {
|
|
165
|
+
type: 'number'
|
|
166
|
+
},
|
|
167
|
+
// Content-addressed blob id, absent until an explicit import fills it.
|
|
168
|
+
blob: {
|
|
169
|
+
type: 'string',
|
|
170
|
+
title: 'attachmentid'
|
|
159
171
|
}
|
|
160
172
|
},
|
|
161
173
|
required: [
|
|
162
174
|
'filename',
|
|
163
|
-
'mimeType'
|
|
175
|
+
'mimeType',
|
|
176
|
+
'providerRef',
|
|
177
|
+
'size'
|
|
164
178
|
],
|
|
165
179
|
additionalProperties: false
|
|
166
180
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/connector-google-mail",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"license": "see LICENSE.md",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
"LICENSE.md"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@
|
|
19
|
-
"@kubun/
|
|
20
|
-
"@kubun/
|
|
18
|
+
"@sozai/codec": "^0.4.0",
|
|
19
|
+
"@kubun/connector": "^0.13.0",
|
|
20
|
+
"@kubun/models": "^0.13.0",
|
|
21
21
|
"@kubun/protocol": "^0.13.0",
|
|
22
|
-
"@kubun/scalars": "^0.
|
|
22
|
+
"@kubun/scalars": "^0.13.0",
|
|
23
|
+
"@kubun/provider-google": "^0.13.0"
|
|
23
24
|
},
|
|
24
25
|
"publishConfig": {
|
|
25
26
|
"access": "public"
|