react-native-pdf-jsi 2.2.7 → 3.0.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/README.md +299 -11
- package/android/src/main/java/org/wonday/pdf/FileDownloader.java +292 -0
- package/android/src/main/java/org/wonday/pdf/FileManager.java +123 -0
- package/android/src/main/java/org/wonday/pdf/LicenseVerifier.java +311 -0
- package/android/src/main/java/org/wonday/pdf/PDFExporter.java +769 -0
- package/android/src/main/java/org/wonday/pdf/RNPDFPackage.java +7 -0
- package/index.js +58 -0
- package/ios/RNPDFPdf/PDFExporter.h +16 -0
- package/ios/RNPDFPdf/PDFExporter.m +537 -0
- package/package.json +7 -6
- package/src/components/AnalyticsPanel.jsx +243 -0
- package/src/components/BookmarkIndicator.jsx +66 -0
- package/src/components/BookmarkListModal.jsx +378 -0
- package/src/components/BookmarkModal.jsx +253 -0
- package/src/components/BottomSheet.jsx +121 -0
- package/src/components/ExportMenu.jsx +223 -0
- package/src/components/LoadingOverlay.jsx +52 -0
- package/src/components/OperationsMenu.jsx +231 -0
- package/src/components/SidePanel.jsx +95 -0
- package/src/components/Toast.jsx +140 -0
- package/src/components/Toolbar.jsx +135 -0
- package/src/managers/AnalyticsManager.js +695 -0
- package/src/managers/BookmarkManager.js +538 -0
- package/src/managers/ExportManager.js +687 -0
- package/src/managers/FileManager.js +89 -0
- package/src/utils/ErrorHandler.js +179 -0
- package/src/utils/TestData.js +112 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
package org.wonday.pdf;
|
|
2
|
+
|
|
3
|
+
import android.app.DownloadManager;
|
|
4
|
+
import android.content.Intent;
|
|
5
|
+
import android.net.Uri;
|
|
6
|
+
import android.util.Log;
|
|
7
|
+
|
|
8
|
+
import com.facebook.react.bridge.Promise;
|
|
9
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
10
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
11
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* FileManager - Native module for file operations like opening folders
|
|
15
|
+
*/
|
|
16
|
+
public class FileManager extends ReactContextBaseJavaModule {
|
|
17
|
+
private static final String TAG = "FileManager";
|
|
18
|
+
private static final String FOLDER_NAME = "PDFDemoApp";
|
|
19
|
+
private final ReactApplicationContext reactContext;
|
|
20
|
+
|
|
21
|
+
public FileManager(ReactApplicationContext reactContext) {
|
|
22
|
+
super(reactContext);
|
|
23
|
+
this.reactContext = reactContext;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Override
|
|
27
|
+
public String getName() {
|
|
28
|
+
return "FileManager";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Open the Downloads/PDFDemoApp folder in the file manager
|
|
33
|
+
* Multiple fallback strategies for maximum compatibility
|
|
34
|
+
*/
|
|
35
|
+
@ReactMethod
|
|
36
|
+
public void openDownloadsFolder(Promise promise) {
|
|
37
|
+
try {
|
|
38
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Attempting to open Downloads/" + FOLDER_NAME);
|
|
39
|
+
|
|
40
|
+
// Strategy 1: Try to open specific Downloads/PDFDemoApp folder
|
|
41
|
+
try {
|
|
42
|
+
Intent specificIntent = new Intent(Intent.ACTION_VIEW);
|
|
43
|
+
Uri folderUri = Uri.parse("content://com.android.externalstorage.documents/document/primary:Download/" + FOLDER_NAME);
|
|
44
|
+
specificIntent.setDataAndType(folderUri, "resource/folder");
|
|
45
|
+
specificIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
46
|
+
|
|
47
|
+
if (specificIntent.resolveActivity(reactContext.getPackageManager()) != null) {
|
|
48
|
+
reactContext.startActivity(specificIntent);
|
|
49
|
+
Log.i(TAG, "✅ [OPEN FOLDER] Opened specific folder via DocumentsUI");
|
|
50
|
+
promise.resolve(true);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
} catch (Exception e) {
|
|
54
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Strategy 1 failed, trying fallback...");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Strategy 2: Open Downloads app
|
|
58
|
+
try {
|
|
59
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Trying Downloads app");
|
|
60
|
+
Intent downloadsIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
|
|
61
|
+
downloadsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
62
|
+
|
|
63
|
+
if (downloadsIntent.resolveActivity(reactContext.getPackageManager()) != null) {
|
|
64
|
+
reactContext.startActivity(downloadsIntent);
|
|
65
|
+
Log.i(TAG, "✅ [OPEN FOLDER] Opened Downloads app");
|
|
66
|
+
promise.resolve(true);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
} catch (Exception e) {
|
|
70
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Strategy 2 failed, trying fallback...");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Strategy 3: Open Files app with generic CATEGORY_APP_FILES intent
|
|
74
|
+
try {
|
|
75
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Trying Files app");
|
|
76
|
+
Intent filesIntent = new Intent(Intent.ACTION_VIEW);
|
|
77
|
+
filesIntent.addCategory(Intent.CATEGORY_DEFAULT);
|
|
78
|
+
filesIntent.setType("resource/folder");
|
|
79
|
+
filesIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
80
|
+
|
|
81
|
+
if (filesIntent.resolveActivity(reactContext.getPackageManager()) != null) {
|
|
82
|
+
reactContext.startActivity(filesIntent);
|
|
83
|
+
Log.i(TAG, "✅ [OPEN FOLDER] Opened Files app");
|
|
84
|
+
promise.resolve(true);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
} catch (Exception e) {
|
|
88
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Strategy 3 failed");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Strategy 4: Try to launch any file manager using generic intent
|
|
92
|
+
try {
|
|
93
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Trying generic file manager");
|
|
94
|
+
Intent genericIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
95
|
+
genericIntent.setType("*/*");
|
|
96
|
+
genericIntent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
97
|
+
genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
98
|
+
|
|
99
|
+
if (genericIntent.resolveActivity(reactContext.getPackageManager()) != null) {
|
|
100
|
+
reactContext.startActivity(Intent.createChooser(genericIntent, "Open File Manager"));
|
|
101
|
+
Log.i(TAG, "✅ [OPEN FOLDER] Opened file picker");
|
|
102
|
+
promise.resolve(true);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
} catch (Exception e) {
|
|
106
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Strategy 4 failed");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// All strategies failed
|
|
110
|
+
Log.w(TAG, "⚠️ [OPEN FOLDER] All strategies failed - no file manager available");
|
|
111
|
+
promise.reject("NO_FILE_MANAGER", "No file manager app available on this device");
|
|
112
|
+
|
|
113
|
+
} catch (Exception e) {
|
|
114
|
+
Log.e(TAG, "❌ [OPEN FOLDER] ERROR", e);
|
|
115
|
+
promise.reject("OPEN_FOLDER_ERROR", e.getMessage());
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
package org.wonday.pdf;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.content.pm.ApplicationInfo;
|
|
5
|
+
import android.content.pm.PackageManager;
|
|
6
|
+
import android.util.Log;
|
|
7
|
+
|
|
8
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
9
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
10
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
11
|
+
import com.facebook.react.bridge.Promise;
|
|
12
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
13
|
+
import com.facebook.react.bridge.WritableMap;
|
|
14
|
+
import com.facebook.react.bridge.Arguments;
|
|
15
|
+
|
|
16
|
+
import java.security.MessageDigest;
|
|
17
|
+
import java.security.NoSuchAlgorithmException;
|
|
18
|
+
import java.util.HashMap;
|
|
19
|
+
import java.util.Map;
|
|
20
|
+
|
|
21
|
+
import javax.crypto.Mac;
|
|
22
|
+
import javax.crypto.spec.SecretKeySpec;
|
|
23
|
+
|
|
24
|
+
public class LicenseVerifier extends ReactContextBaseJavaModule {
|
|
25
|
+
private static final String TAG = "LicenseVerifier";
|
|
26
|
+
|
|
27
|
+
// License info cache
|
|
28
|
+
private String currentLicenseKey = null;
|
|
29
|
+
private String currentTier = null;
|
|
30
|
+
private long expiryTimestamp = 0;
|
|
31
|
+
private String currentEmail = null;
|
|
32
|
+
|
|
33
|
+
// Secret key for HMAC verification (from app config)
|
|
34
|
+
private String secretKey = null;
|
|
35
|
+
|
|
36
|
+
public LicenseVerifier(ReactApplicationContext reactContext) {
|
|
37
|
+
super(reactContext);
|
|
38
|
+
loadSecretKey();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Override
|
|
42
|
+
public String getName() {
|
|
43
|
+
return "LicenseVerifier";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Load secret key from AndroidManifest meta-data
|
|
48
|
+
*/
|
|
49
|
+
private void loadSecretKey() {
|
|
50
|
+
try {
|
|
51
|
+
Context context = getReactApplicationContext();
|
|
52
|
+
ApplicationInfo appInfo = context.getPackageManager()
|
|
53
|
+
.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
|
|
54
|
+
|
|
55
|
+
if (appInfo.metaData != null) {
|
|
56
|
+
secretKey = appInfo.metaData.getString("com.reactnativepdf.license.secret");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (secretKey == null) {
|
|
60
|
+
Log.w(TAG, "License secret not found in AndroidManifest");
|
|
61
|
+
secretKey = "default-secret-change-in-production";
|
|
62
|
+
}
|
|
63
|
+
} catch (Exception e) {
|
|
64
|
+
Log.e(TAG, "Error loading license secret", e);
|
|
65
|
+
secretKey = "default-secret-change-in-production";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Set license information from JS
|
|
71
|
+
*/
|
|
72
|
+
@ReactMethod
|
|
73
|
+
public void setLicenseInfo(ReadableMap licenseInfo, Promise promise) {
|
|
74
|
+
try {
|
|
75
|
+
if (licenseInfo == null) {
|
|
76
|
+
promise.reject("INVALID_LICENSE", "License info is null");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
String licenseKey = licenseInfo.getString("key");
|
|
81
|
+
String tier = licenseInfo.getString("tier");
|
|
82
|
+
String email = licenseInfo.getString("email");
|
|
83
|
+
String expiresAt = licenseInfo.getString("expiresAt");
|
|
84
|
+
|
|
85
|
+
if (licenseKey == null || tier == null) {
|
|
86
|
+
promise.reject("INVALID_LICENSE", "Missing required license fields");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Verify license key format and checksum
|
|
91
|
+
if (!verifyLicenseKey(licenseKey)) {
|
|
92
|
+
promise.reject("INVALID_LICENSE", "Invalid license key format or checksum");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Parse expiry timestamp
|
|
97
|
+
long expiry = 0;
|
|
98
|
+
if (expiresAt != null && !expiresAt.isEmpty()) {
|
|
99
|
+
try {
|
|
100
|
+
expiry = java.time.Instant.parse(expiresAt).toEpochMilli();
|
|
101
|
+
} catch (Exception e) {
|
|
102
|
+
Log.w(TAG, "Invalid expiry date format", e);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Cache license info
|
|
107
|
+
this.currentLicenseKey = licenseKey;
|
|
108
|
+
this.currentTier = tier;
|
|
109
|
+
this.currentEmail = email;
|
|
110
|
+
this.expiryTimestamp = expiry;
|
|
111
|
+
|
|
112
|
+
Log.i(TAG, "License set: " + tier + " for " + email);
|
|
113
|
+
promise.resolve(true);
|
|
114
|
+
|
|
115
|
+
} catch (Exception e) {
|
|
116
|
+
Log.e(TAG, "Error setting license info", e);
|
|
117
|
+
promise.reject("LICENSE_ERROR", e.getMessage());
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Require Pro license for a feature
|
|
123
|
+
*/
|
|
124
|
+
@ReactMethod
|
|
125
|
+
public void requirePro(String featureName, Promise promise) {
|
|
126
|
+
try {
|
|
127
|
+
if (!isProActive()) {
|
|
128
|
+
WritableMap error = Arguments.createMap();
|
|
129
|
+
error.putString("code", "LICENSE_REQUIRED");
|
|
130
|
+
error.putString("feature", featureName);
|
|
131
|
+
error.putString("message", featureName + " requires a Pro license");
|
|
132
|
+
promise.reject("LICENSE_REQUIRED", error);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
promise.resolve(true);
|
|
137
|
+
} catch (Exception e) {
|
|
138
|
+
Log.e(TAG, "Error checking Pro license", e);
|
|
139
|
+
promise.reject("LICENSE_ERROR", e.getMessage());
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Check if Pro license is active
|
|
145
|
+
*/
|
|
146
|
+
public boolean isProActive() {
|
|
147
|
+
// 🧪 TESTING MODE: Always return true for testing
|
|
148
|
+
Log.i(TAG, "🧪 TESTING MODE: isProActive() returning true for testing");
|
|
149
|
+
return true;
|
|
150
|
+
|
|
151
|
+
/* PRODUCTION CODE (commented out for testing):
|
|
152
|
+
if (currentLicenseKey == null || currentTier == null) {
|
|
153
|
+
Log.w(TAG, "No license set");
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check if license is expired
|
|
158
|
+
if (expiryTimestamp > 0 && System.currentTimeMillis() > expiryTimestamp) {
|
|
159
|
+
Log.w(TAG, "License expired");
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check if tier is Pro or higher
|
|
164
|
+
return isProTier(currentTier);
|
|
165
|
+
*/
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Check if tier is Pro or higher
|
|
170
|
+
*/
|
|
171
|
+
private boolean isProTier(String tier) {
|
|
172
|
+
if (tier == null) return false;
|
|
173
|
+
|
|
174
|
+
switch (tier.toLowerCase()) {
|
|
175
|
+
case "solo":
|
|
176
|
+
case "professional":
|
|
177
|
+
case "team":
|
|
178
|
+
case "enterprise":
|
|
179
|
+
return true;
|
|
180
|
+
default:
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get current license tier
|
|
187
|
+
*/
|
|
188
|
+
@ReactMethod
|
|
189
|
+
public void getTier(Promise promise) {
|
|
190
|
+
try {
|
|
191
|
+
if (currentTier == null) {
|
|
192
|
+
promise.resolve("free");
|
|
193
|
+
} else {
|
|
194
|
+
promise.resolve(currentTier);
|
|
195
|
+
}
|
|
196
|
+
} catch (Exception e) {
|
|
197
|
+
Log.e(TAG, "Error getting tier", e);
|
|
198
|
+
promise.reject("LICENSE_ERROR", e.getMessage());
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Check if license is expired
|
|
204
|
+
*/
|
|
205
|
+
@ReactMethod
|
|
206
|
+
public void isExpired(Promise promise) {
|
|
207
|
+
try {
|
|
208
|
+
boolean expired = expiryTimestamp > 0 && System.currentTimeMillis() > expiryTimestamp;
|
|
209
|
+
promise.resolve(expired);
|
|
210
|
+
} catch (Exception e) {
|
|
211
|
+
Log.e(TAG, "Error checking expiry", e);
|
|
212
|
+
promise.reject("LICENSE_ERROR", e.getMessage());
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Verify license key format and checksum
|
|
218
|
+
*/
|
|
219
|
+
private boolean verifyLicenseKey(String licenseKey) {
|
|
220
|
+
if (licenseKey == null || licenseKey.length() != 19) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Check format: X###-####-####-####
|
|
225
|
+
if (!licenseKey.matches("^[SPTE][A-F0-9]{3}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}$")) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Verify checksum
|
|
230
|
+
try {
|
|
231
|
+
String[] parts = licenseKey.split("-");
|
|
232
|
+
String prefix = parts[0].substring(0, 1);
|
|
233
|
+
String seg1 = parts[0].substring(1);
|
|
234
|
+
String seg2 = parts[1];
|
|
235
|
+
String seg3 = parts[2];
|
|
236
|
+
String checksum = parts[3];
|
|
237
|
+
|
|
238
|
+
String data = prefix + seg1 + seg2 + seg3;
|
|
239
|
+
String expectedChecksum = generateChecksum(data);
|
|
240
|
+
|
|
241
|
+
return checksum.equals(expectedChecksum);
|
|
242
|
+
} catch (Exception e) {
|
|
243
|
+
Log.e(TAG, "Error verifying license key", e);
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Generate checksum for license key
|
|
250
|
+
*/
|
|
251
|
+
private String generateChecksum(String data) {
|
|
252
|
+
try {
|
|
253
|
+
Mac mac = Mac.getInstance("HmacSHA256");
|
|
254
|
+
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
|
|
255
|
+
mac.init(secretKeySpec);
|
|
256
|
+
|
|
257
|
+
byte[] hash = mac.doFinal(data.getBytes());
|
|
258
|
+
StringBuilder hexString = new StringBuilder();
|
|
259
|
+
|
|
260
|
+
for (byte b : hash) {
|
|
261
|
+
String hex = Integer.toHexString(0xff & b);
|
|
262
|
+
if (hex.length() == 1) {
|
|
263
|
+
hexString.append('0');
|
|
264
|
+
}
|
|
265
|
+
hexString.append(hex);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return hexString.toString().toUpperCase().substring(0, 4);
|
|
269
|
+
} catch (Exception e) {
|
|
270
|
+
Log.e(TAG, "Error generating checksum", e);
|
|
271
|
+
return "0000";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get license info for debugging
|
|
277
|
+
*/
|
|
278
|
+
@ReactMethod
|
|
279
|
+
public void getLicenseInfo(Promise promise) {
|
|
280
|
+
try {
|
|
281
|
+
WritableMap info = Arguments.createMap();
|
|
282
|
+
info.putString("key", currentLicenseKey);
|
|
283
|
+
info.putString("tier", currentTier);
|
|
284
|
+
info.putString("email", currentEmail);
|
|
285
|
+
info.putDouble("expiresAt", expiryTimestamp);
|
|
286
|
+
info.putBoolean("isPro", isProActive());
|
|
287
|
+
promise.resolve(info);
|
|
288
|
+
} catch (Exception e) {
|
|
289
|
+
Log.e(TAG, "Error getting license info", e);
|
|
290
|
+
promise.reject("LICENSE_ERROR", e.getMessage());
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Clear license info
|
|
296
|
+
*/
|
|
297
|
+
@ReactMethod
|
|
298
|
+
public void clearLicense(Promise promise) {
|
|
299
|
+
try {
|
|
300
|
+
currentLicenseKey = null;
|
|
301
|
+
currentTier = null;
|
|
302
|
+
currentEmail = null;
|
|
303
|
+
expiryTimestamp = 0;
|
|
304
|
+
Log.i(TAG, "License cleared");
|
|
305
|
+
promise.resolve(true);
|
|
306
|
+
} catch (Exception e) {
|
|
307
|
+
Log.e(TAG, "Error clearing license", e);
|
|
308
|
+
promise.reject("LICENSE_ERROR", e.getMessage());
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|