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