@sodyo/react-native-sodyo-sdk 5.0.1 → 5.1.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/android/CODE_ANALYSIS.md +464 -0
- package/android/build.gradle +4 -17
- package/android/src/main/java/com/sodyo/RNSodyoSdk/ConversionUtil.java +26 -5
- package/android/src/main/java/com/sodyo/RNSodyoSdk/RNSodyoSdkModule.java +69 -13
- package/android/src/main/java/com/sodyo/RNSodyoSdk/RNSodyoSdkView.java +9 -6
- package/ios/CODE_ANALYSIS.md +360 -0
- package/ios/RNSodyoScanner.h +3 -4
- package/ios/RNSodyoScanner.m +6 -5
- package/ios/RNSodyoSdk.h +1 -3
- package/ios/RNSodyoSdk.m +72 -30
- package/ios/RNSodyoSdkManager.m +40 -17
- package/ios/RNSodyoSdkView.h +4 -2
- package/ios/RNSodyoSdkView.m +36 -12
- package/package.json +1 -1
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
# Android RNSodyoSdk Code Analysis
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Analysis of all Android source files in the `react-native-sodyo-sdk` bridge layer. Found **4 critical**, **5 major**, and **6 minor** issues across 4 Java files and `build.gradle`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Critical Issues
|
|
10
|
+
|
|
11
|
+
### 1. NullPointerException — `getCurrentActivity()` Used Without Null Check
|
|
12
|
+
|
|
13
|
+
**File:** `RNSodyoSdkModule.java:235-236, 242-243`
|
|
14
|
+
|
|
15
|
+
```java
|
|
16
|
+
// start()
|
|
17
|
+
Activity activity = getCurrentActivity();
|
|
18
|
+
activity.startActivityForResult(intent, SODYO_SCANNER_REQUEST_CODE);
|
|
19
|
+
|
|
20
|
+
// close()
|
|
21
|
+
Activity activity = getCurrentActivity();
|
|
22
|
+
activity.finishActivity(SODYO_SCANNER_REQUEST_CODE);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`getCurrentActivity()` returns `null` when the React Native host activity is not in the foreground (e.g., during transitions, after config changes, or when called too early). This crashes with `NullPointerException`.
|
|
26
|
+
|
|
27
|
+
The same pattern appears in `setTroubleshootMode()` (line 323), `setNormalMode()` (line 330), `startTroubleshoot()` (line 316), and `performMarker()` (line 309) — **6 call sites total**.
|
|
28
|
+
|
|
29
|
+
**Severity:** Critical — crash in production when activity is not available.
|
|
30
|
+
|
|
31
|
+
**Fix:** Add null checks to every call site:
|
|
32
|
+
|
|
33
|
+
```java
|
|
34
|
+
@ReactMethod
|
|
35
|
+
public void start() {
|
|
36
|
+
Log.i(TAG, "start()");
|
|
37
|
+
Activity activity = getCurrentActivity();
|
|
38
|
+
if (activity == null) {
|
|
39
|
+
Log.e(TAG, "start(): current activity is null");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
Intent intent = new Intent(activity, SodyoScannerActivity.class);
|
|
43
|
+
activity.startActivityForResult(intent, SODYO_SCANNER_REQUEST_CODE);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
### 2. `IllegalArgumentException` Crash in `setEnv()` — No Validation
|
|
50
|
+
|
|
51
|
+
**File:** `RNSodyoSdkModule.java:350`
|
|
52
|
+
|
|
53
|
+
```java
|
|
54
|
+
String value = String.valueOf(SodyoEnv.valueOf(env.trim().toUpperCase()).getValue());
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`Enum.valueOf()` throws `IllegalArgumentException` if the input string doesn't match any enum constant. Any JS call like `setEnv("staging")` crashes the app.
|
|
58
|
+
|
|
59
|
+
Additionally, if `env` is `null`, `env.trim()` throws `NullPointerException`.
|
|
60
|
+
|
|
61
|
+
**Severity:** Critical — crash from any unexpected JS input.
|
|
62
|
+
|
|
63
|
+
**Fix:**
|
|
64
|
+
|
|
65
|
+
```java
|
|
66
|
+
@ReactMethod
|
|
67
|
+
private void setEnv(String env) {
|
|
68
|
+
Log.i(TAG, "setEnv:" + env);
|
|
69
|
+
if (env == null) {
|
|
70
|
+
Log.e(TAG, "setEnv: env is null");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
SodyoEnv sodyoEnv = SodyoEnv.valueOf(env.trim().toUpperCase());
|
|
75
|
+
Map<String, String> params = new HashMap<>();
|
|
76
|
+
params.put("webad_env", String.valueOf(sodyoEnv.getValue()));
|
|
77
|
+
params.put("scanner_QR_code_enabled", "false");
|
|
78
|
+
Sodyo.setScannerParams(params);
|
|
79
|
+
} catch (IllegalArgumentException e) {
|
|
80
|
+
Log.e(TAG, "setEnv: unknown env '" + env + "', expected DEV/QA/PROD");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### 3. Fragment View is Null After `commitAllowingStateLoss`
|
|
88
|
+
|
|
89
|
+
**File:** `RNSodyoSdkView.java:67-70`
|
|
90
|
+
|
|
91
|
+
```java
|
|
92
|
+
fragmentTransaction.add(sodyoFragment, TAG_FRAGMENT).commitAllowingStateLoss();
|
|
93
|
+
fragmentManager.executePendingTransactions();
|
|
94
|
+
view.addView(sodyoFragment.getView(), ...);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The fragment is added **without a container ID** (headless fragment). After `commitAllowingStateLoss()` + `executePendingTransactions()`, the fragment's `onCreateView` may not have been called yet, so `sodyoFragment.getView()` can return `null`. Calling `addView(null, ...)` throws `IllegalArgumentException`.
|
|
98
|
+
|
|
99
|
+
Even if the view is non-null, because the fragment is headless (no container), the fragment lifecycle doesn't manage the view's attachment to the layout — leading to lifecycle mismatches.
|
|
100
|
+
|
|
101
|
+
**Severity:** Critical — crash or blank scanner view.
|
|
102
|
+
|
|
103
|
+
**Fix:** Add the fragment to the container by ID instead of headless:
|
|
104
|
+
|
|
105
|
+
```java
|
|
106
|
+
view.setId(View.generateViewId());
|
|
107
|
+
fragmentTransaction.add(view.getId(), sodyoFragment, TAG_FRAGMENT).commitAllowingStateLoss();
|
|
108
|
+
fragmentManager.executePendingTransactions();
|
|
109
|
+
// Fragment's view is now automatically placed inside `view`
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### 4. ActivityEventListener Never Removed — Leak
|
|
115
|
+
|
|
116
|
+
**File:** `RNSodyoSdkModule.java:75`
|
|
117
|
+
|
|
118
|
+
```java
|
|
119
|
+
this.reactContext.addActivityEventListener(mActivityEventListener);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The listener is added in the constructor but never removed. The `RNSodyoSdkModule` holds a reference to `reactContext`, and `reactContext` holds a reference back via the listener — creating a mutual reference that prevents garbage collection of both.
|
|
123
|
+
|
|
124
|
+
**Severity:** Critical — memory leak of the entire module and React context on reload.
|
|
125
|
+
|
|
126
|
+
**Fix:** Override `onCatalystInstanceDestroy()`:
|
|
127
|
+
|
|
128
|
+
```java
|
|
129
|
+
@Override
|
|
130
|
+
public void onCatalystInstanceDestroy() {
|
|
131
|
+
super.onCatalystInstanceDestroy();
|
|
132
|
+
reactContext.removeActivityEventListener(mActivityEventListener);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Major Issues
|
|
139
|
+
|
|
140
|
+
### 5. Deprecated `android.app.Fragment` API
|
|
141
|
+
|
|
142
|
+
**File:** `RNSodyoSdkView.java:11-12`
|
|
143
|
+
|
|
144
|
+
```java
|
|
145
|
+
import android.app.Fragment;
|
|
146
|
+
import android.app.FragmentManager;
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`android.app.Fragment` was deprecated in API 28 and **removed in API 30+**. The `@SuppressWarnings("deprecation")` annotation hides the warning but doesn't fix the underlying compatibility issue. On newer devices, this code path may fail or behave unpredictably.
|
|
150
|
+
|
|
151
|
+
**Severity:** Major — forward-compatibility risk on newer Android versions.
|
|
152
|
+
|
|
153
|
+
**Fix:** Migrate to AndroidX `androidx.fragment.app.Fragment` and `FragmentManager`. This requires the host app to use `AppCompatActivity`/`FragmentActivity` (standard in modern RN apps):
|
|
154
|
+
|
|
155
|
+
```java
|
|
156
|
+
import androidx.fragment.app.Fragment;
|
|
157
|
+
import androidx.fragment.app.FragmentManager;
|
|
158
|
+
// ...
|
|
159
|
+
FragmentManager fragmentManager = ((FragmentActivity) currentActivity).getSupportFragmentManager();
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### 6. `Sodyo.getInstance()` Called Without Initialization Guard
|
|
165
|
+
|
|
166
|
+
**File:** `RNSodyoSdkModule.java:109-111`
|
|
167
|
+
|
|
168
|
+
```java
|
|
169
|
+
Sodyo.getInstance().setSodyoScannerCallback(callbackClosure);
|
|
170
|
+
Sodyo.getInstance().setSodyoMarkerContentCallback(callbackClosure);
|
|
171
|
+
Sodyo.getInstance().setSodyoModeCallback(callbackClosure);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
These are called inside `onSodyoAppLoadSuccess`, which is safe. However, `setUserInfo()` at line 251 also calls `Sodyo.getInstance()`. If the JS side calls `setUserInfo` before `init` completes, `getInstance()` may return `null` or throw.
|
|
175
|
+
|
|
176
|
+
**Severity:** Major — crash if methods called before initialization.
|
|
177
|
+
|
|
178
|
+
**Fix:** Add initialization guard:
|
|
179
|
+
|
|
180
|
+
```java
|
|
181
|
+
@ReactMethod
|
|
182
|
+
public void setUserInfo(ReadableMap userInfo) {
|
|
183
|
+
if (!Sodyo.isInitialized()) {
|
|
184
|
+
Log.w(TAG, "setUserInfo: SDK not initialized yet");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (userInfo != null) {
|
|
188
|
+
Sodyo.getInstance().setUserInfo(ConversionUtil.toMap(userInfo));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### 7. `init()` Silently Ignores Re-initialization Callbacks
|
|
196
|
+
|
|
197
|
+
**File:** `RNSodyoSdkModule.java:212-215`
|
|
198
|
+
|
|
199
|
+
```java
|
|
200
|
+
if (Sodyo.isInitialized()) {
|
|
201
|
+
Log.i(TAG, "init(): already initialized, ignore");
|
|
202
|
+
return; // callbacks never invoked
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
If the SDK is already initialized, the success/error callbacks passed from JS are silently dropped. The JS `Promise` or callback will never resolve, potentially leaving the app in a waiting state.
|
|
207
|
+
|
|
208
|
+
**Severity:** Major — JS side hangs waiting for callback that never fires.
|
|
209
|
+
|
|
210
|
+
**Fix:** Invoke the success callback immediately if already initialized:
|
|
211
|
+
|
|
212
|
+
```java
|
|
213
|
+
if (Sodyo.isInitialized()) {
|
|
214
|
+
Log.i(TAG, "init(): already initialized");
|
|
215
|
+
if (successCallback != null) {
|
|
216
|
+
successCallback.invoke();
|
|
217
|
+
}
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### 8. Nested Array Overwrites Parent List in `ConversionUtil.toList()`
|
|
225
|
+
|
|
226
|
+
**File:** `ConversionUtil.java:160`
|
|
227
|
+
|
|
228
|
+
```java
|
|
229
|
+
case Array:
|
|
230
|
+
result = toList(readableArray.getArray(index)); // overwrites entire result!
|
|
231
|
+
break;
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
When a nested array is encountered, the **entire `result` list** is replaced with the nested array contents. All previously accumulated items are lost.
|
|
235
|
+
|
|
236
|
+
**Severity:** Major — data corruption for any array containing nested arrays.
|
|
237
|
+
|
|
238
|
+
**Fix:**
|
|
239
|
+
|
|
240
|
+
```java
|
|
241
|
+
case Array:
|
|
242
|
+
result.add(toList(readableArray.getArray(index)));
|
|
243
|
+
break;
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
### 9. Null Converted to Key/Index String in ConversionUtil
|
|
249
|
+
|
|
250
|
+
**File:** `ConversionUtil.java:43, 139`
|
|
251
|
+
|
|
252
|
+
```java
|
|
253
|
+
// toObject()
|
|
254
|
+
case Null:
|
|
255
|
+
result = key; // returns the key name as the value
|
|
256
|
+
break;
|
|
257
|
+
|
|
258
|
+
// toList()
|
|
259
|
+
case Null:
|
|
260
|
+
result.add(String.valueOf(index)); // returns "0", "1", etc.
|
|
261
|
+
break;
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
When a `null` value is encountered, instead of returning `null`, it returns the key name or the string index. This silently corrupts data — a map `{name: null}` becomes `{name: "name"}`.
|
|
265
|
+
|
|
266
|
+
**Severity:** Major — silent data corruption.
|
|
267
|
+
|
|
268
|
+
**Fix:**
|
|
269
|
+
|
|
270
|
+
```java
|
|
271
|
+
case Null:
|
|
272
|
+
result = null;
|
|
273
|
+
break;
|
|
274
|
+
|
|
275
|
+
// and in toList:
|
|
276
|
+
case Null:
|
|
277
|
+
result.add(null);
|
|
278
|
+
break;
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Minor Issues
|
|
284
|
+
|
|
285
|
+
### 10. `@ReactMethod` on `private` Method
|
|
286
|
+
|
|
287
|
+
**File:** `RNSodyoSdkModule.java:346`
|
|
288
|
+
|
|
289
|
+
```java
|
|
290
|
+
@ReactMethod
|
|
291
|
+
private void setEnv(String env) {
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`@ReactMethod` requires methods to be `public`. While this may work in some React Native versions due to reflection, it violates the contract and may break in future RN versions or with ProGuard/R8 optimization.
|
|
295
|
+
|
|
296
|
+
**Severity:** Minor — may break silently with build optimizations.
|
|
297
|
+
|
|
298
|
+
**Fix:** Change to `public`.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
### 11. `SodyoEnv` Enum Values Appear Swapped
|
|
303
|
+
|
|
304
|
+
**File:** `RNSodyoSdkModule.java:39-41`
|
|
305
|
+
|
|
306
|
+
```java
|
|
307
|
+
public static enum SodyoEnv {
|
|
308
|
+
DEV(3),
|
|
309
|
+
QA(0), // QA = 0?
|
|
310
|
+
PROD(1); // PROD = 1?
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Compare to the iOS side (`RNSodyoSdk.m:138`):
|
|
315
|
+
|
|
316
|
+
```objc
|
|
317
|
+
NSDictionary *envs = @{ @"DEV": @"3", @"QA": @"1", @"PROD": @"0" };
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
The values are **different across platforms**:
|
|
321
|
+
- iOS: DEV=3, QA=1, PROD=0
|
|
322
|
+
- Android: DEV=3, QA=0, PROD=1
|
|
323
|
+
|
|
324
|
+
This means the same JS call produces different server environments on iOS vs Android.
|
|
325
|
+
|
|
326
|
+
**Severity:** Minor (but potentially dangerous) — platform behavior inconsistency.
|
|
327
|
+
|
|
328
|
+
**Fix:** Align values across platforms. Determine the correct mapping from the Sodyo SDK documentation and make both platforms match.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
### 12. `sendEvent()` Called Without Listener Check
|
|
333
|
+
|
|
334
|
+
**File:** `RNSodyoSdkModule.java:356-360`
|
|
335
|
+
|
|
336
|
+
```java
|
|
337
|
+
private void sendEvent(String eventName, @Nullable WritableMap params) {
|
|
338
|
+
this.reactContext
|
|
339
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
340
|
+
.emit(eventName, params);
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
If the JS module is not loaded yet or the Catalyst instance is destroyed, `getJSModule()` can throw. Events sent during module teardown will crash.
|
|
345
|
+
|
|
346
|
+
**Severity:** Minor — crash during shutdown/hot reload.
|
|
347
|
+
|
|
348
|
+
**Fix:**
|
|
349
|
+
|
|
350
|
+
```java
|
|
351
|
+
private void sendEvent(String eventName, @Nullable WritableMap params) {
|
|
352
|
+
if (!reactContext.hasActiveReactInstance()) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
reactContext
|
|
356
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
357
|
+
.emit(eventName, params);
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
### 13. Excessive `Log.i()` in Production
|
|
364
|
+
|
|
365
|
+
**File:** All Java files.
|
|
366
|
+
|
|
367
|
+
Every method logs with `Log.i(TAG, ...)`. Android `Log.i` is visible in production logcat and adds overhead.
|
|
368
|
+
|
|
369
|
+
**Severity:** Minor — performance and information disclosure.
|
|
370
|
+
|
|
371
|
+
**Fix:** Use `BuildConfig.DEBUG` guard or use `Log.d()` instead:
|
|
372
|
+
|
|
373
|
+
```java
|
|
374
|
+
if (BuildConfig.DEBUG) {
|
|
375
|
+
Log.d(TAG, "start()");
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
### 14. Outdated `build.gradle` Configuration
|
|
382
|
+
|
|
383
|
+
**File:** `build.gradle`
|
|
384
|
+
|
|
385
|
+
```groovy
|
|
386
|
+
def DEFAULT_COMPILE_SDK_VERSION = 24 // Android 7.0 — from 2016
|
|
387
|
+
def DEFAULT_BUILD_TOOLS_VERSION = "25.0.2" // 2017 vintage
|
|
388
|
+
def DEFAULT_TARGET_SDK_VERSION = 22 // Android 5.1
|
|
389
|
+
classpath 'com.android.tools.build:gradle:1.3.1' // Gradle plugin from 2015
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
- `compileSdkVersion 24` — misses 8 years of API improvements
|
|
393
|
+
- `targetSdkVersion 22` — Google Play requires minimum 33+ as of 2024
|
|
394
|
+
- `jcenter()` — shut down, only serves cached artifacts
|
|
395
|
+
- `gradle:1.3.1` — ancient plugin, incompatible with modern AGP
|
|
396
|
+
|
|
397
|
+
These defaults are overridden by the host app in most cases, but they cause issues if the host doesn't specify them.
|
|
398
|
+
|
|
399
|
+
**Severity:** Minor (defaults only) — but causes confusion and build issues.
|
|
400
|
+
|
|
401
|
+
**Fix:** Update defaults to modern values:
|
|
402
|
+
|
|
403
|
+
```groovy
|
|
404
|
+
def DEFAULT_COMPILE_SDK_VERSION = 34
|
|
405
|
+
def DEFAULT_TARGET_SDK_VERSION = 34
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Remove `jcenter()` and the `classpath` line (host app provides the plugin).
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
### 15. `toFlatMap()` Assumes All Values Are Strings
|
|
413
|
+
|
|
414
|
+
**File:** `ConversionUtil.java:117`
|
|
415
|
+
|
|
416
|
+
```java
|
|
417
|
+
result.put(key, readableMap.getString(key));
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
If the map contains non-string values (numbers, booleans), `getString()` throws `ClassCastException` or returns unexpected results.
|
|
421
|
+
|
|
422
|
+
**Severity:** Minor — crash if non-string values passed to `setScannerParams`.
|
|
423
|
+
|
|
424
|
+
**Fix:** Convert all values to strings:
|
|
425
|
+
|
|
426
|
+
```java
|
|
427
|
+
ReadableType type = readableMap.getType(key);
|
|
428
|
+
switch (type) {
|
|
429
|
+
case String: result.put(key, readableMap.getString(key)); break;
|
|
430
|
+
case Number: result.put(key, String.valueOf(readableMap.getDouble(key))); break;
|
|
431
|
+
case Boolean: result.put(key, String.valueOf(readableMap.getBoolean(key))); break;
|
|
432
|
+
default: result.put(key, String.valueOf(toObject(readableMap, key))); break;
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Cross-Platform Inconsistencies
|
|
439
|
+
|
|
440
|
+
| Feature | iOS | Android | Issue |
|
|
441
|
+
|---------|-----|---------|-------|
|
|
442
|
+
| `init` re-call | Overwrites callbacks silently | Silently drops callbacks | Both broken, differently |
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Issue Summary Table
|
|
447
|
+
|
|
448
|
+
| # | Severity | File | Line(s) | Issue |
|
|
449
|
+
|---|----------|------|---------|-------|
|
|
450
|
+
| 1 | Critical | RNSodyoSdkModule.java | 235,242,309,316,323,330 | Null activity — NPE crash |
|
|
451
|
+
| 2 | Critical | RNSodyoSdkModule.java | 350 | `valueOf()` crash on invalid env |
|
|
452
|
+
| 3 | Critical | RNSodyoSdkView.java | 67-70 | Fragment view null — crash |
|
|
453
|
+
| 4 | Critical | RNSodyoSdkModule.java | 75 | ActivityEventListener never removed — leak |
|
|
454
|
+
| 5 | Major | RNSodyoSdkView.java | 11-12 | Deprecated `android.app.Fragment` |
|
|
455
|
+
| 6 | Major | RNSodyoSdkModule.java | 251 | `getInstance()` before init — NPE |
|
|
456
|
+
| 7 | Major | RNSodyoSdkModule.java | 212-215 | Re-init silently drops callbacks |
|
|
457
|
+
| 8 | Major | ConversionUtil.java | 160 | Nested array overwrites parent list |
|
|
458
|
+
| 9 | Major | ConversionUtil.java | 43,139 | Null → key/index string — data corruption |
|
|
459
|
+
| 10 | Minor | RNSodyoSdkModule.java | 346 | `@ReactMethod` on private method |
|
|
460
|
+
| 11 | Minor | RNSodyoSdkModule.java | 39-41 | Env enum values differ from iOS |
|
|
461
|
+
| 12 | Minor | RNSodyoSdkModule.java | 356 | `sendEvent` without active instance check |
|
|
462
|
+
| 13 | Minor | All files | — | Excessive `Log.i()` in production |
|
|
463
|
+
| 14 | Minor | build.gradle | 3-6,15 | Outdated SDK/plugin defaults |
|
|
464
|
+
| 15 | Minor | ConversionUtil.java | 117 | `toFlatMap` assumes all values are strings |
|
package/android/build.gradle
CHANGED
|
@@ -1,25 +1,12 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
def
|
|
5
|
-
def DEFAULT_TARGET_SDK_VERSION =
|
|
3
|
+
// Issue #14 fix: update default SDK versions to modern values
|
|
4
|
+
def DEFAULT_COMPILE_SDK_VERSION = 34
|
|
5
|
+
def DEFAULT_TARGET_SDK_VERSION = 34
|
|
6
6
|
def DEFAULT_GOOGLE_PLAY_SERVICES_VERSION = "+"
|
|
7
7
|
|
|
8
|
-
buildscript {
|
|
9
|
-
repositories {
|
|
10
|
-
jcenter()
|
|
11
|
-
mavenCentral()
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
dependencies {
|
|
15
|
-
classpath 'com.android.tools.build:gradle:1.3.1'
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
8
|
android {
|
|
21
9
|
compileSdkVersion project.hasProperty('compileSdkVersion') ? project.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION
|
|
22
|
-
buildToolsVersion project.hasProperty('buildToolsVersion') ? project.buildToolsVersion : DEFAULT_BUILD_TOOLS_VERSION
|
|
23
10
|
|
|
24
11
|
defaultConfig {
|
|
25
12
|
minSdkVersion 16
|
|
@@ -59,4 +46,4 @@ dependencies {
|
|
|
59
46
|
transitive = true
|
|
60
47
|
exclude group: 'com.parse.bolts', module: 'bolts-android'
|
|
61
48
|
}
|
|
62
|
-
}
|
|
49
|
+
}
|
|
@@ -39,8 +39,9 @@ public final class ConversionUtil {
|
|
|
39
39
|
|
|
40
40
|
ReadableType readableType = readableMap.getType(key);
|
|
41
41
|
switch (readableType) {
|
|
42
|
+
// Issue #9 fix: return null instead of the key name
|
|
42
43
|
case Null:
|
|
43
|
-
result =
|
|
44
|
+
result = null;
|
|
44
45
|
break;
|
|
45
46
|
case Boolean:
|
|
46
47
|
result = readableMap.getBoolean(key);
|
|
@@ -101,6 +102,7 @@ public final class ConversionUtil {
|
|
|
101
102
|
* @param readableMap The ReadableMap to be conveted.
|
|
102
103
|
* @return A HashMap containing the data that was in the ReadableMap.
|
|
103
104
|
*/
|
|
105
|
+
// Issue #15 fix: handle non-string value types
|
|
104
106
|
public static Map<String, String> toFlatMap(@Nullable ReadableMap readableMap) {
|
|
105
107
|
if (readableMap == null) {
|
|
106
108
|
return null;
|
|
@@ -114,7 +116,24 @@ public final class ConversionUtil {
|
|
|
114
116
|
Map<String, String> result = new HashMap<>();
|
|
115
117
|
while (iterator.hasNextKey()) {
|
|
116
118
|
String key = iterator.nextKey();
|
|
117
|
-
|
|
119
|
+
ReadableType type = readableMap.getType(key);
|
|
120
|
+
switch (type) {
|
|
121
|
+
case String:
|
|
122
|
+
result.put(key, readableMap.getString(key));
|
|
123
|
+
break;
|
|
124
|
+
case Number:
|
|
125
|
+
result.put(key, String.valueOf(readableMap.getDouble(key)));
|
|
126
|
+
break;
|
|
127
|
+
case Boolean:
|
|
128
|
+
result.put(key, String.valueOf(readableMap.getBoolean(key)));
|
|
129
|
+
break;
|
|
130
|
+
case Null:
|
|
131
|
+
result.put(key, null);
|
|
132
|
+
break;
|
|
133
|
+
default:
|
|
134
|
+
result.put(key, String.valueOf(toObject(readableMap, key)));
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
118
137
|
}
|
|
119
138
|
|
|
120
139
|
return result;
|
|
@@ -135,8 +154,9 @@ public final class ConversionUtil {
|
|
|
135
154
|
for (int index = 0; index < readableArray.size(); index++) {
|
|
136
155
|
ReadableType readableType = readableArray.getType(index);
|
|
137
156
|
switch (readableType) {
|
|
157
|
+
// Issue #9 fix: add null instead of index string
|
|
138
158
|
case Null:
|
|
139
|
-
result.add(
|
|
159
|
+
result.add(null);
|
|
140
160
|
break;
|
|
141
161
|
case Boolean:
|
|
142
162
|
result.add(readableArray.getBoolean(index));
|
|
@@ -156,8 +176,9 @@ public final class ConversionUtil {
|
|
|
156
176
|
case Map:
|
|
157
177
|
result.add(toMap(readableArray.getMap(index)));
|
|
158
178
|
break;
|
|
179
|
+
// Issue #8 fix: add nested array to result instead of overwriting
|
|
159
180
|
case Array:
|
|
160
|
-
result
|
|
181
|
+
result.add(toList(readableArray.getArray(index)));
|
|
161
182
|
break;
|
|
162
183
|
default:
|
|
163
184
|
throw new IllegalArgumentException("Could not convert object with index: " + index + ".");
|
|
@@ -166,4 +187,4 @@ public final class ConversionUtil {
|
|
|
166
187
|
|
|
167
188
|
return result;
|
|
168
189
|
}
|
|
169
|
-
}
|
|
190
|
+
}
|