@gatekeeperx/cordova-plugin-devicex 0.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/CHANGELOG.md +33 -0
- package/LICENSE +11 -0
- package/README.md +171 -0
- package/package.json +46 -0
- package/plugin.xml +51 -0
- package/src/android/com/gatekeeperx/cordova/DevicexPlugin.java +240 -0
- package/src/android/com/gatekeeperx/cordova/GatekeeperXClient.java +110 -0
- package/src/android/hooks/configure-gradle.js +62 -0
- package/src/android/libs/devicex-1.3.1.aar +0 -0
- package/src/android/libs/devicex-bridge.aar +0 -0
- package/types/index.d.ts +57 -0
- package/www/devicex.js +51 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
> **Nota:** La versión del plugin Cordova es independiente de la versión del SDK nativo (AAR).
|
|
4
|
+
> Cada release del plugin indica qué versión del SDK nativo incluye.
|
|
5
|
+
|
|
6
|
+
## [0.1.0] - 2026-02-10
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- `getPluginVersion()` — retorna la versión del plugin Cordova (string estático).
|
|
10
|
+
- Versionamiento independiente del plugin Cordova vs SDK nativo.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Plugin Cordova ahora usa versión `0.1.0` (antes usaba la misma del SDK nativo).
|
|
14
|
+
- `getVersion()` sigue retornando la versión del **SDK nativo** (AAR).
|
|
15
|
+
|
|
16
|
+
### SDK nativo incluido
|
|
17
|
+
- Device Intelligence SDK: **1.3.x** (AAR)
|
|
18
|
+
- Bridge: `devicex-bridge.aar`
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## [1.0.0] - 2026-01-19 (versión legacy, alineada con SDK)
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- Initial release
|
|
26
|
+
- Android support with cordova-android >= 11
|
|
27
|
+
- Pre-compiled AAR bridge (Kotlin)
|
|
28
|
+
- Device Intelligence SDK integration
|
|
29
|
+
- Auto-configuration via plugin variables
|
|
30
|
+
- TypeScript definitions
|
|
31
|
+
|
|
32
|
+
### SDK nativo incluido
|
|
33
|
+
- Device Intelligence SDK: **1.0.0** (AAR)
|
package/LICENSE
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Apache License 2.0
|
|
2
|
+
Copyright 2026 GatekeeperX
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
See the License for the specific language governing permissions and
|
|
11
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# @gatekeeperx/cordova-plugin-devicex
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
4
|
+
|
|
5
|
+
Plugin oficial de Cordova para la integración del SDK **GatekeeperX Device Intelligence** en aplicaciones Android. Proporciona capacidades avanzadas de fingerprinting, detección de fraude (root, emuladores, hooks) y telemetría de seguridad.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📋 Requisitos
|
|
10
|
+
|
|
11
|
+
Para integrar este plugin, asegúrate de cumplir con los siguientes requisitos:
|
|
12
|
+
|
|
13
|
+
- **Cordova Android**: Versión `11.0.0` o superior.
|
|
14
|
+
- **Java**: JDK 11 o 17.
|
|
15
|
+
- **Android SDK**: API Level 21+ soportado (API 31+ recomendado).
|
|
16
|
+
- **GatekeeperX Credentials**: Tenant ID y API Key válidos.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 🔧 Instalación
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Prerequisitos
|
|
24
|
+
|
|
25
|
+
Asegúrate de tener instalado Node.js y npm en tu máquina. ejecuta este comando para instalar el plugin:
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm i @gatekeeperx/cordova-plugin-devicex
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Instalación
|
|
33
|
+
|
|
34
|
+
Instala el plugin utilizando el CLI de Cordova o Ionic. Esto configurará automáticamente las dependencias nativas en tu proyecto.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Usando Ionic CLI (Recomendado)
|
|
38
|
+
ionic cordova plugin add @gatekeeperx/cordova-plugin-devicex
|
|
39
|
+
|
|
40
|
+
# Usando Cordova CLI
|
|
41
|
+
cordova plugin add @gatekeeperx/cordova-plugin-devicex
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Configuración de Plataforma
|
|
45
|
+
|
|
46
|
+
Asegúrate de tener la plataforma Android configurada correctamente:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cordova platform add android@11
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 📚 API Reference
|
|
55
|
+
|
|
56
|
+
El plugin expone el objeto global `Devicex` una vez que el evento `deviceready` se ha disparado.
|
|
57
|
+
|
|
58
|
+
### 1. `Devicex.configure(options)`
|
|
59
|
+
Inicializa el SDK con las credenciales de tu proyecto. **Debe llamarse antes que cualquier otro método.**
|
|
60
|
+
|
|
61
|
+
**Parámetros:**
|
|
62
|
+
- `options` (`ConfigureOptions`):
|
|
63
|
+
- `tenant` (string, requerido): ID de tu tenant.
|
|
64
|
+
- `apiKey` (string, requerido): Tu clave de API.
|
|
65
|
+
- `environment` (string, opcional): `'SANDBOX'`, o `'PRODUCTION'`.
|
|
66
|
+
- `organizationId` (string, opcional): Identificador de organización.
|
|
67
|
+
|
|
68
|
+
**Ejemplo:**
|
|
69
|
+
```typescript
|
|
70
|
+
await Devicex.configure({
|
|
71
|
+
tenant: 'mi-tenant',
|
|
72
|
+
apiKey: 'tu-api-key',
|
|
73
|
+
environment: 'PRODUCTION'
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2. `Devicex.sendEvent(name, properties?, headers?)`
|
|
78
|
+
Envía un evento de telemetría con señales de seguridad adjuntas automáticamente.
|
|
79
|
+
|
|
80
|
+
**Parámetros:**
|
|
81
|
+
- `name` (string, requerido): Nombre del evento (ej: `'login'`, `'checkout'`).
|
|
82
|
+
- `properties` (object, opcional): Metadatos adicionales del evento.
|
|
83
|
+
- `headers` (object, opcional): Cabeceras HTTP personalizadas.
|
|
84
|
+
|
|
85
|
+
**Ejemplo:**
|
|
86
|
+
```typescript
|
|
87
|
+
const result = await Devicex.sendEvent('login', { user: 'id_123' });
|
|
88
|
+
if (result.ok) {
|
|
89
|
+
console.log('DeviceX ID:', result.deviceXId);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. `Devicex.isInitialized()`
|
|
94
|
+
Verifica si el SDK ha sido configurado correctamente.
|
|
95
|
+
|
|
96
|
+
**Retorna:** `Promise<boolean>`
|
|
97
|
+
|
|
98
|
+
### 4. `Devicex.getVersion()`
|
|
99
|
+
Obtiene la versión del SDK nativo subyacente.
|
|
100
|
+
|
|
101
|
+
**Retorna:** `Promise<string>`
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 🚀 Uso Rápido (Ionic/Angular)
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// En app.component.ts
|
|
109
|
+
async initializeApp() {
|
|
110
|
+
await this.platform.ready();
|
|
111
|
+
|
|
112
|
+
if (this.platform.is('cordova')) {
|
|
113
|
+
declare const Devicex: any;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await Devicex.configure({
|
|
117
|
+
tenant: 'gatekeeperx', // tu tenant
|
|
118
|
+
apiKey: 'YOUR_API_KEY', // tu api key
|
|
119
|
+
environment: 'PRODUCTION' // PRODUCTION o SANDBOX
|
|
120
|
+
});
|
|
121
|
+
console.log('DeviceX Ready');
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.error('DeviceX Init Error', err);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async login() {
|
|
130
|
+
//,,, logica previo a enviar el login
|
|
131
|
+
|
|
132
|
+
loading.present();
|
|
133
|
+
try {
|
|
134
|
+
// Enviar evento de login para obtener deviceXId
|
|
135
|
+
const result = await this.deviceX.sendEvent('login', {
|
|
136
|
+
username: this.username,
|
|
137
|
+
method: 'password',
|
|
138
|
+
timestamp: new Date().toISOString(),
|
|
139
|
+
screen: 'login'
|
|
140
|
+
});
|
|
141
|
+
console.log('Evento enviado, deviceXId:', result.deviceXId);
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
/** EVALUACION DE RIESGO
|
|
145
|
+
* Evaluar riesgo con GatekeeperX - debe hacerse back to back, no en el cliente.
|
|
146
|
+
* debe ser dentro del proceso del login en el backend
|
|
147
|
+
* si la decision es allow, se puede continuar con el login
|
|
148
|
+
* si la decision es deny, se debe denegar el acceso
|
|
149
|
+
* si la decision es unknown, se debe denegar el acceso
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
//invocar proceso de login en el backend
|
|
153
|
+
const response = await this.http.post(BACKEND_URL, {
|
|
154
|
+
username: this.username,
|
|
155
|
+
password: this.password,
|
|
156
|
+
deviceXId: result.deviceXId
|
|
157
|
+
});
|
|
158
|
+
console.log('Login response:', JSON.stringify(response, null, 2));
|
|
159
|
+
|
|
160
|
+
loading.dismiss();
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
console.log('Login event result:', JSON.stringify(result, null, 2));
|
|
164
|
+
|
|
165
|
+
} catch (error) {
|
|
166
|
+
loading.dismiss();
|
|
167
|
+
console.error('Login event error:', error);
|
|
168
|
+
this.showAlert('Error', 'No se pudo enviar el evento de login', 'error');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gatekeeperx/cordova-plugin-devicex",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "GatekeeperX Device Intelligence Cordova plugin (Android)",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"cordova",
|
|
8
|
+
"cordova-android",
|
|
9
|
+
"gatekeeperx",
|
|
10
|
+
"device",
|
|
11
|
+
"security",
|
|
12
|
+
"intelligence"
|
|
13
|
+
],
|
|
14
|
+
"engines": [
|
|
15
|
+
{
|
|
16
|
+
"name": "cordova-android",
|
|
17
|
+
"version": ">=11.0.0"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"cordova": {
|
|
21
|
+
"id": "cordova-plugin-devicex",
|
|
22
|
+
"platforms": [
|
|
23
|
+
"android"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"main": "www/devicex.js",
|
|
27
|
+
"types": "types/index.d.ts",
|
|
28
|
+
"files": [
|
|
29
|
+
"plugin.xml",
|
|
30
|
+
"www/",
|
|
31
|
+
"types/",
|
|
32
|
+
"src/android/",
|
|
33
|
+
"README.md",
|
|
34
|
+
"CHANGELOG.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/gatekeeperx/deviceX-plugins.git"
|
|
40
|
+
},
|
|
41
|
+
"author": "GatekeeperX",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/gatekeeperx/deviceX-plugins/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/gatekeeperx/deviceX-plugins/tree/master/cordova/cordova-plugin-devicex"
|
|
46
|
+
}
|
package/plugin.xml
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<plugin id="@gatekeeperx/cordova-plugin-devicex"
|
|
3
|
+
xmlns="http://apache.org/cordova/ns/plugins/1.0"
|
|
4
|
+
xmlns:android="http://schemas.android.com/apk/res/android" version="0.1.0">
|
|
5
|
+
<name>Devicex</name>
|
|
6
|
+
<description>GatekeeperX Device Intelligence Cordova plugin (Android)</description>
|
|
7
|
+
<license>Apache-2.0</license>
|
|
8
|
+
<keywords>cordova, gatekeeperx, device, intelligence, security</keywords>
|
|
9
|
+
|
|
10
|
+
<js-module name="Devicex" src="www/devicex.js">
|
|
11
|
+
<clobbers target="DeviceX" />
|
|
12
|
+
</js-module>
|
|
13
|
+
|
|
14
|
+
<engines>
|
|
15
|
+
<engine name="cordova-android" version=">=11.0.0" />
|
|
16
|
+
</engines>
|
|
17
|
+
|
|
18
|
+
<platform name="android">
|
|
19
|
+
<!-- Runtime dependencies -->
|
|
20
|
+
<!-- Kotlin stdlib alineado con el bridge -->
|
|
21
|
+
<framework src="org.jetbrains.kotlin:kotlin-stdlib:1.9.25" />
|
|
22
|
+
<framework src="org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" />
|
|
23
|
+
|
|
24
|
+
<!-- Kotlinx Serialization (requerido por Ktor) -->
|
|
25
|
+
<framework src="org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2" />
|
|
26
|
+
|
|
27
|
+
<!-- Ktor Client (HTTP engine del SDK) -->
|
|
28
|
+
<framework src="io.ktor:ktor-client-cio:2.3.7" />
|
|
29
|
+
<framework src="io.ktor:ktor-client-logging:2.3.7" />
|
|
30
|
+
<framework src="io.ktor:ktor-client-content-negotiation:2.3.7" />
|
|
31
|
+
<framework src="io.ktor:ktor-serialization-kotlinx-json:2.3.7" />
|
|
32
|
+
<!-- AARs locales -->
|
|
33
|
+
<lib-file src="src/android/libs/devicex-bridge.aar" arch="device" />
|
|
34
|
+
<lib-file src="src/android/libs/devicex-1.3.1.aar" arch="device" />
|
|
35
|
+
|
|
36
|
+
<!-- Hook para configurar Gradle -->
|
|
37
|
+
<hook type="after_plugin_install" src="src/android/hooks/configure-gradle.js" />
|
|
38
|
+
|
|
39
|
+
<!-- Registrar el plugin nativo en config.xml -->
|
|
40
|
+
<config-file target="res/xml/config.xml" parent="/*">
|
|
41
|
+
<feature name="Devicex">
|
|
42
|
+
<param name="android-package" value="com.gatekeeperx.cordova.DevicexPlugin" />
|
|
43
|
+
<param name="onload" value="true" />
|
|
44
|
+
</feature>
|
|
45
|
+
</config-file>
|
|
46
|
+
|
|
47
|
+
<!-- Incluir las clases Java del plugin -->
|
|
48
|
+
<source-file src="src/android/com/gatekeeperx/cordova/DevicexPlugin.java" target-dir="src/com/gatekeeperx/cordova" />
|
|
49
|
+
<source-file src="src/android/com/gatekeeperx/cordova/GatekeeperXClient.java" target-dir="src/com/gatekeeperx/cordova" />
|
|
50
|
+
</platform>
|
|
51
|
+
</plugin>
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
package com.gatekeeperx.cordova;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
|
|
5
|
+
import org.apache.cordova.CallbackContext;
|
|
6
|
+
import org.apache.cordova.CordovaInterface;
|
|
7
|
+
import org.apache.cordova.CordovaPlugin;
|
|
8
|
+
import org.apache.cordova.CordovaWebView;
|
|
9
|
+
import org.json.JSONArray;
|
|
10
|
+
import org.json.JSONException;
|
|
11
|
+
import org.json.JSONObject;
|
|
12
|
+
|
|
13
|
+
import java.util.ArrayList;
|
|
14
|
+
import java.util.HashMap;
|
|
15
|
+
import java.util.Iterator;
|
|
16
|
+
import java.util.List;
|
|
17
|
+
import java.util.Map;
|
|
18
|
+
|
|
19
|
+
public class DevicexPlugin extends CordovaPlugin {
|
|
20
|
+
|
|
21
|
+
@Override
|
|
22
|
+
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
|
23
|
+
super.initialize(cordova, webView);
|
|
24
|
+
try {
|
|
25
|
+
Context ctx = cordova.getActivity().getApplicationContext();
|
|
26
|
+
android.util.Log.d("DevicexPlugin", "Reading meta-data from manifest...");
|
|
27
|
+
|
|
28
|
+
android.content.pm.ApplicationInfo ai = ctx.getPackageManager()
|
|
29
|
+
.getApplicationInfo(ctx.getPackageName(), android.content.pm.PackageManager.GET_META_DATA);
|
|
30
|
+
android.os.Bundle meta = ai.metaData;
|
|
31
|
+
|
|
32
|
+
if (meta == null) {
|
|
33
|
+
android.util.Log.e("DevicexPlugin", "No meta-data found in manifest");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
String tenant = meta.getString("com.gatekeeperx.deviceintelligence.TENANT");
|
|
38
|
+
String apiKey = meta.getString("com.gatekeeperx.deviceintelligence.API_KEY");
|
|
39
|
+
String env = meta.getString("com.gatekeeperx.deviceintelligence.ENVIRONMENT");
|
|
40
|
+
String orgId = meta.getString("com.gatekeeperx.deviceintelligence.ORGANIZATION_ID");
|
|
41
|
+
|
|
42
|
+
android.util.Log.d("DevicexPlugin", "Tenant: " + tenant + ", Env: " + env);
|
|
43
|
+
|
|
44
|
+
if (tenant == null || tenant.trim().isEmpty() || apiKey == null || apiKey.trim().isEmpty()) {
|
|
45
|
+
android.util.Log.d("DevicexPlugin", "Auto-config skipped: no credentials provided");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
boolean ok = DevicexBridge.INSTANCE.configure(
|
|
50
|
+
ctx,
|
|
51
|
+
tenant.trim(),
|
|
52
|
+
apiKey.trim(),
|
|
53
|
+
env != null ? env.trim() : "PRODUCTION",
|
|
54
|
+
orgId != null ? orgId.trim() : null,
|
|
55
|
+
null);
|
|
56
|
+
|
|
57
|
+
android.util.Log.d("DevicexPlugin", "Manual configure result: " + ok);
|
|
58
|
+
} catch (Throwable e) {
|
|
59
|
+
android.util.Log.e("DevicexPlugin", "Error initializing: " + e.getMessage(), e);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Override
|
|
64
|
+
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
|
|
65
|
+
switch (action) {
|
|
66
|
+
case "configure":
|
|
67
|
+
configure(args, callbackContext);
|
|
68
|
+
return true;
|
|
69
|
+
case "sendEvent":
|
|
70
|
+
sendEvent(args, callbackContext);
|
|
71
|
+
return true;
|
|
72
|
+
case "isInitialized":
|
|
73
|
+
try {
|
|
74
|
+
boolean value = DevicexBridge.INSTANCE.isInitialized();
|
|
75
|
+
callbackContext.success(new JSONObject().put("value", value));
|
|
76
|
+
} catch (JSONException e) {
|
|
77
|
+
callbackContext.error(e.getMessage());
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
case "getVersion":
|
|
81
|
+
try {
|
|
82
|
+
String value = DevicexBridge.INSTANCE.getVersion();
|
|
83
|
+
callbackContext.success(new JSONObject().put("value", value));
|
|
84
|
+
} catch (JSONException e) {
|
|
85
|
+
callbackContext.error(e.getMessage());
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
// case "evaluateRisk":
|
|
89
|
+
// evaluateRisk(args, callbackContext);
|
|
90
|
+
// return true;
|
|
91
|
+
default:
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private void configure(JSONArray args, CallbackContext cb) {
|
|
97
|
+
try {
|
|
98
|
+
JSONObject obj = args.optJSONObject(0);
|
|
99
|
+
if (obj == null)
|
|
100
|
+
obj = new JSONObject();
|
|
101
|
+
Context ctx = cordova.getActivity().getApplicationContext();
|
|
102
|
+
String tenant = obj.optString("tenant", "").trim();
|
|
103
|
+
String apiKey = obj.optString("apiKey", "").trim();
|
|
104
|
+
String orgId = obj.optString("organizationId", "").trim();
|
|
105
|
+
String env = obj.optString("environment", "PRODUCTION").trim();
|
|
106
|
+
JSONObject headersObj = obj.optJSONObject("headers");
|
|
107
|
+
Map<String, String> headers = headersObj != null ? toStringMap(headersObj) : new HashMap<>();
|
|
108
|
+
|
|
109
|
+
if (tenant.isEmpty() || apiKey.isEmpty()) {
|
|
110
|
+
cb.error("tenant and apiKey are required");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
boolean ok = DevicexBridge.INSTANCE.configure(
|
|
115
|
+
ctx,
|
|
116
|
+
tenant,
|
|
117
|
+
apiKey,
|
|
118
|
+
env,
|
|
119
|
+
orgId.isEmpty() ? null : orgId,
|
|
120
|
+
headers);
|
|
121
|
+
if (ok)
|
|
122
|
+
cb.success();
|
|
123
|
+
else
|
|
124
|
+
cb.error("configure failed");
|
|
125
|
+
} catch (Throwable e) {
|
|
126
|
+
cb.error(e.getMessage());
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private void sendEvent(JSONArray args, final CallbackContext cb) {
|
|
131
|
+
try {
|
|
132
|
+
JSONObject obj = args.optJSONObject(0);
|
|
133
|
+
if (obj == null)
|
|
134
|
+
obj = new JSONObject();
|
|
135
|
+
String name = obj.optString("name", "").trim();
|
|
136
|
+
if (name.isEmpty()) {
|
|
137
|
+
cb.error("name is required");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
JSONObject propsObj = obj.optJSONObject("properties");
|
|
141
|
+
JSONObject headersObj = obj.optJSONObject("headers");
|
|
142
|
+
Map<String, Object> properties = propsObj != null ? toAnyMap(propsObj) : new HashMap<String, Object>();
|
|
143
|
+
Map<String, String> headers = headersObj != null ? toStringMap(headersObj) : new HashMap<String, String>();
|
|
144
|
+
|
|
145
|
+
@SuppressWarnings("unchecked")
|
|
146
|
+
DevicexBridge.EventCallback callback = new DevicexBridge.EventCallback() {
|
|
147
|
+
@Override
|
|
148
|
+
public void onResult(Map result) {
|
|
149
|
+
try {
|
|
150
|
+
cb.success(new JSONObject(result));
|
|
151
|
+
} catch (Throwable t) {
|
|
152
|
+
cb.error(t.getMessage());
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
DevicexBridge.INSTANCE.sendEvent(name, properties, headers, callback);
|
|
157
|
+
} catch (Throwable e) {
|
|
158
|
+
cb.error(e.getMessage());
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Helpers
|
|
163
|
+
private static Map<String, String> toStringMap(JSONObject obj) {
|
|
164
|
+
Map<String, String> map = new HashMap<>();
|
|
165
|
+
Iterator<String> it = obj.keys();
|
|
166
|
+
while (it.hasNext()) {
|
|
167
|
+
String k = it.next();
|
|
168
|
+
String v = obj.optString(k, null);
|
|
169
|
+
if (v != null)
|
|
170
|
+
map.put(k, v);
|
|
171
|
+
}
|
|
172
|
+
return map;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private static Map<String, Object> toAnyMap(JSONObject obj) {
|
|
176
|
+
Map<String, Object> map = new HashMap<>();
|
|
177
|
+
Iterator<String> it = obj.keys();
|
|
178
|
+
while (it.hasNext()) {
|
|
179
|
+
String k = it.next();
|
|
180
|
+
Object v = obj.opt(k);
|
|
181
|
+
map.put(k, fromJson(v));
|
|
182
|
+
}
|
|
183
|
+
return map;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private static Object fromJson(Object v) {
|
|
187
|
+
if (v == null || v == JSONObject.NULL)
|
|
188
|
+
return null;
|
|
189
|
+
if (v instanceof JSONObject)
|
|
190
|
+
return toAnyMap((JSONObject) v);
|
|
191
|
+
if (v instanceof JSONArray)
|
|
192
|
+
return toList((JSONArray) v);
|
|
193
|
+
if (v instanceof String || v instanceof Boolean || v instanceof Integer || v instanceof Long
|
|
194
|
+
|| v instanceof Double)
|
|
195
|
+
return v;
|
|
196
|
+
return String.valueOf(v);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private static List<Object> toList(JSONArray arr) {
|
|
200
|
+
List<Object> list = new ArrayList<>();
|
|
201
|
+
for (int i = 0; i < arr.length(); i++) {
|
|
202
|
+
Object v = arr.opt(i);
|
|
203
|
+
list.add(fromJson(v));
|
|
204
|
+
}
|
|
205
|
+
return list;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// private void evaluateRisk(JSONArray args, final CallbackContext cb) {
|
|
209
|
+
// try {
|
|
210
|
+
// JSONObject obj = args.optJSONObject(0);
|
|
211
|
+
// if (obj == null)
|
|
212
|
+
// obj = new JSONObject();
|
|
213
|
+
|
|
214
|
+
// String apiUrl = obj.optString("apiUrl", "");
|
|
215
|
+
// String orgId = obj.optString("organizationId", "");
|
|
216
|
+
// String apiKey = obj.optString("apiKey", "");
|
|
217
|
+
// JSONObject payload = obj.optJSONObject("payload");
|
|
218
|
+
|
|
219
|
+
// if (apiUrl.isEmpty() || orgId.isEmpty() || payload == null) {
|
|
220
|
+
// cb.error("apiUrl, organizationId and payload are required");
|
|
221
|
+
// return;
|
|
222
|
+
// }
|
|
223
|
+
|
|
224
|
+
// GatekeeperXClient.evaluateRisk(apiUrl, orgId, apiKey, payload, new
|
|
225
|
+
// GatekeeperXClient.Callback() {
|
|
226
|
+
// @Override
|
|
227
|
+
// public void onSuccess(JSONObject response) {
|
|
228
|
+
// cb.success(response);
|
|
229
|
+
// }
|
|
230
|
+
|
|
231
|
+
// @Override
|
|
232
|
+
// public void onError(String error) {
|
|
233
|
+
// cb.error(error);
|
|
234
|
+
// }
|
|
235
|
+
// });
|
|
236
|
+
// } catch (Exception e) {
|
|
237
|
+
// cb.error(e.getMessage());
|
|
238
|
+
// }
|
|
239
|
+
// }
|
|
240
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
package com.gatekeeperx.cordova;
|
|
2
|
+
|
|
3
|
+
import android.os.AsyncTask;
|
|
4
|
+
import android.util.Log;
|
|
5
|
+
|
|
6
|
+
import org.json.JSONObject;
|
|
7
|
+
|
|
8
|
+
import java.io.BufferedReader;
|
|
9
|
+
import java.io.InputStreamReader;
|
|
10
|
+
import java.io.OutputStream;
|
|
11
|
+
import java.net.HttpURLConnection;
|
|
12
|
+
import java.net.URL;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Cliente HTTP para llamadas al API de GatekeeperX.
|
|
16
|
+
* Ejecuta requests en background para evitar bloquear el UI thread.
|
|
17
|
+
*/
|
|
18
|
+
public class GatekeeperXClient {
|
|
19
|
+
|
|
20
|
+
private static final String TAG = "GatekeeperXClient";
|
|
21
|
+
private static final int CONNECT_TIMEOUT = 30000;
|
|
22
|
+
private static final int READ_TIMEOUT = 30000;
|
|
23
|
+
|
|
24
|
+
public interface Callback {
|
|
25
|
+
void onSuccess(JSONObject response);
|
|
26
|
+
|
|
27
|
+
void onError(String error);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Evalúa el riesgo de un evento enviando datos al API de GatekeeperX.
|
|
32
|
+
*
|
|
33
|
+
* @param apiUrl URL del endpoint (ej:
|
|
34
|
+
* https://api.gatekeeperx.com/gatekeeperx/events/v1)
|
|
35
|
+
* @param organizationId ID de la organización
|
|
36
|
+
* @param apiKey API Key para autenticación (opcional)
|
|
37
|
+
* @param payload JSON con los datos del evento
|
|
38
|
+
* @param callback Callback para manejar respuesta/error
|
|
39
|
+
*/
|
|
40
|
+
private static void evaluateRisk(
|
|
41
|
+
final String apiUrl,
|
|
42
|
+
final String organizationId,
|
|
43
|
+
final String apiKey,
|
|
44
|
+
final JSONObject payload,
|
|
45
|
+
final Callback callback) {
|
|
46
|
+
|
|
47
|
+
new AsyncTask<Void, Void, Object>() {
|
|
48
|
+
@Override
|
|
49
|
+
protected Object doInBackground(Void... voids) {
|
|
50
|
+
HttpURLConnection conn = null;
|
|
51
|
+
try {
|
|
52
|
+
URL url = new URL(apiUrl);
|
|
53
|
+
conn = (HttpURLConnection) url.openConnection();
|
|
54
|
+
conn.setRequestMethod("POST");
|
|
55
|
+
conn.setRequestProperty("Content-Type", "application/json");
|
|
56
|
+
conn.setRequestProperty("X-Organization-Id", organizationId);
|
|
57
|
+
if (apiKey != null && !apiKey.isEmpty()) {
|
|
58
|
+
conn.setRequestProperty("X-Api-Key", apiKey);
|
|
59
|
+
}
|
|
60
|
+
conn.setDoOutput(true);
|
|
61
|
+
conn.setConnectTimeout(CONNECT_TIMEOUT);
|
|
62
|
+
conn.setReadTimeout(READ_TIMEOUT);
|
|
63
|
+
|
|
64
|
+
OutputStream os = conn.getOutputStream();
|
|
65
|
+
os.write(payload.toString().getBytes("UTF-8"));
|
|
66
|
+
os.close();
|
|
67
|
+
|
|
68
|
+
int responseCode = conn.getResponseCode();
|
|
69
|
+
BufferedReader br;
|
|
70
|
+
if (responseCode >= 200 && responseCode < 300) {
|
|
71
|
+
br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
|
|
72
|
+
} else {
|
|
73
|
+
br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "UTF-8"));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
StringBuilder response = new StringBuilder();
|
|
77
|
+
String line;
|
|
78
|
+
while ((line = br.readLine()) != null) {
|
|
79
|
+
response.append(line);
|
|
80
|
+
}
|
|
81
|
+
br.close();
|
|
82
|
+
|
|
83
|
+
Log.d(TAG, "Response code: " + responseCode);
|
|
84
|
+
Log.d(TAG, "Response body: " + response.toString());
|
|
85
|
+
|
|
86
|
+
if (responseCode >= 200 && responseCode < 300) {
|
|
87
|
+
return new JSONObject(response.toString());
|
|
88
|
+
} else {
|
|
89
|
+
return "HTTP " + responseCode + ": " + response.toString();
|
|
90
|
+
}
|
|
91
|
+
} catch (Exception e) {
|
|
92
|
+
Log.e(TAG, "evaluateRisk error: " + e.getMessage(), e);
|
|
93
|
+
return e.getMessage();
|
|
94
|
+
} finally {
|
|
95
|
+
if (conn != null)
|
|
96
|
+
conn.disconnect();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Override
|
|
101
|
+
protected void onPostExecute(Object result) {
|
|
102
|
+
if (result instanceof JSONObject) {
|
|
103
|
+
callback.onSuccess((JSONObject) result);
|
|
104
|
+
} else {
|
|
105
|
+
callback.onError((String) result);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}.execute();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
module.exports = function (context) {
|
|
7
|
+
const platforms = context.opts.cordova.platforms;
|
|
8
|
+
if (platforms.indexOf('android') === -1) return;
|
|
9
|
+
|
|
10
|
+
const buildGradlePath = path.join(
|
|
11
|
+
context.opts.projectRoot,
|
|
12
|
+
'platforms',
|
|
13
|
+
'android',
|
|
14
|
+
'app',
|
|
15
|
+
'build.gradle'
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(buildGradlePath)) {
|
|
19
|
+
console.log('[devicex] build.gradle no encontrado');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let buildGradle = fs.readFileSync(buildGradlePath, 'utf8');
|
|
24
|
+
|
|
25
|
+
// Agregar repositorio flatDir si no existe en el bloque principal
|
|
26
|
+
if (buildGradle.indexOf('flatDir') === -1) {
|
|
27
|
+
const repoBlock = `
|
|
28
|
+
repositories {
|
|
29
|
+
flatDir {
|
|
30
|
+
dirs 'libs'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
// Insertar antes del último bloque dependencies (el principal)
|
|
35
|
+
const lastDepsIndex = buildGradle.lastIndexOf('dependencies {');
|
|
36
|
+
if (lastDepsIndex !== -1) {
|
|
37
|
+
buildGradle = buildGradle.slice(0, lastDepsIndex) +
|
|
38
|
+
repoBlock + '\n' +
|
|
39
|
+
buildGradle.slice(lastDepsIndex);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Agregar dependencias de AARs después de fileTree si no existen
|
|
44
|
+
if (buildGradle.indexOf('devicex-bridge') === -1) {
|
|
45
|
+
const depsBlock = ` implementation files('libs/devicex-bridge.aar')
|
|
46
|
+
implementation files('libs/devicex-1.3.1.aar')
|
|
47
|
+
|
|
48
|
+
// Dependencias del SDK Devicex
|
|
49
|
+
implementation 'io.ktor:ktor-client-core:2.3.7'
|
|
50
|
+
implementation 'io.ktor:ktor-client-okhttp:2.3.7'
|
|
51
|
+
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2'
|
|
52
|
+
`;
|
|
53
|
+
// Buscar después de fileTree en el bloque principal
|
|
54
|
+
buildGradle = buildGradle.replace(
|
|
55
|
+
/(implementation fileTree\(dir: 'libs', include: '\*\.jar'\))/,
|
|
56
|
+
'$1\n' + depsBlock
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fs.writeFileSync(buildGradlePath, buildGradle, 'utf8');
|
|
61
|
+
console.log('[devicex] build.gradle configurado con AARs locales');
|
|
62
|
+
};
|
|
Binary file
|
|
Binary file
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Type definitions for @gatekeeperx/cordova-plugin-devicex
|
|
2
|
+
// Project: https://npmjs.com/package/@gatekeeperx/cordova-plugin-devicex
|
|
3
|
+
// Definitions by: GatekeeperX
|
|
4
|
+
|
|
5
|
+
export type Environment = 'DEV' | 'DEVELOPMENT' | 'SANDBOX' | 'STAGE' | 'STAGING' | 'PROD' | 'PRODUCTION';
|
|
6
|
+
|
|
7
|
+
export interface ConfigureOptions {
|
|
8
|
+
tenant: string;
|
|
9
|
+
apiKey: string;
|
|
10
|
+
organizationId?: string;
|
|
11
|
+
environment?: Environment;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface EventSuccess {
|
|
16
|
+
ok: true;
|
|
17
|
+
code: number;
|
|
18
|
+
deviceXId: string;
|
|
19
|
+
message?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface EventFailure {
|
|
23
|
+
ok: false;
|
|
24
|
+
errorCode: string;
|
|
25
|
+
errorMessage: string;
|
|
26
|
+
httpCode?: number | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type EventResult = EventSuccess | EventFailure;
|
|
30
|
+
|
|
31
|
+
export interface RiskAssessment {
|
|
32
|
+
isRooted: boolean;
|
|
33
|
+
isFridaDetected: boolean;
|
|
34
|
+
isXposedDetected: boolean;
|
|
35
|
+
isMagiskDetected: boolean;
|
|
36
|
+
isEmulator: boolean;
|
|
37
|
+
isDebuggerAttached: boolean;
|
|
38
|
+
isHookingDetected: boolean;
|
|
39
|
+
riskScore: number;
|
|
40
|
+
riskLevel: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
declare const Devicex: {
|
|
44
|
+
configure(options: ConfigureOptions): Promise<void>;
|
|
45
|
+
sendEvent(name: string, properties?: Record<string, any>, headers?: Record<string, string>): Promise<EventResult>;
|
|
46
|
+
getRiskAssessment(): Promise<RiskAssessment>;
|
|
47
|
+
isInitialized(): Promise<boolean>;
|
|
48
|
+
|
|
49
|
+
/** Retorna la versión del SDK nativo (AAR). Ej: "1.3.1" */
|
|
50
|
+
getVersion(): Promise<string>;
|
|
51
|
+
|
|
52
|
+
/** Retorna la versión del plugin Cordova (wrapper). Ej: "0.1.0" */
|
|
53
|
+
getPluginVersion(): string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default Devicex;
|
|
57
|
+
export as namespace Devicex;
|
package/www/devicex.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
var exec = require('cordova/exec');
|
|
2
|
+
var PLUGIN = 'Devicex';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Versión del plugin Cordova (wrapper).
|
|
6
|
+
* Independiente de la versión del SDK nativo (AAR).
|
|
7
|
+
*
|
|
8
|
+
* - Plugin Cordova: 0.1.0 (este valor)
|
|
9
|
+
* - SDK nativo: se obtiene con getVersion() (desde el AAR)
|
|
10
|
+
*/
|
|
11
|
+
var PLUGIN_VERSION = '0.1.0';
|
|
12
|
+
|
|
13
|
+
function call(action, args) {
|
|
14
|
+
return new Promise(function (resolve, reject) {
|
|
15
|
+
exec(function (res) { resolve(res); }, function (err) { reject(err); }, PLUGIN, action, args || []);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
exports.configure = function (options) {
|
|
20
|
+
options = options || {};
|
|
21
|
+
return call('configure', [options]);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
exports.sendEvent = function (name, properties, headers) {
|
|
25
|
+
if (!name || typeof name !== 'string') {
|
|
26
|
+
return Promise.reject('name is required');
|
|
27
|
+
}
|
|
28
|
+
return call('sendEvent', [{ name: name, properties: properties || {}, headers: headers || {} }]);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
exports.isInitialized = function () {
|
|
32
|
+
return call('isInitialized', []).then(function (res) { return res && res.value === true; });
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Retorna la versión del SDK nativo (AAR de Device Intelligence).
|
|
37
|
+
* Ejemplo: "1.3.1"
|
|
38
|
+
*/
|
|
39
|
+
exports.getVersion = function () {
|
|
40
|
+
return call('getVersion', []).then(function (res) { return (res && res.value) || ''; });
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Retorna la versión del plugin Cordova (wrapper JS + bridge).
|
|
45
|
+
* Ejemplo: "0.1.0"
|
|
46
|
+
*
|
|
47
|
+
* Esta versión es independiente del SDK nativo y sigue su propio ciclo de releases.
|
|
48
|
+
*/
|
|
49
|
+
exports.getPluginVersion = function () {
|
|
50
|
+
return PLUGIN_VERSION;
|
|
51
|
+
};
|