@melio-eng/web-sdk 1.0.29 → 1.0.30-pr.58.0d922c4

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.
@@ -0,0 +1,57 @@
1
+ import { FlowInstance, FlowEventType, FlowEventCallback, FlowCompletionData, NavigationData, BaseFlowConfig, Environment, ErrorData } from '../types.js';
2
+ /**
3
+ * Flow class implementation for handling iframe flows and events
4
+ */
5
+ export declare class Flow implements FlowInstance {
6
+ private containerId;
7
+ protected config: BaseFlowConfig;
8
+ protected partnerName: string;
9
+ protected environment: Environment;
10
+ protected branchOverride?: string | undefined;
11
+ protected iframe: HTMLIFrameElement | null;
12
+ protected container: HTMLElement | null;
13
+ private eventListeners;
14
+ protected keepAliveInterval: number | null;
15
+ constructor(containerId: string, config: BaseFlowConfig, partnerName: string, environment: Environment, branchOverride?: string | undefined);
16
+ /**
17
+ * Initialize the flow by creating and injecting the iframe
18
+ */
19
+ initialize(): Promise<void>;
20
+ /**
21
+ * Construct the specific flow URL - can be overridden by subclasses
22
+ */
23
+ protected constructFlowUrl(baseUrl: string): string;
24
+ /**
25
+ * Create flow URL using partner name and environment
26
+ */
27
+ protected createFlowUrl(): string;
28
+ private setupEventListeners;
29
+ /**
30
+ * Emit events to registered listeners
31
+ */
32
+ protected emit(event: 'completed', data: FlowCompletionData): void;
33
+ protected emit(event: 'loaded'): void;
34
+ protected emit(event: 'exit'): void;
35
+ protected emit(event: 'navigated', data: NavigationData): void;
36
+ protected emit(event: 'authenticationSucceeded'): void;
37
+ protected emit(event: 'authenticationFailed'): void;
38
+ protected emit(event: 'error', data: ErrorData): void;
39
+ /**
40
+ * Register an event listener
41
+ */
42
+ on(event: 'completed', callback: (data: FlowCompletionData) => void): void;
43
+ on(event: 'exit', callback: () => void): void;
44
+ on(event: 'navigated', callback: (payload: NavigationData) => void): void;
45
+ on(event: 'authenticationSucceeded', callback: () => void): void;
46
+ on(event: 'authenticationFailed', callback: () => void): void;
47
+ on(event: 'error', callback: (data: ErrorData) => void): void;
48
+ on(event: 'loaded', callback: () => void): void;
49
+ /**
50
+ * Remove an event listener
51
+ */
52
+ off(event: FlowEventType, callback: FlowEventCallback): void;
53
+ /**
54
+ * Close the flow and clean up resources
55
+ */
56
+ close(): void;
57
+ }
@@ -0,0 +1,150 @@
1
+ import { getBaseUrl } from './utils.js';
2
+ /**
3
+ * Flow class implementation for handling iframe flows and events
4
+ */
5
+ export class Flow {
6
+ constructor(containerId, config, partnerName, environment, branchOverride) {
7
+ this.containerId = containerId;
8
+ this.config = config;
9
+ this.partnerName = partnerName;
10
+ this.environment = environment;
11
+ this.branchOverride = branchOverride;
12
+ this.iframe = null;
13
+ this.container = null;
14
+ this.eventListeners = new Map();
15
+ this.keepAliveInterval = null;
16
+ this.setupEventListeners();
17
+ }
18
+ /**
19
+ * Initialize the flow by creating and injecting the iframe
20
+ */
21
+ async initialize() {
22
+ this.container = document.getElementById(this.containerId);
23
+ if (!this.container) {
24
+ throw new Error(`Container with ID "${this.containerId}" not found`);
25
+ }
26
+ this.iframe = document.createElement('iframe');
27
+ this.iframe.src = this.createFlowUrl();
28
+ this.iframe.style.width = '100%';
29
+ this.iframe.style.height = '1000px';
30
+ this.iframe.style.border = 'none';
31
+ this.iframe.style.display = 'block';
32
+ this.container.appendChild(this.iframe);
33
+ }
34
+ /**
35
+ * Construct the specific flow URL - can be overridden by subclasses
36
+ */
37
+ constructFlowUrl(baseUrl) {
38
+ return `${baseUrl}/${this.partnerName}/auth`;
39
+ }
40
+ /**
41
+ * Create flow URL using partner name and environment
42
+ */
43
+ createFlowUrl() {
44
+ console.log('🔧 Creating flow URL...');
45
+ console.log('📝 Config:', this.config);
46
+ console.log('🏢 Partner:', this.partnerName);
47
+ console.log('🌍 Environment:', this.environment);
48
+ console.log('🔝 Branch Override:', this.branchOverride);
49
+ const baseUrl = getBaseUrl(this.environment);
50
+ let finalUrl = this.constructFlowUrl(baseUrl);
51
+ // Add cdn_branch_override parameter for non-production environments
52
+ if (this.branchOverride && this.environment !== 'production') {
53
+ const separator = finalUrl.includes('?') ? '&' : '?';
54
+ finalUrl += `${separator}cdn_branch_override=${this.branchOverride}`;
55
+ }
56
+ console.log('🌐 Final URL:', finalUrl);
57
+ return finalUrl;
58
+ }
59
+ setupEventListeners() {
60
+ // Add post message handlers for internal events - setHeight, scroll etc
61
+ // Also need to implement callbacks for flow completed exit etc in the platform-app
62
+ window.addEventListener('message', (event) => {
63
+ if (!/melio\.com|melioservices\.com/.test(event.origin)) {
64
+ return;
65
+ }
66
+ const { type, ...data } = event.data;
67
+ console.log('📬 Received message from iframe:', {
68
+ type,
69
+ data,
70
+ origin: event.origin,
71
+ });
72
+ switch (type) {
73
+ case 'FLOW_COMPLETED':
74
+ this.emit('completed', data);
75
+ break;
76
+ case 'FLOW_EXIT':
77
+ this.emit('exit');
78
+ break;
79
+ case 'NAVIGATED_TO_TARGET':
80
+ this.emit('navigated', data);
81
+ break;
82
+ case 'AUTHENTICATION_SUCCESS':
83
+ this.emit('authenticationSucceeded');
84
+ break;
85
+ case 'AUTHENTICATION_ERROR':
86
+ this.emit('authenticationFailed');
87
+ break;
88
+ case 'ONBOARDING_COMPLETED':
89
+ this.emit('completed', { flowName: 'onboarding', ...data });
90
+ break;
91
+ case 'READY_FOR_INTERACTION':
92
+ this.emit('loaded');
93
+ break;
94
+ case 'MELIO_ERROR':
95
+ if (data.code === 'failed_to_sync_bills') {
96
+ this.emit('error', { errorCode: 'billsSyncFailed' });
97
+ }
98
+ break;
99
+ case 'HEIGHT_CHANGE':
100
+ if (this.iframe) {
101
+ this.iframe.style.height = `${data.height}px`;
102
+ }
103
+ break;
104
+ }
105
+ });
106
+ }
107
+ emit(event, data) {
108
+ const listeners = this.eventListeners.get(event);
109
+ if (listeners) {
110
+ listeners.forEach((callback) => {
111
+ try {
112
+ callback(data);
113
+ }
114
+ catch (error) {
115
+ console.error(`Error in ${event} event listener:`, error);
116
+ }
117
+ });
118
+ }
119
+ }
120
+ on(event, callback) {
121
+ if (!this.eventListeners.has(event)) {
122
+ this.eventListeners.set(event, new Set());
123
+ }
124
+ this.eventListeners.get(event).add(callback);
125
+ }
126
+ /* eslint-enable no-dupe-class-members */
127
+ /**
128
+ * Remove an event listener
129
+ */
130
+ off(event, callback) {
131
+ const listeners = this.eventListeners.get(event);
132
+ if (listeners) {
133
+ listeners.delete(callback);
134
+ }
135
+ }
136
+ /**
137
+ * Close the flow and clean up resources
138
+ */
139
+ close() {
140
+ if (this.keepAliveInterval) {
141
+ clearInterval(this.keepAliveInterval);
142
+ this.keepAliveInterval = null;
143
+ }
144
+ if (this.iframe && this.container) {
145
+ this.container.removeChild(this.iframe);
146
+ this.iframe = null;
147
+ }
148
+ this.eventListeners.clear();
149
+ }
150
+ }
@@ -0,0 +1,18 @@
1
+ import { InitConfig, Environment } from '../types.js';
2
+ import { Flow } from './Flow.js';
3
+ /**
4
+ * InitFlow subclass for handling initialization with callbacks
5
+ */
6
+ export declare class InitFlow extends Flow {
7
+ private authorizationCode;
8
+ constructor(containerId: string, config: InitConfig, partnerName: string, environment: Environment, branchOverride?: string);
9
+ /**
10
+ * Initialize the init flow by creating and injecting a hidden iframe
11
+ */
12
+ initialize(): Promise<void>;
13
+ /**
14
+ * Construct the specific flow URL for initialization
15
+ */
16
+ protected constructFlowUrl(baseUrl: string): string;
17
+ setupKeepAlive(): void;
18
+ }
@@ -0,0 +1,45 @@
1
+ import { Flow } from './Flow.js';
2
+ import { isEmptyString } from './utils.js';
3
+ /**
4
+ * InitFlow subclass for handling initialization with callbacks
5
+ */
6
+ export class InitFlow extends Flow {
7
+ constructor(containerId, config, partnerName, environment, branchOverride) {
8
+ super(containerId, config, partnerName, environment, branchOverride);
9
+ if (isEmptyString(config.authCode)) {
10
+ throw new Error('Authorization code is required for init flow');
11
+ }
12
+ this.authorizationCode = config.authCode;
13
+ }
14
+ /**
15
+ * Initialize the init flow by creating and injecting a hidden iframe
16
+ */
17
+ async initialize() {
18
+ this.container = document.body;
19
+ this.iframe = document.createElement('iframe');
20
+ this.iframe.src = this.createFlowUrl();
21
+ this.iframe.style.width = '1px';
22
+ this.iframe.style.height = '1px';
23
+ this.iframe.style.position = 'absolute';
24
+ this.iframe.style.left = '-9999px';
25
+ this.iframe.style.top = '-9999px';
26
+ this.container.appendChild(this.iframe);
27
+ }
28
+ /**
29
+ * Construct the specific flow URL for initialization
30
+ */
31
+ constructFlowUrl(baseUrl) {
32
+ const params = new URLSearchParams({
33
+ token: this.authorizationCode,
34
+ theme: this.partnerName,
35
+ });
36
+ return `${baseUrl}/${this.partnerName}/auth?${params.toString()}`;
37
+ }
38
+ setupKeepAlive() {
39
+ this.keepAliveInterval = window.setInterval(() => {
40
+ if (this.iframe && this.iframe.contentWindow) {
41
+ this.iframe.contentWindow.postMessage({ type: 'USER_ACTIVE_PING' }, '*');
42
+ }
43
+ }, 30000); // Send ping every 30 seconds
44
+ }
45
+ }
@@ -0,0 +1,15 @@
1
+ import { OnboardingConfig, Environment } from '../types.js';
2
+ import { Flow } from './Flow.js';
3
+ /**
4
+ * OnboardingFlow subclass with custom URL construction
5
+ */
6
+ export declare class OnboardingFlow extends Flow {
7
+ private preFilledParams;
8
+ private enforceOnboarding?;
9
+ private authCode;
10
+ constructor(containerId: string, config: OnboardingConfig, partnerName: string, environment: Environment, authCode: string, branchOverride?: string);
11
+ /**
12
+ * Construct the specific flow URL for onboarding - now goes through /auth
13
+ */
14
+ protected constructFlowUrl(baseUrl: string): string;
15
+ }
@@ -0,0 +1,44 @@
1
+ import { Flow } from './Flow.js';
2
+ import { isEmptyString } from './utils.js';
3
+ /**
4
+ * OnboardingFlow subclass with custom URL construction
5
+ */
6
+ export class OnboardingFlow extends Flow {
7
+ constructor(containerId, config, partnerName, environment, authCode, branchOverride) {
8
+ super(containerId, config, partnerName, environment, branchOverride);
9
+ const { userDetails, organizationDetails, enforceOnboarding } = config;
10
+ this.preFilledParams = { userDetails, organizationDetails };
11
+ this.enforceOnboarding = enforceOnboarding;
12
+ this.authCode = authCode;
13
+ if (isEmptyString(this.authCode)) {
14
+ throw new Error('Authorization code is required for onboarding flow. Please call init() before opening onboarding flow.');
15
+ }
16
+ }
17
+ /**
18
+ * Construct the specific flow URL for onboarding - now goes through /auth
19
+ */
20
+ constructFlowUrl(baseUrl) {
21
+ const params = Object.fromEntries(Object.entries(this.preFilledParams).filter(([, v]) => v !== undefined));
22
+ let preFilledBase64 = '';
23
+ if (Object.keys(params).length > 0) {
24
+ try {
25
+ const json = JSON.stringify(params);
26
+ preFilledBase64 = btoa(unescape(encodeURIComponent(json)));
27
+ }
28
+ catch (e) {
29
+ preFilledBase64 = '';
30
+ }
31
+ }
32
+ const targetQueryParams = [];
33
+ if (preFilledBase64) {
34
+ targetQueryParams.push(`preFilledParams=${encodeURIComponent(preFilledBase64)}`);
35
+ }
36
+ if (this.enforceOnboarding !== undefined) {
37
+ targetQueryParams.push(`enforceOnboarding=${this.enforceOnboarding}`);
38
+ }
39
+ targetQueryParams.push(`externalOrigin=${this.partnerName}`);
40
+ const target = `/welcome?${targetQueryParams.join('&')}`;
41
+ const encodedTarget = encodeURIComponent(target);
42
+ return `${baseUrl}/${this.partnerName}/auth?token=${this.authCode}&redirectUrl=${encodedTarget}`;
43
+ }
44
+ }
@@ -0,0 +1,14 @@
1
+ import { PayFlowConfig, Environment } from '../types.js';
2
+ import { Flow } from './Flow.js';
3
+ /**
4
+ * PayFlow subclass with custom URL construction
5
+ */
6
+ export declare class PayFlow extends Flow {
7
+ private billIds;
8
+ private authCode;
9
+ constructor(containerId: string, config: PayFlowConfig, partnerName: string, environment: Environment, authCode: string, branchOverride?: string);
10
+ /**
11
+ * Construct the specific flow URL for bill payment - now goes through /auth
12
+ */
13
+ protected constructFlowUrl(baseUrl: string): string;
14
+ }
@@ -0,0 +1,24 @@
1
+ import { Flow } from './Flow.js';
2
+ import { isEmptyString } from './utils.js';
3
+ /**
4
+ * PayFlow subclass with custom URL construction
5
+ */
6
+ export class PayFlow extends Flow {
7
+ constructor(containerId, config, partnerName, environment, authCode, branchOverride) {
8
+ super(containerId, config, partnerName, environment, branchOverride);
9
+ this.billIds = config.billIds;
10
+ this.authCode = authCode;
11
+ if (isEmptyString(this.authCode)) {
12
+ throw new Error('Authorization code is required for pay flow. Please call init() before opening pay flow.');
13
+ }
14
+ }
15
+ /**
16
+ * Construct the specific flow URL for bill payment - now goes through /auth
17
+ */
18
+ constructFlowUrl(baseUrl) {
19
+ const billIdsParam = this.billIds.join(',');
20
+ const target = `/external-entries/payment-flow?billIds=${billIdsParam}&externalOrigin=${this.partnerName}`;
21
+ const encodedTarget = encodeURIComponent(target);
22
+ return `${baseUrl}/${this.partnerName}/auth?token=${this.authCode}&redirectUrl=${encodedTarget}`;
23
+ }
24
+ }
@@ -0,0 +1,14 @@
1
+ import { PaymentsDashboardConfig, Environment } from '../types.js';
2
+ import { Flow } from './Flow.js';
3
+ /**
4
+ * PaymentsDashboardFlow subclass with custom URL construction
5
+ */
6
+ export declare class PaymentsDashboardFlow extends Flow {
7
+ private paymentId?;
8
+ private authCode;
9
+ constructor(containerId: string, config: PaymentsDashboardConfig, partnerName: string, environment: Environment, authCode: string, branchOverride?: string);
10
+ /**
11
+ * Construct the specific flow URL for payments dashboard - now goes through /auth
12
+ */
13
+ protected constructFlowUrl(baseUrl: string): string;
14
+ }
@@ -0,0 +1,23 @@
1
+ import { Flow } from './Flow.js';
2
+ import { isEmptyString } from './utils.js';
3
+ /**
4
+ * PaymentsDashboardFlow subclass with custom URL construction
5
+ */
6
+ export class PaymentsDashboardFlow extends Flow {
7
+ constructor(containerId, config, partnerName, environment, authCode, branchOverride) {
8
+ super(containerId, config, partnerName, environment, branchOverride);
9
+ this.paymentId = config.paymentId;
10
+ this.authCode = authCode;
11
+ if (isEmptyString(this.authCode)) {
12
+ throw new Error('Authorization code is required for payments dashboard flow. Please call init() before opening payments dashboard flow.');
13
+ }
14
+ }
15
+ /**
16
+ * Construct the specific flow URL for payments dashboard - now goes through /auth
17
+ */
18
+ constructFlowUrl(baseUrl) {
19
+ const target = `/pay-dashboard/payments${this.paymentId ? `/${this.paymentId}` : ''}`;
20
+ const encodedTarget = encodeURIComponent(target);
21
+ return `${baseUrl}/${this.partnerName}/auth?token=${this.authCode}&redirectUrl=${encodedTarget}`;
22
+ }
23
+ }
@@ -0,0 +1,13 @@
1
+ import { SettingsConfig, Environment } from '../types.js';
2
+ import { Flow } from './Flow.js';
3
+ /**
4
+ * SettingsFlow subclass with custom URL construction
5
+ */
6
+ export declare class SettingsFlow extends Flow {
7
+ private authCode;
8
+ constructor(containerId: string, config: SettingsConfig, partnerName: string, environment: Environment, authCode: string, branchOverride?: string);
9
+ /**
10
+ * Construct the specific flow URL for settings - now goes through /auth
11
+ */
12
+ protected constructFlowUrl(baseUrl: string): string;
13
+ }
@@ -0,0 +1,22 @@
1
+ import { Flow } from './Flow.js';
2
+ import { isEmptyString } from './utils.js';
3
+ /**
4
+ * SettingsFlow subclass with custom URL construction
5
+ */
6
+ export class SettingsFlow extends Flow {
7
+ constructor(containerId, config, partnerName, environment, authCode, branchOverride) {
8
+ super(containerId, config, partnerName, environment, branchOverride);
9
+ this.authCode = authCode;
10
+ if (isEmptyString(this.authCode)) {
11
+ throw new Error('Authorization code is required for settings flow. Please call init() before opening settings flow.');
12
+ }
13
+ }
14
+ /**
15
+ * Construct the specific flow URL for settings - now goes through /auth
16
+ */
17
+ constructFlowUrl(baseUrl) {
18
+ const target = '/view-settings';
19
+ const encodedTarget = encodeURIComponent(target);
20
+ return `${baseUrl}/${this.partnerName}/auth?token=${this.authCode}&redirectUrl=${encodedTarget}`;
21
+ }
22
+ }
@@ -0,0 +1,7 @@
1
+ export { Flow } from './Flow.js';
2
+ export { InitFlow } from './InitFlow.js';
3
+ export { OnboardingFlow } from './OnboardingFlow.js';
4
+ export { PayFlow } from './PayFlow.js';
5
+ export { SettingsFlow } from './SettingsFlow.js';
6
+ export { PaymentsDashboardFlow } from './PaymentsDashboardFlow.js';
7
+ export { getBaseUrl, isEmptyString } from './utils.js';
@@ -0,0 +1,7 @@
1
+ export { Flow } from './Flow.js';
2
+ export { InitFlow } from './InitFlow.js';
3
+ export { OnboardingFlow } from './OnboardingFlow.js';
4
+ export { PayFlow } from './PayFlow.js';
5
+ export { SettingsFlow } from './SettingsFlow.js';
6
+ export { PaymentsDashboardFlow } from './PaymentsDashboardFlow.js';
7
+ export { getBaseUrl, isEmptyString } from './utils.js';
@@ -0,0 +1,6 @@
1
+ import { Environment } from '../types.js';
2
+ /**
3
+ * Utility function to get the base URL for a given environment
4
+ */
5
+ export declare function getBaseUrl(environment: Environment): string;
6
+ export declare const isEmptyString: (str: string) => boolean;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Utility function to get the base URL for a given environment
3
+ */
4
+ export function getBaseUrl(environment) {
5
+ switch (environment) {
6
+ case 'production':
7
+ return 'https://partnerships.production.melioservices.com';
8
+ case 'staging01':
9
+ return 'https://partnerships.staging01.melioservices.com';
10
+ case 'public-qa':
11
+ return 'https://partnerships.public-qa.melioservices.com';
12
+ case 'certification':
13
+ return 'https://partnerships.certification.melioservices.com';
14
+ case 'eilat':
15
+ return 'https://partnerships.eilat.melioservices.com';
16
+ case 'localhost':
17
+ return 'http://localhost:3005';
18
+ default:
19
+ return 'https://partnerships.melioservices.com';
20
+ }
21
+ }
22
+ export const isEmptyString = (str) => {
23
+ return !str?.trim();
24
+ };
package/dist/index.js CHANGED
@@ -1,312 +1,4 @@
1
- /**
2
- * Utility function to get the base URL for a given environment
3
- */
4
- function getBaseUrl(environment) {
5
- switch (environment) {
6
- case 'production':
7
- return 'https://partnerships.production.melioservices.com';
8
- case 'staging01':
9
- return 'https://partnerships.staging01.melioservices.com';
10
- case 'public-qa':
11
- return 'https://partnerships.public-qa.melioservices.com';
12
- case 'certification':
13
- return 'https://partnerships.certification.melioservices.com';
14
- case 'eilat':
15
- return 'https://partnerships.eilat.melioservices.com';
16
- case 'localhost':
17
- return 'http://localhost:3005';
18
- default:
19
- return 'https://partnerships.melioservices.com';
20
- }
21
- }
22
- const isEmptyString = (str) => {
23
- return !str?.trim();
24
- };
25
- /**
26
- * Flow class implementation for handling iframe flows and events
27
- */
28
- class Flow {
29
- constructor(containerId, config, partnerName, environment, branchOverride) {
30
- this.containerId = containerId;
31
- this.config = config;
32
- this.partnerName = partnerName;
33
- this.environment = environment;
34
- this.branchOverride = branchOverride;
35
- this.iframe = null;
36
- this.container = null;
37
- this.eventListeners = new Map();
38
- this.keepAliveInterval = null;
39
- this.setupEventListeners();
40
- }
41
- /**
42
- * Initialize the flow by creating and injecting the iframe
43
- */
44
- async initialize() {
45
- this.container = document.getElementById(this.containerId);
46
- if (!this.container) {
47
- throw new Error(`Container with ID "${this.containerId}" not found`);
48
- }
49
- this.iframe = document.createElement('iframe');
50
- this.iframe.src = this.createFlowUrl();
51
- this.iframe.style.width = '100%';
52
- this.iframe.style.height = '1000px';
53
- this.iframe.style.border = 'none';
54
- this.iframe.style.display = 'block';
55
- this.container.appendChild(this.iframe);
56
- }
57
- /**
58
- * Construct the specific flow URL - can be overridden by subclasses
59
- */
60
- constructFlowUrl(baseUrl) {
61
- return `${baseUrl}/${this.partnerName}/auth`;
62
- }
63
- /**
64
- * Create flow URL using partner name and environment
65
- */
66
- createFlowUrl() {
67
- console.log('🔧 Creating flow URL...');
68
- console.log('📝 Config:', this.config);
69
- console.log('🏢 Partner:', this.partnerName);
70
- console.log('🌍 Environment:', this.environment);
71
- console.log('🔝 Branch Override:', this.branchOverride);
72
- const baseUrl = getBaseUrl(this.environment);
73
- let finalUrl = this.constructFlowUrl(baseUrl);
74
- // Add cdn_branch_override parameter for non-production environments
75
- if (this.branchOverride && this.environment !== 'production') {
76
- const separator = finalUrl.includes('?') ? '&' : '?';
77
- finalUrl += `${separator}cdn_branch_override=${this.branchOverride}`;
78
- }
79
- console.log('🌐 Final URL:', finalUrl);
80
- return finalUrl;
81
- }
82
- setupEventListeners() {
83
- // Add post message handlers for internal events - setHeight, scroll etc
84
- // Also need to implement callbacks for flow completed exit etc in the platform-app
85
- window.addEventListener('message', (event) => {
86
- if (!/melio\.com|melioservices\.com/.test(event.origin)) {
87
- return;
88
- }
89
- const { type, ...data } = event.data;
90
- console.log('📬 Received message from iframe:', {
91
- type,
92
- data,
93
- origin: event.origin,
94
- });
95
- switch (type) {
96
- case 'FLOW_COMPLETED':
97
- this.emit('completed', data);
98
- break;
99
- case 'FLOW_EXIT':
100
- this.emit('exit');
101
- break;
102
- case 'NAVIGATED_TO_TARGET':
103
- this.emit('navigated', data);
104
- break;
105
- case 'AUTHENTICATION_SUCCESS':
106
- this.emit('authenticationSucceeded');
107
- break;
108
- case 'AUTHENTICATION_ERROR':
109
- this.emit('authenticationFailed');
110
- break;
111
- case 'ONBOARDING_COMPLETED':
112
- this.emit('completed', { flowName: 'onboarding', ...data });
113
- break;
114
- case 'READY_FOR_INTERACTION':
115
- this.emit('loaded');
116
- break;
117
- case 'MELIO_ERROR':
118
- if (data.code === 'failed_to_sync_bills') {
119
- this.emit('error', { errorCode: 'billsSyncFailed' });
120
- }
121
- break;
122
- case 'HEIGHT_CHANGE':
123
- if (this.iframe) {
124
- this.iframe.style.height = `${data.height}px`;
125
- }
126
- break;
127
- }
128
- });
129
- }
130
- emit(event, data) {
131
- const listeners = this.eventListeners.get(event);
132
- if (listeners) {
133
- listeners.forEach((callback) => {
134
- try {
135
- callback(data);
136
- }
137
- catch (error) {
138
- console.error(`Error in ${event} event listener:`, error);
139
- }
140
- });
141
- }
142
- }
143
- on(event, callback) {
144
- if (!this.eventListeners.has(event)) {
145
- this.eventListeners.set(event, new Set());
146
- }
147
- this.eventListeners.get(event).add(callback);
148
- }
149
- /**
150
- * Remove an event listener
151
- */
152
- off(event, callback) {
153
- const listeners = this.eventListeners.get(event);
154
- if (listeners) {
155
- listeners.delete(callback);
156
- }
157
- }
158
- /**
159
- * Close the flow and clean up resources
160
- */
161
- close() {
162
- if (this.keepAliveInterval) {
163
- clearInterval(this.keepAliveInterval);
164
- this.keepAliveInterval = null;
165
- }
166
- if (this.iframe && this.container) {
167
- this.container.removeChild(this.iframe);
168
- this.iframe = null;
169
- }
170
- this.eventListeners.clear();
171
- }
172
- }
173
- /**
174
- * OnboardingFlow subclass with custom URL construction
175
- */
176
- class OnboardingFlow extends Flow {
177
- constructor(containerId, config, partnerName, environment, authCode, branchOverride) {
178
- super(containerId, config, partnerName, environment, branchOverride);
179
- const { userDetails, organizationDetails, enforceOnboarding } = config;
180
- this.preFilledParams = { userDetails, organizationDetails };
181
- this.enforceOnboarding = enforceOnboarding;
182
- this.authCode = authCode;
183
- if (isEmptyString(this.authCode)) {
184
- throw new Error('Authorization code is required for onboarding flow. Please call init() before opening onboarding flow.');
185
- }
186
- }
187
- /**
188
- * Construct the specific flow URL for onboarding - now goes through /auth
189
- */
190
- constructFlowUrl(baseUrl) {
191
- const params = Object.fromEntries(Object.entries(this.preFilledParams).filter(([_, v]) => v !== undefined));
192
- let preFilledBase64 = '';
193
- if (Object.keys(params).length > 0) {
194
- try {
195
- const json = JSON.stringify(params);
196
- preFilledBase64 = btoa(unescape(encodeURIComponent(json)));
197
- }
198
- catch (e) {
199
- preFilledBase64 = '';
200
- }
201
- }
202
- const targetQueryParams = [];
203
- if (preFilledBase64) {
204
- targetQueryParams.push(`preFilledParams=${encodeURIComponent(preFilledBase64)}`);
205
- }
206
- if (this.enforceOnboarding !== undefined) {
207
- targetQueryParams.push(`enforceOnboarding=${this.enforceOnboarding}`);
208
- }
209
- targetQueryParams.push(`externalOrigin=${this.partnerName}`);
210
- const target = `/welcome?${targetQueryParams.join('&')}`;
211
- const encodedTarget = encodeURIComponent(target);
212
- return `${baseUrl}/${this.partnerName}/auth?token=${this.authCode}&redirectUrl=${encodedTarget}`;
213
- }
214
- }
215
- /**
216
- * PayFlow subclass with custom URL construction
217
- */
218
- class PayFlow extends Flow {
219
- constructor(containerId, config, partnerName, environment, authCode, branchOverride) {
220
- super(containerId, config, partnerName, environment, branchOverride);
221
- this.billIds = config.billIds;
222
- this.authCode = authCode;
223
- if (isEmptyString(this.authCode)) {
224
- throw new Error('Authorization code is required for pay flow. Please call init() before opening pay flow.');
225
- }
226
- }
227
- /**
228
- * Construct the specific flow URL for bill payment - now goes through /auth
229
- */
230
- constructFlowUrl(baseUrl) {
231
- const billIdsParam = this.billIds.join(',');
232
- const target = `/external-entries/payment-flow?billIds=${billIdsParam}&externalOrigin=${this.partnerName}`;
233
- const encodedTarget = encodeURIComponent(target);
234
- return `${baseUrl}/${this.partnerName}/auth?token=${this.authCode}&redirectUrl=${encodedTarget}`;
235
- }
236
- }
237
- /**
238
- * SettingsFlow subclass with custom URL construction
239
- */
240
- class SettingsFlow extends Flow {
241
- constructor(containerId, config, partnerName, environment, authCode, branchOverride) {
242
- super(containerId, config, partnerName, environment, branchOverride);
243
- this.authCode = authCode;
244
- if (isEmptyString(this.authCode)) {
245
- throw new Error('Authorization code is required for settings flow. Please call init() before opening settings flow.');
246
- }
247
- }
248
- /**
249
- * Construct the specific flow URL for settings - now goes through /auth
250
- */
251
- constructFlowUrl(baseUrl) {
252
- const target = '/view-settings';
253
- const encodedTarget = encodeURIComponent(target);
254
- return `${baseUrl}/${this.partnerName}/auth?token=${this.authCode}&redirectUrl=${encodedTarget}`;
255
- }
256
- }
257
- class PaymentsDashboardFlow extends Flow {
258
- constructor(containerId, config, partnerName, environment, authCode, branchOverride) {
259
- super(containerId, config, partnerName, environment, branchOverride);
260
- this.paymentId = config.paymentId;
261
- this.authCode = authCode;
262
- if (isEmptyString(this.authCode)) {
263
- throw new Error('Authorization code is required for payments dashboard flow. Please call init() before opening payments dashboard flow.');
264
- }
265
- }
266
- /**
267
- * Construct the specific flow URL for payments dashboard - now goes through /auth
268
- */
269
- constructFlowUrl(baseUrl) {
270
- const target = `/pay-dashboard/payments${this.paymentId ? `/${this.paymentId}` : ''}`;
271
- const encodedTarget = encodeURIComponent(target);
272
- return `${baseUrl}/${this.partnerName}/auth?token=${this.authCode}&redirectUrl=${encodedTarget}`;
273
- }
274
- }
275
- /**
276
- * InitFlow subclass for handling initialization with callbacks
277
- */
278
- class InitFlow extends Flow {
279
- constructor(containerId, config, partnerName, environment, branchOverride) {
280
- super(containerId, config, partnerName, environment, branchOverride);
281
- if (isEmptyString(config.authCode)) {
282
- throw new Error('Authorization code is required for init flow');
283
- }
284
- this.authorizationCode = config.authCode;
285
- }
286
- /**
287
- * Initialize the init flow by creating and injecting a hidden iframe
288
- */
289
- async initialize() {
290
- this.emit('authenticationSucceeded');
291
- }
292
- /**
293
- * Construct the specific flow URL for initialization
294
- */
295
- constructFlowUrl(baseUrl) {
296
- const params = new URLSearchParams({
297
- token: this.authorizationCode,
298
- theme: this.partnerName,
299
- });
300
- return `${baseUrl}/${this.partnerName}/auth?${params.toString()}`;
301
- }
302
- setupKeepAlive() {
303
- this.keepAliveInterval = window.setInterval(() => {
304
- if (this.iframe && this.iframe.contentWindow) {
305
- this.iframe.contentWindow.postMessage({ type: 'USER_ACTIVE_PING' }, '*');
306
- }
307
- }, 30000); // Send ping every 30 seconds
308
- }
309
- }
1
+ import { InitFlow, OnboardingFlow, PayFlow, SettingsFlow, PaymentsDashboardFlow, isEmptyString, } from './flows/index.js';
310
2
  /**
311
3
  * Main SDK implementation - now partner agnostic
312
4
  */
@@ -332,11 +24,10 @@ export class MelioSDK {
332
24
  });
333
25
  const initFlow = new InitFlow('', // no need container id for init flow as it is created inside the initialization
334
26
  { authCode: authenticationCode, containerId: '' }, this.partnerName, this.environment, this.branchOverride);
335
- // Defer to next tick to prevent race condition
336
- setTimeout(() => {
337
- initFlow.initialize();
338
- console.log('InitFlow initialized successfully');
339
- }, 0);
27
+ initFlow.initialize();
28
+ console.log('InitFlow initialized successfully');
29
+ if (options.keepAlive)
30
+ initFlow.setupKeepAlive();
340
31
  return initFlow;
341
32
  }
342
33
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melio-eng/web-sdk",
3
- "version": "1.0.29",
3
+ "version": "1.0.30-pr.58.0d922c4",
4
4
  "description": "Melio Web SDK - Embed core Melio workflows directly into partner UI with minimal effort",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",