@oobit/react-native-sdk 2.0.2 → 3.0.1

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/src/WidgetSDK.tsx CHANGED
@@ -21,26 +21,17 @@ import {
21
21
  } from "react-native";
22
22
  import { WebView, WebViewMessageEvent } from "react-native-webview";
23
23
  import { getWidgetUrl, stripTokenPrefix } from "./config";
24
- import { CopyToClipboardMessage, WidgetMessage, WidgetSDKConfig } from "./types";
24
+ import { WidgetMessage, WidgetSDKConfig } from "./types";
25
25
  import { isWalletAvailable, openNativeWallet } from "./walletUtils";
26
- import * as Clipboard from "expo-clipboard";
27
26
 
28
27
  export interface WidgetSDKRef {
29
- /** Navigate back within the widget */
30
28
  navigateBack: () => void;
31
- /** Reload the widget */
32
29
  reload: () => void;
33
30
  }
34
31
 
35
32
  export const WidgetSDK = forwardRef<WidgetSDKRef, WidgetSDKConfig>(
36
33
  (
37
- {
38
- accessToken,
39
- userWalletAddress,
40
- onError,
41
- onClose,
42
- onTransactionRequested,
43
- },
34
+ { accessToken, userWalletAddress, onClose, onTransactionRequested },
44
35
  ref
45
36
  ) => {
46
37
  const webViewRef = useRef<WebView>(null);
@@ -48,37 +39,23 @@ export const WidgetSDK = forwardRef<WidgetSDKRef, WidgetSDKConfig>(
48
39
  const [isLoading, setIsLoading] = useState(true);
49
40
  const hasLoadedOnce = useRef(false);
50
41
 
51
- // Check wallet availability on mount
52
42
  useEffect(() => {
53
- checkWalletAvailability();
43
+ isWalletAvailable().then(setWalletAvailable);
54
44
  }, []);
55
45
 
56
- // Handle Android hardware back button
57
46
  useEffect(() => {
58
47
  const backHandler = BackHandler.addEventListener(
59
48
  "hardwareBackPress",
60
49
  () => {
61
- // Send back pressed message to widget
62
- sendMessageToWidget({
63
- type: "native:back-pressed",
64
- timestamp: Date.now(),
65
- });
66
- // Return true to prevent default back behavior
50
+ sendMessageToWidget({ type: "native:back-pressed" });
67
51
  return true;
68
52
  }
69
53
  );
70
-
71
54
  return () => backHandler.remove();
72
55
  }, []);
73
56
 
74
- // Expose methods via ref
75
57
  useImperativeHandle(ref, () => ({
76
- navigateBack: () => {
77
- sendMessageToWidget({
78
- type: "native:navigate-back",
79
- timestamp: Date.now(),
80
- });
81
- },
58
+ navigateBack: () => sendMessageToWidget({ type: "native:navigate-back" }),
82
59
  reload: () => {
83
60
  setIsLoading(true);
84
61
  hasLoadedOnce.current = false;
@@ -86,29 +63,25 @@ export const WidgetSDK = forwardRef<WidgetSDKRef, WidgetSDKConfig>(
86
63
  },
87
64
  }));
88
65
 
89
- const checkWalletAvailability = async () => {
90
- const available = await isWalletAvailable();
91
- setWalletAvailable(available);
92
- };
93
-
94
- /**
95
- * Handle messages from the web widget
96
- */
97
66
  const handleMessage = (event: WebViewMessageEvent) => {
98
67
  try {
99
68
  const message: WidgetMessage = JSON.parse(event.nativeEvent.data);
100
69
 
101
70
  switch (message.type) {
102
71
  case "widget:ready":
103
- handleReady();
72
+ sendMessageToWidget({
73
+ type: "native:platform-info",
74
+ payload: { platform: Platform.OS, walletAvailable },
75
+ });
104
76
  break;
105
77
 
106
78
  case "widget:open-wallet":
107
- handleOpenWallet();
108
- break;
109
-
110
- case "widget:error":
111
- handleError(message);
79
+ openNativeWallet().then((success) => {
80
+ sendMessageToWidget({
81
+ type: "native:wallet-opened",
82
+ payload: { success },
83
+ });
84
+ });
112
85
  break;
113
86
 
114
87
  case "widget:close":
@@ -116,111 +89,27 @@ export const WidgetSDK = forwardRef<WidgetSDKRef, WidgetSDKConfig>(
116
89
  break;
117
90
 
118
91
  case "widget:transaction-requested":
119
- handleTransactionRequested(message);
120
- break;
121
-
122
- case "widget:token-expired":
123
- onError?.("TOKEN_EXPIRED", "Access token has expired");
124
- break;
125
-
126
- case "widget:copy-to-clipboard":
127
- handleCopyToClipboard(message as CopyToClipboardMessage);
92
+ onTransactionRequested(message.payload);
128
93
  break;
129
94
  }
130
95
  } catch {
131
- onError?.("PARSE_ERROR", "Failed to parse widget message");
96
+ // Ignore parse errors
132
97
  }
133
98
  };
134
99
 
135
- const handleReady = () => {
136
- // Send platform info to widget
137
- sendMessageToWidget({
138
- type: "native:platform-info",
139
- payload: {
140
- platform: Platform.OS,
141
- walletAvailable,
142
- },
143
- });
144
- };
145
-
146
- const handleOpenWallet = async () => {
147
- const success = await openNativeWallet();
148
-
149
- // Notify widget of result
150
- sendMessageToWidget({
151
- type: "native:wallet-opened",
152
- payload: { success },
153
- });
154
- };
155
-
156
- const handleError = (message: WidgetMessage) => {
157
- if (message.type !== "widget:error") return;
158
-
159
- const { code, message: errorMessage } = message.payload;
160
- onError?.(code, errorMessage);
161
- };
162
-
163
- const handleTransactionRequested = (message: WidgetMessage) => {
164
- if (message.type !== "widget:transaction-requested") return;
165
-
166
- const { token, cryptoAmount, depositAddress, depositAddressTag } =
167
- message.payload;
168
-
169
- onTransactionRequested(
170
- token,
171
- cryptoAmount,
172
- depositAddress,
173
- depositAddressTag
174
- );
175
- };
176
-
177
- /**
178
- * Handle clipboard copy request from widget
179
- */
180
- const handleCopyToClipboard = async (message: CopyToClipboardMessage) => {
181
- const { text, label } = message.payload;
182
-
183
- try {
184
- await Clipboard.setStringAsync(text);
185
- sendMessageToWidget({
186
- type: "native:clipboard-result",
187
- payload: { success: true, label },
188
- });
189
- } catch {
190
- sendMessageToWidget({
191
- type: "native:clipboard-result",
192
- payload: {
193
- success: false,
194
- label,
195
- error: "Clipboard operation failed",
196
- },
197
- });
198
- }
199
- };
200
-
201
- /**
202
- * Send message to the web widget
203
- */
204
100
  const sendMessageToWidget = (message: unknown) => {
205
- const js = `
206
- window.postMessage(${JSON.stringify(message)}, '*');
207
- true;
208
- `;
209
-
210
- webViewRef.current?.injectJavaScript(js);
101
+ webViewRef.current?.injectJavaScript(
102
+ `window.postMessage(${JSON.stringify(message)}, '*'); true;`
103
+ );
211
104
  };
212
105
 
213
- /**
214
- * Build the widget URL with query parameters
215
- */
216
106
  const finalWidgetUrl = useMemo(() => {
217
107
  const baseUrl = getWidgetUrl(accessToken);
218
108
  const params = new URLSearchParams({
219
109
  token: stripTokenPrefix(accessToken),
220
110
  platform: Platform.OS,
221
- userWalletAddress: userWalletAddress,
111
+ userWalletAddress,
222
112
  });
223
-
224
113
  return `${baseUrl}?${params.toString()}`;
225
114
  }, [accessToken, userWalletAddress]);
226
115
 
@@ -237,36 +126,24 @@ export const WidgetSDK = forwardRef<WidgetSDKRef, WidgetSDKConfig>(
237
126
  setIsLoading(false);
238
127
  }
239
128
  }}
240
- onError={() => {
241
- setIsLoading(false);
242
- onError?.("WEBVIEW_ERROR", "Failed to load widget");
243
- }}
244
- // Allow external URLs to open in native apps
245
129
  onShouldStartLoadWithRequest={(request) => {
246
130
  const { url } = request;
247
-
248
- // Allow wallet URLs to open in native apps
249
131
  if (
250
- url.startsWith("shoebox://") || // Apple Wallet
251
- url.includes("pay.google.com") || // Google Wallet
132
+ url.startsWith("shoebox://") ||
133
+ url.includes("pay.google.com") ||
252
134
  url.includes("wallet.google.com") ||
253
135
  url.includes("wallet.apple.com")
254
136
  ) {
255
137
  Linking.openURL(url).catch(() => {});
256
- return false; // Don't load in WebView
138
+ return false;
257
139
  }
258
-
259
- // Allow normal widget navigation
260
140
  return true;
261
141
  }}
262
- // Security settings
263
142
  javaScriptEnabled={true}
264
143
  domStorageEnabled={true}
265
144
  allowsInlineMediaPlayback={true}
266
- // iOS specific
267
145
  bounces={false}
268
146
  allowsBackForwardNavigationGestures={true}
269
- // Android specific
270
147
  mixedContentMode="always"
271
148
  />
272
149
  {isLoading && (
@@ -280,12 +157,8 @@ export const WidgetSDK = forwardRef<WidgetSDKRef, WidgetSDKConfig>(
280
157
  );
281
158
 
282
159
  const styles = StyleSheet.create({
283
- container: {
284
- flex: 1,
285
- },
286
- webview: {
287
- flex: 1,
288
- },
160
+ container: { flex: 1 },
161
+ webview: { flex: 1 },
289
162
  loadingOverlay: {
290
163
  ...StyleSheet.absoluteFillObject,
291
164
  backgroundColor: "rgba(255, 255, 255, 0.9)",
package/src/index.ts CHANGED
@@ -1,12 +1,10 @@
1
- /**
2
- * Widget SDK - Public API
3
- */
4
-
5
- export { WidgetSDK } from "./WidgetSDK";
6
- export type { WidgetSDKRef } from "./WidgetSDK";
7
-
8
1
  export type {
9
- DepositToken,
10
- SDKErrorCode,
2
+ EvmTransactionData,
3
+ EvmTransactionRequest,
4
+ SolanaTransactionRequest,
5
+ TransactionRequest,
6
+ TransactionTokenMetadata,
11
7
  WidgetSDKConfig,
12
8
  } from "./types";
9
+ export { WidgetSDK } from "./WidgetSDK";
10
+ export type { WidgetSDKRef } from "./WidgetSDK";
package/src/types.ts CHANGED
@@ -1,210 +1,72 @@
1
1
  /**
2
- * Widget SDK Types
3
- * Messages that can be sent between the web widget and React Native
2
+ * Token metadata included with transaction requests
4
3
  */
5
-
6
- /**
7
- * Token information for transaction confirmation
8
- * Minimal fields needed to display a send transaction confirmation page
9
- */
10
- export interface DepositToken {
4
+ export interface TransactionTokenMetadata {
11
5
  symbol: string;
12
- name: string;
13
- networkName: string;
14
- chainId: number;
6
+ amount: string;
7
+ decimals: number;
15
8
  }
16
9
 
17
- export type WidgetMessageType =
18
- | "widget:ready"
19
- | "widget:open-wallet"
20
- | "widget:error"
21
- | "widget:close"
22
- | "widget:transaction-requested"
23
- | "widget:token-expired"
24
- | "widget:request-card-details-session"
25
- | "widget:copy-to-clipboard";
26
-
27
10
  /**
28
- * Native Message Types
29
- * Messages sent from the native app to the web widget
11
+ * EVM transaction data
30
12
  */
31
- export type NativeMessageType =
32
- | "native:platform-info"
33
- | "native:wallet-opened"
34
- | "native:back-pressed"
35
- | "native:navigate-back"
36
- | "native:card-details-session"
37
- | "native:biometric-failed"
38
- | "native:clipboard-result";
39
-
40
- export interface NativeBackPressedMessage {
41
- type: "native:back-pressed";
42
- timestamp: number;
43
- }
44
-
45
- export interface NativeNavigateBackMessage {
46
- type: "native:navigate-back";
47
- timestamp: number;
48
- }
49
-
50
- export interface NativePlatformInfoMessage {
51
- type: "native:platform-info";
52
- payload: {
53
- platform: "ios" | "android";
54
- walletAvailable: boolean;
55
- };
56
- }
57
-
58
- export interface NativeWalletOpenedMessage {
59
- type: "native:wallet-opened";
60
- payload: {
61
- success: boolean;
62
- };
63
- }
64
-
65
- export interface NativeCardDetailsSessionMessage {
66
- type: "native:card-details-session";
67
- payload: {
68
- sessionId: string; // RSA-encrypted session ID (Base64)
69
- secretKey: string; // Secret key in hex format (32 chars)
70
- };
71
- }
72
-
73
- export interface NativeBiometricFailedMessage {
74
- type: "native:biometric-failed";
75
- payload: {
76
- reason: "cancelled" | "failed" | "not_available" | "not_enrolled";
77
- message?: string;
78
- };
79
- }
80
-
81
- export interface NativeClipboardResultMessage {
82
- type: "native:clipboard-result";
83
- payload: {
84
- success: boolean;
85
- label?: string; // Echo back the label for correlation
86
- error?: string;
87
- };
88
- }
89
-
90
- export type NativeMessage =
91
- | NativeBackPressedMessage
92
- | NativeNavigateBackMessage
93
- | NativePlatformInfoMessage
94
- | NativeWalletOpenedMessage
95
- | NativeCardDetailsSessionMessage
96
- | NativeBiometricFailedMessage
97
- | NativeClipboardResultMessage;
98
-
99
- export interface BaseWidgetMessage {
100
- type: WidgetMessageType;
101
- timestamp: number;
102
- }
103
-
104
- export interface WidgetReadyMessage extends BaseWidgetMessage {
105
- type: "widget:ready";
106
- }
107
-
108
- export interface OpenWalletMessage extends BaseWidgetMessage {
109
- type: "widget:open-wallet";
110
- payload: {
111
- platform: "ios" | "android";
112
- };
113
- }
114
-
115
- export interface WidgetErrorMessage extends BaseWidgetMessage {
116
- type: "widget:error";
117
- payload: {
118
- code: string;
119
- message: string;
120
- };
121
- }
122
-
123
- export interface WidgetCloseMessage extends BaseWidgetMessage {
124
- type: "widget:close";
125
- }
126
-
127
- export interface TransactionRequestedMessage extends BaseWidgetMessage {
128
- type: "widget:transaction-requested";
129
- payload: {
130
- token: DepositToken;
131
- cryptoAmount: string;
132
- depositAddress: string;
133
- depositAddressTag: string | null;
134
- };
135
- }
136
-
137
- export interface TokenExpiredMessage extends BaseWidgetMessage {
138
- type: "widget:token-expired";
139
- payload?: {
140
- reason?: string;
141
- };
13
+ export interface EvmTransactionData {
14
+ to: string;
15
+ data: string;
16
+ value: string;
142
17
  }
143
18
 
144
- export interface RequestCardDetailsSessionMessage extends BaseWidgetMessage {
145
- type: "widget:request-card-details-session";
146
- payload: {
147
- publicKey: string; // RSA public key in PEM format
148
- };
19
+ /**
20
+ * EVM transaction request payload
21
+ */
22
+ export interface EvmTransactionRequest {
23
+ type: "evm";
24
+ chainId: number;
25
+ transaction: EvmTransactionData;
26
+ tokenMetadata: TransactionTokenMetadata;
149
27
  }
150
28
 
151
- export interface CopyToClipboardMessage extends BaseWidgetMessage {
152
- type: "widget:copy-to-clipboard";
153
- payload: {
154
- text: string;
155
- label?: string; // Optional: human-readable label for analytics/logging (e.g., "Card Number", "Deposit Address")
156
- };
29
+ /**
30
+ * Solana transaction request payload
31
+ */
32
+ export interface SolanaTransactionRequest {
33
+ type: "solana";
34
+ transaction: string;
35
+ tokenMetadata: TransactionTokenMetadata;
157
36
  }
158
37
 
159
- export type WidgetMessage =
160
- | WidgetReadyMessage
161
- | OpenWalletMessage
162
- | WidgetErrorMessage
163
- | WidgetCloseMessage
164
- | TransactionRequestedMessage
165
- | TokenExpiredMessage
166
- | RequestCardDetailsSessionMessage
167
- | CopyToClipboardMessage;
168
-
169
38
  /**
170
- * Known SDK error codes
171
- * - TOKEN_EXPIRED: The access token has expired
172
- * - PARSE_ERROR: Failed to parse a message from the widget
173
- * - WEBVIEW_ERROR: The WebView failed to load
39
+ * Transaction request payload
174
40
  */
175
- export type SDKErrorCode = "TOKEN_EXPIRED" | "PARSE_ERROR" | "WEBVIEW_ERROR";
41
+ export type TransactionRequest =
42
+ | EvmTransactionRequest
43
+ | SolanaTransactionRequest;
176
44
 
177
45
  /**
178
46
  * SDK Configuration
179
47
  */
180
48
  export interface WidgetSDKConfig {
181
- accessToken: string; // Required: Access token from backend (prefixed with sbx_ or prod_)
182
- userWalletAddress: string; // Required: User's external wallet address for crypto deposits
183
- /**
184
- * Called when an error occurs
185
- */
186
- onError?: (code: SDKErrorCode | string, message: string) => void;
187
- /**
188
- * Called when the widget requests to close
189
- */
49
+ accessToken: string;
50
+ userWalletAddress: string;
190
51
  onClose?: () => void;
191
- /**
192
- * Called when a crypto transaction is requested
193
- */
194
- onTransactionRequested: (
195
- token: DepositToken,
196
- cryptoAmount: string,
197
- depositAddress: string,
198
- depositAddressTag: string | null
199
- ) => void;
52
+ onTransactionRequested: (transaction: TransactionRequest) => void;
200
53
  }
201
54
 
202
55
  /**
203
- * Platform-specific wallet URLs
56
+ * Internal: Messages from widget
57
+ */
58
+ export type WidgetMessage =
59
+ | { type: "widget:ready" }
60
+ | { type: "widget:open-wallet" }
61
+ | { type: "widget:close" }
62
+ | { type: "widget:transaction-requested"; payload: TransactionRequest };
63
+
64
+ /**
65
+ * Internal: Wallet URLs
204
66
  */
205
67
  export const WALLET_URLS = {
206
68
  ios: {
207
- passkit: "shoebox://", // Apple Wallet deep link
69
+ passkit: "shoebox://",
208
70
  fallback: "https://wallet.apple.com",
209
71
  },
210
72
  android: {
@@ -212,30 +74,3 @@ export const WALLET_URLS = {
212
74
  fallback: "https://wallet.google.com",
213
75
  },
214
76
  } as const;
215
-
216
- /**
217
- * Message type constants that clients can use
218
- * @example
219
- * if (message.type === MessageTypes.READY) {
220
- * // Handle ready
221
- * }
222
- */
223
- export const MessageTypes = {
224
- // Widget → Native
225
- READY: "widget:ready",
226
- OPEN_WALLET: "widget:open-wallet",
227
- ERROR: "widget:error",
228
- CLOSE: "widget:close",
229
- TRANSACTION_REQUESTED: "widget:transaction-requested",
230
- TOKEN_EXPIRED: "widget:token-expired",
231
- REQUEST_CARD_DETAILS_SESSION: "widget:request-card-details-session",
232
- COPY_TO_CLIPBOARD: "widget:copy-to-clipboard",
233
- // Native → Widget
234
- PLATFORM_INFO: "native:platform-info",
235
- WALLET_OPENED: "native:wallet-opened",
236
- BACK_PRESSED: "native:back-pressed",
237
- NAVIGATE_BACK: "native:navigate-back",
238
- CARD_DETAILS_SESSION: "native:card-details-session",
239
- BIOMETRIC_FAILED: "native:biometric-failed",
240
- CLIPBOARD_RESULT: "native:clipboard-result",
241
- } as const;
@@ -3,7 +3,6 @@
3
3
  * Handles opening Apple Wallet (iOS) and Google Wallet (Android)
4
4
  */
5
5
 
6
- import * as IntentLauncher from "expo-intent-launcher";
7
6
  import { Alert, Linking, Platform } from "react-native";
8
7
  import { WALLET_URLS } from "./types";
9
8
 
@@ -84,7 +83,7 @@ const openAppleWallet = async (): Promise<boolean> => {
84
83
 
85
84
  /**
86
85
  * Opens Google Wallet on Android
87
- * Matches the proven Android native implementation from LinksUtils.kt
86
+ * Uses Linking API to launch the app or fall back to Play Store
88
87
  */
89
88
  const openGoogleWallet = async (): Promise<boolean> => {
90
89
  const GOOGLE_WALLET_PACKAGE = "com.google.android.apps.walletnfcrel";
@@ -92,8 +91,7 @@ const openGoogleWallet = async (): Promise<boolean> => {
92
91
  try {
93
92
  console.log("Attempting to open Google Wallet...");
94
93
 
95
- // First, try using the market:// scheme to just launch the app if it's installed
96
- // This is the most reliable way to open an installed app without knowing the exact activity
94
+ // Try using the market:// scheme to launch the app if it's installed
97
95
  try {
98
96
  const canOpen = await Linking.canOpenURL(`market://launch?id=${GOOGLE_WALLET_PACKAGE}`);
99
97
  console.log("Can open market launch URL:", canOpen);
@@ -104,47 +102,34 @@ const openGoogleWallet = async (): Promise<boolean> => {
104
102
  return true;
105
103
  }
106
104
  } catch (marketLaunchError) {
107
- console.log("Market launch failed, trying intent launcher:", marketLaunchError);
105
+ console.log("Market launch failed:", marketLaunchError);
108
106
  }
109
107
 
110
- // Fallback: Try intent launcher with ACTION_VIEW (no explicit className)
111
- // This should open the app if it handles the VIEW action
108
+ // App is not installed or market:// not available, open Play Store
109
+ console.log("Opening Play Store to install Google Wallet");
112
110
  try {
113
- await IntentLauncher.startActivityAsync("android.intent.action.VIEW", {
114
- packageName: GOOGLE_WALLET_PACKAGE,
115
- flags: 268435456, // FLAG_ACTIVITY_NEW_TASK (0x10000000)
116
- });
117
- console.log("Successfully opened Google Wallet with VIEW intent");
118
- return true;
119
- } catch (intentError) {
120
- console.log("Intent launcher failed:", intentError);
121
-
122
- // App is not installed, open Play Store
123
- console.log("App appears to not be installed, opening Play Store");
124
- try {
125
- // Try native Play Store app first
126
- const playStoreUrl = `market://details?id=${GOOGLE_WALLET_PACKAGE}`;
127
- await Linking.openURL(playStoreUrl);
128
-
129
- Alert.alert(
130
- "Google Wallet",
131
- "Google Wallet is not installed. Opening Play Store to install the app."
132
- );
133
- return false;
134
- } catch (marketError) {
135
- console.log("Market URL failed, trying web Play Store:", marketError);
136
-
137
- // Fallback to web Play Store
138
- await Linking.openURL(
139
- `https://play.google.com/store/apps/details?id=${GOOGLE_WALLET_PACKAGE}`
140
- );
141
-
142
- Alert.alert(
143
- "Google Wallet",
144
- "Google Wallet is not installed. Opening Play Store to install the app."
145
- );
146
- return false;
147
- }
111
+ // Try native Play Store app first
112
+ const playStoreUrl = `market://details?id=${GOOGLE_WALLET_PACKAGE}`;
113
+ await Linking.openURL(playStoreUrl);
114
+
115
+ Alert.alert(
116
+ "Google Wallet",
117
+ "Google Wallet is not installed. Opening Play Store to install the app."
118
+ );
119
+ return false;
120
+ } catch (marketError) {
121
+ console.log("Market URL failed, trying web Play Store:", marketError);
122
+
123
+ // Fallback to web Play Store
124
+ await Linking.openURL(
125
+ `https://play.google.com/store/apps/details?id=${GOOGLE_WALLET_PACKAGE}`
126
+ );
127
+
128
+ Alert.alert(
129
+ "Google Wallet",
130
+ "Google Wallet is not installed. Opening Play Store to install the app."
131
+ );
132
+ return false;
148
133
  }
149
134
  } catch (error) {
150
135
  console.error("Failed to open Google Wallet:", error);