nylas 7.10.0 → 7.12.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/cjs/apiClient.js +4 -4
- package/lib/cjs/config.js +1 -1
- package/lib/cjs/models/availability.js +1 -1
- package/lib/cjs/models/credentials.js +1 -1
- package/lib/cjs/models/events.js +1 -1
- package/lib/cjs/models/freeBusy.js +1 -1
- package/lib/cjs/models/messages.js +3 -1
- package/lib/cjs/models/webhooks.js +1 -1
- package/lib/cjs/resources/auth.js +2 -2
- package/lib/cjs/resources/drafts.js +8 -12
- package/lib/cjs/resources/messages.js +29 -16
- package/lib/cjs/utils.js +89 -16
- package/lib/cjs/version.js +1 -1
- package/lib/esm/apiClient.js +4 -4
- package/lib/esm/models/messages.js +2 -0
- package/lib/esm/resources/auth.js +1 -1
- package/lib/esm/resources/drafts.js +9 -13
- package/lib/esm/resources/messages.js +30 -17
- package/lib/esm/utils.js +80 -9
- package/lib/esm/version.js +1 -1
- package/lib/types/apiClient.d.ts +1 -3
- package/lib/types/models/attachments.d.ts +0 -2
- package/lib/types/models/folders.d.ts +10 -0
- package/lib/types/models/messages.d.ts +36 -1
- package/lib/types/resources/attachments.d.ts +0 -2
- package/lib/types/resources/messages.d.ts +1 -1
- package/lib/types/resources/resource.d.ts +0 -2
- package/lib/types/utils.d.ts +22 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +5 -4
package/lib/cjs/apiClient.js
CHANGED
|
@@ -156,10 +156,6 @@ class APIClient {
|
|
|
156
156
|
}
|
|
157
157
|
if (optionParams.form) {
|
|
158
158
|
requestOptions.body = optionParams.form;
|
|
159
|
-
requestOptions.headers = {
|
|
160
|
-
...requestOptions.headers,
|
|
161
|
-
...optionParams.form.getHeaders(),
|
|
162
|
-
};
|
|
163
159
|
}
|
|
164
160
|
return requestOptions;
|
|
165
161
|
}
|
|
@@ -198,6 +194,10 @@ class APIClient {
|
|
|
198
194
|
}
|
|
199
195
|
async requestStream(options) {
|
|
200
196
|
const response = await this.sendRequest(options);
|
|
197
|
+
// TODO: See if we can fix this in a backwards compatible way
|
|
198
|
+
if (!response.body) {
|
|
199
|
+
throw new Error('No response body');
|
|
200
|
+
}
|
|
201
201
|
return response.body;
|
|
202
202
|
}
|
|
203
203
|
}
|
package/lib/cjs/config.js
CHANGED
|
@@ -9,4 +9,4 @@ var AvailabilityMethod;
|
|
|
9
9
|
AvailabilityMethod["MaxFairness"] = "max-fairness";
|
|
10
10
|
AvailabilityMethod["MaxAvailability"] = "max-availability";
|
|
11
11
|
AvailabilityMethod["Collective"] = "collective";
|
|
12
|
-
})(AvailabilityMethod
|
|
12
|
+
})(AvailabilityMethod || (exports.AvailabilityMethod = AvailabilityMethod = {}));
|
|
@@ -9,4 +9,4 @@ var CredentialType;
|
|
|
9
9
|
CredentialType["ADMINCONSENT"] = "adminconsent";
|
|
10
10
|
CredentialType["SERVICEACCOUNT"] = "serviceaccount";
|
|
11
11
|
CredentialType["CONNECTOR"] = "connector";
|
|
12
|
-
})(CredentialType
|
|
12
|
+
})(CredentialType || (exports.CredentialType = CredentialType = {}));
|
package/lib/cjs/models/events.js
CHANGED
|
@@ -8,4 +8,6 @@ var MessageFields;
|
|
|
8
8
|
(function (MessageFields) {
|
|
9
9
|
MessageFields["STANDARD"] = "standard";
|
|
10
10
|
MessageFields["INCLUDE_HEADERS"] = "include_headers";
|
|
11
|
-
|
|
11
|
+
MessageFields["INCLUDE_TRACKING_OPTIONS"] = "include_tracking_options";
|
|
12
|
+
MessageFields["RAW_MIME"] = "raw_mime";
|
|
13
|
+
})(MessageFields || (exports.MessageFields = MessageFields = {}));
|
|
@@ -45,4 +45,4 @@ var WebhookTriggers;
|
|
|
45
45
|
WebhookTriggers["BookingRescheduled"] = "booking.rescheduled";
|
|
46
46
|
WebhookTriggers["BookingCancelled"] = "booking.cancelled";
|
|
47
47
|
WebhookTriggers["BookingReminder"] = "booking.reminder";
|
|
48
|
-
})(WebhookTriggers
|
|
48
|
+
})(WebhookTriggers || (exports.WebhookTriggers = WebhookTriggers = {}));
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Auth = void 0;
|
|
4
4
|
const uuid_1 = require("uuid");
|
|
5
|
-
const
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
6
|
const resource_js_1 = require("./resource.js");
|
|
7
7
|
const utils_js_1 = require("../utils.js");
|
|
8
8
|
/**
|
|
@@ -166,7 +166,7 @@ class Auth extends resource_js_1.Resource {
|
|
|
166
166
|
return url;
|
|
167
167
|
}
|
|
168
168
|
hashPKCESecret(secret) {
|
|
169
|
-
const hash = (0,
|
|
169
|
+
const hash = (0, node_crypto_1.createHash)('sha256').update(secret).digest('hex');
|
|
170
170
|
return Buffer.from(hash).toString('base64').replace(/=+$/, '');
|
|
171
171
|
}
|
|
172
172
|
getTokenInfo(params) {
|
|
@@ -43,11 +43,9 @@ class Drafts extends resource_js_1.Resource {
|
|
|
43
43
|
const path = (0, utils_js_2.makePathParams)('/v3/grants/{identifier}/drafts', {
|
|
44
44
|
identifier,
|
|
45
45
|
});
|
|
46
|
-
// Use form data
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
}, 0) || 0;
|
|
50
|
-
if (attachmentSize >= messages_js_1.Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
|
|
46
|
+
// Use form data if the total payload size (body + attachments) is greater than 3mb
|
|
47
|
+
const totalPayloadSize = (0, utils_js_1.calculateTotalPayloadSize)(requestBody);
|
|
48
|
+
if (totalPayloadSize >= messages_js_1.Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
|
|
51
49
|
const form = messages_js_1.Messages._buildFormRequest(requestBody);
|
|
52
50
|
return this.apiClient.request({
|
|
53
51
|
method: 'POST',
|
|
@@ -57,7 +55,7 @@ class Drafts extends resource_js_1.Resource {
|
|
|
57
55
|
});
|
|
58
56
|
}
|
|
59
57
|
else if (requestBody.attachments) {
|
|
60
|
-
const processedAttachments = await (0, utils_js_1.
|
|
58
|
+
const processedAttachments = await (0, utils_js_1.encodeAttachmentContent)(requestBody.attachments);
|
|
61
59
|
requestBody = {
|
|
62
60
|
...requestBody,
|
|
63
61
|
attachments: processedAttachments,
|
|
@@ -78,11 +76,9 @@ class Drafts extends resource_js_1.Resource {
|
|
|
78
76
|
identifier,
|
|
79
77
|
draftId,
|
|
80
78
|
});
|
|
81
|
-
// Use form data
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
}, 0) || 0;
|
|
85
|
-
if (attachmentSize >= messages_js_1.Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
|
|
79
|
+
// Use form data if the total payload size (body + attachments) is greater than 3mb
|
|
80
|
+
const totalPayloadSize = (0, utils_js_1.calculateTotalPayloadSize)(requestBody);
|
|
81
|
+
if (totalPayloadSize >= messages_js_1.Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
|
|
86
82
|
const form = messages_js_1.Messages._buildFormRequest(requestBody);
|
|
87
83
|
return this.apiClient.request({
|
|
88
84
|
method: 'PUT',
|
|
@@ -92,7 +88,7 @@ class Drafts extends resource_js_1.Resource {
|
|
|
92
88
|
});
|
|
93
89
|
}
|
|
94
90
|
else if (requestBody.attachments) {
|
|
95
|
-
const processedAttachments = await (0, utils_js_1.
|
|
91
|
+
const processedAttachments = await (0, utils_js_1.encodeAttachmentContent)(requestBody.attachments);
|
|
96
92
|
requestBody = {
|
|
97
93
|
...requestBody,
|
|
98
94
|
attachments: processedAttachments,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Messages = void 0;
|
|
4
|
+
const formdata_node_1 = require("formdata-node");
|
|
4
5
|
const utils_js_1 = require("../utils.js");
|
|
5
6
|
const resource_js_1 = require("./resource.js");
|
|
6
7
|
const smartCompose_js_1 = require("./smartCompose.js");
|
|
@@ -89,16 +90,14 @@ class Messages extends resource_js_1.Resource {
|
|
|
89
90
|
path,
|
|
90
91
|
overrides,
|
|
91
92
|
};
|
|
92
|
-
// Use form data
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
}, 0) || 0;
|
|
96
|
-
if (attachmentSize >= Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
|
|
93
|
+
// Use form data if the total payload size (body + attachments) is greater than 3mb
|
|
94
|
+
const totalPayloadSize = (0, utils_js_1.calculateTotalPayloadSize)(requestBody);
|
|
95
|
+
if (totalPayloadSize >= Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
|
|
97
96
|
requestOptions.form = Messages._buildFormRequest(requestBody);
|
|
98
97
|
}
|
|
99
98
|
else {
|
|
100
99
|
if (requestBody.attachments) {
|
|
101
|
-
const processedAttachments = await (0, utils_js_1.
|
|
100
|
+
const processedAttachments = await (0, utils_js_1.encodeAttachmentContent)(requestBody.attachments);
|
|
102
101
|
requestOptions.body = {
|
|
103
102
|
...requestBody,
|
|
104
103
|
attachments: processedAttachments,
|
|
@@ -157,10 +156,7 @@ class Messages extends resource_js_1.Resource {
|
|
|
157
156
|
});
|
|
158
157
|
}
|
|
159
158
|
static _buildFormRequest(requestBody) {
|
|
160
|
-
|
|
161
|
-
const FD = require('form-data');
|
|
162
|
-
const FormDataConstructor = FD.default || FD;
|
|
163
|
-
const form = new FormDataConstructor();
|
|
159
|
+
const form = new formdata_node_1.FormData();
|
|
164
160
|
// Split out the message payload from the attachments
|
|
165
161
|
const messagePayload = {
|
|
166
162
|
...requestBody,
|
|
@@ -168,13 +164,30 @@ class Messages extends resource_js_1.Resource {
|
|
|
168
164
|
};
|
|
169
165
|
form.append('message', JSON.stringify((0, utils_js_1.objKeysToSnakeCase)(messagePayload)));
|
|
170
166
|
// Add a separate form field for each attachment
|
|
171
|
-
requestBody.attachments
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
167
|
+
if (requestBody.attachments && requestBody.attachments.length > 0) {
|
|
168
|
+
requestBody.attachments.map((attachment, index) => {
|
|
169
|
+
const contentId = attachment.contentId || `file${index}`;
|
|
170
|
+
// Handle different content types for formdata-node
|
|
171
|
+
if (typeof attachment.content === 'string') {
|
|
172
|
+
// Base64 string - create a Blob
|
|
173
|
+
const buffer = Buffer.from(attachment.content, 'base64');
|
|
174
|
+
const blob = new formdata_node_1.Blob([buffer], { type: attachment.contentType });
|
|
175
|
+
form.append(contentId, blob, attachment.filename);
|
|
176
|
+
}
|
|
177
|
+
else if (Buffer.isBuffer(attachment.content)) {
|
|
178
|
+
// Buffer - create a Blob
|
|
179
|
+
const blob = new formdata_node_1.Blob([attachment.content], {
|
|
180
|
+
type: attachment.contentType,
|
|
181
|
+
});
|
|
182
|
+
form.append(contentId, blob, attachment.filename);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
// ReadableStream - create a proper file-like object according to formdata-node docs
|
|
186
|
+
const file = (0, utils_js_1.attachmentStreamToFile)(attachment);
|
|
187
|
+
form.append(contentId, file, attachment.filename);
|
|
188
|
+
}
|
|
176
189
|
});
|
|
177
|
-
}
|
|
190
|
+
}
|
|
178
191
|
return form;
|
|
179
192
|
}
|
|
180
193
|
}
|
package/lib/cjs/utils.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.createFileRequestBuilder = createFileRequestBuilder;
|
|
4
|
+
exports.attachmentStreamToFile = attachmentStreamToFile;
|
|
5
|
+
exports.encodeAttachmentContent = encodeAttachmentContent;
|
|
6
|
+
exports.encodeAttachmentStreams = encodeAttachmentStreams;
|
|
7
|
+
exports.objKeysToCamelCase = objKeysToCamelCase;
|
|
8
|
+
exports.objKeysToSnakeCase = objKeysToSnakeCase;
|
|
9
|
+
exports.safePath = safePath;
|
|
10
|
+
exports.makePathParams = makePathParams;
|
|
11
|
+
exports.calculateTotalPayloadSize = calculateTotalPayloadSize;
|
|
4
12
|
const change_case_1 = require("change-case");
|
|
5
|
-
const fs = require("fs");
|
|
6
|
-
const path = require("path");
|
|
13
|
+
const fs = require("node:fs");
|
|
14
|
+
const path = require("node:path");
|
|
7
15
|
const mime = require("mime-types");
|
|
8
|
-
const
|
|
16
|
+
const node_stream_1 = require("node:stream");
|
|
9
17
|
function createFileRequestBuilder(filePath) {
|
|
10
18
|
const stats = fs.statSync(filePath);
|
|
11
19
|
const filename = path.basename(filePath);
|
|
@@ -18,7 +26,6 @@ function createFileRequestBuilder(filePath) {
|
|
|
18
26
|
size: stats.size,
|
|
19
27
|
};
|
|
20
28
|
}
|
|
21
|
-
exports.createFileRequestBuilder = createFileRequestBuilder;
|
|
22
29
|
/**
|
|
23
30
|
* Converts a ReadableStream to a base64 encoded string.
|
|
24
31
|
* @param stream The ReadableStream containing the binary data.
|
|
@@ -40,19 +47,67 @@ function streamToBase64(stream) {
|
|
|
40
47
|
});
|
|
41
48
|
}
|
|
42
49
|
/**
|
|
43
|
-
*
|
|
50
|
+
* Converts a ReadableStream to a File-like object that can be used with FormData.
|
|
51
|
+
* @param attachment The attachment containing the stream and metadata.
|
|
52
|
+
* @param mimeType The MIME type for the file (optional).
|
|
53
|
+
* @returns A File-like object that properly handles the stream.
|
|
54
|
+
*/
|
|
55
|
+
function attachmentStreamToFile(attachment, mimeType) {
|
|
56
|
+
if (mimeType != null && typeof mimeType !== 'string') {
|
|
57
|
+
throw new Error('Invalid mimetype, expected string.');
|
|
58
|
+
}
|
|
59
|
+
const content = attachment.content;
|
|
60
|
+
if (typeof content === 'string' || Buffer.isBuffer(content)) {
|
|
61
|
+
throw new Error('Invalid attachment content, expected ReadableStream.');
|
|
62
|
+
}
|
|
63
|
+
// Create a file-shaped object that FormData can handle properly
|
|
64
|
+
const fileObject = {
|
|
65
|
+
type: mimeType || attachment.contentType,
|
|
66
|
+
name: attachment.filename,
|
|
67
|
+
[Symbol.toStringTag]: 'File',
|
|
68
|
+
stream() {
|
|
69
|
+
return content;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
// Add size if available
|
|
73
|
+
if (attachment.size !== undefined) {
|
|
74
|
+
fileObject.size = attachment.size;
|
|
75
|
+
}
|
|
76
|
+
return fileObject;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Encodes the content of each attachment to base64.
|
|
80
|
+
* Handles ReadableStream, Buffer, and string content types.
|
|
44
81
|
* @param attachments The attachments to encode.
|
|
45
82
|
* @returns The attachments with their content encoded to base64.
|
|
46
83
|
*/
|
|
47
|
-
async function
|
|
84
|
+
async function encodeAttachmentContent(attachments) {
|
|
48
85
|
return await Promise.all(attachments.map(async (attachment) => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
86
|
+
let base64EncodedContent;
|
|
87
|
+
if (attachment.content instanceof node_stream_1.Readable) {
|
|
88
|
+
// ReadableStream -> base64
|
|
89
|
+
base64EncodedContent = await streamToBase64(attachment.content);
|
|
90
|
+
}
|
|
91
|
+
else if (Buffer.isBuffer(attachment.content)) {
|
|
92
|
+
// Buffer -> base64
|
|
93
|
+
base64EncodedContent = attachment.content.toString('base64');
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// string (assumed to already be base64)
|
|
97
|
+
base64EncodedContent = attachment.content;
|
|
98
|
+
}
|
|
99
|
+
return { ...attachment, content: base64EncodedContent };
|
|
53
100
|
}));
|
|
54
101
|
}
|
|
55
|
-
|
|
102
|
+
/**
|
|
103
|
+
* @deprecated Use encodeAttachmentContent instead. This alias is provided for backwards compatibility.
|
|
104
|
+
* Encodes the content of each attachment stream to base64.
|
|
105
|
+
* @param attachments The attachments to encode.
|
|
106
|
+
* @returns The attachments with their content encoded to base64.
|
|
107
|
+
*/
|
|
108
|
+
async function encodeAttachmentStreams(attachments) {
|
|
109
|
+
return encodeAttachmentContent(attachments);
|
|
110
|
+
}
|
|
56
111
|
/**
|
|
57
112
|
* Applies the casing function and ensures numeric parts are preceded by underscores in snake_case.
|
|
58
113
|
* @param casingFunction The original casing function.
|
|
@@ -111,7 +166,6 @@ function convertCase(obj, casingFunction, excludeKeys) {
|
|
|
111
166
|
function objKeysToCamelCase(obj, exclude) {
|
|
112
167
|
return convertCase(obj, change_case_1.camelCase, exclude);
|
|
113
168
|
}
|
|
114
|
-
exports.objKeysToCamelCase = objKeysToCamelCase;
|
|
115
169
|
/**
|
|
116
170
|
* A utility function that recursively converts all keys in an object to snake_case.
|
|
117
171
|
* @param obj The object to convert
|
|
@@ -121,7 +175,6 @@ exports.objKeysToCamelCase = objKeysToCamelCase;
|
|
|
121
175
|
function objKeysToSnakeCase(obj, exclude) {
|
|
122
176
|
return convertCase(obj, change_case_1.snakeCase, exclude);
|
|
123
177
|
}
|
|
124
|
-
exports.objKeysToSnakeCase = objKeysToSnakeCase;
|
|
125
178
|
/**
|
|
126
179
|
* Safely encodes a path template with replacements.
|
|
127
180
|
* @param pathTemplate The path template to encode.
|
|
@@ -145,9 +198,29 @@ function safePath(pathTemplate, replacements) {
|
|
|
145
198
|
}
|
|
146
199
|
});
|
|
147
200
|
}
|
|
148
|
-
exports.safePath = safePath;
|
|
149
201
|
// Helper to create PathParams with type safety and runtime interpolation
|
|
150
202
|
function makePathParams(path, params) {
|
|
151
203
|
return safePath(path, params);
|
|
152
204
|
}
|
|
153
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Calculates the total payload size for a message request, including body and attachments.
|
|
207
|
+
* This is used to determine if multipart/form-data should be used instead of JSON.
|
|
208
|
+
* @param requestBody The message request body
|
|
209
|
+
* @returns The total estimated payload size in bytes
|
|
210
|
+
*/
|
|
211
|
+
function calculateTotalPayloadSize(requestBody) {
|
|
212
|
+
let totalSize = 0;
|
|
213
|
+
// Calculate size of the message body (JSON payload without attachments)
|
|
214
|
+
const messagePayloadWithoutAttachments = {
|
|
215
|
+
...requestBody,
|
|
216
|
+
attachments: undefined,
|
|
217
|
+
};
|
|
218
|
+
const messagePayloadString = JSON.stringify(objKeysToSnakeCase(messagePayloadWithoutAttachments));
|
|
219
|
+
totalSize += Buffer.byteLength(messagePayloadString, 'utf8');
|
|
220
|
+
// Add attachment sizes
|
|
221
|
+
const attachmentSize = requestBody.attachments?.reduce((total, attachment) => {
|
|
222
|
+
return total + (attachment.size || 0);
|
|
223
|
+
}, 0) || 0;
|
|
224
|
+
totalSize += attachmentSize;
|
|
225
|
+
return totalSize;
|
|
226
|
+
}
|
package/lib/cjs/version.js
CHANGED
package/lib/esm/apiClient.js
CHANGED
|
@@ -153,10 +153,6 @@ export default class APIClient {
|
|
|
153
153
|
}
|
|
154
154
|
if (optionParams.form) {
|
|
155
155
|
requestOptions.body = optionParams.form;
|
|
156
|
-
requestOptions.headers = {
|
|
157
|
-
...requestOptions.headers,
|
|
158
|
-
...optionParams.form.getHeaders(),
|
|
159
|
-
};
|
|
160
156
|
}
|
|
161
157
|
return requestOptions;
|
|
162
158
|
}
|
|
@@ -195,6 +191,10 @@ export default class APIClient {
|
|
|
195
191
|
}
|
|
196
192
|
async requestStream(options) {
|
|
197
193
|
const response = await this.sendRequest(options);
|
|
194
|
+
// TODO: See if we can fix this in a backwards compatible way
|
|
195
|
+
if (!response.body) {
|
|
196
|
+
throw new Error('No response body');
|
|
197
|
+
}
|
|
198
198
|
return response.body;
|
|
199
199
|
}
|
|
200
200
|
}
|
|
@@ -5,4 +5,6 @@ export var MessageFields;
|
|
|
5
5
|
(function (MessageFields) {
|
|
6
6
|
MessageFields["STANDARD"] = "standard";
|
|
7
7
|
MessageFields["INCLUDE_HEADERS"] = "include_headers";
|
|
8
|
+
MessageFields["INCLUDE_TRACKING_OPTIONS"] = "include_tracking_options";
|
|
9
|
+
MessageFields["RAW_MIME"] = "raw_mime";
|
|
8
10
|
})(MessageFields || (MessageFields = {}));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Messages } from './messages.js';
|
|
2
2
|
import { Resource } from './resource.js';
|
|
3
|
-
import {
|
|
3
|
+
import { encodeAttachmentContent, calculateTotalPayloadSize, } from '../utils.js';
|
|
4
4
|
import { makePathParams } from '../utils.js';
|
|
5
5
|
/**
|
|
6
6
|
* Nylas Drafts API
|
|
@@ -40,11 +40,9 @@ export class Drafts extends Resource {
|
|
|
40
40
|
const path = makePathParams('/v3/grants/{identifier}/drafts', {
|
|
41
41
|
identifier,
|
|
42
42
|
});
|
|
43
|
-
// Use form data
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
}, 0) || 0;
|
|
47
|
-
if (attachmentSize >= Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
|
|
43
|
+
// Use form data if the total payload size (body + attachments) is greater than 3mb
|
|
44
|
+
const totalPayloadSize = calculateTotalPayloadSize(requestBody);
|
|
45
|
+
if (totalPayloadSize >= Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
|
|
48
46
|
const form = Messages._buildFormRequest(requestBody);
|
|
49
47
|
return this.apiClient.request({
|
|
50
48
|
method: 'POST',
|
|
@@ -54,7 +52,7 @@ export class Drafts extends Resource {
|
|
|
54
52
|
});
|
|
55
53
|
}
|
|
56
54
|
else if (requestBody.attachments) {
|
|
57
|
-
const processedAttachments = await
|
|
55
|
+
const processedAttachments = await encodeAttachmentContent(requestBody.attachments);
|
|
58
56
|
requestBody = {
|
|
59
57
|
...requestBody,
|
|
60
58
|
attachments: processedAttachments,
|
|
@@ -75,11 +73,9 @@ export class Drafts extends Resource {
|
|
|
75
73
|
identifier,
|
|
76
74
|
draftId,
|
|
77
75
|
});
|
|
78
|
-
// Use form data
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
}, 0) || 0;
|
|
82
|
-
if (attachmentSize >= Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
|
|
76
|
+
// Use form data if the total payload size (body + attachments) is greater than 3mb
|
|
77
|
+
const totalPayloadSize = calculateTotalPayloadSize(requestBody);
|
|
78
|
+
if (totalPayloadSize >= Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
|
|
83
79
|
const form = Messages._buildFormRequest(requestBody);
|
|
84
80
|
return this.apiClient.request({
|
|
85
81
|
method: 'PUT',
|
|
@@ -89,7 +85,7 @@ export class Drafts extends Resource {
|
|
|
89
85
|
});
|
|
90
86
|
}
|
|
91
87
|
else if (requestBody.attachments) {
|
|
92
|
-
const processedAttachments = await
|
|
88
|
+
const processedAttachments = await encodeAttachmentContent(requestBody.attachments);
|
|
93
89
|
requestBody = {
|
|
94
90
|
...requestBody,
|
|
95
91
|
attachments: processedAttachments,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Blob, FormData } from 'formdata-node';
|
|
2
|
+
import { attachmentStreamToFile, calculateTotalPayloadSize, encodeAttachmentContent, makePathParams, objKeysToSnakeCase, } from '../utils.js';
|
|
2
3
|
import { Resource } from './resource.js';
|
|
3
4
|
import { SmartCompose } from './smartCompose.js';
|
|
4
5
|
/**
|
|
@@ -86,16 +87,14 @@ export class Messages extends Resource {
|
|
|
86
87
|
path,
|
|
87
88
|
overrides,
|
|
88
89
|
};
|
|
89
|
-
// Use form data
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
}, 0) || 0;
|
|
93
|
-
if (attachmentSize >= Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
|
|
90
|
+
// Use form data if the total payload size (body + attachments) is greater than 3mb
|
|
91
|
+
const totalPayloadSize = calculateTotalPayloadSize(requestBody);
|
|
92
|
+
if (totalPayloadSize >= Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
|
|
94
93
|
requestOptions.form = Messages._buildFormRequest(requestBody);
|
|
95
94
|
}
|
|
96
95
|
else {
|
|
97
96
|
if (requestBody.attachments) {
|
|
98
|
-
const processedAttachments = await
|
|
97
|
+
const processedAttachments = await encodeAttachmentContent(requestBody.attachments);
|
|
99
98
|
requestOptions.body = {
|
|
100
99
|
...requestBody,
|
|
101
100
|
attachments: processedAttachments,
|
|
@@ -154,10 +153,7 @@ export class Messages extends Resource {
|
|
|
154
153
|
});
|
|
155
154
|
}
|
|
156
155
|
static _buildFormRequest(requestBody) {
|
|
157
|
-
|
|
158
|
-
const FD = require('form-data');
|
|
159
|
-
const FormDataConstructor = FD.default || FD;
|
|
160
|
-
const form = new FormDataConstructor();
|
|
156
|
+
const form = new FormData();
|
|
161
157
|
// Split out the message payload from the attachments
|
|
162
158
|
const messagePayload = {
|
|
163
159
|
...requestBody,
|
|
@@ -165,13 +161,30 @@ export class Messages extends Resource {
|
|
|
165
161
|
};
|
|
166
162
|
form.append('message', JSON.stringify(objKeysToSnakeCase(messagePayload)));
|
|
167
163
|
// Add a separate form field for each attachment
|
|
168
|
-
requestBody.attachments
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
164
|
+
if (requestBody.attachments && requestBody.attachments.length > 0) {
|
|
165
|
+
requestBody.attachments.map((attachment, index) => {
|
|
166
|
+
const contentId = attachment.contentId || `file${index}`;
|
|
167
|
+
// Handle different content types for formdata-node
|
|
168
|
+
if (typeof attachment.content === 'string') {
|
|
169
|
+
// Base64 string - create a Blob
|
|
170
|
+
const buffer = Buffer.from(attachment.content, 'base64');
|
|
171
|
+
const blob = new Blob([buffer], { type: attachment.contentType });
|
|
172
|
+
form.append(contentId, blob, attachment.filename);
|
|
173
|
+
}
|
|
174
|
+
else if (Buffer.isBuffer(attachment.content)) {
|
|
175
|
+
// Buffer - create a Blob
|
|
176
|
+
const blob = new Blob([attachment.content], {
|
|
177
|
+
type: attachment.contentType,
|
|
178
|
+
});
|
|
179
|
+
form.append(contentId, blob, attachment.filename);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// ReadableStream - create a proper file-like object according to formdata-node docs
|
|
183
|
+
const file = attachmentStreamToFile(attachment);
|
|
184
|
+
form.append(contentId, file, attachment.filename);
|
|
185
|
+
}
|
|
173
186
|
});
|
|
174
|
-
}
|
|
187
|
+
}
|
|
175
188
|
return form;
|
|
176
189
|
}
|
|
177
190
|
}
|
package/lib/esm/utils.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { camelCase, snakeCase } from 'change-case';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
4
|
import * as mime from 'mime-types';
|
|
5
|
-
import { Readable } from 'stream';
|
|
5
|
+
import { Readable } from 'node:stream';
|
|
6
6
|
export function createFileRequestBuilder(filePath) {
|
|
7
7
|
const stats = fs.statSync(filePath);
|
|
8
8
|
const filename = path.basename(filePath);
|
|
@@ -36,18 +36,67 @@ function streamToBase64(stream) {
|
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
39
|
+
* Converts a ReadableStream to a File-like object that can be used with FormData.
|
|
40
|
+
* @param attachment The attachment containing the stream and metadata.
|
|
41
|
+
* @param mimeType The MIME type for the file (optional).
|
|
42
|
+
* @returns A File-like object that properly handles the stream.
|
|
43
|
+
*/
|
|
44
|
+
export function attachmentStreamToFile(attachment, mimeType) {
|
|
45
|
+
if (mimeType != null && typeof mimeType !== 'string') {
|
|
46
|
+
throw new Error('Invalid mimetype, expected string.');
|
|
47
|
+
}
|
|
48
|
+
const content = attachment.content;
|
|
49
|
+
if (typeof content === 'string' || Buffer.isBuffer(content)) {
|
|
50
|
+
throw new Error('Invalid attachment content, expected ReadableStream.');
|
|
51
|
+
}
|
|
52
|
+
// Create a file-shaped object that FormData can handle properly
|
|
53
|
+
const fileObject = {
|
|
54
|
+
type: mimeType || attachment.contentType,
|
|
55
|
+
name: attachment.filename,
|
|
56
|
+
[Symbol.toStringTag]: 'File',
|
|
57
|
+
stream() {
|
|
58
|
+
return content;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
// Add size if available
|
|
62
|
+
if (attachment.size !== undefined) {
|
|
63
|
+
fileObject.size = attachment.size;
|
|
64
|
+
}
|
|
65
|
+
return fileObject;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Encodes the content of each attachment to base64.
|
|
69
|
+
* Handles ReadableStream, Buffer, and string content types.
|
|
40
70
|
* @param attachments The attachments to encode.
|
|
41
71
|
* @returns The attachments with their content encoded to base64.
|
|
42
72
|
*/
|
|
43
|
-
export async function
|
|
73
|
+
export async function encodeAttachmentContent(attachments) {
|
|
44
74
|
return await Promise.all(attachments.map(async (attachment) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
75
|
+
let base64EncodedContent;
|
|
76
|
+
if (attachment.content instanceof Readable) {
|
|
77
|
+
// ReadableStream -> base64
|
|
78
|
+
base64EncodedContent = await streamToBase64(attachment.content);
|
|
79
|
+
}
|
|
80
|
+
else if (Buffer.isBuffer(attachment.content)) {
|
|
81
|
+
// Buffer -> base64
|
|
82
|
+
base64EncodedContent = attachment.content.toString('base64');
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// string (assumed to already be base64)
|
|
86
|
+
base64EncodedContent = attachment.content;
|
|
87
|
+
}
|
|
88
|
+
return { ...attachment, content: base64EncodedContent };
|
|
49
89
|
}));
|
|
50
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* @deprecated Use encodeAttachmentContent instead. This alias is provided for backwards compatibility.
|
|
93
|
+
* Encodes the content of each attachment stream to base64.
|
|
94
|
+
* @param attachments The attachments to encode.
|
|
95
|
+
* @returns The attachments with their content encoded to base64.
|
|
96
|
+
*/
|
|
97
|
+
export async function encodeAttachmentStreams(attachments) {
|
|
98
|
+
return encodeAttachmentContent(attachments);
|
|
99
|
+
}
|
|
51
100
|
/**
|
|
52
101
|
* Applies the casing function and ensures numeric parts are preceded by underscores in snake_case.
|
|
53
102
|
* @param casingFunction The original casing function.
|
|
@@ -142,3 +191,25 @@ export function safePath(pathTemplate, replacements) {
|
|
|
142
191
|
export function makePathParams(path, params) {
|
|
143
192
|
return safePath(path, params);
|
|
144
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Calculates the total payload size for a message request, including body and attachments.
|
|
196
|
+
* This is used to determine if multipart/form-data should be used instead of JSON.
|
|
197
|
+
* @param requestBody The message request body
|
|
198
|
+
* @returns The total estimated payload size in bytes
|
|
199
|
+
*/
|
|
200
|
+
export function calculateTotalPayloadSize(requestBody) {
|
|
201
|
+
let totalSize = 0;
|
|
202
|
+
// Calculate size of the message body (JSON payload without attachments)
|
|
203
|
+
const messagePayloadWithoutAttachments = {
|
|
204
|
+
...requestBody,
|
|
205
|
+
attachments: undefined,
|
|
206
|
+
};
|
|
207
|
+
const messagePayloadString = JSON.stringify(objKeysToSnakeCase(messagePayloadWithoutAttachments));
|
|
208
|
+
totalSize += Buffer.byteLength(messagePayloadString, 'utf8');
|
|
209
|
+
// Add attachment sizes
|
|
210
|
+
const attachmentSize = requestBody.attachments?.reduce((total, attachment) => {
|
|
211
|
+
return total + (attachment.size || 0);
|
|
212
|
+
}, 0) || 0;
|
|
213
|
+
totalSize += attachmentSize;
|
|
214
|
+
return totalSize;
|
|
215
|
+
}
|
package/lib/esm/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// This file is generated by scripts/exportVersion.js
|
|
2
|
-
export const SDK_VERSION = '7.
|
|
2
|
+
export const SDK_VERSION = '7.12.0';
|
package/lib/types/apiClient.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
1
|
import { Request, Response } from 'node-fetch';
|
|
4
2
|
import { NylasConfig, OverridableNylasConfig } from './config.js';
|
|
5
|
-
import FormData from '
|
|
3
|
+
import { FormData } from 'formdata-node';
|
|
6
4
|
/**
|
|
7
5
|
* The header key for the debugging flow ID
|
|
8
6
|
*/
|
|
@@ -84,5 +84,15 @@ export interface ListFolderQueryParams extends ListQueryParams {
|
|
|
84
84
|
* (Microsoft and EWS only.) Use the ID of a folder to find all child folders it contains.
|
|
85
85
|
*/
|
|
86
86
|
parentId?: string;
|
|
87
|
+
/**
|
|
88
|
+
* (Microsoft only) When true, Nylas includes hidden folders in its response.
|
|
89
|
+
*/
|
|
90
|
+
includeHiddenFolders?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* (Microsoft only) If true, retrieves folders from a single-level hierarchy only.
|
|
93
|
+
* If false, retrieves folders across a multi-level hierarchy.
|
|
94
|
+
* @default false
|
|
95
|
+
*/
|
|
96
|
+
singleLevel?: boolean;
|
|
87
97
|
}
|
|
88
98
|
export type UpdateFolderRequest = Partial<CreateFolderRequest>;
|
|
@@ -74,6 +74,28 @@ export interface BaseMessage {
|
|
|
74
74
|
*/
|
|
75
75
|
unread?: boolean;
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Interface representing message tracking options.
|
|
79
|
+
*/
|
|
80
|
+
export interface MessageTrackingOptions {
|
|
81
|
+
/**
|
|
82
|
+
* When true, shows that message open tracking is enabled.
|
|
83
|
+
*/
|
|
84
|
+
opens: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* When true, shows that thread replied tracking is enabled.
|
|
87
|
+
*/
|
|
88
|
+
threadReplies: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* When true, shows that link clicked tracking is enabled.
|
|
91
|
+
*/
|
|
92
|
+
links: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* A label describing the message tracking purpose.
|
|
95
|
+
* Maximum length: 2048 characters.
|
|
96
|
+
*/
|
|
97
|
+
label: string;
|
|
98
|
+
}
|
|
77
99
|
/**
|
|
78
100
|
* Interface representing a Nylas Message object.
|
|
79
101
|
*/
|
|
@@ -87,6 +109,17 @@ export interface Message extends BaseMessage {
|
|
|
87
109
|
* Only present if the 'fields' query parameter is set to includeHeaders.
|
|
88
110
|
*/
|
|
89
111
|
headers?: MessageHeaders[];
|
|
112
|
+
/**
|
|
113
|
+
* The message tracking options.
|
|
114
|
+
* Only present if the 'fields' query parameter is set to include_tracking_options.
|
|
115
|
+
*/
|
|
116
|
+
trackingOptions?: MessageTrackingOptions;
|
|
117
|
+
/**
|
|
118
|
+
* A Base64url-encoded string containing the message data (including the body content).
|
|
119
|
+
* Only present if the 'fields' query parameter is set to raw_mime.
|
|
120
|
+
* When this field is requested, only grant_id, object, id, and raw_mime fields are returned.
|
|
121
|
+
*/
|
|
122
|
+
rawMime?: string;
|
|
90
123
|
/**
|
|
91
124
|
* A list of key-value pairs storing additional data.
|
|
92
125
|
*/
|
|
@@ -144,7 +177,9 @@ export interface MessageHeaders {
|
|
|
144
177
|
*/
|
|
145
178
|
export declare enum MessageFields {
|
|
146
179
|
STANDARD = "standard",
|
|
147
|
-
INCLUDE_HEADERS = "include_headers"
|
|
180
|
+
INCLUDE_HEADERS = "include_headers",
|
|
181
|
+
INCLUDE_TRACKING_OPTIONS = "include_tracking_options",
|
|
182
|
+
RAW_MIME = "raw_mime"
|
|
148
183
|
}
|
|
149
184
|
/**
|
|
150
185
|
* Interface representing information about a scheduled message.
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
1
|
import { Overrides } from '../config.js';
|
|
4
2
|
import { Attachment, FindAttachmentQueryParams, DownloadAttachmentQueryParams } from '../models/attachments.js';
|
|
5
3
|
import { NylasResponse } from '../models/response.js';
|
package/lib/types/utils.d.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { CreateAttachmentRequest } from './models/attachments.js';
|
|
2
2
|
export declare function createFileRequestBuilder(filePath: string): CreateAttachmentRequest;
|
|
3
3
|
/**
|
|
4
|
+
* Converts a ReadableStream to a File-like object that can be used with FormData.
|
|
5
|
+
* @param attachment The attachment containing the stream and metadata.
|
|
6
|
+
* @param mimeType The MIME type for the file (optional).
|
|
7
|
+
* @returns A File-like object that properly handles the stream.
|
|
8
|
+
*/
|
|
9
|
+
export declare function attachmentStreamToFile(attachment: CreateAttachmentRequest, mimeType?: string): any;
|
|
10
|
+
/**
|
|
11
|
+
* Encodes the content of each attachment to base64.
|
|
12
|
+
* Handles ReadableStream, Buffer, and string content types.
|
|
13
|
+
* @param attachments The attachments to encode.
|
|
14
|
+
* @returns The attachments with their content encoded to base64.
|
|
15
|
+
*/
|
|
16
|
+
export declare function encodeAttachmentContent(attachments: CreateAttachmentRequest[]): Promise<CreateAttachmentRequest[]>;
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated Use encodeAttachmentContent instead. This alias is provided for backwards compatibility.
|
|
4
19
|
* Encodes the content of each attachment stream to base64.
|
|
5
20
|
* @param attachments The attachments to encode.
|
|
6
21
|
* @returns The attachments with their content encoded to base64.
|
|
@@ -43,3 +58,10 @@ export interface PathParams<Path extends string> {
|
|
|
43
58
|
toPath(): string;
|
|
44
59
|
}
|
|
45
60
|
export declare function makePathParams<Path extends string>(path: Path, params: Record<ExtractPathParams<Path>, string>): string;
|
|
61
|
+
/**
|
|
62
|
+
* Calculates the total payload size for a message request, including body and attachments.
|
|
63
|
+
* This is used to determine if multipart/form-data should be used instead of JSON.
|
|
64
|
+
* @param requestBody The message request body
|
|
65
|
+
* @returns The total estimated payload size in bytes
|
|
66
|
+
*/
|
|
67
|
+
export declare function calculateTotalPayloadSize(requestBody: any): number;
|
package/lib/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "7.
|
|
1
|
+
export declare const SDK_VERSION = "7.12.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nylas",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.12.0",
|
|
4
4
|
"description": "A NodeJS wrapper for the Nylas REST API for email, contacts, and calendar.",
|
|
5
5
|
"main": "lib/cjs/nylas.js",
|
|
6
6
|
"types": "lib/types/nylas.d.ts",
|
|
@@ -40,9 +40,10 @@
|
|
|
40
40
|
"license": "MIT",
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"change-case": "^4.1.2",
|
|
43
|
-
"form-data": "^4.
|
|
43
|
+
"form-data-encoder": "^4.1.0",
|
|
44
|
+
"formdata-node": "^6.0.3",
|
|
44
45
|
"mime-types": "^2.1.35",
|
|
45
|
-
"node-fetch": "^
|
|
46
|
+
"node-fetch": "^3.3.2",
|
|
46
47
|
"uuid": "^8.3.2"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
@@ -50,7 +51,6 @@
|
|
|
50
51
|
"@types/jest": "^29.5.2",
|
|
51
52
|
"@types/mime-types": "^2.1.2",
|
|
52
53
|
"@types/node": "^22.15.21",
|
|
53
|
-
"@types/node-fetch": "^2.6.4",
|
|
54
54
|
"@types/uuid": "^8.3.4",
|
|
55
55
|
"@typescript-eslint/eslint-plugin": "^2.25.0",
|
|
56
56
|
"@typescript-eslint/parser": "^2.25.0",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"eslint-plugin-import": "^2.28.1",
|
|
61
61
|
"eslint-plugin-prettier": "^3.0.1",
|
|
62
62
|
"jest": "^29.6.1",
|
|
63
|
+
"jest-fetch-mock": "^3.0.3",
|
|
63
64
|
"prettier": "^3.5.3",
|
|
64
65
|
"ts-jest": "^29.1.1",
|
|
65
66
|
"typedoc": "^0.28.4",
|