@oobit/react-native-sdk 1.0.7 → 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/README.md +177 -377
- package/dist/WidgetSDK.d.ts +3 -0
- package/dist/WidgetSDK.d.ts.map +1 -1
- package/dist/WidgetSDK.js +39 -30
- package/dist/WidgetSDK.js.map +1 -1
- package/dist/index.d.ts +6 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -17
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +41 -15
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/WidgetSDK.tsx +45 -33
- package/src/index.ts +27 -43
- package/src/types.ts +40 -16
- package/dist/biometricUtils.d.ts +0 -66
- package/dist/biometricUtils.d.ts.map +0 -1
- package/dist/biometricUtils.js +0 -183
- package/dist/biometricUtils.js.map +0 -1
- package/dist/cryptoUtils.d.ts +0 -64
- package/dist/cryptoUtils.d.ts.map +0 -1
- package/dist/cryptoUtils.js +0 -123
- package/dist/cryptoUtils.js.map +0 -1
- package/src/biometricUtils.ts +0 -183
- package/src/cryptoUtils.ts +0 -131
package/README.md
CHANGED
|
@@ -18,9 +18,9 @@ yarn add @oobit/react-native-sdk
|
|
|
18
18
|
Make sure you have these installed:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
npm install react-native-webview expo-linking
|
|
21
|
+
npm install react-native-webview expo-linking expo-clipboard expo-intent-launcher expo-local-authentication
|
|
22
22
|
# or
|
|
23
|
-
yarn add react-native-webview expo-linking
|
|
23
|
+
yarn add react-native-webview expo-linking expo-clipboard expo-intent-launcher expo-local-authentication
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
## Quick Start
|
|
@@ -30,28 +30,23 @@ import { WidgetSDK } from '@oobit/react-native-sdk';
|
|
|
30
30
|
import { View, Alert } from 'react-native';
|
|
31
31
|
|
|
32
32
|
function MyApp() {
|
|
33
|
-
const widgetUrl = 'https://your-widget.com?token=xyz'; // Your authenticated widget URL
|
|
34
|
-
|
|
35
33
|
return (
|
|
36
34
|
<View style={{ flex: 1 }}>
|
|
37
35
|
<WidgetSDK
|
|
38
|
-
|
|
36
|
+
accessToken="your-jwt-token-from-backend"
|
|
37
|
+
userWalletAddress="0x1234...abcd"
|
|
39
38
|
environment="production"
|
|
40
|
-
widgetUrl={widgetUrl}
|
|
41
39
|
onReady={() => {
|
|
42
40
|
console.log('Widget loaded successfully');
|
|
43
41
|
}}
|
|
44
|
-
onCardCreated={(cardId, cardType, last4) => {
|
|
45
|
-
Alert.alert('Success', `Card ${last4} created!`);
|
|
46
|
-
// Save card info, navigate to card details, etc.
|
|
47
|
-
}}
|
|
48
42
|
onError={(code, message) => {
|
|
49
43
|
Alert.alert('Error', message);
|
|
50
|
-
// Log to error tracking, show user-friendly message, etc.
|
|
51
44
|
}}
|
|
52
45
|
onClose={() => {
|
|
53
46
|
console.log('User closed the widget');
|
|
54
|
-
|
|
47
|
+
}}
|
|
48
|
+
onTransactionRequested={(token, amount, address, tag) => {
|
|
49
|
+
console.log(`Send ${amount} ${token.symbol} to ${address}`);
|
|
55
50
|
}}
|
|
56
51
|
/>
|
|
57
52
|
</View>
|
|
@@ -63,105 +58,72 @@ function MyApp() {
|
|
|
63
58
|
|
|
64
59
|
### Required Props
|
|
65
60
|
|
|
66
|
-
#### `
|
|
67
|
-
|
|
61
|
+
#### `accessToken: string`
|
|
62
|
+
JWT access token from your backend for authentication.
|
|
68
63
|
|
|
69
64
|
```typescript
|
|
70
|
-
<WidgetSDK
|
|
65
|
+
<WidgetSDK accessToken="eyJhbGciOiJIUzI1NiIs..." />
|
|
71
66
|
```
|
|
72
67
|
|
|
73
|
-
#### `
|
|
74
|
-
The
|
|
68
|
+
#### `userWalletAddress: string`
|
|
69
|
+
The user's external wallet address for crypto deposits.
|
|
75
70
|
|
|
76
71
|
```typescript
|
|
77
|
-
<WidgetSDK
|
|
72
|
+
<WidgetSDK userWalletAddress="0x1234567890abcdef..." />
|
|
78
73
|
```
|
|
79
74
|
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
### Optional Props
|
|
76
|
+
|
|
77
|
+
#### `environment?: 'development' | 'production'`
|
|
78
|
+
The environment to use. Defaults to `'production'`.
|
|
82
79
|
|
|
83
80
|
```typescript
|
|
84
|
-
<WidgetSDK
|
|
81
|
+
<WidgetSDK environment="development" />
|
|
85
82
|
```
|
|
86
83
|
|
|
87
|
-
|
|
84
|
+
#### `debug?: boolean`
|
|
85
|
+
Enable debug logging to console. Defaults to `false`.
|
|
88
86
|
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
```typescript
|
|
88
|
+
<WidgetSDK debug={true} />
|
|
89
|
+
```
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
- Track analytics events
|
|
95
|
-
- Initialize app state
|
|
91
|
+
#### `loadingIndicatorColor?: string`
|
|
92
|
+
Custom color for the loading indicator. Defaults to `'#007AFF'`.
|
|
96
93
|
|
|
97
94
|
```typescript
|
|
98
|
-
<WidgetSDK
|
|
99
|
-
onReady={() => {
|
|
100
|
-
console.log('Widget is ready!');
|
|
101
|
-
analytics.track('widget_loaded');
|
|
102
|
-
}}
|
|
103
|
-
/>
|
|
95
|
+
<WidgetSDK loadingIndicatorColor="#6200EE" />
|
|
104
96
|
```
|
|
105
97
|
|
|
106
|
-
|
|
107
|
-
Called when a card is successfully created in the widget.
|
|
98
|
+
### Callback Props
|
|
108
99
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
- `cardType` - Type of card: `'virtual'` or `'physical'`
|
|
112
|
-
- `last4` - Last 4 digits of the card number
|
|
113
|
-
|
|
114
|
-
**Use this to:**
|
|
115
|
-
- Save card info to your app's state
|
|
116
|
-
- Navigate to card details screen
|
|
117
|
-
- Show success message to user
|
|
118
|
-
- Trigger analytics events
|
|
119
|
-
- Close the widget
|
|
100
|
+
#### `onReady?: () => void`
|
|
101
|
+
Called when the widget has finished loading and is ready for interaction.
|
|
120
102
|
|
|
121
103
|
```typescript
|
|
122
104
|
<WidgetSDK
|
|
123
|
-
|
|
124
|
-
console.log(
|
|
125
|
-
|
|
126
|
-
// Save to state
|
|
127
|
-
setUserCard({ id: cardId, type: cardType, last4 });
|
|
128
|
-
|
|
129
|
-
// Navigate to card details
|
|
130
|
-
navigation.navigate('CardDetails', { cardId });
|
|
131
|
-
|
|
132
|
-
// Track analytics
|
|
133
|
-
analytics.track('card_created', { cardType });
|
|
105
|
+
onReady={() => {
|
|
106
|
+
console.log('Widget is ready!');
|
|
107
|
+
setIsLoading(false);
|
|
134
108
|
}}
|
|
135
109
|
/>
|
|
136
110
|
```
|
|
137
111
|
|
|
138
|
-
#### `onError?: (code: string, message: string) => void`
|
|
112
|
+
#### `onError?: (code: SDKErrorCode | string, message: string) => void`
|
|
139
113
|
Called when an error occurs in the widget.
|
|
140
114
|
|
|
141
|
-
**
|
|
142
|
-
- `
|
|
143
|
-
- `
|
|
144
|
-
|
|
145
|
-
**Use this to:**
|
|
146
|
-
- Show error alerts to user
|
|
147
|
-
- Log errors to monitoring service
|
|
148
|
-
- Handle specific error cases
|
|
149
|
-
- Retry operations
|
|
115
|
+
**Error Codes:**
|
|
116
|
+
- `TOKEN_EXPIRED` - The access token has expired
|
|
117
|
+
- `PARSE_ERROR` - Failed to parse a message from the widget
|
|
118
|
+
- `WEBVIEW_ERROR` - The WebView failed to load
|
|
150
119
|
|
|
151
120
|
```typescript
|
|
152
121
|
<WidgetSDK
|
|
153
122
|
onError={(code, message) => {
|
|
154
123
|
console.error(`Widget error [${code}]: ${message}`);
|
|
155
124
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// Log to error tracking
|
|
160
|
-
Sentry.captureException(new Error(`Widget error: ${code} - ${message}`));
|
|
161
|
-
|
|
162
|
-
// Handle specific errors
|
|
163
|
-
if (code === 'SESSION_EXPIRED') {
|
|
164
|
-
// Refresh authentication and reload widget
|
|
125
|
+
if (code === 'TOKEN_EXPIRED') {
|
|
126
|
+
// Refresh token and reload
|
|
165
127
|
refreshAuth();
|
|
166
128
|
}
|
|
167
129
|
}}
|
|
@@ -169,135 +131,92 @@ Called when an error occurs in the widget.
|
|
|
169
131
|
```
|
|
170
132
|
|
|
171
133
|
#### `onClose?: () => void`
|
|
172
|
-
Called when the user requests to close the widget
|
|
173
|
-
|
|
174
|
-
**Use this to:**
|
|
175
|
-
- Navigate back to previous screen
|
|
176
|
-
- Clean up state
|
|
177
|
-
- Track analytics events
|
|
178
|
-
- Hide the widget
|
|
134
|
+
Called when the user requests to close the widget.
|
|
179
135
|
|
|
180
136
|
```typescript
|
|
181
137
|
<WidgetSDK
|
|
182
138
|
onClose={() => {
|
|
183
|
-
console.log('User closed widget');
|
|
184
|
-
|
|
185
|
-
// Navigate back
|
|
186
139
|
navigation.goBack();
|
|
140
|
+
}}
|
|
141
|
+
/>
|
|
142
|
+
```
|
|
187
143
|
|
|
188
|
-
|
|
189
|
-
|
|
144
|
+
#### `onTransactionRequested?: (token, cryptoAmount, depositAddress, depositAddressTag) => void`
|
|
145
|
+
Called when a crypto transaction is requested.
|
|
190
146
|
|
|
191
|
-
|
|
192
|
-
|
|
147
|
+
```typescript
|
|
148
|
+
<WidgetSDK
|
|
149
|
+
onTransactionRequested={(token, cryptoAmount, depositAddress, depositAddressTag) => {
|
|
150
|
+
console.log(`Send ${cryptoAmount} ${token.symbol} to ${depositAddress}`);
|
|
151
|
+
// Navigate to your send crypto screen
|
|
152
|
+
navigation.navigate('SendCrypto', {
|
|
153
|
+
token,
|
|
154
|
+
amount: cryptoAmount,
|
|
155
|
+
address: depositAddress,
|
|
156
|
+
tag: depositAddressTag,
|
|
157
|
+
});
|
|
193
158
|
}}
|
|
194
159
|
/>
|
|
195
160
|
```
|
|
196
161
|
|
|
197
|
-
|
|
162
|
+
#### `onLoadingChange?: (isLoading: boolean) => void`
|
|
163
|
+
Called when the loading state changes.
|
|
198
164
|
|
|
199
165
|
```typescript
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
166
|
+
<WidgetSDK
|
|
167
|
+
onLoadingChange={(isLoading) => {
|
|
168
|
+
setShowCustomLoader(isLoading);
|
|
169
|
+
}}
|
|
170
|
+
/>
|
|
171
|
+
```
|
|
203
172
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
});
|
|
218
|
-
const { url } = await response.json();
|
|
219
|
-
|
|
220
|
-
setWidgetUrl(url);
|
|
221
|
-
setShowWidget(true);
|
|
222
|
-
} catch (error) {
|
|
223
|
-
Alert.alert('Error', 'Failed to load widget');
|
|
224
|
-
} finally {
|
|
225
|
-
setIsLoading(false);
|
|
226
|
-
}
|
|
173
|
+
## Ref Methods
|
|
174
|
+
|
|
175
|
+
You can control the widget programmatically using a ref:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { useRef } from 'react';
|
|
179
|
+
import { WidgetSDK, WidgetSDKRef } from '@oobit/react-native-sdk';
|
|
180
|
+
|
|
181
|
+
function MyScreen() {
|
|
182
|
+
const widgetRef = useRef<WidgetSDKRef>(null);
|
|
183
|
+
|
|
184
|
+
const handleBackPress = () => {
|
|
185
|
+
widgetRef.current?.navigateBack();
|
|
227
186
|
};
|
|
228
187
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
<WidgetSDK
|
|
233
|
-
apiKey="your-api-key"
|
|
234
|
-
environment="production"
|
|
235
|
-
widgetUrl={widgetUrl}
|
|
236
|
-
onReady={() => {
|
|
237
|
-
console.log('Widget ready');
|
|
238
|
-
}}
|
|
239
|
-
onCardCreated={(cardId, cardType, last4) => {
|
|
240
|
-
Alert.alert(
|
|
241
|
-
'Card Created!',
|
|
242
|
-
`Your ${cardType} card ending in ${last4} is ready.`,
|
|
243
|
-
[
|
|
244
|
-
{
|
|
245
|
-
text: 'View Card',
|
|
246
|
-
onPress: () => {
|
|
247
|
-
setShowWidget(false);
|
|
248
|
-
// Navigate to card details
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
]
|
|
252
|
-
);
|
|
253
|
-
}}
|
|
254
|
-
onError={(code, message) => {
|
|
255
|
-
Alert.alert('Error', message);
|
|
256
|
-
}}
|
|
257
|
-
onClose={() => {
|
|
258
|
-
setShowWidget(false);
|
|
259
|
-
}}
|
|
260
|
-
/>
|
|
261
|
-
</View>
|
|
262
|
-
);
|
|
263
|
-
}
|
|
188
|
+
const handleRetry = () => {
|
|
189
|
+
widgetRef.current?.reload();
|
|
190
|
+
};
|
|
264
191
|
|
|
265
192
|
return (
|
|
266
|
-
<
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
/>
|
|
272
|
-
{isLoading && <ActivityIndicator style={{ marginTop: 20 }} />}
|
|
273
|
-
</View>
|
|
193
|
+
<WidgetSDK
|
|
194
|
+
ref={widgetRef}
|
|
195
|
+
accessToken={token}
|
|
196
|
+
userWalletAddress={address}
|
|
197
|
+
/>
|
|
274
198
|
);
|
|
275
199
|
}
|
|
276
|
-
|
|
277
|
-
const styles = StyleSheet.create({
|
|
278
|
-
container: {
|
|
279
|
-
flex: 1,
|
|
280
|
-
justifyContent: 'center',
|
|
281
|
-
padding: 20,
|
|
282
|
-
},
|
|
283
|
-
});
|
|
284
200
|
```
|
|
285
201
|
|
|
202
|
+
### Available Methods
|
|
203
|
+
|
|
204
|
+
| Method | Description |
|
|
205
|
+
|--------|-------------|
|
|
206
|
+
| `navigateBack()` | Navigate back within the widget |
|
|
207
|
+
| `reload()` | Reload the widget |
|
|
208
|
+
|
|
286
209
|
## TypeScript Support
|
|
287
210
|
|
|
288
211
|
The SDK is fully typed. Import types as needed:
|
|
289
212
|
|
|
290
213
|
```typescript
|
|
291
|
-
import type {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
onCardCreated: (cardId, cardType, last4) => {
|
|
298
|
-
// Fully typed parameters
|
|
299
|
-
},
|
|
300
|
-
};
|
|
214
|
+
import type {
|
|
215
|
+
WidgetSDKConfig,
|
|
216
|
+
WidgetSDKRef,
|
|
217
|
+
SDKErrorCode,
|
|
218
|
+
DepositToken,
|
|
219
|
+
} from '@oobit/react-native-sdk';
|
|
301
220
|
```
|
|
302
221
|
|
|
303
222
|
### Message Type Constants
|
|
@@ -308,49 +227,14 @@ For advanced use cases where you need to handle messages directly:
|
|
|
308
227
|
import { MessageTypes } from '@oobit/react-native-sdk';
|
|
309
228
|
|
|
310
229
|
// Use constants instead of strings
|
|
311
|
-
if (message.type === MessageTypes.
|
|
312
|
-
// Handle
|
|
230
|
+
if (message.type === MessageTypes.ERROR) {
|
|
231
|
+
// Handle error
|
|
313
232
|
}
|
|
314
233
|
```
|
|
315
234
|
|
|
316
|
-
Available constants:
|
|
317
|
-
|
|
318
|
-
**Widget → Native:**
|
|
319
|
-
- `MessageTypes.READY` - Widget is ready
|
|
320
|
-
- `MessageTypes.CARD_CREATED` - Card was created
|
|
321
|
-
- `MessageTypes.ERROR` - An error occurred
|
|
322
|
-
- `MessageTypes.CLOSE` - User closed widget
|
|
323
|
-
- `MessageTypes.OPEN_WALLET` - User wants to add card to wallet
|
|
324
|
-
|
|
325
|
-
**Native → Widget:**
|
|
326
|
-
- `MessageTypes.PLATFORM_INFO` - Platform info sent to widget
|
|
327
|
-
- `MessageTypes.WALLET_OPENED` - Wallet open result
|
|
328
|
-
- `MessageTypes.BACK_PRESSED` - User pressed back button/gesture
|
|
329
|
-
- `MessageTypes.NAVIGATE_BACK` - Programmatic back navigation
|
|
330
|
-
|
|
331
|
-
## Platform Support
|
|
332
|
-
|
|
333
|
-
| Platform | Supported | Notes |
|
|
334
|
-
|----------|-----------|-------|
|
|
335
|
-
| iOS | ✅ | Includes Apple Wallet integration |
|
|
336
|
-
| Android | ✅ | Includes Google Pay integration |
|
|
337
|
-
| Web | ❌ | React Native only |
|
|
338
|
-
|
|
339
235
|
## Native Wallet Integration
|
|
340
236
|
|
|
341
|
-
The SDK automatically handles adding cards to Apple Wallet (iOS) and Google Pay (Android)
|
|
342
|
-
|
|
343
|
-
**Your widget can trigger wallet addition:**
|
|
344
|
-
```javascript
|
|
345
|
-
// In your web widget
|
|
346
|
-
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
347
|
-
type: 'widget:open-wallet',
|
|
348
|
-
timestamp: Date.now(),
|
|
349
|
-
payload: { platform: 'ios' } // or 'android'
|
|
350
|
-
}));
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
**The SDK handles it automatically** - no callback needed from your app!
|
|
237
|
+
The SDK automatically handles adding cards to Apple Wallet (iOS) and Google Pay (Android).
|
|
354
238
|
|
|
355
239
|
You can also manually open the wallet from your React Native app:
|
|
356
240
|
|
|
@@ -366,84 +250,81 @@ if (available) {
|
|
|
366
250
|
}
|
|
367
251
|
```
|
|
368
252
|
|
|
369
|
-
##
|
|
370
|
-
|
|
371
|
-
The SDK handles back navigation gestures and forwards them to your web widget, allowing the widget to control its own internal navigation.
|
|
372
|
-
|
|
373
|
-
### How It Works
|
|
374
|
-
|
|
375
|
-
1. **Android**: Hardware back button press is intercepted and forwarded to the widget
|
|
376
|
-
2. **iOS**: Swipe-back gestures are enabled via `allowsBackForwardNavigationGestures`
|
|
377
|
-
|
|
378
|
-
The widget receives a `native:back-pressed` message and decides whether to:
|
|
379
|
-
- Navigate back internally (if there's navigation history)
|
|
380
|
-
- Close the widget (if on the first screen)
|
|
253
|
+
## Platform Support
|
|
381
254
|
|
|
382
|
-
|
|
255
|
+
| Platform | Supported | Notes |
|
|
256
|
+
|----------|-----------|-------|
|
|
257
|
+
| iOS | ✅ | Includes Apple Wallet integration |
|
|
258
|
+
| Android | ✅ | Includes Google Pay integration |
|
|
259
|
+
| Web | ❌ | React Native only |
|
|
383
260
|
|
|
384
|
-
|
|
261
|
+
## Complete Example
|
|
385
262
|
|
|
386
263
|
```typescript
|
|
387
|
-
import { useRef } from 'react';
|
|
264
|
+
import React, { useState, useRef } from 'react';
|
|
265
|
+
import { View, Button, StyleSheet, Alert } from 'react-native';
|
|
388
266
|
import { WidgetSDK, WidgetSDKRef } from '@oobit/react-native-sdk';
|
|
389
267
|
|
|
390
|
-
function
|
|
268
|
+
export default function PaymentScreen() {
|
|
269
|
+
const [showWidget, setShowWidget] = useState(false);
|
|
391
270
|
const widgetRef = useRef<WidgetSDKRef>(null);
|
|
392
271
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
272
|
+
if (showWidget) {
|
|
273
|
+
return (
|
|
274
|
+
<View style={styles.container}>
|
|
275
|
+
<WidgetSDK
|
|
276
|
+
ref={widgetRef}
|
|
277
|
+
accessToken="your-jwt-token"
|
|
278
|
+
userWalletAddress="0x1234..."
|
|
279
|
+
environment="production"
|
|
280
|
+
debug={__DEV__}
|
|
281
|
+
onReady={() => {
|
|
282
|
+
console.log('Widget ready');
|
|
283
|
+
}}
|
|
284
|
+
onError={(code, message) => {
|
|
285
|
+
Alert.alert('Error', `${code}: ${message}`);
|
|
286
|
+
if (code === 'WEBVIEW_ERROR') {
|
|
287
|
+
// Offer retry option
|
|
288
|
+
Alert.alert('Error', 'Failed to load. Retry?', [
|
|
289
|
+
{ text: 'Cancel', onPress: () => setShowWidget(false) },
|
|
290
|
+
{ text: 'Retry', onPress: () => widgetRef.current?.reload() },
|
|
291
|
+
]);
|
|
292
|
+
}
|
|
293
|
+
}}
|
|
294
|
+
onClose={() => {
|
|
295
|
+
setShowWidget(false);
|
|
296
|
+
}}
|
|
297
|
+
onTransactionRequested={(token, amount, address, tag) => {
|
|
298
|
+
Alert.alert(
|
|
299
|
+
'Transaction Requested',
|
|
300
|
+
`Send ${amount} ${token.symbol} to ${address}`,
|
|
301
|
+
);
|
|
302
|
+
}}
|
|
303
|
+
onLoadingChange={(isLoading) => {
|
|
304
|
+
console.log('Loading:', isLoading);
|
|
305
|
+
}}
|
|
306
|
+
/>
|
|
307
|
+
</View>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
397
310
|
|
|
398
311
|
return (
|
|
399
|
-
<
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
312
|
+
<View style={styles.container}>
|
|
313
|
+
<Button
|
|
314
|
+
title="Open Payment Widget"
|
|
315
|
+
onPress={() => setShowWidget(true)}
|
|
316
|
+
/>
|
|
317
|
+
</View>
|
|
405
318
|
);
|
|
406
319
|
}
|
|
407
|
-
```
|
|
408
320
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
```javascript
|
|
414
|
-
// In your web widget
|
|
415
|
-
window.addEventListener('message', (event) => {
|
|
416
|
-
try {
|
|
417
|
-
const message = JSON.parse(event.data);
|
|
418
|
-
|
|
419
|
-
if (message.type === 'native:back-pressed' || message.type === 'native:navigate-back') {
|
|
420
|
-
// Check if on first screen (login, etc.)
|
|
421
|
-
if (isOnFirstScreen()) {
|
|
422
|
-
// Close widget - return to native app
|
|
423
|
-
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
424
|
-
type: 'widget:close',
|
|
425
|
-
timestamp: Date.now()
|
|
426
|
-
}));
|
|
427
|
-
} else {
|
|
428
|
-
// Navigate back internally
|
|
429
|
-
window.history.back();
|
|
430
|
-
// OR: router.back() if using React Router
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
} catch (e) {
|
|
434
|
-
// Ignore non-JSON messages
|
|
435
|
-
}
|
|
321
|
+
const styles = StyleSheet.create({
|
|
322
|
+
container: {
|
|
323
|
+
flex: 1,
|
|
324
|
+
},
|
|
436
325
|
});
|
|
437
326
|
```
|
|
438
327
|
|
|
439
|
-
### Message Types
|
|
440
|
-
|
|
441
|
-
| Message | Direction | Description |
|
|
442
|
-
|---------|-----------|-------------|
|
|
443
|
-
| `native:back-pressed` | Native → Widget | User pressed back button/gesture |
|
|
444
|
-
| `native:navigate-back` | Native → Widget | Programmatic back navigation request |
|
|
445
|
-
| `widget:close` | Widget → Native | Widget requests to close |
|
|
446
|
-
|
|
447
328
|
## Dependencies
|
|
448
329
|
|
|
449
330
|
### Required Peer Dependencies
|
|
@@ -453,116 +334,35 @@ window.addEventListener('message', (event) => {
|
|
|
453
334
|
"react": ">=18.0.0",
|
|
454
335
|
"react-native": ">=0.70.0",
|
|
455
336
|
"react-native-webview": ">=13.0.0",
|
|
456
|
-
"expo-linking": ">=6.0.0"
|
|
337
|
+
"expo-linking": ">=6.0.0",
|
|
338
|
+
"expo-clipboard": ">=5.0.0",
|
|
339
|
+
"expo-intent-launcher": ">=6.0.0",
|
|
340
|
+
"expo-local-authentication": ">=14.0.0"
|
|
457
341
|
}
|
|
458
342
|
```
|
|
459
343
|
|
|
460
|
-
### Optional Dependencies
|
|
461
|
-
|
|
462
|
-
For full wallet integration on Android:
|
|
463
|
-
- `expo-intent-launcher` - Android wallet integration
|
|
464
|
-
|
|
465
|
-
Install with:
|
|
466
|
-
```bash
|
|
467
|
-
expo install expo-intent-launcher
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
## Common Use Cases
|
|
471
|
-
|
|
472
|
-
### Show Widget After Authentication
|
|
473
|
-
|
|
474
|
-
```typescript
|
|
475
|
-
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
476
|
-
const [widgetUrl, setWidgetUrl] = useState('');
|
|
477
|
-
|
|
478
|
-
useEffect(() => {
|
|
479
|
-
if (isAuthenticated) {
|
|
480
|
-
fetchWidgetUrl().then(setWidgetUrl);
|
|
481
|
-
}
|
|
482
|
-
}, [isAuthenticated]);
|
|
483
|
-
|
|
484
|
-
if (!isAuthenticated) {
|
|
485
|
-
return <LoginScreen onLogin={() => setIsAuthenticated(true)} />;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
return <WidgetSDK widgetUrl={widgetUrl} {...props} />;
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
### Close Widget and Navigate
|
|
492
|
-
|
|
493
|
-
```typescript
|
|
494
|
-
<WidgetSDK
|
|
495
|
-
onCardCreated={(cardId) => {
|
|
496
|
-
// Navigate to card details immediately
|
|
497
|
-
navigation.replace('CardDetails', { cardId });
|
|
498
|
-
}}
|
|
499
|
-
onClose={() => {
|
|
500
|
-
// Go back to previous screen
|
|
501
|
-
navigation.goBack();
|
|
502
|
-
}}
|
|
503
|
-
/>
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
### Track Analytics
|
|
507
|
-
|
|
508
|
-
```typescript
|
|
509
|
-
<WidgetSDK
|
|
510
|
-
onReady={() => {
|
|
511
|
-
analytics.track('widget_viewed');
|
|
512
|
-
}}
|
|
513
|
-
onCardCreated={(cardId, cardType) => {
|
|
514
|
-
analytics.track('card_created', {
|
|
515
|
-
cardId,
|
|
516
|
-
cardType,
|
|
517
|
-
source: 'mobile_app'
|
|
518
|
-
});
|
|
519
|
-
}}
|
|
520
|
-
onError={(code, message) => {
|
|
521
|
-
analytics.track('widget_error', { code, message });
|
|
522
|
-
}}
|
|
523
|
-
/>
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
### Show Loading State
|
|
527
|
-
|
|
528
|
-
```typescript
|
|
529
|
-
const [isWidgetReady, setIsWidgetReady] = useState(false);
|
|
530
|
-
|
|
531
|
-
<View style={{ flex: 1 }}>
|
|
532
|
-
{!isWidgetReady && (
|
|
533
|
-
<View style={styles.loadingOverlay}>
|
|
534
|
-
<ActivityIndicator size="large" />
|
|
535
|
-
<Text>Loading widget...</Text>
|
|
536
|
-
</View>
|
|
537
|
-
)}
|
|
538
|
-
<WidgetSDK
|
|
539
|
-
{...props}
|
|
540
|
-
onReady={() => setIsWidgetReady(true)}
|
|
541
|
-
/>
|
|
542
|
-
</View>
|
|
543
|
-
```
|
|
544
|
-
|
|
545
344
|
## Troubleshooting
|
|
546
345
|
|
|
547
346
|
### Widget Not Loading
|
|
548
347
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
4. Verify `react-native-webview` is installed
|
|
348
|
+
1. Verify `accessToken` is valid and not expired
|
|
349
|
+
2. Check that `userWalletAddress` is a valid address
|
|
350
|
+
3. Enable `debug={true}` to see console logs
|
|
351
|
+
4. Verify all peer dependencies are installed
|
|
554
352
|
|
|
555
353
|
### Callbacks Not Firing
|
|
556
354
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
355
|
+
1. Enable debug mode to see message logs
|
|
356
|
+
2. Check that your widget is sending proper postMessage events
|
|
357
|
+
3. Verify message format matches expected structure
|
|
358
|
+
|
|
359
|
+
### Error: TOKEN_EXPIRED
|
|
360
|
+
|
|
361
|
+
The access token has expired. Fetch a new token from your backend and re-render the widget with the new token.
|
|
561
362
|
|
|
562
|
-
###
|
|
363
|
+
### Error: WEBVIEW_ERROR
|
|
563
364
|
|
|
564
|
-
|
|
565
|
-
**Android:** Ensure Google Play Services is configured on the device/emulator
|
|
365
|
+
The WebView failed to load. This could be a network issue. Use `widgetRef.current?.reload()` to retry.
|
|
566
366
|
|
|
567
367
|
## License
|
|
568
368
|
|