react-native-app-attestation 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/README.md +451 -0
- package/android/build.gradle +31 -0
- package/android/src/main/java/com/reactnativeappattestation/PlayIntegrityModule.kt +38 -0
- package/android/src/main/java/com/reactnativeappattestation/PlayIntegrityPackage.kt +21 -0
- package/ios/AppAttestModule.m +11 -0
- package/ios/AppAttestModule.swift +56 -0
- package/ios/ReactNativeAppAttestation-Bridging-Header.h +1 -0
- package/lib/AttestationService.d.ts +15 -0
- package/lib/AttestationService.js +126 -0
- package/lib/DeviceIDService.d.ts +9 -0
- package/lib/DeviceIDService.js +62 -0
- package/lib/index.d.ts +64 -0
- package/lib/index.js +171 -0
- package/lib/interceptors.d.ts +18 -0
- package/lib/interceptors.js +56 -0
- package/lib/types.d.ts +38 -0
- package/lib/types.js +3 -0
- package/package.json +49 -0
- package/react-native-app-attestation.podspec +27 -0
- package/src/AttestationService.ts +123 -0
- package/src/DeviceIDService.ts +62 -0
- package/src/index.ts +174 -0
- package/src/interceptors.ts +72 -0
- package/src/types.ts +52 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/AttestationService.ts
|
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.AttestationService = void 0;
|
|
14
|
+
const react_native_1 = require("react-native");
|
|
15
|
+
class AttestationService {
|
|
16
|
+
constructor(config) {
|
|
17
|
+
var _a;
|
|
18
|
+
this.cachedToken = null;
|
|
19
|
+
this.tokenExpiry = null;
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.cacheDuration = (_a = config.tokenCacheDurationMs) !== null && _a !== void 0 ? _a : 10 * 60 * 1000;
|
|
22
|
+
}
|
|
23
|
+
log(msg) {
|
|
24
|
+
if (this.config.debug)
|
|
25
|
+
console.log(`[Attestation] ${msg}`);
|
|
26
|
+
}
|
|
27
|
+
// Backend se nonce fetch karo
|
|
28
|
+
fetchNonce() {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
const response = yield fetch(this.config.nonceEndpoint);
|
|
31
|
+
const data = yield response.json();
|
|
32
|
+
if (!data.nonce)
|
|
33
|
+
throw new Error('Nonce nahi mila backend se');
|
|
34
|
+
return data.nonce;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Android — Play Integrity
|
|
38
|
+
getAndroidToken() {
|
|
39
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
try {
|
|
41
|
+
const { PlayIntegrityModule } = react_native_1.NativeModules;
|
|
42
|
+
if (!PlayIntegrityModule) {
|
|
43
|
+
this.log('PlayIntegrityModule nahi mila');
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const nonce = yield this.fetchNonce();
|
|
47
|
+
const token = yield PlayIntegrityModule
|
|
48
|
+
.getAttestationToken(nonce);
|
|
49
|
+
this.log('Android token mila!');
|
|
50
|
+
return token;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.log(`Android error: ${error}`);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// iOS — App Attest
|
|
59
|
+
getIOSToken() {
|
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
try {
|
|
62
|
+
const { AppAttestModule } = react_native_1.NativeModules;
|
|
63
|
+
if (!AppAttestModule) {
|
|
64
|
+
this.log('AppAttestModule nahi mila');
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const challenge = yield this.fetchNonce();
|
|
68
|
+
const token = yield AppAttestModule
|
|
69
|
+
.getAttestationToken(challenge);
|
|
70
|
+
this.log('iOS token mila!');
|
|
71
|
+
return token;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
this.log(`iOS error: ${error}`);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// Main function
|
|
80
|
+
getToken() {
|
|
81
|
+
return __awaiter(this, arguments, void 0, function* (forceRefresh = false) {
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
const platform = react_native_1.Platform.OS;
|
|
84
|
+
// Cache valid hai?
|
|
85
|
+
if (!forceRefresh &&
|
|
86
|
+
this.cachedToken &&
|
|
87
|
+
this.tokenExpiry &&
|
|
88
|
+
now < this.tokenExpiry) {
|
|
89
|
+
this.log('Cached token use ho raha hai');
|
|
90
|
+
return {
|
|
91
|
+
token: this.cachedToken,
|
|
92
|
+
fromCache: true,
|
|
93
|
+
platform,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// Fresh token lo
|
|
97
|
+
let token = null;
|
|
98
|
+
if (react_native_1.Platform.OS === 'android') {
|
|
99
|
+
token = yield this.getAndroidToken();
|
|
100
|
+
}
|
|
101
|
+
else if (react_native_1.Platform.OS === 'ios') {
|
|
102
|
+
token = yield this.getIOSToken();
|
|
103
|
+
}
|
|
104
|
+
// Cache karo
|
|
105
|
+
if (token) {
|
|
106
|
+
this.cachedToken = token;
|
|
107
|
+
this.tokenExpiry = now + this.cacheDuration;
|
|
108
|
+
this.log(`Token cached — ${this.cacheDuration / 60000} min valid`);
|
|
109
|
+
}
|
|
110
|
+
return { token, fromCache: false, platform };
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Sensitive operations ke liye
|
|
114
|
+
getFreshToken() {
|
|
115
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
116
|
+
return this.getToken(true);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// Logout pe call karo
|
|
120
|
+
clearCache() {
|
|
121
|
+
this.cachedToken = null;
|
|
122
|
+
this.tokenExpiry = null;
|
|
123
|
+
this.log('Cache cleared');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
exports.AttestationService = AttestationService;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/DeviceIDService.ts
|
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.DeviceIDService = void 0;
|
|
14
|
+
const DEVICE_ID_KEY = 'rn_attestation_device_id';
|
|
15
|
+
// Khud ka UUID generator — koi library nahi!
|
|
16
|
+
const generateUUID = () => {
|
|
17
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
18
|
+
const r = (Math.random() * 16) | 0;
|
|
19
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
20
|
+
return v.toString(16);
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
class DeviceIDService {
|
|
24
|
+
constructor(storage, debug = false) {
|
|
25
|
+
this.storage = storage;
|
|
26
|
+
this.debug = debug;
|
|
27
|
+
}
|
|
28
|
+
log(msg) {
|
|
29
|
+
if (this.debug)
|
|
30
|
+
console.log(`[DeviceID] ${msg}`);
|
|
31
|
+
}
|
|
32
|
+
getDeviceID() {
|
|
33
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
34
|
+
try {
|
|
35
|
+
// Storage se check karo
|
|
36
|
+
let deviceId = yield Promise.resolve(this.storage.get(DEVICE_ID_KEY));
|
|
37
|
+
if (!deviceId) {
|
|
38
|
+
// Pehli baar — naya UUID banao
|
|
39
|
+
deviceId = generateUUID();
|
|
40
|
+
yield Promise.resolve(this.storage.set(DEVICE_ID_KEY, deviceId));
|
|
41
|
+
this.log(`Naya Device ID banaya: ${deviceId}`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
this.log(`Existing Device ID mila: ${deviceId}`);
|
|
45
|
+
}
|
|
46
|
+
return deviceId;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
this.log(`Error: ${error}`);
|
|
50
|
+
// Fallback — storage fail ho toh bhi app crash na ho
|
|
51
|
+
return generateUUID();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
resetDeviceID() {
|
|
56
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
57
|
+
yield Promise.resolve(this.storage.delete(DEVICE_ID_KEY));
|
|
58
|
+
this.log('Device ID reset ho gaya');
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.DeviceIDService = DeviceIDService;
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { AttestationConfig, SecurityHeaders } from './types';
|
|
2
|
+
export * from './types';
|
|
3
|
+
export { DeviceIDService } from './DeviceIDService';
|
|
4
|
+
export { AttestationService } from './AttestationService';
|
|
5
|
+
export { axiosInterceptor, secureFetch } from './interceptors';
|
|
6
|
+
/**
|
|
7
|
+
* App start pe ek baar call karo
|
|
8
|
+
*
|
|
9
|
+
* MMKV example:
|
|
10
|
+
* initAttestation({
|
|
11
|
+
* storage: {
|
|
12
|
+
* get: (key) => mmkv.getString(key) ?? null,
|
|
13
|
+
* set: (key, val) => mmkv.set(key, val),
|
|
14
|
+
* delete: (key) => mmkv.delete(key),
|
|
15
|
+
* },
|
|
16
|
+
* nonceEndpoint: 'https://api.yourapp.com/auth/nonce',
|
|
17
|
+
* appVersion: '1.4',
|
|
18
|
+
* debug: __DEV__,
|
|
19
|
+
* });
|
|
20
|
+
*/
|
|
21
|
+
export declare const initAttestation: (config: AttestationConfig) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Device ID lo
|
|
24
|
+
*/
|
|
25
|
+
export declare const getDeviceID: () => Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Attestation token lo (cached ya fresh)
|
|
28
|
+
*/
|
|
29
|
+
export declare const getAttestationToken: (forceRefresh?: boolean) => Promise<string | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Sensitive operations ke liye fresh token
|
|
32
|
+
* Payment, Login, OTP pe ye use karo
|
|
33
|
+
*/
|
|
34
|
+
export declare const getFreshAttestationToken: () => Promise<string | null>;
|
|
35
|
+
/**
|
|
36
|
+
* Saare security headers ek saath lo
|
|
37
|
+
* Interceptor mein ye use karo
|
|
38
|
+
*/
|
|
39
|
+
export declare const getSecurityHeaders: () => Promise<SecurityHeaders>;
|
|
40
|
+
/**
|
|
41
|
+
* Axios interceptor setup
|
|
42
|
+
*
|
|
43
|
+
* import axios from 'axios';
|
|
44
|
+
* import { initAttestation, setupAxios } from 'react-native-app-attestation';
|
|
45
|
+
*
|
|
46
|
+
* const api = axios.create({ baseURL: '...' });
|
|
47
|
+
* setupAxios(api);
|
|
48
|
+
*/
|
|
49
|
+
export declare const setupAxios: (axiosInstance: any) => void;
|
|
50
|
+
/**
|
|
51
|
+
* Secure fetch — fetch ki jagah use karo
|
|
52
|
+
*
|
|
53
|
+
* const res = await secureGet('https://api.com/user');
|
|
54
|
+
*/
|
|
55
|
+
export declare const secureGet: (url: string, options?: RequestInit) => Promise<Response>;
|
|
56
|
+
export declare const securePost: (url: string, body: unknown, options?: RequestInit) => Promise<Response>;
|
|
57
|
+
/**
|
|
58
|
+
* Logout pe call karo
|
|
59
|
+
*/
|
|
60
|
+
export declare const clearAttestationCache: () => void;
|
|
61
|
+
/**
|
|
62
|
+
* Device ID reset karo
|
|
63
|
+
*/
|
|
64
|
+
export declare const resetDeviceID: () => Promise<void>;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/index.ts
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
15
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
16
|
+
};
|
|
17
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
18
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
19
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
20
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
21
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
22
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
23
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.resetDeviceID = exports.clearAttestationCache = exports.securePost = exports.secureGet = exports.setupAxios = exports.getSecurityHeaders = exports.getFreshAttestationToken = exports.getAttestationToken = exports.getDeviceID = exports.initAttestation = exports.secureFetch = exports.axiosInterceptor = exports.AttestationService = exports.DeviceIDService = void 0;
|
|
28
|
+
const react_native_1 = require("react-native");
|
|
29
|
+
const DeviceIDService_1 = require("./DeviceIDService");
|
|
30
|
+
const AttestationService_1 = require("./AttestationService");
|
|
31
|
+
const interceptors_1 = require("./interceptors");
|
|
32
|
+
// Re-export — user directly import kar sake
|
|
33
|
+
__exportStar(require("./types"), exports);
|
|
34
|
+
var DeviceIDService_2 = require("./DeviceIDService");
|
|
35
|
+
Object.defineProperty(exports, "DeviceIDService", { enumerable: true, get: function () { return DeviceIDService_2.DeviceIDService; } });
|
|
36
|
+
var AttestationService_2 = require("./AttestationService");
|
|
37
|
+
Object.defineProperty(exports, "AttestationService", { enumerable: true, get: function () { return AttestationService_2.AttestationService; } });
|
|
38
|
+
var interceptors_2 = require("./interceptors");
|
|
39
|
+
Object.defineProperty(exports, "axiosInterceptor", { enumerable: true, get: function () { return interceptors_2.axiosInterceptor; } });
|
|
40
|
+
Object.defineProperty(exports, "secureFetch", { enumerable: true, get: function () { return interceptors_2.secureFetch; } });
|
|
41
|
+
// ========================================
|
|
42
|
+
// SINGLETON — ek baar init, har jagah use
|
|
43
|
+
// ========================================
|
|
44
|
+
let deviceIDService = null;
|
|
45
|
+
let attestationService = null;
|
|
46
|
+
let globalConfig = null;
|
|
47
|
+
/**
|
|
48
|
+
* App start pe ek baar call karo
|
|
49
|
+
*
|
|
50
|
+
* MMKV example:
|
|
51
|
+
* initAttestation({
|
|
52
|
+
* storage: {
|
|
53
|
+
* get: (key) => mmkv.getString(key) ?? null,
|
|
54
|
+
* set: (key, val) => mmkv.set(key, val),
|
|
55
|
+
* delete: (key) => mmkv.delete(key),
|
|
56
|
+
* },
|
|
57
|
+
* nonceEndpoint: 'https://api.yourapp.com/auth/nonce',
|
|
58
|
+
* appVersion: '1.4',
|
|
59
|
+
* debug: __DEV__,
|
|
60
|
+
* });
|
|
61
|
+
*/
|
|
62
|
+
const initAttestation = (config) => {
|
|
63
|
+
globalConfig = config;
|
|
64
|
+
deviceIDService = new DeviceIDService_1.DeviceIDService(config.storage, config.debug);
|
|
65
|
+
attestationService = new AttestationService_1.AttestationService(config);
|
|
66
|
+
};
|
|
67
|
+
exports.initAttestation = initAttestation;
|
|
68
|
+
// Init check
|
|
69
|
+
const checkInit = (fnName) => {
|
|
70
|
+
if (!deviceIDService || !attestationService || !globalConfig) {
|
|
71
|
+
throw new Error(`[react-native-app-attestation] ${fnName}() call karne se pehle initAttestation() call karo!`);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Device ID lo
|
|
76
|
+
*/
|
|
77
|
+
const getDeviceID = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
78
|
+
checkInit('getDeviceID');
|
|
79
|
+
return deviceIDService.getDeviceID();
|
|
80
|
+
});
|
|
81
|
+
exports.getDeviceID = getDeviceID;
|
|
82
|
+
/**
|
|
83
|
+
* Attestation token lo (cached ya fresh)
|
|
84
|
+
*/
|
|
85
|
+
const getAttestationToken = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (forceRefresh = false) {
|
|
86
|
+
checkInit('getAttestationToken');
|
|
87
|
+
const result = yield attestationService.getToken(forceRefresh);
|
|
88
|
+
return result.token;
|
|
89
|
+
});
|
|
90
|
+
exports.getAttestationToken = getAttestationToken;
|
|
91
|
+
/**
|
|
92
|
+
* Sensitive operations ke liye fresh token
|
|
93
|
+
* Payment, Login, OTP pe ye use karo
|
|
94
|
+
*/
|
|
95
|
+
const getFreshAttestationToken = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
96
|
+
checkInit('getFreshAttestationToken');
|
|
97
|
+
const result = yield attestationService.getFreshToken();
|
|
98
|
+
return result.token;
|
|
99
|
+
});
|
|
100
|
+
exports.getFreshAttestationToken = getFreshAttestationToken;
|
|
101
|
+
/**
|
|
102
|
+
* Saare security headers ek saath lo
|
|
103
|
+
* Interceptor mein ye use karo
|
|
104
|
+
*/
|
|
105
|
+
const getSecurityHeaders = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
106
|
+
checkInit('getSecurityHeaders');
|
|
107
|
+
const [deviceId, attestationResult] = yield Promise.all([
|
|
108
|
+
deviceIDService.getDeviceID(),
|
|
109
|
+
attestationService.getToken(),
|
|
110
|
+
]);
|
|
111
|
+
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
112
|
+
const platform = react_native_1.Platform.OS === 'ios' ? 'iOS' : 'Android';
|
|
113
|
+
const headers = {
|
|
114
|
+
'User-Agent': `App/${globalConfig.appVersion} (${platform})`,
|
|
115
|
+
'X-App-Platform': react_native_1.Platform.OS,
|
|
116
|
+
'X-App-Version': globalConfig.appVersion,
|
|
117
|
+
'X-Device-ID': deviceId,
|
|
118
|
+
'X-Timestamp': timestamp,
|
|
119
|
+
};
|
|
120
|
+
if (attestationResult.token) {
|
|
121
|
+
headers['X-Attestation'] = attestationResult.token;
|
|
122
|
+
}
|
|
123
|
+
return headers;
|
|
124
|
+
});
|
|
125
|
+
exports.getSecurityHeaders = getSecurityHeaders;
|
|
126
|
+
/**
|
|
127
|
+
* Axios interceptor setup
|
|
128
|
+
*
|
|
129
|
+
* import axios from 'axios';
|
|
130
|
+
* import { initAttestation, setupAxios } from 'react-native-app-attestation';
|
|
131
|
+
*
|
|
132
|
+
* const api = axios.create({ baseURL: '...' });
|
|
133
|
+
* setupAxios(api);
|
|
134
|
+
*/
|
|
135
|
+
const setupAxios = (axiosInstance) => {
|
|
136
|
+
checkInit('setupAxios');
|
|
137
|
+
(0, interceptors_1.axiosInterceptor)(axiosInstance, {
|
|
138
|
+
appVersion: globalConfig.appVersion,
|
|
139
|
+
getHeaders: exports.getSecurityHeaders,
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
exports.setupAxios = setupAxios;
|
|
143
|
+
/**
|
|
144
|
+
* Secure fetch — fetch ki jagah use karo
|
|
145
|
+
*
|
|
146
|
+
* const res = await secureGet('https://api.com/user');
|
|
147
|
+
*/
|
|
148
|
+
const secureGet = (url, options) => {
|
|
149
|
+
checkInit('secureGet');
|
|
150
|
+
return (0, interceptors_1.secureFetch)(url, options !== null && options !== void 0 ? options : {}, exports.getSecurityHeaders);
|
|
151
|
+
};
|
|
152
|
+
exports.secureGet = secureGet;
|
|
153
|
+
const securePost = (url, body, options) => {
|
|
154
|
+
checkInit('securePost');
|
|
155
|
+
return (0, interceptors_1.secureFetch)(url, Object.assign(Object.assign({}, options), { method: 'POST', body: JSON.stringify(body) }), exports.getSecurityHeaders);
|
|
156
|
+
};
|
|
157
|
+
exports.securePost = securePost;
|
|
158
|
+
/**
|
|
159
|
+
* Logout pe call karo
|
|
160
|
+
*/
|
|
161
|
+
const clearAttestationCache = () => {
|
|
162
|
+
attestationService === null || attestationService === void 0 ? void 0 : attestationService.clearCache();
|
|
163
|
+
};
|
|
164
|
+
exports.clearAttestationCache = clearAttestationCache;
|
|
165
|
+
/**
|
|
166
|
+
* Device ID reset karo
|
|
167
|
+
*/
|
|
168
|
+
const resetDeviceID = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
169
|
+
yield (deviceIDService === null || deviceIDService === void 0 ? void 0 : deviceIDService.resetDeviceID());
|
|
170
|
+
});
|
|
171
|
+
exports.resetDeviceID = resetDeviceID;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SecurityHeaders } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* AXIOS ke liye
|
|
4
|
+
*
|
|
5
|
+
* Use karo:
|
|
6
|
+
* axiosInterceptor(api, { appVersion: '1.4', getHeaders })
|
|
7
|
+
*/
|
|
8
|
+
export declare const axiosInterceptor: (axiosInstance: any, options: {
|
|
9
|
+
appVersion: string;
|
|
10
|
+
getHeaders: () => Promise<SecurityHeaders>;
|
|
11
|
+
}) => void;
|
|
12
|
+
/**
|
|
13
|
+
* FETCH ke liye
|
|
14
|
+
*
|
|
15
|
+
* Use karo:
|
|
16
|
+
* const res = await secureFetch(url, options, getHeaders)
|
|
17
|
+
*/
|
|
18
|
+
export declare const secureFetch: (url: string, options: RequestInit | undefined, getHeaders: () => Promise<SecurityHeaders>) => Promise<Response>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/interceptors.ts
|
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.secureFetch = exports.axiosInterceptor = void 0;
|
|
14
|
+
/**
|
|
15
|
+
* AXIOS ke liye
|
|
16
|
+
*
|
|
17
|
+
* Use karo:
|
|
18
|
+
* axiosInterceptor(api, { appVersion: '1.4', getHeaders })
|
|
19
|
+
*/
|
|
20
|
+
const axiosInterceptor = (axiosInstance, options) => {
|
|
21
|
+
axiosInstance.interceptors.request.use((config) => __awaiter(void 0, void 0, void 0, function* () {
|
|
22
|
+
try {
|
|
23
|
+
// Security headers lo
|
|
24
|
+
const securityHeaders = yield options.getHeaders();
|
|
25
|
+
// Existing headers ke saath merge karo
|
|
26
|
+
config.headers = Object.assign(Object.assign({}, config.headers), securityHeaders);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.warn('[Attestation] Headers add nahi ho sake:', error);
|
|
30
|
+
// App crash nahi hona chahiye — silently fail
|
|
31
|
+
}
|
|
32
|
+
return config;
|
|
33
|
+
}), (error) => Promise.reject(error));
|
|
34
|
+
};
|
|
35
|
+
exports.axiosInterceptor = axiosInterceptor;
|
|
36
|
+
/**
|
|
37
|
+
* FETCH ke liye
|
|
38
|
+
*
|
|
39
|
+
* Use karo:
|
|
40
|
+
* const res = await secureFetch(url, options, getHeaders)
|
|
41
|
+
*/
|
|
42
|
+
const secureFetch = (url_1, ...args_1) => __awaiter(void 0, [url_1, ...args_1], void 0, function* (url, options = {}, getHeaders) {
|
|
43
|
+
try {
|
|
44
|
+
// Security headers lo
|
|
45
|
+
const securityHeaders = yield getHeaders();
|
|
46
|
+
// Existing headers ke saath merge karo
|
|
47
|
+
const mergedHeaders = Object.assign(Object.assign({}, options.headers), securityHeaders);
|
|
48
|
+
return fetch(url, Object.assign(Object.assign({}, options), { headers: mergedHeaders }));
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.warn('[Attestation] secureFetch headers error:', error);
|
|
52
|
+
// Headers add nahi hue — normal fetch karo
|
|
53
|
+
return fetch(url, options);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
exports.secureFetch = secureFetch;
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage interface — user apna storage dega
|
|
3
|
+
* MMKV, AsyncStorage, ya koi bhi
|
|
4
|
+
*/
|
|
5
|
+
export interface StorageAdapter {
|
|
6
|
+
get: (key: string) => string | null | Promise<string | null>;
|
|
7
|
+
set: (key: string, value: string) => void | Promise<void>;
|
|
8
|
+
delete: (key: string) => void | Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Package initialize karne ke liye config
|
|
12
|
+
*/
|
|
13
|
+
export interface AttestationConfig {
|
|
14
|
+
storage: StorageAdapter;
|
|
15
|
+
nonceEndpoint: string;
|
|
16
|
+
appVersion: string;
|
|
17
|
+
tokenCacheDurationMs?: number;
|
|
18
|
+
debug?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Attestation token result
|
|
22
|
+
*/
|
|
23
|
+
export interface AttestationResult {
|
|
24
|
+
token: string | null;
|
|
25
|
+
fromCache: boolean;
|
|
26
|
+
platform: 'android' | 'ios';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Security headers jo API call mein jayenge
|
|
30
|
+
*/
|
|
31
|
+
export interface SecurityHeaders {
|
|
32
|
+
'User-Agent': string;
|
|
33
|
+
'X-App-Platform': string;
|
|
34
|
+
'X-App-Version': string;
|
|
35
|
+
'X-Device-ID': string;
|
|
36
|
+
'X-Timestamp': string;
|
|
37
|
+
'X-Attestation'?: string;
|
|
38
|
+
}
|
package/lib/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-app-attestation",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Mobile app attestation for React Native — Android Play Integrity & iOS App Attest",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"src",
|
|
9
|
+
"lib",
|
|
10
|
+
"android",
|
|
11
|
+
"ios",
|
|
12
|
+
"react-native-app-attestation.podspec",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"prepare": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"react-native",
|
|
21
|
+
"attestation",
|
|
22
|
+
"play-integrity",
|
|
23
|
+
"app-attest",
|
|
24
|
+
"security",
|
|
25
|
+
"android",
|
|
26
|
+
"ios"
|
|
27
|
+
],
|
|
28
|
+
"author": "Madhurmeet Jadhav",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/madhurmeetjadhav/react-native-app-attestation"
|
|
33
|
+
},
|
|
34
|
+
"codegenConfig": {
|
|
35
|
+
"name": "ReactNativeAppAttestation",
|
|
36
|
+
"type": "all",
|
|
37
|
+
"jsSrcsDir": "src"
|
|
38
|
+
},
|
|
39
|
+
"react-native": "src/index.ts",
|
|
40
|
+
"source": "src/index.ts",
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"react": "*",
|
|
43
|
+
"react-native": "*"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"typescript": "^5.0.0",
|
|
47
|
+
"@types/react-native": "^0.72.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "react-native-app-attestation"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = "https://github.com/madhurmeetjadhav/react-native-app-attestation"
|
|
10
|
+
s.license = "MIT"
|
|
11
|
+
s.authors = { "Madhurmeet Jadhav" => "madhurmeetj@gmail.com" }
|
|
12
|
+
s.platforms = { :ios => "14.0" }
|
|
13
|
+
s.source = {
|
|
14
|
+
:git => "https://github.com/madhurmeetjadhav/react-native-app-attestation.git",
|
|
15
|
+
:tag => "#{s.version}"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
19
|
+
|
|
20
|
+
s.dependency "React-Core"
|
|
21
|
+
|
|
22
|
+
# Autolinking support
|
|
23
|
+
s.pod_target_xcconfig = {
|
|
24
|
+
"SWIFT_VERSION" => "5.0",
|
|
25
|
+
"DEFINES_MODULE" => "YES"
|
|
26
|
+
}
|
|
27
|
+
end
|