@consentera/react-native-consent 2.0.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/LICENSE +21 -0
- package/README.md +200 -0
- package/lib/commonjs/gate.js +87 -0
- package/lib/commonjs/gate.js.map +1 -0
- package/lib/commonjs/index.js +92 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/session.js +805 -0
- package/lib/commonjs/session.js.map +1 -0
- package/lib/module/gate.js +80 -0
- package/lib/module/gate.js.map +1 -0
- package/lib/module/index.js +23 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/session.js +789 -0
- package/lib/module/session.js.map +1 -0
- package/lib/typescript/src/gate.d.ts +45 -0
- package/lib/typescript/src/gate.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +25 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/session.d.ts +600 -0
- package/lib/typescript/src/session.d.ts.map +1 -0
- package/package.json +130 -0
- package/src/gate.ts +81 -0
- package/src/index.ts +50 -0
- package/src/session.ts +1051 -0
package/package.json
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@consentera/react-native-consent",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Consentera Consent SDK for React Native \u2014 DPDP Act 2023 compliant consent collection",
|
|
5
|
+
"main": "lib/commonjs/index",
|
|
6
|
+
"module": "lib/module/index",
|
|
7
|
+
"types": "lib/typescript/src/index.d.ts",
|
|
8
|
+
"react-native": "src/index",
|
|
9
|
+
"source": "src/index",
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"lib",
|
|
13
|
+
"!**/__tests__",
|
|
14
|
+
"!**/__fixtures__",
|
|
15
|
+
"!**/__mocks__",
|
|
16
|
+
"!**/.*"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"example": "npm run --workspace @consentera/react-native-consent-example",
|
|
20
|
+
"test": "jest",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"lint": "eslint .",
|
|
23
|
+
"clean": "del-cli android/build lib",
|
|
24
|
+
"prepare": "bob build"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"react-native",
|
|
28
|
+
"ios",
|
|
29
|
+
"android",
|
|
30
|
+
"consent",
|
|
31
|
+
"privacy",
|
|
32
|
+
"dpdp",
|
|
33
|
+
"india",
|
|
34
|
+
"consentera"
|
|
35
|
+
],
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/consentera-platform/consentera-sdks.git",
|
|
39
|
+
"directory": "react-native"
|
|
40
|
+
},
|
|
41
|
+
"author": "ConsentEra <sdk@consentera.in>",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/consentera-platform/consentera-sdks/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/consentera-platform/consentera-sdks/tree/main/react-native#readme",
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"registry": "https://registry.npmjs.org/"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@commitlint/config-conventional": "^18.4.3",
|
|
52
|
+
"@evilmartians/lefthook": "^1.5.5",
|
|
53
|
+
"@types/jest": "^29.5.14",
|
|
54
|
+
"@types/react": "^18.3.12",
|
|
55
|
+
"commitlint": "^18.4.3",
|
|
56
|
+
"del-cli": "^5.1.0",
|
|
57
|
+
"eslint": "^9.14.0",
|
|
58
|
+
"eslint-config-prettier": "^9.1.0",
|
|
59
|
+
"eslint-plugin-prettier": "^5.1.2",
|
|
60
|
+
"jest": "^29.7.0",
|
|
61
|
+
"prettier": "^3.3.3",
|
|
62
|
+
"react": "18.3.1",
|
|
63
|
+
"react-native": "0.76.5",
|
|
64
|
+
"react-native-builder-bob": "^0.23.2",
|
|
65
|
+
"typescript": "^5.6.3",
|
|
66
|
+
"@eslint/js": "^9.14.0",
|
|
67
|
+
"typescript-eslint": "^8.13.0"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"react": ">=18.2.0",
|
|
71
|
+
"react-native": ">=0.73.0"
|
|
72
|
+
},
|
|
73
|
+
"workspaces": [
|
|
74
|
+
"example"
|
|
75
|
+
],
|
|
76
|
+
"jest": {
|
|
77
|
+
"preset": "react-native",
|
|
78
|
+
"modulePathIgnorePatterns": [
|
|
79
|
+
"<rootDir>/example/node_modules",
|
|
80
|
+
"<rootDir>/lib/"
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
"commitlint": {
|
|
84
|
+
"extends": [
|
|
85
|
+
"@commitlint/config-conventional"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
"prettier": {
|
|
89
|
+
"quoteProps": "consistent",
|
|
90
|
+
"singleQuote": true,
|
|
91
|
+
"tabWidth": 2,
|
|
92
|
+
"trailingComma": "es5",
|
|
93
|
+
"useTabs": false
|
|
94
|
+
},
|
|
95
|
+
"react-native-builder-bob": {
|
|
96
|
+
"source": "src",
|
|
97
|
+
"output": "lib",
|
|
98
|
+
"targets": [
|
|
99
|
+
"commonjs",
|
|
100
|
+
"module",
|
|
101
|
+
[
|
|
102
|
+
"typescript",
|
|
103
|
+
{
|
|
104
|
+
"project": "tsconfig.build.json"
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
"engines": {
|
|
110
|
+
"node": ">=20"
|
|
111
|
+
},
|
|
112
|
+
"sideEffects": false,
|
|
113
|
+
"exports": {
|
|
114
|
+
".": {
|
|
115
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
116
|
+
"react-native": "./src/index.ts",
|
|
117
|
+
"import": "./lib/module/index.js",
|
|
118
|
+
"require": "./lib/commonjs/index.js"
|
|
119
|
+
},
|
|
120
|
+
"./package.json": "./package.json"
|
|
121
|
+
},
|
|
122
|
+
"peerDependenciesMeta": {
|
|
123
|
+
"react-native-inappbrowser-reborn": {
|
|
124
|
+
"optional": true
|
|
125
|
+
},
|
|
126
|
+
"react-native-get-random-values": {
|
|
127
|
+
"optional": true
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
package/src/gate.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ConsenteraSession, PrincipalRef } from './session';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ConsentGate — the mobile equivalent of cookie consent (RN twin of the
|
|
5
|
+
* Android SDK's gate package). Gate third-party SDKs (analytics, ads,
|
|
6
|
+
* attribution) and advertising identifiers on the DP's live consent state:
|
|
7
|
+
* `init` runs only after the purpose validates ALLOW; `revoke` runs when a
|
|
8
|
+
* previously-allowed purpose stops validating (stop the SDK, flush queues,
|
|
9
|
+
* clear stored identifiers). Never "initialize then opt out".
|
|
10
|
+
*
|
|
11
|
+
* Call refresh() on app start and whenever the app returns to the foreground
|
|
12
|
+
* (AppState 'active') — the deep-link callback is best-effort only.
|
|
13
|
+
*/
|
|
14
|
+
export interface Tracker {
|
|
15
|
+
purposeCode: string;
|
|
16
|
+
name: string;
|
|
17
|
+
init: () => void;
|
|
18
|
+
revoke: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface GateState {
|
|
22
|
+
name: string;
|
|
23
|
+
purposeCode: string;
|
|
24
|
+
allowed: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class ConsentGate {
|
|
28
|
+
private trackers: Tracker[] = [];
|
|
29
|
+
private started = new Set<string>();
|
|
30
|
+
private listeners = new Set<(states: GateState[]) => void>();
|
|
31
|
+
states: GateState[] = [];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param who the person this gate is about, in the ONE shape the lifecycle
|
|
35
|
+
* roads accept. It was a bare `dataPrincipalRef: string` until 2026-09-22,
|
|
36
|
+
* which no longer type-checked against `validate` (tsc: "Argument of type
|
|
37
|
+
* 'string' is not assignable to parameter of type 'PrincipalRef'") and could
|
|
38
|
+
* not express the identifiers the API now requires.
|
|
39
|
+
*/
|
|
40
|
+
constructor(private session: ConsenteraSession, private who: PrincipalRef) {}
|
|
41
|
+
|
|
42
|
+
register(tracker: Tracker): void {
|
|
43
|
+
this.trackers.push(tracker);
|
|
44
|
+
this.states = [...this.states, { name: tracker.name, purposeCode: tracker.purposeCode, allowed: false }];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
onChange(fn: (states: GateState[]) => void): () => void {
|
|
48
|
+
this.listeners.add(fn);
|
|
49
|
+
return () => this.listeners.delete(fn);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Re-validate every registered purpose; fail-closed on errors. */
|
|
53
|
+
async refresh(): Promise<GateState[]> {
|
|
54
|
+
const next: GateState[] = [];
|
|
55
|
+
for (const t of this.trackers) {
|
|
56
|
+
let allowed = false;
|
|
57
|
+
try {
|
|
58
|
+
const d = await this.session.validate(this.who, t.purposeCode);
|
|
59
|
+
allowed = (d.decision ?? '').toUpperCase() === 'ALLOW';
|
|
60
|
+
} catch {
|
|
61
|
+
allowed = false;
|
|
62
|
+
}
|
|
63
|
+
const wasStarted = this.started.has(t.name);
|
|
64
|
+
if (allowed && !wasStarted) {
|
|
65
|
+
try { t.init(); } catch { /* tracker init must never crash the app */ }
|
|
66
|
+
this.started.add(t.name);
|
|
67
|
+
} else if (!allowed && wasStarted) {
|
|
68
|
+
try { t.revoke(); } catch { /* ditto */ }
|
|
69
|
+
this.started.delete(t.name);
|
|
70
|
+
}
|
|
71
|
+
next.push({ name: t.name, purposeCode: t.purposeCode, allowed });
|
|
72
|
+
}
|
|
73
|
+
this.states = next;
|
|
74
|
+
this.listeners.forEach((fn) => fn(next));
|
|
75
|
+
return next;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
isAllowed(purposeCode: string): boolean {
|
|
79
|
+
return this.states.some((s) => s.purposeCode === purposeCode && s.allowed);
|
|
80
|
+
}
|
|
81
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @consentera/react-native-consent — DPDP consent for React Native apps.
|
|
3
|
+
*
|
|
4
|
+
* Reoriented 2026-07-20 to the proven hosted-collect session flow (the old
|
|
5
|
+
* native-module cookie/ad-category model targeted a phantom /v1/sdk/* API and
|
|
6
|
+
* could never collect DPDP consent — see ../MOBILE-P0-FINDINGS.md).
|
|
7
|
+
*
|
|
8
|
+
* Pure JS — no native module, no pod install, works in any RN app:
|
|
9
|
+
* const session = new ConsenteraSession({ backendBaseUrl, callbackScheme });
|
|
10
|
+
* const s = await session.createSession({ dataPrincipalRef, noticeInternalName });
|
|
11
|
+
* session.presentConsent(s.consent_url); // hosted page in the browser
|
|
12
|
+
* // on AppState 'active': re-validate (deep link is best-effort)
|
|
13
|
+
* const d = await session.validate(dataPrincipalRef, 'product_analytics');
|
|
14
|
+
* await session.withdraw(dataPrincipalRef, ['product_analytics']);
|
|
15
|
+
* await session.openPortal(dataPrincipalRef); // full DP portal, one tap
|
|
16
|
+
*
|
|
17
|
+
* ConsentGate = the mobile equivalent of cookie consent: register each
|
|
18
|
+
* third-party SDK with init/revoke hooks; refresh() starts/stops them on the
|
|
19
|
+
* live consent state.
|
|
20
|
+
*/
|
|
21
|
+
export {
|
|
22
|
+
ConsenteraSession,
|
|
23
|
+
ConsenteraError,
|
|
24
|
+
SDK_VERSION,
|
|
25
|
+
SDK_IDENTIFIER,
|
|
26
|
+
SDK_USER_AGENT,
|
|
27
|
+
setInAppBrowser,
|
|
28
|
+
resolveInAppBrowser,
|
|
29
|
+
newCallbackState,
|
|
30
|
+
parseRetryAfter,
|
|
31
|
+
callbackUrlFor,
|
|
32
|
+
callbackStatusOf,
|
|
33
|
+
portalIdentifier,
|
|
34
|
+
readErrorEnvelope,
|
|
35
|
+
} from './session';
|
|
36
|
+
export type {
|
|
37
|
+
SessionConfig,
|
|
38
|
+
ConsentSession,
|
|
39
|
+
ConsentCallback,
|
|
40
|
+
CallbackStatus,
|
|
41
|
+
ConsentDecision,
|
|
42
|
+
PortalSession,
|
|
43
|
+
CreateSessionRequest,
|
|
44
|
+
PrincipalRef,
|
|
45
|
+
DataPrincipalIdentifiers,
|
|
46
|
+
GuardianVerificationPending,
|
|
47
|
+
InAppBrowserAdapter,
|
|
48
|
+
} from './session';
|
|
49
|
+
export { ConsentGate } from './gate';
|
|
50
|
+
export type { Tracker, GateState } from './gate';
|