imapflow 1.0.74 → 1.0.78
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/.eslintrc +2 -1
- package/CHANGELOG.md +14 -0
- package/README.md +5 -1
- package/lib/commands/idle.js +35 -5
- package/lib/imap-flow.js +80 -17
- package/lib/proxy-connection.js +120 -0
- package/lib/search-compiler.js +5 -0
- package/lib/types.d.ts +2 -0
- package/package.json +13 -11
package/.eslintrc
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
- 1.0.78 - 2021-11-28
|
|
4
|
+
|
|
5
|
+
- Proxy support. Use configuration options `proxy: "url"` to use proxies
|
|
6
|
+
|
|
7
|
+
- 1.0.77 - 2021-11-25
|
|
8
|
+
|
|
9
|
+
- Testing out proxy connections
|
|
10
|
+
- Add X-GM-RAW support for search (jhi721 #68)
|
|
11
|
+
|
|
12
|
+
- 1.0.75 - 2021-10-15
|
|
13
|
+
|
|
14
|
+
- Fixed an exception with failing IDLE (ramiroaisen #60)
|
|
15
|
+
- Do not use 1 and 1.MIME for single node messages, fallback to TEXT and HEADERS
|
|
16
|
+
|
|
3
17
|
- 1.0.71 - 2021-09-28
|
|
4
18
|
|
|
5
19
|
- IDLE precheck changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# ImapFlow
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
> Managing IMAP connections is cool but if you are only looking for an easy way to integrate email accounts, then ImapFlow was built for [EmailEngine Email API](https://emailengine.app/). It's a self hosted software that converts all IMAP accounts to easy to use REST interfaces.
|
|
6
|
+
|
|
7
|
+
---
|
|
4
8
|
|
|
5
9
|
The focus for ImapFlow is to provide easy to use API over IMAP. Using ImapFlow does not expect knowledge about specific IMAP details, general understanding is good enough.
|
|
6
10
|
|
package/lib/commands/idle.js
CHANGED
|
@@ -12,6 +12,7 @@ module.exports = async connection => {
|
|
|
12
12
|
if (connection.capabilities.has('IDLE')) {
|
|
13
13
|
let response;
|
|
14
14
|
|
|
15
|
+
let preCheckWaitQueue = [];
|
|
15
16
|
try {
|
|
16
17
|
connection.idling = true;
|
|
17
18
|
|
|
@@ -20,12 +21,16 @@ module.exports = async connection => {
|
|
|
20
21
|
let doneSent = false;
|
|
21
22
|
let canEnd = false;
|
|
22
23
|
|
|
23
|
-
let preCheckWaitQueue = [];
|
|
24
|
-
|
|
25
24
|
let preCheck = () => {
|
|
26
25
|
doneRequested = true;
|
|
27
26
|
if (canEnd && !doneSent) {
|
|
28
|
-
connection.log.debug({
|
|
27
|
+
connection.log.debug({
|
|
28
|
+
src: 'c',
|
|
29
|
+
msg: `DONE`,
|
|
30
|
+
comment: `breaking IDLE`,
|
|
31
|
+
lockId: connection.currentLockId,
|
|
32
|
+
path: connection.mailbox && connection.mailbox.path
|
|
33
|
+
});
|
|
29
34
|
connection.write('DONE');
|
|
30
35
|
doneSent = true;
|
|
31
36
|
|
|
@@ -44,6 +49,16 @@ module.exports = async connection => {
|
|
|
44
49
|
preCheckWaitQueue.push({ resolve, reject });
|
|
45
50
|
});
|
|
46
51
|
|
|
52
|
+
connection.log.trace({
|
|
53
|
+
msg: 'Requesting IDLE break',
|
|
54
|
+
lockId: connection.currentLockId,
|
|
55
|
+
path: connection.mailbox && connection.mailbox.path,
|
|
56
|
+
queued: preCheckWaitQueue.length,
|
|
57
|
+
doneRequested,
|
|
58
|
+
canEnd,
|
|
59
|
+
doneSent
|
|
60
|
+
});
|
|
61
|
+
|
|
47
62
|
preCheck();
|
|
48
63
|
|
|
49
64
|
return handler;
|
|
@@ -51,7 +66,7 @@ module.exports = async connection => {
|
|
|
51
66
|
|
|
52
67
|
response = await connection.exec('IDLE', false, {
|
|
53
68
|
onPlusTag: async () => {
|
|
54
|
-
connection.log.debug({ msg: `Initiated IDLE, waiting for server input
|
|
69
|
+
connection.log.debug({ msg: `Initiated IDLE, waiting for server input`, doneRequested });
|
|
55
70
|
canEnd = true;
|
|
56
71
|
if (doneRequested) {
|
|
57
72
|
preCheck();
|
|
@@ -63,7 +78,22 @@ module.exports = async connection => {
|
|
|
63
78
|
});
|
|
64
79
|
|
|
65
80
|
// unset before response.next()
|
|
66
|
-
connection.preCheck
|
|
81
|
+
if (typeof connection.preCheck === 'function') {
|
|
82
|
+
connection.log.trace({
|
|
83
|
+
msg: 'Clearing pre-check function',
|
|
84
|
+
lockId: connection.currentLockId,
|
|
85
|
+
path: connection.mailbox && connection.mailbox.path,
|
|
86
|
+
queued: preCheckWaitQueue.length,
|
|
87
|
+
doneRequested,
|
|
88
|
+
canEnd,
|
|
89
|
+
doneSent
|
|
90
|
+
});
|
|
91
|
+
connection.preCheck = false;
|
|
92
|
+
while (preCheckWaitQueue.length) {
|
|
93
|
+
let { resolve } = preCheckWaitQueue.shift();
|
|
94
|
+
resolve();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
67
97
|
|
|
68
98
|
response.next();
|
|
69
99
|
return;
|
package/lib/imap-flow.js
CHANGED
|
@@ -27,6 +27,8 @@ const libbase64 = require('libbase64');
|
|
|
27
27
|
const FlowedDecoder = require('mailsplit/lib/flowed-decoder');
|
|
28
28
|
const { PassThrough } = require('stream');
|
|
29
29
|
|
|
30
|
+
const { proxyConnection } = require('./proxy-connection');
|
|
31
|
+
|
|
30
32
|
const { comparePaths, updateCapabilities, getFolderTree, formatMessageResponse, getDecoder, packMessageRange, normalizePath } = require('./tools');
|
|
31
33
|
|
|
32
34
|
const imapCommands = require('./imap-commands.js');
|
|
@@ -124,6 +126,7 @@ class ImapFlow extends EventEmitter {
|
|
|
124
126
|
* @param {Boolean} [options.logRaw=false] If true then log data read from and written to socket encoded in base64
|
|
125
127
|
* @param {Boolean} [options.emitLogs=false] If `true` then in addition of sending data to logger, ImapFlow emits 'log' events with the same data
|
|
126
128
|
* @param {Boolean} [options.verifyOnly=false] If `true` then logs out automatically after successful authentication
|
|
129
|
+
* @param {Boolean} [options.proxy] Optional proxy URL. Supports HTTP CONNECT (`http://`, `https://`) and SOCKS (`socks://`, `socks4://`, `socks5://`) proxies
|
|
127
130
|
*/
|
|
128
131
|
constructor(options) {
|
|
129
132
|
super();
|
|
@@ -1031,7 +1034,7 @@ class ImapFlow extends EventEmitter {
|
|
|
1031
1034
|
* await client.connect();
|
|
1032
1035
|
*/
|
|
1033
1036
|
async connect() {
|
|
1034
|
-
let connector = this.
|
|
1037
|
+
let connector = this.secureConnection ? tls : net;
|
|
1035
1038
|
|
|
1036
1039
|
let opts = Object.assign(
|
|
1037
1040
|
{
|
|
@@ -1054,6 +1057,22 @@ class ImapFlow extends EventEmitter {
|
|
|
1054
1057
|
this.untaggedHandlers.EXISTS = (...args) => this.untaggedExists(...args);
|
|
1055
1058
|
this.untaggedHandlers.EXPUNGE = (...args) => this.untaggedExpunge(...args);
|
|
1056
1059
|
|
|
1060
|
+
let socket = false;
|
|
1061
|
+
if (this.options.proxy) {
|
|
1062
|
+
try {
|
|
1063
|
+
socket = await proxyConnection(this.log, this.options.proxy, this.host, this.port);
|
|
1064
|
+
if (!socket) {
|
|
1065
|
+
throw new Error('Failed to setup proxy connection');
|
|
1066
|
+
}
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
let error = new Error('Failed to setup proxy connection');
|
|
1069
|
+
error.code = err.code || 'ProxyError';
|
|
1070
|
+
error._err = err;
|
|
1071
|
+
this.log.error({ error, cid: this.id });
|
|
1072
|
+
throw error;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1057
1076
|
await new Promise((resolve, reject) => {
|
|
1058
1077
|
this.connectTimeout = setTimeout(() => {
|
|
1059
1078
|
let err = new Error('Failed to established connection in required time');
|
|
@@ -1063,7 +1082,7 @@ class ImapFlow extends EventEmitter {
|
|
|
1063
1082
|
reject(err);
|
|
1064
1083
|
}, CONNECT_TIMEOUT);
|
|
1065
1084
|
|
|
1066
|
-
|
|
1085
|
+
let onConnect = () => {
|
|
1067
1086
|
clearTimeout(this.connectTimeout);
|
|
1068
1087
|
this.socket.setKeepAlive(true, 5 * 1000);
|
|
1069
1088
|
this.socket.setTimeout(SOCKET_TIMEOUT);
|
|
@@ -1105,7 +1124,23 @@ class ImapFlow extends EventEmitter {
|
|
|
1105
1124
|
// executed by initial "* OK"
|
|
1106
1125
|
this.initialResolve = resolve;
|
|
1107
1126
|
this.initialReject = reject;
|
|
1108
|
-
}
|
|
1127
|
+
};
|
|
1128
|
+
|
|
1129
|
+
if (socket) {
|
|
1130
|
+
// socket is already establised via proxy
|
|
1131
|
+
if (this.secureConnection) {
|
|
1132
|
+
// TLS socket requires a handshake
|
|
1133
|
+
opts.socket = socket;
|
|
1134
|
+
this.socket = connector.connect(opts, onConnect);
|
|
1135
|
+
} else {
|
|
1136
|
+
// cleartext socket is already usable
|
|
1137
|
+
this.socket = socket;
|
|
1138
|
+
setImmediate(onConnect);
|
|
1139
|
+
}
|
|
1140
|
+
} else {
|
|
1141
|
+
this.socket = connector.connect(opts, onConnect);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1109
1144
|
this.writeSocket = this.socket;
|
|
1110
1145
|
|
|
1111
1146
|
this.socket.once('error', err => {
|
|
@@ -2025,7 +2060,13 @@ class ImapFlow extends EventEmitter {
|
|
|
2025
2060
|
return {};
|
|
2026
2061
|
}
|
|
2027
2062
|
|
|
2028
|
-
options =
|
|
2063
|
+
options = Object.assign(
|
|
2064
|
+
{
|
|
2065
|
+
chunkSize: 64 * 1024,
|
|
2066
|
+
maxBytes: Infinity
|
|
2067
|
+
},
|
|
2068
|
+
options || {}
|
|
2069
|
+
);
|
|
2029
2070
|
|
|
2030
2071
|
let hasMore = true;
|
|
2031
2072
|
let processed = 0;
|
|
@@ -2035,6 +2076,28 @@ class ImapFlow extends EventEmitter {
|
|
|
2035
2076
|
|
|
2036
2077
|
let uid = false;
|
|
2037
2078
|
|
|
2079
|
+
if (part === '1') {
|
|
2080
|
+
// First part has special conditions for single node emails as
|
|
2081
|
+
// the mime parts for root node are not 1 and 1.MIME but TEXT and HEADERS
|
|
2082
|
+
let response = await this.fetchOne(range, { uid: true, bodyStructure: true }, options);
|
|
2083
|
+
|
|
2084
|
+
if (!response) {
|
|
2085
|
+
return { response: false, chunk: false };
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
if (!uid && response.uid) {
|
|
2089
|
+
uid = response.uid;
|
|
2090
|
+
// force UID from now on even if first range was a sequence number
|
|
2091
|
+
range = uid;
|
|
2092
|
+
options.uid = true;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
if (!response.bodyStructure.childNodes) {
|
|
2096
|
+
// single text message
|
|
2097
|
+
part = 'TEXT';
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2038
2101
|
let getNextPart = async query => {
|
|
2039
2102
|
query = query || {};
|
|
2040
2103
|
|
|
@@ -2052,10 +2115,15 @@ class ImapFlow extends EventEmitter {
|
|
|
2052
2115
|
query.bodyParts = [];
|
|
2053
2116
|
}
|
|
2054
2117
|
|
|
2055
|
-
if (query.size
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2118
|
+
if (query.size) {
|
|
2119
|
+
if (/^[\d.]+$/.test(part)) {
|
|
2120
|
+
// fetch meta as well
|
|
2121
|
+
mimeKey = part + '.mime';
|
|
2122
|
+
query.bodyParts.push(mimeKey);
|
|
2123
|
+
} else if (part === 'text') {
|
|
2124
|
+
mimeKey = 'header';
|
|
2125
|
+
query.bodyParts.push(mimeKey);
|
|
2126
|
+
}
|
|
2059
2127
|
}
|
|
2060
2128
|
|
|
2061
2129
|
query.bodyParts.push({
|
|
@@ -2092,15 +2160,10 @@ class ImapFlow extends EventEmitter {
|
|
|
2092
2160
|
}
|
|
2093
2161
|
|
|
2094
2162
|
if (query.bodyParts) {
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
let mimeResponse = await this.fetchOne(range, { bodyParts: ['header'] }, options);
|
|
2100
|
-
if (!mimeResponse) {
|
|
2101
|
-
return { response: false, chunk: false };
|
|
2102
|
-
}
|
|
2103
|
-
result.mime = mimeResponse.headers;
|
|
2163
|
+
if (mimeKey === 'header') {
|
|
2164
|
+
result.mime = response.headers;
|
|
2165
|
+
} else {
|
|
2166
|
+
result.mime = response.bodyParts.get(mimeKey);
|
|
2104
2167
|
}
|
|
2105
2168
|
}
|
|
2106
2169
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const httpProxyClient = require('nodemailer/lib/smtp-connection/http-proxy-client');
|
|
4
|
+
const { SocksClient } = require('socks');
|
|
5
|
+
const util = require('util');
|
|
6
|
+
const httpProxyClientAsync = util.promisify(httpProxyClient);
|
|
7
|
+
const dns = require('dns').promises;
|
|
8
|
+
const net = require('net');
|
|
9
|
+
|
|
10
|
+
const proxyConnection = async (logger, connectionUrl, host, port) => {
|
|
11
|
+
let proxyUrl = new URL(connectionUrl);
|
|
12
|
+
|
|
13
|
+
let protocol = proxyUrl.protocol.replace(/:$/, '').toLowerCase();
|
|
14
|
+
|
|
15
|
+
if (!net.isIP(host)) {
|
|
16
|
+
let resolveResult = await dns.resolve(host);
|
|
17
|
+
if (resolveResult && resolveResult.length) {
|
|
18
|
+
host = resolveResult[0];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
switch (protocol) {
|
|
23
|
+
// Connect using a HTTP CONNECT method
|
|
24
|
+
case 'http':
|
|
25
|
+
case 'https': {
|
|
26
|
+
try {
|
|
27
|
+
let socket = await httpProxyClientAsync(proxyUrl.href, port, host);
|
|
28
|
+
if (socket) {
|
|
29
|
+
if (proxyUrl.password) {
|
|
30
|
+
proxyUrl.password = '(hidden)';
|
|
31
|
+
}
|
|
32
|
+
logger.info({
|
|
33
|
+
msg: 'Established a socket via HTTP proxy',
|
|
34
|
+
proxyUrl: proxyUrl.href,
|
|
35
|
+
port,
|
|
36
|
+
host
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return socket;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (proxyUrl.password) {
|
|
42
|
+
proxyUrl.password = '(hidden)';
|
|
43
|
+
}
|
|
44
|
+
logger.error({
|
|
45
|
+
msg: 'Failed to establish a socket via HTTP proxy',
|
|
46
|
+
proxyUrl: proxyUrl.href,
|
|
47
|
+
port,
|
|
48
|
+
host,
|
|
49
|
+
err
|
|
50
|
+
});
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// SOCKS proxy
|
|
56
|
+
case 'socks':
|
|
57
|
+
case 'socks5':
|
|
58
|
+
case 'socks4':
|
|
59
|
+
case 'socks4a': {
|
|
60
|
+
let proxyType = Number(protocol.replace(/\D/g, '')) || 5;
|
|
61
|
+
|
|
62
|
+
let targetHost = proxyUrl.hostname;
|
|
63
|
+
if (!net.isIP(targetHost)) {
|
|
64
|
+
let resolveResult = await dns.resolve(targetHost);
|
|
65
|
+
if (resolveResult && resolveResult.length) {
|
|
66
|
+
targetHost = resolveResult[0];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let connectionOpts = {
|
|
71
|
+
proxy: {
|
|
72
|
+
host: targetHost,
|
|
73
|
+
port: Number(proxyUrl.port) || 1080,
|
|
74
|
+
type: proxyType
|
|
75
|
+
},
|
|
76
|
+
destination: {
|
|
77
|
+
host,
|
|
78
|
+
port
|
|
79
|
+
},
|
|
80
|
+
command: 'connect',
|
|
81
|
+
set_tcp_nodelay: true
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (proxyUrl.username || proxyUrl.password) {
|
|
85
|
+
connectionOpts.proxy.userId = proxyUrl.username;
|
|
86
|
+
connectionOpts.proxy.password = proxyUrl.password;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const info = await SocksClient.createConnection(connectionOpts);
|
|
91
|
+
if (info && info.socket) {
|
|
92
|
+
if (proxyUrl.password) {
|
|
93
|
+
proxyUrl.password = '(hidden)';
|
|
94
|
+
}
|
|
95
|
+
logger.info({
|
|
96
|
+
msg: 'Established a socket via SOCKS proxy',
|
|
97
|
+
proxyUrl: proxyUrl.href,
|
|
98
|
+
port,
|
|
99
|
+
host
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return info.socket;
|
|
103
|
+
} catch (err) {
|
|
104
|
+
if (proxyUrl.password) {
|
|
105
|
+
proxyUrl.password = '(hidden)';
|
|
106
|
+
}
|
|
107
|
+
logger.error({
|
|
108
|
+
msg: 'Failed to establish a socket via SOCKS proxy',
|
|
109
|
+
proxyUrl: proxyUrl.href,
|
|
110
|
+
port,
|
|
111
|
+
host,
|
|
112
|
+
err
|
|
113
|
+
});
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
module.exports = { proxyConnection };
|
package/lib/search-compiler.js
CHANGED
|
@@ -117,6 +117,11 @@ module.exports.searchCompiler = (connection, query) => {
|
|
|
117
117
|
setOpt(attributes, 'X-GM-THRID', params[term]);
|
|
118
118
|
}
|
|
119
119
|
break;
|
|
120
|
+
case 'GMRAW':
|
|
121
|
+
if (connection.capabilities.has('X-GM-EXT-1')) {
|
|
122
|
+
setOpt(attributes, 'X-GM-RAW', params[term]);
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
120
125
|
|
|
121
126
|
case 'BEFORE':
|
|
122
127
|
case 'ON':
|
package/lib/types.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ declare module "imapflow" {
|
|
|
26
26
|
* @param [options.logRaw = false] - If true then log data read from and written to socket encoded in base64
|
|
27
27
|
* @param [options.emitLogs = false] - If `true` then in addition of sending data to logger, ImapFlow emits 'log' events with the same data
|
|
28
28
|
* @param [options.verifyOnly = false] - If `true` then logs out automatically after successful authentication
|
|
29
|
+
* @param [options.proxy] - Optional proxy URL. Supports HTTP CONNECT (`http://`, `https://`) and SOCKS (`socks://`, `socks4://`, `socks5://`) proxies
|
|
29
30
|
*/
|
|
30
31
|
class ImapFlow extends EventEmitter {
|
|
31
32
|
constructor(options: {
|
|
@@ -50,6 +51,7 @@ declare module "imapflow" {
|
|
|
50
51
|
logRaw?: boolean;
|
|
51
52
|
emitLogs?: boolean;
|
|
52
53
|
verifyOnly?: boolean;
|
|
54
|
+
proxy?: boolean;
|
|
53
55
|
});
|
|
54
56
|
/**
|
|
55
57
|
* Current module version as a static class property
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.78",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "./lib/imap-flow.js",
|
|
6
6
|
"scripts": {
|
|
@@ -26,19 +26,19 @@
|
|
|
26
26
|
},
|
|
27
27
|
"homepage": "https://imapflow.com/",
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@babel/eslint-parser": "
|
|
30
|
-
"@babel/eslint-plugin": "
|
|
31
|
-
"@babel/plugin-syntax-class-properties": "
|
|
32
|
-
"@babel/preset-env": "
|
|
33
|
-
"@types/node": "16.10
|
|
29
|
+
"@babel/eslint-parser": "7.16.3",
|
|
30
|
+
"@babel/eslint-plugin": "7.14.5",
|
|
31
|
+
"@babel/plugin-syntax-class-properties": "7.12.13",
|
|
32
|
+
"@babel/preset-env": "7.16.4",
|
|
33
|
+
"@types/node": "16.11.10",
|
|
34
34
|
"braintree-jsdoc-template": "3.3.0",
|
|
35
|
-
"eslint": "
|
|
35
|
+
"eslint": "8.3.0",
|
|
36
36
|
"eslint-config-nodemailer": "1.2.0",
|
|
37
37
|
"eslint-config-prettier": "8.3.0",
|
|
38
38
|
"grunt": "1.4.1",
|
|
39
39
|
"grunt-cli": "1.4.3",
|
|
40
|
-
"grunt-contrib-nodeunit": "
|
|
41
|
-
"grunt-eslint": "
|
|
40
|
+
"grunt-contrib-nodeunit": "4.0.0",
|
|
41
|
+
"grunt-eslint": "24.0.0",
|
|
42
42
|
"jsdoc": "3.6.7",
|
|
43
43
|
"tsd-jsdoc": "2.5.0"
|
|
44
44
|
},
|
|
@@ -48,7 +48,9 @@
|
|
|
48
48
|
"libbase64": "1.2.1",
|
|
49
49
|
"libmime": "5.0.0",
|
|
50
50
|
"libqp": "1.1.0",
|
|
51
|
-
"mailsplit": "5.
|
|
52
|
-
"
|
|
51
|
+
"mailsplit": "5.3.1",
|
|
52
|
+
"nodemailer": "6.7.2",
|
|
53
|
+
"pino": "7.4.1",
|
|
54
|
+
"socks": "2.6.1"
|
|
53
55
|
}
|
|
54
56
|
}
|