react-native-email-imap-smtp 0.1.0 → 0.2.1
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/package.json +1 -1
- package/react-native.config.js +1 -2
- package/windows/EmailImapSmtp/EmailImapSmtp.def +3 -0
- package/windows/EmailImapSmtp/EmailImapSmtp.vcxproj +159 -0
- package/windows/EmailImapSmtp/EmailImapSmtp.vcxproj.filters +74 -0
- package/windows/EmailImapSmtp/EmailImapSmtpModule.h +494 -0
- package/windows/{ImapProtocol.cpp → EmailImapSmtp/ImapProtocol.cpp} +1 -0
- package/windows/EmailImapSmtp/PropertySheet.props +28 -0
- package/windows/EmailImapSmtp/ReactPackageProvider.cpp +20 -0
- package/windows/EmailImapSmtp/ReactPackageProvider.h +22 -0
- package/windows/EmailImapSmtp/ReactPackageProvider.idl +13 -0
- package/windows/{SmtpProtocol.cpp → EmailImapSmtp/SmtpProtocol.cpp} +1 -0
- package/windows/{TlsSocket.cpp → EmailImapSmtp/TlsSocket.cpp} +139 -15
- package/windows/{TlsSocket.h → EmailImapSmtp/TlsSocket.h} +3 -3
- package/windows/EmailImapSmtp/pch.cpp +5 -0
- package/windows/EmailImapSmtp/pch.h +39 -0
- package/windows/EmailImapSmtp.sln +30 -0
- package/windows/ExperimentalFeatures.props +15 -0
- package/windows/EmailImapSmtpModule.h +0 -139
- /package/windows/{EmailImapSmtp.cpp → EmailImapSmtp/EmailImapSmtp.cpp} +0 -0
- /package/windows/{EmailImapSmtp.h → EmailImapSmtp/EmailImapSmtp.h} +0 -0
- /package/windows/{EmailImapSmtpModule.cpp → EmailImapSmtp/EmailImapSmtpModule.cpp} +0 -0
- /package/windows/{EmailImapSmtpPackage.cpp → EmailImapSmtp/EmailImapSmtpPackage.cpp} +0 -0
- /package/windows/{EmailImapSmtpPackage.h → EmailImapSmtp/EmailImapSmtpPackage.h} +0 -0
- /package/windows/{ImapProtocol.h → EmailImapSmtp/ImapProtocol.h} +0 -0
- /package/windows/{ProtocolDefs.h → EmailImapSmtp/ProtocolDefs.h} +0 -0
- /package/windows/{SmtpProtocol.h → EmailImapSmtp/SmtpProtocol.h} +0 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// EmailImapSmtpModule.h — Windows TurboModule 实现
|
|
3
|
+
// 通过 JSON 字符串与 JS 层通信,内部调用协议层 C++ 代码
|
|
4
|
+
// ============================================================
|
|
5
|
+
|
|
6
|
+
#pragma once
|
|
7
|
+
|
|
8
|
+
#include <string>
|
|
9
|
+
#include <unordered_map>
|
|
10
|
+
#include <memory>
|
|
11
|
+
#include <mutex>
|
|
12
|
+
#include <sstream>
|
|
13
|
+
#include <optional>
|
|
14
|
+
#include <thread>
|
|
15
|
+
|
|
16
|
+
#include "winrt/Microsoft.ReactNative.h"
|
|
17
|
+
#include "NativeModules.h"
|
|
18
|
+
|
|
19
|
+
#include "ImapProtocol.h"
|
|
20
|
+
#include "SmtpProtocol.h"
|
|
21
|
+
|
|
22
|
+
namespace winrt::EmailImapSmtp {
|
|
23
|
+
|
|
24
|
+
// ─── JSON 序列化辅助 ────────────────────────────────────
|
|
25
|
+
namespace detail {
|
|
26
|
+
|
|
27
|
+
inline std::string escapeJson(const std::string& s) {
|
|
28
|
+
std::string out;
|
|
29
|
+
out.reserve(s.size() + 2);
|
|
30
|
+
for (char c : s) {
|
|
31
|
+
switch (c) {
|
|
32
|
+
case '"': out += "\\\""; break;
|
|
33
|
+
case '\\': out += "\\\\"; break;
|
|
34
|
+
case '\n': out += "\\n"; break;
|
|
35
|
+
case '\r': out += "\\r"; break;
|
|
36
|
+
case '\t': out += "\\t"; break;
|
|
37
|
+
default: out += c;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
inline std::string toJson(const emailimap::MailboxInfo& mb) {
|
|
44
|
+
std::ostringstream os;
|
|
45
|
+
os << "{"
|
|
46
|
+
<< "\"name\":\"" << escapeJson(mb.name) << "\","
|
|
47
|
+
<< "\"path\":\"" << escapeJson(mb.path) << "\","
|
|
48
|
+
<< "\"uidNext\":" << mb.uidNext << ","
|
|
49
|
+
<< "\"uidValidity\":" << mb.uidValidity << ","
|
|
50
|
+
<< "\"messageCount\":" << mb.messageCount << ","
|
|
51
|
+
<< "\"recentCount\":" << mb.recentCount << ","
|
|
52
|
+
<< "\"unseenCount\":" << mb.unseenCount << ","
|
|
53
|
+
<< "\"firstUnseenUid\":" << mb.firstUnseenUid
|
|
54
|
+
<< "}";
|
|
55
|
+
return os.str();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
inline std::string toJson(const emailimap::MailboxListItem& item) {
|
|
59
|
+
std::ostringstream os;
|
|
60
|
+
os << "{"
|
|
61
|
+
<< "\"name\":\"" << escapeJson(item.name) << "\","
|
|
62
|
+
<< "\"path\":\"" << escapeJson(item.path) << "\","
|
|
63
|
+
<< "\"subscribed\":" << (item.subscribed ? "true" : "false")
|
|
64
|
+
<< "}";
|
|
65
|
+
return os.str();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
inline std::string toJson(const emailimap::EmailAddress& addr) {
|
|
69
|
+
std::ostringstream os;
|
|
70
|
+
os << "{"
|
|
71
|
+
<< "\"name\":\"" << escapeJson(addr.name) << "\","
|
|
72
|
+
<< "\"email\":\"" << escapeJson(addr.email) << "\""
|
|
73
|
+
<< "}";
|
|
74
|
+
return os.str();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
inline std::string toJson(const emailimap::AttachmentInfo& att) {
|
|
78
|
+
std::ostringstream os;
|
|
79
|
+
os << "{"
|
|
80
|
+
<< "\"filename\":\"" << escapeJson(att.filename) << "\","
|
|
81
|
+
<< "\"mimeType\":\"" << escapeJson(att.mimeType) << "\","
|
|
82
|
+
<< "\"partId\":\"" << escapeJson(att.partId) << "\","
|
|
83
|
+
<< "\"size\":" << att.size
|
|
84
|
+
<< "}";
|
|
85
|
+
return os.str();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
inline std::string toJson(const emailimap::EmailMessage& msg) {
|
|
89
|
+
std::ostringstream os;
|
|
90
|
+
os << "{"
|
|
91
|
+
<< "\"uid\":" << msg.uid << ","
|
|
92
|
+
<< "\"subject\":\"" << escapeJson(msg.subject) << "\","
|
|
93
|
+
<< "\"from\":" << toJson(msg.from) << ","
|
|
94
|
+
<< "\"date\":\"" << escapeJson(msg.date) << "\","
|
|
95
|
+
<< "\"size\":" << msg.size
|
|
96
|
+
<< "}";
|
|
97
|
+
return os.str();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
template<typename T, typename Fn>
|
|
101
|
+
inline std::string arrayToJson(const std::vector<T>& items, Fn serializer) {
|
|
102
|
+
std::string result = "[";
|
|
103
|
+
for (size_t i = 0; i < items.size(); ++i) {
|
|
104
|
+
if (i > 0) result += ",";
|
|
105
|
+
result += serializer(items[i]);
|
|
106
|
+
}
|
|
107
|
+
result += "]";
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
} // namespace detail
|
|
112
|
+
|
|
113
|
+
struct SessionState {
|
|
114
|
+
std::unique_ptr<emailimap::ImapClient> imapClient;
|
|
115
|
+
std::unique_ptr<emailimap::SmtpClient> smtpClient;
|
|
116
|
+
bool imapConnected = false;
|
|
117
|
+
bool smtpConnected = false;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
REACT_MODULE(EmailImapSmtpModule, L"EmailImapSmtp");
|
|
121
|
+
struct EmailImapSmtpModule {
|
|
122
|
+
// ─── Session 管理 ─────────────────────────────────────
|
|
123
|
+
std::unordered_map<std::string, std::shared_ptr<SessionState>> m_sessions;
|
|
124
|
+
std::mutex m_mutex;
|
|
125
|
+
int m_nextId = 1;
|
|
126
|
+
|
|
127
|
+
std::string createSession() {
|
|
128
|
+
auto state = std::make_shared<SessionState>();
|
|
129
|
+
state->imapClient = std::make_unique<emailimap::ImapClient>();
|
|
130
|
+
state->smtpClient = std::make_unique<emailimap::SmtpClient>();
|
|
131
|
+
std::string id = "sess_" + std::to_string(m_nextId++);
|
|
132
|
+
std::lock_guard<std::mutex> lock(m_mutex);
|
|
133
|
+
m_sessions[id] = state;
|
|
134
|
+
return id;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
std::shared_ptr<SessionState> getSession(const std::string& id) {
|
|
138
|
+
std::lock_guard<std::mutex> lock(m_mutex);
|
|
139
|
+
auto it = m_sessions.find(id);
|
|
140
|
+
if (it == m_sessions.end()) return nullptr;
|
|
141
|
+
return it->second;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
void removeSession(const std::string& id) {
|
|
145
|
+
std::lock_guard<std::mutex> lock(m_mutex);
|
|
146
|
+
m_sessions.erase(id);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─── 异步辅助 ─────────────────────────────────────────
|
|
150
|
+
// 在后台线程执行网络操作,不阻塞 JS 线程
|
|
151
|
+
template<typename Fn>
|
|
152
|
+
void runAsync(React::ReactPromise<std::string> const& promise, Fn&& fn) noexcept {
|
|
153
|
+
std::thread([promise, fn = std::forward<Fn>(fn)]() {
|
|
154
|
+
try {
|
|
155
|
+
fn();
|
|
156
|
+
} catch (const std::exception& e) {
|
|
157
|
+
std::string err = detail::escapeJson(e.what());
|
|
158
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + err + "\"}");
|
|
159
|
+
} catch (...) {
|
|
160
|
+
promise.Resolve("{\"success\":false,\"error\":\"Unknown error\"}");
|
|
161
|
+
}
|
|
162
|
+
}).detach();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ─── IMAP 连接 ────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
REACT_METHOD(imapConnect);
|
|
168
|
+
void imapConnect(std::string configJson, React::ReactPromise<std::string> promise) noexcept {
|
|
169
|
+
runAsync(promise, [this, configJson = std::move(configJson), promise]() {
|
|
170
|
+
auto config = emailimap::ConnectionConfig{};
|
|
171
|
+
auto extractStr = [](const std::string& json, const std::string& key) -> std::string {
|
|
172
|
+
std::string search = "\"" + key + "\":\"";
|
|
173
|
+
size_t pos = json.find(search);
|
|
174
|
+
if (pos == std::string::npos) return "";
|
|
175
|
+
pos += search.size();
|
|
176
|
+
size_t end = json.find("\"", pos);
|
|
177
|
+
if (end == std::string::npos) return "";
|
|
178
|
+
return json.substr(pos, end - pos);
|
|
179
|
+
};
|
|
180
|
+
auto extractDbl = [](const std::string& json, const std::string& key, double def) -> double {
|
|
181
|
+
std::string search = "\"" + key + "\":";
|
|
182
|
+
size_t pos = json.find(search);
|
|
183
|
+
if (pos == std::string::npos) return def;
|
|
184
|
+
pos += search.size();
|
|
185
|
+
size_t end = json.find_first_of(",}", pos);
|
|
186
|
+
if (end == std::string::npos) return def;
|
|
187
|
+
try { return std::stod(json.substr(pos, end - pos)); }
|
|
188
|
+
catch (...) { return def; }
|
|
189
|
+
};
|
|
190
|
+
auto extractBool = [](const std::string& json, const std::string& key, bool def) -> bool {
|
|
191
|
+
std::string search = "\"" + key + "\":true";
|
|
192
|
+
if (json.find(search) != std::string::npos) return true;
|
|
193
|
+
search = "\"" + key + "\":false";
|
|
194
|
+
if (json.find(search) != std::string::npos) return false;
|
|
195
|
+
return def;
|
|
196
|
+
};
|
|
197
|
+
config.host = extractStr(configJson, "host");
|
|
198
|
+
config.port = extractDbl(configJson, "port", 993);
|
|
199
|
+
config.username = extractStr(configJson, "username");
|
|
200
|
+
config.password = extractStr(configJson, "password");
|
|
201
|
+
config.tls = extractBool(configJson, "tls", true);
|
|
202
|
+
config.authType = extractStr(configJson, "authType");
|
|
203
|
+
if (config.authType.empty()) config.authType = "password";
|
|
204
|
+
config.timeout = extractDbl(configJson, "timeout", 30000);
|
|
205
|
+
config.checkCertificate = extractBool(configJson, "checkCertificate", true);
|
|
206
|
+
config.debug = extractBool(configJson, "debug", false);
|
|
207
|
+
|
|
208
|
+
auto sessionId = createSession();
|
|
209
|
+
auto session = getSession(sessionId);
|
|
210
|
+
std::string error;
|
|
211
|
+
if (session->imapClient->connect(config, error)) {
|
|
212
|
+
session->imapConnected = true;
|
|
213
|
+
promise.Resolve("{\"success\":true,\"sessionId\":\"" + sessionId + "\"}");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
removeSession(sessionId);
|
|
217
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
REACT_METHOD(imapDisconnect);
|
|
222
|
+
void imapDisconnect(std::string sessionId, React::ReactPromise<std::string> promise) noexcept {
|
|
223
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), promise]() {
|
|
224
|
+
auto session = getSession(sessionId);
|
|
225
|
+
if (session && session->imapConnected) {
|
|
226
|
+
session->imapClient->disconnect();
|
|
227
|
+
session->imapConnected = false;
|
|
228
|
+
}
|
|
229
|
+
removeSession(sessionId);
|
|
230
|
+
promise.Resolve("{\"success\":true}");
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ─── IMAP 其他方法 ──────────────────────────────────
|
|
235
|
+
|
|
236
|
+
REACT_METHOD(imapListMailboxes);
|
|
237
|
+
void imapListMailboxes(std::string sessionId, React::ReactPromise<std::string> promise) noexcept {
|
|
238
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), promise]() {
|
|
239
|
+
auto session = getSession(sessionId);
|
|
240
|
+
if (!session || !session->imapConnected) {
|
|
241
|
+
promise.Resolve("{\"success\":false,\"error\":\"Not connected\"}");
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
std::string error;
|
|
245
|
+
auto mailboxes = session->imapClient->listMailboxes(error);
|
|
246
|
+
if (!error.empty()) {
|
|
247
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
promise.Resolve("{\"success\":true,\"mailboxes\":" +
|
|
251
|
+
detail::arrayToJson(mailboxes, [](const emailimap::MailboxListItem& i) { return detail::toJson(i); }) + "}");
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
REACT_METHOD(imapSelectMailbox);
|
|
256
|
+
void imapSelectMailbox(std::string sessionId, std::string mailbox, bool readOnly, React::ReactPromise<std::string> promise) noexcept {
|
|
257
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), mailbox = std::move(mailbox), readOnly, promise]() {
|
|
258
|
+
auto session = getSession(sessionId);
|
|
259
|
+
if (!session || !session->imapConnected) {
|
|
260
|
+
promise.Resolve("{\"success\":false,\"error\":\"Not connected\"}");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
std::string error;
|
|
264
|
+
auto info = session->imapClient->selectMailbox(mailbox, readOnly, error);
|
|
265
|
+
if (!error.empty()) {
|
|
266
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
promise.Resolve("{\"success\":true,\"mailbox\":" + detail::toJson(info) + "}");
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
REACT_METHOD(imapFetchEmails);
|
|
274
|
+
void imapFetchEmails(std::string sessionId, std::string optionsJson, React::ReactPromise<std::string> promise) noexcept {
|
|
275
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), optionsJson = std::move(optionsJson), promise]() {
|
|
276
|
+
auto session = getSession(sessionId);
|
|
277
|
+
if (!session || !session->imapConnected) {
|
|
278
|
+
promise.Resolve("{\"success\":false,\"error\":\"Not connected\"}");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
emailimap::FetchEmailsOptions opts;
|
|
282
|
+
auto extractDouble = [](const std::string& json, const std::string& key) -> std::optional<double> {
|
|
283
|
+
std::string search = "\"" + key + "\":";
|
|
284
|
+
size_t pos = json.find(search);
|
|
285
|
+
if (pos == std::string::npos) return std::nullopt;
|
|
286
|
+
pos += search.size();
|
|
287
|
+
size_t end = json.find_first_of(",}", pos);
|
|
288
|
+
if (end == std::string::npos) return std::nullopt;
|
|
289
|
+
std::string val = json.substr(pos, end - pos);
|
|
290
|
+
size_t start = val.find_first_not_of(" \t\r\n\"");
|
|
291
|
+
size_t finish = val.find_last_not_of(" \t\r\n\"");
|
|
292
|
+
if (start == std::string::npos) return std::nullopt;
|
|
293
|
+
val = val.substr(start, finish - start + 1);
|
|
294
|
+
try { return std::stod(val); }
|
|
295
|
+
catch (...) { return std::nullopt; }
|
|
296
|
+
};
|
|
297
|
+
auto extractString = [](const std::string& json, const std::string& key) -> std::optional<std::string> {
|
|
298
|
+
std::string search = "\"" + key + "\":\"";
|
|
299
|
+
size_t pos = json.find(search);
|
|
300
|
+
if (pos == std::string::npos) return std::nullopt;
|
|
301
|
+
pos += search.size();
|
|
302
|
+
size_t end = json.find("\"", pos);
|
|
303
|
+
if (end == std::string::npos) return std::nullopt;
|
|
304
|
+
return json.substr(pos, end - pos);
|
|
305
|
+
};
|
|
306
|
+
auto extractBool = [](const std::string& json, const std::string& key) -> bool {
|
|
307
|
+
std::string search = "\"" + key + "\":true";
|
|
308
|
+
return json.find(search) != std::string::npos;
|
|
309
|
+
};
|
|
310
|
+
auto page = extractDouble(optionsJson, "page");
|
|
311
|
+
auto pageSize = extractDouble(optionsJson, "pageSize");
|
|
312
|
+
auto from = extractDouble(optionsJson, "from");
|
|
313
|
+
auto to = extractDouble(optionsJson, "to");
|
|
314
|
+
if (page.has_value()) opts.page = page.value();
|
|
315
|
+
if (pageSize.has_value()) opts.pageSize = pageSize.value();
|
|
316
|
+
if (from.has_value()) opts.from = from.value();
|
|
317
|
+
if (to.has_value()) opts.to = to.value();
|
|
318
|
+
opts.fetchBody = extractBool(optionsJson, "fetchBody");
|
|
319
|
+
opts.fetchAttachments = extractBool(optionsJson, "fetchAttachments");
|
|
320
|
+
opts.fetchFlags = extractBool(optionsJson, "fetchFlags");
|
|
321
|
+
opts.fetchHeaders = extractBool(optionsJson, "fetchHeaders");
|
|
322
|
+
auto since = extractString(optionsJson, "since");
|
|
323
|
+
auto before = extractString(optionsJson, "before");
|
|
324
|
+
if (since.has_value()) opts.since = since.value();
|
|
325
|
+
if (before.has_value()) opts.before = before.value();
|
|
326
|
+
std::string error;
|
|
327
|
+
auto emails = session->imapClient->fetchEmails(opts, error);
|
|
328
|
+
if (!error.empty()) {
|
|
329
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
promise.Resolve("{\"success\":true,\"emails\":" +
|
|
333
|
+
detail::arrayToJson(emails, [](const emailimap::EmailMessage& e) { return detail::toJson(e); }) + "}");
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
REACT_METHOD(imapFetchEmailByUID);
|
|
338
|
+
void imapFetchEmailByUID(std::string sessionId, double uid, React::ReactPromise<std::string> promise) noexcept {
|
|
339
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), uid, promise]() {
|
|
340
|
+
auto session = getSession(sessionId);
|
|
341
|
+
if (!session || !session->imapConnected) {
|
|
342
|
+
promise.Resolve("{\"success\":false,\"error\":\"Not connected\"}");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
std::string error;
|
|
346
|
+
auto* msg = session->imapClient->fetchEmailByUID(uid, error);
|
|
347
|
+
if (!msg) {
|
|
348
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
promise.Resolve("{\"success\":true,\"email\":" + detail::toJson(*msg) + "}");
|
|
352
|
+
delete msg;
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
REACT_METHOD(imapSearchEmails);
|
|
357
|
+
void imapSearchEmails(std::string sessionId, std::string criteriaJson, React::ReactPromise<std::string> promise) noexcept {
|
|
358
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), criteriaJson = std::move(criteriaJson), promise]() {
|
|
359
|
+
auto session = getSession(sessionId);
|
|
360
|
+
if (!session || !session->imapConnected) {
|
|
361
|
+
promise.Resolve("{\"success\":false,\"error\":\"Not connected\"}");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
emailimap::SearchCriteria criteria;
|
|
365
|
+
auto extractStr = [](const std::string& json, const std::string& key) -> std::optional<std::string> {
|
|
366
|
+
std::string search = "\"" + key + "\":\"";
|
|
367
|
+
size_t pos = json.find(search);
|
|
368
|
+
if (pos == std::string::npos) return std::nullopt;
|
|
369
|
+
pos += search.size();
|
|
370
|
+
size_t end = json.find("\"", pos);
|
|
371
|
+
if (end == std::string::npos) return std::nullopt;
|
|
372
|
+
return json.substr(pos, end - pos);
|
|
373
|
+
};
|
|
374
|
+
auto extractBool = [](const std::string& json, const std::string& key) -> bool {
|
|
375
|
+
return json.find("\"" + key + "\":true") != std::string::npos;
|
|
376
|
+
};
|
|
377
|
+
auto subject = extractStr(criteriaJson, "subject");
|
|
378
|
+
auto from = extractStr(criteriaJson, "from");
|
|
379
|
+
auto to = extractStr(criteriaJson, "to");
|
|
380
|
+
auto text = extractStr(criteriaJson, "text");
|
|
381
|
+
auto body = extractStr(criteriaJson, "body");
|
|
382
|
+
auto since = extractStr(criteriaJson, "since");
|
|
383
|
+
auto before = extractStr(criteriaJson, "before");
|
|
384
|
+
if (subject.has_value()) criteria.subject = subject.value();
|
|
385
|
+
if (from.has_value()) criteria.from = from.value();
|
|
386
|
+
if (to.has_value()) criteria.to = to.value();
|
|
387
|
+
if (text.has_value()) criteria.text = text.value();
|
|
388
|
+
if (body.has_value()) criteria.body = body.value();
|
|
389
|
+
if (since.has_value()) criteria.since = since.value();
|
|
390
|
+
if (before.has_value()) criteria.before = before.value();
|
|
391
|
+
criteria.flagged = extractBool(criteriaJson, "flagged");
|
|
392
|
+
criteria.seen = extractBool(criteriaJson, "seen");
|
|
393
|
+
std::string error;
|
|
394
|
+
auto emails = session->imapClient->searchEmails(criteria, error);
|
|
395
|
+
if (!error.empty()) {
|
|
396
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
promise.Resolve("{\"success\":true,\"emails\":" +
|
|
400
|
+
detail::arrayToJson(emails, [](const emailimap::EmailMessage& e) { return detail::toJson(e); }) + "}");
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ─── SMTP ──────────────────────────────────────────────
|
|
405
|
+
|
|
406
|
+
REACT_METHOD(smtpConnect);
|
|
407
|
+
void smtpConnect(std::string configJson, React::ReactPromise<std::string> promise) noexcept {
|
|
408
|
+
runAsync(promise, [this, configJson = std::move(configJson), promise]() {
|
|
409
|
+
auto config = emailimap::SmtpConnectionConfig{};
|
|
410
|
+
auto extractStr = [](const std::string& json, const std::string& key) -> std::string {
|
|
411
|
+
std::string search = "\"" + key + "\":\"";
|
|
412
|
+
size_t pos = json.find(search);
|
|
413
|
+
if (pos == std::string::npos) return "";
|
|
414
|
+
pos += search.size();
|
|
415
|
+
size_t end = json.find("\"", pos);
|
|
416
|
+
if (end == std::string::npos) return "";
|
|
417
|
+
return json.substr(pos, end - pos);
|
|
418
|
+
};
|
|
419
|
+
auto extractDbl = [](const std::string& json, const std::string& key, double def) -> double {
|
|
420
|
+
std::string search = "\"" + key + "\":";
|
|
421
|
+
size_t pos = json.find(search);
|
|
422
|
+
if (pos == std::string::npos) return def;
|
|
423
|
+
pos += search.size();
|
|
424
|
+
size_t end = json.find_first_of(",}", pos);
|
|
425
|
+
if (end == std::string::npos) return def;
|
|
426
|
+
try { return std::stod(json.substr(pos, end - pos)); }
|
|
427
|
+
catch (...) { return def; }
|
|
428
|
+
};
|
|
429
|
+
auto extractBool = [](const std::string& json, const std::string& key, bool def) -> bool {
|
|
430
|
+
std::string search = "\"" + key + "\":true";
|
|
431
|
+
if (json.find(search) != std::string::npos) return true;
|
|
432
|
+
search = "\"" + key + "\":false";
|
|
433
|
+
if (json.find(search) != std::string::npos) return false;
|
|
434
|
+
return def;
|
|
435
|
+
};
|
|
436
|
+
config.host = extractStr(configJson, "host");
|
|
437
|
+
config.port = extractDbl(configJson, "port", 465);
|
|
438
|
+
config.username = extractStr(configJson, "username");
|
|
439
|
+
config.password = extractStr(configJson, "password");
|
|
440
|
+
config.tls = extractBool(configJson, "tls", true);
|
|
441
|
+
config.authType = extractStr(configJson, "authType");
|
|
442
|
+
if (config.authType.empty()) config.authType = "password";
|
|
443
|
+
config.timeout = extractDbl(configJson, "timeout", 30000);
|
|
444
|
+
config.checkCertificate = extractBool(configJson, "checkCertificate", true);
|
|
445
|
+
config.debug = extractBool(configJson, "debug", false);
|
|
446
|
+
|
|
447
|
+
auto sessionId = createSession();
|
|
448
|
+
auto session = getSession(sessionId);
|
|
449
|
+
std::string error;
|
|
450
|
+
if (session->smtpClient->connect(config, error)) {
|
|
451
|
+
session->smtpConnected = true;
|
|
452
|
+
promise.Resolve("{\"success\":true,\"sessionId\":\"" + sessionId + "\"}");
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
removeSession(sessionId);
|
|
456
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
REACT_METHOD(smtpDisconnect);
|
|
461
|
+
void smtpDisconnect(std::string sessionId, React::ReactPromise<std::string> promise) noexcept {
|
|
462
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), promise]() {
|
|
463
|
+
auto session = getSession(sessionId);
|
|
464
|
+
if (session && session->smtpConnected) {
|
|
465
|
+
session->smtpClient->disconnect();
|
|
466
|
+
session->smtpConnected = false;
|
|
467
|
+
}
|
|
468
|
+
removeSession(sessionId);
|
|
469
|
+
promise.Resolve("{\"success\":true}");
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
REACT_METHOD(smtpSendMail);
|
|
474
|
+
void smtpSendMail(std::string sessionId, std::string mailJson, React::ReactPromise<std::string> promise) noexcept {
|
|
475
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), mailJson = std::move(mailJson), promise]() {
|
|
476
|
+
auto session = getSession(sessionId);
|
|
477
|
+
if (!session || !session->smtpConnected) {
|
|
478
|
+
promise.Resolve("{\"success\":false,\"error\":\"SMTP not connected\"}");
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
emailimap::SendMailOptions opts;
|
|
482
|
+
emailimap::SendMailResult result;
|
|
483
|
+
// TODO: parse mailJson → opts
|
|
484
|
+
std::string error;
|
|
485
|
+
if (session->smtpClient->sendMail(opts, result, error)) {
|
|
486
|
+
promise.Resolve("{\"success\":true}");
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
} // namespace winrt::EmailImapSmtp
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
3
|
+
<ImportGroup Label="PropertySheets">
|
|
4
|
+
<Import Project="$(MSBuildThisFileDirectory)..\ExperimentalFeatures.props" Condition="Exists('$(MSBuildThisFileDirectory)..\ExperimentalFeatures.props')" />
|
|
5
|
+
</ImportGroup>
|
|
6
|
+
|
|
7
|
+
<PropertyGroup Label="Configuration">
|
|
8
|
+
<!-- C++/WinRT -->
|
|
9
|
+
<CppWinRTOptimized>true</CppWinRTOptimized>
|
|
10
|
+
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
|
|
11
|
+
<CppWinRTGenerateWindowsMetadata>true</CppWinRTGenerateWindowsMetadata>
|
|
12
|
+
<CppWinRTUsePrefixes>true</CppWinRTUsePrefixes>
|
|
13
|
+
</PropertyGroup>
|
|
14
|
+
|
|
15
|
+
<PropertyGroup Label="ReactNativeWindowsProps">
|
|
16
|
+
<ReactNativeWindowsDir Condition="'$(ReactNativeWindowsDir)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\</ReactNativeWindowsDir>
|
|
17
|
+
</PropertyGroup>
|
|
18
|
+
|
|
19
|
+
<ItemDefinitionGroup>
|
|
20
|
+
<ClCompile>
|
|
21
|
+
<!-- 添加 ReactNativeWindowsDir 到包含路径 -->
|
|
22
|
+
<AdditionalIncludeDirectories>$(ReactNativeWindowsDir);$(ReactNativeWindowsDir)Microsoft.ReactNative;$(ReactNativeWindowsDir)Microsoft.ReactNative.Cxx;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
|
23
|
+
</ClCompile>
|
|
24
|
+
<Midl>
|
|
25
|
+
<AdditionalIncludeDirectories>$(ReactNativeWindowsDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
|
26
|
+
</Midl>
|
|
27
|
+
</ItemDefinitionGroup>
|
|
28
|
+
</Project>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// ReactPackageProvider.cpp — Package Provider Implementation
|
|
3
|
+
// 注册 TurboModule (EmailImapSmtpModule) 到 React Native
|
|
4
|
+
// ============================================================
|
|
5
|
+
|
|
6
|
+
#include "pch.h"
|
|
7
|
+
#include "ReactPackageProvider.h"
|
|
8
|
+
#include "ReactPackageProvider.g.cpp"
|
|
9
|
+
|
|
10
|
+
#include "EmailImapSmtpModule.h"
|
|
11
|
+
|
|
12
|
+
using namespace winrt::Microsoft::ReactNative;
|
|
13
|
+
|
|
14
|
+
namespace winrt::EmailImapSmtp::implementation
|
|
15
|
+
{
|
|
16
|
+
void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept
|
|
17
|
+
{
|
|
18
|
+
AddAttributedModules(packageBuilder);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// ReactPackageProvider.h — Package Provider Header
|
|
3
|
+
// ============================================================
|
|
4
|
+
|
|
5
|
+
#pragma once
|
|
6
|
+
#include "ReactPackageProvider.g.h"
|
|
7
|
+
|
|
8
|
+
using namespace winrt::Microsoft::ReactNative;
|
|
9
|
+
|
|
10
|
+
namespace winrt::EmailImapSmtp::implementation
|
|
11
|
+
{
|
|
12
|
+
struct ReactPackageProvider : ReactPackageProviderT<ReactPackageProvider>
|
|
13
|
+
{
|
|
14
|
+
ReactPackageProvider() = default;
|
|
15
|
+
void CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
namespace winrt::EmailImapSmtp::factory_implementation
|
|
20
|
+
{
|
|
21
|
+
struct ReactPackageProvider : ReactPackageProviderT<ReactPackageProvider, implementation::ReactPackageProvider> {};
|
|
22
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// ReactPackageProvider.idl — WinRT IDL for Package Provider
|
|
3
|
+
// ============================================================
|
|
4
|
+
|
|
5
|
+
namespace EmailImapSmtp
|
|
6
|
+
{
|
|
7
|
+
[default_interface]
|
|
8
|
+
runtimeclass ReactPackageProvider : Microsoft.ReactNative.IReactPackageProvider
|
|
9
|
+
{
|
|
10
|
+
ReactPackageProvider();
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|