react-native-blob-util 0.22.0 → 0.22.2

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.
Files changed (97) hide show
  1. package/.eslintrc.js +56 -56
  2. package/LICENSE +21 -21
  3. package/Migration.md +41 -41
  4. package/NuGet.config +11 -11
  5. package/README.md +1109 -1104
  6. package/android/gradle.properties +1 -1
  7. package/android/src/main/AndroidManifest.xml +37 -37
  8. package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilConfig.java +57 -57
  9. package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilConst.java +20 -20
  10. package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilFileTransformer.java +9 -9
  11. package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilImpl.java +431 -431
  12. package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilProgressConfig.java +39 -39
  13. package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java +1014 -1014
  14. package/android/src/main/java/com/ReactNativeBlobUtil/Response/ReactNativeBlobUtilDefaultResp.java +98 -98
  15. package/android/src/main/java/com/ReactNativeBlobUtil/Response/ReactNativeBlobUtilFileResp.java +166 -166
  16. package/android/src/main/java/com/ReactNativeBlobUtil/Utils/FileDescription.java +19 -19
  17. package/android/src/main/java/com/ReactNativeBlobUtil/Utils/FileProvider.java +4 -4
  18. package/android/src/main/java/com/ReactNativeBlobUtil/Utils/MimeType.java +70 -70
  19. package/android/src/main/java/com/ReactNativeBlobUtil/Utils/PathResolver.java +223 -223
  20. package/android/src/main/java/com/ReactNativeBlobUtil/Utils/Tls12SocketFactory.java +68 -68
  21. package/android/src/main/res/values/strings.xml +3 -3
  22. package/android/src/main/res/xml/provider_paths.xml +12 -12
  23. package/android.js +58 -58
  24. package/class/ReactNativeBlobUtilCanceledFetchError.js +9 -9
  25. package/class/ReactNativeBlobUtilFile.js +5 -5
  26. package/class/ReactNativeBlobUtilReadStream.js +84 -84
  27. package/class/ReactNativeBlobUtilSession.js +68 -68
  28. package/class/ReactNativeBlobUtilWriteStream.js +50 -50
  29. package/class/StatefulPromise.js +7 -7
  30. package/codegenSpecs/NativeBlobUtils.js +86 -86
  31. package/components/Fetch.onPress.js +10 -10
  32. package/components/Fetch.when.js +15 -15
  33. package/fetch.js +350 -350
  34. package/fs.js +480 -480
  35. package/index.js +64 -64
  36. package/index.js.flow +192 -192
  37. package/index.web.js +1 -1
  38. package/ios/PrivacyInfo.xcprivacy +28 -28
  39. package/ios/ReactNativeBlobUtil/ReactNativeBlobUtil.h +55 -55
  40. package/ios/ReactNativeBlobUtil/ReactNativeBlobUtil.mm +992 -992
  41. package/ios/ReactNativeBlobUtil.xcodeproj/project.pbxproj +405 -405
  42. package/ios/ReactNativeBlobUtilConst.h +58 -58
  43. package/ios/ReactNativeBlobUtilConst.mm +47 -47
  44. package/ios/ReactNativeBlobUtilFS.h +108 -108
  45. package/ios/ReactNativeBlobUtilFS.mm +1064 -1064
  46. package/ios/ReactNativeBlobUtilFileTransformer.h +23 -23
  47. package/ios/ReactNativeBlobUtilFileTransformer.mm +20 -20
  48. package/ios/ReactNativeBlobUtilNetwork.h +49 -49
  49. package/ios/ReactNativeBlobUtilNetwork.mm +159 -159
  50. package/ios/ReactNativeBlobUtilProgress.h +32 -32
  51. package/ios/ReactNativeBlobUtilProgress.mm +57 -57
  52. package/ios/ReactNativeBlobUtilReqBuilder.h +37 -37
  53. package/ios/ReactNativeBlobUtilReqBuilder.mm +292 -292
  54. package/ios/ReactNativeBlobUtilRequest.h +48 -48
  55. package/ios/ReactNativeBlobUtilRequest.mm +584 -584
  56. package/ios.js +61 -61
  57. package/json-stream.js +42 -42
  58. package/lib/oboe-browser.js +2703 -2703
  59. package/mediacollection.js +36 -36
  60. package/package.json +68 -68
  61. package/polyfill/Blob.js +362 -362
  62. package/polyfill/Event.js +11 -11
  63. package/polyfill/EventTarget.js +78 -78
  64. package/polyfill/Fetch.js +219 -219
  65. package/polyfill/File.js +27 -27
  66. package/polyfill/FileReader.js +91 -91
  67. package/polyfill/ProgressEvent.js +32 -32
  68. package/polyfill/XMLHttpRequest.js +466 -466
  69. package/polyfill/XMLHttpRequestEventTarget.js +126 -126
  70. package/polyfill/index.js +11 -11
  71. package/react-native-blob-util.podspec +48 -48
  72. package/types.js +69 -69
  73. package/utils/log.js +40 -40
  74. package/utils/unicode.js +7 -7
  75. package/utils/uri.js +33 -33
  76. package/utils/uuid.js +4 -4
  77. package/windows/ExperimentalFeatures.props +33 -33
  78. package/windows/README.md +2 -2
  79. package/windows/ReactNativeBlobUtil/PropertySheet.props +15 -15
  80. package/windows/ReactNativeBlobUtil/ReactNativeBlobUtil.cpp +1836 -1836
  81. package/windows/ReactNativeBlobUtil/ReactNativeBlobUtil.def +3 -3
  82. package/windows/ReactNativeBlobUtil/ReactNativeBlobUtil.h +372 -372
  83. package/windows/ReactNativeBlobUtil/ReactNativeBlobUtil.vcxproj +139 -139
  84. package/windows/ReactNativeBlobUtil/ReactNativeBlobUtil.vcxproj.filters +43 -43
  85. package/windows/ReactNativeBlobUtil/ReactPackageProvider.cpp +20 -20
  86. package/windows/ReactNativeBlobUtil/ReactPackageProvider.h +24 -24
  87. package/windows/ReactNativeBlobUtil/ReactPackageProvider.idl +9 -9
  88. package/windows/ReactNativeBlobUtil/codegen/.clang-format +1 -1
  89. package/windows/ReactNativeBlobUtil/codegen/NativeBlobUtilsDataTypes.g.h +42 -42
  90. package/windows/ReactNativeBlobUtil/codegen/NativeBlobUtilsSpec.g.h +353 -353
  91. package/windows/ReactNativeBlobUtil/packages.config +3 -3
  92. package/windows/ReactNativeBlobUtil/packages.lock.json +59 -59
  93. package/windows/ReactNativeBlobUtil/pch.cpp +1 -1
  94. package/windows/ReactNativeBlobUtil/pch.h +30 -30
  95. package/windows/ReactNativeBlobUtil/resource.h +5 -5
  96. package/windows/ReactNativeBlobUtil/targetver.h +8 -8
  97. package/windows/ReactNativeBlobUtil.sln +43 -43
@@ -1,1837 +1,1837 @@
1
- #include "pch.h"
2
-
3
- #include "ReactNativeBlobUtil.h"
4
- #include <winrt/Windows.ApplicationModel.Activation.h>
5
- #include <winrt/Windows.Security.Cryptography.h>
6
- #include <winrt/Windows.Security.Cryptography.Core.h>
7
- #include <winrt/Windows.Storage.FileProperties.h>
8
- #include <winrt/Windows.Storage.Streams.h>
9
- #include <winrt/Windows.Storage.h>
10
- #include <winrt/Windows.Web.Http.h>
11
- #include <winrt/Windows.Web.Http.Headers.h>
12
- #include <winrt/windows.web.http.filters.h>
13
- #include <winrt/Windows.System.Threading.h>
14
- #include <filesystem>
15
- #include <sstream>
16
-
17
- using namespace winrt;
18
- using namespace winrt::Windows::ApplicationModel;
19
- using namespace winrt::Windows::Storage;
20
- using namespace winrt::Windows::Storage::Streams;
21
- using namespace winrt::Windows::Foundation;
22
- using namespace winrt::Windows::Security::Cryptography;
23
- using namespace winrt::Windows::Security::Cryptography::Core;
24
- using namespace std::chrono_literals;
25
-
26
- CancellationDisposable::CancellationDisposable(IAsyncInfo const& async, std::function<void()>&& onCancel) noexcept
27
- : m_async{ async }
28
- , m_onCancel{ std::move(onCancel) }
29
- {
30
- }
31
-
32
- CancellationDisposable::CancellationDisposable(CancellationDisposable&& other) noexcept
33
- : m_async{ std::move(other.m_async) }
34
- , m_onCancel{ std::move(other.m_onCancel) }
35
- {
36
- }
37
-
38
- CancellationDisposable& CancellationDisposable::operator=(CancellationDisposable&& other) noexcept
39
- {
40
- if (this != &other)
41
- {
42
- CancellationDisposable temp{ std::move(*this) };
43
- m_async = std::move(other.m_async);
44
- m_onCancel = std::move(other.m_onCancel);
45
- }
46
- return *this;
47
- }
48
-
49
- CancellationDisposable::~CancellationDisposable() noexcept
50
- {
51
- Cancel();
52
- }
53
-
54
- void CancellationDisposable::Cancel() noexcept
55
- {
56
- if (m_async)
57
- {
58
- if (m_async.Status() == AsyncStatus::Started)
59
- {
60
- m_async.Cancel();
61
- }
62
-
63
- if (m_onCancel)
64
- {
65
- m_onCancel();
66
- }
67
- }
68
- }
69
-
70
- TaskCancellationManager::~TaskCancellationManager() noexcept
71
- {
72
- // Do the explicit cleaning to make sure that CancellationDisposable
73
- // destructors run while this instance still has valid fields because
74
- // they are used by the onCancel callback.
75
- // We also want to clear the m_pendingTasks before running the
76
- // CancellationDisposable destructors since they touch the m_pendingTasks.
77
- std::map<TaskId, CancellationDisposable> pendingTasks;
78
- {
79
- std::scoped_lock lock{ m_mutex };
80
- pendingTasks = std::move(m_pendingTasks);
81
- }
82
- }
83
-
84
-
85
- IAsyncAction TaskCancellationManager::Add(TaskId taskId, IAsyncAction const& asyncAction) noexcept
86
- {
87
- std::scoped_lock lock{ m_mutex };
88
- m_pendingTasks.try_emplace(taskId, asyncAction, [this, taskId]()
89
- {
90
- Cancel(taskId);
91
- });
92
- return asyncAction;
93
- }
94
-
95
- void TaskCancellationManager::Cancel(TaskId taskId) noexcept
96
- {
97
- // The destructor of the token does the cancellation. We must do it outside of lock.
98
- CancellationDisposable token;
99
-
100
- {
101
- std::scoped_lock lock{ m_mutex };
102
- if (!m_pendingTasks.empty())
103
- {
104
- if (auto it = m_pendingTasks.find(taskId); it != m_pendingTasks.end())
105
- {
106
- token = std::move(it->second);
107
- m_pendingTasks.erase(it);
108
- }
109
- }
110
- }
111
- }
112
-
113
- ReactNativeBlobUtilConfig::ReactNativeBlobUtilConfig(::React::JSValue& options)
114
- {
115
- auto getStringOrDefault = [](const winrt::Microsoft::ReactNative::JSValue& value, const std::string& defaultValue = "") -> std::string {
116
- return value.IsNull() ? defaultValue : value.AsString();
117
- };
118
-
119
- auto getBoolOrDefault = [](const winrt::Microsoft::ReactNative::JSValue& value, bool defaultValue = false) -> bool {
120
- return value.IsNull() ? defaultValue : value.AsBoolean();
121
- };
122
-
123
- auto getInt64OrDefault = [](const winrt::Microsoft::ReactNative::JSValue& value, int64_t defaultValue = 60000) -> int64_t {
124
- return value.IsNull() ? defaultValue : value.AsInt64();
125
- };
126
-
127
- appendExt = getStringOrDefault(options["appendExt"]);
128
- fileCache = getBoolOrDefault(options["fileCache"]);
129
- followRedirect = getBoolOrDefault(options["followRedirect"]);
130
- overwrite = getBoolOrDefault(options["overwrite"]);
131
- trusty = getBoolOrDefault(options["trusty"]);
132
-
133
- // Handle path sanitization
134
- {
135
- std::string filepath = getStringOrDefault(options["path"]);
136
- if (!filepath.empty())
137
- {
138
- size_t fileLength = filepath.length();
139
- bool hasTrailingSlash = filepath[fileLength - 1] == '\\' || filepath[fileLength - 1] == '/';
140
- std::filesystem::path pathToParse = hasTrailingSlash ? filepath.substr(0, fileLength - 1) : filepath;
141
- pathToParse.make_preferred();
142
- path = pathToParse.string();
143
- }
144
- else
145
- {
146
- path = "";
147
- }
148
- }
149
-
150
- // Timeout handling
151
- int64_t potentialTimeout = getInt64OrDefault(options["timeout"]);
152
- timeout = std::chrono::seconds{ potentialTimeout > 0 ? potentialTimeout : 60000 };
153
- }
154
-
155
- ReactNativeBlobUtilProgressConfig::ReactNativeBlobUtilProgressConfig(double count_, double interval_) : count(count_), interval(interval_) {
156
- }
157
-
158
- ReactNativeBlobUtilStream::ReactNativeBlobUtilStream(Streams::IRandomAccessStream& _streamInstance, EncodingOptions _encoding) noexcept
159
- : streamInstance{ std::move(_streamInstance) }
160
- , encoding{ _encoding }
161
- {
162
- }
163
-
164
- namespace winrt::ReactNativeBlobUtil
165
- {
166
-
167
- // See https://microsoft.github.io/react-native-windows/docs/native-modules for details on writing native modules
168
-
169
- void ReactNativeBlobUtil::Initialize(React::ReactContext const& reactContext) noexcept {
170
- m_context = reactContext;
171
- }
172
-
173
- // Constants method
174
- ReactNativeBlobUtilCodegen::BlobUtilsSpec_Constants ReactNativeBlobUtil::GetConstants() noexcept {
175
- ReactNativeBlobUtilCodegen::BlobUtilsSpec_Constants constants;
176
- constants.DocumentDir = to_string(ApplicationData::Current().LocalFolder().Path());
177
- constants.CacheDir = to_string(ApplicationData::Current().LocalCacheFolder().Path());
178
- constants.PictureDir = to_string(UserDataPaths::GetDefault().Pictures());
179
- constants.MusicDir = to_string(UserDataPaths::GetDefault().Music());
180
- constants.MovieDir = to_string(UserDataPaths::GetDefault().Videos());
181
- constants.DownloadDir = to_string(UserDataPaths::GetDefault().Downloads());
182
- constants.MainBundleDir = to_string(Package::Current().InstalledLocation().Path());
183
- return constants;
184
- }
185
-
186
- winrt::fire_and_forget ReactNativeBlobUtil::fetchBlobForm(
187
- ::React::JSValue options,
188
- std::string taskId,
189
- std::string method,
190
- std::string url,
191
- ::React::JSValue headers,
192
- ::React::JSValueArray body,
193
- std::function<void(::React::JSValueArray)> callback
194
- ) noexcept
195
- {
196
- try
197
- {
198
- winrt::hstring boundary{ L"-----" };
199
- winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter filter;
200
- ReactNativeBlobUtilConfig config{ options };
201
- filter.AllowAutoRedirect(false);
202
-
203
- if (config.trusty)
204
- {
205
- filter.IgnorableServerCertificateErrors().Append(
206
- winrt::Windows::Security::Cryptography::Certificates::ChainValidationResult::Untrusted);
207
- }
208
-
209
- winrt::Windows::Web::Http::HttpMethod httpMethod = winrt::Windows::Web::Http::HttpMethod::Post();
210
- if (method == "DELETE" || method == "delete")
211
- httpMethod = winrt::Windows::Web::Http::HttpMethod::Delete();
212
- else if (method == "PUT" || method == "put")
213
- httpMethod = winrt::Windows::Web::Http::HttpMethod::Put();
214
- else if (method == "GET" || method == "get")
215
- httpMethod = winrt::Windows::Web::Http::HttpMethod::Get();
216
- else if (method != "POST" && method != "post")
217
- {
218
- ::React::JSValueArray errorArray;
219
- errorArray.push_back("Method not supported");
220
- callback(std::move(errorArray));
221
- co_return;
222
- }
223
-
224
- winrt::Windows::Web::Http::HttpRequestMessage requestMessage{ httpMethod, winrt::Windows::Foundation::Uri{ winrt::to_hstring(url) } };
225
- winrt::Windows::Web::Http::HttpMultipartFormDataContent requestContent{ boundary };
226
-
227
- // Add headers
228
- if (headers.ItemCount() > 0)
229
- {
230
- for (const auto& entry : headers.AsObject())
231
- {
232
- if (!requestMessage.Headers().TryAppendWithoutValidation(
233
- winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString())))
234
- {
235
- requestContent.Headers().TryAppendWithoutValidation(
236
- winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString()));
237
- }
238
- }
239
- }
240
-
241
- // Add form data
242
- for (auto& entry : body)
243
- {
244
- auto& items = entry.AsObject();
245
- auto data = items["data"].AsString();
246
-
247
- // File upload support: expects "file://" prefix
248
- bool isFile = data.rfind("file://", 0) == 0;
249
- if (isFile)
250
- {
251
- std::string contentPath = data.substr(strlen("file://"));
252
- winrt::hstring directoryPath, fileName;
253
- splitPath(contentPath, directoryPath, fileName);
254
- auto folder = co_await winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(directoryPath);
255
- auto storageFile = co_await folder.GetFileAsync(fileName);
256
- auto requestBuffer = co_await winrt::Windows::Storage::FileIO::ReadBufferAsync(storageFile);
257
-
258
- winrt::Windows::Web::Http::HttpBufferContent requestBufferContent{ requestBuffer };
259
- if (!items["type"].IsNull())
260
- {
261
- requestBufferContent.Headers().TryAppendWithoutValidation(
262
- L"content-type", winrt::to_hstring(items["type"].AsString()));
263
- }
264
-
265
- auto name = items["name"].IsNull() ? L"" : winrt::to_hstring(items["name"].AsString());
266
- auto filename = items["filename"].IsNull() ? L"" : winrt::to_hstring(items["filename"].AsString());
267
- if (name.empty())
268
- {
269
- requestContent.Add(requestBufferContent);
270
- }
271
- else if (filename.empty())
272
- {
273
- requestContent.Add(requestBufferContent, name);
274
- }
275
- else
276
- {
277
- requestContent.Add(requestBufferContent, name, filename);
278
- }
279
- }
280
- else
281
- {
282
- winrt::Windows::Web::Http::HttpStringContent dataContents{ winrt::to_hstring(data) };
283
- if (!items["type"].IsNull())
284
- {
285
- dataContents.Headers().TryAppendWithoutValidation(
286
- L"content-type", winrt::to_hstring(items["type"].AsString()));
287
- }
288
-
289
- auto name = items["name"].IsNull() ? L"" : winrt::to_hstring(items["name"].AsString());
290
- auto filename = items["filename"].IsNull() ? L"" : winrt::to_hstring(items["filename"].AsString());
291
- if (name.empty())
292
- {
293
- requestContent.Add(dataContents);
294
- }
295
- else if (filename.empty())
296
- {
297
- requestContent.Add(dataContents, name);
298
- }
299
- else
300
- {
301
- requestContent.Add(dataContents, name, filename);
302
- }
303
- }
304
- }
305
-
306
- requestMessage.Content(requestContent);
307
-
308
- winrt::Windows::Web::Http::HttpClient httpClient{ filter };
309
- auto response = co_await httpClient.SendRequestAsync(requestMessage);
310
-
311
- std::string responseBody;
312
- if (response.Content() != nullptr)
313
- {
314
- responseBody = winrt::to_string(co_await response.Content().ReadAsStringAsync());
315
- }
316
-
317
- ::React::JSValueArray resultArray;
318
- resultArray.push_back(responseBody);
319
- callback(std::move(resultArray));
320
- }
321
- catch (const winrt::hresult_error& ex)
322
- {
323
- ::React::JSValueArray errorArray;
324
- errorArray.push_back("EUNSPECIFIED");
325
- errorArray.push_back(winrt::to_string(ex.message()));
326
- callback(std::move(errorArray));
327
- }
328
- catch (...)
329
- {
330
- ::React::JSValueArray errorArray;
331
- errorArray.push_back("EUNSPECIFIED");
332
- errorArray.push_back("Unknown error in fetchBlobForm");
333
- callback(std::move(errorArray));
334
- }
335
- }
336
-
337
- winrt::fire_and_forget ReactNativeBlobUtil::fetchBlob(
338
- ::React::JSValue options,
339
- std::string taskId,
340
- std::string method,
341
- std::string url,
342
- ::React::JSValue headers,
343
- std::string body,
344
- std::function<void(::React::JSValueArray)> callback
345
- ) noexcept
346
- {
347
- // Convert rvalue references to lvalues for safe use
348
- ::React::JSValue& optionsRef = options;
349
- ::React::JSValue& headersRef = headers;
350
-
351
- try
352
- {
353
- winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter filter;
354
- ReactNativeBlobUtilConfig config{ optionsRef };
355
- filter.AllowAutoRedirect(false);
356
- if (config.trusty)
357
- {
358
- filter.IgnorableServerCertificateErrors().Append(Cryptography::Certificates::ChainValidationResult::Untrusted);
359
- }
360
-
361
- winrt::Windows::Web::Http::HttpClient httpClient{ filter };
362
-
363
- winrt::Windows::Web::Http::HttpMethod httpMethod{ winrt::Windows::Web::Http::HttpMethod::Post() };
364
- if (method == "DELETE" || method == "delete")
365
- {
366
- httpMethod = winrt::Windows::Web::Http::HttpMethod::Delete();
367
- }
368
- else if (method == "PUT" || method == "put")
369
- {
370
- httpMethod = winrt::Windows::Web::Http::HttpMethod::Put();
371
- }
372
- else if (method == "GET" || method == "get")
373
- {
374
- httpMethod = winrt::Windows::Web::Http::HttpMethod::Get();
375
- }
376
- else
377
- {
378
- ::React::JSValueArray errorArray;
379
- errorArray.push_back("Method not supported");
380
- callback(std::move(errorArray));
381
- co_return;
382
- }
383
-
384
- winrt::Windows::Web::Http::HttpRequestMessage requestMessage{
385
- httpMethod,
386
- winrt::Windows::Foundation::Uri{ winrt::to_hstring(url) }
387
- };
388
-
389
- std::string prefix = "file://";
390
- bool pathToFile = body.rfind(prefix, 0) == 0;
391
- if (pathToFile)
392
- {
393
- std::string contentPath = body.substr(prefix.length());
394
- size_t fileLength = contentPath.length();
395
- bool hasTrailingSlash = contentPath[fileLength - 1] == '\\' || contentPath[fileLength - 1] == '/';
396
- winrt::hstring directoryPath, fileName;
397
- splitPath(hasTrailingSlash ? contentPath.substr(0, fileLength - 1) : contentPath, directoryPath, fileName);
398
- auto folder = co_await winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(directoryPath);
399
- auto storageFile = co_await folder.GetFileAsync(fileName);
400
- auto requestBuffer = co_await winrt::Windows::Storage::FileIO::ReadBufferAsync(storageFile);
401
-
402
- winrt::Windows::Web::Http::HttpBufferContent requestContent{ requestBuffer };
403
-
404
- for (const auto& entry : headersRef.AsObject())
405
- {
406
- if (!requestMessage.Headers().TryAppendWithoutValidation(winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString())))
407
- {
408
- requestContent.Headers().TryAppendWithoutValidation(winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString()));
409
- }
410
- }
411
- requestMessage.Content(requestContent);
412
- }
413
- else if (!body.empty()) {
414
- winrt::Windows::Web::Http::HttpStringContent requestString{ winrt::to_hstring(body) };
415
-
416
- for (const auto& entry : headersRef.AsObject())
417
- {
418
- if (!requestMessage.Headers().TryAppendWithoutValidation(winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString())))
419
- {
420
- requestString.Headers().TryAppendWithoutValidation(winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString()));
421
- }
422
- }
423
- requestMessage.Content(requestString);
424
- }
425
- else {
426
- for (const auto& entry : headersRef.AsObject())
427
- {
428
- requestMessage.Headers().TryAppendWithoutValidation(winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString()));
429
- }
430
- }
431
-
432
- // Send the request
433
- auto response = co_await httpClient.SendRequestAsync(requestMessage);
434
-
435
- std::string responseBody;
436
- if (response.Content() != nullptr)
437
- {
438
- responseBody = winrt::to_string(co_await response.Content().ReadAsStringAsync());
439
- }
440
-
441
- ::React::JSValueArray resultArray;
442
- resultArray.push_back(responseBody);
443
- callback(std::move(resultArray));
444
- }
445
- catch (const winrt::hresult_error& ex)
446
- {
447
- ::React::JSValueArray errorArray;
448
- errorArray.push_back("EUNSPECIFIED");
449
- errorArray.push_back(winrt::to_string(ex.message()));
450
- callback(std::move(errorArray));
451
- }
452
- catch (...)
453
- {
454
- ::React::JSValueArray errorArray;
455
- errorArray.push_back("EUNSPECIFIED");
456
- errorArray.push_back("Unknown error in fetchBlob");
457
- callback(std::move(errorArray));
458
- }
459
- }
460
-
461
- winrt::fire_and_forget ReactNativeBlobUtil::createFile(
462
- std::string path,
463
- std::wstring content,
464
- std::string encoding,
465
- winrt::Microsoft::ReactNative::ReactPromise<void> promise) noexcept
466
- {
467
- try
468
- {
469
- bool shouldExit{ false };
470
- Streams::IBuffer buffer;
471
- if (encoding.compare("uri") == 0)
472
- {
473
- try
474
- {
475
- winrt::hstring srcDirectoryPath, srcFileName;
476
- splitPath(content, srcDirectoryPath, srcFileName);
477
- StorageFolder srcFolder{ co_await StorageFolder::GetFolderFromPathAsync(srcDirectoryPath) };
478
- StorageFile srcFile{ co_await srcFolder.GetFileAsync(srcFileName) };
479
- buffer = co_await FileIO::ReadBufferAsync(srcFile);
480
- }
481
- catch (...)
482
- {
483
- shouldExit = true;
484
- }
485
- }
486
- else if (encoding.compare("utf8") == 0)
487
- {
488
- buffer = CryptographicBuffer::ConvertStringToBinary(content, BinaryStringEncoding::Utf8);
489
- }
490
- else if (encoding.compare("base64") == 0)
491
- {
492
- buffer = CryptographicBuffer::DecodeFromBase64String(content);
493
- }
494
- else
495
- {
496
- promise.Reject("Invalid encoding");
497
- shouldExit = true;
498
- }
499
-
500
- if (!shouldExit)
501
- {
502
-
503
- winrt::hstring destDirectoryPath, destFileName;
504
- splitPath(path, destDirectoryPath, destFileName);
505
-
506
- auto folder{ co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath) };
507
-
508
- try
509
- {
510
- auto file{ co_await folder.CreateFileAsync(destFileName, CreationCollisionOption::FailIfExists) };
511
- auto stream{ co_await file.OpenAsync(FileAccessMode::ReadWrite) };
512
- co_await stream.WriteAsync(buffer);
513
- }
514
- catch (...)
515
- {
516
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EEXIST", "EEXIST: File already exists; " + path });
517
- shouldExit = true;
518
- }
519
- }
520
- if (!shouldExit)
521
- {
522
- promise.Resolve();
523
- }
524
- co_return;
525
- }
526
- catch (const hresult_error& ex)
527
- {
528
- hresult result{ ex.code() };
529
- if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) // FileNotFoundException
530
- {
531
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "ENOENT", "ENOENT: File does not exist and could not be created; " + path });
532
- }
533
- else
534
- {
535
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EUNSPECIFIED", "EUNSPECIFIED: " + winrt::to_string(ex.message()) + "; " + path });
536
- }
537
- }
538
- }
539
-
540
- winrt::fire_and_forget ReactNativeBlobUtil::createFileASCII(
541
- std::string path,
542
- winrt::Microsoft::ReactNative::JSValueArray dataArray,
543
- winrt::Microsoft::ReactNative::ReactPromise<void> promise) noexcept {
544
- try
545
- {
546
- std::vector<uint8_t> data;
547
- data.reserve(dataArray.size());
548
- for (auto& var : dataArray)
549
- {
550
- data.push_back(var.AsUInt8());
551
- }
552
-
553
- Streams::IBuffer buffer{ CryptographicBuffer::CreateFromByteArray(data) };
554
-
555
- winrt::hstring directoryPath, fileName;
556
- splitPath(path, directoryPath, fileName);
557
-
558
- StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(directoryPath) };
559
-
560
- StorageFile file{ co_await folder.CreateFileAsync(fileName, CreationCollisionOption::FailIfExists) };
561
- Streams::IRandomAccessStream stream{ co_await file.OpenAsync(FileAccessMode::ReadWrite) };
562
- co_await stream.WriteAsync(buffer);
563
-
564
- promise.Resolve();
565
- }
566
- catch (const hresult_error& ex)
567
- {
568
- hresult result{ ex.code() };
569
- if (result == 0x80070002) // FileNotFoundException
570
- {
571
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "ENOENT", "ENOENT: File does not exist and could not be created; " + path });
572
- }
573
- else if (result == 0x80070050)
574
- {
575
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EEXIST", "EEXIST: File already exists; " + path });
576
- }
577
- else
578
- {
579
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EUNSPECIFIED", "EUNSPECIFIED: " + winrt::to_string(ex.message()) });
580
- }
581
- }
582
- }
583
-
584
- winrt::fire_and_forget ReactNativeBlobUtil::writeFile(
585
- std::string path,
586
- std::string encoding,
587
- std::wstring data,
588
- bool transformFile,
589
- bool append,
590
- winrt::Microsoft::ReactNative::ReactPromise<double> promise) noexcept
591
- {
592
- try
593
- {
594
- Streams::IBuffer buffer;
595
- if (encoding.compare("utf8") == 0)
596
- {
597
- buffer = Cryptography::CryptographicBuffer::ConvertStringToBinary(data, BinaryStringEncoding::Utf8);
598
- }
599
- else if (encoding.compare("base64") == 0)
600
- {
601
- buffer = Cryptography::CryptographicBuffer::DecodeFromBase64String(data);
602
- }
603
- else if (encoding.compare("uri") == 0)
604
- {
605
- winrt::hstring srcDirectoryPath, srcFileName;
606
- splitPath(data, srcDirectoryPath, srcFileName);
607
- StorageFolder srcFolder{ co_await StorageFolder::GetFolderFromPathAsync(srcDirectoryPath) };
608
- StorageFile srcFile{ co_await srcFolder.GetFileAsync(srcFileName) };
609
- buffer = co_await FileIO::ReadBufferAsync(srcFile);
610
- }
611
- else
612
- {
613
- auto errorMessage{ "Invalid encoding: " + encoding };
614
- promise.Reject(errorMessage.c_str());
615
- }
616
-
617
- winrt::hstring destDirectoryPath, destFileName;
618
- splitPath(path, destDirectoryPath, destFileName);
619
- StorageFolder destFolder{ co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath) };
620
- StorageFile destFile{ nullptr };
621
- if (append)
622
- {
623
- destFile = co_await destFolder.CreateFileAsync(destFileName, CreationCollisionOption::OpenIfExists);
624
- }
625
- else
626
- {
627
- destFile = co_await destFolder.CreateFileAsync(destFileName, CreationCollisionOption::ReplaceExisting);
628
- }
629
- Streams::IRandomAccessStream stream{ co_await destFile.OpenAsync(FileAccessMode::ReadWrite) };
630
-
631
- if (append)
632
- {
633
- stream.Seek(UINT64_MAX);
634
- }
635
- co_await stream.WriteAsync(buffer);
636
- promise.Resolve(static_cast<double>(buffer.Length()));
637
- }
638
- catch (const hresult_error& ex)
639
- {
640
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EUNSPECIFIED", "EUNSPECIFIED: " + winrt::to_string(ex.message()) + "; " + path });
641
- }
642
- }
643
-
644
- winrt::fire_and_forget ReactNativeBlobUtil::writeFileArray(
645
- std::string path,
646
- winrt::Microsoft::ReactNative::JSValueArray dataArray,
647
- bool append,
648
- winrt::Microsoft::ReactNative::ReactPromise<double> promise) noexcept
649
- {
650
- try
651
- {
652
- std::vector<uint8_t> data;
653
- data.reserve(dataArray.size());
654
- for (auto& var : dataArray)
655
- {
656
- data.push_back(var.AsUInt8());
657
- }
658
- Streams::IBuffer buffer{ CryptographicBuffer::CreateFromByteArray(data) };
659
-
660
- winrt::hstring destDirectoryPath, destFileName;
661
- splitPath(path, destDirectoryPath, destFileName);
662
- StorageFolder destFolder{ co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath) };
663
- StorageFile destFile{ nullptr };
664
- if (append)
665
- {
666
- destFile = co_await destFolder.CreateFileAsync(destFileName, CreationCollisionOption::OpenIfExists);
667
- }
668
- else
669
- {
670
- destFile = co_await destFolder.CreateFileAsync(destFileName, CreationCollisionOption::ReplaceExisting);
671
- }
672
- Streams::IRandomAccessStream stream{ co_await destFile.OpenAsync(FileAccessMode::ReadWrite) };
673
-
674
- if (append)
675
- {
676
- stream.Seek(UINT64_MAX);
677
- }
678
- co_await stream.WriteAsync(buffer);
679
- promise.Resolve(static_cast<double>(buffer.Length()));
680
-
681
- co_return;
682
- }
683
- catch (const hresult_error& ex)
684
- {
685
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EUNSPECIFIED", "EUNSPECIFIED: " + winrt::to_string(ex.message()) + "; " + path });
686
- }
687
- }
688
-
689
- void ReactNativeBlobUtil::pathForAppGroup(
690
- std::string groupName,
691
- ::React::ReactPromise<std::string>&& result) noexcept
692
- {
693
- result.Resolve("");
694
- }
695
-
696
- std::string ReactNativeBlobUtil::syncPathAppGroup(
697
- std::string groupName) noexcept
698
- {
699
- return "";
700
- }
701
-
702
- void ReactNativeBlobUtil::exists(
703
- std::string path,
704
- std::function<void(std::vector<bool> const&)> const& callback) noexcept
705
- {
706
- try
707
- {
708
- std::filesystem::path fsPath(path);
709
- bool doesExist = std::filesystem::exists(fsPath);
710
- bool isDirectory = std::filesystem::is_directory(fsPath);
711
-
712
- callback(std::vector<bool>{ doesExist, isDirectory });
713
- }
714
- catch (const std::exception&)
715
- {
716
- // If something goes wrong, return false, false
717
- callback(std::vector<bool>{ false, false });
718
- }
719
- }
720
-
721
- winrt::fire_and_forget ReactNativeBlobUtil::writeStream(
722
- std::string path,
723
- std::string encoding,
724
- bool appendData,
725
- std::function<void(::React::JSValueArray)> callback) noexcept
726
- {
727
- try
728
- {
729
- winrt::hstring directoryPath, fileName;
730
- splitPath(path, directoryPath, fileName);
731
- auto folder = co_await StorageFolder::GetFolderFromPathAsync(directoryPath);
732
- auto file = co_await folder.CreateFileAsync(fileName, CreationCollisionOption::OpenIfExists);
733
- auto stream = co_await file.OpenAsync(FileAccessMode::ReadWrite);
734
- if (appendData)
735
- {
736
- stream.Seek(stream.Size());
737
- }
738
-
739
- EncodingOptions encodingOption;
740
- if (encoding == "utf8")
741
- {
742
- encodingOption = EncodingOptions::UTF8;
743
- }
744
- else if (encoding == "base64")
745
- {
746
- encodingOption = EncodingOptions::BASE64;
747
- }
748
- else if (encoding == "ascii")
749
- {
750
- encodingOption = EncodingOptions::ASCII;
751
- }
752
- else
753
- {
754
- // Return error as JSValueArray
755
- ::React::JSValueArray errorArray;
756
- errorArray.push_back("EUNSPECIFIED");
757
- errorArray.push_back("Invalid encoding: " + encoding);
758
- errorArray.push_back("");
759
- callback(std::move(errorArray));
760
- co_return;
761
- }
762
-
763
- // Generate a random streamId
764
- uint32_t length = 16;
765
- IBuffer buffer = Cryptography::CryptographicBuffer::GenerateRandom(length);
766
- std::string streamId = winrt::to_string(Cryptography::CryptographicBuffer::EncodeToHexString(buffer));
767
-
768
- ReactNativeBlobUtilStream streamInstance{ stream, encodingOption };
769
- m_streamMap.try_emplace(streamId, streamInstance);
770
-
771
- // Return success as JSValueArray
772
- ::React::JSValueArray resultArray;
773
- resultArray.push_back(""); // no error
774
- resultArray.push_back(""); // no message
775
- resultArray.push_back(streamId);
776
- callback(std::move(resultArray));
777
- }
778
- catch (const winrt::hresult_error& ex)
779
- {
780
- ::React::JSValueArray errorArray;
781
- errorArray.push_back("EUNSPECIFIED");
782
- errorArray.push_back("Failed to create write stream at path '" + path + "'; " + winrt::to_string(ex.message()));
783
- errorArray.push_back("");
784
- callback(std::move(errorArray));
785
- }
786
- }
787
-
788
- void ReactNativeBlobUtil::writeArrayChunk(
789
- std::string streamId,
790
- ::React::JSValueArray&& dataArray,
791
- std::function<void(::React::JSValueArray const&)> const& callback) noexcept
792
- {
793
- try
794
- {
795
- auto streamIt = m_streamMap.find(streamId);
796
- if (streamIt == m_streamMap.end()) {
797
- ::React::JSValueArray errorArray;
798
- errorArray.push_back("EUNSPECIFIED");
799
- errorArray.push_back("Stream not found for id: " + streamId);
800
- callback(errorArray);
801
- return;
802
- }
803
- auto& stream = streamIt->second;
804
- std::vector<uint8_t> data;
805
- data.reserve(dataArray.size());
806
- for (auto& var : dataArray)
807
- {
808
- data.push_back(var.AsUInt8());
809
- }
810
- Streams::IBuffer buffer{ CryptographicBuffer::CreateFromByteArray(data) };
811
-
812
- stream.streamInstance.WriteAsync(buffer).get(); // Calls it synchronously
813
- ::React::JSValueArray resultArray;
814
- callback(resultArray); // Success: empty array
815
- }
816
- catch (const winrt::hresult_error& ex)
817
- {
818
- ::React::JSValueArray errorArray;
819
- errorArray.push_back("EUNSPECIFIED");
820
- errorArray.push_back(winrt::to_string(ex.message()));
821
- callback(errorArray);
822
- }
823
- }
824
-
825
- void ReactNativeBlobUtil::writeChunk(
826
- std::string streamId,
827
- std::string data,
828
- std::function<void(::React::JSValueArray const&)> const& callback) noexcept
829
- {
830
- try
831
- {
832
- auto streamIt = m_streamMap.find(streamId);
833
- if (streamIt == m_streamMap.end()) {
834
- ::React::JSValueArray errorArray;
835
- errorArray.push_back("EUNSPECIFIED");
836
- errorArray.push_back("Stream not found for id: " + streamId);
837
- callback(errorArray);
838
- return;
839
- }
840
- auto& stream = streamIt->second;
841
- Streams::IBuffer buffer;
842
- if (stream.encoding == EncodingOptions::UTF8)
843
- {
844
- buffer = Cryptography::CryptographicBuffer::ConvertStringToBinary(
845
- winrt::to_hstring(data), BinaryStringEncoding::Utf8);
846
- }
847
- else if (stream.encoding == EncodingOptions::BASE64)
848
- {
849
- buffer = Cryptography::CryptographicBuffer::DecodeFromBase64String(winrt::to_hstring(data));
850
- }
851
- else
852
- {
853
- ::React::JSValueArray errorArray;
854
- errorArray.push_back("EUNSPECIFIED");
855
- errorArray.push_back("Invalid encoding type");
856
- callback(errorArray);
857
- return;
858
- }
859
- stream.streamInstance.WriteAsync(buffer).get(); // Synchronous write
860
- ::React::JSValueArray resultArray;
861
- callback(resultArray); // Success: empty array
862
- }
863
- catch (const winrt::hresult_error& ex)
864
- {
865
- ::React::JSValueArray errorArray;
866
- errorArray.push_back("EUNSPECIFIED");
867
- errorArray.push_back(winrt::to_string(ex.message()));
868
- callback(errorArray);
869
- }
870
- }
871
-
872
- void ReactNativeBlobUtil::closeStream(
873
- std::string streamId,
874
- std::function<void(::React::JSValueArray const&)> const& callback) noexcept
875
- {
876
- ::React::JSValueArray resultArray;
877
- try
878
- {
879
- auto it = m_streamMap.find(streamId);
880
- if (it != m_streamMap.end()) {
881
- it->second.streamInstance.Close();
882
- m_streamMap.erase(it);
883
- // Success: return empty array
884
- callback(resultArray);
885
- } else {
886
- // Stream not found
887
- resultArray.push_back("EUNSPECIFIED");
888
- resultArray.push_back("Stream not found for id: " + streamId);
889
- callback(resultArray);
890
- }
891
- }
892
- catch (const winrt::hresult_error& ex)
893
- {
894
- resultArray.push_back("EUNSPECIFIED");
895
- resultArray.push_back(winrt::to_string(ex.message()));
896
- callback(resultArray);
897
- }
898
- }
899
-
900
- winrt::fire_and_forget ReactNativeBlobUtil::unlink(
901
- std::string path,
902
- std::function<void(::React::JSValueArray)> callback) noexcept
903
- {
904
- try
905
- {
906
- if (std::filesystem::is_directory(path))
907
- {
908
- std::filesystem::path unlinkPath(path);
909
- unlinkPath.make_preferred();
910
- auto folderOp = winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(
911
- winrt::to_hstring(unlinkPath.c_str()));
912
- co_await folderOp.get().DeleteAsync();
913
- }
914
- else
915
- {
916
- winrt::hstring directoryPath, fileName;
917
- splitPath(path, directoryPath, fileName);
918
- auto folder = co_await winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(directoryPath);
919
- auto item = co_await folder.GetItemAsync(fileName);
920
- co_await item.DeleteAsync();
921
- }
922
-
923
- ::React::JSValueArray result;
924
- result.push_back(::React::JSValue(true));
925
- callback(std::move(result));
926
- }
927
- catch (const winrt::hresult_error& ex)
928
- {
929
- ::React::JSValueArray errorResult;
930
- errorResult.push_back(::React::JSValue(false));
931
- errorResult.push_back(::React::JSValue(winrt::to_string(ex.message())));
932
- callback(std::move(errorResult));
933
- }
934
-
935
- }
936
-
937
- winrt::fire_and_forget ReactNativeBlobUtil::removeSession(::React::JSValueArray paths, std::function<void(::React::JSValueArray)> callback) noexcept
938
- {
939
- try
940
- {
941
- for (const auto& pathValue : paths)
942
- {
943
- if (pathValue)
944
- {
945
- std::string path = pathValue.AsString();
946
- auto folder = co_await winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(winrt::to_hstring(path));
947
- auto file = co_await folder.GetFileAsync(winrt::to_hstring(path));
948
- co_await file.DeleteAsync();
949
- }
950
- }
951
-
952
- ::React::JSValueArray resultArray;
953
- resultArray.push_back("SUCCESS");
954
- callback(std::move(resultArray));
955
- }
956
- catch (const winrt::hresult_error& ex)
957
- {
958
- ::React::JSValueArray errorArray;
959
- errorArray.push_back("ERROR");
960
- errorArray.push_back(winrt::to_string(ex.message()));
961
- callback(std::move(errorArray));
962
- }
963
- catch (...)
964
- {
965
- ::React::JSValueArray errorArray;
966
- errorArray.push_back("ERROR");
967
- errorArray.push_back("Unknown error in removeSession");
968
- callback(std::move(errorArray));
969
- }
970
- }
971
-
972
- winrt::fire_and_forget ReactNativeBlobUtil::ls(
973
- std::string path,
974
- ::React::ReactPromise<::React::JSValueArray> promise) noexcept
975
- {
976
- try
977
- {
978
- winrt::hstring directoryPath, fileName;
979
- splitPath(path, directoryPath, fileName);
980
-
981
- auto folder = co_await Windows::Storage::StorageFolder::GetFolderFromPathAsync(directoryPath);
982
- auto items = co_await folder.GetItemsAsync();
983
-
984
- ::React::JSValueArray results;
985
- for (const auto& item : items)
986
- {
987
- results.push_back(::React::JSValue{ winrt::to_string(item.Name()) });
988
- }
989
-
990
- promise.Resolve(results);
991
- }
992
- catch (const winrt::hresult_error& ex)
993
- {
994
- hresult result = ex.code();
995
- if (result == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))
996
- {
997
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "ENOTDIR", "Not a directory '" + path + "'" });
998
- }
999
- else
1000
- {
1001
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EUNSPECIFIED", winrt::to_string(ex.message()) });
1002
- }
1003
- }
1004
- }
1005
-
1006
- winrt::fire_and_forget ReactNativeBlobUtil::stat(
1007
- std::string path,
1008
- std::function<void(::React::JSValueArray)> callback) noexcept
1009
- {
1010
- try
1011
- {
1012
- std::filesystem::path givenPath(path);
1013
- givenPath.make_preferred();
1014
- bool isDirectory{ std::filesystem::is_directory(path) };
1015
-
1016
- //std::string resultPath{ winrt::to_string(givenPath.c_str()) };
1017
- auto resultPath{ winrt::to_hstring(givenPath.c_str()) };
1018
-
1019
- // Try to open as folder
1020
- IStorageItem item;
1021
- if (isDirectory) {
1022
- item = co_await StorageFolder::GetFolderFromPathAsync(resultPath);
1023
- }
1024
- else {
1025
- item = co_await StorageFile::GetFileFromPathAsync(resultPath);
1026
- }
1027
- auto properties{ co_await item.GetBasicPropertiesAsync() };
1028
- winrt::Microsoft::ReactNative::JSValueObject fileInfo;
1029
- fileInfo["size"] = properties.Size();
1030
- fileInfo["filename"] = givenPath.filename().string();
1031
- fileInfo["path"] = givenPath.string();
1032
- fileInfo["lastModified"] = winrt::clock::to_time_t(properties.DateModified());;
1033
- fileInfo["type"] = isDirectory ? "directory" : "file";
1034
-
1035
- ::React::JSValueArray result;
1036
- result.push_back(std::move(fileInfo));
1037
- callback(std::move(result));
1038
- }
1039
- catch (const hresult_error& ex)
1040
- {
1041
- ::React::JSValueArray errorArray;
1042
- ::React::JSValueObject error;
1043
- error["error"] = winrt::to_string(ex.message());
1044
- errorArray.push_back(std::move(error));
1045
- callback(std::move(errorArray));
1046
- }
1047
- }
1048
-
1049
- winrt::fire_and_forget ReactNativeBlobUtil::lstat(
1050
- std::string path,
1051
- std::function<void(::React::JSValueArray)> callback) noexcept
1052
- {
1053
- try
1054
- {
1055
- std::filesystem::path directory(path);
1056
- directory.make_preferred();
1057
- StorageFolder targetDirectory{ co_await StorageFolder::GetFolderFromPathAsync(directory.c_str()) };
1058
-
1059
- winrt::Microsoft::ReactNative::JSValueArray resultsArray;
1060
-
1061
- auto items{ co_await targetDirectory.GetItemsAsync() };
1062
- for (auto item : items)
1063
- {
1064
- auto properties{ co_await item.GetBasicPropertiesAsync() };
1065
-
1066
- winrt::Microsoft::ReactNative::JSValueObject itemInfo;
1067
-
1068
- itemInfo["filename"] = to_string(item.Name());
1069
- itemInfo["path"] = to_string(item.Path());
1070
- itemInfo["size"] = properties.Size();
1071
- itemInfo["type"] = item.IsOfType(StorageItemTypes::Folder) ? "directory" : "file";
1072
- itemInfo["lastModified"] = properties.DateModified().time_since_epoch() / std::chrono::seconds(1) - UNIX_EPOCH_IN_WINRT_SECONDS;
1073
-
1074
- resultsArray.push_back(std::move(itemInfo));
1075
- }
1076
-
1077
- callback(std::move(resultsArray));
1078
- }
1079
- catch (...)
1080
- {
1081
- // "Failed to read directory."
1082
- winrt::Microsoft::ReactNative::JSValueArray emptyArray;
1083
- callback(std::move(emptyArray));
1084
- }
1085
- }
1086
-
1087
- winrt::fire_and_forget ReactNativeBlobUtil::cp(
1088
- std::string src,
1089
- std::string dest,
1090
- std::function<void(::React::JSValueArray)> callback) noexcept
1091
- {
1092
- try
1093
- {
1094
- winrt::hstring srcDirectoryPath, srcFileName;
1095
- splitPath(src, srcDirectoryPath, srcFileName);
1096
-
1097
- winrt::hstring destDirectoryPath, destFileName;
1098
- splitPath(dest, destDirectoryPath, destFileName);
1099
-
1100
- StorageFolder srcFolder = co_await StorageFolder::GetFolderFromPathAsync(srcDirectoryPath);
1101
- StorageFolder destFolder = co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath);
1102
-
1103
- StorageFile file = co_await srcFolder.GetFileAsync(srcFileName);
1104
- co_await file.CopyAsync(destFolder, destFileName, NameCollisionOption::FailIfExists);
1105
-
1106
- ::React::JSValueArray success{};
1107
- callback(std::move(success));
1108
- }
1109
- catch (const winrt::hresult_error& ex)
1110
- {
1111
- ::React::JSValueArray error{ winrt::to_string(ex.message()) };
1112
- callback(std::move(error));
1113
- }
1114
- }
1115
-
1116
- winrt::fire_and_forget ReactNativeBlobUtil::mv(
1117
- std::string src,
1118
- std::string dest,
1119
- std::function<void(::React::JSValueArray)> callback) noexcept
1120
- {
1121
- try
1122
- {
1123
- winrt::hstring srcDirectoryPath, srcFileName;
1124
- splitPath(src, srcDirectoryPath, srcFileName);
1125
-
1126
- winrt::hstring destDirectoryPath, destFileName;
1127
- splitPath(dest, destDirectoryPath, destFileName);
1128
-
1129
- StorageFolder srcFolder = co_await StorageFolder::GetFolderFromPathAsync(srcDirectoryPath);
1130
- StorageFolder destFolder = co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath);
1131
-
1132
- StorageFile file = co_await srcFolder.GetFileAsync(srcFileName);
1133
- co_await file.MoveAsync(destFolder, destFileName, NameCollisionOption::ReplaceExisting);
1134
-
1135
- ::React::JSValueArray success{}; // Success: empty array
1136
- callback(std::move(success));
1137
- }
1138
- catch (const winrt::hresult_error& ex)
1139
- {
1140
- ::React::JSValueArray error{ winrt::to_string(ex.message()) };
1141
- callback(std::move(error));
1142
- }
1143
- }
1144
-
1145
- void ReactNativeBlobUtil::mkdir(
1146
- std::string path,
1147
- ::React::ReactPromise<bool>&& promise) noexcept
1148
- {
1149
- try
1150
- {
1151
- std::filesystem::path dirPath(path);
1152
- dirPath.make_preferred();
1153
-
1154
- // Consistent with Apple's createDirectoryAtPath method and result, but not with Android's
1155
- std::error_code ec;
1156
- bool created = std::filesystem::create_directories(dirPath, ec);
1157
- if (!created && !std::filesystem::exists(dirPath))
1158
- {
1159
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + path });
1160
- }
1161
- else
1162
- {
1163
- promise.Resolve(true);
1164
- }
1165
- }
1166
- catch (const hresult_error& ex)
1167
- {
1168
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EUNSPECIFIED", "Error creating folder " + path + ", error: " + winrt::to_string(ex.message()) });
1169
- }
1170
- }
1171
-
1172
- winrt::fire_and_forget ReactNativeBlobUtil::readFile(
1173
- std::string path,
1174
- std::string encoding,
1175
- bool transformFile,
1176
- ::React::ReactPromise<::React::JSValueArray> promise) noexcept
1177
- {
1178
- try
1179
- {
1180
- winrt::hstring directoryPath, fileName;
1181
- splitPath(path, directoryPath, fileName);
1182
-
1183
- auto folder = co_await StorageFolder::GetFolderFromPathAsync(directoryPath);
1184
- auto file = co_await folder.GetFileAsync(fileName);
1185
- auto buffer = co_await FileIO::ReadBufferAsync(file);
1186
-
1187
- ::React::JSValueArray resultArray;
1188
- if (encoding == "base64")
1189
- {
1190
- std::string base64Content = winrt::to_string(Cryptography::CryptographicBuffer::EncodeToBase64String(buffer));
1191
- resultArray.push_back(base64Content);
1192
- }
1193
- else
1194
- {
1195
- std::string utf8Content = winrt::to_string(Cryptography::CryptographicBuffer::ConvertBinaryToString(BinaryStringEncoding::Utf8, buffer));
1196
- if (encoding == "ascii")
1197
- {
1198
- // For ASCII, just return the utf8Content as a string
1199
- resultArray.push_back(utf8Content);
1200
- }
1201
- else
1202
- {
1203
- resultArray.push_back(utf8Content);
1204
- }
1205
- }
1206
- promise.Resolve(resultArray);
1207
- }
1208
- catch (const winrt::hresult_error& ex)
1209
- {
1210
- hresult result{ ex.code() };
1211
- if (result == 0x80070002) // FileNotFoundException
1212
- {
1213
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + path });
1214
- }
1215
- else if (result == 0x80070005) // UnauthorizedAccessException
1216
- {
1217
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EISDIR", "EISDIR: illegal operation on a directory, read" });
1218
- }
1219
- else
1220
- {
1221
- // "Failed to read file."
1222
- promise.Reject(winrt::to_string(ex.message()).c_str());
1223
- }
1224
- }
1225
- }
1226
-
1227
- winrt::fire_and_forget ReactNativeBlobUtil::hash(
1228
- std::string path,
1229
- std::string algorithm,
1230
- ::React::ReactPromise<std::string> promise) noexcept
1231
- {
1232
- try
1233
- {
1234
- // Note: SHA224 is not part of winrt
1235
- if (algorithm.compare("sha224") == 0)
1236
- {
1237
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "Error", "WinRT does not offer sha224 encryption." });
1238
- co_return;
1239
- }
1240
-
1241
- winrt::hstring directoryPath, fileName;
1242
- splitPath(path, directoryPath, fileName);
1243
-
1244
- StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(directoryPath) };
1245
- StorageFile file{ co_await folder.GetFileAsync(fileName) };
1246
-
1247
- auto search{ availableHashes.find(algorithm) };
1248
- if (search == availableHashes.end())
1249
- {
1250
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "Error", "Invalid hash algorithm " + algorithm });
1251
- co_return;
1252
- }
1253
-
1254
- CryptographyCore::HashAlgorithmProvider provider{ search->second() };
1255
- Streams::IBuffer buffer{ co_await FileIO::ReadBufferAsync(file) };
1256
-
1257
- auto hashedBuffer{ provider.HashData(buffer) };
1258
- std::wstring result{ Cryptography::CryptographicBuffer::EncodeToHexString(hashedBuffer) };
1259
- std::string sResult = winrt::to_string(result);
1260
- promise.Resolve(sResult);
1261
- }
1262
- catch (const hresult_error& ex)
1263
- {
1264
- hresult result{ ex.code() };
1265
- if (result == 0x80070002) // FileNotFoundException
1266
- {
1267
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + path });
1268
- }
1269
- else if (result == 0x80070005) // UnauthorizedAccessException
1270
- {
1271
- promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EISDIR", "EISDIR: illegal operation on a directory, read" });
1272
- }
1273
- else
1274
- {
1275
- // "Failed to get checksum from file."
1276
- promise.Reject(winrt::to_string(ex.message()).c_str());
1277
- }
1278
- }
1279
- }
1280
-
1281
- winrt::fire_and_forget ReactNativeBlobUtil::readStream(
1282
- std::string path,
1283
- std::string encoding,
1284
- double bufferSize,
1285
- double tick,
1286
- std::string streamId) noexcept
1287
- {
1288
- try
1289
- {
1290
- EncodingOptions usedEncoding;
1291
- if (encoding == "utf8")
1292
- {
1293
- usedEncoding = EncodingOptions::UTF8;
1294
- }
1295
- else if (encoding == "base64")
1296
- {
1297
- usedEncoding = EncodingOptions::BASE64;
1298
- }
1299
- else if (encoding == "ascii")
1300
- {
1301
- usedEncoding = EncodingOptions::ASCII;
1302
- }
1303
- else
1304
- {
1305
- // Invalid encoding
1306
- m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", streamId,
1307
- winrt::Microsoft::ReactNative::JSValueObject{
1308
- {"event", "error"},
1309
- {"EINVAL", "Unsupported encoding: " + encoding}
1310
- });
1311
- co_return;
1312
- }
1313
-
1314
- uint32_t chunkSize = (usedEncoding == EncodingOptions::BASE64) ? 4095 : 4096;
1315
- if (bufferSize > 0)
1316
- {
1317
- chunkSize = static_cast<uint32_t>(bufferSize);
1318
- }
1319
-
1320
- winrt::hstring directoryPath, fileName;
1321
- splitPath(path, directoryPath, fileName);
1322
-
1323
- StorageFolder folder = co_await StorageFolder::GetFolderFromPathAsync(directoryPath);
1324
- StorageFile file = co_await folder.GetFileAsync(fileName);
1325
- Streams::IRandomAccessStream stream = co_await file.OpenAsync(FileAccessMode::Read);
1326
-
1327
- Buffer buffer{ chunkSize };
1328
-
1329
- for (;;)
1330
- {
1331
- auto readBuffer = co_await stream.ReadAsync(buffer, buffer.Capacity(), InputStreamOptions::None);
1332
- if (readBuffer.Length() == 0)
1333
- {
1334
- m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", streamId,
1335
- winrt::Microsoft::ReactNative::JSValueObject{
1336
- {"event", "end"}
1337
- });
1338
- break;
1339
- }
1340
-
1341
- if (usedEncoding == EncodingOptions::BASE64)
1342
- {
1343
- winrt::hstring base64Content = Cryptography::CryptographicBuffer::EncodeToBase64String(readBuffer);
1344
- m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", streamId,
1345
- winrt::Microsoft::ReactNative::JSValueObject{
1346
- {"event", "data"},
1347
- {"detail", winrt::to_string(base64Content)}
1348
- });
1349
- }
1350
- else
1351
- {
1352
- winrt::hstring stringContent = Cryptography::CryptographicBuffer::ConvertBinaryToString(
1353
- BinaryStringEncoding::Utf8, readBuffer);
1354
- std::string utf8Content = winrt::to_string(stringContent);
1355
-
1356
- // For ASCII, trim to 7-bit range
1357
- if (usedEncoding == EncodingOptions::ASCII)
1358
- {
1359
- for (char& c : utf8Content)
1360
- {
1361
- c &= 0x7F;
1362
- }
1363
- }
1364
-
1365
- m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", streamId,
1366
- winrt::Microsoft::ReactNative::JSValueObject{
1367
- {"event", "data"},
1368
- {"detail", utf8Content}
1369
- });
1370
- }
1371
-
1372
- if (tick > 0)
1373
- {
1374
- std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int64_t>(tick)));
1375
- }
1376
- }
1377
- }
1378
- catch (const hresult_error& ex)
1379
- {
1380
- hresult result = ex.code();
1381
- if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) // 0x80070002
1382
- {
1383
- m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", streamId,
1384
- winrt::Microsoft::ReactNative::JSValueObject{
1385
- {"event", "error"},
1386
- {"ENOENT", "No such file: " + path}
1387
- });
1388
- }
1389
- else
1390
- {
1391
- m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", streamId,
1392
- winrt::Microsoft::ReactNative::JSValueObject{
1393
- {"event", "error"},
1394
- {"EUNSPECIFIED", winrt::to_string(ex.message())}
1395
- });
1396
- }
1397
- }
1398
- }
1399
-
1400
- void ReactNativeBlobUtil::getEnvironmentDirs(
1401
- std::function<void(::React::JSValueArray const&)> const& callback) noexcept
1402
- {
1403
- callback(::React::JSValueArray{});
1404
- }
1405
-
1406
- void ReactNativeBlobUtil::cancelRequest(
1407
- std::string taskId,
1408
- std::function<void(::React::JSValueArray const&)> const& callback) noexcept
1409
- {
1410
- ::React::JSValueArray resultArray;
1411
- try
1412
- {
1413
- m_tasks.Cancel(taskId);
1414
- // Success: return empty array
1415
- callback(resultArray);
1416
- }
1417
- catch (const winrt::hresult_error& ex)
1418
- {
1419
- resultArray.push_back("EUNSPECIFIED");
1420
- resultArray.push_back(winrt::to_string(ex.message()));
1421
- callback(resultArray);
1422
- }
1423
- }
1424
-
1425
- void ReactNativeBlobUtil::enableProgressReport(
1426
- std::string taskId,
1427
- double interval,
1428
- double count) noexcept
1429
- {
1430
- ReactNativeBlobUtilProgressConfig config{ count, interval };
1431
- std::scoped_lock lock{ m_mutex };
1432
- downloadProgressMap.try_emplace(taskId, config);
1433
- }
1434
-
1435
- void ReactNativeBlobUtil::enableUploadProgressReport(
1436
- std::string taskId,
1437
- double interval,
1438
- double count) noexcept
1439
- {
1440
- ReactNativeBlobUtilProgressConfig config{ count, interval };
1441
- std::scoped_lock lock{ m_mutex };
1442
- uploadProgressMap.try_emplace(taskId, config);
1443
- }
1444
-
1445
- winrt::fire_and_forget ReactNativeBlobUtil::slice(
1446
- std::string src,
1447
- std::string dest,
1448
- double start,
1449
- double end,
1450
- ::React::ReactPromise<std::string> promise) noexcept
1451
- {
1452
- try
1453
- {
1454
- winrt::hstring srcDirectoryPath, srcFileName, destDirectoryPath, destFileName;
1455
- splitPath(src, srcDirectoryPath, srcFileName);
1456
- splitPath(src, destDirectoryPath, destFileName);
1457
-
1458
- StorageFolder srcFolder{ co_await StorageFolder::GetFolderFromPathAsync(srcDirectoryPath) };
1459
- StorageFolder destFolder{ co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath) };
1460
-
1461
- StorageFile srcFile{ co_await srcFolder.GetFileAsync(srcFileName) };
1462
- StorageFile destFile{ co_await destFolder.CreateFileAsync(destFileName, CreationCollisionOption::OpenIfExists) };
1463
-
1464
- uint64_t uStart = static_cast<uint64_t>(start);
1465
- uint64_t uEnd = static_cast<uint64_t>(end);
1466
- uint32_t length = static_cast<uint32_t>(uEnd > uStart ? uEnd - uStart : 0);
1467
-
1468
- if (length == 0) {
1469
- promise.Reject("Invalid slice range");
1470
- co_return;
1471
- }
1472
- Streams::IBuffer buffer;
1473
- Streams::IRandomAccessStream stream{ co_await srcFile.OpenAsync(FileAccessMode::Read) };
1474
- stream.Seek(start);
1475
- stream.ReadAsync(buffer, length, Streams::InputStreamOptions::None);
1476
- co_await FileIO::WriteBufferAsync(destFile, buffer);
1477
-
1478
- promise.Resolve(dest);
1479
- }
1480
- catch (...)
1481
- {
1482
- promise.Reject("Unable to slice file");
1483
- }
1484
- }
1485
-
1486
- IAsyncAction setTimeout(std::chrono::seconds time) {
1487
- co_await time;
1488
- }
1489
-
1490
- void ReactNativeBlobUtil::presentOptionsMenu(
1491
- std::string uri,
1492
- std::string scheme,
1493
- ::React::ReactPromise<::React::JSValueArray>&& result) noexcept
1494
- {
1495
- result.Resolve(::React::JSValueArray{});
1496
- }
1497
-
1498
- void ReactNativeBlobUtil::presentOpenInMenu(
1499
- std::string uri,
1500
- std::string scheme,
1501
- ::React::ReactPromise<::React::JSValueArray>&& result) noexcept
1502
- {
1503
- result.Resolve(::React::JSValueArray{});
1504
- }
1505
-
1506
- void ReactNativeBlobUtil::presentPreview(
1507
- std::string uri,
1508
- std::string scheme,
1509
- ::React::ReactPromise<::React::JSValueArray>&& result) noexcept
1510
- {
1511
- result.Resolve(::React::JSValueArray{});
1512
- }
1513
-
1514
- void ReactNativeBlobUtil::excludeFromBackupKey(
1515
- std::string url,
1516
- ::React::ReactPromise<::React::JSValueArray>&& result) noexcept
1517
- {
1518
- result.Resolve(::React::JSValueArray{});
1519
- }
1520
-
1521
- winrt::fire_and_forget ReactNativeBlobUtil::df(
1522
- std::function<void(::React::JSValueArray)> callback) noexcept
1523
- {
1524
- try
1525
- {
1526
- auto localFolder = winrt::Windows::Storage::ApplicationData::Current().LocalFolder();
1527
- auto properties{ co_await localFolder.Properties().RetrievePropertiesAsync({L"System.FreeSpace", L"System.Capacity"}) };
1528
-
1529
- winrt::Microsoft::ReactNative::JSValueObject result;
1530
- result["free"] = winrt::unbox_value<uint64_t>(properties.Lookup(L"System.FreeSpace"));
1531
- result["total"] = winrt::unbox_value<uint64_t>(properties.Lookup(L"System.Capacity"));
1532
-
1533
- ::React::JSValueArray arr;
1534
- arr.push_back(::React::JSValueObject(std::move(result)));
1535
- callback(std::move(arr));
1536
- }
1537
- catch (...)
1538
- {
1539
- ::React::JSValueArray arr;
1540
- winrt::Microsoft::ReactNative::JSValueObject error;
1541
- error["error"] = "Failed to get storage usage.";
1542
- arr.push_back(::React::JSValueObject(std::move(error)));
1543
- callback(std::move(arr));
1544
- }
1545
-
1546
- }
1547
-
1548
- void ReactNativeBlobUtil::emitExpiredEvent(
1549
- std::function<void(std::string)> const& callback) noexcept
1550
- {
1551
- callback("");
1552
- }
1553
-
1554
- void ReactNativeBlobUtil::actionViewIntent(
1555
- std::string path,
1556
- std::string mime,
1557
- std::string chooserTitle,
1558
- ::React::ReactPromise<void>&& result) noexcept
1559
- {
1560
- // No-op: Android API
1561
- result.Resolve();
1562
- }
1563
-
1564
- void ReactNativeBlobUtil::addCompleteDownload(
1565
- ::React::JSValue&& config,
1566
- ::React::ReactPromise<void>&& result) noexcept
1567
- {
1568
- // No-op: Android API
1569
- result.Resolve();
1570
- }
1571
-
1572
- void ReactNativeBlobUtil::copyToInternal(
1573
- std::string contentUri,
1574
- std::string destpath,
1575
- ::React::ReactPromise<std::string>&& result) noexcept
1576
- {
1577
- // No-op: Android API
1578
- result.Resolve("");
1579
- }
1580
-
1581
- void ReactNativeBlobUtil::copyToMediaStore(
1582
- ::React::JSValue&& filedata,
1583
- std::string mt,
1584
- std::string path,
1585
- ::React::ReactPromise<std::string>&& result) noexcept
1586
- {
1587
- // No-op: Android API
1588
- result.Resolve("");
1589
- }
1590
-
1591
- void ReactNativeBlobUtil::createMediaFile(
1592
- ::React::JSValue&& filedata,
1593
- std::string mt,
1594
- ::React::ReactPromise<std::string>&& result) noexcept
1595
- {
1596
- // No-op: Android API
1597
- result.Resolve("");
1598
- }
1599
-
1600
- void ReactNativeBlobUtil::getBlob(
1601
- std::string contentUri,
1602
- std::string encoding,
1603
- ::React::ReactPromise<::React::JSValueArray>&& result) noexcept
1604
- {
1605
- // No-op: Android API
1606
- result.Resolve(::React::JSValueArray{});
1607
- }
1608
-
1609
- void ReactNativeBlobUtil::getContentIntent(
1610
- std::string mime,
1611
- ::React::ReactPromise<std::string>&& result) noexcept
1612
- {
1613
- // No-op: Android API
1614
- result.Resolve("");
1615
- }
1616
-
1617
- void ReactNativeBlobUtil::getSDCardDir(
1618
- ::React::ReactPromise<std::string>&& result) noexcept
1619
- {
1620
- // No-op: Android API
1621
- result.Resolve("");
1622
- }
1623
-
1624
- void ReactNativeBlobUtil::getSDCardApplicationDir(
1625
- ::React::ReactPromise<std::string>&& result) noexcept
1626
- {
1627
- // No-op: Android API
1628
- result.Resolve("");
1629
- }
1630
-
1631
- void ReactNativeBlobUtil::scanFile(
1632
- ::React::JSValueArray&& pairs,
1633
- std::function<void(::React::JSValueArray const&)> const& callback) noexcept
1634
- {
1635
- // No-op: Android API
1636
- callback(::React::JSValueArray{});
1637
- }
1638
-
1639
- void ReactNativeBlobUtil::writeToMediaFile(
1640
- std::string fileUri,
1641
- std::string path,
1642
- bool transformFile,
1643
- ::React::ReactPromise<std::string>&& result) noexcept
1644
- {
1645
- // No-op: Android API
1646
- result.Resolve("");
1647
- }
1648
-
1649
- void ReactNativeBlobUtil::addListener(
1650
- std::string eventName) noexcept
1651
- {
1652
- // No-op
1653
- }
1654
-
1655
- void ReactNativeBlobUtil::removeListeners(
1656
- double count) noexcept
1657
- {
1658
- // No-op
1659
- }
1660
-
1661
- void ReactNativeBlobUtil::splitPath(const std::string& fullPath, winrt::hstring& directoryPath, winrt::hstring& fileName) noexcept
1662
- {
1663
- std::filesystem::path path{ fullPath };
1664
- path.make_preferred();
1665
-
1666
- directoryPath = path.has_parent_path() ? winrt::to_hstring(path.parent_path().c_str()) : L"";
1667
- fileName = path.has_filename() ? winrt::to_hstring(path.filename().c_str()) : L"";
1668
- }
1669
-
1670
- void ReactNativeBlobUtil::splitPath(const std::wstring& fullPath, winrt::hstring& directoryPath, winrt::hstring& fileName) noexcept
1671
- {
1672
- std::filesystem::path path{ fullPath };
1673
- path.make_preferred();
1674
-
1675
- directoryPath = path.has_parent_path() ? winrt::to_hstring(path.parent_path().c_str()) : L"";
1676
- fileName = path.has_filename() ? winrt::to_hstring(path.filename().c_str()) : L"";
1677
- }
1678
-
1679
- winrt::Windows::Foundation::IAsyncAction ReactNativeBlobUtil::ProcessRequestAsync(
1680
- const std::string& taskId,
1681
- const winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter& filter,
1682
- winrt::Windows::Web::Http::HttpRequestMessage& httpRequestMessage,
1683
- ReactNativeBlobUtilConfig& config,
1684
- std::function<void(std::string, std::string, std::string)> callback,
1685
- std::string& error) noexcept
1686
- try
1687
- {
1688
- winrt::Windows::Web::Http::HttpClient httpClient{filter};
1689
-
1690
- winrt::Windows::Web::Http::HttpResponseMessage response{ co_await httpClient.SendRequestAsync(httpRequestMessage, winrt::Windows::Web::Http::HttpCompletionOption::ResponseHeadersRead) };
1691
-
1692
- ReactNativeBlobUtilState eventState;
1693
-
1694
- auto status{ static_cast<int>(response.StatusCode()) };
1695
- if (config.followRedirect) {
1696
- while (status >= 300 && status < 400) {
1697
- auto redirect{ response.Headers().Location().ToString() };
1698
- eventState.redirects.push_back(Microsoft::ReactNative::JSValue(winrt::to_string(redirect)));
1699
- httpRequestMessage.RequestUri(Uri{ redirect });
1700
- response = co_await httpClient.SendRequestAsync(httpRequestMessage, winrt::Windows::Web::Http::HttpCompletionOption::ResponseHeadersRead);
1701
- status = static_cast<int>(response.StatusCode());
1702
- }
1703
- }
1704
-
1705
- eventState.status = static_cast<int>(response.StatusCode());
1706
-
1707
- for (const auto header : response.Content().Headers().GetView()) {
1708
- eventState.headers[winrt::to_string(header.Key())] = winrt::to_string(header.Value());
1709
- }
1710
-
1711
- if (response.Content().Headers().ContentType() != nullptr) {
1712
- eventState.respType = winrt::to_string(response.Content().Headers().ContentType().ToString());
1713
- }
1714
-
1715
- eventState.state = winrt::to_string(response.ReasonPhrase());
1716
-
1717
- m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", L"ReactNativeBlobUtilState",
1718
- Microsoft::ReactNative::JSValueObject{
1719
- { "taskId", taskId },
1720
- { "state", eventState.state },
1721
- { "headers", std::move(eventState.headers) },
1722
- { "redirects", std::move(eventState.redirects) },
1723
- { "respType", eventState.respType },
1724
- { "status", eventState.status },
1725
- { "timeout", false },
1726
- });
1727
-
1728
- IReference<uint64_t> contentLength{ response.Content().Headers().ContentLength() };
1729
-
1730
- IOutputStream outputStream;
1731
- bool writeToFile{ config.fileCache || !config.path.empty() };
1732
-
1733
- if (writeToFile)
1734
- {
1735
- if (config.path.empty())
1736
- {
1737
- config.path = winrt::to_string(ApplicationData::Current().TemporaryFolder().Path()) + "\\ReactNativeBlobUtilTmp_" + taskId;
1738
- if (config.appendExt.length() > 0)
1739
- {
1740
- config.path += "." + config.appendExt;
1741
- }
1742
- }
1743
- std::filesystem::path path{ config.path };
1744
- StorageFolder storageFolder{ co_await StorageFolder::GetFolderFromPathAsync( path.parent_path().wstring()) };
1745
- StorageFile storageFile{ co_await storageFolder.CreateFileAsync(path.filename().wstring(), CreationCollisionOption::FailIfExists) };
1746
- IRandomAccessStream stream{ co_await storageFile.OpenAsync(FileAccessMode::ReadWrite) };
1747
- outputStream = stream.GetOutputStreamAt(0) ;
1748
- }
1749
-
1750
- auto contentStream{ co_await response.Content().ReadAsInputStreamAsync() };
1751
- Buffer buffer{ 10 * 1024 };
1752
- uint64_t read{ 0 };
1753
- uint64_t totalRead{ 0 };
1754
-
1755
- ReactNativeBlobUtilProgressConfig progressInfo;
1756
- uint64_t progressInterval{ 0 };
1757
-
1758
- std::stringstream chunkStream;
1759
- std::stringstream resultOutput;
1760
-
1761
- std::string readContents{""};
1762
-
1763
- auto exists{ downloadProgressMap.find(taskId) };
1764
- if (exists != downloadProgressMap.end() && contentLength.Type() == PropertyType::UInt64) {
1765
- progressInfo = downloadProgressMap[taskId];
1766
-
1767
- if (progressInfo.count > -1) {
1768
- progressInterval = contentLength.Value() / 100 * progressInfo.count;
1769
- }
1770
- }
1771
-
1772
- int64_t initialProgressTime{ winrt::clock::now().time_since_epoch().count() / 10000 };
1773
- int64_t currentProgressTime;
1774
-
1775
- for (;;)
1776
- {
1777
- buffer.Length(0);
1778
- auto readBuffer{ co_await contentStream.ReadAsync(buffer, buffer.Capacity(), InputStreamOptions::None) };
1779
-
1780
- read += readBuffer.Length();
1781
- totalRead += read;
1782
-
1783
- if (readBuffer.Length() == 0)
1784
- {
1785
- break;
1786
- }
1787
-
1788
- readContents = winrt::to_string(CryptographicBuffer::EncodeToBase64String(readBuffer));
1789
-
1790
- if (writeToFile) {
1791
- co_await outputStream.WriteAsync(readBuffer);
1792
- }
1793
- else {
1794
- resultOutput << readContents;
1795
- }
1796
-
1797
- if (progressInfo.count > -1 || progressInfo.interval > -1) {
1798
- chunkStream << readContents;
1799
-
1800
- currentProgressTime = winrt::clock::now().time_since_epoch().count() / 10000;
1801
- if ((currentProgressTime - initialProgressTime >= progressInfo.interval && progressInfo.interval > -1) ||
1802
- (totalRead >= progressInterval && progressInfo.count > -1)) {
1803
- m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", L"ReactNativeBlobUtilProgress",
1804
- Microsoft::ReactNative::JSValueObject{
1805
- { "taskId", taskId },
1806
- { "written", int64_t(totalRead) },
1807
- { "total", contentLength.Type() == PropertyType::UInt64 ?
1808
- Microsoft::ReactNative::JSValue(contentLength.Value()) :
1809
- Microsoft::ReactNative::JSValue{nullptr} },
1810
- { "chunk", chunkStream.str() },
1811
- });
1812
- chunkStream.clear();
1813
- initialProgressTime = winrt::clock::now().time_since_epoch().count() / 10000;
1814
- if (progressInfo.count > -1) {
1815
- read = 0;
1816
- }
1817
- }
1818
- }
1819
- }
1820
-
1821
- if (writeToFile) {
1822
- callback("", "path", config.path);
1823
- }
1824
- else {
1825
- callback("", "result", resultOutput.str());
1826
- }
1827
- }
1828
- catch (const hresult_error& ex)
1829
- {
1830
- error = winrt::to_string(ex.message().c_str());
1831
- //callback(winrt::to_string(ex.message().c_str()), "error", "");
1832
- }
1833
- catch (...) {
1834
- co_return;
1835
- }
1836
-
1
+ #include "pch.h"
2
+
3
+ #include "ReactNativeBlobUtil.h"
4
+ #include <winrt/Windows.ApplicationModel.Activation.h>
5
+ #include <winrt/Windows.Security.Cryptography.h>
6
+ #include <winrt/Windows.Security.Cryptography.Core.h>
7
+ #include <winrt/Windows.Storage.FileProperties.h>
8
+ #include <winrt/Windows.Storage.Streams.h>
9
+ #include <winrt/Windows.Storage.h>
10
+ #include <winrt/Windows.Web.Http.h>
11
+ #include <winrt/Windows.Web.Http.Headers.h>
12
+ #include <winrt/windows.web.http.filters.h>
13
+ #include <winrt/Windows.System.Threading.h>
14
+ #include <filesystem>
15
+ #include <sstream>
16
+
17
+ using namespace winrt;
18
+ using namespace winrt::Windows::ApplicationModel;
19
+ using namespace winrt::Windows::Storage;
20
+ using namespace winrt::Windows::Storage::Streams;
21
+ using namespace winrt::Windows::Foundation;
22
+ using namespace winrt::Windows::Security::Cryptography;
23
+ using namespace winrt::Windows::Security::Cryptography::Core;
24
+ using namespace std::chrono_literals;
25
+
26
+ CancellationDisposable::CancellationDisposable(IAsyncInfo const& async, std::function<void()>&& onCancel) noexcept
27
+ : m_async{ async }
28
+ , m_onCancel{ std::move(onCancel) }
29
+ {
30
+ }
31
+
32
+ CancellationDisposable::CancellationDisposable(CancellationDisposable&& other) noexcept
33
+ : m_async{ std::move(other.m_async) }
34
+ , m_onCancel{ std::move(other.m_onCancel) }
35
+ {
36
+ }
37
+
38
+ CancellationDisposable& CancellationDisposable::operator=(CancellationDisposable&& other) noexcept
39
+ {
40
+ if (this != &other)
41
+ {
42
+ CancellationDisposable temp{ std::move(*this) };
43
+ m_async = std::move(other.m_async);
44
+ m_onCancel = std::move(other.m_onCancel);
45
+ }
46
+ return *this;
47
+ }
48
+
49
+ CancellationDisposable::~CancellationDisposable() noexcept
50
+ {
51
+ Cancel();
52
+ }
53
+
54
+ void CancellationDisposable::Cancel() noexcept
55
+ {
56
+ if (m_async)
57
+ {
58
+ if (m_async.Status() == AsyncStatus::Started)
59
+ {
60
+ m_async.Cancel();
61
+ }
62
+
63
+ if (m_onCancel)
64
+ {
65
+ m_onCancel();
66
+ }
67
+ }
68
+ }
69
+
70
+ TaskCancellationManager::~TaskCancellationManager() noexcept
71
+ {
72
+ // Do the explicit cleaning to make sure that CancellationDisposable
73
+ // destructors run while this instance still has valid fields because
74
+ // they are used by the onCancel callback.
75
+ // We also want to clear the m_pendingTasks before running the
76
+ // CancellationDisposable destructors since they touch the m_pendingTasks.
77
+ std::map<TaskId, CancellationDisposable> pendingTasks;
78
+ {
79
+ std::scoped_lock lock{ m_mutex };
80
+ pendingTasks = std::move(m_pendingTasks);
81
+ }
82
+ }
83
+
84
+
85
+ IAsyncAction TaskCancellationManager::Add(TaskId taskId, IAsyncAction const& asyncAction) noexcept
86
+ {
87
+ std::scoped_lock lock{ m_mutex };
88
+ m_pendingTasks.try_emplace(taskId, asyncAction, [this, taskId]()
89
+ {
90
+ Cancel(taskId);
91
+ });
92
+ return asyncAction;
93
+ }
94
+
95
+ void TaskCancellationManager::Cancel(TaskId taskId) noexcept
96
+ {
97
+ // The destructor of the token does the cancellation. We must do it outside of lock.
98
+ CancellationDisposable token;
99
+
100
+ {
101
+ std::scoped_lock lock{ m_mutex };
102
+ if (!m_pendingTasks.empty())
103
+ {
104
+ if (auto it = m_pendingTasks.find(taskId); it != m_pendingTasks.end())
105
+ {
106
+ token = std::move(it->second);
107
+ m_pendingTasks.erase(it);
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ ReactNativeBlobUtilConfig::ReactNativeBlobUtilConfig(::React::JSValue& options)
114
+ {
115
+ auto getStringOrDefault = [](const winrt::Microsoft::ReactNative::JSValue& value, const std::string& defaultValue = "") -> std::string {
116
+ return value.IsNull() ? defaultValue : value.AsString();
117
+ };
118
+
119
+ auto getBoolOrDefault = [](const winrt::Microsoft::ReactNative::JSValue& value, bool defaultValue = false) -> bool {
120
+ return value.IsNull() ? defaultValue : value.AsBoolean();
121
+ };
122
+
123
+ auto getInt64OrDefault = [](const winrt::Microsoft::ReactNative::JSValue& value, int64_t defaultValue = 60000) -> int64_t {
124
+ return value.IsNull() ? defaultValue : value.AsInt64();
125
+ };
126
+
127
+ appendExt = getStringOrDefault(options["appendExt"]);
128
+ fileCache = getBoolOrDefault(options["fileCache"]);
129
+ followRedirect = getBoolOrDefault(options["followRedirect"]);
130
+ overwrite = getBoolOrDefault(options["overwrite"]);
131
+ trusty = getBoolOrDefault(options["trusty"]);
132
+
133
+ // Handle path sanitization
134
+ {
135
+ std::string filepath = getStringOrDefault(options["path"]);
136
+ if (!filepath.empty())
137
+ {
138
+ size_t fileLength = filepath.length();
139
+ bool hasTrailingSlash = filepath[fileLength - 1] == '\\' || filepath[fileLength - 1] == '/';
140
+ std::filesystem::path pathToParse = hasTrailingSlash ? filepath.substr(0, fileLength - 1) : filepath;
141
+ pathToParse.make_preferred();
142
+ path = pathToParse.string();
143
+ }
144
+ else
145
+ {
146
+ path = "";
147
+ }
148
+ }
149
+
150
+ // Timeout handling
151
+ int64_t potentialTimeout = getInt64OrDefault(options["timeout"]);
152
+ timeout = std::chrono::seconds{ potentialTimeout > 0 ? potentialTimeout : 60000 };
153
+ }
154
+
155
+ ReactNativeBlobUtilProgressConfig::ReactNativeBlobUtilProgressConfig(double count_, double interval_) : count(count_), interval(interval_) {
156
+ }
157
+
158
+ ReactNativeBlobUtilStream::ReactNativeBlobUtilStream(Streams::IRandomAccessStream& _streamInstance, EncodingOptions _encoding) noexcept
159
+ : streamInstance{ std::move(_streamInstance) }
160
+ , encoding{ _encoding }
161
+ {
162
+ }
163
+
164
+ namespace winrt::ReactNativeBlobUtil
165
+ {
166
+
167
+ // See https://microsoft.github.io/react-native-windows/docs/native-modules for details on writing native modules
168
+
169
+ void ReactNativeBlobUtil::Initialize(React::ReactContext const& reactContext) noexcept {
170
+ m_context = reactContext;
171
+ }
172
+
173
+ // Constants method
174
+ ReactNativeBlobUtilCodegen::BlobUtilsSpec_Constants ReactNativeBlobUtil::GetConstants() noexcept {
175
+ ReactNativeBlobUtilCodegen::BlobUtilsSpec_Constants constants;
176
+ constants.DocumentDir = to_string(ApplicationData::Current().LocalFolder().Path());
177
+ constants.CacheDir = to_string(ApplicationData::Current().LocalCacheFolder().Path());
178
+ constants.PictureDir = to_string(UserDataPaths::GetDefault().Pictures());
179
+ constants.MusicDir = to_string(UserDataPaths::GetDefault().Music());
180
+ constants.MovieDir = to_string(UserDataPaths::GetDefault().Videos());
181
+ constants.DownloadDir = to_string(UserDataPaths::GetDefault().Downloads());
182
+ constants.MainBundleDir = to_string(Package::Current().InstalledLocation().Path());
183
+ return constants;
184
+ }
185
+
186
+ winrt::fire_and_forget ReactNativeBlobUtil::fetchBlobForm(
187
+ ::React::JSValue options,
188
+ std::string taskId,
189
+ std::string method,
190
+ std::string url,
191
+ ::React::JSValue headers,
192
+ ::React::JSValueArray body,
193
+ std::function<void(::React::JSValueArray)> callback
194
+ ) noexcept
195
+ {
196
+ try
197
+ {
198
+ winrt::hstring boundary{ L"-----" };
199
+ winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter filter;
200
+ ReactNativeBlobUtilConfig config{ options };
201
+ filter.AllowAutoRedirect(false);
202
+
203
+ if (config.trusty)
204
+ {
205
+ filter.IgnorableServerCertificateErrors().Append(
206
+ winrt::Windows::Security::Cryptography::Certificates::ChainValidationResult::Untrusted);
207
+ }
208
+
209
+ winrt::Windows::Web::Http::HttpMethod httpMethod = winrt::Windows::Web::Http::HttpMethod::Post();
210
+ if (method == "DELETE" || method == "delete")
211
+ httpMethod = winrt::Windows::Web::Http::HttpMethod::Delete();
212
+ else if (method == "PUT" || method == "put")
213
+ httpMethod = winrt::Windows::Web::Http::HttpMethod::Put();
214
+ else if (method == "GET" || method == "get")
215
+ httpMethod = winrt::Windows::Web::Http::HttpMethod::Get();
216
+ else if (method != "POST" && method != "post")
217
+ {
218
+ ::React::JSValueArray errorArray;
219
+ errorArray.push_back("Method not supported");
220
+ callback(std::move(errorArray));
221
+ co_return;
222
+ }
223
+
224
+ winrt::Windows::Web::Http::HttpRequestMessage requestMessage{ httpMethod, winrt::Windows::Foundation::Uri{ winrt::to_hstring(url) } };
225
+ winrt::Windows::Web::Http::HttpMultipartFormDataContent requestContent{ boundary };
226
+
227
+ // Add headers
228
+ if (headers.ItemCount() > 0)
229
+ {
230
+ for (const auto& entry : headers.AsObject())
231
+ {
232
+ if (!requestMessage.Headers().TryAppendWithoutValidation(
233
+ winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString())))
234
+ {
235
+ requestContent.Headers().TryAppendWithoutValidation(
236
+ winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString()));
237
+ }
238
+ }
239
+ }
240
+
241
+ // Add form data
242
+ for (auto& entry : body)
243
+ {
244
+ auto& items = entry.AsObject();
245
+ auto data = items["data"].AsString();
246
+
247
+ // File upload support: expects "file://" prefix
248
+ bool isFile = data.rfind("file://", 0) == 0;
249
+ if (isFile)
250
+ {
251
+ std::string contentPath = data.substr(strlen("file://"));
252
+ winrt::hstring directoryPath, fileName;
253
+ splitPath(contentPath, directoryPath, fileName);
254
+ auto folder = co_await winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(directoryPath);
255
+ auto storageFile = co_await folder.GetFileAsync(fileName);
256
+ auto requestBuffer = co_await winrt::Windows::Storage::FileIO::ReadBufferAsync(storageFile);
257
+
258
+ winrt::Windows::Web::Http::HttpBufferContent requestBufferContent{ requestBuffer };
259
+ if (!items["type"].IsNull())
260
+ {
261
+ requestBufferContent.Headers().TryAppendWithoutValidation(
262
+ L"content-type", winrt::to_hstring(items["type"].AsString()));
263
+ }
264
+
265
+ auto name = items["name"].IsNull() ? L"" : winrt::to_hstring(items["name"].AsString());
266
+ auto filename = items["filename"].IsNull() ? L"" : winrt::to_hstring(items["filename"].AsString());
267
+ if (name.empty())
268
+ {
269
+ requestContent.Add(requestBufferContent);
270
+ }
271
+ else if (filename.empty())
272
+ {
273
+ requestContent.Add(requestBufferContent, name);
274
+ }
275
+ else
276
+ {
277
+ requestContent.Add(requestBufferContent, name, filename);
278
+ }
279
+ }
280
+ else
281
+ {
282
+ winrt::Windows::Web::Http::HttpStringContent dataContents{ winrt::to_hstring(data) };
283
+ if (!items["type"].IsNull())
284
+ {
285
+ dataContents.Headers().TryAppendWithoutValidation(
286
+ L"content-type", winrt::to_hstring(items["type"].AsString()));
287
+ }
288
+
289
+ auto name = items["name"].IsNull() ? L"" : winrt::to_hstring(items["name"].AsString());
290
+ auto filename = items["filename"].IsNull() ? L"" : winrt::to_hstring(items["filename"].AsString());
291
+ if (name.empty())
292
+ {
293
+ requestContent.Add(dataContents);
294
+ }
295
+ else if (filename.empty())
296
+ {
297
+ requestContent.Add(dataContents, name);
298
+ }
299
+ else
300
+ {
301
+ requestContent.Add(dataContents, name, filename);
302
+ }
303
+ }
304
+ }
305
+
306
+ requestMessage.Content(requestContent);
307
+
308
+ winrt::Windows::Web::Http::HttpClient httpClient{ filter };
309
+ auto response = co_await httpClient.SendRequestAsync(requestMessage);
310
+
311
+ std::string responseBody;
312
+ if (response.Content() != nullptr)
313
+ {
314
+ responseBody = winrt::to_string(co_await response.Content().ReadAsStringAsync());
315
+ }
316
+
317
+ ::React::JSValueArray resultArray;
318
+ resultArray.push_back(responseBody);
319
+ callback(std::move(resultArray));
320
+ }
321
+ catch (const winrt::hresult_error& ex)
322
+ {
323
+ ::React::JSValueArray errorArray;
324
+ errorArray.push_back("EUNSPECIFIED");
325
+ errorArray.push_back(winrt::to_string(ex.message()));
326
+ callback(std::move(errorArray));
327
+ }
328
+ catch (...)
329
+ {
330
+ ::React::JSValueArray errorArray;
331
+ errorArray.push_back("EUNSPECIFIED");
332
+ errorArray.push_back("Unknown error in fetchBlobForm");
333
+ callback(std::move(errorArray));
334
+ }
335
+ }
336
+
337
+ winrt::fire_and_forget ReactNativeBlobUtil::fetchBlob(
338
+ ::React::JSValue options,
339
+ std::string taskId,
340
+ std::string method,
341
+ std::string url,
342
+ ::React::JSValue headers,
343
+ std::string body,
344
+ std::function<void(::React::JSValueArray)> callback
345
+ ) noexcept
346
+ {
347
+ // Convert rvalue references to lvalues for safe use
348
+ ::React::JSValue& optionsRef = options;
349
+ ::React::JSValue& headersRef = headers;
350
+
351
+ try
352
+ {
353
+ winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter filter;
354
+ ReactNativeBlobUtilConfig config{ optionsRef };
355
+ filter.AllowAutoRedirect(false);
356
+ if (config.trusty)
357
+ {
358
+ filter.IgnorableServerCertificateErrors().Append(Cryptography::Certificates::ChainValidationResult::Untrusted);
359
+ }
360
+
361
+ winrt::Windows::Web::Http::HttpClient httpClient{ filter };
362
+
363
+ winrt::Windows::Web::Http::HttpMethod httpMethod{ winrt::Windows::Web::Http::HttpMethod::Post() };
364
+ if (method == "DELETE" || method == "delete")
365
+ {
366
+ httpMethod = winrt::Windows::Web::Http::HttpMethod::Delete();
367
+ }
368
+ else if (method == "PUT" || method == "put")
369
+ {
370
+ httpMethod = winrt::Windows::Web::Http::HttpMethod::Put();
371
+ }
372
+ else if (method == "GET" || method == "get")
373
+ {
374
+ httpMethod = winrt::Windows::Web::Http::HttpMethod::Get();
375
+ }
376
+ else
377
+ {
378
+ ::React::JSValueArray errorArray;
379
+ errorArray.push_back("Method not supported");
380
+ callback(std::move(errorArray));
381
+ co_return;
382
+ }
383
+
384
+ winrt::Windows::Web::Http::HttpRequestMessage requestMessage{
385
+ httpMethod,
386
+ winrt::Windows::Foundation::Uri{ winrt::to_hstring(url) }
387
+ };
388
+
389
+ std::string prefix = "file://";
390
+ bool pathToFile = body.rfind(prefix, 0) == 0;
391
+ if (pathToFile)
392
+ {
393
+ std::string contentPath = body.substr(prefix.length());
394
+ size_t fileLength = contentPath.length();
395
+ bool hasTrailingSlash = contentPath[fileLength - 1] == '\\' || contentPath[fileLength - 1] == '/';
396
+ winrt::hstring directoryPath, fileName;
397
+ splitPath(hasTrailingSlash ? contentPath.substr(0, fileLength - 1) : contentPath, directoryPath, fileName);
398
+ auto folder = co_await winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(directoryPath);
399
+ auto storageFile = co_await folder.GetFileAsync(fileName);
400
+ auto requestBuffer = co_await winrt::Windows::Storage::FileIO::ReadBufferAsync(storageFile);
401
+
402
+ winrt::Windows::Web::Http::HttpBufferContent requestContent{ requestBuffer };
403
+
404
+ for (const auto& entry : headersRef.AsObject())
405
+ {
406
+ if (!requestMessage.Headers().TryAppendWithoutValidation(winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString())))
407
+ {
408
+ requestContent.Headers().TryAppendWithoutValidation(winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString()));
409
+ }
410
+ }
411
+ requestMessage.Content(requestContent);
412
+ }
413
+ else if (!body.empty()) {
414
+ winrt::Windows::Web::Http::HttpStringContent requestString{ winrt::to_hstring(body) };
415
+
416
+ for (const auto& entry : headersRef.AsObject())
417
+ {
418
+ if (!requestMessage.Headers().TryAppendWithoutValidation(winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString())))
419
+ {
420
+ requestString.Headers().TryAppendWithoutValidation(winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString()));
421
+ }
422
+ }
423
+ requestMessage.Content(requestString);
424
+ }
425
+ else {
426
+ for (const auto& entry : headersRef.AsObject())
427
+ {
428
+ requestMessage.Headers().TryAppendWithoutValidation(winrt::to_hstring(entry.first), winrt::to_hstring(entry.second.AsString()));
429
+ }
430
+ }
431
+
432
+ // Send the request
433
+ auto response = co_await httpClient.SendRequestAsync(requestMessage);
434
+
435
+ std::string responseBody;
436
+ if (response.Content() != nullptr)
437
+ {
438
+ responseBody = winrt::to_string(co_await response.Content().ReadAsStringAsync());
439
+ }
440
+
441
+ ::React::JSValueArray resultArray;
442
+ resultArray.push_back(responseBody);
443
+ callback(std::move(resultArray));
444
+ }
445
+ catch (const winrt::hresult_error& ex)
446
+ {
447
+ ::React::JSValueArray errorArray;
448
+ errorArray.push_back("EUNSPECIFIED");
449
+ errorArray.push_back(winrt::to_string(ex.message()));
450
+ callback(std::move(errorArray));
451
+ }
452
+ catch (...)
453
+ {
454
+ ::React::JSValueArray errorArray;
455
+ errorArray.push_back("EUNSPECIFIED");
456
+ errorArray.push_back("Unknown error in fetchBlob");
457
+ callback(std::move(errorArray));
458
+ }
459
+ }
460
+
461
+ winrt::fire_and_forget ReactNativeBlobUtil::createFile(
462
+ std::string path,
463
+ std::wstring content,
464
+ std::string encoding,
465
+ winrt::Microsoft::ReactNative::ReactPromise<void> promise) noexcept
466
+ {
467
+ try
468
+ {
469
+ bool shouldExit{ false };
470
+ Streams::IBuffer buffer;
471
+ if (encoding.compare("uri") == 0)
472
+ {
473
+ try
474
+ {
475
+ winrt::hstring srcDirectoryPath, srcFileName;
476
+ splitPath(content, srcDirectoryPath, srcFileName);
477
+ StorageFolder srcFolder{ co_await StorageFolder::GetFolderFromPathAsync(srcDirectoryPath) };
478
+ StorageFile srcFile{ co_await srcFolder.GetFileAsync(srcFileName) };
479
+ buffer = co_await FileIO::ReadBufferAsync(srcFile);
480
+ }
481
+ catch (...)
482
+ {
483
+ shouldExit = true;
484
+ }
485
+ }
486
+ else if (encoding.compare("utf8") == 0)
487
+ {
488
+ buffer = CryptographicBuffer::ConvertStringToBinary(content, BinaryStringEncoding::Utf8);
489
+ }
490
+ else if (encoding.compare("base64") == 0)
491
+ {
492
+ buffer = CryptographicBuffer::DecodeFromBase64String(content);
493
+ }
494
+ else
495
+ {
496
+ promise.Reject("Invalid encoding");
497
+ shouldExit = true;
498
+ }
499
+
500
+ if (!shouldExit)
501
+ {
502
+
503
+ winrt::hstring destDirectoryPath, destFileName;
504
+ splitPath(path, destDirectoryPath, destFileName);
505
+
506
+ auto folder{ co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath) };
507
+
508
+ try
509
+ {
510
+ auto file{ co_await folder.CreateFileAsync(destFileName, CreationCollisionOption::FailIfExists) };
511
+ auto stream{ co_await file.OpenAsync(FileAccessMode::ReadWrite) };
512
+ co_await stream.WriteAsync(buffer);
513
+ }
514
+ catch (...)
515
+ {
516
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EEXIST", "EEXIST: File already exists; " + path });
517
+ shouldExit = true;
518
+ }
519
+ }
520
+ if (!shouldExit)
521
+ {
522
+ promise.Resolve();
523
+ }
524
+ co_return;
525
+ }
526
+ catch (const hresult_error& ex)
527
+ {
528
+ hresult result{ ex.code() };
529
+ if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) // FileNotFoundException
530
+ {
531
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "ENOENT", "ENOENT: File does not exist and could not be created; " + path });
532
+ }
533
+ else
534
+ {
535
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EUNSPECIFIED", "EUNSPECIFIED: " + winrt::to_string(ex.message()) + "; " + path });
536
+ }
537
+ }
538
+ }
539
+
540
+ winrt::fire_and_forget ReactNativeBlobUtil::createFileASCII(
541
+ std::string path,
542
+ winrt::Microsoft::ReactNative::JSValueArray dataArray,
543
+ winrt::Microsoft::ReactNative::ReactPromise<void> promise) noexcept {
544
+ try
545
+ {
546
+ std::vector<uint8_t> data;
547
+ data.reserve(dataArray.size());
548
+ for (auto& var : dataArray)
549
+ {
550
+ data.push_back(var.AsUInt8());
551
+ }
552
+
553
+ Streams::IBuffer buffer{ CryptographicBuffer::CreateFromByteArray(data) };
554
+
555
+ winrt::hstring directoryPath, fileName;
556
+ splitPath(path, directoryPath, fileName);
557
+
558
+ StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(directoryPath) };
559
+
560
+ StorageFile file{ co_await folder.CreateFileAsync(fileName, CreationCollisionOption::FailIfExists) };
561
+ Streams::IRandomAccessStream stream{ co_await file.OpenAsync(FileAccessMode::ReadWrite) };
562
+ co_await stream.WriteAsync(buffer);
563
+
564
+ promise.Resolve();
565
+ }
566
+ catch (const hresult_error& ex)
567
+ {
568
+ hresult result{ ex.code() };
569
+ if (result == 0x80070002) // FileNotFoundException
570
+ {
571
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "ENOENT", "ENOENT: File does not exist and could not be created; " + path });
572
+ }
573
+ else if (result == 0x80070050)
574
+ {
575
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EEXIST", "EEXIST: File already exists; " + path });
576
+ }
577
+ else
578
+ {
579
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EUNSPECIFIED", "EUNSPECIFIED: " + winrt::to_string(ex.message()) });
580
+ }
581
+ }
582
+ }
583
+
584
+ winrt::fire_and_forget ReactNativeBlobUtil::writeFile(
585
+ std::string path,
586
+ std::string encoding,
587
+ std::wstring data,
588
+ bool transformFile,
589
+ bool append,
590
+ winrt::Microsoft::ReactNative::ReactPromise<double> promise) noexcept
591
+ {
592
+ try
593
+ {
594
+ Streams::IBuffer buffer;
595
+ if (encoding.compare("utf8") == 0)
596
+ {
597
+ buffer = Cryptography::CryptographicBuffer::ConvertStringToBinary(data, BinaryStringEncoding::Utf8);
598
+ }
599
+ else if (encoding.compare("base64") == 0)
600
+ {
601
+ buffer = Cryptography::CryptographicBuffer::DecodeFromBase64String(data);
602
+ }
603
+ else if (encoding.compare("uri") == 0)
604
+ {
605
+ winrt::hstring srcDirectoryPath, srcFileName;
606
+ splitPath(data, srcDirectoryPath, srcFileName);
607
+ StorageFolder srcFolder{ co_await StorageFolder::GetFolderFromPathAsync(srcDirectoryPath) };
608
+ StorageFile srcFile{ co_await srcFolder.GetFileAsync(srcFileName) };
609
+ buffer = co_await FileIO::ReadBufferAsync(srcFile);
610
+ }
611
+ else
612
+ {
613
+ auto errorMessage{ "Invalid encoding: " + encoding };
614
+ promise.Reject(errorMessage.c_str());
615
+ }
616
+
617
+ winrt::hstring destDirectoryPath, destFileName;
618
+ splitPath(path, destDirectoryPath, destFileName);
619
+ StorageFolder destFolder{ co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath) };
620
+ StorageFile destFile{ nullptr };
621
+ if (append)
622
+ {
623
+ destFile = co_await destFolder.CreateFileAsync(destFileName, CreationCollisionOption::OpenIfExists);
624
+ }
625
+ else
626
+ {
627
+ destFile = co_await destFolder.CreateFileAsync(destFileName, CreationCollisionOption::ReplaceExisting);
628
+ }
629
+ Streams::IRandomAccessStream stream{ co_await destFile.OpenAsync(FileAccessMode::ReadWrite) };
630
+
631
+ if (append)
632
+ {
633
+ stream.Seek(UINT64_MAX);
634
+ }
635
+ co_await stream.WriteAsync(buffer);
636
+ promise.Resolve(static_cast<double>(buffer.Length()));
637
+ }
638
+ catch (const hresult_error& ex)
639
+ {
640
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EUNSPECIFIED", "EUNSPECIFIED: " + winrt::to_string(ex.message()) + "; " + path });
641
+ }
642
+ }
643
+
644
+ winrt::fire_and_forget ReactNativeBlobUtil::writeFileArray(
645
+ std::string path,
646
+ winrt::Microsoft::ReactNative::JSValueArray dataArray,
647
+ bool append,
648
+ winrt::Microsoft::ReactNative::ReactPromise<double> promise) noexcept
649
+ {
650
+ try
651
+ {
652
+ std::vector<uint8_t> data;
653
+ data.reserve(dataArray.size());
654
+ for (auto& var : dataArray)
655
+ {
656
+ data.push_back(var.AsUInt8());
657
+ }
658
+ Streams::IBuffer buffer{ CryptographicBuffer::CreateFromByteArray(data) };
659
+
660
+ winrt::hstring destDirectoryPath, destFileName;
661
+ splitPath(path, destDirectoryPath, destFileName);
662
+ StorageFolder destFolder{ co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath) };
663
+ StorageFile destFile{ nullptr };
664
+ if (append)
665
+ {
666
+ destFile = co_await destFolder.CreateFileAsync(destFileName, CreationCollisionOption::OpenIfExists);
667
+ }
668
+ else
669
+ {
670
+ destFile = co_await destFolder.CreateFileAsync(destFileName, CreationCollisionOption::ReplaceExisting);
671
+ }
672
+ Streams::IRandomAccessStream stream{ co_await destFile.OpenAsync(FileAccessMode::ReadWrite) };
673
+
674
+ if (append)
675
+ {
676
+ stream.Seek(UINT64_MAX);
677
+ }
678
+ co_await stream.WriteAsync(buffer);
679
+ promise.Resolve(static_cast<double>(buffer.Length()));
680
+
681
+ co_return;
682
+ }
683
+ catch (const hresult_error& ex)
684
+ {
685
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EUNSPECIFIED", "EUNSPECIFIED: " + winrt::to_string(ex.message()) + "; " + path });
686
+ }
687
+ }
688
+
689
+ void ReactNativeBlobUtil::pathForAppGroup(
690
+ std::string groupName,
691
+ ::React::ReactPromise<std::string>&& result) noexcept
692
+ {
693
+ result.Resolve("");
694
+ }
695
+
696
+ std::string ReactNativeBlobUtil::syncPathAppGroup(
697
+ std::string groupName) noexcept
698
+ {
699
+ return "";
700
+ }
701
+
702
+ void ReactNativeBlobUtil::exists(
703
+ std::string path,
704
+ std::function<void(std::vector<bool> const&)> const& callback) noexcept
705
+ {
706
+ try
707
+ {
708
+ std::filesystem::path fsPath(path);
709
+ bool doesExist = std::filesystem::exists(fsPath);
710
+ bool isDirectory = std::filesystem::is_directory(fsPath);
711
+
712
+ callback(std::vector<bool>{ doesExist, isDirectory });
713
+ }
714
+ catch (const std::exception&)
715
+ {
716
+ // If something goes wrong, return false, false
717
+ callback(std::vector<bool>{ false, false });
718
+ }
719
+ }
720
+
721
+ winrt::fire_and_forget ReactNativeBlobUtil::writeStream(
722
+ std::string path,
723
+ std::string encoding,
724
+ bool appendData,
725
+ std::function<void(::React::JSValueArray)> callback) noexcept
726
+ {
727
+ try
728
+ {
729
+ winrt::hstring directoryPath, fileName;
730
+ splitPath(path, directoryPath, fileName);
731
+ auto folder = co_await StorageFolder::GetFolderFromPathAsync(directoryPath);
732
+ auto file = co_await folder.CreateFileAsync(fileName, CreationCollisionOption::OpenIfExists);
733
+ auto stream = co_await file.OpenAsync(FileAccessMode::ReadWrite);
734
+ if (appendData)
735
+ {
736
+ stream.Seek(stream.Size());
737
+ }
738
+
739
+ EncodingOptions encodingOption;
740
+ if (encoding == "utf8")
741
+ {
742
+ encodingOption = EncodingOptions::UTF8;
743
+ }
744
+ else if (encoding == "base64")
745
+ {
746
+ encodingOption = EncodingOptions::BASE64;
747
+ }
748
+ else if (encoding == "ascii")
749
+ {
750
+ encodingOption = EncodingOptions::ASCII;
751
+ }
752
+ else
753
+ {
754
+ // Return error as JSValueArray
755
+ ::React::JSValueArray errorArray;
756
+ errorArray.push_back("EUNSPECIFIED");
757
+ errorArray.push_back("Invalid encoding: " + encoding);
758
+ errorArray.push_back("");
759
+ callback(std::move(errorArray));
760
+ co_return;
761
+ }
762
+
763
+ // Generate a random streamId
764
+ uint32_t length = 16;
765
+ IBuffer buffer = Cryptography::CryptographicBuffer::GenerateRandom(length);
766
+ std::string streamId = winrt::to_string(Cryptography::CryptographicBuffer::EncodeToHexString(buffer));
767
+
768
+ ReactNativeBlobUtilStream streamInstance{ stream, encodingOption };
769
+ m_streamMap.try_emplace(streamId, streamInstance);
770
+
771
+ // Return success as JSValueArray
772
+ ::React::JSValueArray resultArray;
773
+ resultArray.push_back(""); // no error
774
+ resultArray.push_back(""); // no message
775
+ resultArray.push_back(streamId);
776
+ callback(std::move(resultArray));
777
+ }
778
+ catch (const winrt::hresult_error& ex)
779
+ {
780
+ ::React::JSValueArray errorArray;
781
+ errorArray.push_back("EUNSPECIFIED");
782
+ errorArray.push_back("Failed to create write stream at path '" + path + "'; " + winrt::to_string(ex.message()));
783
+ errorArray.push_back("");
784
+ callback(std::move(errorArray));
785
+ }
786
+ }
787
+
788
+ void ReactNativeBlobUtil::writeArrayChunk(
789
+ std::string streamId,
790
+ ::React::JSValueArray&& dataArray,
791
+ std::function<void(::React::JSValueArray const&)> const& callback) noexcept
792
+ {
793
+ try
794
+ {
795
+ auto streamIt = m_streamMap.find(streamId);
796
+ if (streamIt == m_streamMap.end()) {
797
+ ::React::JSValueArray errorArray;
798
+ errorArray.push_back("EUNSPECIFIED");
799
+ errorArray.push_back("Stream not found for id: " + streamId);
800
+ callback(errorArray);
801
+ return;
802
+ }
803
+ auto& stream = streamIt->second;
804
+ std::vector<uint8_t> data;
805
+ data.reserve(dataArray.size());
806
+ for (auto& var : dataArray)
807
+ {
808
+ data.push_back(var.AsUInt8());
809
+ }
810
+ Streams::IBuffer buffer{ CryptographicBuffer::CreateFromByteArray(data) };
811
+
812
+ stream.streamInstance.WriteAsync(buffer).get(); // Calls it synchronously
813
+ ::React::JSValueArray resultArray;
814
+ callback(resultArray); // Success: empty array
815
+ }
816
+ catch (const winrt::hresult_error& ex)
817
+ {
818
+ ::React::JSValueArray errorArray;
819
+ errorArray.push_back("EUNSPECIFIED");
820
+ errorArray.push_back(winrt::to_string(ex.message()));
821
+ callback(errorArray);
822
+ }
823
+ }
824
+
825
+ void ReactNativeBlobUtil::writeChunk(
826
+ std::string streamId,
827
+ std::string data,
828
+ std::function<void(::React::JSValueArray const&)> const& callback) noexcept
829
+ {
830
+ try
831
+ {
832
+ auto streamIt = m_streamMap.find(streamId);
833
+ if (streamIt == m_streamMap.end()) {
834
+ ::React::JSValueArray errorArray;
835
+ errorArray.push_back("EUNSPECIFIED");
836
+ errorArray.push_back("Stream not found for id: " + streamId);
837
+ callback(errorArray);
838
+ return;
839
+ }
840
+ auto& stream = streamIt->second;
841
+ Streams::IBuffer buffer;
842
+ if (stream.encoding == EncodingOptions::UTF8)
843
+ {
844
+ buffer = Cryptography::CryptographicBuffer::ConvertStringToBinary(
845
+ winrt::to_hstring(data), BinaryStringEncoding::Utf8);
846
+ }
847
+ else if (stream.encoding == EncodingOptions::BASE64)
848
+ {
849
+ buffer = Cryptography::CryptographicBuffer::DecodeFromBase64String(winrt::to_hstring(data));
850
+ }
851
+ else
852
+ {
853
+ ::React::JSValueArray errorArray;
854
+ errorArray.push_back("EUNSPECIFIED");
855
+ errorArray.push_back("Invalid encoding type");
856
+ callback(errorArray);
857
+ return;
858
+ }
859
+ stream.streamInstance.WriteAsync(buffer).get(); // Synchronous write
860
+ ::React::JSValueArray resultArray;
861
+ callback(resultArray); // Success: empty array
862
+ }
863
+ catch (const winrt::hresult_error& ex)
864
+ {
865
+ ::React::JSValueArray errorArray;
866
+ errorArray.push_back("EUNSPECIFIED");
867
+ errorArray.push_back(winrt::to_string(ex.message()));
868
+ callback(errorArray);
869
+ }
870
+ }
871
+
872
+ void ReactNativeBlobUtil::closeStream(
873
+ std::string streamId,
874
+ std::function<void(::React::JSValueArray const&)> const& callback) noexcept
875
+ {
876
+ ::React::JSValueArray resultArray;
877
+ try
878
+ {
879
+ auto it = m_streamMap.find(streamId);
880
+ if (it != m_streamMap.end()) {
881
+ it->second.streamInstance.Close();
882
+ m_streamMap.erase(it);
883
+ // Success: return empty array
884
+ callback(resultArray);
885
+ } else {
886
+ // Stream not found
887
+ resultArray.push_back("EUNSPECIFIED");
888
+ resultArray.push_back("Stream not found for id: " + streamId);
889
+ callback(resultArray);
890
+ }
891
+ }
892
+ catch (const winrt::hresult_error& ex)
893
+ {
894
+ resultArray.push_back("EUNSPECIFIED");
895
+ resultArray.push_back(winrt::to_string(ex.message()));
896
+ callback(resultArray);
897
+ }
898
+ }
899
+
900
+ winrt::fire_and_forget ReactNativeBlobUtil::unlink(
901
+ std::string path,
902
+ std::function<void(::React::JSValueArray)> callback) noexcept
903
+ {
904
+ try
905
+ {
906
+ if (std::filesystem::is_directory(path))
907
+ {
908
+ std::filesystem::path unlinkPath(path);
909
+ unlinkPath.make_preferred();
910
+ auto folderOp = winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(
911
+ winrt::to_hstring(unlinkPath.c_str()));
912
+ co_await folderOp.get().DeleteAsync();
913
+ }
914
+ else
915
+ {
916
+ winrt::hstring directoryPath, fileName;
917
+ splitPath(path, directoryPath, fileName);
918
+ auto folder = co_await winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(directoryPath);
919
+ auto item = co_await folder.GetItemAsync(fileName);
920
+ co_await item.DeleteAsync();
921
+ }
922
+
923
+ ::React::JSValueArray result;
924
+ result.push_back(::React::JSValue(true));
925
+ callback(std::move(result));
926
+ }
927
+ catch (const winrt::hresult_error& ex)
928
+ {
929
+ ::React::JSValueArray errorResult;
930
+ errorResult.push_back(::React::JSValue(false));
931
+ errorResult.push_back(::React::JSValue(winrt::to_string(ex.message())));
932
+ callback(std::move(errorResult));
933
+ }
934
+
935
+ }
936
+
937
+ winrt::fire_and_forget ReactNativeBlobUtil::removeSession(::React::JSValueArray paths, std::function<void(::React::JSValueArray)> callback) noexcept
938
+ {
939
+ try
940
+ {
941
+ for (const auto& pathValue : paths)
942
+ {
943
+ if (pathValue)
944
+ {
945
+ std::string path = pathValue.AsString();
946
+ auto folder = co_await winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(winrt::to_hstring(path));
947
+ auto file = co_await folder.GetFileAsync(winrt::to_hstring(path));
948
+ co_await file.DeleteAsync();
949
+ }
950
+ }
951
+
952
+ ::React::JSValueArray resultArray;
953
+ resultArray.push_back("SUCCESS");
954
+ callback(std::move(resultArray));
955
+ }
956
+ catch (const winrt::hresult_error& ex)
957
+ {
958
+ ::React::JSValueArray errorArray;
959
+ errorArray.push_back("ERROR");
960
+ errorArray.push_back(winrt::to_string(ex.message()));
961
+ callback(std::move(errorArray));
962
+ }
963
+ catch (...)
964
+ {
965
+ ::React::JSValueArray errorArray;
966
+ errorArray.push_back("ERROR");
967
+ errorArray.push_back("Unknown error in removeSession");
968
+ callback(std::move(errorArray));
969
+ }
970
+ }
971
+
972
+ winrt::fire_and_forget ReactNativeBlobUtil::ls(
973
+ std::string path,
974
+ ::React::ReactPromise<::React::JSValueArray> promise) noexcept
975
+ {
976
+ try
977
+ {
978
+ winrt::hstring directoryPath, fileName;
979
+ splitPath(path, directoryPath, fileName);
980
+
981
+ auto folder = co_await Windows::Storage::StorageFolder::GetFolderFromPathAsync(directoryPath);
982
+ auto items = co_await folder.GetItemsAsync();
983
+
984
+ ::React::JSValueArray results;
985
+ for (const auto& item : items)
986
+ {
987
+ results.push_back(::React::JSValue{ winrt::to_string(item.Name()) });
988
+ }
989
+
990
+ promise.Resolve(results);
991
+ }
992
+ catch (const winrt::hresult_error& ex)
993
+ {
994
+ hresult result = ex.code();
995
+ if (result == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))
996
+ {
997
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "ENOTDIR", "Not a directory '" + path + "'" });
998
+ }
999
+ else
1000
+ {
1001
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EUNSPECIFIED", winrt::to_string(ex.message()) });
1002
+ }
1003
+ }
1004
+ }
1005
+
1006
+ winrt::fire_and_forget ReactNativeBlobUtil::stat(
1007
+ std::string path,
1008
+ std::function<void(::React::JSValueArray)> callback) noexcept
1009
+ {
1010
+ try
1011
+ {
1012
+ std::filesystem::path givenPath(path);
1013
+ givenPath.make_preferred();
1014
+ bool isDirectory{ std::filesystem::is_directory(path) };
1015
+
1016
+ //std::string resultPath{ winrt::to_string(givenPath.c_str()) };
1017
+ auto resultPath{ winrt::to_hstring(givenPath.c_str()) };
1018
+
1019
+ // Try to open as folder
1020
+ IStorageItem item;
1021
+ if (isDirectory) {
1022
+ item = co_await StorageFolder::GetFolderFromPathAsync(resultPath);
1023
+ }
1024
+ else {
1025
+ item = co_await StorageFile::GetFileFromPathAsync(resultPath);
1026
+ }
1027
+ auto properties{ co_await item.GetBasicPropertiesAsync() };
1028
+ winrt::Microsoft::ReactNative::JSValueObject fileInfo;
1029
+ fileInfo["size"] = properties.Size();
1030
+ fileInfo["filename"] = givenPath.filename().string();
1031
+ fileInfo["path"] = givenPath.string();
1032
+ fileInfo["lastModified"] = winrt::clock::to_time_t(properties.DateModified());;
1033
+ fileInfo["type"] = isDirectory ? "directory" : "file";
1034
+
1035
+ ::React::JSValueArray result;
1036
+ result.push_back(std::move(fileInfo));
1037
+ callback(std::move(result));
1038
+ }
1039
+ catch (const hresult_error& ex)
1040
+ {
1041
+ ::React::JSValueArray errorArray;
1042
+ ::React::JSValueObject error;
1043
+ error["error"] = winrt::to_string(ex.message());
1044
+ errorArray.push_back(std::move(error));
1045
+ callback(std::move(errorArray));
1046
+ }
1047
+ }
1048
+
1049
+ winrt::fire_and_forget ReactNativeBlobUtil::lstat(
1050
+ std::string path,
1051
+ std::function<void(::React::JSValueArray)> callback) noexcept
1052
+ {
1053
+ try
1054
+ {
1055
+ std::filesystem::path directory(path);
1056
+ directory.make_preferred();
1057
+ StorageFolder targetDirectory{ co_await StorageFolder::GetFolderFromPathAsync(directory.c_str()) };
1058
+
1059
+ winrt::Microsoft::ReactNative::JSValueArray resultsArray;
1060
+
1061
+ auto items{ co_await targetDirectory.GetItemsAsync() };
1062
+ for (auto item : items)
1063
+ {
1064
+ auto properties{ co_await item.GetBasicPropertiesAsync() };
1065
+
1066
+ winrt::Microsoft::ReactNative::JSValueObject itemInfo;
1067
+
1068
+ itemInfo["filename"] = to_string(item.Name());
1069
+ itemInfo["path"] = to_string(item.Path());
1070
+ itemInfo["size"] = properties.Size();
1071
+ itemInfo["type"] = item.IsOfType(StorageItemTypes::Folder) ? "directory" : "file";
1072
+ itemInfo["lastModified"] = properties.DateModified().time_since_epoch() / std::chrono::seconds(1) - UNIX_EPOCH_IN_WINRT_SECONDS;
1073
+
1074
+ resultsArray.push_back(std::move(itemInfo));
1075
+ }
1076
+
1077
+ callback(std::move(resultsArray));
1078
+ }
1079
+ catch (...)
1080
+ {
1081
+ // "Failed to read directory."
1082
+ winrt::Microsoft::ReactNative::JSValueArray emptyArray;
1083
+ callback(std::move(emptyArray));
1084
+ }
1085
+ }
1086
+
1087
+ winrt::fire_and_forget ReactNativeBlobUtil::cp(
1088
+ std::string src,
1089
+ std::string dest,
1090
+ std::function<void(::React::JSValueArray)> callback) noexcept
1091
+ {
1092
+ try
1093
+ {
1094
+ winrt::hstring srcDirectoryPath, srcFileName;
1095
+ splitPath(src, srcDirectoryPath, srcFileName);
1096
+
1097
+ winrt::hstring destDirectoryPath, destFileName;
1098
+ splitPath(dest, destDirectoryPath, destFileName);
1099
+
1100
+ StorageFolder srcFolder = co_await StorageFolder::GetFolderFromPathAsync(srcDirectoryPath);
1101
+ StorageFolder destFolder = co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath);
1102
+
1103
+ StorageFile file = co_await srcFolder.GetFileAsync(srcFileName);
1104
+ co_await file.CopyAsync(destFolder, destFileName, NameCollisionOption::FailIfExists);
1105
+
1106
+ ::React::JSValueArray success{};
1107
+ callback(std::move(success));
1108
+ }
1109
+ catch (const winrt::hresult_error& ex)
1110
+ {
1111
+ ::React::JSValueArray error{ winrt::to_string(ex.message()) };
1112
+ callback(std::move(error));
1113
+ }
1114
+ }
1115
+
1116
+ winrt::fire_and_forget ReactNativeBlobUtil::mv(
1117
+ std::string src,
1118
+ std::string dest,
1119
+ std::function<void(::React::JSValueArray)> callback) noexcept
1120
+ {
1121
+ try
1122
+ {
1123
+ winrt::hstring srcDirectoryPath, srcFileName;
1124
+ splitPath(src, srcDirectoryPath, srcFileName);
1125
+
1126
+ winrt::hstring destDirectoryPath, destFileName;
1127
+ splitPath(dest, destDirectoryPath, destFileName);
1128
+
1129
+ StorageFolder srcFolder = co_await StorageFolder::GetFolderFromPathAsync(srcDirectoryPath);
1130
+ StorageFolder destFolder = co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath);
1131
+
1132
+ StorageFile file = co_await srcFolder.GetFileAsync(srcFileName);
1133
+ co_await file.MoveAsync(destFolder, destFileName, NameCollisionOption::ReplaceExisting);
1134
+
1135
+ ::React::JSValueArray success{}; // Success: empty array
1136
+ callback(std::move(success));
1137
+ }
1138
+ catch (const winrt::hresult_error& ex)
1139
+ {
1140
+ ::React::JSValueArray error{ winrt::to_string(ex.message()) };
1141
+ callback(std::move(error));
1142
+ }
1143
+ }
1144
+
1145
+ void ReactNativeBlobUtil::mkdir(
1146
+ std::string path,
1147
+ ::React::ReactPromise<bool>&& promise) noexcept
1148
+ {
1149
+ try
1150
+ {
1151
+ std::filesystem::path dirPath(path);
1152
+ dirPath.make_preferred();
1153
+
1154
+ // Consistent with Apple's createDirectoryAtPath method and result, but not with Android's
1155
+ std::error_code ec;
1156
+ bool created = std::filesystem::create_directories(dirPath, ec);
1157
+ if (!created && !std::filesystem::exists(dirPath))
1158
+ {
1159
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + path });
1160
+ }
1161
+ else
1162
+ {
1163
+ promise.Resolve(true);
1164
+ }
1165
+ }
1166
+ catch (const hresult_error& ex)
1167
+ {
1168
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EUNSPECIFIED", "Error creating folder " + path + ", error: " + winrt::to_string(ex.message()) });
1169
+ }
1170
+ }
1171
+
1172
+ winrt::fire_and_forget ReactNativeBlobUtil::readFile(
1173
+ std::string path,
1174
+ std::string encoding,
1175
+ bool transformFile,
1176
+ ::React::ReactPromise<::React::JSValueArray> promise) noexcept
1177
+ {
1178
+ try
1179
+ {
1180
+ winrt::hstring directoryPath, fileName;
1181
+ splitPath(path, directoryPath, fileName);
1182
+
1183
+ auto folder = co_await StorageFolder::GetFolderFromPathAsync(directoryPath);
1184
+ auto file = co_await folder.GetFileAsync(fileName);
1185
+ auto buffer = co_await FileIO::ReadBufferAsync(file);
1186
+
1187
+ ::React::JSValueArray resultArray;
1188
+ if (encoding == "base64")
1189
+ {
1190
+ std::string base64Content = winrt::to_string(Cryptography::CryptographicBuffer::EncodeToBase64String(buffer));
1191
+ resultArray.push_back(base64Content);
1192
+ }
1193
+ else
1194
+ {
1195
+ std::string utf8Content = winrt::to_string(Cryptography::CryptographicBuffer::ConvertBinaryToString(BinaryStringEncoding::Utf8, buffer));
1196
+ if (encoding == "ascii")
1197
+ {
1198
+ // For ASCII, just return the utf8Content as a string
1199
+ resultArray.push_back(utf8Content);
1200
+ }
1201
+ else
1202
+ {
1203
+ resultArray.push_back(utf8Content);
1204
+ }
1205
+ }
1206
+ promise.Resolve(resultArray);
1207
+ }
1208
+ catch (const winrt::hresult_error& ex)
1209
+ {
1210
+ hresult result{ ex.code() };
1211
+ if (result == 0x80070002) // FileNotFoundException
1212
+ {
1213
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + path });
1214
+ }
1215
+ else if (result == 0x80070005) // UnauthorizedAccessException
1216
+ {
1217
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EISDIR", "EISDIR: illegal operation on a directory, read" });
1218
+ }
1219
+ else
1220
+ {
1221
+ // "Failed to read file."
1222
+ promise.Reject(winrt::to_string(ex.message()).c_str());
1223
+ }
1224
+ }
1225
+ }
1226
+
1227
+ winrt::fire_and_forget ReactNativeBlobUtil::hash(
1228
+ std::string path,
1229
+ std::string algorithm,
1230
+ ::React::ReactPromise<std::string> promise) noexcept
1231
+ {
1232
+ try
1233
+ {
1234
+ // Note: SHA224 is not part of winrt
1235
+ if (algorithm.compare("sha224") == 0)
1236
+ {
1237
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "Error", "WinRT does not offer sha224 encryption." });
1238
+ co_return;
1239
+ }
1240
+
1241
+ winrt::hstring directoryPath, fileName;
1242
+ splitPath(path, directoryPath, fileName);
1243
+
1244
+ StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(directoryPath) };
1245
+ StorageFile file{ co_await folder.GetFileAsync(fileName) };
1246
+
1247
+ auto search{ availableHashes.find(algorithm) };
1248
+ if (search == availableHashes.end())
1249
+ {
1250
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "Error", "Invalid hash algorithm " + algorithm });
1251
+ co_return;
1252
+ }
1253
+
1254
+ CryptographyCore::HashAlgorithmProvider provider{ search->second() };
1255
+ Streams::IBuffer buffer{ co_await FileIO::ReadBufferAsync(file) };
1256
+
1257
+ auto hashedBuffer{ provider.HashData(buffer) };
1258
+ std::wstring result{ Cryptography::CryptographicBuffer::EncodeToHexString(hashedBuffer) };
1259
+ std::string sResult = winrt::to_string(result);
1260
+ promise.Resolve(sResult);
1261
+ }
1262
+ catch (const hresult_error& ex)
1263
+ {
1264
+ hresult result{ ex.code() };
1265
+ if (result == 0x80070002) // FileNotFoundException
1266
+ {
1267
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + path });
1268
+ }
1269
+ else if (result == 0x80070005) // UnauthorizedAccessException
1270
+ {
1271
+ promise.Reject(winrt::Microsoft::ReactNative::ReactError{ "EISDIR", "EISDIR: illegal operation on a directory, read" });
1272
+ }
1273
+ else
1274
+ {
1275
+ // "Failed to get checksum from file."
1276
+ promise.Reject(winrt::to_string(ex.message()).c_str());
1277
+ }
1278
+ }
1279
+ }
1280
+
1281
+ winrt::fire_and_forget ReactNativeBlobUtil::readStream(
1282
+ std::string path,
1283
+ std::string encoding,
1284
+ double bufferSize,
1285
+ double tick,
1286
+ std::string streamId) noexcept
1287
+ {
1288
+ try
1289
+ {
1290
+ EncodingOptions usedEncoding;
1291
+ if (encoding == "utf8")
1292
+ {
1293
+ usedEncoding = EncodingOptions::UTF8;
1294
+ }
1295
+ else if (encoding == "base64")
1296
+ {
1297
+ usedEncoding = EncodingOptions::BASE64;
1298
+ }
1299
+ else if (encoding == "ascii")
1300
+ {
1301
+ usedEncoding = EncodingOptions::ASCII;
1302
+ }
1303
+ else
1304
+ {
1305
+ // Invalid encoding
1306
+ m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", streamId,
1307
+ winrt::Microsoft::ReactNative::JSValueObject{
1308
+ {"event", "error"},
1309
+ {"EINVAL", "Unsupported encoding: " + encoding}
1310
+ });
1311
+ co_return;
1312
+ }
1313
+
1314
+ uint32_t chunkSize = (usedEncoding == EncodingOptions::BASE64) ? 4095 : 4096;
1315
+ if (bufferSize > 0)
1316
+ {
1317
+ chunkSize = static_cast<uint32_t>(bufferSize);
1318
+ }
1319
+
1320
+ winrt::hstring directoryPath, fileName;
1321
+ splitPath(path, directoryPath, fileName);
1322
+
1323
+ StorageFolder folder = co_await StorageFolder::GetFolderFromPathAsync(directoryPath);
1324
+ StorageFile file = co_await folder.GetFileAsync(fileName);
1325
+ Streams::IRandomAccessStream stream = co_await file.OpenAsync(FileAccessMode::Read);
1326
+
1327
+ Buffer buffer{ chunkSize };
1328
+
1329
+ for (;;)
1330
+ {
1331
+ auto readBuffer = co_await stream.ReadAsync(buffer, buffer.Capacity(), InputStreamOptions::None);
1332
+ if (readBuffer.Length() == 0)
1333
+ {
1334
+ m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", streamId,
1335
+ winrt::Microsoft::ReactNative::JSValueObject{
1336
+ {"event", "end"}
1337
+ });
1338
+ break;
1339
+ }
1340
+
1341
+ if (usedEncoding == EncodingOptions::BASE64)
1342
+ {
1343
+ winrt::hstring base64Content = Cryptography::CryptographicBuffer::EncodeToBase64String(readBuffer);
1344
+ m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", streamId,
1345
+ winrt::Microsoft::ReactNative::JSValueObject{
1346
+ {"event", "data"},
1347
+ {"detail", winrt::to_string(base64Content)}
1348
+ });
1349
+ }
1350
+ else
1351
+ {
1352
+ winrt::hstring stringContent = Cryptography::CryptographicBuffer::ConvertBinaryToString(
1353
+ BinaryStringEncoding::Utf8, readBuffer);
1354
+ std::string utf8Content = winrt::to_string(stringContent);
1355
+
1356
+ // For ASCII, trim to 7-bit range
1357
+ if (usedEncoding == EncodingOptions::ASCII)
1358
+ {
1359
+ for (char& c : utf8Content)
1360
+ {
1361
+ c &= 0x7F;
1362
+ }
1363
+ }
1364
+
1365
+ m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", streamId,
1366
+ winrt::Microsoft::ReactNative::JSValueObject{
1367
+ {"event", "data"},
1368
+ {"detail", utf8Content}
1369
+ });
1370
+ }
1371
+
1372
+ if (tick > 0)
1373
+ {
1374
+ std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int64_t>(tick)));
1375
+ }
1376
+ }
1377
+ }
1378
+ catch (const hresult_error& ex)
1379
+ {
1380
+ hresult result = ex.code();
1381
+ if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) // 0x80070002
1382
+ {
1383
+ m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", streamId,
1384
+ winrt::Microsoft::ReactNative::JSValueObject{
1385
+ {"event", "error"},
1386
+ {"ENOENT", "No such file: " + path}
1387
+ });
1388
+ }
1389
+ else
1390
+ {
1391
+ m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", streamId,
1392
+ winrt::Microsoft::ReactNative::JSValueObject{
1393
+ {"event", "error"},
1394
+ {"EUNSPECIFIED", winrt::to_string(ex.message())}
1395
+ });
1396
+ }
1397
+ }
1398
+ }
1399
+
1400
+ void ReactNativeBlobUtil::getEnvironmentDirs(
1401
+ std::function<void(::React::JSValueArray const&)> const& callback) noexcept
1402
+ {
1403
+ callback(::React::JSValueArray{});
1404
+ }
1405
+
1406
+ void ReactNativeBlobUtil::cancelRequest(
1407
+ std::string taskId,
1408
+ std::function<void(::React::JSValueArray const&)> const& callback) noexcept
1409
+ {
1410
+ ::React::JSValueArray resultArray;
1411
+ try
1412
+ {
1413
+ m_tasks.Cancel(taskId);
1414
+ // Success: return empty array
1415
+ callback(resultArray);
1416
+ }
1417
+ catch (const winrt::hresult_error& ex)
1418
+ {
1419
+ resultArray.push_back("EUNSPECIFIED");
1420
+ resultArray.push_back(winrt::to_string(ex.message()));
1421
+ callback(resultArray);
1422
+ }
1423
+ }
1424
+
1425
+ void ReactNativeBlobUtil::enableProgressReport(
1426
+ std::string taskId,
1427
+ double interval,
1428
+ double count) noexcept
1429
+ {
1430
+ ReactNativeBlobUtilProgressConfig config{ count, interval };
1431
+ std::scoped_lock lock{ m_mutex };
1432
+ downloadProgressMap.try_emplace(taskId, config);
1433
+ }
1434
+
1435
+ void ReactNativeBlobUtil::enableUploadProgressReport(
1436
+ std::string taskId,
1437
+ double interval,
1438
+ double count) noexcept
1439
+ {
1440
+ ReactNativeBlobUtilProgressConfig config{ count, interval };
1441
+ std::scoped_lock lock{ m_mutex };
1442
+ uploadProgressMap.try_emplace(taskId, config);
1443
+ }
1444
+
1445
+ winrt::fire_and_forget ReactNativeBlobUtil::slice(
1446
+ std::string src,
1447
+ std::string dest,
1448
+ double start,
1449
+ double end,
1450
+ ::React::ReactPromise<std::string> promise) noexcept
1451
+ {
1452
+ try
1453
+ {
1454
+ winrt::hstring srcDirectoryPath, srcFileName, destDirectoryPath, destFileName;
1455
+ splitPath(src, srcDirectoryPath, srcFileName);
1456
+ splitPath(src, destDirectoryPath, destFileName);
1457
+
1458
+ StorageFolder srcFolder{ co_await StorageFolder::GetFolderFromPathAsync(srcDirectoryPath) };
1459
+ StorageFolder destFolder{ co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath) };
1460
+
1461
+ StorageFile srcFile{ co_await srcFolder.GetFileAsync(srcFileName) };
1462
+ StorageFile destFile{ co_await destFolder.CreateFileAsync(destFileName, CreationCollisionOption::OpenIfExists) };
1463
+
1464
+ uint64_t uStart = static_cast<uint64_t>(start);
1465
+ uint64_t uEnd = static_cast<uint64_t>(end);
1466
+ uint32_t length = static_cast<uint32_t>(uEnd > uStart ? uEnd - uStart : 0);
1467
+
1468
+ if (length == 0) {
1469
+ promise.Reject("Invalid slice range");
1470
+ co_return;
1471
+ }
1472
+ Streams::IBuffer buffer;
1473
+ Streams::IRandomAccessStream stream{ co_await srcFile.OpenAsync(FileAccessMode::Read) };
1474
+ stream.Seek(start);
1475
+ stream.ReadAsync(buffer, length, Streams::InputStreamOptions::None);
1476
+ co_await FileIO::WriteBufferAsync(destFile, buffer);
1477
+
1478
+ promise.Resolve(dest);
1479
+ }
1480
+ catch (...)
1481
+ {
1482
+ promise.Reject("Unable to slice file");
1483
+ }
1484
+ }
1485
+
1486
+ IAsyncAction setTimeout(std::chrono::seconds time) {
1487
+ co_await time;
1488
+ }
1489
+
1490
+ void ReactNativeBlobUtil::presentOptionsMenu(
1491
+ std::string uri,
1492
+ std::string scheme,
1493
+ ::React::ReactPromise<::React::JSValueArray>&& result) noexcept
1494
+ {
1495
+ result.Resolve(::React::JSValueArray{});
1496
+ }
1497
+
1498
+ void ReactNativeBlobUtil::presentOpenInMenu(
1499
+ std::string uri,
1500
+ std::string scheme,
1501
+ ::React::ReactPromise<::React::JSValueArray>&& result) noexcept
1502
+ {
1503
+ result.Resolve(::React::JSValueArray{});
1504
+ }
1505
+
1506
+ void ReactNativeBlobUtil::presentPreview(
1507
+ std::string uri,
1508
+ std::string scheme,
1509
+ ::React::ReactPromise<::React::JSValueArray>&& result) noexcept
1510
+ {
1511
+ result.Resolve(::React::JSValueArray{});
1512
+ }
1513
+
1514
+ void ReactNativeBlobUtil::excludeFromBackupKey(
1515
+ std::string url,
1516
+ ::React::ReactPromise<::React::JSValueArray>&& result) noexcept
1517
+ {
1518
+ result.Resolve(::React::JSValueArray{});
1519
+ }
1520
+
1521
+ winrt::fire_and_forget ReactNativeBlobUtil::df(
1522
+ std::function<void(::React::JSValueArray)> callback) noexcept
1523
+ {
1524
+ try
1525
+ {
1526
+ auto localFolder = winrt::Windows::Storage::ApplicationData::Current().LocalFolder();
1527
+ auto properties{ co_await localFolder.Properties().RetrievePropertiesAsync({L"System.FreeSpace", L"System.Capacity"}) };
1528
+
1529
+ winrt::Microsoft::ReactNative::JSValueObject result;
1530
+ result["free"] = winrt::unbox_value<uint64_t>(properties.Lookup(L"System.FreeSpace"));
1531
+ result["total"] = winrt::unbox_value<uint64_t>(properties.Lookup(L"System.Capacity"));
1532
+
1533
+ ::React::JSValueArray arr;
1534
+ arr.push_back(::React::JSValueObject(std::move(result)));
1535
+ callback(std::move(arr));
1536
+ }
1537
+ catch (...)
1538
+ {
1539
+ ::React::JSValueArray arr;
1540
+ winrt::Microsoft::ReactNative::JSValueObject error;
1541
+ error["error"] = "Failed to get storage usage.";
1542
+ arr.push_back(::React::JSValueObject(std::move(error)));
1543
+ callback(std::move(arr));
1544
+ }
1545
+
1546
+ }
1547
+
1548
+ void ReactNativeBlobUtil::emitExpiredEvent(
1549
+ std::function<void(std::string)> const& callback) noexcept
1550
+ {
1551
+ callback("");
1552
+ }
1553
+
1554
+ void ReactNativeBlobUtil::actionViewIntent(
1555
+ std::string path,
1556
+ std::string mime,
1557
+ std::string chooserTitle,
1558
+ ::React::ReactPromise<void>&& result) noexcept
1559
+ {
1560
+ // No-op: Android API
1561
+ result.Resolve();
1562
+ }
1563
+
1564
+ void ReactNativeBlobUtil::addCompleteDownload(
1565
+ ::React::JSValue&& config,
1566
+ ::React::ReactPromise<void>&& result) noexcept
1567
+ {
1568
+ // No-op: Android API
1569
+ result.Resolve();
1570
+ }
1571
+
1572
+ void ReactNativeBlobUtil::copyToInternal(
1573
+ std::string contentUri,
1574
+ std::string destpath,
1575
+ ::React::ReactPromise<std::string>&& result) noexcept
1576
+ {
1577
+ // No-op: Android API
1578
+ result.Resolve("");
1579
+ }
1580
+
1581
+ void ReactNativeBlobUtil::copyToMediaStore(
1582
+ ::React::JSValue&& filedata,
1583
+ std::string mt,
1584
+ std::string path,
1585
+ ::React::ReactPromise<std::string>&& result) noexcept
1586
+ {
1587
+ // No-op: Android API
1588
+ result.Resolve("");
1589
+ }
1590
+
1591
+ void ReactNativeBlobUtil::createMediaFile(
1592
+ ::React::JSValue&& filedata,
1593
+ std::string mt,
1594
+ ::React::ReactPromise<std::string>&& result) noexcept
1595
+ {
1596
+ // No-op: Android API
1597
+ result.Resolve("");
1598
+ }
1599
+
1600
+ void ReactNativeBlobUtil::getBlob(
1601
+ std::string contentUri,
1602
+ std::string encoding,
1603
+ ::React::ReactPromise<::React::JSValueArray>&& result) noexcept
1604
+ {
1605
+ // No-op: Android API
1606
+ result.Resolve(::React::JSValueArray{});
1607
+ }
1608
+
1609
+ void ReactNativeBlobUtil::getContentIntent(
1610
+ std::string mime,
1611
+ ::React::ReactPromise<std::string>&& result) noexcept
1612
+ {
1613
+ // No-op: Android API
1614
+ result.Resolve("");
1615
+ }
1616
+
1617
+ void ReactNativeBlobUtil::getSDCardDir(
1618
+ ::React::ReactPromise<std::string>&& result) noexcept
1619
+ {
1620
+ // No-op: Android API
1621
+ result.Resolve("");
1622
+ }
1623
+
1624
+ void ReactNativeBlobUtil::getSDCardApplicationDir(
1625
+ ::React::ReactPromise<std::string>&& result) noexcept
1626
+ {
1627
+ // No-op: Android API
1628
+ result.Resolve("");
1629
+ }
1630
+
1631
+ void ReactNativeBlobUtil::scanFile(
1632
+ ::React::JSValueArray&& pairs,
1633
+ std::function<void(::React::JSValueArray const&)> const& callback) noexcept
1634
+ {
1635
+ // No-op: Android API
1636
+ callback(::React::JSValueArray{});
1637
+ }
1638
+
1639
+ void ReactNativeBlobUtil::writeToMediaFile(
1640
+ std::string fileUri,
1641
+ std::string path,
1642
+ bool transformFile,
1643
+ ::React::ReactPromise<std::string>&& result) noexcept
1644
+ {
1645
+ // No-op: Android API
1646
+ result.Resolve("");
1647
+ }
1648
+
1649
+ void ReactNativeBlobUtil::addListener(
1650
+ std::string eventName) noexcept
1651
+ {
1652
+ // No-op
1653
+ }
1654
+
1655
+ void ReactNativeBlobUtil::removeListeners(
1656
+ double count) noexcept
1657
+ {
1658
+ // No-op
1659
+ }
1660
+
1661
+ void ReactNativeBlobUtil::splitPath(const std::string& fullPath, winrt::hstring& directoryPath, winrt::hstring& fileName) noexcept
1662
+ {
1663
+ std::filesystem::path path{ fullPath };
1664
+ path.make_preferred();
1665
+
1666
+ directoryPath = path.has_parent_path() ? winrt::to_hstring(path.parent_path().c_str()) : L"";
1667
+ fileName = path.has_filename() ? winrt::to_hstring(path.filename().c_str()) : L"";
1668
+ }
1669
+
1670
+ void ReactNativeBlobUtil::splitPath(const std::wstring& fullPath, winrt::hstring& directoryPath, winrt::hstring& fileName) noexcept
1671
+ {
1672
+ std::filesystem::path path{ fullPath };
1673
+ path.make_preferred();
1674
+
1675
+ directoryPath = path.has_parent_path() ? winrt::to_hstring(path.parent_path().c_str()) : L"";
1676
+ fileName = path.has_filename() ? winrt::to_hstring(path.filename().c_str()) : L"";
1677
+ }
1678
+
1679
+ winrt::Windows::Foundation::IAsyncAction ReactNativeBlobUtil::ProcessRequestAsync(
1680
+ const std::string& taskId,
1681
+ const winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter& filter,
1682
+ winrt::Windows::Web::Http::HttpRequestMessage& httpRequestMessage,
1683
+ ReactNativeBlobUtilConfig& config,
1684
+ std::function<void(std::string, std::string, std::string)> callback,
1685
+ std::string& error) noexcept
1686
+ try
1687
+ {
1688
+ winrt::Windows::Web::Http::HttpClient httpClient{filter};
1689
+
1690
+ winrt::Windows::Web::Http::HttpResponseMessage response{ co_await httpClient.SendRequestAsync(httpRequestMessage, winrt::Windows::Web::Http::HttpCompletionOption::ResponseHeadersRead) };
1691
+
1692
+ ReactNativeBlobUtilState eventState;
1693
+
1694
+ auto status{ static_cast<int>(response.StatusCode()) };
1695
+ if (config.followRedirect) {
1696
+ while (status >= 300 && status < 400) {
1697
+ auto redirect{ response.Headers().Location().ToString() };
1698
+ eventState.redirects.push_back(Microsoft::ReactNative::JSValue(winrt::to_string(redirect)));
1699
+ httpRequestMessage.RequestUri(Uri{ redirect });
1700
+ response = co_await httpClient.SendRequestAsync(httpRequestMessage, winrt::Windows::Web::Http::HttpCompletionOption::ResponseHeadersRead);
1701
+ status = static_cast<int>(response.StatusCode());
1702
+ }
1703
+ }
1704
+
1705
+ eventState.status = static_cast<int>(response.StatusCode());
1706
+
1707
+ for (const auto header : response.Content().Headers().GetView()) {
1708
+ eventState.headers[winrt::to_string(header.Key())] = winrt::to_string(header.Value());
1709
+ }
1710
+
1711
+ if (response.Content().Headers().ContentType() != nullptr) {
1712
+ eventState.respType = winrt::to_string(response.Content().Headers().ContentType().ToString());
1713
+ }
1714
+
1715
+ eventState.state = winrt::to_string(response.ReasonPhrase());
1716
+
1717
+ m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", L"ReactNativeBlobUtilState",
1718
+ Microsoft::ReactNative::JSValueObject{
1719
+ { "taskId", taskId },
1720
+ { "state", eventState.state },
1721
+ { "headers", std::move(eventState.headers) },
1722
+ { "redirects", std::move(eventState.redirects) },
1723
+ { "respType", eventState.respType },
1724
+ { "status", eventState.status },
1725
+ { "timeout", false },
1726
+ });
1727
+
1728
+ IReference<uint64_t> contentLength{ response.Content().Headers().ContentLength() };
1729
+
1730
+ IOutputStream outputStream;
1731
+ bool writeToFile{ config.fileCache || !config.path.empty() };
1732
+
1733
+ if (writeToFile)
1734
+ {
1735
+ if (config.path.empty())
1736
+ {
1737
+ config.path = winrt::to_string(ApplicationData::Current().TemporaryFolder().Path()) + "\\ReactNativeBlobUtilTmp_" + taskId;
1738
+ if (config.appendExt.length() > 0)
1739
+ {
1740
+ config.path += "." + config.appendExt;
1741
+ }
1742
+ }
1743
+ std::filesystem::path path{ config.path };
1744
+ StorageFolder storageFolder{ co_await StorageFolder::GetFolderFromPathAsync( path.parent_path().wstring()) };
1745
+ StorageFile storageFile{ co_await storageFolder.CreateFileAsync(path.filename().wstring(), CreationCollisionOption::FailIfExists) };
1746
+ IRandomAccessStream stream{ co_await storageFile.OpenAsync(FileAccessMode::ReadWrite) };
1747
+ outputStream = stream.GetOutputStreamAt(0) ;
1748
+ }
1749
+
1750
+ auto contentStream{ co_await response.Content().ReadAsInputStreamAsync() };
1751
+ Buffer buffer{ 10 * 1024 };
1752
+ uint64_t read{ 0 };
1753
+ uint64_t totalRead{ 0 };
1754
+
1755
+ ReactNativeBlobUtilProgressConfig progressInfo;
1756
+ uint64_t progressInterval{ 0 };
1757
+
1758
+ std::stringstream chunkStream;
1759
+ std::stringstream resultOutput;
1760
+
1761
+ std::string readContents{""};
1762
+
1763
+ auto exists{ downloadProgressMap.find(taskId) };
1764
+ if (exists != downloadProgressMap.end() && contentLength.Type() == PropertyType::UInt64) {
1765
+ progressInfo = downloadProgressMap[taskId];
1766
+
1767
+ if (progressInfo.count > -1) {
1768
+ progressInterval = contentLength.Value() / 100 * progressInfo.count;
1769
+ }
1770
+ }
1771
+
1772
+ int64_t initialProgressTime{ winrt::clock::now().time_since_epoch().count() / 10000 };
1773
+ int64_t currentProgressTime;
1774
+
1775
+ for (;;)
1776
+ {
1777
+ buffer.Length(0);
1778
+ auto readBuffer{ co_await contentStream.ReadAsync(buffer, buffer.Capacity(), InputStreamOptions::None) };
1779
+
1780
+ read += readBuffer.Length();
1781
+ totalRead += read;
1782
+
1783
+ if (readBuffer.Length() == 0)
1784
+ {
1785
+ break;
1786
+ }
1787
+
1788
+ readContents = winrt::to_string(CryptographicBuffer::EncodeToBase64String(readBuffer));
1789
+
1790
+ if (writeToFile) {
1791
+ co_await outputStream.WriteAsync(readBuffer);
1792
+ }
1793
+ else {
1794
+ resultOutput << readContents;
1795
+ }
1796
+
1797
+ if (progressInfo.count > -1 || progressInfo.interval > -1) {
1798
+ chunkStream << readContents;
1799
+
1800
+ currentProgressTime = winrt::clock::now().time_since_epoch().count() / 10000;
1801
+ if ((currentProgressTime - initialProgressTime >= progressInfo.interval && progressInfo.interval > -1) ||
1802
+ (totalRead >= progressInterval && progressInfo.count > -1)) {
1803
+ m_reactContext.CallJSFunction(L"RCTDeviceEventEmitter", L"emit", L"ReactNativeBlobUtilProgress",
1804
+ Microsoft::ReactNative::JSValueObject{
1805
+ { "taskId", taskId },
1806
+ { "written", int64_t(totalRead) },
1807
+ { "total", contentLength.Type() == PropertyType::UInt64 ?
1808
+ Microsoft::ReactNative::JSValue(contentLength.Value()) :
1809
+ Microsoft::ReactNative::JSValue{nullptr} },
1810
+ { "chunk", chunkStream.str() },
1811
+ });
1812
+ chunkStream.clear();
1813
+ initialProgressTime = winrt::clock::now().time_since_epoch().count() / 10000;
1814
+ if (progressInfo.count > -1) {
1815
+ read = 0;
1816
+ }
1817
+ }
1818
+ }
1819
+ }
1820
+
1821
+ if (writeToFile) {
1822
+ callback("", "path", config.path);
1823
+ }
1824
+ else {
1825
+ callback("", "result", resultOutput.str());
1826
+ }
1827
+ }
1828
+ catch (const hresult_error& ex)
1829
+ {
1830
+ error = winrt::to_string(ex.message().c_str());
1831
+ //callback(winrt::to_string(ex.message().c_str()), "error", "");
1832
+ }
1833
+ catch (...) {
1834
+ co_return;
1835
+ }
1836
+
1837
1837
  } // namespace winrt::ReactNativeBlobUtil