@edge-markets/connect-react-native 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 +307 -0
- package/dist/index.d.mts +355 -0
- package/dist/index.d.ts +355 -0
- package/dist/index.js +705 -0
- package/dist/index.mjs +686 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# @edgeboost/edge-connect-react-native
|
|
2
|
+
|
|
3
|
+
React Native SDK for EDGE Connect - Plaid-like authentication flow for mobile apps.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 **Secure OAuth 2.0 with PKCE** - No client secrets in mobile app
|
|
8
|
+
- 📱 **Native In-App Browser** - Uses SFSafariViewController (iOS) / Chrome Custom Tabs (Android)
|
|
9
|
+
- 🔗 **Deep Link Support** - Seamless callback handling
|
|
10
|
+
- ⚛️ **React Hooks** - Simple, declarative API
|
|
11
|
+
- 📊 **Event Tracking** - Analytics and debugging support
|
|
12
|
+
- 🎨 **Customizable** - Works with your app's flow
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# npm
|
|
18
|
+
npm install @edgeboost/edge-connect-react-native react-native-inappbrowser-reborn
|
|
19
|
+
|
|
20
|
+
# yarn
|
|
21
|
+
yarn add @edgeboost/edge-connect-react-native react-native-inappbrowser-reborn
|
|
22
|
+
|
|
23
|
+
# pnpm
|
|
24
|
+
pnpm add @edgeboost/edge-connect-react-native react-native-inappbrowser-reborn
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### iOS Setup
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd ios && pod install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### For Expo Projects
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx expo install expo-web-browser
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The SDK will automatically use `expo-web-browser` if available.
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### 1. Configure Deep Linking
|
|
44
|
+
|
|
45
|
+
**iOS (Info.plist):**
|
|
46
|
+
```xml
|
|
47
|
+
<key>CFBundleURLTypes</key>
|
|
48
|
+
<array>
|
|
49
|
+
<dict>
|
|
50
|
+
<key>CFBundleURLSchemes</key>
|
|
51
|
+
<array>
|
|
52
|
+
<string>myapp</string>
|
|
53
|
+
</array>
|
|
54
|
+
</dict>
|
|
55
|
+
</array>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Android (AndroidManifest.xml):**
|
|
59
|
+
```xml
|
|
60
|
+
<intent-filter>
|
|
61
|
+
<action android:name="android.intent.action.VIEW" />
|
|
62
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
63
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
64
|
+
<data android:scheme="myapp" android:host="oauth" android:pathPrefix="/callback" />
|
|
65
|
+
</intent-filter>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Use the Hook
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { useEdgeLink } from '@edgeboost/edge-connect-react-native'
|
|
72
|
+
import { Button, Alert } from 'react-native'
|
|
73
|
+
|
|
74
|
+
function ConnectEdgeButton() {
|
|
75
|
+
const { open, isOpen, isSuccess, result, error, reset } = useEdgeLink({
|
|
76
|
+
clientId: 'your-client-id',
|
|
77
|
+
environment: 'staging',
|
|
78
|
+
redirectUri: 'myapp://oauth/callback',
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Handle success
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
if (isSuccess && result) {
|
|
84
|
+
// Send code to your backend for token exchange
|
|
85
|
+
fetch('https://your-api.com/edge/exchange', {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: { 'Content-Type': 'application/json' },
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
code: result.code,
|
|
90
|
+
codeVerifier: result.codeVerifier,
|
|
91
|
+
}),
|
|
92
|
+
})
|
|
93
|
+
.then(res => res.json())
|
|
94
|
+
.then(data => {
|
|
95
|
+
Alert.alert('Success', 'EdgeBoost connected!')
|
|
96
|
+
reset() // Reset for next use
|
|
97
|
+
})
|
|
98
|
+
.catch(err => {
|
|
99
|
+
Alert.alert('Error', 'Failed to connect EdgeBoost')
|
|
100
|
+
reset()
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
}, [isSuccess, result])
|
|
104
|
+
|
|
105
|
+
// Handle errors
|
|
106
|
+
React.useEffect(() => {
|
|
107
|
+
if (error) {
|
|
108
|
+
Alert.alert('Error', error.message)
|
|
109
|
+
reset()
|
|
110
|
+
}
|
|
111
|
+
}, [error])
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<Button
|
|
115
|
+
title={isOpen ? 'Connecting...' : 'Connect EdgeBoost'}
|
|
116
|
+
onPress={open}
|
|
117
|
+
disabled={isOpen}
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## API Reference
|
|
124
|
+
|
|
125
|
+
### `useEdgeLink(config)`
|
|
126
|
+
|
|
127
|
+
React hook for EdgeLink integration.
|
|
128
|
+
|
|
129
|
+
**Config:**
|
|
130
|
+
| Property | Type | Required | Description |
|
|
131
|
+
|----------|------|----------|-------------|
|
|
132
|
+
| `clientId` | `string` | ✅ | Your OAuth client ID |
|
|
133
|
+
| `environment` | `'production' \| 'staging' \| 'sandbox'` | ✅ | Environment |
|
|
134
|
+
| `redirectUri` | `string` | ✅ | Deep link URI for callback |
|
|
135
|
+
| `scopes` | `EdgeScope[]` | ❌ | Requested permissions |
|
|
136
|
+
| `linkUrl` | `string` | ❌ | Custom Link URL (dev only) |
|
|
137
|
+
| `useExternalBrowser` | `boolean` | ❌ | Use external browser |
|
|
138
|
+
|
|
139
|
+
**Returns:**
|
|
140
|
+
| Property | Type | Description |
|
|
141
|
+
|----------|------|-------------|
|
|
142
|
+
| `open` | `() => Promise<void>` | Opens the Link flow |
|
|
143
|
+
| `close` | `() => Promise<void>` | Closes the Link flow |
|
|
144
|
+
| `isOpen` | `boolean` | Whether flow is open |
|
|
145
|
+
| `isSuccess` | `boolean` | Whether flow succeeded |
|
|
146
|
+
| `isError` | `boolean` | Whether there was an error |
|
|
147
|
+
| `result` | `EdgeLinkSuccess \| null` | Success result with code |
|
|
148
|
+
| `error` | `{ code, message } \| null` | Error info |
|
|
149
|
+
| `reset` | `() => void` | Reset state for retry |
|
|
150
|
+
|
|
151
|
+
### `EdgeLink` Class
|
|
152
|
+
|
|
153
|
+
For more control, use the class directly:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { EdgeLink } from '@edgeboost/edge-connect-react-native'
|
|
157
|
+
|
|
158
|
+
const link = new EdgeLink({
|
|
159
|
+
clientId: 'your-client-id',
|
|
160
|
+
environment: 'staging',
|
|
161
|
+
redirectUri: 'myapp://oauth/callback',
|
|
162
|
+
onSuccess: (result) => {
|
|
163
|
+
console.log('Auth code:', result.code)
|
|
164
|
+
console.log('Code verifier:', result.codeVerifier)
|
|
165
|
+
},
|
|
166
|
+
onExit: (metadata) => {
|
|
167
|
+
if (metadata.error) {
|
|
168
|
+
console.error('Error:', metadata.error.message)
|
|
169
|
+
} else {
|
|
170
|
+
console.log('User closed')
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
onEvent: (event) => {
|
|
174
|
+
analytics.track('edge_link_event', event)
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// Open the flow
|
|
179
|
+
await link.open()
|
|
180
|
+
|
|
181
|
+
// Later, cleanup
|
|
182
|
+
link.destroy()
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Deep Link Handling
|
|
186
|
+
|
|
187
|
+
The SDK automatically sets up deep link listeners. If you have custom routing, you can handle links manually:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { useEdgeLinkHandler } from '@edgeboost/edge-connect-react-native'
|
|
191
|
+
import { Linking } from 'react-native'
|
|
192
|
+
|
|
193
|
+
function App() {
|
|
194
|
+
const { handleUrl } = useEdgeLinkHandler({
|
|
195
|
+
redirectUri: 'myapp://oauth/callback',
|
|
196
|
+
onSuccess: (result) => {
|
|
197
|
+
// Handle success
|
|
198
|
+
},
|
|
199
|
+
onError: (error) => {
|
|
200
|
+
// Handle error
|
|
201
|
+
},
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
const subscription = Linking.addEventListener('url', ({ url }) => {
|
|
206
|
+
if (!handleUrl(url)) {
|
|
207
|
+
// Handle other deep links
|
|
208
|
+
handleOtherDeepLinks(url)
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
return () => subscription.remove()
|
|
213
|
+
}, [handleUrl])
|
|
214
|
+
|
|
215
|
+
return <YourApp />
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Security Considerations
|
|
220
|
+
|
|
221
|
+
### PKCE
|
|
222
|
+
|
|
223
|
+
The SDK uses PKCE (Proof Key for Code Exchange) to secure the OAuth flow. This prevents authorization code interception attacks without requiring a client secret in the mobile app.
|
|
224
|
+
|
|
225
|
+
### In-App Browser
|
|
226
|
+
|
|
227
|
+
The SDK uses secure system browsers (SFSafariViewController / Chrome Custom Tabs) instead of WebViews. This is more secure because:
|
|
228
|
+
|
|
229
|
+
- Cookies are isolated from your app
|
|
230
|
+
- Users can verify they're on the real EdgeBoost domain
|
|
231
|
+
- Prevents credential theft by malicious apps
|
|
232
|
+
|
|
233
|
+
### Secure Random
|
|
234
|
+
|
|
235
|
+
For production, install a cryptographic random number generator:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
npm install react-native-get-random-values
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Then import it at the top of your entry file:
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
import 'react-native-get-random-values'
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Without this, the SDK falls back to less secure random generation and will log a warning.
|
|
248
|
+
|
|
249
|
+
## Backend Integration
|
|
250
|
+
|
|
251
|
+
The `result.code` and `result.codeVerifier` must be sent to your backend for token exchange:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// Your backend (using @edgeboost/edge-connect-server)
|
|
255
|
+
import { EdgeConnectServer } from '@edgeboost/edge-connect-server'
|
|
256
|
+
|
|
257
|
+
const server = new EdgeConnectServer({
|
|
258
|
+
clientId: 'your-client-id',
|
|
259
|
+
clientSecret: 'your-client-secret',
|
|
260
|
+
environment: 'staging',
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
// In your API endpoint
|
|
264
|
+
app.post('/edge/exchange', async (req, res) => {
|
|
265
|
+
const { code, codeVerifier } = req.body
|
|
266
|
+
|
|
267
|
+
const tokens = await server.exchangeCode(code, codeVerifier)
|
|
268
|
+
|
|
269
|
+
// Store tokens securely
|
|
270
|
+
await saveTokensForUser(req.user.id, tokens)
|
|
271
|
+
|
|
272
|
+
res.json({ success: true })
|
|
273
|
+
})
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Troubleshooting
|
|
277
|
+
|
|
278
|
+
### Deep link not working
|
|
279
|
+
|
|
280
|
+
1. Verify your URL scheme is registered in both iOS and Android configs
|
|
281
|
+
2. Test with `npx uri-scheme open myapp://oauth/callback --ios` or `--android`
|
|
282
|
+
3. Make sure the redirectUri matches exactly (including trailing slashes)
|
|
283
|
+
|
|
284
|
+
### In-app browser not opening
|
|
285
|
+
|
|
286
|
+
1. Install `react-native-inappbrowser-reborn` and run `pod install`
|
|
287
|
+
2. Or for Expo, install `expo-web-browser`
|
|
288
|
+
3. The SDK will fall back to external browser if neither is available
|
|
289
|
+
|
|
290
|
+
### "Insecure random" warning
|
|
291
|
+
|
|
292
|
+
Install and import `react-native-get-random-values` before any crypto operations.
|
|
293
|
+
|
|
294
|
+
## Comparison with Browser SDK
|
|
295
|
+
|
|
296
|
+
| Feature | Browser SDK | React Native SDK |
|
|
297
|
+
|---------|-------------|------------------|
|
|
298
|
+
| Auth UI | Popup window | In-app browser |
|
|
299
|
+
| Callback | postMessage | Deep links |
|
|
300
|
+
| Crypto | Web Crypto API | Polyfills/native |
|
|
301
|
+
| API | `EdgeLink` class | `EdgeLink` + hooks |
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
MIT
|
|
306
|
+
|
|
307
|
+
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { EdgeEnvironment, EdgeLinkSuccess, EdgeLinkExit, EdgeScope } from '@edge-markets/connect';
|
|
2
|
+
export { ALL_EDGE_SCOPES, Balance, EDGE_ENVIRONMENTS, EdgeApiError, EdgeAuthenticationError, EdgeConsentRequiredError, EdgeEnvironment, EdgeError, EdgeLinkExit, EdgeLinkSuccess, EdgeNetworkError, EdgePopupBlockedError, EdgeScope, EdgeStateMismatchError, EdgeTokenExchangeError, EdgeTokens, SCOPE_DESCRIPTIONS, Transfer, User, formatScopesForEnvironment, getEnvironmentConfig, isApiError, isAuthenticationError, isConsentRequiredError, isNetworkError } from '@edge-markets/connect';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* EdgeLink for React Native - Plaid-Style Authentication Flow
|
|
6
|
+
*
|
|
7
|
+
* Unlike the browser SDK which uses popups, the React Native SDK uses:
|
|
8
|
+
* - In-App Browser (SFSafariViewController / Chrome Custom Tabs)
|
|
9
|
+
* - Deep Links for OAuth callback
|
|
10
|
+
* - Secure storage for PKCE state
|
|
11
|
+
*
|
|
12
|
+
* This provides a native, secure authentication experience similar to Plaid Link.
|
|
13
|
+
*
|
|
14
|
+
* @module @edge-markets/connect-react-native
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { EdgeLink } from '@edge-markets/connect-react-native'
|
|
19
|
+
*
|
|
20
|
+
* const link = new EdgeLink({
|
|
21
|
+
* clientId: 'your-client-id',
|
|
22
|
+
* environment: 'staging',
|
|
23
|
+
* redirectUri: 'myapp://oauth/callback',
|
|
24
|
+
* onSuccess: async (result) => {
|
|
25
|
+
* await sendToBackend(result.code, result.codeVerifier)
|
|
26
|
+
* },
|
|
27
|
+
* onExit: (metadata) => {
|
|
28
|
+
* console.log('User exited:', metadata.reason)
|
|
29
|
+
* },
|
|
30
|
+
* })
|
|
31
|
+
*
|
|
32
|
+
* // Open from a button press
|
|
33
|
+
* <Button onPress={() => link.open()} title="Connect EdgeBoost" />
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Configuration for EdgeLink React Native.
|
|
39
|
+
*/
|
|
40
|
+
interface EdgeLinkConfig {
|
|
41
|
+
/**
|
|
42
|
+
* Your OAuth client ID from the EdgeBoost partner portal.
|
|
43
|
+
*/
|
|
44
|
+
clientId: string;
|
|
45
|
+
/**
|
|
46
|
+
* Environment to connect to.
|
|
47
|
+
*/
|
|
48
|
+
environment: EdgeEnvironment;
|
|
49
|
+
/**
|
|
50
|
+
* Deep link URI for OAuth callback.
|
|
51
|
+
*
|
|
52
|
+
* This must be registered in your app's URL schemes (iOS) and
|
|
53
|
+
* intent filters (Android), and in your EdgeBoost OAuth client settings.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* - Custom scheme: `myapp://oauth/callback`
|
|
57
|
+
* - Universal link: `https://myapp.com/oauth/callback`
|
|
58
|
+
*/
|
|
59
|
+
redirectUri: string;
|
|
60
|
+
/**
|
|
61
|
+
* Called when user successfully authenticates and grants consent.
|
|
62
|
+
*/
|
|
63
|
+
onSuccess: (result: EdgeLinkSuccess) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Called when user exits the flow.
|
|
66
|
+
*/
|
|
67
|
+
onExit?: (metadata: EdgeLinkExit) => void;
|
|
68
|
+
/**
|
|
69
|
+
* Called for various events during the flow.
|
|
70
|
+
*/
|
|
71
|
+
onEvent?: (event: EdgeLinkEvent) => void;
|
|
72
|
+
/**
|
|
73
|
+
* OAuth scopes to request.
|
|
74
|
+
* @default All available scopes
|
|
75
|
+
*/
|
|
76
|
+
scopes?: EdgeScope[];
|
|
77
|
+
/**
|
|
78
|
+
* Custom URL for the Link page (development only).
|
|
79
|
+
*/
|
|
80
|
+
linkUrl?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Use external browser instead of in-app browser.
|
|
83
|
+
* @default false
|
|
84
|
+
*/
|
|
85
|
+
useExternalBrowser?: boolean;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Event emitted during the Link flow.
|
|
89
|
+
*/
|
|
90
|
+
interface EdgeLinkEvent {
|
|
91
|
+
eventName: EdgeLinkEventName;
|
|
92
|
+
timestamp: number;
|
|
93
|
+
metadata?: Record<string, unknown>;
|
|
94
|
+
}
|
|
95
|
+
type EdgeLinkEventName = 'OPEN' | 'CLOSE' | 'HANDOFF' | 'SUCCESS' | 'ERROR' | 'REDIRECT';
|
|
96
|
+
/**
|
|
97
|
+
* EdgeLink for React Native - handles OAuth flow with in-app browser.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* const link = new EdgeLink({
|
|
102
|
+
* clientId: 'your-client-id',
|
|
103
|
+
* environment: 'staging',
|
|
104
|
+
* redirectUri: 'myapp://oauth/callback',
|
|
105
|
+
* onSuccess: handleSuccess,
|
|
106
|
+
* onExit: handleExit,
|
|
107
|
+
* })
|
|
108
|
+
*
|
|
109
|
+
* // Later, in response to user action
|
|
110
|
+
* await link.open()
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
declare class EdgeLink {
|
|
114
|
+
private readonly config;
|
|
115
|
+
private pkce;
|
|
116
|
+
private state;
|
|
117
|
+
private linkListener;
|
|
118
|
+
private isOpen;
|
|
119
|
+
private isDestroyed;
|
|
120
|
+
constructor(config: EdgeLinkConfig);
|
|
121
|
+
/**
|
|
122
|
+
* Opens the EdgeLink authentication flow.
|
|
123
|
+
*
|
|
124
|
+
* This launches an in-app browser with the EdgeBoost login/consent page.
|
|
125
|
+
* When complete, the browser redirects to your redirectUri and the
|
|
126
|
+
* onSuccess/onExit callback is called.
|
|
127
|
+
*/
|
|
128
|
+
open(): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Manually handles a deep link URL.
|
|
131
|
+
*
|
|
132
|
+
* Use this if you're handling deep links yourself instead of relying
|
|
133
|
+
* on the automatic listener.
|
|
134
|
+
*
|
|
135
|
+
* @param url - The deep link URL received
|
|
136
|
+
* @returns true if the URL was handled, false otherwise
|
|
137
|
+
*/
|
|
138
|
+
handleDeepLink(url: string): boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Closes the EdgeLink flow.
|
|
141
|
+
*/
|
|
142
|
+
close(): Promise<void>;
|
|
143
|
+
/**
|
|
144
|
+
* Destroys the EdgeLink instance.
|
|
145
|
+
*/
|
|
146
|
+
destroy(): void;
|
|
147
|
+
private buildLinkUrl;
|
|
148
|
+
private setupLinkListener;
|
|
149
|
+
private removeLinkListener;
|
|
150
|
+
private processCallback;
|
|
151
|
+
private cleanup;
|
|
152
|
+
private emitEvent;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* React Hooks for EdgeLink React Native
|
|
157
|
+
*
|
|
158
|
+
* These hooks provide a convenient, React-idiomatic way to integrate
|
|
159
|
+
* EdgeLink into your React Native application.
|
|
160
|
+
*
|
|
161
|
+
* @module @edge-markets/connect-react-native/hooks
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Configuration for useEdgeLink hook.
|
|
166
|
+
*/
|
|
167
|
+
interface UseEdgeLinkConfig {
|
|
168
|
+
/** Your OAuth client ID */
|
|
169
|
+
clientId: string;
|
|
170
|
+
/** Environment to connect to */
|
|
171
|
+
environment: EdgeEnvironment;
|
|
172
|
+
/** Deep link URI for OAuth callback */
|
|
173
|
+
redirectUri: string;
|
|
174
|
+
/** OAuth scopes to request */
|
|
175
|
+
scopes?: EdgeScope[];
|
|
176
|
+
/** Custom Link URL (development only) */
|
|
177
|
+
linkUrl?: string;
|
|
178
|
+
/** Use external browser instead of in-app browser */
|
|
179
|
+
useExternalBrowser?: boolean;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Return type for useEdgeLink hook.
|
|
183
|
+
*/
|
|
184
|
+
interface UseEdgeLinkReturn {
|
|
185
|
+
/** Opens the EdgeLink flow */
|
|
186
|
+
open: () => Promise<void>;
|
|
187
|
+
/** Closes the EdgeLink flow */
|
|
188
|
+
close: () => Promise<void>;
|
|
189
|
+
/** Whether the Link flow is currently open */
|
|
190
|
+
isOpen: boolean;
|
|
191
|
+
/** Whether the flow completed successfully */
|
|
192
|
+
isSuccess: boolean;
|
|
193
|
+
/** Whether there was an error */
|
|
194
|
+
isError: boolean;
|
|
195
|
+
/** The success result (code, codeVerifier, state) */
|
|
196
|
+
result: EdgeLinkSuccess | null;
|
|
197
|
+
/** Error information if the flow failed */
|
|
198
|
+
error: {
|
|
199
|
+
code: string;
|
|
200
|
+
message: string;
|
|
201
|
+
} | null;
|
|
202
|
+
/** Resets the state to allow another attempt */
|
|
203
|
+
reset: () => void;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* React hook for EdgeLink integration.
|
|
207
|
+
*
|
|
208
|
+
* Provides a simple, declarative API for connecting EdgeBoost accounts.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```tsx
|
|
212
|
+
* function ConnectButton() {
|
|
213
|
+
* const { open, isOpen, isSuccess, result, error } = useEdgeLink({
|
|
214
|
+
* clientId: 'your-client-id',
|
|
215
|
+
* environment: 'staging',
|
|
216
|
+
* redirectUri: 'myapp://oauth/callback',
|
|
217
|
+
* })
|
|
218
|
+
*
|
|
219
|
+
* useEffect(() => {
|
|
220
|
+
* if (isSuccess && result) {
|
|
221
|
+
* // Send to your backend
|
|
222
|
+
* exchangeCode(result.code, result.codeVerifier)
|
|
223
|
+
* }
|
|
224
|
+
* }, [isSuccess, result])
|
|
225
|
+
*
|
|
226
|
+
* return (
|
|
227
|
+
* <Button
|
|
228
|
+
* onPress={open}
|
|
229
|
+
* disabled={isOpen}
|
|
230
|
+
* title={isOpen ? 'Connecting...' : 'Connect EdgeBoost'}
|
|
231
|
+
* />
|
|
232
|
+
* )
|
|
233
|
+
* }
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
declare function useEdgeLink(config: UseEdgeLinkConfig): UseEdgeLinkReturn;
|
|
237
|
+
/**
|
|
238
|
+
* Hook to manually handle EdgeLink deep links.
|
|
239
|
+
*
|
|
240
|
+
* Use this if you have custom deep link routing and want to
|
|
241
|
+
* handle EdgeLink callbacks yourself.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```tsx
|
|
245
|
+
* function App() {
|
|
246
|
+
* const { handleUrl, isHandled } = useEdgeLinkHandler({
|
|
247
|
+
* redirectUri: 'myapp://oauth/callback',
|
|
248
|
+
* onSuccess: (result) => {
|
|
249
|
+
* console.log('Auth code:', result.code)
|
|
250
|
+
* },
|
|
251
|
+
* onExit: (error) => {
|
|
252
|
+
* console.log('Auth failed:', error)
|
|
253
|
+
* },
|
|
254
|
+
* })
|
|
255
|
+
*
|
|
256
|
+
* useEffect(() => {
|
|
257
|
+
* const sub = Linking.addEventListener('url', ({ url }) => {
|
|
258
|
+
* if (!handleUrl(url)) {
|
|
259
|
+
* // Handle other deep links
|
|
260
|
+
* }
|
|
261
|
+
* })
|
|
262
|
+
* return () => sub.remove()
|
|
263
|
+
* }, [handleUrl])
|
|
264
|
+
*
|
|
265
|
+
* return <YourApp />
|
|
266
|
+
* }
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
interface UseEdgeLinkHandlerConfig {
|
|
270
|
+
/** Your redirect URI prefix */
|
|
271
|
+
redirectUri: string;
|
|
272
|
+
/** Called on successful auth */
|
|
273
|
+
onSuccess: (result: EdgeLinkSuccess) => void;
|
|
274
|
+
/** Called on error or user exit */
|
|
275
|
+
onError?: (error: {
|
|
276
|
+
code: string;
|
|
277
|
+
message: string;
|
|
278
|
+
}) => void;
|
|
279
|
+
}
|
|
280
|
+
interface UseEdgeLinkHandlerReturn {
|
|
281
|
+
/** Process a URL. Returns true if it was an EdgeLink callback. */
|
|
282
|
+
handleUrl: (url: string) => boolean;
|
|
283
|
+
}
|
|
284
|
+
declare function useEdgeLinkHandler(config: UseEdgeLinkHandlerConfig): UseEdgeLinkHandlerReturn;
|
|
285
|
+
/**
|
|
286
|
+
* Hook to listen to EdgeLink events for analytics.
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```tsx
|
|
290
|
+
* function MyComponent() {
|
|
291
|
+
* useEdgeLinkEvents((event) => {
|
|
292
|
+
* analytics.track('edge_link_event', {
|
|
293
|
+
* name: event.eventName,
|
|
294
|
+
* timestamp: event.timestamp,
|
|
295
|
+
* ...event.metadata,
|
|
296
|
+
* })
|
|
297
|
+
* })
|
|
298
|
+
*
|
|
299
|
+
* // ...
|
|
300
|
+
* }
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
declare function useEdgeLinkEvents(onEvent: (event: EdgeLinkEvent) => void, deps?: React.DependencyList): void;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* PKCE (Proof Key for Code Exchange) Utilities for React Native
|
|
307
|
+
*
|
|
308
|
+
* React Native doesn't have Web Crypto API natively, so we use a
|
|
309
|
+
* compatible implementation that works across platforms.
|
|
310
|
+
*
|
|
311
|
+
* For production apps, consider installing:
|
|
312
|
+
* - `react-native-get-random-values` (polyfill for crypto.getRandomValues)
|
|
313
|
+
* - `expo-crypto` (if using Expo)
|
|
314
|
+
*
|
|
315
|
+
* @module @edge-markets/connect-react-native/pkce
|
|
316
|
+
*/
|
|
317
|
+
/**
|
|
318
|
+
* A PKCE code verifier and challenge pair.
|
|
319
|
+
*/
|
|
320
|
+
interface PKCEPair {
|
|
321
|
+
/** High-entropy random string (43-128 characters). Keep secret until token exchange. */
|
|
322
|
+
verifier: string;
|
|
323
|
+
/** SHA-256 hash of verifier, base64url encoded. Include in authorization URL. */
|
|
324
|
+
challenge: string;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Generates a PKCE code verifier and challenge pair.
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* ```typescript
|
|
331
|
+
* const pkce = await generatePKCE()
|
|
332
|
+
*
|
|
333
|
+
* // Include challenge in authorization URL
|
|
334
|
+
* const authUrl = `https://auth.example.com/authorize?code_challenge=${pkce.challenge}&code_challenge_method=S256`
|
|
335
|
+
*
|
|
336
|
+
* // Later, include verifier in token exchange
|
|
337
|
+
* const tokens = await exchangeCode(code, pkce.verifier)
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
declare function generatePKCE(): Promise<PKCEPair>;
|
|
341
|
+
/**
|
|
342
|
+
* Generates a random state parameter for CSRF protection.
|
|
343
|
+
*
|
|
344
|
+
* @returns 64-character hex string (32 bytes of entropy)
|
|
345
|
+
*/
|
|
346
|
+
declare function generateState(): string;
|
|
347
|
+
/**
|
|
348
|
+
* Checks if secure crypto is available.
|
|
349
|
+
*
|
|
350
|
+
* Returns false if only Math.random fallback is being used.
|
|
351
|
+
* Use this to warn users in development.
|
|
352
|
+
*/
|
|
353
|
+
declare function isSecureCryptoAvailable(): boolean;
|
|
354
|
+
|
|
355
|
+
export { EdgeLink, type EdgeLinkConfig, type EdgeLinkEvent, type EdgeLinkEventName, type PKCEPair, type UseEdgeLinkConfig, type UseEdgeLinkHandlerConfig, type UseEdgeLinkHandlerReturn, type UseEdgeLinkReturn, generatePKCE, generateState, isSecureCryptoAvailable, useEdgeLink, useEdgeLinkEvents, useEdgeLinkHandler };
|