@ohos-ports/react-native-fs 2.20.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/FS.common.js ADDED
@@ -0,0 +1,997 @@
1
+ /**
2
+ * React Native FS — HarmonyOS Node.js migration (Round 1)
3
+ *
4
+ * Original implementation was backed by React Native's NativeModules.RNFSManager
5
+ * (Objective-C / Java / C# native bridges) and react-native's NativeEventEmitter.
6
+ * The HarmonyOS Node.js runtime has no React Native host runtime, so the native
7
+ * bridge layer has been rewritten on top of verified Node.js built-in capabilities:
8
+ * - file operations -> node:fs / node:fs/promises
9
+ * - downloadFile -> node:http / node:https
10
+ * - uploadFiles -> node:http / node:https (multipart/form-data)
11
+ * - NativeEventEmitter -> node:events (EventEmitter)
12
+ *
13
+ * Flow type annotations have been stripped so the file is plain CommonJS that
14
+ * Node.js can parse directly. All public API names, signatures and
15
+ * Promise/event semantics are preserved from the original FS.common.js.
16
+ */
17
+
18
+ 'use strict';
19
+
20
+ var fsp = require('node:fs/promises');
21
+ var fs = require('node:fs');
22
+ var path = require('node:path');
23
+ var os = require('node:os');
24
+ var http = require('node:http');
25
+ var https = require('node:https');
26
+ var crypto = require('node:crypto');
27
+ var EventEmitter = require('node:events');
28
+
29
+ var base64 = require('base-64');
30
+ var utf8 = require('utf8');
31
+
32
+ function fsSafeCreateWriteStream(toFile) {
33
+ var fspSync = require('node:fs');
34
+ return fspSync.createWriteStream(toFile, { flags: 'w' });
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Event emitter replacement (react-native NativeEventEmitter -> node:events)
39
+ // ---------------------------------------------------------------------------
40
+ var RNFS_NativeEventEmitter = new EventEmitter();
41
+ var _nativeAddListener = RNFS_NativeEventEmitter.addListener.bind(RNFS_NativeEventEmitter);
42
+ // RN listeners are removed via `subscription.remove()`; wrap to keep that contract
43
+ RNFS_NativeEventEmitter.addListener = function (eventName, handler) {
44
+ _nativeAddListener(eventName, handler);
45
+ return {
46
+ remove: function () {
47
+ RNFS_NativeEventEmitter.removeListener(eventName, handler);
48
+ },
49
+ };
50
+ };
51
+
52
+ var RNFSFileTypeRegular = 0;
53
+ var RNFSFileTypeDirectory = 1;
54
+
55
+ var jobId = 0;
56
+
57
+ var getJobId = function () {
58
+ jobId += 1;
59
+ return jobId;
60
+ };
61
+
62
+ var normalizeFilePath = function (path) {
63
+ return path.startsWith('file://') ? path.slice(7) : path;
64
+ };
65
+
66
+ var homeDir = process.env.HOME || process.cwd();
67
+
68
+ var downloadJobs = {};
69
+ var uploadJobs = {};
70
+
71
+ function toSeconds(ms) {
72
+ return Math.floor(ms / 1000);
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Bridge layer (replaces NativeModules.RNFSManager)
77
+ // Every method mirrors the original RNFSManager native bridge contract:
78
+ // readFile/read/write return base64 strings, stat/readDir return raw records
79
+ // with epoch-second timestamps, so the public API layer below stays untouched.
80
+ // ---------------------------------------------------------------------------
81
+ var RNFSManager = {
82
+
83
+ mkdir: function (dirPath) {
84
+ return fsp.mkdir(dirPath, { recursive: true }).then(function () { return void 0; });
85
+ },
86
+
87
+ moveFile: function (filepath, destPath) {
88
+ return fsp.rename(filepath, destPath).catch(function (err) {
89
+ if (err && err.code === 'EXDEV') {
90
+ // cross-device move: copy then remove
91
+ return fsp.copyFile(filepath, destPath).then(function () {
92
+ return fsp.unlink(filepath);
93
+ });
94
+ }
95
+ throw err;
96
+ }).then(function () { return void 0; });
97
+ },
98
+
99
+ copyFile: function (filepath, destPath) {
100
+ return fsp.copyFile(filepath, destPath).then(function () { return void 0; });
101
+ },
102
+
103
+ unlink: function (filepath) {
104
+ return fsp.rm(filepath, { recursive: true }).then(function () { return void 0; });
105
+ },
106
+
107
+ exists: function (filepath) {
108
+ return fsp.stat(filepath).then(function () { return true; }, function () { return false; });
109
+ },
110
+
111
+ stat: function (filepath) {
112
+ return fsp.stat(filepath).then(function (st) {
113
+ return {
114
+ name: path.basename(filepath),
115
+ path: filepath,
116
+ size: st.size,
117
+ mode: st.mode,
118
+ ctime: toSeconds(st.ctimeMs),
119
+ mtime: toSeconds(st.mtimeMs),
120
+ originalFilepath: filepath,
121
+ type: st.isDirectory() ? RNFSFileTypeDirectory : RNFSFileTypeRegular,
122
+ };
123
+ });
124
+ },
125
+
126
+ readFile: function (filepath) {
127
+ return fsp.readFile(filepath).then(function (buf) {
128
+ return buf.toString('base64');
129
+ });
130
+ },
131
+
132
+ read: function (filepath, length, position) {
133
+ return fsp.open(filepath, 'r').then(function (fh) {
134
+ var done = false;
135
+ var finish = function (p) {
136
+ if (done) return p;
137
+ done = true;
138
+ return fh.close().then(function () { return p; });
139
+ };
140
+ return fh.stat().then(function (st) {
141
+ var start = position && position >= 0 ? position : 0;
142
+ var readLen = length && length > 0 ? length : Math.max(st.size - start, 0);
143
+ var buf = Buffer.alloc(readLen);
144
+ return fh.read(buf, 0, readLen, start).then(function (result) {
145
+ var out = buf.slice(0, result.bytesRead).toString('base64');
146
+ return finish(out);
147
+ });
148
+ }).catch(function (err) {
149
+ return finish(Promise.reject(err));
150
+ });
151
+ });
152
+ },
153
+
154
+ writeFile: function (filepath, b64) {
155
+ return fsp.writeFile(filepath, Buffer.from(b64, 'base64')).then(function () { return void 0; });
156
+ },
157
+
158
+ appendFile: function (filepath, b64) {
159
+ return fsp.appendFile(filepath, Buffer.from(b64, 'base64')).then(function () { return void 0; });
160
+ },
161
+
162
+ write: function (filepath, b64, position) {
163
+ var data = Buffer.from(b64, 'base64');
164
+ if (position === undefined || position === null || position < 0) {
165
+ // RN bridge semantics: position -1 (or undefined) means append
166
+ return fsp.appendFile(filepath, data).then(function () { return void 0; });
167
+ }
168
+ return fsp.open(filepath, 'r+').then(function (fh) {
169
+ var done = false;
170
+ var finish = function (p) {
171
+ if (done) return p;
172
+ done = true;
173
+ return fh.close().then(function () { return p; });
174
+ };
175
+ return fh.write(data, 0, data.length, position)
176
+ .then(function () { return finish(void 0); })
177
+ .catch(function (err) { return finish(Promise.reject(err)); });
178
+ });
179
+ },
180
+
181
+ readDir: function (dirPath) {
182
+ return fsp.readdir(dirPath, { withFileTypes: true }).then(function (entries) {
183
+ return Promise.all(entries.map(function (entry) {
184
+ var full = path.join(dirPath, entry.name);
185
+ return fsp.stat(full).then(
186
+ function (st) {
187
+ return {
188
+ ctime: toSeconds(st.ctimeMs),
189
+ mtime: toSeconds(st.mtimeMs),
190
+ name: entry.name,
191
+ path: full,
192
+ size: String(st.size),
193
+ type: st.isDirectory() ? RNFSFileTypeDirectory : RNFSFileTypeRegular,
194
+ };
195
+ },
196
+ function () {
197
+ return {
198
+ ctime: null,
199
+ mtime: null,
200
+ name: entry.name,
201
+ path: full,
202
+ size: '0',
203
+ type: entry.isDirectory() ? RNFSFileTypeDirectory : RNFSFileTypeRegular,
204
+ };
205
+ }
206
+ );
207
+ }));
208
+ });
209
+ },
210
+
211
+ hash: function (filepath, algorithm) {
212
+ return fsp.readFile(filepath).then(function (buf) {
213
+ return crypto.createHash(algorithm).update(buf).digest('hex');
214
+ });
215
+ },
216
+
217
+ touch: function (filepath, mtimeMs) {
218
+ var when = mtimeMs ? new Date(mtimeMs) : new Date();
219
+ // touch creates the file if it does not exist (same as the native bridges)
220
+ return fsp.stat(filepath).catch(function (err) {
221
+ if (err && err.code === 'ENOENT') {
222
+ return fsp.writeFile(filepath, '', { flag: 'a' }).then(function () { return null; });
223
+ }
224
+ throw err;
225
+ }).then(function () {
226
+ return fsp.utimes(filepath, when, when).then(function () { return void 0; });
227
+ });
228
+ },
229
+
230
+ setReadable: function (filepath, readable, ownerOnly) {
231
+ var mode = readable ? (ownerOnly ? 0o700 : 0o755) : 0o000;
232
+ return fsp.chmod(filepath, mode).then(function () { return true; }, function () { return false; });
233
+ },
234
+
235
+ scanFile: function (filepath) {
236
+ return fsp.stat(filepath).then(function (st) {
237
+ return [{
238
+ path: filepath,
239
+ name: path.basename(filepath),
240
+ size: String(st.size),
241
+ type: st.isDirectory() ? RNFSFileTypeDirectory : RNFSFileTypeRegular,
242
+ ctime: toSeconds(st.ctimeMs),
243
+ mtime: toSeconds(st.mtimeMs),
244
+ }];
245
+ });
246
+ },
247
+
248
+ getFSInfo: function () {
249
+ return fsp.statfs(homeDir).then(function (stats) {
250
+ return {
251
+ totalSpace: Number(stats.blocks) * Number(stats.bsize),
252
+ freeSpace: Number(stats.bavail) * Number(stats.bsize),
253
+ };
254
+ });
255
+ },
256
+
257
+ downloadFile: function (options) {
258
+ return new Promise(function (resolve, reject) {
259
+ var jid = options.jobId;
260
+ var failed = false;
261
+
262
+ var doRequest = function (urlStr, redirects) {
263
+ var parsed;
264
+ try {
265
+ parsed = new URL(urlStr);
266
+ } catch (e) {
267
+ reject(e);
268
+ return;
269
+ }
270
+ var mod = parsed.protocol === 'https:' ? https : (parsed.protocol === 'http:' ? http : null);
271
+ if (!mod) {
272
+ reject(new Error('Unsupported protocol: ' + parsed.protocol));
273
+ return;
274
+ }
275
+
276
+ var req = mod.get(parsed, { headers: options.headers || {} }, function (res) {
277
+ // follow redirects like the native bridges do
278
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
279
+ if (redirects >= 5) {
280
+ res.resume();
281
+ reject(new Error('Maximum redirect count reached'));
282
+ return;
283
+ }
284
+ res.resume();
285
+ doRequest(new URL(res.headers.location, parsed).toString(), redirects + 1);
286
+ return;
287
+ }
288
+
289
+ var statusCode = res.statusCode;
290
+ var contentLength = parseInt(res.headers['content-length'] || '0', 10) || 0;
291
+
292
+ RNFS_NativeEventEmitter.emit('DownloadBegin', {
293
+ jobId: jid,
294
+ statusCode: statusCode,
295
+ contentLength: contentLength,
296
+ headers: res.headers,
297
+ });
298
+
299
+ var out = fs.createWriteStream(options.toFile, { flags: 'w' });
300
+ var bytesWritten = 0;
301
+ var lastEmitTime = Date.now();
302
+ var lastEmitBytes = 0;
303
+
304
+ var abort = function (err) {
305
+ if (failed) return;
306
+ failed = true;
307
+ delete downloadJobs[jid];
308
+ try { req.destroy(); } catch (e) { /* noop */ }
309
+ try { out.destroy(); } catch (e) { /* noop */ }
310
+ reject(err);
311
+ };
312
+ downloadJobs[jid] = { abort: abort };
313
+
314
+ res.on('data', function (chunk) {
315
+ bytesWritten += chunk.length;
316
+ if (!out.write(chunk)) {
317
+ res.pause();
318
+ out.once('drain', function () { res.resume(); });
319
+ }
320
+ var now = Date.now();
321
+ var intervalOk = !options.progressInterval || (now - lastEmitTime) >= options.progressInterval;
322
+ var dividerOk = !options.progressDivider || (bytesWritten - lastEmitBytes) >= options.progressDivider;
323
+ if (intervalOk && dividerOk) {
324
+ lastEmitTime = now;
325
+ lastEmitBytes = bytesWritten;
326
+ RNFS_NativeEventEmitter.emit('DownloadProgress', {
327
+ jobId: jid,
328
+ contentLength: contentLength,
329
+ bytesWritten: bytesWritten,
330
+ });
331
+ }
332
+ });
333
+
334
+ res.on('error', function (err) { abort(err); });
335
+ out.on('error', function (err) { abort(err); });
336
+
337
+ res.on('end', function () {
338
+ out.end(function () {
339
+ if (failed) return;
340
+ delete downloadJobs[jid];
341
+ resolve({ jobId: jid, statusCode: statusCode, bytesWritten: bytesWritten });
342
+ });
343
+ });
344
+ });
345
+
346
+ // connection timeout (time to establish the connection)
347
+ var connectTimer = setTimeout(function () {
348
+ req.destroy(new Error('Connection timeout'));
349
+ reject(new Error('Connection timeout'));
350
+ }, options.connectionTimeout || 5000);
351
+ req.on('socket', function (socket) {
352
+ socket.once('connect', function () { clearTimeout(connectTimer); });
353
+ });
354
+ // read/inactivity timeout
355
+ req.setTimeout(options.readTimeout || 15000, function () {
356
+ req.destroy(new Error('Read timeout'));
357
+ reject(new Error('Read timeout'));
358
+ });
359
+ req.on('error', function (err) {
360
+ if (failed) return;
361
+ failed = true;
362
+ delete downloadJobs[jid];
363
+ reject(err);
364
+ });
365
+ };
366
+
367
+ doRequest(options.fromUrl, 0);
368
+ });
369
+ },
370
+
371
+ uploadFiles: function (options) {
372
+ return new Promise(function (resolve, reject) {
373
+ var jid = options.jobId;
374
+ var boundary = 'RNFSBoundary' + Date.now().toString(16) + Math.random().toString(16).slice(2);
375
+
376
+ var prepareBody;
377
+ if (options.binaryStreamOnly && options.files && options.files.length > 0) {
378
+ // raw binary stream upload of the first file, no multipart framing
379
+ prepareBody = fsp.readFile(options.files[0].filepath).then(function (data) {
380
+ return { body: data, contentType: null };
381
+ });
382
+ } else {
383
+ var parts = [];
384
+ var fields = options.fields || {};
385
+ Object.keys(fields).forEach(function (name) {
386
+ parts.push(Buffer.from(
387
+ '--' + boundary + '\r\n' +
388
+ 'Content-Disposition: form-data; name="' + String(name) + '"\r\n\r\n' +
389
+ String(fields[name]) + '\r\n'
390
+ ));
391
+ });
392
+ var fileParts = (options.files || []).map(function (file) {
393
+ var filename = file.filename || path.basename(file.filepath || '');
394
+ var filetype = file.filetype || 'application/octet-stream';
395
+ return fsp.readFile(normalizeFilePath(file.filepath)).then(function (data) {
396
+ parts.push(Buffer.concat([
397
+ Buffer.from(
398
+ '--' + boundary + '\r\n' +
399
+ 'Content-Disposition: form-data; name="' + (file.name || filename) +
400
+ '"; filename="' + filename + '"\r\n' +
401
+ 'Content-Type: ' + filetype + '\r\n\r\n'
402
+ ),
403
+ data,
404
+ Buffer.from('\r\n'),
405
+ ]));
406
+ });
407
+ });
408
+ prepareBody = Promise.all(fileParts).then(function () {
409
+ parts.push(Buffer.from('--' + boundary + '--\r\n'));
410
+ return { body: Buffer.concat(parts), contentType: 'multipart/form-data; boundary=' + boundary };
411
+ });
412
+ }
413
+
414
+ prepareBody.then(function (prepared) {
415
+ var body = prepared.body;
416
+ var parsed;
417
+ try {
418
+ parsed = new URL(options.toUrl);
419
+ } catch (e) {
420
+ reject(e);
421
+ return;
422
+ }
423
+ var mod = parsed.protocol === 'https:' ? https : (parsed.protocol === 'http:' ? http : null);
424
+ if (!mod) {
425
+ reject(new Error('Unsupported protocol: ' + parsed.protocol));
426
+ return;
427
+ }
428
+ var headers = Object.assign({}, options.headers || {});
429
+ if (prepared.contentType) headers['Content-Type'] = prepared.contentType;
430
+ headers['Content-Length'] = body.length;
431
+ var req = mod.request(parsed, { method: options.method || 'POST', headers: headers }, function (res) {
432
+ var chunks = [];
433
+ res.on('data', function (c) { chunks.push(c); });
434
+ res.on('end', function () {
435
+ delete uploadJobs[jid];
436
+ resolve({
437
+ jobId: jid,
438
+ statusCode: res.statusCode,
439
+ headers: res.headers,
440
+ body: Buffer.concat(chunks).toString('utf8'),
441
+ });
442
+ });
443
+ res.on('error', function (err) { reject(err); });
444
+ });
445
+ uploadJobs[jid] = {
446
+ abort: function (err) {
447
+ delete uploadJobs[jid];
448
+ try { req.destroy(); } catch (e) { /* noop */ }
449
+ reject(err);
450
+ },
451
+ };
452
+
453
+ RNFS_NativeEventEmitter.emit('UploadBegin', { jobId: jid });
454
+
455
+ // write in chunks to provide progress callbacks
456
+ var CHUNK = 64 * 1024;
457
+ var totalBytesSent = 0;
458
+ var offset = 0;
459
+ var writeNext = function () {
460
+ if (offset >= body.length) {
461
+ req.end();
462
+ return;
463
+ }
464
+ var chunk = body.slice(offset, Math.min(offset + CHUNK, body.length));
465
+ offset += chunk.length;
466
+ totalBytesSent += chunk.length;
467
+ RNFS_NativeEventEmitter.emit('UploadProgress', {
468
+ jobId: jid,
469
+ totalBytesExpectedToSend: body.length,
470
+ totalBytesSent: totalBytesSent,
471
+ });
472
+ if (req.write(chunk)) {
473
+ process.nextTick(writeNext);
474
+ } else {
475
+ req.once('drain', writeNext);
476
+ }
477
+ };
478
+ writeNext();
479
+
480
+ req.on('error', function (err) {
481
+ delete uploadJobs[jid];
482
+ reject(err);
483
+ });
484
+ }).catch(reject);
485
+ });
486
+ },
487
+ };
488
+
489
+ // ---------------------------------------------------------------------------
490
+ // Public API layer — identical structure and semantics to the original
491
+ // FS.common.js, with Flow type annotations stripped for plain Node.js.
492
+ // ---------------------------------------------------------------------------
493
+
494
+ /**
495
+ * Generic function used by readFile and readFileAssets
496
+ */
497
+ function readFileGeneric(filepath, encodingOrOptions, command) {
498
+ var options = {
499
+ encoding: 'utf8'
500
+ };
501
+
502
+ if (encodingOrOptions) {
503
+ if (typeof encodingOrOptions === 'string') {
504
+ options.encoding = encodingOrOptions;
505
+ } else if (typeof encodingOrOptions === 'object') {
506
+ options = encodingOrOptions;
507
+ }
508
+ }
509
+
510
+ return command(normalizeFilePath(filepath)).then(function (b64) {
511
+ var contents;
512
+
513
+ if (options.encoding === 'utf8') {
514
+ contents = utf8.decode(base64.decode(b64));
515
+ } else if (options.encoding === 'ascii') {
516
+ contents = base64.decode(b64);
517
+ } else if (options.encoding === 'base64') {
518
+ contents = b64;
519
+ } else {
520
+ throw new Error('Invalid encoding type "' + String(options.encoding) + '"');
521
+ }
522
+
523
+ return contents;
524
+ });
525
+ }
526
+
527
+ /**
528
+ * Generic function used by readDir and readDirAssets
529
+ */
530
+ function readDirGeneric(dirpath, command) {
531
+ return command(normalizeFilePath(dirpath)).then(function (files) {
532
+ return files.map(function (file) {
533
+ return {
534
+ ctime: file.ctime && new Date(file.ctime * 1000) || null,
535
+ mtime: file.mtime && new Date(file.mtime * 1000) || null,
536
+ name: file.name,
537
+ path: file.path,
538
+ size: file.size,
539
+ isFile: function () { return file.type === RNFSFileTypeRegular; },
540
+ isDirectory: function () { return file.type === RNFSFileTypeDirectory; },
541
+ };
542
+ });
543
+ });
544
+ }
545
+
546
+ var RNFS = {
547
+
548
+ mkdir(filepath, options = {}) {
549
+ return RNFSManager.mkdir(normalizeFilePath(filepath), options).then(() => void 0);
550
+ },
551
+
552
+ moveFile(filepath, destPath, options = {}) {
553
+ return RNFSManager.moveFile(normalizeFilePath(filepath), normalizeFilePath(destPath), options).then(() => void 0);
554
+ },
555
+
556
+ copyFile(filepath, destPath, options = {}) {
557
+ return RNFSManager.copyFile(normalizeFilePath(filepath), normalizeFilePath(destPath), options).then(() => void 0);
558
+ },
559
+
560
+ pathForBundle(bundleNamed) {
561
+ // Bundle concept does not exist in a plain Node.js runtime; resolve
562
+ // relative to the module directory instead of failing entirely.
563
+ return Promise.resolve(path.resolve(__dirname, bundleNamed));
564
+ },
565
+
566
+ pathForGroup(groupName) {
567
+ return Promise.reject(new Error('pathForGroup is not supported on this platform'));
568
+ },
569
+
570
+ getFSInfo() {
571
+ return RNFSManager.getFSInfo();
572
+ },
573
+
574
+ getAllExternalFilesDirs() {
575
+ return Promise.resolve(path.join(homeDir, 'files'));
576
+ },
577
+
578
+ unlink(filepath) {
579
+ return RNFSManager.unlink(normalizeFilePath(filepath)).then(() => void 0);
580
+ },
581
+
582
+ exists(filepath) {
583
+ return RNFSManager.exists(normalizeFilePath(filepath));
584
+ },
585
+
586
+ stopDownload(jobId) {
587
+ var job = downloadJobs[jobId];
588
+ if (job && job.abort) {
589
+ job.abort(new Error('Download has been aborted'));
590
+ }
591
+ },
592
+
593
+ resumeDownload(jobId) {
594
+ throw new Error('resumeDownload is not supported on this platform');
595
+ },
596
+
597
+ isResumable(jobId) {
598
+ return Promise.resolve(false);
599
+ },
600
+
601
+ stopUpload(jobId) {
602
+ var job = uploadJobs[jobId];
603
+ if (job && job.abort) {
604
+ job.abort(new Error('Upload has been aborted'));
605
+ }
606
+ },
607
+
608
+ completeHandlerIOS(jobId) {
609
+ // iOS background-fetch completion handler; no-op without an RN host app
610
+ return void 0;
611
+ },
612
+
613
+ readDir(dirpath) {
614
+ return readDirGeneric(dirpath, RNFSManager.readDir);
615
+ },
616
+
617
+ // Android-only
618
+ readDirAssets(dirpath) {
619
+ if (!RNFSManager.readDirAssets) {
620
+ throw new Error('readDirAssets is not available on this platform');
621
+ }
622
+ return readDirGeneric(dirpath, RNFSManager.readDirAssets);
623
+ },
624
+
625
+ // Android-only
626
+ existsAssets(filepath) {
627
+ if (!RNFSManager.existsAssets) {
628
+ throw new Error('existsAssets is not available on this platform');
629
+ }
630
+ return RNFSManager.existsAssets(filepath);
631
+ },
632
+
633
+ // Android-only
634
+ existsRes(filename) {
635
+ if (!RNFSManager.existsRes) {
636
+ throw new Error('existsRes is not available on this platform');
637
+ }
638
+ return RNFSManager.existsRes(filename);
639
+ },
640
+
641
+ // Node style version (lowercase d). Returns just the names
642
+ readdir(dirpath) {
643
+ return RNFS.readDir(normalizeFilePath(dirpath)).then(files => {
644
+ return files.map(file => file.name);
645
+ });
646
+ },
647
+
648
+ // setReadable for Android
649
+ setReadable(filepath, readable, ownerOnly) {
650
+ return RNFSManager.setReadable(filepath, readable, ownerOnly).then((result) => {
651
+ return result;
652
+ })
653
+ },
654
+
655
+ stat(filepath) {
656
+ return RNFSManager.stat(normalizeFilePath(filepath)).then((result) => {
657
+ return {
658
+ 'path': filepath,
659
+ 'ctime': new Date(result.ctime * 1000),
660
+ 'mtime': new Date(result.mtime * 1000),
661
+ 'size': result.size,
662
+ 'mode': result.mode,
663
+ 'originalFilepath': result.originalFilepath,
664
+ isFile: () => result.type === RNFSFileTypeRegular,
665
+ isDirectory: () => result.type === RNFSFileTypeDirectory,
666
+ };
667
+ });
668
+ },
669
+
670
+ readFile(filepath, encodingOrOptions) {
671
+ return readFileGeneric(filepath, encodingOrOptions, RNFSManager.readFile);
672
+ },
673
+
674
+ read(filepath, length = 0, position = 0, encodingOrOptions) {
675
+ var options = {
676
+ encoding: 'utf8'
677
+ };
678
+
679
+ if (encodingOrOptions) {
680
+ if (typeof encodingOrOptions === 'string') {
681
+ options.encoding = encodingOrOptions;
682
+ } else if (typeof encodingOrOptions === 'object') {
683
+ options = encodingOrOptions;
684
+ }
685
+ }
686
+
687
+ return RNFSManager.read(normalizeFilePath(filepath), length, position).then((b64) => {
688
+ var contents;
689
+
690
+ if (options.encoding === 'utf8') {
691
+ contents = utf8.decode(base64.decode(b64));
692
+ } else if (options.encoding === 'ascii') {
693
+ contents = base64.decode(b64);
694
+ } else if (options.encoding === 'base64') {
695
+ contents = b64;
696
+ } else {
697
+ throw new Error('Invalid encoding type "' + String(options.encoding) + '"');
698
+ }
699
+
700
+ return contents;
701
+ });
702
+ },
703
+
704
+ // Android only
705
+ readFileAssets(filepath, encodingOrOptions) {
706
+ if (!RNFSManager.readFileAssets) {
707
+ throw new Error('readFileAssets is not available on this platform');
708
+ }
709
+ return readFileGeneric(filepath, encodingOrOptions, RNFSManager.readFileAssets);
710
+ },
711
+
712
+ // Android only
713
+ readFileRes(filename, encodingOrOptions) {
714
+ if (!RNFSManager.readFileRes) {
715
+ throw new Error('readFileRes is not available on this platform');
716
+ }
717
+ return readFileGeneric(filename, encodingOrOptions, RNFSManager.readFileRes);
718
+ },
719
+
720
+ hash(filepath, algorithm) {
721
+ return RNFSManager.hash(normalizeFilePath(filepath), algorithm);
722
+ },
723
+
724
+ // Android only
725
+ copyFileAssets(filepath, destPath) {
726
+ if (!RNFSManager.copyFileAssets) {
727
+ throw new Error('copyFileAssets is not available on this platform');
728
+ }
729
+ return RNFSManager.copyFileAssets(normalizeFilePath(filepath), normalizeFilePath(destPath)).then(() => void 0);
730
+ },
731
+
732
+ // Android only
733
+ copyFileRes(filename, destPath) {
734
+ if (!RNFSManager.copyFileRes) {
735
+ throw new Error('copyFileRes is not available on this platform');
736
+ }
737
+ return RNFSManager.copyFileRes(filename, normalizeFilePath(destPath)).then(() => void 0);
738
+ },
739
+
740
+ // iOS only
741
+ // Copies fotos from asset-library (camera-roll) to a specific location
742
+ // with a given width or height
743
+ // @see: https://developer.apple.com/reference/photos/phimagemanager/1616964-requestimageforasset
744
+ copyAssetsFileIOS(imageUri, destPath, width, height,
745
+ scale = 1.0, compression = 1.0, resizeMode = 'contain') {
746
+ return Promise.reject(new Error('copyAssetsFileIOS is not available on this platform'));
747
+ },
748
+
749
+ // iOS only
750
+ copyAssetsVideoIOS(imageUri, destPath) {
751
+ return Promise.reject(new Error('copyAssetsVideoIOS is not available on this platform'));
752
+ },
753
+
754
+ writeFile(filepath, contents, encodingOrOptions) {
755
+ var b64;
756
+
757
+ var options = {
758
+ encoding: 'utf8'
759
+ };
760
+
761
+ if (encodingOrOptions) {
762
+ if (typeof encodingOrOptions === 'string') {
763
+ options.encoding = encodingOrOptions;
764
+ } else if (typeof encodingOrOptions === 'object') {
765
+ options = {
766
+ ...options,
767
+ ...encodingOrOptions
768
+ };
769
+ }
770
+ }
771
+
772
+ if (options.encoding === 'utf8') {
773
+ b64 = base64.encode(utf8.encode(contents));
774
+ } else if (options.encoding === 'ascii') {
775
+ b64 = base64.encode(contents);
776
+ } else if (options.encoding === 'base64') {
777
+ b64 = contents;
778
+ } else {
779
+ throw new Error('Invalid encoding type "' + options.encoding + '"');
780
+ }
781
+
782
+ return RNFSManager.writeFile(normalizeFilePath(filepath), b64, options).then(() => void 0);
783
+ },
784
+
785
+ appendFile(filepath, contents, encodingOrOptions) {
786
+ var b64;
787
+
788
+ var options = {
789
+ encoding: 'utf8'
790
+ };
791
+
792
+ if (encodingOrOptions) {
793
+ if (typeof encodingOrOptions === 'string') {
794
+ options.encoding = encodingOrOptions;
795
+ } else if (typeof encodingOrOptions === 'object') {
796
+ options = encodingOrOptions;
797
+ }
798
+ }
799
+
800
+ if (options.encoding === 'utf8') {
801
+ b64 = base64.encode(utf8.encode(contents));
802
+ } else if (options.encoding === 'ascii') {
803
+ b64 = base64.encode(contents);
804
+ } else if (options.encoding === 'base64') {
805
+ b64 = contents;
806
+ } else {
807
+ throw new Error('Invalid encoding type "' + options.encoding + '"');
808
+ }
809
+
810
+ return RNFSManager.appendFile(normalizeFilePath(filepath), b64);
811
+ },
812
+
813
+ write(filepath, contents, position, encodingOrOptions) {
814
+ var b64;
815
+
816
+ var options = {
817
+ encoding: 'utf8'
818
+ };
819
+
820
+ if (encodingOrOptions) {
821
+ if (typeof encodingOrOptions === 'string') {
822
+ options.encoding = encodingOrOptions;
823
+ } else if (typeof encodingOrOptions === 'object') {
824
+ options = encodingOrOptions;
825
+ }
826
+ }
827
+
828
+ if (options.encoding === 'utf8') {
829
+ b64 = base64.encode(utf8.encode(contents));
830
+ } else if (options.encoding === 'ascii') {
831
+ b64 = base64.encode(contents);
832
+ } else if (options.encoding === 'base64') {
833
+ b64 = contents;
834
+ } else {
835
+ throw new Error('Invalid encoding type "' + options.encoding + '"');
836
+ }
837
+
838
+ if (position === undefined) {
839
+ position = -1;
840
+ }
841
+
842
+ return RNFSManager.write(normalizeFilePath(filepath), b64, position).then(() => void 0);
843
+ },
844
+
845
+ downloadFile(options) {
846
+ if (typeof options !== 'object') throw new Error('downloadFile: Invalid value for argument `options`');
847
+ if (typeof options.fromUrl !== 'string') throw new Error('downloadFile: Invalid value for property `fromUrl`');
848
+ if (typeof options.toFile !== 'string') throw new Error('downloadFile: Invalid value for property `toFile`');
849
+ if (options.headers && typeof options.headers !== 'object') throw new Error('downloadFile: Invalid value for property `headers`');
850
+ if (options.background && typeof options.background !== 'boolean') throw new Error('downloadFile: Invalid value for property `background`');
851
+ if (options.progressDivider && typeof options.progressDivider !== 'number') throw new Error('downloadFile: Invalid value for property `progressDivider`');
852
+ if (options.progressInterval && typeof options.progressInterval !== 'number') throw new Error('downloadFile: Invalid value for property `progressInterval`');
853
+ if (options.readTimeout && typeof options.readTimeout !== 'number') throw new Error('downloadFile: Invalid value for property `readTimeout`');
854
+ if (options.connectionTimeout && typeof options.connectionTimeout !== 'number') throw new Error('downloadFile: Invalid value for property `connectionTimeout`');
855
+ if (options.backgroundTimeout && typeof options.backgroundTimeout !== 'number') throw new Error('downloadFile: Invalid value for property `backgroundTimeout`');
856
+
857
+ var jobId = getJobId();
858
+ var subscriptions = [];
859
+
860
+ if (options.begin) {
861
+ subscriptions.push(RNFS_NativeEventEmitter.addListener('DownloadBegin', (res) => {
862
+ if (res.jobId === jobId) options.begin(res);
863
+ }));
864
+ }
865
+
866
+ if (options.progress) {
867
+ subscriptions.push(RNFS_NativeEventEmitter.addListener('DownloadProgress', (res) => {
868
+ if (res.jobId === jobId) options.progress(res);
869
+ }));
870
+ }
871
+
872
+ if (options.resumable) {
873
+ subscriptions.push(RNFS_NativeEventEmitter.addListener('DownloadResumable', (res) => {
874
+ if (res.jobId === jobId) options.resumable(res);
875
+ }));
876
+ }
877
+
878
+ var bridgeOptions = {
879
+ jobId: jobId,
880
+ fromUrl: options.fromUrl,
881
+ toFile: normalizeFilePath(options.toFile),
882
+ headers: options.headers || {},
883
+ background: !!options.background,
884
+ progressDivider: options.progressDivider || 0,
885
+ progressInterval: options.progressInterval || 0,
886
+ readTimeout: options.readTimeout || 15000,
887
+ connectionTimeout: options.connectionTimeout || 5000,
888
+ backgroundTimeout: options.backgroundTimeout || 3600000, // 1 hour
889
+ hasBeginCallback: options.begin instanceof Function,
890
+ hasProgressCallback: options.progress instanceof Function,
891
+ hasResumableCallback: options.resumable instanceof Function,
892
+ };
893
+
894
+ return {
895
+ jobId,
896
+ promise: RNFSManager.downloadFile(bridgeOptions).then(res => {
897
+ subscriptions.forEach(sub => sub.remove());
898
+ return res;
899
+ })
900
+ .catch(e => {
901
+ subscriptions.forEach(sub => sub.remove());
902
+ return Promise.reject(e);
903
+ })
904
+ };
905
+ },
906
+
907
+ uploadFiles(options) {
908
+ if (!RNFSManager.uploadFiles) {
909
+ return {
910
+ jobId: -1,
911
+ promise: Promise.reject(new Error('`uploadFiles` is unsupported on this platform'))
912
+ };
913
+ }
914
+
915
+ var jobId = getJobId();
916
+ var subscriptions = [];
917
+
918
+ if (typeof options !== 'object') throw new Error('uploadFiles: Invalid value for argument `options`');
919
+ if (typeof options.toUrl !== 'string') throw new Error('uploadFiles: Invalid value for property `toUrl`');
920
+ if (!Array.isArray(options.files)) throw new Error('uploadFiles: Invalid value for property `files`');
921
+ if (options.headers && typeof options.headers !== 'object') throw new Error('uploadFiles: Invalid value for property `headers`');
922
+ if (options.fields && typeof options.fields !== 'object') throw new Error('uploadFiles: Invalid value for property `fields`');
923
+ if (options.method && typeof options.method !== 'string') throw new Error('uploadFiles: Invalid value for property `method`');
924
+
925
+ if (options.begin) {
926
+ subscriptions.push(RNFS_NativeEventEmitter.addListener('UploadBegin', options.begin));
927
+ } else if (options.beginCallback) {
928
+ // Deprecated
929
+ subscriptions.push(RNFS_NativeEventEmitter.addListener('UploadBegin', options.beginCallback));
930
+ }
931
+
932
+ if (options.progress) {
933
+ subscriptions.push(RNFS_NativeEventEmitter.addListener('UploadProgress', options.progress));
934
+ } else if (options.progressCallback) {
935
+ // Deprecated
936
+ subscriptions.push(RNFS_NativeEventEmitter.addListener('UploadProgress', options.progressCallback));
937
+ }
938
+
939
+ var bridgeOptions = {
940
+ jobId: jobId,
941
+ toUrl: options.toUrl,
942
+ files: options.files,
943
+ binaryStreamOnly: options.binaryStreamOnly || false,
944
+ headers: options.headers || {},
945
+ fields: options.fields || {},
946
+ method: options.method || 'POST',
947
+ hasBeginCallback: options.begin instanceof Function || options.beginCallback instanceof Function,
948
+ hasProgressCallback: options.progress instanceof Function || options.progressCallback instanceof Function,
949
+ };
950
+
951
+ return {
952
+ jobId,
953
+ promise: RNFSManager.uploadFiles(bridgeOptions).then(res => {
954
+ subscriptions.forEach(sub => sub.remove());
955
+ return res;
956
+ }).catch(e => {
957
+ subscriptions.forEach(sub => sub.remove());
958
+ return Promise.reject(e);
959
+ })
960
+ };
961
+ },
962
+
963
+ touch(filepath, mtime, ctime) {
964
+ if (ctime && !(ctime instanceof Date)) throw new Error('touch: Invalid value for argument `ctime`');
965
+ if (mtime && !(mtime instanceof Date)) throw new Error('touch: Invalid value for argument `mtime`');
966
+ var ctimeTime = 0;
967
+ if (isIOS) {
968
+ ctimeTime = ctime && ctime.getTime();
969
+ }
970
+ return RNFSManager.touch(
971
+ normalizeFilePath(filepath),
972
+ mtime && mtime.getTime(),
973
+ ctimeTime
974
+ );
975
+ },
976
+
977
+ scanFile(path) {
978
+ return RNFSManager.scanFile(path);
979
+ },
980
+
981
+ MainBundlePath: __dirname,
982
+ CachesDirectoryPath: path.join(os.tmpdir(), 'rnfs-cache'),
983
+ ExternalCachesDirectoryPath: path.join(os.tmpdir(), 'rnfs-cache'),
984
+ DocumentDirectoryPath: path.join(homeDir, 'Documents'),
985
+ DownloadDirectoryPath: path.join(homeDir, 'Downloads'),
986
+ ExternalDirectoryPath: path.join(homeDir, 'files'),
987
+ ExternalStorageDirectoryPath: homeDir,
988
+ TemporaryDirectoryPath: os.tmpdir(),
989
+ LibraryDirectoryPath: path.join(homeDir, 'Library'),
990
+ PicturesDirectoryPath: path.join(homeDir, 'Pictures'),
991
+ FileProtectionKeys: null
992
+ };
993
+
994
+ var isIOS = false; // React Native Platform.OS is unavailable; Node.js runtime is not iOS
995
+
996
+ module.exports = RNFS;
997
+ exports = RNFS;