react-native-dev-guard 1.0.0 → 1.0.1
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/LICENSE +1 -1
- package/README.md +6 -6
- package/android/src/main/cpp/devguard-lib.cpp +12 -11
- package/android/src/main/java/com/devguard/DevGuardModule.kt +15 -0
- package/cpp/devguard_core.c +82 -4
- package/cpp/devguard_core.h +15 -13
- package/ios/DevGuard.mm +20 -6
- package/lib/components/LockScreen.d.ts +2 -0
- package/lib/components/LockScreen.js +90 -44
- package/lib/index.d.ts +4 -1
- package/lib/index.js +166 -148
- package/lib/internal/obf.d.ts +72 -0
- package/lib/internal/obf.js +100 -0
- package/lib/services/CacheService.js +3 -11
- package/lib/services/GuardEnforcement.d.ts +3 -12
- package/lib/services/GuardEnforcement.js +34 -15
- package/lib/services/cacheCrypto.d.ts +6 -0
- package/lib/services/cacheCrypto.js +40 -0
- package/lib/types/branding.d.ts +21 -0
- package/lib/types/branding.js +30 -0
- package/lib/utils/contactUrls.d.ts +4 -0
- package/lib/utils/contactUrls.js +18 -0
- package/package.json +9 -3
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ The Lock Screen, warning toast, and diagnostics overlay are all **built in** —
|
|
|
37
37
|
- **Heartbeat & Sync**: Automated background pings (with `lifecycleSync` / `syncPolicy` support) to keep license status fresh.
|
|
38
38
|
- **Hardware Fingerprinting**: Robust device identity that survives app re-installs.
|
|
39
39
|
- **Emulator Blocking**: When the project enables `blockEmulators`, the SDK locks on emulators/simulators automatically.
|
|
40
|
-
- **Hardened Remote Wipe**: Nonce-based remote wipe clears the response cache, usage logs,
|
|
40
|
+
- **Hardened Remote Wipe**: Nonce-based remote wipe clears the response cache, usage logs, local diagnostic vault, and stored device-user identity.
|
|
41
41
|
- **Privacy-Gated Telemetry**: Advanced metrics (RAM, storage, battery, network) are only collected after the server enables `advancedTelemetry`.
|
|
42
42
|
|
|
43
43
|
## 🚀 Getting Started
|
|
@@ -56,7 +56,7 @@ npm install react-native-dev-guard react-native-device-info react-native-keychai
|
|
|
56
56
|
yarn add react-native-dev-guard react-native-device-info react-native-keychain @react-native-async-storage/async-storage
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
`@react-native-async-storage/async-storage` is required by the bundled Vault Logger (
|
|
59
|
+
`@react-native-async-storage/async-storage` is required by the bundled Vault Logger (local diagnostics). Use **v3.x** with React Native 0.85+; run `npx pod-install` after adding it on iOS.
|
|
60
60
|
|
|
61
61
|
### 3. Native setup
|
|
62
62
|
|
|
@@ -186,10 +186,10 @@ const ok = await unlock('USER-ENTERED-KEY');
|
|
|
186
186
|
|
|
187
187
|
## 🐞 Diagnostic Overlay & Vault Logger
|
|
188
188
|
|
|
189
|
-
DevGuard includes an integrated diagnostic UI (the Bug Icon) and
|
|
189
|
+
DevGuard includes an integrated diagnostic UI (the Bug Icon) and a local Vault Logger for advanced debugging without rebuilding your app.
|
|
190
190
|
|
|
191
191
|
### Vault Logger
|
|
192
|
-
The SDK bundles `react-native-vault-logger`. By default, it automatically
|
|
192
|
+
The SDK bundles `react-native-vault-logger`. By default, it automatically captures fatal JS errors and usage info, saving them securely to local storage using **per-device keys** derived from the hardware `deviceId` at init (no static secret in source).
|
|
193
193
|
You can manually log data to the Info Vault:
|
|
194
194
|
```tsx
|
|
195
195
|
import { DevGuardLogger } from 'react-native-dev-guard/src/services/DevGuardLogger';
|
|
@@ -203,7 +203,7 @@ To view telemetry and logs directly inside the running app without connecting a
|
|
|
203
203
|
2. Ensure you have configured a **6-digit Diagnostic Passcode**.
|
|
204
204
|
3. Under the project settings, toggle on **Enable Diagnostic Logs (Beta Feature)** for the desired devices.
|
|
205
205
|
4. A floating **🐛 Bug Icon** will appear in your app.
|
|
206
|
-
5. Tap it and enter your Passcode to view device telemetry and access
|
|
206
|
+
5. Tap it and enter your Passcode to view device telemetry and access diagnostic vaults.
|
|
207
207
|
|
|
208
208
|
## ⚙️ Configuration
|
|
209
209
|
|
|
@@ -213,7 +213,7 @@ To view telemetry and logs directly inside the running app without connecting a
|
|
|
213
213
|
|
|
214
214
|
## 🔐 Security Best Practices
|
|
215
215
|
|
|
216
|
-
1. **
|
|
216
|
+
1. **Release builds**: Use `javascript-obfuscator` or similar tools for your JS bundle.
|
|
217
217
|
2. **Hermes**: Ensure Hermes is enabled in your `app/build.gradle` and `Podfile` to benefit from bytecode pre-compilation.
|
|
218
218
|
3. **ProGuard**: Use the included ProGuard rules to protect the native C++ library.
|
|
219
219
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#include <jni.h>
|
|
2
|
-
#include <string>
|
|
3
2
|
#include "devguard_core.h"
|
|
4
3
|
|
|
5
4
|
extern "C" JNIEXPORT jstring JNICALL
|
|
@@ -8,14 +7,10 @@ Java_com_devguard_DevGuardModule_generateSignatureNative(
|
|
|
8
7
|
jobject /* this */,
|
|
9
8
|
jstring projectId,
|
|
10
9
|
jlong timestamp) {
|
|
11
|
-
|
|
12
10
|
const char *project_id_c = env->GetStringUTFChars(projectId, 0);
|
|
13
11
|
char output[65];
|
|
14
|
-
|
|
15
|
-
generate_signature(project_id_c, (long long)timestamp, output);
|
|
16
|
-
|
|
12
|
+
dg_x9(project_id_c, (long long)timestamp, output);
|
|
17
13
|
env->ReleaseStringUTFChars(projectId, project_id_c);
|
|
18
|
-
|
|
19
14
|
return env->NewStringUTF(output);
|
|
20
15
|
}
|
|
21
16
|
|
|
@@ -25,14 +20,20 @@ Java_com_devguard_DevGuardModule_verifyResponseNative(
|
|
|
25
20
|
jobject /* this */,
|
|
26
21
|
jstring responseBody,
|
|
27
22
|
jstring signature) {
|
|
28
|
-
|
|
29
23
|
const char *response_body_c = env->GetStringUTFChars(responseBody, 0);
|
|
30
24
|
const char *signature_c = env->GetStringUTFChars(signature, 0);
|
|
31
|
-
|
|
32
|
-
int result = verify_response(response_body_c, signature_c);
|
|
33
|
-
|
|
25
|
+
int result = dg_v2(response_body_c, signature_c);
|
|
34
26
|
env->ReleaseStringUTFChars(responseBody, response_body_c);
|
|
35
27
|
env->ReleaseStringUTFChars(signature, signature_c);
|
|
36
|
-
|
|
37
28
|
return result == 1 ? JNI_TRUE : JNI_FALSE;
|
|
38
29
|
}
|
|
30
|
+
|
|
31
|
+
extern "C" JNIEXPORT jint JNICALL
|
|
32
|
+
Java_com_devguard_DevGuardModule_evaluatePolicyNative(
|
|
33
|
+
JNIEnv* env,
|
|
34
|
+
jobject /* this */,
|
|
35
|
+
jint blockEmulators,
|
|
36
|
+
jint isPhysical,
|
|
37
|
+
jint isCompromised) {
|
|
38
|
+
return dg_e1(blockEmulators, isPhysical, isCompromised);
|
|
39
|
+
}
|
|
@@ -17,6 +17,7 @@ class DevGuardModule(reactContext: ReactApplicationContext) : ReactContextBaseJa
|
|
|
17
17
|
|
|
18
18
|
private external fun generateSignatureNative(projectId: String, timestamp: Long): String
|
|
19
19
|
private external fun verifyResponseNative(responseBody: String, signature: String): Boolean
|
|
20
|
+
private external fun evaluatePolicyNative(blockEmulators: Int, isPhysical: Int, isCompromised: Int): Int
|
|
20
21
|
|
|
21
22
|
@ReactMethod
|
|
22
23
|
fun generateSignature(projectId: String, timestamp: Double, promise: Promise) {
|
|
@@ -37,4 +38,18 @@ class DevGuardModule(reactContext: ReactApplicationContext) : ReactContextBaseJa
|
|
|
37
38
|
promise.reject("DEVGUARD_ERROR", "Failed to verify response", e)
|
|
38
39
|
}
|
|
39
40
|
}
|
|
41
|
+
|
|
42
|
+
@ReactMethod
|
|
43
|
+
fun evaluatePolicy(blockEmulators: Boolean, isPhysical: Boolean, isCompromised: Boolean, promise: Promise) {
|
|
44
|
+
try {
|
|
45
|
+
val code = evaluatePolicyNative(
|
|
46
|
+
if (blockEmulators) 1 else 0,
|
|
47
|
+
if (isPhysical) 1 else 0,
|
|
48
|
+
if (isCompromised) 1 else 0,
|
|
49
|
+
)
|
|
50
|
+
promise.resolve(code)
|
|
51
|
+
} catch (e: Exception) {
|
|
52
|
+
promise.reject("DEVGUARD_ERROR", "Failed to evaluate policy", e)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
40
55
|
}
|
package/cpp/devguard_core.c
CHANGED
|
@@ -180,7 +180,7 @@ static void _token_scramble(const char* input, char* output, size_t len) {
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
DEVGUARD_EXPORT void
|
|
183
|
+
DEVGUARD_EXPORT void dg_x9(const char* project_id, long long timestamp, char* output) {
|
|
184
184
|
// Validate project_id length to prevent buffer truncation
|
|
185
185
|
size_t pid_len = strlen(project_id);
|
|
186
186
|
if (pid_len == 0 || pid_len > 200) {
|
|
@@ -206,7 +206,7 @@ DEVGUARD_EXPORT void generate_signature(const char* project_id, long long timest
|
|
|
206
206
|
output[64] = '\0';
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
DEVGUARD_EXPORT int
|
|
209
|
+
DEVGUARD_EXPORT int dg_v2(const char* response_body, const char* signature) {
|
|
210
210
|
char secret_key[64];
|
|
211
211
|
_reconstruct_key(secret_key);
|
|
212
212
|
|
|
@@ -226,14 +226,92 @@ DEVGUARD_EXPORT int verify_response(const char* response_body, const char* signa
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
// Secure token scrambling for in-memory protection
|
|
229
|
-
DEVGUARD_EXPORT void
|
|
229
|
+
DEVGUARD_EXPORT void dg_s3(const char* token, char* output) {
|
|
230
230
|
size_t len = strlen(token);
|
|
231
231
|
_token_scramble(token, output, len);
|
|
232
232
|
output[len] = '\0';
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
DEVGUARD_EXPORT void
|
|
235
|
+
DEVGUARD_EXPORT void dg_g4(const char* scrambled, char* output) {
|
|
236
236
|
size_t len = strlen(scrambled);
|
|
237
237
|
_token_scramble(scrambled, output, len);
|
|
238
238
|
output[len] = '\0';
|
|
239
239
|
}
|
|
240
|
+
|
|
241
|
+
// SHA-256 hex digest of an arbitrary UTF-8 string (license keys, fingerprints).
|
|
242
|
+
static void _hash_sha256_hex(const char* input, char* output) {
|
|
243
|
+
if (input == NULL || output == NULL) {
|
|
244
|
+
if (output) output[0] = '\0';
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
uint8_t hash[32];
|
|
249
|
+
SHA256_CTX ctx;
|
|
250
|
+
sha256_init(&ctx);
|
|
251
|
+
sha256_update(&ctx, (const uint8_t*)input, strlen(input));
|
|
252
|
+
sha256_final(&ctx, hash);
|
|
253
|
+
|
|
254
|
+
for (int i = 0; i < 32; i++) {
|
|
255
|
+
sprintf(output + (i * 2), "%02x", hash[i]);
|
|
256
|
+
}
|
|
257
|
+
output[64] = '\0';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Symmetric XOR transform. output must hold at least input_len bytes.
|
|
261
|
+
DEVGUARD_EXPORT void dg_h5(const char* input, char* output) {
|
|
262
|
+
_hash_sha256_hex(input, output);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
DEVGUARD_EXPORT void dg_x6(
|
|
266
|
+
const char* input,
|
|
267
|
+
size_t input_len,
|
|
268
|
+
const char* key,
|
|
269
|
+
size_t key_len,
|
|
270
|
+
char* output
|
|
271
|
+
) {
|
|
272
|
+
if (input == NULL || key == NULL || output == NULL || key_len == 0) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
for (size_t i = 0; i < input_len; i++) {
|
|
277
|
+
output[i] = input[i] ^ key[i % key_len];
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Derive a 64-char hex key from passcode + salt (usage log encryption).
|
|
282
|
+
DEVGUARD_EXPORT void dg_d7(const char* passcode, const char* salt, char* output) {
|
|
283
|
+
char combined[512];
|
|
284
|
+
snprintf(combined, sizeof(combined), "%s_%s",
|
|
285
|
+
passcode != NULL ? passcode : "secure_default",
|
|
286
|
+
salt != NULL ? salt : "salt");
|
|
287
|
+
_hash_sha256_hex(combined, output);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
#if defined(__APPLE__)
|
|
291
|
+
#include <sys/sysctl.h>
|
|
292
|
+
|
|
293
|
+
// Returns total RAM in megabytes on Apple platforms, or -1 on failure.
|
|
294
|
+
DEVGUARD_EXPORT int dg_r8(void) {
|
|
295
|
+
int64_t mem = 0;
|
|
296
|
+
size_t len = sizeof(mem);
|
|
297
|
+
if (sysctlbyname("hw.memsize", &mem, &len, NULL, 0) != 0) {
|
|
298
|
+
return -1;
|
|
299
|
+
}
|
|
300
|
+
return (int)(mem / (1024 * 1024));
|
|
301
|
+
}
|
|
302
|
+
#else
|
|
303
|
+
DEVGUARD_EXPORT int dg_r8(void) {
|
|
304
|
+
return -1;
|
|
305
|
+
}
|
|
306
|
+
#endif
|
|
307
|
+
|
|
308
|
+
// Policy gate: 0=allow, 1=emulator block, 2=compromised device block
|
|
309
|
+
DEVGUARD_EXPORT int dg_e1(int block_emulators, int is_physical, int is_compromised) {
|
|
310
|
+
if (is_compromised) {
|
|
311
|
+
return 2;
|
|
312
|
+
}
|
|
313
|
+
if (block_emulators && !is_physical) {
|
|
314
|
+
return 1;
|
|
315
|
+
}
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
package/cpp/devguard_core.h
CHANGED
|
@@ -14,19 +14,21 @@
|
|
|
14
14
|
extern "C" {
|
|
15
15
|
#endif
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
DEVGUARD_EXPORT void
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
DEVGUARD_EXPORT void
|
|
17
|
+
DEVGUARD_EXPORT void dg_x9(const char* project_id, long long timestamp, char* output);
|
|
18
|
+
DEVGUARD_EXPORT int dg_v2(const char* response_body, const char* signature);
|
|
19
|
+
DEVGUARD_EXPORT void dg_s3(const char* token, char* output);
|
|
20
|
+
DEVGUARD_EXPORT void dg_g4(const char* scrambled, char* output);
|
|
21
|
+
DEVGUARD_EXPORT void dg_h5(const char* input, char* output);
|
|
22
|
+
DEVGUARD_EXPORT void dg_x6(
|
|
23
|
+
const char* input,
|
|
24
|
+
size_t input_len,
|
|
25
|
+
const char* key,
|
|
26
|
+
size_t key_len,
|
|
27
|
+
char* output
|
|
28
|
+
);
|
|
29
|
+
DEVGUARD_EXPORT void dg_d7(const char* passcode, const char* salt, char* output);
|
|
30
|
+
DEVGUARD_EXPORT int dg_r8(void);
|
|
31
|
+
DEVGUARD_EXPORT int dg_e1(int block_emulators, int is_physical, int is_compromised);
|
|
30
32
|
|
|
31
33
|
#ifdef __cplusplus
|
|
32
34
|
}
|
package/ios/DevGuard.mm
CHANGED
|
@@ -13,10 +13,8 @@ RCT_EXPORT_METHOD(generateSignature:(NSString *)projectId
|
|
|
13
13
|
@try {
|
|
14
14
|
const char *project_id_c = [projectId UTF8String];
|
|
15
15
|
long long timestamp_val = [timestamp longLongValue];
|
|
16
|
-
|
|
17
16
|
char output[65];
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
dg_x9(project_id_c, timestamp_val, output);
|
|
20
18
|
NSString *result = [NSString stringWithUTF8String:output];
|
|
21
19
|
resolve(result);
|
|
22
20
|
} @catch (NSException *exception) {
|
|
@@ -32,13 +30,29 @@ RCT_EXPORT_METHOD(verifyResponse:(NSString *)responseBody
|
|
|
32
30
|
@try {
|
|
33
31
|
const char *response_body_c = [responseBody UTF8String];
|
|
34
32
|
const char *signature_c = [signature UTF8String];
|
|
35
|
-
|
|
36
|
-
int isValid = verify_response(response_body_c, signature_c);
|
|
37
|
-
|
|
33
|
+
int isValid = dg_v2(response_body_c, signature_c);
|
|
38
34
|
resolve(@(isValid == 1));
|
|
39
35
|
} @catch (NSException *exception) {
|
|
40
36
|
reject(@"DEVGUARD_ERROR", @"Failed to verify response", nil);
|
|
41
37
|
}
|
|
42
38
|
}
|
|
43
39
|
|
|
40
|
+
RCT_EXPORT_METHOD(evaluatePolicy:(nonnull NSNumber *)blockEmulators
|
|
41
|
+
isPhysical:(nonnull NSNumber *)isPhysical
|
|
42
|
+
isCompromised:(nonnull NSNumber *)isCompromised
|
|
43
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
44
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
45
|
+
{
|
|
46
|
+
@try {
|
|
47
|
+
int code = dg_e1(
|
|
48
|
+
[blockEmulators intValue],
|
|
49
|
+
[isPhysical intValue],
|
|
50
|
+
[isCompromised intValue]
|
|
51
|
+
);
|
|
52
|
+
resolve(@(code));
|
|
53
|
+
} @catch (NSException *exception) {
|
|
54
|
+
reject(@"DEVGUARD_ERROR", @"Failed to evaluate policy", nil);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
@end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { LockScreenBranding } from '../types/branding';
|
|
2
3
|
interface LockScreenProps {
|
|
3
4
|
status: string;
|
|
4
5
|
title?: string;
|
|
@@ -7,6 +8,7 @@ interface LockScreenProps {
|
|
|
7
8
|
contactPhone?: string;
|
|
8
9
|
contactWhatsapp?: string;
|
|
9
10
|
allowUnlock?: boolean;
|
|
11
|
+
branding?: LockScreenBranding | null;
|
|
10
12
|
onUnlock: (key: string) => Promise<boolean>;
|
|
11
13
|
}
|
|
12
14
|
export declare const LockScreen: React.FC<LockScreenProps>;
|
|
@@ -5,36 +5,89 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const react_native_1 = require("react-native");
|
|
7
7
|
const ContactButton_1 = require("./ContactButton");
|
|
8
|
-
const
|
|
9
|
-
const
|
|
8
|
+
const branding_1 = require("../types/branding");
|
|
9
|
+
const contactUrls_1 = require("../utils/contactUrls");
|
|
10
|
+
const obf_1 = require("../internal/obf");
|
|
11
|
+
const UNLOCK_TOGGLE_COLOR = '#fcd34d';
|
|
12
|
+
const LockScreen = ({ status, title, message, contactEmail = '', contactPhone = '', contactWhatsapp = '', allowUnlock = false, branding, onUnlock, }) => {
|
|
13
|
+
const { width, height } = (0, react_native_1.useWindowDimensions)();
|
|
10
14
|
const [unlockKey, setUnlockKey] = (0, react_1.useState)('');
|
|
11
15
|
const [loading, setLoading] = (0, react_1.useState)(false);
|
|
12
16
|
const [error, setError] = (0, react_1.useState)(null);
|
|
13
17
|
const [showUnlock, setShowUnlock] = (0, react_1.useState)(false);
|
|
18
|
+
const [logoFailed, setLogoFailed] = (0, react_1.useState)(false);
|
|
19
|
+
const footer = (0, react_1.useMemo)(() => (0, branding_1.resolveBrandingFooter)(branding), [branding]);
|
|
20
|
+
const blobStyles = (0, react_1.useMemo)(() => ({
|
|
21
|
+
blob1: {
|
|
22
|
+
top: -width * 0.2,
|
|
23
|
+
right: -width * 0.2,
|
|
24
|
+
width: width * 0.8,
|
|
25
|
+
height: width * 0.8,
|
|
26
|
+
borderRadius: width * 0.4,
|
|
27
|
+
backgroundColor: footer.primaryColor,
|
|
28
|
+
},
|
|
29
|
+
blob2: {
|
|
30
|
+
bottom: -width * 0.1,
|
|
31
|
+
left: -width * 0.3,
|
|
32
|
+
width: width * 0.8,
|
|
33
|
+
height: width * 0.8,
|
|
34
|
+
borderRadius: width * 0.4,
|
|
35
|
+
backgroundColor: footer.accentColor,
|
|
36
|
+
},
|
|
37
|
+
blob3: {
|
|
38
|
+
top: height * 0.3,
|
|
39
|
+
right: -width * 0.4,
|
|
40
|
+
width: width * 0.6,
|
|
41
|
+
height: width * 0.6,
|
|
42
|
+
borderRadius: width * 0.3,
|
|
43
|
+
backgroundColor: footer.primaryColor,
|
|
44
|
+
},
|
|
45
|
+
}), [footer.accentColor, footer.primaryColor, height, width]);
|
|
46
|
+
const themedStyles = (0, react_1.useMemo)(() => react_native_1.StyleSheet.create({
|
|
47
|
+
iconContainer: {
|
|
48
|
+
backgroundColor: `${footer.primaryColor}22`,
|
|
49
|
+
borderColor: `${footer.primaryColor}44`,
|
|
50
|
+
},
|
|
51
|
+
divider: { backgroundColor: footer.primaryColor },
|
|
52
|
+
}), [footer.primaryColor]);
|
|
14
53
|
const handleUnlock = async () => {
|
|
15
|
-
if (!unlockKey.trim())
|
|
54
|
+
if (!unlockKey.trim() || loading)
|
|
16
55
|
return;
|
|
17
56
|
setLoading(true);
|
|
18
57
|
setError(null);
|
|
19
58
|
const success = await onUnlock(unlockKey.trim());
|
|
20
59
|
if (!success) {
|
|
21
|
-
setError(
|
|
60
|
+
setError(obf_1.Obf.invalidUnlockKey);
|
|
22
61
|
setLoading(false);
|
|
23
62
|
}
|
|
24
63
|
};
|
|
64
|
+
const closeUnlock = () => {
|
|
65
|
+
if (loading)
|
|
66
|
+
return;
|
|
67
|
+
setShowUnlock(false);
|
|
68
|
+
setError(null);
|
|
69
|
+
setUnlockKey('');
|
|
70
|
+
};
|
|
25
71
|
const openUrl = (url) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
72
|
+
if (!url)
|
|
73
|
+
return;
|
|
74
|
+
react_native_1.Linking.openURL(url).catch((err) => {
|
|
75
|
+
console.error('DevGuard: Failed to open URL:', url, err);
|
|
76
|
+
react_native_1.Alert.alert('Error', obf_1.Obf.contactOpenError);
|
|
30
77
|
});
|
|
31
78
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
79
|
+
const defaultTitle = status === 'LOCKED'
|
|
80
|
+
? 'Access Restricted'
|
|
81
|
+
: status === 'WARNING'
|
|
82
|
+
? 'Security Warning'
|
|
83
|
+
: 'License Expired';
|
|
84
|
+
const whatsappUrl = (0, contactUrls_1.buildWhatsAppUrl)(contactWhatsapp);
|
|
85
|
+
const mailtoUrl = (0, contactUrls_1.buildMailtoUrl)(contactEmail);
|
|
86
|
+
const telUrl = (0, contactUrls_1.buildTelUrl)(contactPhone);
|
|
87
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.container, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { pointerEvents: "none", style: [styles.blob, blobStyles.blob1] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { pointerEvents: "none", style: [styles.blob, blobStyles.blob2] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { pointerEvents: "none", style: [styles.blob, blobStyles.blob3] }), (0, jsx_runtime_1.jsx)(react_native_1.SafeAreaView, { style: styles.safeArea, children: (0, jsx_runtime_1.jsx)(react_native_1.KeyboardAvoidingView, { behavior: react_native_1.Platform.OS === 'ios' ? 'padding' : 'height', style: styles.keyboardView, children: (0, jsx_runtime_1.jsxs)(react_native_1.ScrollView, { contentContainerStyle: styles.scrollContent, keyboardShouldPersistTaps: "handled", showsVerticalScrollIndicator: false, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.glassCard, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.iconContainer, themedStyles.iconContainer], children: branding?.logoUrl && !logoFailed ? ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: branding.logoUrl }, style: styles.logoImage, resizeMode: "contain", onError: () => setLogoFailed(true) })) : ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.lockIcon, children: "\uD83D\uDEE1\uFE0F" })) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.title, children: (title || defaultTitle).toUpperCase() }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.divider, themedStyles.divider] }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.message, children: message || obf_1.Obf.defaultLockMessage }), !showUnlock && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.actions, children: [whatsappUrl ? ((0, jsx_runtime_1.jsx)(ContactButton_1.ContactButton, { label: obf_1.Obf.whatsappSupport, color: "#25D366", icon: "\uD83D\uDCAC", onPress: () => openUrl(whatsappUrl) })) : null, mailtoUrl ? ((0, jsx_runtime_1.jsx)(ContactButton_1.ContactButton, { label: obf_1.Obf.emailSupport, color: footer.primaryColor, icon: "\uD83D\uDCE7", onPress: () => openUrl(mailtoUrl) })) : null, telUrl ? ((0, jsx_runtime_1.jsx)(ContactButton_1.ContactButton, { label: obf_1.Obf.callSupport, color: "rgba(255, 255, 255, 0.1)", icon: "\uD83D\uDCDE", onPress: () => openUrl(telUrl) })) : null, allowUnlock ? ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: styles.unlockToggle, onPress: () => setShowUnlock(true), accessibilityRole: "button", accessibilityLabel: "Enter unlock key", children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.unlockToggleText, children: obf_1.Obf.enterUnlockKey }) })) : null] })), showUnlock && allowUnlock && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.unlockSection, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.inputContainer, children: [error ? (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.errorText, children: error }) : null, (0, jsx_runtime_1.jsx)(react_native_1.TextInput, { style: styles.input, placeholder: obf_1.Obf.licenseKeyHint, placeholderTextColor: "rgba(255, 255, 255, 0.4)", value: unlockKey, onChangeText: setUnlockKey, autoCapitalize: "none", autoCorrect: false, autoFocus: true, secureTextEntry: true, returnKeyType: "done", onSubmitEditing: handleUnlock, editable: !loading }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.inputActions, children: [(0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: styles.cancelButton, onPress: closeUnlock, disabled: loading, accessibilityRole: "button", accessibilityLabel: "Cancel unlock", children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
|
|
88
|
+
styles.cancelButtonText,
|
|
89
|
+
loading && styles.cancelButtonTextDisabled,
|
|
90
|
+
], children: obf_1.Obf.cancelLabel }) }), (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: [styles.unlockButton, loading && styles.unlockButtonDisabled], onPress: handleUnlock, disabled: loading, accessibilityRole: "button", accessibilityLabel: "Unlock application", children: loading ? ((0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { color: "#000", size: "small" })) : ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.unlockButtonText, children: obf_1.Obf.unlockLabel })) })] })] }) }))] }), !footer.hidePoweredBy && ((0, jsx_runtime_1.jsxs)(react_native_1.TouchableOpacity, { style: styles.footer, onPress: () => openUrl(footer.url), accessibilityRole: "link", accessibilityLabel: `${footer.label} ${footer.brand}`, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.footerLabel, children: footer.label }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.footerBrand, children: footer.brand.toUpperCase() })] }))] }) }) })] }));
|
|
38
91
|
};
|
|
39
92
|
exports.LockScreen = LockScreen;
|
|
40
93
|
const styles = react_native_1.StyleSheet.create({
|
|
@@ -45,33 +98,16 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
45
98
|
},
|
|
46
99
|
blob: {
|
|
47
100
|
position: 'absolute',
|
|
48
|
-
width: width * 0.8,
|
|
49
|
-
height: width * 0.8,
|
|
50
|
-
borderRadius: width * 0.4,
|
|
51
101
|
opacity: 0.15,
|
|
52
102
|
},
|
|
53
|
-
blob1: {
|
|
54
|
-
top: -width * 0.2,
|
|
55
|
-
right: -width * 0.2,
|
|
56
|
-
backgroundColor: '#D32F2F',
|
|
57
|
-
},
|
|
58
|
-
blob2: {
|
|
59
|
-
bottom: -width * 0.1,
|
|
60
|
-
left: -width * 0.3,
|
|
61
|
-
backgroundColor: '#B71C1C',
|
|
62
|
-
},
|
|
63
|
-
blob3: {
|
|
64
|
-
top: height * 0.3,
|
|
65
|
-
right: -width * 0.4,
|
|
66
|
-
backgroundColor: '#FF5252',
|
|
67
|
-
width: width * 0.6,
|
|
68
|
-
height: width * 0.6,
|
|
69
|
-
},
|
|
70
103
|
safeArea: {
|
|
71
104
|
flex: 1,
|
|
72
105
|
},
|
|
73
106
|
keyboardView: {
|
|
74
107
|
flex: 1,
|
|
108
|
+
},
|
|
109
|
+
scrollContent: {
|
|
110
|
+
flexGrow: 1,
|
|
75
111
|
justifyContent: 'center',
|
|
76
112
|
alignItems: 'center',
|
|
77
113
|
padding: 24,
|
|
@@ -90,12 +126,15 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
90
126
|
width: 100,
|
|
91
127
|
height: 100,
|
|
92
128
|
borderRadius: 50,
|
|
93
|
-
backgroundColor: 'rgba(211, 47, 47, 0.1)',
|
|
94
129
|
justifyContent: 'center',
|
|
95
130
|
alignItems: 'center',
|
|
96
131
|
marginBottom: 24,
|
|
97
132
|
borderWidth: 1,
|
|
98
|
-
|
|
133
|
+
},
|
|
134
|
+
logoImage: {
|
|
135
|
+
width: 56,
|
|
136
|
+
height: 56,
|
|
137
|
+
borderRadius: 8,
|
|
99
138
|
},
|
|
100
139
|
lockIcon: {
|
|
101
140
|
fontSize: 48,
|
|
@@ -111,7 +150,6 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
111
150
|
divider: {
|
|
112
151
|
width: 40,
|
|
113
152
|
height: 3,
|
|
114
|
-
backgroundColor: '#D32F2F',
|
|
115
153
|
borderRadius: 2,
|
|
116
154
|
marginBottom: 16,
|
|
117
155
|
},
|
|
@@ -124,18 +162,20 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
124
162
|
},
|
|
125
163
|
actions: {
|
|
126
164
|
width: '100%',
|
|
127
|
-
marginBottom: 24,
|
|
128
165
|
},
|
|
129
|
-
|
|
130
|
-
|
|
166
|
+
unlockToggle: {
|
|
167
|
+
marginTop: 8,
|
|
131
168
|
alignItems: 'center',
|
|
132
|
-
|
|
169
|
+
paddingVertical: 8,
|
|
133
170
|
},
|
|
134
171
|
unlockToggleText: {
|
|
135
|
-
color:
|
|
136
|
-
fontSize:
|
|
172
|
+
color: UNLOCK_TOGGLE_COLOR,
|
|
173
|
+
fontSize: 13,
|
|
137
174
|
fontWeight: '600',
|
|
138
175
|
},
|
|
176
|
+
unlockSection: {
|
|
177
|
+
width: '100%',
|
|
178
|
+
},
|
|
139
179
|
inputContainer: {
|
|
140
180
|
width: '100%',
|
|
141
181
|
gap: 12,
|
|
@@ -152,7 +192,7 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
152
192
|
},
|
|
153
193
|
errorText: {
|
|
154
194
|
color: '#FF5252',
|
|
155
|
-
fontSize:
|
|
195
|
+
fontSize: 13,
|
|
156
196
|
textAlign: 'center',
|
|
157
197
|
},
|
|
158
198
|
inputActions: {
|
|
@@ -169,6 +209,9 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
169
209
|
color: 'rgba(255, 255, 255, 0.4)',
|
|
170
210
|
fontWeight: '600',
|
|
171
211
|
},
|
|
212
|
+
cancelButtonTextDisabled: {
|
|
213
|
+
opacity: 0.4,
|
|
214
|
+
},
|
|
172
215
|
unlockButton: {
|
|
173
216
|
flex: 2,
|
|
174
217
|
backgroundColor: '#FFF',
|
|
@@ -177,6 +220,9 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
177
220
|
alignItems: 'center',
|
|
178
221
|
justifyContent: 'center',
|
|
179
222
|
},
|
|
223
|
+
unlockButtonDisabled: {
|
|
224
|
+
opacity: 0.85,
|
|
225
|
+
},
|
|
180
226
|
unlockButtonText: {
|
|
181
227
|
color: '#000',
|
|
182
228
|
fontWeight: 'bold',
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { LockScreenBranding } from './types/branding';
|
|
2
3
|
export type GuardStatus = 'LOCKED' | 'ACTIVE' | 'PENDING' | 'ERROR' | 'EXPIRED' | 'WARNING';
|
|
3
4
|
interface GuardResponse {
|
|
4
5
|
status: GuardStatus;
|
|
@@ -27,6 +28,7 @@ interface GuardResponse {
|
|
|
27
28
|
syncPolicy?: SyncPolicy;
|
|
28
29
|
/** When true, the client locks on emulators/simulators (non-physical devices). */
|
|
29
30
|
blockEmulators?: boolean;
|
|
31
|
+
branding?: LockScreenBranding | null;
|
|
30
32
|
}
|
|
31
33
|
interface DevGuardContextType {
|
|
32
34
|
status: GuardStatus;
|
|
@@ -49,4 +51,5 @@ export declare const DevGuardProvider: React.FC<{
|
|
|
49
51
|
failSafe?: FailSafe;
|
|
50
52
|
}>;
|
|
51
53
|
export declare const useDevGuard: () => DevGuardContextType;
|
|
52
|
-
export {};
|
|
54
|
+
export type { LockScreenBranding } from './types/branding';
|
|
55
|
+
export { resolveBrandingFooter } from './types/branding';
|