@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 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
@@ -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
- 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;
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
- * Events follow the pattern: <mode>.<object>.<action>
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 | 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
- }
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
- /** Current error state, if any */
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, isReady, isOpen, error } = useOpal({
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 'opal.session.started':
118
+ * case OpalEventType.SESSION_STARTED:
157
119
  * console.log('Session started');
158
120
  * break;
159
- * case 'card_connect.card.verified':
160
- * console.log('Card verified:', event.data);
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
- 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
+ // 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__*/ React.createContext(null);
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] = 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({});
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__*/ React__default.default.createElement(OpalContext.Provider, {
84
+ return /*#__PURE__*/ React.createElement(OpalContext.Provider, {
91
85
  value: contextValue
92
- }, children, isOpen && currentToken && /*#__PURE__*/ React__default.default.createElement(OpalIframe, {
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 = React.useContext(OpalContext);
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, isReady, isOpen, error } = useOpal({
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 'opal.session.started':
121
+ * case OpalEventType.SESSION_STARTED:
128
122
  * console.log('Session started');
129
123
  * break;
130
- * case 'card_connect.card.verified':
131
- * console.log('Card verified:', event.data);
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
- React.useEffect(()=>{
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 = React.useRef(null);
163
- const handleMessage = React.useCallback((event)=>{
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
- React.useEffect(()=>{
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__*/ React__default.default.createElement("iframe", {
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
- exports.OpalProvider = OpalProvider;
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": "0.0.2",
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"