@methodfi/opal-react 0.0.2 → 1.1.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 +17 -20
- package/dist/index.d.ts +37 -70
- package/dist/index.js +32 -35
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ yarn add @methodfi/opal-react
|
|
|
17
17
|
Wrap your app with the `OpalProvider`:
|
|
18
18
|
|
|
19
19
|
```tsx
|
|
20
|
-
import {OpalProvider} from '@methodfi/opal-react';
|
|
20
|
+
import { OpalProvider } from '@methodfi/opal-react';
|
|
21
21
|
|
|
22
22
|
export default function App() {
|
|
23
23
|
return (
|
|
@@ -33,37 +33,34 @@ export default function App() {
|
|
|
33
33
|
Use the `useOpal` hook in your components:
|
|
34
34
|
|
|
35
35
|
```tsx
|
|
36
|
-
import {useOpal} from '@methodfi/opal-react';
|
|
36
|
+
import { useOpal, OpalEventType } from '@methodfi/opal-react';
|
|
37
37
|
|
|
38
38
|
function Screen() {
|
|
39
|
-
const {open} = useOpal({
|
|
39
|
+
const { open, close, isOpen, error } = useOpal({
|
|
40
40
|
env: 'dev',
|
|
41
41
|
onOpen: () => {},
|
|
42
42
|
onExit: () => {},
|
|
43
|
-
onError: error => console.error('Error:', error),
|
|
44
43
|
onEvent: event => {
|
|
45
44
|
switch (event.type) {
|
|
46
|
-
case
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
case
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
case
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
case
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// See events section
|
|
45
|
+
case OpalEventType.SESSION_STARTED:
|
|
46
|
+
// ...
|
|
47
|
+
break;
|
|
48
|
+
case OpalEventType.SESSION_COMPLETED:
|
|
49
|
+
// ...
|
|
50
|
+
break;
|
|
51
|
+
case OpalEventType.SESSION_ERRORED:
|
|
52
|
+
// ...
|
|
53
|
+
break;
|
|
54
|
+
case OpalEventType.SESSION_EXITED:
|
|
55
|
+
// ...
|
|
56
|
+
break;
|
|
59
57
|
}
|
|
60
58
|
},
|
|
61
59
|
});
|
|
62
60
|
|
|
63
61
|
const onLaunchOpal = async () => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
open({token});
|
|
62
|
+
const token = await getOpalToken(); // POST /opal/token
|
|
63
|
+
open({ token });
|
|
67
64
|
};
|
|
68
65
|
|
|
69
66
|
return <button onClick={onLaunchOpal}>Launch Opal</button>;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,72 +4,53 @@ type OpalEnvironment = 'local' | 'dev' | 'sandbox' | 'production';
|
|
|
4
4
|
type OpalMode = 'opal' | 'card_connect' | 'account_verification' | string;
|
|
5
5
|
type OpalEventObject = 'session' | 'card' | 'account' | 'bank' | string;
|
|
6
6
|
type OpalEventAction = 'started' | 'errored' | 'exited' | 'verified' | 'completed' | string;
|
|
7
|
-
|
|
7
|
+
declare const OpalEventType: {
|
|
8
|
+
readonly SESSION_STARTED: "opal.session.started";
|
|
9
|
+
readonly SESSION_ERRORED: "opal.session.errored";
|
|
10
|
+
readonly SESSION_EXITED: "opal.session.exited";
|
|
11
|
+
readonly SESSION_COMPLETED: "opal.session.completed";
|
|
12
|
+
};
|
|
13
|
+
type OpalEventType = 'opal.session.started' | 'opal.session.errored' | 'opal.session.exited' | 'opal.session.completed';
|
|
8
14
|
/**
|
|
9
15
|
* Structured event object matching Method's Opal event format
|
|
10
|
-
*
|
|
16
|
+
* Types follow the pattern: <mode>.<object>.<action>, which is
|
|
17
|
+
* also split up into those properties for convenience.
|
|
11
18
|
*/
|
|
12
19
|
interface OpalEvent {
|
|
13
|
-
/** The type of event, formatted as <mode>.<object>.<action> */
|
|
14
20
|
type: OpalEventType;
|
|
15
|
-
/** The operational mode associated with the event (e.g., 'card_connect') */
|
|
16
21
|
mode: OpalMode;
|
|
17
|
-
/** The object the event is related to (e.g., 'card', 'session') */
|
|
18
22
|
object: OpalEventObject;
|
|
19
|
-
/** The action that occurred (e.g., 'started', 'verified', 'errored') */
|
|
20
23
|
action: OpalEventAction;
|
|
21
|
-
/** The ISO 8601 timestamp of when the event occurred */
|
|
22
24
|
timestamp: string;
|
|
23
|
-
/** Additional context or details related to the event (varies by event type) */
|
|
24
25
|
data?: OpalEventData | null;
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
28
|
* Error data structure for Opal session errors
|
|
28
29
|
*/
|
|
29
30
|
interface OpalErrorData {
|
|
30
|
-
/** Error type identifier */
|
|
31
31
|
type: string;
|
|
32
|
-
/** Error sub-type for more specific categorization */
|
|
33
32
|
sub_type?: string;
|
|
34
|
-
/** Human-readable error message */
|
|
35
33
|
message: string;
|
|
36
34
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Card verification event data
|
|
39
|
-
*/
|
|
40
|
-
interface CardVerifiedEventData {
|
|
41
|
-
/** The account ID of the verified card */
|
|
42
|
-
account: string;
|
|
43
|
-
/** Additional metadata about the card verification */
|
|
44
|
-
metadata?: Record<string, any>;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Account verification event data
|
|
48
|
-
*/
|
|
49
|
-
interface AccountVerifiedEventData {
|
|
50
|
-
/** The account ID of the verified account */
|
|
51
|
-
account: string;
|
|
52
|
-
/** Account verification details */
|
|
53
|
-
account_verification?: {
|
|
54
|
-
account_id: string;
|
|
55
|
-
type: string;
|
|
56
|
-
[key: string]: any;
|
|
57
|
-
};
|
|
58
|
-
/** Additional metadata */
|
|
59
|
-
metadata?: Record<string, any>;
|
|
60
|
-
}
|
|
61
35
|
/**
|
|
62
36
|
* Union type for all possible event data structures
|
|
63
37
|
*/
|
|
64
|
-
type OpalEventData = OpalErrorData |
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
38
|
+
type OpalEventData = OpalErrorData | OpalIdentityEventData | OpalConnectEventData | OpalCardConnectEventData | OpalAccountVerificationEventData;
|
|
39
|
+
type OpalIdentityEventData = {
|
|
40
|
+
entity_id: string;
|
|
41
|
+
};
|
|
42
|
+
type OpalConnectEventData = {
|
|
43
|
+
entity_id: string;
|
|
44
|
+
accounts: string[];
|
|
45
|
+
};
|
|
46
|
+
type OpalCardConnectEventData = {
|
|
47
|
+
entity_id: string;
|
|
48
|
+
accounts: string[];
|
|
49
|
+
};
|
|
50
|
+
type OpalAccountVerificationEventData = {
|
|
51
|
+
entity_id: string;
|
|
52
|
+
accounts: string[];
|
|
53
|
+
};
|
|
73
54
|
/**
|
|
74
55
|
* Configuration object for the useOpal hook
|
|
75
56
|
*/
|
|
@@ -82,19 +63,6 @@ interface OpalConfig {
|
|
|
82
63
|
onExit?: (data?: any) => void;
|
|
83
64
|
/** Called for all Opal events with properly typed event object */
|
|
84
65
|
onEvent?: (event: OpalEvent) => void;
|
|
85
|
-
/** Called when an error occurs */
|
|
86
|
-
onError?: (error: OpalErrorData) => void;
|
|
87
|
-
/** Called on successful completion */
|
|
88
|
-
onSuccess?: (data: OpalSuccessData) => void;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Success data structure for completed Opal sessions
|
|
92
|
-
*/
|
|
93
|
-
interface OpalSuccessData {
|
|
94
|
-
/** The session ID of the completed session */
|
|
95
|
-
sessionId: string;
|
|
96
|
-
/** Additional metadata from the session */
|
|
97
|
-
metadata?: Record<string, any>;
|
|
98
66
|
}
|
|
99
67
|
/**
|
|
100
68
|
* Options for opening an Opal session
|
|
@@ -107,16 +75,10 @@ interface OpalOpenOptions {
|
|
|
107
75
|
* Return type for the useOpal hook
|
|
108
76
|
*/
|
|
109
77
|
interface UseOpalReturn {
|
|
110
|
-
/** Opens Opal with the provided token and options */
|
|
111
78
|
open: (options: OpalOpenOptions) => void;
|
|
112
|
-
/** Closes the current Opal session */
|
|
113
79
|
close: () => void;
|
|
114
|
-
/** Whether Opal is loaded and ready for interaction */
|
|
115
|
-
isReady: boolean;
|
|
116
|
-
/** Whether Opal is currently open */
|
|
117
80
|
isOpen: boolean;
|
|
118
|
-
|
|
119
|
-
error: OpalError | OpalErrorData | null;
|
|
81
|
+
error: OpalErrorData | null;
|
|
120
82
|
}
|
|
121
83
|
|
|
122
84
|
interface OpalProviderProps {
|
|
@@ -147,21 +109,26 @@ declare function OpalProvider({ children }: OpalProviderProps): JSX.Element;
|
|
|
147
109
|
*
|
|
148
110
|
* @example
|
|
149
111
|
* ```tsx
|
|
150
|
-
* const { open, close,
|
|
112
|
+
* const { open, close, isOpen, error } = useOpal({
|
|
151
113
|
* env: 'dev',
|
|
152
114
|
* onOpen: () => console.log('Opal opened'),
|
|
153
115
|
* onExit: () => console.log('Opal closed'),
|
|
154
116
|
* onEvent: (event) => {
|
|
155
117
|
* switch (event.type) {
|
|
156
|
-
* case
|
|
118
|
+
* case OpalEventType.SESSION_STARTED:
|
|
157
119
|
* console.log('Session started');
|
|
158
120
|
* break;
|
|
159
|
-
* case
|
|
160
|
-
* console.log('
|
|
121
|
+
* case OpalEventType.SESSION_COMPLETED:
|
|
122
|
+
* console.log('Session completed:', event.data);
|
|
123
|
+
* break;
|
|
124
|
+
* case OpalEventType.SESSION_EXITED:
|
|
125
|
+
* console.log('Session exited:', event.data);
|
|
126
|
+
* break;
|
|
127
|
+
* case OpalEventType.SESSION_ERRORED:
|
|
128
|
+
* console.log('Session errored:', event.data);
|
|
161
129
|
* break;
|
|
162
130
|
* }
|
|
163
131
|
* },
|
|
164
|
-
* onError: (error) => console.error('Opal error:', error),
|
|
165
132
|
* });
|
|
166
133
|
*
|
|
167
134
|
* // Open Opal with a token
|
|
@@ -176,4 +143,4 @@ declare function OpalProvider({ children }: OpalProviderProps): JSX.Element;
|
|
|
176
143
|
*/
|
|
177
144
|
declare function useOpal(config?: OpalConfig): UseOpalReturn;
|
|
178
145
|
|
|
179
|
-
export { OpalProvider, useOpal };
|
|
146
|
+
export { OpalEventType, OpalProvider, useOpal };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
|
|
2
|
+
import React, { useState, createContext, useEffect, useRef, useCallback, useContext } from 'react';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
// Common Opal event types - supports card_connect and account_verification modes
|
|
5
|
+
const OpalEventType = {
|
|
6
|
+
SESSION_STARTED: 'opal.session.started',
|
|
7
|
+
SESSION_ERRORED: 'opal.session.errored',
|
|
8
|
+
SESSION_EXITED: 'opal.session.exited',
|
|
9
|
+
SESSION_COMPLETED: 'opal.session.completed'
|
|
10
|
+
};
|
|
9
11
|
|
|
10
12
|
const OPAL_URLS = {
|
|
11
13
|
local: 'http://localhost:3000',
|
|
@@ -14,7 +16,7 @@ const OPAL_URLS = {
|
|
|
14
16
|
production: 'https://opal.production.methodfi.com'
|
|
15
17
|
};
|
|
16
18
|
|
|
17
|
-
const OpalContext = /*#__PURE__*/
|
|
19
|
+
const OpalContext = /*#__PURE__*/ createContext(null);
|
|
18
20
|
/**
|
|
19
21
|
* Provider component that wraps your app to enable Opal functionality.
|
|
20
22
|
* This component manages the Opal iframe state and provides context to child components.
|
|
@@ -32,11 +34,10 @@ const OpalContext = /*#__PURE__*/ React.createContext(null);
|
|
|
32
34
|
* }
|
|
33
35
|
* ```
|
|
34
36
|
*/ function OpalProvider({ children }) {
|
|
35
|
-
const [isOpen, setIsOpen] =
|
|
36
|
-
const [
|
|
37
|
-
const [
|
|
38
|
-
const [
|
|
39
|
-
const [currentConfig, setCurrentConfig] = React.useState({});
|
|
37
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
38
|
+
const [error, setError] = useState(null);
|
|
39
|
+
const [currentToken, setCurrentToken] = useState(null);
|
|
40
|
+
const [currentConfig, setCurrentConfig] = useState({});
|
|
40
41
|
const environment = currentConfig.env || 'production';
|
|
41
42
|
const baseUrl = OPAL_URLS[environment];
|
|
42
43
|
const opalUrl = currentToken ? `${baseUrl}/?token=${currentToken}` : '';
|
|
@@ -47,16 +48,12 @@ const OpalContext = /*#__PURE__*/ React.createContext(null);
|
|
|
47
48
|
const { action, data } = event;
|
|
48
49
|
// Legacy support for action-based handling
|
|
49
50
|
switch(action){
|
|
50
|
-
case 'started':
|
|
51
|
-
if (!isReady) setIsReady(true);
|
|
52
|
-
break;
|
|
53
51
|
case 'exited':
|
|
54
52
|
close();
|
|
55
53
|
break;
|
|
56
54
|
case 'errored':
|
|
57
55
|
if (!error && data) {
|
|
58
56
|
setError(data);
|
|
59
|
-
currentConfig.onError == null ? void 0 : currentConfig.onError.call(currentConfig, data);
|
|
60
57
|
}
|
|
61
58
|
break;
|
|
62
59
|
}
|
|
@@ -65,13 +62,11 @@ const OpalContext = /*#__PURE__*/ React.createContext(null);
|
|
|
65
62
|
setCurrentToken(options.token);
|
|
66
63
|
setIsOpen(true);
|
|
67
64
|
setError(null);
|
|
68
|
-
setIsReady(false);
|
|
69
65
|
currentConfig.onOpen == null ? void 0 : currentConfig.onOpen.call(currentConfig);
|
|
70
66
|
};
|
|
71
67
|
const close = ()=>{
|
|
72
68
|
setTimeout(()=>{
|
|
73
69
|
setIsOpen(false);
|
|
74
|
-
setIsReady(false);
|
|
75
70
|
setCurrentToken(null);
|
|
76
71
|
currentConfig.onExit == null ? void 0 : currentConfig.onExit.call(currentConfig);
|
|
77
72
|
}, 500);
|
|
@@ -81,15 +76,14 @@ const OpalContext = /*#__PURE__*/ React.createContext(null);
|
|
|
81
76
|
};
|
|
82
77
|
const contextValue = {
|
|
83
78
|
isOpen,
|
|
84
|
-
isReady,
|
|
85
79
|
error,
|
|
86
80
|
open,
|
|
87
81
|
close,
|
|
88
82
|
updateConfig
|
|
89
83
|
};
|
|
90
|
-
return /*#__PURE__*/
|
|
84
|
+
return /*#__PURE__*/ React.createElement(OpalContext.Provider, {
|
|
91
85
|
value: contextValue
|
|
92
|
-
}, children, isOpen && currentToken && /*#__PURE__*/
|
|
86
|
+
}, children, isOpen && currentToken && /*#__PURE__*/ React.createElement(OpalIframe, {
|
|
93
87
|
token: currentToken,
|
|
94
88
|
opalUrl: opalUrl,
|
|
95
89
|
onMessage: handleMessage,
|
|
@@ -105,7 +99,7 @@ const OpalContext = /*#__PURE__*/ React.createContext(null);
|
|
|
105
99
|
}));
|
|
106
100
|
}
|
|
107
101
|
function useOpalContext() {
|
|
108
|
-
const context =
|
|
102
|
+
const context = useContext(OpalContext);
|
|
109
103
|
if (!context) {
|
|
110
104
|
throw new Error('useOpalContext must be used within an OpalProvider');
|
|
111
105
|
}
|
|
@@ -118,21 +112,26 @@ function useOpalContext() {
|
|
|
118
112
|
*
|
|
119
113
|
* @example
|
|
120
114
|
* ```tsx
|
|
121
|
-
* const { open, close,
|
|
115
|
+
* const { open, close, isOpen, error } = useOpal({
|
|
122
116
|
* env: 'dev',
|
|
123
117
|
* onOpen: () => console.log('Opal opened'),
|
|
124
118
|
* onExit: () => console.log('Opal closed'),
|
|
125
119
|
* onEvent: (event) => {
|
|
126
120
|
* switch (event.type) {
|
|
127
|
-
* case
|
|
121
|
+
* case OpalEventType.SESSION_STARTED:
|
|
128
122
|
* console.log('Session started');
|
|
129
123
|
* break;
|
|
130
|
-
* case
|
|
131
|
-
* console.log('
|
|
124
|
+
* case OpalEventType.SESSION_COMPLETED:
|
|
125
|
+
* console.log('Session completed:', event.data);
|
|
126
|
+
* break;
|
|
127
|
+
* case OpalEventType.SESSION_EXITED:
|
|
128
|
+
* console.log('Session exited:', event.data);
|
|
129
|
+
* break;
|
|
130
|
+
* case OpalEventType.SESSION_ERRORED:
|
|
131
|
+
* console.log('Session errored:', event.data);
|
|
132
132
|
* break;
|
|
133
133
|
* }
|
|
134
134
|
* },
|
|
135
|
-
* onError: (error) => console.error('Opal error:', error),
|
|
136
135
|
* });
|
|
137
136
|
*
|
|
138
137
|
* // Open Opal with a token
|
|
@@ -147,20 +146,19 @@ function useOpalContext() {
|
|
|
147
146
|
*/ function useOpal(config = {}) {
|
|
148
147
|
const context = useOpalContext();
|
|
149
148
|
// Update the provider's config when this hook's config changes
|
|
150
|
-
|
|
149
|
+
useEffect(()=>{
|
|
151
150
|
context.updateConfig(config);
|
|
152
151
|
}, []);
|
|
153
152
|
return {
|
|
154
153
|
open: context.open,
|
|
155
154
|
close: context.close,
|
|
156
|
-
isReady: context.isReady,
|
|
157
155
|
isOpen: context.isOpen,
|
|
158
156
|
error: context.error
|
|
159
157
|
};
|
|
160
158
|
}
|
|
161
159
|
function OpalIframe({ token, opalUrl, onMessage, style }) {
|
|
162
|
-
const iframeRef =
|
|
163
|
-
const handleMessage =
|
|
160
|
+
const iframeRef = useRef(null);
|
|
161
|
+
const handleMessage = useCallback((event)=>{
|
|
164
162
|
// Check if the event is from an allowed origin
|
|
165
163
|
const allowedOrigins = [
|
|
166
164
|
'https://opal.production.methodfi.com',
|
|
@@ -190,7 +188,7 @@ function OpalIframe({ token, opalUrl, onMessage, style }) {
|
|
|
190
188
|
}, [
|
|
191
189
|
onMessage
|
|
192
190
|
]);
|
|
193
|
-
|
|
191
|
+
useEffect(()=>{
|
|
194
192
|
// Add event listener for iframe messages
|
|
195
193
|
window.addEventListener('message', handleMessage);
|
|
196
194
|
return ()=>{
|
|
@@ -202,7 +200,7 @@ function OpalIframe({ token, opalUrl, onMessage, style }) {
|
|
|
202
200
|
if (!token || !opalUrl) {
|
|
203
201
|
return null;
|
|
204
202
|
}
|
|
205
|
-
return /*#__PURE__*/
|
|
203
|
+
return /*#__PURE__*/ React.createElement("iframe", {
|
|
206
204
|
ref: iframeRef,
|
|
207
205
|
src: opalUrl,
|
|
208
206
|
style: style,
|
|
@@ -211,5 +209,4 @@ function OpalIframe({ token, opalUrl, onMessage, style }) {
|
|
|
211
209
|
});
|
|
212
210
|
}
|
|
213
211
|
|
|
214
|
-
|
|
215
|
-
exports.useOpal = useOpal;
|
|
212
|
+
export { OpalEventType, OpalProvider, useOpal };
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@methodfi/opal-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "React SDK for Opal integration",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist"
|
|
9
9
|
],
|
|
10
|
+
"type": "module",
|
|
10
11
|
"scripts": {
|
|
11
12
|
"build": "bunchee",
|
|
12
13
|
"dev": "bunchee --watch"
|