react-native-mosquito-transport 0.0.27 → 0.0.29

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.
@@ -1,16 +1,25 @@
1
1
  package com.mosquitodb;
2
2
 
3
+ import android.util.ArrayMap;
4
+
3
5
  import androidx.annotation.NonNull;
6
+ import androidx.annotation.Nullable;
4
7
 
5
- import com.facebook.react.bridge.Promise;
6
8
  import com.facebook.react.bridge.ReactApplicationContext;
7
9
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
8
10
  import com.facebook.react.bridge.ReactMethod;
11
+ import com.facebook.react.bridge.ReadableMap;
12
+ import com.facebook.react.bridge.WritableMap;
13
+ import com.facebook.react.bridge.WritableNativeMap;
9
14
  import com.facebook.react.module.annotations.ReactModule;
15
+ import com.facebook.react.modules.core.DeviceEventManagerModule;
16
+ import com.mosquitodb.utils.FileUploader;
17
+ import com.mosquitodb.utils.UploadCallback;
10
18
 
11
19
  @ReactModule(name = MosquitodbModule.NAME)
12
20
  public class MosquitodbModule extends ReactContextBaseJavaModule {
13
21
  public static final String NAME = "Mosquitodb";
22
+ private final ArrayMap<String, FileUploader> uploaderMap = new ArrayMap<>();
14
23
 
15
24
  public MosquitodbModule(ReactApplicationContext reactContext) {
16
25
  super(reactContext);
@@ -22,11 +31,52 @@ public class MosquitodbModule extends ReactContextBaseJavaModule {
22
31
  return NAME;
23
32
  }
24
33
 
34
+ @ReactMethod
35
+ public void uploadFile(ReadableMap readable) {
36
+ String processId = readable.getString("processID");
37
+ FileUploader uploader = new FileUploader();
38
+
39
+ uploader.uploadFile(readable, new UploadCallback() {
40
+ @Override
41
+ public void onProgress(long sentBytes, long totalBytes) {
42
+ WritableMap progress = new WritableNativeMap();
43
+ progress.putInt("sentBytes", (int) sentBytes);
44
+ progress.putInt("totalBytes", (int) totalBytes);
45
+ progress.putString("processID", processId);
46
+ sendEvent("mt-uploading-progress", progress);
47
+ }
48
+
49
+ @Override
50
+ public void onComplete(int responseCode, String responseBody) {
51
+ WritableMap statusData = new WritableNativeMap();
52
+ statusData.putString("processID", processId);
53
+ statusData.putString("result", responseBody);
54
+ sendEvent("mt-uploading-status", statusData);
55
+ }
56
+
57
+ @Override
58
+ public void onError(Exception e) {
59
+ WritableMap statusData = new WritableNativeMap();
60
+ statusData.putString("processID", processId);
61
+ statusData.putString("error", "internal_error");
62
+ statusData.putString("errorDes", e.getLocalizedMessage());
63
+ sendEvent("mt-uploading-status", statusData);
64
+ }
65
+ });
66
+ uploaderMap.put(processId, uploader);
67
+ }
25
68
 
26
- // Example method
27
- // See https://reactnative.dev/docs/native-modules-android
28
69
  @ReactMethod
29
- public void multiply(double a, double b, Promise promise) {
30
- promise.resolve(a * b);
70
+ public void cancelUpload(String processID) {
71
+ FileUploader uploader = uploaderMap.get(processID);
72
+ if (uploader != null) {
73
+ uploader.cancelUpload();
74
+ }
75
+ }
76
+
77
+ private void sendEvent(String eventName, @Nullable WritableMap params) {
78
+ getReactApplicationContext()
79
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
80
+ .emit(eventName, params);
31
81
  }
32
82
  }
@@ -0,0 +1,101 @@
1
+ package com.mosquitodb.utils;
2
+
3
+ import java.io.*;
4
+ import java.net.*;
5
+ import java.util.Objects;
6
+
7
+ import android.net.Uri;
8
+
9
+ import com.facebook.react.bridge.ReadableMap;
10
+ import com.facebook.react.bridge.ReadableMapKeySetIterator;
11
+
12
+ public class FileUploader {
13
+
14
+ private volatile boolean isCancelled = false;
15
+
16
+ public void cancelUpload() {
17
+ isCancelled = true;
18
+ }
19
+
20
+ public void uploadFile(ReadableMap config, UploadCallback callback) {
21
+ new Thread(() -> {
22
+ try {
23
+ Uri fileUri = Uri.parse(config.getString("file"));
24
+ File file = new File(Objects.requireNonNull(fileUri.getPath()));
25
+
26
+ if (!file.exists()) {
27
+ throw new FileNotFoundException("File not found: " + file.getAbsolutePath());
28
+ }
29
+
30
+ long totalBytes = file.length();
31
+ HttpURLConnection connection = (HttpURLConnection) new URL(config.getString("url")).openConnection();
32
+ connection.setDoOutput(true);
33
+ connection.setRequestMethod("POST");
34
+
35
+ ReadableMap extraHeaders = config.getMap("extraHeaders");
36
+ ReadableMapKeySetIterator iterator = Objects.requireNonNull(extraHeaders).keySetIterator();
37
+ while (iterator.hasNextKey()) {
38
+ String key = iterator.nextKey();
39
+ connection.setRequestProperty(key, extraHeaders.getString(key));
40
+ }
41
+
42
+ connection.setRequestProperty("Content-Type", "buffer/upload");
43
+ connection.setRequestProperty("hash-upload", config.getString("createHash"));
44
+ connection.setRequestProperty("Mosquito-Destination", config.getString("destination"));
45
+ if (config.hasKey("authToken")) {
46
+ connection.setRequestProperty("Mosquito-Token", config.getString("authToken"));
47
+ }
48
+ connection.setFixedLengthStreamingMode((int) totalBytes);
49
+
50
+ OutputStream out = connection.getOutputStream();
51
+ FileInputStream in = new FileInputStream(file);
52
+
53
+ byte[] buffer = new byte[8192];
54
+ long sentBytes = 0;
55
+ int bytesRead;
56
+
57
+ while ((bytesRead = in.read(buffer)) != -1) {
58
+ if (isCancelled) {
59
+ in.close();
60
+ out.close();
61
+ connection.disconnect();
62
+ callback.onError(new IOException("Upload cancelled"));
63
+ return;
64
+ }
65
+
66
+ out.write(buffer, 0, bytesRead);
67
+ sentBytes += bytesRead;
68
+ callback.onProgress(sentBytes, totalBytes);
69
+ }
70
+
71
+ out.flush();
72
+ in.close();
73
+ out.close();
74
+
75
+ int responseCode = connection.getResponseCode();
76
+ InputStream responseStream = (responseCode >= 200 && responseCode < 400)
77
+ ? connection.getInputStream()
78
+ : connection.getErrorStream();
79
+ String responseBody = readStream(responseStream);
80
+
81
+ callback.onComplete(responseCode, responseBody);
82
+ connection.disconnect();
83
+ } catch (Exception e) {
84
+ callback.onError(e);
85
+ }
86
+ }).start();
87
+ }
88
+
89
+ private String readStream(InputStream stream) throws IOException {
90
+ if (stream == null) return "";
91
+
92
+ BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
93
+ StringBuilder result = new StringBuilder();
94
+ String line;
95
+ while ((line = reader.readLine()) != null) {
96
+ result.append(line).append("\n");
97
+ }
98
+ reader.close();
99
+ return result.toString().trim();
100
+ }
101
+ }
@@ -0,0 +1,7 @@
1
+ package com.mosquitodb.utils;
2
+
3
+ public interface UploadCallback {
4
+ void onProgress(long sentBytes, long totalBytes);
5
+ void onComplete(int responseCode, String responseBody);
6
+ void onError(Exception e);
7
+ }
@@ -100,7 +100,6 @@ class MosquitodbUploadTask: NSObject, URLSessionDataDelegate {
100
100
  request.setValue(value, forHTTPHeaderField: key)
101
101
  }
102
102
  }
103
- request.setValue("application/json", forHTTPHeaderField: "Accept")
104
103
  if options["authToken"] != nil {
105
104
  request.setValue(options["authToken"] as? String, forHTTPHeaderField: "Mosquito-Token")
106
105
  }
@@ -137,7 +136,7 @@ class MosquitodbUploadTask: NSObject, URLSessionDataDelegate {
137
136
 
138
137
  trigger!([
139
138
  "mt-uploading-progress", [
140
- "sentBtyes": Float(totalBytesSent),
139
+ "sentBytes": Float(totalBytesSent),
141
140
  "totalBytes": Float(totalBytesExpectedToSend),
142
141
  "processID": mainProcessID
143
142
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-mosquito-transport",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "description": "React native javascript sdk for mosquito-transport (https://github.com/brainbehindx/mosquito-transport)",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -42,7 +42,7 @@
42
42
  "peerDependencies": {
43
43
  "react": "*",
44
44
  "react-native": "*",
45
- "react-native-file-access": "*",
45
+ "react-native-fs": "*",
46
46
  "react-native-sha256": "*",
47
47
  "react-native-sqlite-storage": "*"
48
48
  },
@@ -1,28 +1,28 @@
1
1
  import { Platform } from "react-native";
2
2
  import { Buffer } from 'buffer';
3
3
  import { deserialize, serialize } from 'entity-serializer';
4
- import { Dirs, FileSystem } from 'react-native-file-access';
4
+ import { writeFile, mkdir, MainBundlePath, readFile, unlink } from "react-native-fs";
5
5
 
6
6
  const MAX_INLINE_BLOB = 1024;
7
7
 
8
- const DIR_PATH = `${Platform.OS === 'android' ? Dirs.DatabaseDir : Dirs.MainBundleDir}/MOSQUITO`;
8
+ const DIR_PATH = `${Platform.OS === 'android' ? DocumentDirectoryPath.split('/').slice(0, -1).join('/').concat('/database') : MainBundlePath}/MOSQUITO`;
9
9
  const resolvePath = (path = '') => `${DIR_PATH}/${path.startsWith('/') ? path : '/' + path}`;
10
10
 
11
- const DIR_CREATION_PROMISE = FileSystem.mkdir(DIR_PATH).catch(() => null);
11
+ const DIR_CREATION_PROMISE = mkdir(DIR_PATH).catch(() => null);
12
12
 
13
13
  const fsWrite = async (path, data) => {
14
14
  await DIR_CREATION_PROMISE;
15
- return FileSystem.writeFile(resolvePath(path), data instanceof Buffer ? data.toString('base64') : data, 'base64');
15
+ return writeFile(resolvePath(path), data instanceof Buffer ? data.toString('base64') : data, 'base64');
16
16
  }
17
17
 
18
18
  const fsRead = async (path) => {
19
19
  await DIR_CREATION_PROMISE;
20
- return Buffer.from(await FileSystem.readFile(resolvePath(path), 'base64'), 'base64');
20
+ return Buffer.from(await readFile(resolvePath(path), 'base64'), 'base64');
21
21
  }
22
22
 
23
23
  export const getStoreID = (db_filename, table, primary_key) => `${table}_${primary_key}_${db_filename}.blob`;
24
24
 
25
- export const deleteBigData = (store_id) => FileSystem.unlink(resolvePath(store_id));
25
+ export const deleteBigData = (store_id) => unlink(resolvePath(store_id));
26
26
 
27
27
  export const handleBigData = async (store_id, data) => {
28
28
  const bufData = serialize(data);
@@ -11,6 +11,7 @@ export const listenReachableServer = (callback, projectUrl) => {
11
11
  let lastValue;
12
12
  return ServerReachableListener.listenTo(projectUrl, t => {
13
13
  if (typeof t === 'boolean' && t !== lastValue) callback?.(t);
14
+ lastValue = t;
14
15
  }, true);
15
16
  };
16
17
 
package/src/index.d.ts CHANGED
@@ -427,7 +427,7 @@ interface DocumentWriteValue {
427
427
 
428
428
  interface RNMTAuth {
429
429
  customSignin: (email: string, password: string) => Promise<SigninResult>;
430
- customSignup: (email: string, password: string, name?: string, metadata?: Object) => Promise<SigninResult>;
430
+ customSignup: (email: string, password: string, name?: string, metadata?: Object) => Promise<SignupResult>;
431
431
  googleSignin: (token: string) => Promise<SignupResult>;
432
432
  appleSignin: () => Promise<SignupResult>;
433
433
  facebookSignin: () => Promise<SignupResult>;
@@ -531,7 +531,7 @@ interface DownloadProgressStats {
531
531
  }
532
532
 
533
533
  interface UploadProgressStats {
534
- sentBtyes: number;
534
+ sentBytes: number;
535
535
  totalBytes: number;
536
536
  }
537
537
 
package/src/index.js CHANGED
@@ -21,7 +21,8 @@ const {
21
21
  _listenDocument,
22
22
  _startDisconnectWriteTask,
23
23
  _cancelDisconnectWriteTask,
24
- _listenUserVerification
24
+ _listenUserVerification,
25
+ _areYouOk
25
26
  } = EngineApi;
26
27
 
27
28
  // https://socket.io/docs/v3/emit-cheatsheet/#reserved-events
@@ -73,12 +74,7 @@ class RNMT {
73
74
  _from_base: true
74
75
  }
75
76
  });
76
-
77
- socket.on('_signal_signout', () => {
78
- this.auth().signOut();
79
- });
80
-
81
- socket.on('connect', () => {
77
+ const onConnect = () => {
82
78
  isConnected = true;
83
79
  Scoped.IS_CONNECTED[projectUrl] = true;
84
80
  if (queuedToken) updateMountedToken(queuedToken.token);
@@ -86,12 +82,28 @@ class RNMT {
86
82
  awaitStore().then(() => {
87
83
  trySendPendingWrite(projectUrl);
88
84
  });
89
- });
90
-
91
- socket.on('disconnect', () => {
85
+ };
86
+ const onDisconnect = () => {
92
87
  isConnected = false;
93
88
  Scoped.IS_CONNECTED[projectUrl] = false;
94
89
  ServerReachableListener.dispatch(projectUrl, false);
90
+ }
91
+
92
+ const manualCheckConnection = () =>
93
+ fetch(_areYouOk(projectUrl), { cache: 'no-cache' }).then(async r => {
94
+ if ((await r.json()).status === 'yes') {
95
+ onConnect();
96
+ } else throw null;
97
+ }).catch(onDisconnect);
98
+ manualCheckConnection();
99
+
100
+ socket.on('_signal_signout', () => {
101
+ this.auth().signOut();
102
+ });
103
+
104
+ socket.on('connect', onConnect);
105
+ socket.on('disconnect', () => {
106
+ manualCheckConnection();
95
107
  });
96
108
 
97
109
  const updateMountedToken = (token) => {
@@ -220,7 +220,8 @@ const doCustomSignup = (builder, email, password, name, metadata) => new Promise
220
220
  resolve({
221
221
  user: parseToken(r.result.token),
222
222
  token: r.result.token,
223
- refreshToken: r.result.refreshToken
223
+ refreshToken: r.result.refreshToken,
224
+ isNewUser: !!r.result.isNewUser
224
225
  });
225
226
  await injectFreshToken(builder, r.result);
226
227
  revokeAuthIntance(builder, thisAuthStore);
@@ -118,8 +118,14 @@ export class MTStorage {
118
118
  const { createHash, awaitServer } = options || {};
119
119
  let hasFinished, hasCancelled;
120
120
 
121
+ const thisComplete = (...args) => {
122
+ if (hasFinished) return;
123
+ hasFinished = true;
124
+ onComplete?.(...args);
125
+ }
126
+
121
127
  if (typeof file !== 'string' || !file.trim()) {
122
- onComplete?.({ error: 'file_path_invalid', message: 'file must be a non-empty string in uploadFile()' });
128
+ thisComplete?.({ error: 'file_path_invalid', message: 'file must be a non-empty string in uploadFile()' });
123
129
  return () => { };
124
130
  }
125
131
  destination = destination?.trim?.();
@@ -127,12 +133,11 @@ export class MTStorage {
127
133
  try {
128
134
  validateDestination(destination);
129
135
  } catch (error) {
130
- onComplete?.({ error: 'destination_invalid', message: error });
136
+ thisComplete?.({ error: 'destination_invalid', message: error });
131
137
  return () => { };
132
138
  }
133
139
 
134
- const isAsset = (file.startsWith('ph://') || file.startsWith('content://'));
135
-
140
+ const isAsset = file.startsWith('ph://') || file.startsWith('content://');
136
141
  file = isAsset ? file.trim() : prefixStoragePath(file.trim());
137
142
 
138
143
  const { projectUrl, uglify, extraHeaders } = this.builder;
@@ -143,30 +148,28 @@ export class MTStorage {
143
148
  await awaitRefreshToken(projectUrl);
144
149
 
145
150
  if (hasCancelled) return;
146
- const progressListener = emitter.addListener('mt-uploading-progress', ({ processID: ref, sentBtyes, totalBytes }) => {
151
+ const progressListener = emitter.addListener('mt-uploading-progress', ({ processID: ref, sentBytes, totalBytes }) => {
147
152
  if (processID !== ref || hasFinished || hasCancelled) return;
148
- onProgress?.({ sentBtyes, totalBytes });
153
+ onProgress?.({ sentBytes, totalBytes });
149
154
  });
150
155
  const resultListener = emitter.addListener('mt-uploading-status', ({ processID: ref, error, errorDes, result }) => {
151
- if (processID !== ref || hasFinished) return;
156
+ if (processID !== ref) return;
152
157
  if (result)
153
158
  try {
154
159
  result = JSON.parse(result);
155
- } catch (e) { }
160
+ } catch (_) { }
156
161
 
157
162
  const downloadUrl = result?.downloadUrl || undefined;
158
-
159
- if (!hasFinished && !hasCancelled)
160
- onComplete?.(downloadUrl ? undefined : (result?.simpleError || { error, message: errorDes }), downloadUrl);
163
+ thisComplete?.(downloadUrl ? undefined : (result?.simpleError || { error, message: errorDes }), downloadUrl);
161
164
  resultListener.remove();
162
165
  progressListener.remove();
163
- hasFinished = true;
164
166
  });
167
+ const authToken = Scoped.AuthJWTToken[projectUrl];
165
168
 
166
169
  RNMTModule.uploadFile({
167
170
  url: EngineApi._uploadFile(projectUrl, uglify),
168
171
  file: isAsset ? file : file.substring('file://'.length),
169
- authToken: Scoped.AuthJWTToken[projectUrl],
172
+ ...authToken ? { authToken } : {},
170
173
  createHash: createHash ? 'yes' : 'no',
171
174
  destination,
172
175
  processID,
@@ -180,8 +183,8 @@ export class MTStorage {
180
183
  if (hasFinished || hasCancelled) return;
181
184
  hasCancelled = true;
182
185
  setTimeout(() => {
183
- onComplete?.({ error: 'upload_aborted', message: 'The upload process was aborted' });
184
- }, 1);
186
+ thisComplete?.({ error: 'upload_aborted', message: 'The upload process was aborted' });
187
+ }, 0);
185
188
  RNMTModule.cancelUpload(processID);
186
189
  }
187
190
  }