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