@sodyo/react-native-sodyo-sdk 5.0.2 → 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 +66 -10
- 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
|
@@ -75,6 +75,13 @@ public class RNSodyoSdkModule extends ReactContextBaseJavaModule {
|
|
|
75
75
|
this.reactContext.addActivityEventListener(mActivityEventListener);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// Issue #4 fix: remove listener on destroy to prevent leak
|
|
79
|
+
@Override
|
|
80
|
+
public void onCatalystInstanceDestroy() {
|
|
81
|
+
super.onCatalystInstanceDestroy();
|
|
82
|
+
reactContext.removeActivityEventListener(mActivityEventListener);
|
|
83
|
+
}
|
|
84
|
+
|
|
78
85
|
@Override
|
|
79
86
|
public String getName() {
|
|
80
87
|
return "RNSodyoSdk";
|
|
@@ -205,12 +212,16 @@ public class RNSodyoSdkModule extends ReactContextBaseJavaModule {
|
|
|
205
212
|
}
|
|
206
213
|
}
|
|
207
214
|
|
|
215
|
+
// Issue #7 fix: invoke success callback if already initialized
|
|
208
216
|
@ReactMethod
|
|
209
217
|
public void init(final String apiKey, Callback successCallback, Callback errorCallback) {
|
|
210
218
|
Log.i(TAG, "init()");
|
|
211
219
|
|
|
212
220
|
if (Sodyo.isInitialized()) {
|
|
213
|
-
Log.i(TAG, "init(): already initialized
|
|
221
|
+
Log.i(TAG, "init(): already initialized");
|
|
222
|
+
if (successCallback != null) {
|
|
223
|
+
successCallback.invoke();
|
|
224
|
+
}
|
|
214
225
|
return;
|
|
215
226
|
}
|
|
216
227
|
|
|
@@ -228,11 +239,16 @@ public class RNSodyoSdkModule extends ReactContextBaseJavaModule {
|
|
|
228
239
|
});
|
|
229
240
|
}
|
|
230
241
|
|
|
242
|
+
// Issue #1 fix: null-check getCurrentActivity()
|
|
231
243
|
@ReactMethod
|
|
232
244
|
public void start() {
|
|
233
245
|
Log.i(TAG, "start()");
|
|
234
|
-
Intent intent = new Intent(this.reactContext, SodyoScannerActivity.class);
|
|
235
246
|
Activity activity = getCurrentActivity();
|
|
247
|
+
if (activity == null) {
|
|
248
|
+
Log.e(TAG, "start(): current activity is null");
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
Intent intent = new Intent(activity, SodyoScannerActivity.class);
|
|
236
252
|
activity.startActivityForResult(intent, SODYO_SCANNER_REQUEST_CODE);
|
|
237
253
|
}
|
|
238
254
|
|
|
@@ -240,13 +256,23 @@ public class RNSodyoSdkModule extends ReactContextBaseJavaModule {
|
|
|
240
256
|
public void close() {
|
|
241
257
|
Log.i(TAG, "close()");
|
|
242
258
|
Activity activity = getCurrentActivity();
|
|
259
|
+
if (activity == null) {
|
|
260
|
+
Log.e(TAG, "close(): current activity is null");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
243
263
|
activity.finishActivity(SODYO_SCANNER_REQUEST_CODE);
|
|
244
264
|
}
|
|
245
265
|
|
|
266
|
+
// Issue #6 fix: guard against uninitialized SDK
|
|
246
267
|
@ReactMethod
|
|
247
268
|
public void setUserInfo(ReadableMap userInfo) {
|
|
248
269
|
Log.i(TAG, "setUserInfo()");
|
|
249
270
|
|
|
271
|
+
if (!Sodyo.isInitialized()) {
|
|
272
|
+
Log.w(TAG, "setUserInfo(): SDK not initialized yet");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
250
276
|
if(userInfo != null) {
|
|
251
277
|
Sodyo.getInstance().setUserInfo(ConversionUtil.toMap(userInfo));
|
|
252
278
|
}
|
|
@@ -307,6 +333,10 @@ public class RNSodyoSdkModule extends ReactContextBaseJavaModule {
|
|
|
307
333
|
public void performMarker(String markerId, ReadableMap customProperties) {
|
|
308
334
|
Log.i(TAG, "performMarker()");
|
|
309
335
|
Activity activity = getCurrentActivity();
|
|
336
|
+
if (activity == null) {
|
|
337
|
+
Log.e(TAG, "performMarker(): current activity is null");
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
310
340
|
Sodyo.performMarker(markerId, activity, ConversionUtil.toMap(customProperties));
|
|
311
341
|
}
|
|
312
342
|
|
|
@@ -314,6 +344,10 @@ public class RNSodyoSdkModule extends ReactContextBaseJavaModule {
|
|
|
314
344
|
public void startTroubleshoot() {
|
|
315
345
|
Log.i(TAG, "startTroubleshoot()");
|
|
316
346
|
Activity activity = getCurrentActivity();
|
|
347
|
+
if (activity == null) {
|
|
348
|
+
Log.e(TAG, "startTroubleshoot(): current activity is null");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
317
351
|
Sodyo.startTroubleshoot(activity);
|
|
318
352
|
}
|
|
319
353
|
|
|
@@ -321,6 +355,10 @@ public class RNSodyoSdkModule extends ReactContextBaseJavaModule {
|
|
|
321
355
|
public void setTroubleshootMode() {
|
|
322
356
|
Log.i(TAG, "setTroubleshootMode()");
|
|
323
357
|
Activity activity = getCurrentActivity();
|
|
358
|
+
if (activity == null) {
|
|
359
|
+
Log.e(TAG, "setTroubleshootMode(): current activity is null");
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
324
362
|
Sodyo.setMode(activity, SettingsHelper.ScannerViewMode.Troubleshoot);
|
|
325
363
|
}
|
|
326
364
|
|
|
@@ -328,6 +366,10 @@ public class RNSodyoSdkModule extends ReactContextBaseJavaModule {
|
|
|
328
366
|
public void setNormalMode() {
|
|
329
367
|
Log.i(TAG, "setNormalMode()");
|
|
330
368
|
Activity activity = getCurrentActivity();
|
|
369
|
+
if (activity == null) {
|
|
370
|
+
Log.e(TAG, "setNormalMode(): current activity is null");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
331
373
|
Sodyo.setMode(activity, SettingsHelper.ScannerViewMode.Normal);
|
|
332
374
|
}
|
|
333
375
|
|
|
@@ -342,20 +384,34 @@ public class RNSodyoSdkModule extends ReactContextBaseJavaModule {
|
|
|
342
384
|
Sodyo.setSodyoLogoVisible(isVisible);
|
|
343
385
|
}
|
|
344
386
|
|
|
387
|
+
// Issue #2 fix: validate env input, Issue #10 fix: public instead of private
|
|
345
388
|
@ReactMethod
|
|
346
|
-
|
|
389
|
+
public void setEnv(String env) {
|
|
347
390
|
Log.i(TAG, "setEnv:" + env);
|
|
348
391
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
392
|
+
if (env == null) {
|
|
393
|
+
Log.e(TAG, "setEnv: env is null");
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
SodyoEnv sodyoEnv = SodyoEnv.valueOf(env.trim().toUpperCase());
|
|
399
|
+
Map<String, String> params = new HashMap<>();
|
|
400
|
+
params.put("webad_env", String.valueOf(sodyoEnv.getValue()));
|
|
401
|
+
params.put("scanner_QR_code_enabled", "false");
|
|
402
|
+
Sodyo.setScannerParams(params);
|
|
403
|
+
} catch (IllegalArgumentException e) {
|
|
404
|
+
Log.e(TAG, "setEnv: unknown env '" + env + "', expected DEV/QA/PROD");
|
|
405
|
+
}
|
|
354
406
|
}
|
|
355
407
|
|
|
408
|
+
// Issue #12 fix: check for active React instance before sending events
|
|
356
409
|
private void sendEvent(String eventName, @Nullable WritableMap params) {
|
|
357
|
-
|
|
410
|
+
if (!reactContext.hasActiveReactInstance()) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
reactContext
|
|
358
414
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
359
415
|
.emit(eventName, params);
|
|
360
416
|
}
|
|
361
|
-
}
|
|
417
|
+
}
|
|
@@ -81,11 +81,14 @@ public class RNSodyoSdkView extends SimpleViewManager<FrameLayout> {
|
|
|
81
81
|
isCameraEnabled = true;
|
|
82
82
|
|
|
83
83
|
try {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
Activity currentActivity = mCallerContext.getCurrentActivity();
|
|
85
|
+
if (currentActivity != null) {
|
|
86
|
+
FragmentManager fragmentManager = currentActivity.getFragmentManager();
|
|
87
|
+
Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT);
|
|
88
|
+
|
|
89
|
+
if (fragment != null) {
|
|
90
|
+
fragmentManager.beginTransaction().remove(fragment).commitNowAllowingStateLoss();
|
|
91
|
+
}
|
|
89
92
|
}
|
|
90
93
|
} catch (Exception e) {
|
|
91
94
|
e.printStackTrace();
|
|
@@ -110,4 +113,4 @@ public class RNSodyoSdkView extends SimpleViewManager<FrameLayout> {
|
|
|
110
113
|
sodyoFragment.stopCamera();
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
|
-
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# iOS RNSodyoSdk Code Analysis
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Analysis of all iOS source files in the `react-native-sodyo-sdk` bridge layer. Found **5 critical**, **4 major**, and **5 minor** issues across 6 files.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Critical Issues
|
|
10
|
+
|
|
11
|
+
### 1. Static Variable in Header File — Duplicate Symbol / Undefined Behavior
|
|
12
|
+
|
|
13
|
+
**File:** `RNSodyoScanner.h:14`
|
|
14
|
+
|
|
15
|
+
```objc
|
|
16
|
+
static UIViewController* sodyoScanner = nil;
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
A `static` variable in a header creates a **separate copy** in every `.m` file that imports it. `RNSodyoSdk.m`, `RNSodyoSdkManager.m`, and `RNSodyoScanner.m` each get their own independent `sodyoScanner`. This means:
|
|
20
|
+
|
|
21
|
+
- `RNSodyoSdkManager` sets `sodyoScanner` via `[RNSodyoScanner setSodyoScanner:]`, but that only updates the copy inside `RNSodyoScanner.m`.
|
|
22
|
+
- `RNSodyoSdk.m` reads its **own local copy** (`sodyoScanner`) which is always `nil` unless assigned directly in that file.
|
|
23
|
+
- `startTroubleshoot` at line 104 uses the local `sodyoScanner` which may be `nil`, causing a silent no-op or crash.
|
|
24
|
+
|
|
25
|
+
**Severity:** Critical — scanner functionality silently broken across modules.
|
|
26
|
+
|
|
27
|
+
**Fix:** Remove the `static` variable from the header. Use the `RNSodyoScanner` class methods exclusively:
|
|
28
|
+
|
|
29
|
+
```objc
|
|
30
|
+
// RNSodyoScanner.h — remove line 14 entirely
|
|
31
|
+
// RNSodyoScanner.m — store the scanner as a class-level static
|
|
32
|
+
static UIViewController* _sharedScanner = nil;
|
|
33
|
+
|
|
34
|
+
@implementation RNSodyoScanner
|
|
35
|
+
+ (UIViewController *)getSodyoScanner { return _sharedScanner; }
|
|
36
|
+
+ (void)setSodyoScanner:(UIViewController *)scanner { _sharedScanner = scanner; }
|
|
37
|
+
@end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then in `RNSodyoSdk.m`, replace all bare `sodyoScanner` references with `[RNSodyoScanner getSodyoScanner]`.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
### 2. Inverted Null-Check Logic — Scanner Never Saved
|
|
45
|
+
|
|
46
|
+
**File:** `RNSodyoSdk.m:153-155`
|
|
47
|
+
|
|
48
|
+
```objc
|
|
49
|
+
if (!sodyoScanner) {
|
|
50
|
+
[RNSodyoScanner setSodyoScanner:sodyoScanner]; // saves nil
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This is inverted. It saves the scanner only when it's `nil`, which stores `nil` into the shared singleton. When `sodyoScanner` is non-nil, it's never persisted.
|
|
55
|
+
|
|
56
|
+
**Severity:** Critical — the scanner reference is never properly shared.
|
|
57
|
+
|
|
58
|
+
**Fix:**
|
|
59
|
+
|
|
60
|
+
```objc
|
|
61
|
+
if (sodyoScanner) {
|
|
62
|
+
[RNSodyoScanner setSodyoScanner:sodyoScanner];
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or better, always assign and let the setter handle dedup (it already does).
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### 3. `BOOL *` (Pointer) Instead of `BOOL` — Always Truthy
|
|
71
|
+
|
|
72
|
+
**File:** `RNSodyoSdk.m:124`
|
|
73
|
+
|
|
74
|
+
```objc
|
|
75
|
+
RCT_EXPORT_METHOD(setSodyoLogoVisible:(BOOL *) isVisible)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`BOOL *` is a pointer-to-BOOL, not a BOOL. React Native will pass a non-null pointer, so the `if (isVisible)` check at line 127 always evaluates to `true` (non-null pointer), making `hideDefaultOverlay` unreachable.
|
|
79
|
+
|
|
80
|
+
**Severity:** Critical — logo can never be hidden from JS.
|
|
81
|
+
|
|
82
|
+
**Fix:**
|
|
83
|
+
|
|
84
|
+
```objc
|
|
85
|
+
RCT_EXPORT_METHOD(setSodyoLogoVisible:(BOOL) isVisible)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### 4. NSNotificationCenter Observer Never Removed — Memory Leak
|
|
91
|
+
|
|
92
|
+
**File:** `RNSodyoSdk.m:35`
|
|
93
|
+
|
|
94
|
+
```objc
|
|
95
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
96
|
+
selector:@selector(sendCloseContentEvent)
|
|
97
|
+
name:@"SodyoNotificationCloseIAD" object:nil];
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The observer is added but never removed. Since `NSNotificationCenter` holds an **unsafe unretained reference** (pre-iOS 9 behavior on block-based API, and always for selector-based), this can:
|
|
101
|
+
|
|
102
|
+
- Cause **crashes** if the module is deallocated while notifications fire.
|
|
103
|
+
- Cause **duplicate event delivery** if `createCloseContentListener` semantics change.
|
|
104
|
+
|
|
105
|
+
**Severity:** Critical — potential crash on deallocation.
|
|
106
|
+
|
|
107
|
+
**Fix:** Add a `dealloc` method:
|
|
108
|
+
|
|
109
|
+
```objc
|
|
110
|
+
- (void)dealloc {
|
|
111
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### 5. Callback Retain Cycle — Blocks Held Indefinitely
|
|
118
|
+
|
|
119
|
+
**File:** `RNSodyoSdk.m:21-22`, `RNSodyoSdk.h:22-23`
|
|
120
|
+
|
|
121
|
+
```objc
|
|
122
|
+
@property (nonatomic, strong) RCTResponseSenderBlock successStartCallback;
|
|
123
|
+
@property (nonatomic, strong) RCTResponseSenderBlock errorStartCallback;
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
These blocks are stored as `strong` properties. If `onSodyoAppLoadSuccess` is called, `errorStartCallback` is **never nilled out** (and vice versa). The unused callback retains captured JS context forever.
|
|
127
|
+
|
|
128
|
+
Additionally, if `init:` is called multiple times, the previous callbacks are silently overwritten and leaked.
|
|
129
|
+
|
|
130
|
+
**Severity:** Critical — memory leak of JS bridge context.
|
|
131
|
+
|
|
132
|
+
**Fix:** Nil both callbacks after either fires:
|
|
133
|
+
|
|
134
|
+
```objc
|
|
135
|
+
- (void)onSodyoAppLoadSuccess:(NSInteger)AppID {
|
|
136
|
+
if (self.successStartCallback) {
|
|
137
|
+
self.successStartCallback(@[[NSNull null]]);
|
|
138
|
+
}
|
|
139
|
+
self.successStartCallback = nil;
|
|
140
|
+
self.errorStartCallback = nil; // release the other one too
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
- (void)onSodyoAppLoadFailed:(NSInteger)AppID error:(NSError *)error {
|
|
144
|
+
if (self.errorStartCallback) {
|
|
145
|
+
self.errorStartCallback(@[@{@"error": error.localizedDescription ?: @"Unknown error"}]);
|
|
146
|
+
}
|
|
147
|
+
self.successStartCallback = nil; // release the other one too
|
|
148
|
+
self.errorStartCallback = nil;
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Major Issues
|
|
155
|
+
|
|
156
|
+
### 6. NSError Passed Directly to JS — Crash Risk
|
|
157
|
+
|
|
158
|
+
**File:** `RNSodyoSdk.m:203`
|
|
159
|
+
|
|
160
|
+
```objc
|
|
161
|
+
self.errorStartCallback(@[@{@"error": error}]);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`NSError` is not JSON-serializable. React Native's bridge expects serializable types. This will throw an exception or produce undefined behavior.
|
|
165
|
+
|
|
166
|
+
**Severity:** Major — crash when SDK load fails.
|
|
167
|
+
|
|
168
|
+
**Fix:**
|
|
169
|
+
|
|
170
|
+
```objc
|
|
171
|
+
NSString *message = error.localizedDescription ?: @"Unknown error";
|
|
172
|
+
self.errorStartCallback(@[@{@"error": message}]);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### 7. Nil Dictionary Value Crash in `sodyoError:`
|
|
178
|
+
|
|
179
|
+
**File:** `RNSodyoSdk.m:211`
|
|
180
|
+
|
|
181
|
+
```objc
|
|
182
|
+
NSArray* params = @[@"sodyoError", error.userInfo[@"NSLocalizedDescription"]];
|
|
183
|
+
[self sendEventWithName:@"EventSodyoError" body:@{@"error": params[1]}];
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
If `error.userInfo[@"NSLocalizedDescription"]` is `nil`, creating the `NSArray` literal crashes with `NSInvalidArgumentException` (nil inserted into immutable array).
|
|
187
|
+
|
|
188
|
+
**Severity:** Major — crash on certain error types.
|
|
189
|
+
|
|
190
|
+
**Fix:**
|
|
191
|
+
|
|
192
|
+
```objc
|
|
193
|
+
NSString *desc = error.localizedDescription ?: @"Unknown error";
|
|
194
|
+
[self sendEventWithName:@"EventSodyoError" body:@{@"error": desc}];
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### 8. Nil Dictionary Value Crash in `setEnv:`
|
|
200
|
+
|
|
201
|
+
**File:** `RNSodyoSdk.m:138-139`
|
|
202
|
+
|
|
203
|
+
```objc
|
|
204
|
+
NSDictionary *envs = @{ @"DEV": @"3", @"QA": @"1", @"PROD": @"0" };
|
|
205
|
+
NSDictionary *params = @{ @"SodyoAdEnv" : envs[env], @"ScanQR": @"false" };
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
If `env` is not one of `DEV`, `QA`, `PROD`, then `envs[env]` returns `nil`. Inserting `nil` into an `NSDictionary` literal crashes.
|
|
209
|
+
|
|
210
|
+
**Severity:** Major — crash on invalid env string from JS.
|
|
211
|
+
|
|
212
|
+
**Fix:**
|
|
213
|
+
|
|
214
|
+
```objc
|
|
215
|
+
NSString *envValue = envs[env];
|
|
216
|
+
if (!envValue) {
|
|
217
|
+
NSLog(@"RNSodyoSdk: Unknown env '%@', defaulting to PROD", env);
|
|
218
|
+
envValue = @"0";
|
|
219
|
+
}
|
|
220
|
+
NSDictionary *params = @{ @"SodyoAdEnv": envValue, @"ScanQR": @"false" };
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### 9. Nil Value Crash in `SodyoMarkerDetectedWithData:`
|
|
226
|
+
|
|
227
|
+
**File:** `RNSodyoSdk.m:218`
|
|
228
|
+
|
|
229
|
+
```objc
|
|
230
|
+
[self sendEventWithName:@"EventMarkerDetectSuccess" body:@{@"data": Data[@"sodyoMarkerData"]}];
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
If `Data` is `nil` or `Data[@"sodyoMarkerData"]` is `nil`, creating the dictionary literal crashes.
|
|
234
|
+
|
|
235
|
+
**Severity:** Major — crash when marker data is missing.
|
|
236
|
+
|
|
237
|
+
**Fix:**
|
|
238
|
+
|
|
239
|
+
```objc
|
|
240
|
+
id markerData = Data[@"sodyoMarkerData"] ?: [NSNull null];
|
|
241
|
+
[self sendEventWithName:@"EventMarkerDetectSuccess" body:@{@"data": markerData}];
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Apply the same pattern to `SodyoMarkerContent:Data:` at line 223.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Minor Issues
|
|
249
|
+
|
|
250
|
+
### 10. Method Named `init:` Shadows NSObject
|
|
251
|
+
|
|
252
|
+
**File:** `RNSodyoSdk.m:14`
|
|
253
|
+
|
|
254
|
+
```objc
|
|
255
|
+
RCT_EXPORT_METHOD(init:(NSString *)apiKey ...)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
While RCT_EXPORT_METHOD creates a different Objective-C selector internally, naming the JS method `init` is confusing and risks future collision with `NSObject`'s `init` family. Static analyzers may flag this.
|
|
259
|
+
|
|
260
|
+
**Fix:** Rename to `initialize:` or `setup:` on the native side, or document the JS-side name mapping.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### 11. Deprecated `window` Access Pattern
|
|
265
|
+
|
|
266
|
+
**File:** `RNSodyoSdk.m:95,162,170` and `RNSodyoSdkManager.m:63`
|
|
267
|
+
|
|
268
|
+
```objc
|
|
269
|
+
UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
`UIApplicationDelegate.window` is deprecated in iOS 15+ with UIScene. On apps using scenes, this returns `nil`, breaking all scanner presentation.
|
|
273
|
+
|
|
274
|
+
**Fix:** Use the key window from connected scenes:
|
|
275
|
+
|
|
276
|
+
```objc
|
|
277
|
+
UIWindow *keyWindow = nil;
|
|
278
|
+
for (UIWindowScene *scene in [UIApplication sharedApplication].connectedScenes) {
|
|
279
|
+
if (scene.activationState == UISceneActivationStateForegroundActive) {
|
|
280
|
+
for (UIWindow *window in scene.windows) {
|
|
281
|
+
if (window.isKeyWindow) {
|
|
282
|
+
keyWindow = window;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
UIViewController *rootViewController = keyWindow.rootViewController;
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
### 12. Child View Controller Not Removed on Cleanup
|
|
294
|
+
|
|
295
|
+
**File:** `RNSodyoSdkManager.m:68`
|
|
296
|
+
|
|
297
|
+
```objc
|
|
298
|
+
[rootViewController addChildViewController:sodyoScanner];
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The scanner is added as a child view controller but never removed via `removeFromParentViewController` when the React view is unmounted. This leaks the child VC relationship.
|
|
302
|
+
|
|
303
|
+
**Fix:** Override cleanup in `RNSodyoSdkView` or the manager to call `[sodyoScanner removeFromParentViewController]`.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
### 13. `isTroubleShootingEnabled` Has No `false` Handler
|
|
308
|
+
|
|
309
|
+
**File:** `RNSodyoSdkManager.m:45-59`
|
|
310
|
+
|
|
311
|
+
```objc
|
|
312
|
+
if ([RCTConvert BOOL:json]) {
|
|
313
|
+
[SodyoSDK startTroubleshoot:sodyoScanner];
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
// nothing happens when false
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Setting `isTroubleShootingEnabled={false}` is a no-op. There's no call to stop troubleshooting.
|
|
320
|
+
|
|
321
|
+
**Fix:** Add the false branch, e.g., `[SodyoSDK setMode:sodyoScanner mode:SodyoModeNormal]`.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
### 14. Excessive `NSLog` in Production
|
|
326
|
+
|
|
327
|
+
**Files:** All `.m` files
|
|
328
|
+
|
|
329
|
+
Every method logs via `NSLog`, which writes to the system log in production builds. This is a minor performance issue and exposes internal method names.
|
|
330
|
+
|
|
331
|
+
**Fix:** Replace with `RCTLogInfo` (already used in one place) or wrap in `#ifdef DEBUG`:
|
|
332
|
+
|
|
333
|
+
```objc
|
|
334
|
+
#ifdef DEBUG
|
|
335
|
+
#define SDLog(...) NSLog(__VA_ARGS__)
|
|
336
|
+
#else
|
|
337
|
+
#define SDLog(...) ((void)0)
|
|
338
|
+
#endif
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Issue Summary Table
|
|
344
|
+
|
|
345
|
+
| # | Severity | File | Line | Issue |
|
|
346
|
+
|---|----------|------|------|-------|
|
|
347
|
+
| 1 | Critical | RNSodyoScanner.h | 14 | Static var in header — separate copies per file |
|
|
348
|
+
| 2 | Critical | RNSodyoSdk.m | 153 | Inverted null-check — scanner never saved |
|
|
349
|
+
| 3 | Critical | RNSodyoSdk.m | 124 | `BOOL *` instead of `BOOL` — always truthy |
|
|
350
|
+
| 4 | Critical | RNSodyoSdk.m | 35 | NSNotification observer never removed |
|
|
351
|
+
| 5 | Critical | RNSodyoSdk.m | 21-22 | Callback retain cycle — unused block never nilled |
|
|
352
|
+
| 6 | Major | RNSodyoSdk.m | 203 | NSError sent to JS — not serializable |
|
|
353
|
+
| 7 | Major | RNSodyoSdk.m | 211 | Nil value in array literal — crash |
|
|
354
|
+
| 8 | Major | RNSodyoSdk.m | 139 | Nil value in dict literal — crash on bad env |
|
|
355
|
+
| 9 | Major | RNSodyoSdk.m | 218 | Nil marker data — crash |
|
|
356
|
+
| 10 | Minor | RNSodyoSdk.m | 14 | Method named `init:` shadows NSObject |
|
|
357
|
+
| 11 | Minor | Multiple | — | Deprecated `window` access (iOS 15+) |
|
|
358
|
+
| 12 | Minor | RNSodyoSdkManager.m | 68 | Child VC never removed |
|
|
359
|
+
| 13 | Minor | RNSodyoSdkManager.m | 55 | No false-branch for troubleshooting toggle |
|
|
360
|
+
| 14 | Minor | All files | — | Excessive NSLog in production |
|
package/ios/RNSodyoScanner.h
CHANGED
|
@@ -8,12 +8,11 @@
|
|
|
8
8
|
#ifndef RNSodyoScanner_h
|
|
9
9
|
#define RNSodyoScanner_h
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
#endif /* RNSodyoScanner_h */
|
|
13
|
-
|
|
14
|
-
static UIViewController* sodyoScanner = nil;
|
|
11
|
+
#import <UIKit/UIKit.h>
|
|
15
12
|
|
|
16
13
|
@interface RNSodyoScanner : NSObject
|
|
17
14
|
+ (UIViewController *)getSodyoScanner;
|
|
18
15
|
+ (void)setSodyoScanner:(UIViewController *)sodyoScanner;
|
|
19
16
|
@end
|
|
17
|
+
|
|
18
|
+
#endif /* RNSodyoScanner_h */
|
package/ios/RNSodyoScanner.m
CHANGED
|
@@ -7,17 +7,18 @@
|
|
|
7
7
|
#import "RNSodyoScanner.h"
|
|
8
8
|
#import <Foundation/Foundation.h>
|
|
9
9
|
|
|
10
|
+
static UIViewController* _sharedScanner = nil;
|
|
11
|
+
|
|
10
12
|
@implementation RNSodyoScanner
|
|
11
13
|
|
|
12
14
|
+ (UIViewController *) getSodyoScanner {
|
|
13
|
-
return
|
|
15
|
+
return _sharedScanner;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
+ (void) setSodyoScanner:(UIViewController*) newSodyoScanner {
|
|
17
|
-
if(
|
|
18
|
-
|
|
19
|
+
if(_sharedScanner != newSodyoScanner) {
|
|
20
|
+
_sharedScanner = newSodyoScanner;
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
@end
|
|
23
|
-
// implementation of getter and setter
|
|
24
|
+
@end
|
package/ios/RNSodyoSdk.h
CHANGED
|
@@ -15,9 +15,7 @@
|
|
|
15
15
|
#import "SodyoSDK.h"
|
|
16
16
|
#endif
|
|
17
17
|
|
|
18
|
-
@interface RNSodyoSdk : RCTEventEmitter <RCTBridgeModule, SodyoSDKDelegate, SodyoMarkerDelegate>
|
|
19
|
-
UIViewController *sodyoScanner;
|
|
20
|
-
}
|
|
18
|
+
@interface RNSodyoSdk : RCTEventEmitter <RCTBridgeModule, SodyoSDKDelegate, SodyoMarkerDelegate>
|
|
21
19
|
|
|
22
20
|
@property (nonatomic, strong) RCTResponseSenderBlock successStartCallback;
|
|
23
21
|
@property (nonatomic, strong) RCTResponseSenderBlock errorStartCallback;
|