equal-react-native-pfm-sdk 1.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/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # PFM Stack — React Native Integration
2
+
3
+ The PFM Stack provides an embedded React Native SDK for integrating **Personal Finance Management** into your mobile application. End-users can aggregate their financial data through the **Account Aggregator** framework with a minimal-effort integration.
4
+
5
+ ---
6
+
7
+ ## Prerequisites
8
+
9
+ | Requirement | Minimum Version |
10
+ |------------------------|-----------------|
11
+ | React Native | 0.68+ |
12
+ | React | 17+ |
13
+ | Node.js | 16+ |
14
+ | Android `minSdkVersion`| 21 (Android 5.0)|
15
+ | iOS Deployment Target | 13.0+ |
16
+
17
+ ---
18
+
19
+ ## Step 1: Initialization of SDK API
20
+
21
+ Before the consumer can access PFM features, your **backend service** must initialize the SDK by calling the **SDK Init API**.
22
+
23
+ The Init API returns an `access_token` (referred to as `token` in the SDK). This token is required to launch the PFM interface.
24
+
25
+ > Refer to the [SDK Init API documentation](https://developer.moneyone.in/pfm/sdk-init-api) for complete backend integration steps.
26
+
27
+ **Example Init API request body:**
28
+
29
+ ```json
30
+ {
31
+ "reference_id": "unique_ref_id",
32
+ "exchange_application_id": "pfm.equal.tata_amc_uat",
33
+ "user_profile": {
34
+ "mobile_number": "9876543210",
35
+ "name": "User Name",
36
+ "dob": "18-01-1990",
37
+ "pan": "XXNPS9280W"
38
+ }
39
+ }
40
+ ```
41
+
42
+ The API response contains a `session_token` — pass this value as the `token` parameter when launching the SDK.
43
+
44
+ ---
45
+
46
+ ## Step 2: Add SDK as Dependency
47
+
48
+ ### Using npm
49
+
50
+ ```bash
51
+ npm install equal-react-native-pfm-sdk
52
+ ```
53
+
54
+ ### Using yarn
55
+
56
+ ```bash
57
+ yarn add equal-react-native-pfm-sdk
58
+ ```
59
+
60
+ ### Install peer dependency
61
+
62
+ The SDK requires `react-native-webview`. If not already in your project:
63
+
64
+ ```bash
65
+ npm install react-native-webview
66
+ # or
67
+ yarn add react-native-webview
68
+ ```
69
+
70
+ ### iOS — Install CocoaPods
71
+
72
+ ```bash
73
+ cd ios && pod install && cd ..
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Step 3: Launch PFM SDK
79
+
80
+ ### 3a. Android Configuration
81
+
82
+ Add the following provider entry inside the `<application>` tag in your `android/app/src/main/AndroidManifest.xml`. This is required by the WebView component for file handling:
83
+
84
+ ```xml
85
+ <application
86
+ ...>
87
+
88
+ <!-- Required for PFM SDK WebView file access -->
89
+ <provider
90
+ android:name="androidx.core.content.FileProvider"
91
+ android:authorities="${applicationId}.fileprovider"
92
+ android:exported="false"
93
+ android:grantUriPermissions="true">
94
+ <meta-data
95
+ android:name="android.support.FILE_PROVIDER_PATHS"
96
+ android:resource="@xml/file_paths" />
97
+ </provider>
98
+
99
+ </application>
100
+ ```
101
+
102
+ Ensure internet permission is declared (usually present by default):
103
+
104
+ ```xml
105
+ <uses-permission android:name="android.permission.INTERNET" />
106
+ ```
107
+
108
+ ### 3b. iOS Configuration
109
+
110
+ Add the following keys to your `ios/<YourApp>/Info.plist` to allow the WebView to load content and open external links:
111
+
112
+ ```xml
113
+ <key>NSAppTransportSecurity</key>
114
+ <dict>
115
+ <key>NSAllowsArbitraryLoads</key>
116
+ <true/>
117
+ </dict>
118
+ ```
119
+
120
+ ### 3c. Usage Example
121
+
122
+ ```tsx
123
+ import React, { useState } from 'react';
124
+ import { Alert, Button, SafeAreaView, View } from 'react-native';
125
+ import { PFMWebView, EventResponse } from 'equal-react-native-pfm-sdk';
126
+
127
+ export default function App() {
128
+ const [showPFM, setShowPFM] = useState(false);
129
+
130
+ const handleClosed = (data: EventResponse) => {
131
+ console.log('PFM Closed:', data);
132
+ setShowPFM(false);
133
+ Alert.alert('PFM Closed', data.message);
134
+ };
135
+
136
+ const handleError = (data: EventResponse) => {
137
+ console.error('PFM Error:', data);
138
+ setShowPFM(false);
139
+ Alert.alert('PFM Error', data.message);
140
+ };
141
+
142
+ if (showPFM) {
143
+ return (
144
+ <PFMWebView
145
+ token="#token" // Replace with token from SDK Init API
146
+ env="sandbox" // "sandbox" | "production"
147
+ onClosed={handleClosed}
148
+ onError={handleError}
149
+ />
150
+ );
151
+ }
152
+
153
+ return (
154
+ <SafeAreaView style={{ flex: 1 }}>
155
+ <View style={{ flex: 1, justifyContent: 'center', padding: 24 }}>
156
+ <Button title="Launch PFM" onPress={() => setShowPFM(true)} />
157
+ </View>
158
+ </SafeAreaView>
159
+ );
160
+ }
161
+ ```
162
+
163
+ ---
164
+
165
+ ## SDK Parameters
166
+
167
+ | Parameter | Type | Required | Default | Description |
168
+ |-----------|------------|----------|-------------|-----------------------------------------------------------------------------|
169
+ | `token` | `string` | Yes | — | Access token obtained from the SDK Init API response (`session_token`). |
170
+ | `env` | `string` | No | `"sandbox"` | Environment selector. Use `"production"` for live; any other value routes to UAT. |
171
+ | `onClosed`| `function` | Yes | — | Callback invoked when the user exits the PFM interface. |
172
+ | `onError` | `function` | Yes | — | Callback invoked when the SDK encounters an error. |
173
+
174
+ ---
175
+
176
+ ## SDK Callback Methods
177
+
178
+ ### `onClosed`
179
+
180
+ Triggered when the user explicitly closes or exits the PFM interface. The callback receives an `EventResponse` object:
181
+
182
+ ```ts
183
+ interface EventResponse {
184
+ eventType: string; // "PFM_SDK_CALLBACK"
185
+ status: string; // "CLOSED"
186
+ message: string; // Human-readable description
187
+ statusCode?: string; // Optional HTTP-style status code
188
+ }
189
+ ```
190
+
191
+ **Example:**
192
+
193
+ ```ts
194
+ onClosed={(data) => {
195
+ console.log(data);
196
+ // {
197
+ // eventType: "PFM_SDK_CALLBACK",
198
+ // status: "CLOSED",
199
+ // message: "User closed",
200
+ // statusCode: "200"
201
+ // }
202
+ }}
203
+ ```
204
+
205
+ ### `onError`
206
+
207
+ Triggered when the PFM UI encounters an error during operation — for example, an expired token, a network failure, or a server-side issue. The callback receives the same `EventResponse` structure:
208
+
209
+ ```ts
210
+ onError={(data) => {
211
+ console.error(data);
212
+ // {
213
+ // eventType: "PFM_SDK_CALLBACK",
214
+ // status: "ERROR",
215
+ // message: "Token expired",
216
+ // statusCode: "401"
217
+ // }
218
+ }}
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Environment URLs
224
+
225
+ | `env` value | Resolved URL |
226
+ |----------------------------------|-------------------------------------|
227
+ | `"production"` | `https://pfm.equal.in/pfm` |
228
+ | Any other value (e.g. `"sandbox"`, `"pre-prod"`) | `https://uat.pfm.equal.in/pfm` |
229
+
230
+ ---
231
+
232
+ ## Project Structure
233
+
234
+ ```
235
+ equal-react-native-pfm-sdk/
236
+ ├── src/
237
+ │ ├── index.ts # Barrel export
238
+ │ ├── PFMWebView.tsx # Core WebView component
239
+ │ ├── PFMSDKManager.ts # URL construction logic
240
+ │ ├── constants.ts # Endpoints & config
241
+ │ └── models/
242
+ │ ├── PFMSDKConfig.ts # SDK configuration interface
243
+ │ └── EventResponse.ts # Callback event payload
244
+ ├── __tests__/
245
+ │ ├── setup.ts # Jest setup & mocks
246
+ │ ├── models.test.ts # Model unit tests
247
+ │ ├── PFMSDKManager.test.ts # URL logic tests
248
+ │ └── PFMWebView.test.tsx # Component integration tests
249
+ ├── example/
250
+ │ └── App.tsx # Example integration
251
+ ├── package.json
252
+ ├── tsconfig.json
253
+ ├── jest.config.js
254
+ └── README.md
255
+ ```
256
+
257
+ ---
258
+
259
+ ## Running Tests
260
+
261
+ ```bash
262
+ npm test
263
+ # or
264
+ yarn test
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Support
270
+
271
+ For integration support, refer to the [Moneyone Developer Portal](https://developer.moneyone.in) or contact the Equal engineering team.
@@ -0,0 +1,19 @@
1
+ import { PFMSDKConfig } from './models/PFMSDKConfig';
2
+ import { EventResponse } from './models/EventResponse';
3
+ /**
4
+ * Resolves the correct PFM domain based on the environment value.
5
+ *
6
+ * - If `env` contains the substring "production" → production URL.
7
+ * - Otherwise → UAT URL.
8
+ */
9
+ export declare function getEqualDomain(env: string): string;
10
+ /**
11
+ * Builds the full gateway URL that the webview should load.
12
+ *
13
+ * @returns The URL string on success, or `null` if construction fails
14
+ * (in which case `onError` is invoked).
15
+ *
16
+ * URL format: `https://<domain>/pfm?access_token=<token>`
17
+ */
18
+ export declare function getGatewayURL(config: PFMSDKConfig, onError: (data: EventResponse) => void): string | null;
19
+ //# sourceMappingURL=PFMSDKManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PFMSDKManager.d.ts","sourceRoot":"","sources":["../src/PFMSDKManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAwB,MAAM,wBAAwB,CAAC;AAO7E;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,GACrC,MAAM,GAAG,IAAI,CAaf"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getEqualDomain = getEqualDomain;
4
+ exports.getGatewayURL = getGatewayURL;
5
+ const PFMSDKConfig_1 = require("./models/PFMSDKConfig");
6
+ const EventResponse_1 = require("./models/EventResponse");
7
+ const constants_1 = require("./constants");
8
+ /**
9
+ * Resolves the correct PFM domain based on the environment value.
10
+ *
11
+ * - If `env` contains the substring "production" → production URL.
12
+ * - Otherwise → UAT URL.
13
+ */
14
+ function getEqualDomain(env) {
15
+ return env.includes('production') ? constants_1.PROD_PFM_APP_URL : constants_1.UAT_PFM_APP_URL;
16
+ }
17
+ /**
18
+ * Builds the full gateway URL that the webview should load.
19
+ *
20
+ * @returns The URL string on success, or `null` if construction fails
21
+ * (in which case `onError` is invoked).
22
+ *
23
+ * URL format: `https://<domain>/pfm?access_token=<token>`
24
+ */
25
+ function getGatewayURL(config, onError) {
26
+ try {
27
+ const resolved = (0, PFMSDKConfig_1.resolvePFMSDKConfig)(config);
28
+ const domain = getEqualDomain(resolved.env);
29
+ const url = new URL(constants_1.PFM_PATH, domain);
30
+ url.searchParams.set('access_token', resolved.token);
31
+ return url.toString();
32
+ }
33
+ catch (e) {
34
+ const message = 'Unable to load the PFM due to a technical error. Please try again.';
35
+ onError(EventResponse_1.EventResponseFactory.error(message));
36
+ return null;
37
+ }
38
+ }
39
+ //# sourceMappingURL=PFMSDKManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PFMSDKManager.js","sourceRoot":"","sources":["../src/PFMSDKManager.ts"],"names":[],"mappings":";;AAcA,wCAEC;AAUD,sCAgBC;AA1CD,wDAA0E;AAC1E,0DAA6E;AAC7E,2CAIqB;AAErB;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,GAAW;IACxC,OAAO,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,4BAAgB,CAAC,CAAC,CAAC,2BAAe,CAAC;AACzE,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,aAAa,CAC3B,MAAoB,EACpB,OAAsC;IAEtC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAA,kCAAmB,EAAC,MAAM,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC5C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,oBAAQ,EAAE,MAAM,CAAC,CAAC;QACtC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;QACrD,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,OAAO,GACX,oEAAoE,CAAC;QACvE,OAAO,CAAC,oCAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import { EventResponse } from './models/EventResponse';
3
+ export interface PFMWebViewProps {
4
+ /** Access token obtained from the SDK Init API. */
5
+ token: string;
6
+ /**
7
+ * Environment selector.
8
+ * - `"production"` → production endpoint
9
+ * - Any other value → UAT endpoint
10
+ * @default "sandbox"
11
+ */
12
+ env?: string;
13
+ /** Fired when the user explicitly closes the PFM interface. */
14
+ onClosed: (data: EventResponse) => void;
15
+ /** Fired when the SDK encounters an error. */
16
+ onError: (data: EventResponse) => void;
17
+ }
18
+ export declare function PFMWebView({ token, env, onClosed, onError, }: PFMWebViewProps): React.JSX.Element;
19
+ //# sourceMappingURL=PFMWebView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PFMWebView.d.ts","sourceRoot":"","sources":["../src/PFMWebView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAc5D,OAAO,EAAE,aAAa,EAAwB,MAAM,wBAAwB,CAAC;AAY7E,MAAM,WAAW,eAAe;IAC9B,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IAEd;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,+DAA+D;IAC/D,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAExC,8CAA8C;IAC9C,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CACxC;AA+CD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,GAAe,EACf,QAAQ,EACR,OAAO,GACR,EAAE,eAAe,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAyGrC"}
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.PFMWebView = PFMWebView;
37
+ const react_1 = __importStar(require("react"));
38
+ const react_native_1 = require("react-native");
39
+ const react_native_webview_1 = require("react-native-webview");
40
+ const EventResponse_1 = require("./models/EventResponse");
41
+ const PFMSDKManager_1 = require("./PFMSDKManager");
42
+ const constants_1 = require("./constants");
43
+ /* ------------------------------------------------------------------ */
44
+ /* Helpers */
45
+ /* ------------------------------------------------------------------ */
46
+ /**
47
+ * Returns `true` when the URL matches one of the known external patterns
48
+ * that should be opened in the device's default browser.
49
+ */
50
+ function isExternalUrl(url) {
51
+ return constants_1.EXTERNAL_URL_PATTERNS.some((pattern) => url.includes(pattern));
52
+ }
53
+ /**
54
+ * Parses query-parameters from a `/pfm/close` URL and invokes the
55
+ * appropriate callback (onClosed or onError).
56
+ */
57
+ function handleSdkEvent(url, onClosed, onError) {
58
+ try {
59
+ const parsed = new URL(url);
60
+ const message = parsed.searchParams.get('message') ?? '';
61
+ const status = parsed.searchParams.get('status') ?? '';
62
+ const statusCode = parsed.searchParams.get('status_code') ?? undefined;
63
+ if (status === 'ERROR') {
64
+ onError(EventResponse_1.EventResponseFactory.error(message, statusCode));
65
+ }
66
+ else if (url.includes('CLOSED')) {
67
+ onClosed(EventResponse_1.EventResponseFactory.closed(message, statusCode));
68
+ }
69
+ else {
70
+ // Fallback — treat any other /close as a normal close.
71
+ onClosed(EventResponse_1.EventResponseFactory.closed(message, statusCode));
72
+ }
73
+ }
74
+ catch (e) {
75
+ const errorMessage = e instanceof Error ? e.message : String(e);
76
+ onError(EventResponse_1.EventResponseFactory.error(errorMessage));
77
+ }
78
+ }
79
+ /* ------------------------------------------------------------------ */
80
+ /* Component */
81
+ /* ------------------------------------------------------------------ */
82
+ function PFMWebView({ token, env = 'sandbox', onClosed, onError, }) {
83
+ const webViewRef = (0, react_1.useRef)(null);
84
+ /* ---- Build the gateway URL once ---- */
85
+ const gatewayUrl = (0, react_1.useMemo)(() => {
86
+ const config = { token, env };
87
+ return (0, PFMSDKManager_1.getGatewayURL)(config, (err) => onError(err));
88
+ }, [token, env, onError]);
89
+ /* ---- Android hardware-back handling ---- */
90
+ const handleBackPress = (0, react_1.useCallback)(() => {
91
+ react_native_1.Alert.alert('Exit PFM', 'Are you sure you want to exit?', [
92
+ { text: 'No', style: 'cancel' },
93
+ {
94
+ text: 'Yes',
95
+ onPress: () => onClosed(EventResponse_1.EventResponseFactory.closed('User exited via back button')),
96
+ },
97
+ ], { cancelable: true });
98
+ return true;
99
+ }, [onClosed]);
100
+ react_1.default.useEffect(() => {
101
+ if (react_native_1.Platform.OS === 'android') {
102
+ const sub = react_native_1.BackHandler.addEventListener('hardwareBackPress', handleBackPress);
103
+ return () => sub.remove();
104
+ }
105
+ return undefined;
106
+ }, [handleBackPress]);
107
+ /* ---- Pre-navigation interception (matches Flutter shouldOverrideUrlLoading) ---- */
108
+ const handleShouldStartLoad = (0, react_1.useCallback)((event) => {
109
+ const { url } = event;
110
+ // External URLs → cancel navigation, open in system browser
111
+ if (isExternalUrl(url)) {
112
+ react_native_1.Linking.openURL(url).catch(() => { });
113
+ return false;
114
+ }
115
+ // /pfm/close → cancel navigation, fire callbacks
116
+ if (url.includes(constants_1.CLOSE_PATH)) {
117
+ handleSdkEvent(url, onClosed, onError);
118
+ return false;
119
+ }
120
+ // All other URLs → allow navigation
121
+ return true;
122
+ }, [onClosed, onError]);
123
+ /* ---- Error fallback ---- */
124
+ if (!gatewayUrl) {
125
+ return react_1.default.createElement(react_native_1.View, { style: styles.container });
126
+ }
127
+ /* ---- Render ---- */
128
+ return (react_1.default.createElement(react_native_1.SafeAreaView, { style: styles.container },
129
+ react_1.default.createElement(react_native_1.StatusBar, { barStyle: "dark-content" }),
130
+ react_1.default.createElement(react_native_webview_1.WebView, { ref: webViewRef, testID: "pfm-webview", source: { uri: gatewayUrl }, style: styles.webview, userAgent: constants_1.CUSTOM_USER_AGENT, javaScriptEnabled: true, javaScriptCanOpenWindowsAutomatically: true, domStorageEnabled: true, allowFileAccessFromFileURLs: true, allowUniversalAccessFromFileURLs: true, mediaPlaybackRequiresUserAction: false, allowsBackForwardNavigationGestures: false, scalesPageToFit: false, thirdPartyCookiesEnabled: true, sharedCookiesEnabled: true, cacheEnabled: true, mixedContentMode: "compatibility", setSupportMultipleWindows: false, originWhitelist: ['*'], onShouldStartLoadWithRequest: handleShouldStartLoad, onError: (syntheticEvent) => {
131
+ const { nativeEvent } = syntheticEvent;
132
+ onError(EventResponse_1.EventResponseFactory.error(nativeEvent.description ?? 'WebView failed to load'));
133
+ } })));
134
+ }
135
+ /* ------------------------------------------------------------------ */
136
+ /* Styles */
137
+ /* ------------------------------------------------------------------ */
138
+ const styles = react_native_1.StyleSheet.create({
139
+ container: {
140
+ flex: 1,
141
+ backgroundColor: '#FFFFFF',
142
+ },
143
+ webview: {
144
+ flex: 1,
145
+ },
146
+ });
147
+ //# sourceMappingURL=PFMWebView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PFMWebView.js","sourceRoot":"","sources":["../src/PFMWebView.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0FA,gCA8GC;AAxMD,+CAA4D;AAC5D,+CASsB;AACtB,+DAA+C;AAG/C,0DAA6E;AAC7E,mDAAgD;AAChD,2CAIqB;AAyBrB,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE;;;GAGG;AACH,SAAS,aAAa,CAAC,GAAW;IAChC,OAAO,iCAAqB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AACxE,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CACrB,GAAW,EACX,QAAuC,EACvC,OAAsC;IAEtC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QACzD,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACvD,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,SAAS,CAAC;QAEvE,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,OAAO,CAAC,oCAAoB,CAAC,KAAK,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;QAC3D,CAAC;aAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClC,QAAQ,CAAC,oCAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;QAC7D,CAAC;aAAM,CAAC;YACN,uDAAuD;YACvD,QAAQ,CAAC,oCAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,YAAY,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAChE,OAAO,CAAC,oCAAoB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;IACpD,CAAC;AACH,CAAC;AAED,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,SAAgB,UAAU,CAAC,EACzB,KAAK,EACL,GAAG,GAAG,SAAS,EACf,QAAQ,EACR,OAAO,GACS;IAChB,MAAM,UAAU,GAAG,IAAA,cAAM,EAAU,IAAI,CAAC,CAAC;IAEzC,0CAA0C;IAC1C,MAAM,UAAU,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE;QAC9B,MAAM,MAAM,GAAiB,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QAC5C,OAAO,IAAA,6BAAa,EAAC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;IAE1B,8CAA8C;IAC9C,MAAM,eAAe,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE;QACvC,oBAAK,CAAC,KAAK,CACT,UAAU,EACV,gCAAgC,EAChC;YACE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE;YAC/B;gBACE,IAAI,EAAE,KAAK;gBACX,OAAO,EAAE,GAAG,EAAE,CACZ,QAAQ,CACN,oCAAoB,CAAC,MAAM,CAAC,6BAA6B,CAAC,CAC3D;aACJ;SACF,EACD,EAAE,UAAU,EAAE,IAAI,EAAE,CACrB,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,eAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,uBAAQ,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAG,0BAAW,CAAC,gBAAgB,CACtC,mBAAmB,EACnB,eAAe,CAChB,CAAC;YACF,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;QAC5B,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IAEtB,sFAAsF;IACtF,MAAM,qBAAqB,GAAG,IAAA,mBAAW,EACvC,CAAC,KAAsB,EAAW,EAAE;QAClC,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;QAEtB,4DAA4D;QAC5D,IAAI,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,sBAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACrC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,iDAAiD;QACjD,IAAI,GAAG,CAAC,QAAQ,CAAC,sBAAU,CAAC,EAAE,CAAC;YAC7B,cAAc,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;YACvC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,oCAAoC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC,EACD,CAAC,QAAQ,EAAE,OAAO,CAAC,CACpB,CAAC;IAEF,8BAA8B;IAC9B,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,8BAAC,mBAAI,IAAC,KAAK,EAAE,MAAM,CAAC,SAAS,GAAI,CAAC;IAC3C,CAAC;IAED,sBAAsB;IACtB,OAAO,CACL,8BAAC,2BAAY,IAAC,KAAK,EAAE,MAAM,CAAC,SAAS;QACnC,8BAAC,wBAAS,IAAC,QAAQ,EAAC,cAAc,GAAG;QAErC,8BAAC,8BAAO,IACN,GAAG,EAAE,UAAU,EACf,MAAM,EAAC,aAAa,EACpB,MAAM,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,EAC3B,KAAK,EAAE,MAAM,CAAC,OAAO,EACrB,SAAS,EAAE,6BAAiB,EAC5B,iBAAiB,QACjB,qCAAqC,QACrC,iBAAiB,QACjB,2BAA2B,QAC3B,gCAAgC,QAChC,+BAA+B,EAAE,KAAK,EACtC,mCAAmC,EAAE,KAAK,EAC1C,eAAe,EAAE,KAAK,EACtB,wBAAwB,EAAE,IAAI,EAC9B,oBAAoB,EAAE,IAAI,EAC1B,YAAY,EAAE,IAAI,EAClB,gBAAgB,EAAC,eAAe,EAChC,yBAAyB,EAAE,KAAK,EAChC,eAAe,EAAE,CAAC,GAAG,CAAC,EACtB,4BAA4B,EAAE,qBAAqB,EACnD,OAAO,EAAE,CAAC,cAAc,EAAE,EAAE;gBAC1B,MAAM,EAAE,WAAW,EAAE,GAAG,cAAc,CAAC;gBACvC,OAAO,CACL,oCAAoB,CAAC,KAAK,CACxB,WAAW,CAAC,WAAW,IAAI,wBAAwB,CACpD,CACF,CAAC;YACJ,CAAC,GACD,CACW,CAChB,CAAC;AACJ,CAAC;AAED,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,MAAM,MAAM,GAAG,yBAAU,CAAC,MAAM,CAAC;IAC/B,SAAS,EAAE;QACT,IAAI,EAAE,CAAC;QACP,eAAe,EAAE,SAAS;KAC3B;IACD,OAAO,EAAE;QACP,IAAI,EAAE,CAAC;KACR;CACF,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ /** Production PFM endpoint. */
2
+ export declare const PROD_PFM_APP_URL = "https://pfm.equal.in";
3
+ /** UAT / pre-prod PFM endpoint. */
4
+ export declare const UAT_PFM_APP_URL = "https://uat.pfm.equal.in";
5
+ /** PFM path appended to the base domain. */
6
+ export declare const PFM_PATH = "/pfm";
7
+ /** URL path fragment that signals an SDK close event. */
8
+ export declare const CLOSE_PATH = "/pfm/close";
9
+ /**
10
+ * External URLs that must be opened in the device's default browser
11
+ * rather than inside the embedded webview.
12
+ */
13
+ export declare const EXTERNAL_URL_PATTERNS: string[];
14
+ /**
15
+ * Custom user-agent string matching the Flutter SDK to ensure
16
+ * identical server-side behaviour.
17
+ */
18
+ export declare const CUSTOM_USER_AGENT = "Firefox/5.0 (Linux; Android 11; Nokia 8.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36";
19
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAEvD,mCAAmC;AACnC,eAAO,MAAM,eAAe,6BAA6B,CAAC;AAE1D,4CAA4C;AAC5C,eAAO,MAAM,QAAQ,SAAS,CAAC;AAE/B,yDAAyD;AACzD,eAAO,MAAM,UAAU,eAAe,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,EAGzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,gIACiG,CAAC"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CUSTOM_USER_AGENT = exports.EXTERNAL_URL_PATTERNS = exports.CLOSE_PATH = exports.PFM_PATH = exports.UAT_PFM_APP_URL = exports.PROD_PFM_APP_URL = void 0;
4
+ /** Production PFM endpoint. */
5
+ exports.PROD_PFM_APP_URL = 'https://pfm.equal.in';
6
+ /** UAT / pre-prod PFM endpoint. */
7
+ exports.UAT_PFM_APP_URL = 'https://uat.pfm.equal.in';
8
+ /** PFM path appended to the base domain. */
9
+ exports.PFM_PATH = '/pfm';
10
+ /** URL path fragment that signals an SDK close event. */
11
+ exports.CLOSE_PATH = '/pfm/close';
12
+ /**
13
+ * External URLs that must be opened in the device's default browser
14
+ * rather than inside the embedded webview.
15
+ */
16
+ exports.EXTERNAL_URL_PATTERNS = [
17
+ 'https://www.tatamutualfund.com/raise-query',
18
+ 'https://aa-uat.onemoney.in/',
19
+ ];
20
+ /**
21
+ * Custom user-agent string matching the Flutter SDK to ensure
22
+ * identical server-side behaviour.
23
+ */
24
+ exports.CUSTOM_USER_AGENT = 'Firefox/5.0 (Linux; Android 11; Nokia 8.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36';
25
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAAA,+BAA+B;AAClB,QAAA,gBAAgB,GAAG,sBAAsB,CAAC;AAEvD,mCAAmC;AACtB,QAAA,eAAe,GAAG,0BAA0B,CAAC;AAE1D,4CAA4C;AAC/B,QAAA,QAAQ,GAAG,MAAM,CAAC;AAE/B,yDAAyD;AAC5C,QAAA,UAAU,GAAG,YAAY,CAAC;AAEvC;;;GAGG;AACU,QAAA,qBAAqB,GAAa;IAC7C,4CAA4C;IAC5C,6BAA6B;CAC9B,CAAC;AAEF;;;GAGG;AACU,QAAA,iBAAiB,GAC5B,6HAA6H,CAAC"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export { PFMWebView } from './PFMWebView';
2
+ export type { PFMWebViewProps } from './PFMWebView';
3
+ export type { PFMSDKConfig } from './models/PFMSDKConfig';
4
+ export { resolvePFMSDKConfig } from './models/PFMSDKConfig';
5
+ export type { EventResponse } from './models/EventResponse';
6
+ export { EventResponseFactory } from './models/EventResponse';
7
+ export { getGatewayURL, getEqualDomain } from './PFMSDKManager';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getEqualDomain = exports.getGatewayURL = exports.EventResponseFactory = exports.resolvePFMSDKConfig = exports.PFMWebView = void 0;
4
+ var PFMWebView_1 = require("./PFMWebView");
5
+ Object.defineProperty(exports, "PFMWebView", { enumerable: true, get: function () { return PFMWebView_1.PFMWebView; } });
6
+ var PFMSDKConfig_1 = require("./models/PFMSDKConfig");
7
+ Object.defineProperty(exports, "resolvePFMSDKConfig", { enumerable: true, get: function () { return PFMSDKConfig_1.resolvePFMSDKConfig; } });
8
+ var EventResponse_1 = require("./models/EventResponse");
9
+ Object.defineProperty(exports, "EventResponseFactory", { enumerable: true, get: function () { return EventResponse_1.EventResponseFactory; } });
10
+ var PFMSDKManager_1 = require("./PFMSDKManager");
11
+ Object.defineProperty(exports, "getGatewayURL", { enumerable: true, get: function () { return PFMSDKManager_1.getGatewayURL; } });
12
+ Object.defineProperty(exports, "getEqualDomain", { enumerable: true, get: function () { return PFMSDKManager_1.getEqualDomain; } });
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AAInB,sDAA4D;AAAnD,mHAAA,mBAAmB,OAAA;AAG5B,wDAA8D;AAArD,qHAAA,oBAAoB,OAAA;AAE7B,iDAAgE;AAAvD,8GAAA,aAAa,OAAA;AAAE,+GAAA,cAAc,OAAA"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Structured event payload returned through SDK callbacks.
3
+ */
4
+ export interface EventResponse {
5
+ /** Discriminator for the event origin. */
6
+ eventType: string;
7
+ /** High-level outcome — e.g. "ERROR", "CLOSED". */
8
+ status: string;
9
+ /** Human-readable description of the event. */
10
+ message: string;
11
+ /** Optional status code echoed from the PFM backend. */
12
+ statusCode?: string;
13
+ }
14
+ /**
15
+ * Factory helpers for building well-typed EventResponse objects.
16
+ */
17
+ export declare const EventResponseFactory: {
18
+ closed(message: string, statusCode?: string): EventResponse;
19
+ error(message: string, statusCode?: string): EventResponse;
20
+ };
21
+ //# sourceMappingURL=EventResponse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventResponse.d.ts","sourceRoot":"","sources":["../../src/models/EventResponse.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAElB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IAEf,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAEhB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB;oBACf,MAAM,eAAe,MAAM,GAAG,aAAa;mBAS5C,MAAM,eAAe,MAAM,GAAG,aAAa;CAQ3D,CAAC"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EventResponseFactory = void 0;
4
+ /**
5
+ * Factory helpers for building well-typed EventResponse objects.
6
+ */
7
+ exports.EventResponseFactory = {
8
+ closed(message, statusCode) {
9
+ return {
10
+ eventType: 'PFM_SDK_CALLBACK',
11
+ status: 'CLOSED',
12
+ message,
13
+ statusCode,
14
+ };
15
+ },
16
+ error(message, statusCode) {
17
+ return {
18
+ eventType: 'PFM_SDK_CALLBACK',
19
+ status: 'ERROR',
20
+ message,
21
+ statusCode,
22
+ };
23
+ },
24
+ };
25
+ //# sourceMappingURL=EventResponse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventResponse.js","sourceRoot":"","sources":["../../src/models/EventResponse.ts"],"names":[],"mappings":";;;AAiBA;;GAEG;AACU,QAAA,oBAAoB,GAAG;IAClC,MAAM,CAAC,OAAe,EAAE,UAAmB;QACzC,OAAO;YACL,SAAS,EAAE,kBAAkB;YAC7B,MAAM,EAAE,QAAQ;YAChB,OAAO;YACP,UAAU;SACX,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,UAAmB;QACxC,OAAO;YACL,SAAS,EAAE,kBAAkB;YAC7B,MAAM,EAAE,OAAO;YACf,OAAO;YACP,UAAU;SACX,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Configuration for the PFM SDK.
3
+ *
4
+ * @param token - Access token obtained from the SDK Init API (required).
5
+ * @param env - Target environment. Use "production" for live,
6
+ * any other value (e.g. "pre-prod", "sandbox") routes to UAT.
7
+ * Defaults to "sandbox".
8
+ */
9
+ export interface PFMSDKConfig {
10
+ /** Access token returned by the SDK Init API. */
11
+ token: string;
12
+ /**
13
+ * Environment selector.
14
+ * - `"production"` → production endpoint
15
+ * - Any other value → UAT endpoint
16
+ * @default "sandbox"
17
+ */
18
+ env?: string;
19
+ }
20
+ /**
21
+ * Build a PFMSDKConfig with defaults applied.
22
+ */
23
+ export declare function resolvePFMSDKConfig(config: PFMSDKConfig): Required<PFMSDKConfig>;
24
+ //# sourceMappingURL=PFMSDKConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PFMSDKConfig.d.ts","sourceRoot":"","sources":["../../src/models/PFMSDKConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IAEd;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC,CAKhF"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolvePFMSDKConfig = resolvePFMSDKConfig;
4
+ /**
5
+ * Build a PFMSDKConfig with defaults applied.
6
+ */
7
+ function resolvePFMSDKConfig(config) {
8
+ return {
9
+ token: config.token,
10
+ env: config.env ?? 'sandbox',
11
+ };
12
+ }
13
+ //# sourceMappingURL=PFMSDKConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PFMSDKConfig.js","sourceRoot":"","sources":["../../src/models/PFMSDKConfig.ts"],"names":[],"mappings":";;AAwBA,kDAKC;AARD;;GAEG;AACH,SAAgB,mBAAmB,CAAC,MAAoB;IACtD,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI,SAAS;KAC7B,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "equal-react-native-pfm-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Equal PFM React Native SDK",
5
+ "main": "src/index.ts",
6
+ "types": "lib/index.d.ts",
7
+ "react-native": "src/index.ts",
8
+ "files": [
9
+ "lib/",
10
+ "src/",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "prepare": "tsc",
16
+ "test": "jest",
17
+ "test:coverage": "jest --coverage",
18
+ "lint": "eslint src/ --ext .ts,.tsx",
19
+ "publish-npm": "npm publish --access public"
20
+ },
21
+ "keywords": [
22
+ "pfm",
23
+ "sdk",
24
+ "react-native",
25
+ "equal"
26
+ ],
27
+ "author": "One Equal PFM",
28
+ "license": "ISC",
29
+ "homepage": "https://www.npmjs.com/package/equal-react-native-pfm-sdk",
30
+ "peerDependencies": {
31
+ "react": ">=17.0.0",
32
+ "react-native": ">=0.68.0",
33
+ "react-native-webview": ">=11.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@testing-library/react-native": "^12.4.0",
37
+ "@types/jest": "^29.5.0",
38
+ "@types/react": "^18.2.0",
39
+ "@types/react-native": "^0.72.0",
40
+ "jest": "^29.7.0",
41
+ "react": "^18.2.0",
42
+ "react-native": "^0.73.0",
43
+ "react-native-webview": "^13.6.0",
44
+ "react-test-renderer": "^18.2.0",
45
+ "typescript": "^5.3.0",
46
+ "@testing-library/jest-native": "^5.4.0"
47
+ }
48
+ }
@@ -0,0 +1,43 @@
1
+ import { PFMSDKConfig, resolvePFMSDKConfig } from './models/PFMSDKConfig';
2
+ import { EventResponse, EventResponseFactory } from './models/EventResponse';
3
+ import {
4
+ PROD_PFM_APP_URL,
5
+ UAT_PFM_APP_URL,
6
+ PFM_PATH,
7
+ } from './constants';
8
+
9
+ /**
10
+ * Resolves the correct PFM domain based on the environment value.
11
+ *
12
+ * - If `env` contains the substring "production" → production URL.
13
+ * - Otherwise → UAT URL.
14
+ */
15
+ export function getEqualDomain(env: string): string {
16
+ return env.includes('production') ? PROD_PFM_APP_URL : UAT_PFM_APP_URL;
17
+ }
18
+
19
+ /**
20
+ * Builds the full gateway URL that the webview should load.
21
+ *
22
+ * @returns The URL string on success, or `null` if construction fails
23
+ * (in which case `onError` is invoked).
24
+ *
25
+ * URL format: `https://<domain>/pfm?access_token=<token>`
26
+ */
27
+ export function getGatewayURL(
28
+ config: PFMSDKConfig,
29
+ onError: (data: EventResponse) => void,
30
+ ): string | null {
31
+ try {
32
+ const resolved = resolvePFMSDKConfig(config);
33
+ const domain = getEqualDomain(resolved.env);
34
+ const url = new URL(PFM_PATH, domain);
35
+ url.searchParams.set('access_token', resolved.token);
36
+ return url.toString();
37
+ } catch (e) {
38
+ const message =
39
+ 'Unable to load the PFM due to a technical error. Please try again.';
40
+ onError(EventResponseFactory.error(message));
41
+ return null;
42
+ }
43
+ }
@@ -0,0 +1,215 @@
1
+ import React, { useCallback, useMemo, useRef } from 'react';
2
+ import {
3
+ Alert,
4
+ BackHandler,
5
+ Linking,
6
+ Platform,
7
+ SafeAreaView,
8
+ StatusBar,
9
+ StyleSheet,
10
+ View,
11
+ } from 'react-native';
12
+ import { WebView } from 'react-native-webview';
13
+
14
+ import { PFMSDKConfig } from './models/PFMSDKConfig';
15
+ import { EventResponse, EventResponseFactory } from './models/EventResponse';
16
+ import { getGatewayURL } from './PFMSDKManager';
17
+ import {
18
+ CLOSE_PATH,
19
+ CUSTOM_USER_AGENT,
20
+ EXTERNAL_URL_PATTERNS,
21
+ } from './constants';
22
+
23
+ /* ------------------------------------------------------------------ */
24
+ /* Public prop types */
25
+ /* ------------------------------------------------------------------ */
26
+
27
+ export interface PFMWebViewProps {
28
+ /** Access token obtained from the SDK Init API. */
29
+ token: string;
30
+
31
+ /**
32
+ * Environment selector.
33
+ * - `"production"` → production endpoint
34
+ * - Any other value → UAT endpoint
35
+ * @default "sandbox"
36
+ */
37
+ env?: string;
38
+
39
+ /** Fired when the user explicitly closes the PFM interface. */
40
+ onClosed: (data: EventResponse) => void;
41
+
42
+ /** Fired when the SDK encounters an error. */
43
+ onError: (data: EventResponse) => void;
44
+ }
45
+
46
+ /* ------------------------------------------------------------------ */
47
+ /* Helpers */
48
+ /* ------------------------------------------------------------------ */
49
+
50
+ /**
51
+ * Returns `true` when the URL matches one of the known external patterns
52
+ * that should be opened in the device's default browser.
53
+ */
54
+ function isExternalUrl(url: string): boolean {
55
+ return EXTERNAL_URL_PATTERNS.some((pattern) => url.includes(pattern));
56
+ }
57
+
58
+ /**
59
+ * Parses query-parameters from a `/pfm/close` URL and invokes the
60
+ * appropriate callback (onClosed or onError).
61
+ */
62
+ function handleSdkEvent(
63
+ url: string,
64
+ onClosed: (data: EventResponse) => void,
65
+ onError: (data: EventResponse) => void,
66
+ ): void {
67
+ try {
68
+ const parsed = new URL(url);
69
+ const message = parsed.searchParams.get('message') ?? '';
70
+ const status = parsed.searchParams.get('status') ?? '';
71
+ const statusCode = parsed.searchParams.get('status_code') ?? undefined;
72
+
73
+ if (status === 'ERROR') {
74
+ onError(EventResponseFactory.error(message, statusCode));
75
+ } else if (url.includes('CLOSED')) {
76
+ onClosed(EventResponseFactory.closed(message, statusCode));
77
+ } else {
78
+ // Fallback — treat any other /close as a normal close.
79
+ onClosed(EventResponseFactory.closed(message, statusCode));
80
+ }
81
+ } catch (e) {
82
+ const errorMessage = e instanceof Error ? e.message : String(e);
83
+ onError(EventResponseFactory.error(errorMessage));
84
+ }
85
+ }
86
+
87
+ /* ------------------------------------------------------------------ */
88
+ /* Component */
89
+ /* ------------------------------------------------------------------ */
90
+
91
+ export function PFMWebView({
92
+ token,
93
+ env = 'sandbox',
94
+ onClosed,
95
+ onError,
96
+ }: PFMWebViewProps): React.JSX.Element {
97
+ const webViewRef = useRef<WebView>(null);
98
+
99
+ /* ---- Build the gateway URL once ---- */
100
+ const gatewayUrl = useMemo(() => {
101
+ const config: PFMSDKConfig = { token, env };
102
+ return getGatewayURL(config, (err) => onError(err));
103
+ }, [token, env, onError]);
104
+
105
+ /* ---- Android hardware-back handling ---- */
106
+ const handleBackPress = useCallback(() => {
107
+ Alert.alert(
108
+ 'Exit PFM',
109
+ 'Are you sure you want to exit?',
110
+ [
111
+ { text: 'No', style: 'cancel' },
112
+ {
113
+ text: 'Yes',
114
+ onPress: () =>
115
+ onClosed(
116
+ EventResponseFactory.closed('User exited via back button'),
117
+ ),
118
+ },
119
+ ],
120
+ { cancelable: true },
121
+ );
122
+ return true;
123
+ }, [onClosed]);
124
+
125
+ React.useEffect(() => {
126
+ if (Platform.OS === 'android') {
127
+ const sub = BackHandler.addEventListener(
128
+ 'hardwareBackPress',
129
+ handleBackPress,
130
+ );
131
+ return () => sub.remove();
132
+ }
133
+ return undefined;
134
+ }, [handleBackPress]);
135
+
136
+ /* ---- Pre-navigation interception (matches Flutter shouldOverrideUrlLoading) ---- */
137
+ const handleShouldStartLoad = useCallback(
138
+ (event: { url: string }): boolean => {
139
+ const { url } = event;
140
+
141
+ // External URLs → cancel navigation, open in system browser
142
+ if (isExternalUrl(url)) {
143
+ Linking.openURL(url).catch(() => {});
144
+ return false;
145
+ }
146
+
147
+ // /pfm/close → cancel navigation, fire callbacks
148
+ if (url.includes(CLOSE_PATH)) {
149
+ handleSdkEvent(url, onClosed, onError);
150
+ return false;
151
+ }
152
+
153
+ // All other URLs → allow navigation
154
+ return true;
155
+ },
156
+ [onClosed, onError],
157
+ );
158
+
159
+ /* ---- Error fallback ---- */
160
+ if (!gatewayUrl) {
161
+ return <View style={styles.container} />;
162
+ }
163
+
164
+ /* ---- Render ---- */
165
+ return (
166
+ <SafeAreaView style={styles.container}>
167
+ <StatusBar barStyle="dark-content" />
168
+
169
+ <WebView
170
+ ref={webViewRef}
171
+ testID="pfm-webview"
172
+ source={{ uri: gatewayUrl }}
173
+ style={styles.webview}
174
+ userAgent={CUSTOM_USER_AGENT}
175
+ javaScriptEnabled
176
+ javaScriptCanOpenWindowsAutomatically
177
+ domStorageEnabled
178
+ allowFileAccessFromFileURLs
179
+ allowUniversalAccessFromFileURLs
180
+ mediaPlaybackRequiresUserAction={false}
181
+ allowsBackForwardNavigationGestures={false}
182
+ scalesPageToFit={false}
183
+ thirdPartyCookiesEnabled={true}
184
+ sharedCookiesEnabled={true}
185
+ cacheEnabled={true}
186
+ mixedContentMode="compatibility"
187
+ setSupportMultipleWindows={false}
188
+ originWhitelist={['*']}
189
+ onShouldStartLoadWithRequest={handleShouldStartLoad}
190
+ onError={(syntheticEvent) => {
191
+ const { nativeEvent } = syntheticEvent;
192
+ onError(
193
+ EventResponseFactory.error(
194
+ nativeEvent.description ?? 'WebView failed to load',
195
+ ),
196
+ );
197
+ }}
198
+ />
199
+ </SafeAreaView>
200
+ );
201
+ }
202
+
203
+ /* ------------------------------------------------------------------ */
204
+ /* Styles */
205
+ /* ------------------------------------------------------------------ */
206
+
207
+ const styles = StyleSheet.create({
208
+ container: {
209
+ flex: 1,
210
+ backgroundColor: '#FFFFFF',
211
+ },
212
+ webview: {
213
+ flex: 1,
214
+ },
215
+ });
@@ -0,0 +1,27 @@
1
+ /** Production PFM endpoint. */
2
+ export const PROD_PFM_APP_URL = 'https://pfm.equal.in';
3
+
4
+ /** UAT / pre-prod PFM endpoint. */
5
+ export const UAT_PFM_APP_URL = 'https://uat.pfm.equal.in';
6
+
7
+ /** PFM path appended to the base domain. */
8
+ export const PFM_PATH = '/pfm';
9
+
10
+ /** URL path fragment that signals an SDK close event. */
11
+ export const CLOSE_PATH = '/pfm/close';
12
+
13
+ /**
14
+ * External URLs that must be opened in the device's default browser
15
+ * rather than inside the embedded webview.
16
+ */
17
+ export const EXTERNAL_URL_PATTERNS: string[] = [
18
+ 'https://www.tatamutualfund.com/raise-query',
19
+ 'https://aa-uat.onemoney.in/',
20
+ ];
21
+
22
+ /**
23
+ * Custom user-agent string matching the Flutter SDK to ensure
24
+ * identical server-side behaviour.
25
+ */
26
+ export const CUSTOM_USER_AGENT =
27
+ 'Firefox/5.0 (Linux; Android 11; Nokia 8.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36';
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export { PFMWebView } from './PFMWebView';
2
+ export type { PFMWebViewProps } from './PFMWebView';
3
+
4
+ export type { PFMSDKConfig } from './models/PFMSDKConfig';
5
+ export { resolvePFMSDKConfig } from './models/PFMSDKConfig';
6
+
7
+ export type { EventResponse } from './models/EventResponse';
8
+ export { EventResponseFactory } from './models/EventResponse';
9
+
10
+ export { getGatewayURL, getEqualDomain } from './PFMSDKManager';
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Structured event payload returned through SDK callbacks.
3
+ */
4
+ export interface EventResponse {
5
+ /** Discriminator for the event origin. */
6
+ eventType: string;
7
+
8
+ /** High-level outcome — e.g. "ERROR", "CLOSED". */
9
+ status: string;
10
+
11
+ /** Human-readable description of the event. */
12
+ message: string;
13
+
14
+ /** Optional status code echoed from the PFM backend. */
15
+ statusCode?: string;
16
+ }
17
+
18
+ /**
19
+ * Factory helpers for building well-typed EventResponse objects.
20
+ */
21
+ export const EventResponseFactory = {
22
+ closed(message: string, statusCode?: string): EventResponse {
23
+ return {
24
+ eventType: 'PFM_SDK_CALLBACK',
25
+ status: 'CLOSED',
26
+ message,
27
+ statusCode,
28
+ };
29
+ },
30
+
31
+ error(message: string, statusCode?: string): EventResponse {
32
+ return {
33
+ eventType: 'PFM_SDK_CALLBACK',
34
+ status: 'ERROR',
35
+ message,
36
+ statusCode,
37
+ };
38
+ },
39
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Configuration for the PFM SDK.
3
+ *
4
+ * @param token - Access token obtained from the SDK Init API (required).
5
+ * @param env - Target environment. Use "production" for live,
6
+ * any other value (e.g. "pre-prod", "sandbox") routes to UAT.
7
+ * Defaults to "sandbox".
8
+ */
9
+ export interface PFMSDKConfig {
10
+ /** Access token returned by the SDK Init API. */
11
+ token: string;
12
+
13
+ /**
14
+ * Environment selector.
15
+ * - `"production"` → production endpoint
16
+ * - Any other value → UAT endpoint
17
+ * @default "sandbox"
18
+ */
19
+ env?: string;
20
+ }
21
+
22
+ /**
23
+ * Build a PFMSDKConfig with defaults applied.
24
+ */
25
+ export function resolvePFMSDKConfig(config: PFMSDKConfig): Required<PFMSDKConfig> {
26
+ return {
27
+ token: config.token,
28
+ env: config.env ?? 'sandbox',
29
+ };
30
+ }