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.
@@ -1,29 +1,48 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GuardEnforcement = void 0;
4
+ const react_native_1 = require("react-native");
5
+ const obf_1 = require("../internal/obf");
6
+ const { DevGuard: DevGuardNative } = react_native_1.NativeModules;
4
7
  /**
5
8
  * Client-side policy enforcement applied to a server response before it is
6
- * shown to the user. Mirrors the Flutter plugin's `GuardEnforcement` so both
7
- * platforms behave identically.
9
+ * shown to the user. Mirrors Flutter `GuardEnforcement` + native `dg_e1`.
8
10
  */
9
11
  class GuardEnforcement {
10
- /**
11
- * Applies enforcement rules to a fresh server response.
12
- *
13
- * - `blockEmulators`: if the project blocks emulators and the current device
14
- * is not a physical device, the response is overridden to LOCKED.
15
- *
16
- * @param response The parsed server response (mutated copy returned).
17
- * @param metadata The device metadata collected for this sync.
18
- */
19
- static apply(response, metadata) {
20
- if (response?.blockEmulators === true && metadata?.isPhysicalDevice === false) {
12
+ static async evaluatePolicy(blockEmulators, isPhysicalDevice, isCompromised) {
13
+ if (typeof DevGuardNative?.evaluatePolicy === 'function') {
14
+ try {
15
+ return await DevGuardNative.evaluatePolicy(blockEmulators, isPhysicalDevice, isCompromised);
16
+ }
17
+ catch {
18
+ // Fall through to JS fallback when native bridge unavailable (e.g. tests).
19
+ }
20
+ }
21
+ if (isCompromised)
22
+ return obf_1.PolicyLock.compromised;
23
+ if (blockEmulators && !isPhysicalDevice)
24
+ return obf_1.PolicyLock.emulator;
25
+ return obf_1.PolicyLock.allow;
26
+ }
27
+ static async apply(response, metadata, isCompromised = false) {
28
+ const isPhysical = metadata?.isPhysicalDevice !== false;
29
+ const code = await this.evaluatePolicy(response?.blockEmulators === true, isPhysical, isCompromised);
30
+ if (code === obf_1.PolicyLock.compromised) {
31
+ console.warn(obf_1.Obf.compromisedLog);
32
+ return {
33
+ ...response,
34
+ status: 'LOCKED',
35
+ title: obf_1.Obf.securityTitle,
36
+ message: obf_1.Obf.compromisedMessage,
37
+ };
38
+ }
39
+ if (code === obf_1.PolicyLock.emulator) {
21
40
  console.warn('DevGuard: Emulator blocked by project policy.');
22
41
  return {
23
42
  ...response,
24
43
  status: 'LOCKED',
25
- title: 'Emulator Detected',
26
- message: 'This application cannot run on emulators or simulators for security reasons.',
44
+ title: obf_1.Obf.emulatorTitle,
45
+ message: obf_1.Obf.emulatorMessage,
27
46
  };
28
47
  }
29
48
  return response;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * AES-CBC with key/IV derived from projectId — no OpenSSL random salt (RN-safe).
3
+ * Parity with DevGuardLogger vault key derivation; avoids Node crypto RNG on Android/Hermes.
4
+ */
5
+ export declare function encryptCachePayload(projectId: string, plainText: string): string;
6
+ export declare function decryptCachePayload(projectId: string, cipherText: string): string | null;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.encryptCachePayload = encryptCachePayload;
7
+ exports.decryptCachePayload = decryptCachePayload;
8
+ const crypto_js_1 = __importDefault(require("crypto-js"));
9
+ const CACHE_KEY_SALT = 'dg_cache_key_v1';
10
+ const CACHE_IV_SALT = 'dg_cache_iv_v1';
11
+ /** OpenSSL salted ciphertext prefix (legacy passphrase-based AES). */
12
+ const LEGACY_OPENSSL_PREFIX = 'U2FsdGVk';
13
+ function deriveCacheKeyIv(projectId) {
14
+ const keyHex = crypto_js_1.default.SHA256(`${projectId}_${CACHE_KEY_SALT}`).toString(crypto_js_1.default.enc.Hex);
15
+ const ivHex = crypto_js_1.default.SHA256(`${projectId}_${CACHE_IV_SALT}`).toString(crypto_js_1.default.enc.Hex);
16
+ return {
17
+ key: crypto_js_1.default.enc.Hex.parse(keyHex),
18
+ iv: crypto_js_1.default.enc.Hex.parse(ivHex.substring(0, 32)),
19
+ };
20
+ }
21
+ /**
22
+ * AES-CBC with key/IV derived from projectId — no OpenSSL random salt (RN-safe).
23
+ * Parity with DevGuardLogger vault key derivation; avoids Node crypto RNG on Android/Hermes.
24
+ */
25
+ function encryptCachePayload(projectId, plainText) {
26
+ const { key, iv } = deriveCacheKeyIv(projectId);
27
+ return crypto_js_1.default.AES.encrypt(plainText, key, { iv }).toString();
28
+ }
29
+ function decryptCachePayload(projectId, cipherText) {
30
+ const { key, iv } = deriveCacheKeyIv(projectId);
31
+ const decrypted = crypto_js_1.default.AES.decrypt(cipherText, key, { iv }).toString(crypto_js_1.default.enc.Utf8);
32
+ if (decrypted)
33
+ return decrypted;
34
+ // Legacy entries used passphrase mode (random salt → needs native crypto RNG).
35
+ if (cipherText.startsWith(LEGACY_OPENSSL_PREFIX)) {
36
+ const legacy = crypto_js_1.default.AES.decrypt(cipherText, projectId).toString(crypto_js_1.default.enc.Utf8);
37
+ return legacy || null;
38
+ }
39
+ return null;
40
+ }
@@ -0,0 +1,21 @@
1
+ export interface LockScreenBranding {
2
+ brandName?: string | null;
3
+ logoUrl?: string | null;
4
+ primaryColor?: string;
5
+ accentColor?: string | null;
6
+ websiteUrl?: string | null;
7
+ hidePoweredBy?: boolean;
8
+ }
9
+ /** Default lock-screen palette when API sends no branding colors (Flutter PaymentWall parity). */
10
+ export declare const DEFAULT_LOCK_PRIMARY = "#d32f2f";
11
+ export declare const DEFAULT_LOCK_ACCENT = "#b71c1c";
12
+ export declare function parseHexColor(hex?: string | null, fallback?: string): string;
13
+ export declare function resolveBrandingFooter(branding?: LockScreenBranding | null): {
14
+ hasCustom: boolean;
15
+ label: string;
16
+ brand: string;
17
+ url: string;
18
+ hidePoweredBy: boolean;
19
+ primaryColor: string;
20
+ accentColor: string;
21
+ };
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_LOCK_ACCENT = exports.DEFAULT_LOCK_PRIMARY = void 0;
4
+ exports.parseHexColor = parseHexColor;
5
+ exports.resolveBrandingFooter = resolveBrandingFooter;
6
+ /** Default lock-screen palette when API sends no branding colors (Flutter PaymentWall parity). */
7
+ exports.DEFAULT_LOCK_PRIMARY = '#d32f2f';
8
+ exports.DEFAULT_LOCK_ACCENT = '#b71c1c';
9
+ function parseHexColor(hex, fallback = exports.DEFAULT_LOCK_PRIMARY) {
10
+ if (!hex)
11
+ return fallback;
12
+ const normalized = hex.startsWith('#') ? hex : `#${hex}`;
13
+ return /^#[0-9A-Fa-f]{6}$/.test(normalized) ? normalized : fallback;
14
+ }
15
+ function resolveBrandingFooter(branding) {
16
+ const hasCustom = !!branding && (!!branding.brandName?.trim() || !!branding.logoUrl?.trim());
17
+ return {
18
+ hasCustom,
19
+ label: hasCustom ? 'Powered by' : 'Secured by',
20
+ brand: hasCustom
21
+ ? branding?.brandName?.trim() || 'DevGuard'
22
+ : 'DevGuard',
23
+ url: hasCustom && branding?.websiteUrl?.trim()
24
+ ? branding.websiteUrl.trim()
25
+ : 'https://devguard.uk',
26
+ hidePoweredBy: branding?.hidePoweredBy === true,
27
+ primaryColor: parseHexColor(branding?.primaryColor, exports.DEFAULT_LOCK_PRIMARY),
28
+ accentColor: parseHexColor(branding?.accentColor || branding?.primaryColor, exports.DEFAULT_LOCK_ACCENT),
29
+ };
30
+ }
@@ -0,0 +1,4 @@
1
+ /** Build contact deep-links for lock-screen support buttons (Flutter ContactButton parity). */
2
+ export declare function buildWhatsAppUrl(raw: string): string;
3
+ export declare function buildMailtoUrl(raw: string): string;
4
+ export declare function buildTelUrl(raw: string): string;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ /** Build contact deep-links for lock-screen support buttons (Flutter ContactButton parity). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.buildWhatsAppUrl = buildWhatsAppUrl;
5
+ exports.buildMailtoUrl = buildMailtoUrl;
6
+ exports.buildTelUrl = buildTelUrl;
7
+ function buildWhatsAppUrl(raw) {
8
+ const digits = raw.replace(/[^0-9]/g, '');
9
+ return digits ? `https://wa.me/${digits}` : '';
10
+ }
11
+ function buildMailtoUrl(raw) {
12
+ const email = raw.trim();
13
+ return email ? `mailto:${email}` : '';
14
+ }
15
+ function buildTelUrl(raw) {
16
+ const phone = raw.replace(/[^\d+]/g, '');
17
+ return phone ? `tel:${phone}` : '';
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-dev-guard",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "DevGuard protection for React Native apps",
5
5
  "license": "MIT",
6
6
  "author": "DevGuard-uk",
@@ -31,9 +31,11 @@
31
31
  ],
32
32
  "scripts": {
33
33
  "clean": "rimraf lib",
34
- "build:ts": "tsc",
34
+ "gen:obf": "node tool/genObf.js",
35
+ "build:ts": "npm run gen:obf && tsc",
35
36
  "build:obfuscate": "echo 'Skipping obfuscator for RN bundle compat'",
36
37
  "build": "npm-run-all clean build:ts build:obfuscate",
38
+ "test": "npm run build && node --test test/branding.test.js test/obf.test.js",
37
39
  "prepublishOnly": "npm run build",
38
40
  "verify:versions": "node -e \"const rn=require('react-native/package.json');const r=require('react/package.json');console.log('react-native',rn.version,'react',r.version);\""
39
41
  },
@@ -41,7 +43,7 @@
41
43
  "crypto-js": "^4.2.0",
42
44
  "pako": "^2.1.0",
43
45
  "react-native-safe-area-context": "^5.5.2",
44
- "react-native-vault-logger": "^0.1.2"
46
+ "react-native-vault-logger": "^0.1.3"
45
47
  },
46
48
  "devDependencies": {
47
49
  "@types/crypto-js": "^4.2.2",
@@ -73,5 +75,9 @@
73
75
  "react-native-permissions": {
74
76
  "optional": true
75
77
  }
78
+ },
79
+ "overrides": {
80
+ "image-size": "2.0.2",
81
+ "shell-quote": "1.8.4"
76
82
  }
77
83
  }