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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 DevGuard-uk
3
+ Copyright (c) 2026 DevGuard UK
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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, encrypted vault diagnostics, and stored device-user identity.
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 (encrypted local diagnostics). Use **v3.x** with React Native 0.85+; run `npx pod-install` after adding it on iOS.
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 an encrypted local Vault Logger for advanced debugging without rebuilding your app.
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 intercepts and encrypts fatal JS errors and usage info, saving them securely to local storage using **per-device AES-256-CBC keys** derived from the hardware `deviceId` at init (no static secret in source).
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 the encrypted error vaults.
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. **Obfuscation**: Always use `javascript-obfuscator` or similar tools for your JS bundle.
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
  }
@@ -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 generate_signature(const char* project_id, long long timestamp, char* output) {
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 verify_response(const char* response_body, const char* signature) {
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 secure_save_token(const char* token, char* output) {
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 secure_get_token(const char* scrambled, char* output) {
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
+ }
@@ -14,19 +14,21 @@
14
14
  extern "C" {
15
15
  #endif
16
16
 
17
- // Generates an HMAC-SHA256 signature for a given project ID and timestamp.
18
- // The secret key is embedded inside the compiled binary.
19
- // output must be a buffer of at least 65 bytes (64 hex characters + null terminator).
20
- DEVGUARD_EXPORT void generate_signature(const char* project_id, long long timestamp, char* output);
21
-
22
- // Verifies a server response signature against the response body.
23
- // Returns 1 if valid, 0 if invalid.
24
- DEVGUARD_EXPORT int verify_response(const char* response_body, const char* signature);
25
-
26
- // Secure token scrambling for in-memory protection.
27
- // output must be a buffer of at least the length of token + 1.
28
- DEVGUARD_EXPORT void secure_save_token(const char* token, char* output);
29
- DEVGUARD_EXPORT void secure_get_token(const char* scrambled, char* output);
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
- generate_signature(project_id_c, timestamp_val, output);
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 { width, height } = react_native_1.Dimensions.get('window');
9
- const LockScreen = ({ status, title, message, contactEmail = '', contactPhone = '', contactWhatsapp = '', allowUnlock = true, onUnlock }) => {
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('Invalid unlock key. Please try again.');
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
- console.log("DevGuard: Attempting to open URL:", url);
27
- react_native_1.Linking.openURL(url).catch(err => {
28
- console.error("DevGuard: Failed to open URL:", url, err);
29
- react_native_1.Alert.alert("Error", "Could not open the contact application. Please make sure it is installed and configured.");
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
- 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, styles.blob1] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { pointerEvents: "none", style: [styles.blob, styles.blob2] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { pointerEvents: "none", style: [styles.blob, styles.blob3] }), (0, jsx_runtime_1.jsx)(react_native_1.SafeAreaView, { style: styles.safeArea, children: (0, jsx_runtime_1.jsxs)(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.View, { style: styles.glassCard, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.iconContainer, children: (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 || (status === 'LOCKED' ? 'Access Restricted' :
33
- status === 'WARNING' ? 'Security Warning' :
34
- 'License Expired')).toUpperCase() }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.divider }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.message, children: message || 'This application has been remotely locked by the developer.' }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.actions, children: [contactWhatsapp ? ((0, jsx_runtime_1.jsx)(ContactButton_1.ContactButton, { label: "WhatsApp Support", color: "#25D366", icon: "\uD83D\uDCAC", onPress: () => openUrl(`https://wa.me/${contactWhatsapp.replace(/[^0-9]/g, '')}`) })) : null, contactEmail ? ((0, jsx_runtime_1.jsx)(ContactButton_1.ContactButton, { label: "Email Support", color: "#D32F2F", icon: "\uD83D\uDCE7", onPress: () => openUrl(`mailto:${contactEmail.trim()}`) })) : null, contactPhone ? ((0, jsx_runtime_1.jsx)(ContactButton_1.ContactButton, { label: "Call Support", color: "rgba(255, 255, 255, 0.1)", icon: "\uD83D\uDCDE", onPress: () => openUrl(`tel:${contactPhone.replace(/[^\d+]/g, '')}`) })) : null] }), allowUnlock && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.unlockSection, children: !showUnlock ? ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPress: () => setShowUnlock(true), children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.unlockToggleText, children: "\uD83D\uDD11 Enter Unlock Key" }) })) : ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.inputContainer, children: [(0, jsx_runtime_1.jsx)(react_native_1.TextInput, { style: styles.input, placeholder: "License Key", placeholderTextColor: "rgba(255, 255, 255, 0.4)", value: unlockKey, onChangeText: setUnlockKey, autoCapitalize: "none", secureTextEntry: true }), error && (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.errorText, children: error }), (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: () => {
35
- setShowUnlock(false);
36
- setError(null);
37
- }, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.cancelButtonText, children: "Cancel" }) }), (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: styles.unlockButton, onPress: handleUnlock, disabled: loading, 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: "Unlock Now" })) })] })] })) }))] }), (0, jsx_runtime_1.jsxs)(react_native_1.TouchableOpacity, { style: styles.footer, onPress: () => openUrl('https://antssolution.com/'), children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.footerLabel, children: "Powered by" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.footerBrand, children: "ANTS SOLUTION" })] })] }) })] }));
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
- borderColor: 'rgba(211, 47, 47, 0.2)',
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
- unlockSection: {
130
- width: '100%',
166
+ unlockToggle: {
167
+ marginTop: 8,
131
168
  alignItems: 'center',
132
- marginTop: 16,
169
+ paddingVertical: 8,
133
170
  },
134
171
  unlockToggleText: {
135
- color: 'rgba(255, 255, 255, 0.5)',
136
- fontSize: 14,
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: 12,
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';