@imtbl/auth 2.10.7-alpha.2
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/.eslintrc.cjs +18 -0
- package/LICENSE.md +176 -0
- package/dist/browser/index.mjs +397 -0
- package/dist/node/index.js +416 -0
- package/dist/node/index.mjs +397 -0
- package/dist/types/authManager.d.ts +62 -0
- package/dist/types/config.d.ts +19 -0
- package/dist/types/confirmation/confirmation.d.ts +28 -0
- package/dist/types/confirmation/embeddedLoginPrompt.d.ts +10 -0
- package/dist/types/confirmation/index.d.ts +3 -0
- package/dist/types/confirmation/popup.d.ts +8 -0
- package/dist/types/confirmation/types.d.ts +33 -0
- package/dist/types/errors.d.ts +30 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/overlay/confirmationOverlay.d.ts +17 -0
- package/dist/types/overlay/constants.d.ts +7 -0
- package/dist/types/overlay/elements.d.ts +12 -0
- package/dist/types/overlay/embeddedLoginPromptOverlay.d.ts +7 -0
- package/dist/types/storage/LocalForageAsyncStorage.d.ts +11 -0
- package/dist/types/storage/device_credentials_manager.d.ts +6 -0
- package/dist/types/types.d.ts +112 -0
- package/dist/types/utils/logger.d.ts +4 -0
- package/dist/types/utils/token.d.ts +2 -0
- package/package.json +49 -0
- package/src/authManager.ts +659 -0
- package/src/config.ts +70 -0
- package/src/confirmation/confirmation.ts +275 -0
- package/src/confirmation/embeddedLoginPrompt.ts +146 -0
- package/src/confirmation/index.ts +3 -0
- package/src/confirmation/popup.ts +41 -0
- package/src/confirmation/types.ts +36 -0
- package/src/errors.ts +62 -0
- package/src/index.ts +33 -0
- package/src/overlay/confirmationOverlay.ts +85 -0
- package/src/overlay/constants.ts +221 -0
- package/src/overlay/elements.ts +187 -0
- package/src/overlay/embeddedLoginPromptOverlay.ts +37 -0
- package/src/storage/LocalForageAsyncStorage.ts +34 -0
- package/src/storage/device_credentials_manager.ts +34 -0
- package/src/types.ts +128 -0
- package/src/utils/logger.ts +15 -0
- package/src/utils/token.ts +35 -0
- package/tsconfig.json +15 -0
package/src/config.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OidcConfiguration,
|
|
3
|
+
AuthModuleConfiguration,
|
|
4
|
+
PopupOverlayOptions,
|
|
5
|
+
} from './types';
|
|
6
|
+
import { PassportError, PassportErrorType } from './errors';
|
|
7
|
+
|
|
8
|
+
const validateConfiguration = <T>(
|
|
9
|
+
configuration: T,
|
|
10
|
+
requiredKeys: Array<keyof T>,
|
|
11
|
+
prefix?: string,
|
|
12
|
+
) => {
|
|
13
|
+
const missingKeys = requiredKeys
|
|
14
|
+
.map((key) => !configuration[key] && key)
|
|
15
|
+
.filter((n) => n)
|
|
16
|
+
.join(', ');
|
|
17
|
+
if (missingKeys !== '') {
|
|
18
|
+
const errorMessage = prefix
|
|
19
|
+
? `${prefix} - ${missingKeys} cannot be null`
|
|
20
|
+
: `${missingKeys} cannot be null`;
|
|
21
|
+
throw new PassportError(
|
|
22
|
+
errorMessage,
|
|
23
|
+
PassportErrorType.INVALID_CONFIGURATION,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Interface that any configuration must implement to work with AuthManager
|
|
30
|
+
*/
|
|
31
|
+
export interface IAuthConfiguration {
|
|
32
|
+
readonly authenticationDomain: string;
|
|
33
|
+
readonly passportDomain: string;
|
|
34
|
+
readonly oidcConfiguration: OidcConfiguration;
|
|
35
|
+
readonly crossSdkBridgeEnabled: boolean;
|
|
36
|
+
readonly popupOverlayOptions?: PopupOverlayOptions;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class AuthConfiguration implements IAuthConfiguration {
|
|
40
|
+
readonly authenticationDomain: string;
|
|
41
|
+
|
|
42
|
+
readonly passportDomain: string;
|
|
43
|
+
|
|
44
|
+
readonly oidcConfiguration: OidcConfiguration;
|
|
45
|
+
|
|
46
|
+
readonly crossSdkBridgeEnabled: boolean;
|
|
47
|
+
|
|
48
|
+
readonly popupOverlayOptions?: PopupOverlayOptions;
|
|
49
|
+
|
|
50
|
+
constructor({
|
|
51
|
+
authenticationDomain,
|
|
52
|
+
passportDomain,
|
|
53
|
+
crossSdkBridgeEnabled,
|
|
54
|
+
popupOverlayOptions,
|
|
55
|
+
...oidcConfiguration
|
|
56
|
+
}: AuthModuleConfiguration) {
|
|
57
|
+
validateConfiguration(oidcConfiguration, [
|
|
58
|
+
'clientId',
|
|
59
|
+
'redirectUri',
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
this.oidcConfiguration = oidcConfiguration;
|
|
63
|
+
this.crossSdkBridgeEnabled = crossSdkBridgeEnabled || false;
|
|
64
|
+
this.popupOverlayOptions = popupOverlayOptions;
|
|
65
|
+
|
|
66
|
+
// Default to production auth domain if not provided
|
|
67
|
+
this.authenticationDomain = authenticationDomain || 'https://auth.immutable.com';
|
|
68
|
+
this.passportDomain = passportDomain || 'https://passport.immutable.com';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import * as GeneratedClients from '@imtbl/generated-clients';
|
|
2
|
+
import { trackError } from '@imtbl/metrics';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ConfirmationResult,
|
|
6
|
+
PASSPORT_CONFIRMATION_EVENT_TYPE,
|
|
7
|
+
ConfirmationReceiveMessage,
|
|
8
|
+
ConfirmationSendMessage,
|
|
9
|
+
} from './types';
|
|
10
|
+
import { openPopupCenter } from './popup';
|
|
11
|
+
import { IAuthConfiguration } from '../config';
|
|
12
|
+
import ConfirmationOverlay from '../overlay/confirmationOverlay';
|
|
13
|
+
|
|
14
|
+
const CONFIRMATION_WINDOW_TITLE = 'Confirm this transaction';
|
|
15
|
+
const CONFIRMATION_WINDOW_HEIGHT = 720;
|
|
16
|
+
const CONFIRMATION_WINDOW_WIDTH = 480;
|
|
17
|
+
const CONFIRMATION_WINDOW_CLOSED_POLLING_DURATION = 1000;
|
|
18
|
+
|
|
19
|
+
export const CONFIRMATION_IFRAME_ID = 'passport-confirm';
|
|
20
|
+
export const CONFIRMATION_IFRAME_STYLE = 'display: none; position: absolute;width:0px;height:0px;border:0;';
|
|
21
|
+
|
|
22
|
+
type MessageHandler = (arg0: MessageEvent) => void;
|
|
23
|
+
|
|
24
|
+
type MessageType = 'erc191' | 'eip712';
|
|
25
|
+
|
|
26
|
+
export default class ConfirmationScreen {
|
|
27
|
+
private config: IAuthConfiguration;
|
|
28
|
+
|
|
29
|
+
private confirmationWindow: Window | undefined;
|
|
30
|
+
|
|
31
|
+
private popupOptions: { width: number; height: number } | undefined;
|
|
32
|
+
|
|
33
|
+
private overlay: ConfirmationOverlay | undefined;
|
|
34
|
+
|
|
35
|
+
private overlayClosed: boolean;
|
|
36
|
+
|
|
37
|
+
private timer: NodeJS.Timeout | undefined;
|
|
38
|
+
|
|
39
|
+
constructor(config: IAuthConfiguration) {
|
|
40
|
+
this.config = config;
|
|
41
|
+
this.overlayClosed = false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private getHref(relativePath: string, queryStringParams?: { [key: string]: any }) {
|
|
45
|
+
let href = `${this.config.passportDomain}/transaction-confirmation/${relativePath}`;
|
|
46
|
+
|
|
47
|
+
if (queryStringParams) {
|
|
48
|
+
const queryString = queryStringParams
|
|
49
|
+
? Object.keys(queryStringParams)
|
|
50
|
+
.map((key) => `${key}=${queryStringParams[key]}`)
|
|
51
|
+
.join('&')
|
|
52
|
+
: '';
|
|
53
|
+
|
|
54
|
+
href = `${href}?${queryString}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return href;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
requestConfirmation(
|
|
61
|
+
transactionId: string,
|
|
62
|
+
etherAddress: string,
|
|
63
|
+
chainType: GeneratedClients.mr.TransactionApprovalRequestChainTypeEnum,
|
|
64
|
+
chainId?: string,
|
|
65
|
+
): Promise<ConfirmationResult> {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const messageHandler = ({ data, origin }: MessageEvent) => {
|
|
68
|
+
if (
|
|
69
|
+
origin !== this.config.passportDomain
|
|
70
|
+
|| data.eventType !== PASSPORT_CONFIRMATION_EVENT_TYPE
|
|
71
|
+
) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
switch (data.messageType as ConfirmationReceiveMessage) {
|
|
76
|
+
case ConfirmationReceiveMessage.CONFIRMATION_WINDOW_READY: {
|
|
77
|
+
this.confirmationWindow?.postMessage({
|
|
78
|
+
eventType: PASSPORT_CONFIRMATION_EVENT_TYPE,
|
|
79
|
+
messageType: ConfirmationSendMessage.CONFIRMATION_START,
|
|
80
|
+
}, this.config.passportDomain);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case ConfirmationReceiveMessage.TRANSACTION_CONFIRMED: {
|
|
84
|
+
this.closeWindow();
|
|
85
|
+
resolve({ confirmed: true });
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case ConfirmationReceiveMessage.TRANSACTION_REJECTED: {
|
|
89
|
+
this.closeWindow();
|
|
90
|
+
resolve({ confirmed: false });
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case ConfirmationReceiveMessage.TRANSACTION_ERROR: {
|
|
94
|
+
this.closeWindow();
|
|
95
|
+
reject(new Error('Error during transaction confirmation'));
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
default:
|
|
99
|
+
this.closeWindow();
|
|
100
|
+
reject(new Error('Unsupported message type'));
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
let href = '';
|
|
105
|
+
if (chainType === GeneratedClients.mr.TransactionApprovalRequestChainTypeEnum.Starkex) {
|
|
106
|
+
href = this.getHref('transaction', { transactionId, etherAddress, chainType });
|
|
107
|
+
} else {
|
|
108
|
+
href = this.getHref('zkevm/transaction', {
|
|
109
|
+
transactionID: transactionId, etherAddress, chainType, chainID: chainId,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
window.addEventListener('message', messageHandler);
|
|
113
|
+
this.showConfirmationScreen(href, messageHandler, resolve);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
requestMessageConfirmation(
|
|
118
|
+
messageID: string,
|
|
119
|
+
etherAddress: string,
|
|
120
|
+
messageType?: MessageType,
|
|
121
|
+
): Promise<ConfirmationResult> {
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
const messageHandler = ({ data, origin }: MessageEvent) => {
|
|
124
|
+
if (
|
|
125
|
+
origin !== this.config.passportDomain
|
|
126
|
+
|| data.eventType !== PASSPORT_CONFIRMATION_EVENT_TYPE
|
|
127
|
+
) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
switch (data.messageType as ConfirmationReceiveMessage) {
|
|
131
|
+
case ConfirmationReceiveMessage.CONFIRMATION_WINDOW_READY: {
|
|
132
|
+
this.confirmationWindow?.postMessage({
|
|
133
|
+
eventType: PASSPORT_CONFIRMATION_EVENT_TYPE,
|
|
134
|
+
messageType: ConfirmationSendMessage.CONFIRMATION_START,
|
|
135
|
+
}, this.config.passportDomain);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case ConfirmationReceiveMessage.MESSAGE_CONFIRMED: {
|
|
139
|
+
this.closeWindow();
|
|
140
|
+
resolve({ confirmed: true });
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case ConfirmationReceiveMessage.MESSAGE_REJECTED: {
|
|
144
|
+
this.closeWindow();
|
|
145
|
+
resolve({ confirmed: false });
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case ConfirmationReceiveMessage.MESSAGE_ERROR: {
|
|
149
|
+
this.closeWindow();
|
|
150
|
+
reject(new Error('Error during message confirmation'));
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
default:
|
|
154
|
+
this.closeWindow();
|
|
155
|
+
reject(new Error('Unsupported message type'));
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
window.addEventListener('message', messageHandler);
|
|
160
|
+
const href = this.getHref('zkevm/message', {
|
|
161
|
+
messageID,
|
|
162
|
+
etherAddress,
|
|
163
|
+
...(messageType ? { messageType } : {}),
|
|
164
|
+
});
|
|
165
|
+
this.showConfirmationScreen(href, messageHandler, resolve);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
showServiceUnavailable(): Promise<void> {
|
|
170
|
+
return new Promise((_, reject) => {
|
|
171
|
+
this.showConfirmationScreen(
|
|
172
|
+
this.getHref('unavailable'),
|
|
173
|
+
() => {},
|
|
174
|
+
() => {
|
|
175
|
+
this.closeWindow();
|
|
176
|
+
reject(new Error('Service unavailable'));
|
|
177
|
+
},
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
loading(popupOptions?: { width: number; height: number }) {
|
|
183
|
+
if (this.config.crossSdkBridgeEnabled) {
|
|
184
|
+
// There is no need to open a confirmation window if cross-sdk bridge is enabled
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.popupOptions = popupOptions;
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
this.confirmationWindow = openPopupCenter({
|
|
192
|
+
url: this.getHref('loading'),
|
|
193
|
+
title: CONFIRMATION_WINDOW_TITLE,
|
|
194
|
+
width: popupOptions?.width || CONFIRMATION_WINDOW_WIDTH,
|
|
195
|
+
height: popupOptions?.height || CONFIRMATION_WINDOW_HEIGHT,
|
|
196
|
+
});
|
|
197
|
+
this.overlay = new ConfirmationOverlay(this.config.popupOverlayOptions || {});
|
|
198
|
+
} catch (error) {
|
|
199
|
+
// If an error is thrown here then the popup is blocked
|
|
200
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
201
|
+
trackError('passport', 'confirmationPopupDenied', new Error(errorMessage));
|
|
202
|
+
this.overlay = new ConfirmationOverlay(this.config.popupOverlayOptions || {}, true);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.overlay.append(
|
|
206
|
+
() => {
|
|
207
|
+
try {
|
|
208
|
+
this.confirmationWindow?.close();
|
|
209
|
+
this.confirmationWindow = openPopupCenter({
|
|
210
|
+
url: this.getHref('loading'),
|
|
211
|
+
title: CONFIRMATION_WINDOW_TITLE,
|
|
212
|
+
width: this.popupOptions?.width || CONFIRMATION_WINDOW_WIDTH,
|
|
213
|
+
height: this.popupOptions?.height || CONFIRMATION_WINDOW_HEIGHT,
|
|
214
|
+
});
|
|
215
|
+
} catch { /* Empty */ }
|
|
216
|
+
},
|
|
217
|
+
() => {
|
|
218
|
+
this.overlayClosed = true;
|
|
219
|
+
this.closeWindow();
|
|
220
|
+
},
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
closeWindow() {
|
|
225
|
+
this.confirmationWindow?.close();
|
|
226
|
+
this.overlay?.remove();
|
|
227
|
+
this.overlay = undefined;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
showConfirmationScreen(href: string, messageHandler: MessageHandler, resolve: Function) {
|
|
231
|
+
// If popup blocked, the confirmation window will not exist
|
|
232
|
+
if (this.confirmationWindow) {
|
|
233
|
+
this.confirmationWindow.location.href = href;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// This indicates the user closed the overlay so the transaction should be rejected
|
|
237
|
+
if (!this.overlay) {
|
|
238
|
+
this.overlayClosed = false;
|
|
239
|
+
resolve({ confirmed: false });
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// https://stackoverflow.com/questions/9388380/capture-the-close-event-of-popup-window-in-javascript/48240128#48240128
|
|
244
|
+
const timerCallback = () => {
|
|
245
|
+
if (this.confirmationWindow?.closed || this.overlayClosed) {
|
|
246
|
+
clearInterval(this.timer);
|
|
247
|
+
window.removeEventListener('message', messageHandler);
|
|
248
|
+
resolve({ confirmed: false });
|
|
249
|
+
this.overlayClosed = false;
|
|
250
|
+
this.confirmationWindow = undefined;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
this.timer = setInterval(
|
|
254
|
+
timerCallback,
|
|
255
|
+
CONFIRMATION_WINDOW_CLOSED_POLLING_DURATION,
|
|
256
|
+
);
|
|
257
|
+
this.overlay.update(() => this.recreateConfirmationWindow(href, timerCallback));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private recreateConfirmationWindow(href: string, timerCallback: () => void) {
|
|
261
|
+
try {
|
|
262
|
+
// Clears and recreates the timer to ensure when the confirmation window
|
|
263
|
+
// is closed and recreated the transaction is not rejected.
|
|
264
|
+
clearInterval(this.timer);
|
|
265
|
+
this.confirmationWindow?.close();
|
|
266
|
+
this.confirmationWindow = openPopupCenter({
|
|
267
|
+
url: href,
|
|
268
|
+
title: CONFIRMATION_WINDOW_TITLE,
|
|
269
|
+
width: this.popupOptions?.width || CONFIRMATION_WINDOW_WIDTH,
|
|
270
|
+
height: this.popupOptions?.height || CONFIRMATION_WINDOW_HEIGHT,
|
|
271
|
+
});
|
|
272
|
+
this.timer = setInterval(timerCallback, CONFIRMATION_WINDOW_CLOSED_POLLING_DURATION);
|
|
273
|
+
} catch { /* Empty */ }
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Detail, getDetail } from '@imtbl/metrics';
|
|
2
|
+
import {
|
|
3
|
+
EMBEDDED_LOGIN_PROMPT_EVENT_TYPE,
|
|
4
|
+
EmbeddedLoginPromptResult,
|
|
5
|
+
EmbeddedLoginPromptReceiveMessage,
|
|
6
|
+
} from './types';
|
|
7
|
+
import { IAuthConfiguration } from '../config';
|
|
8
|
+
import EmbeddedLoginPromptOverlay from '../overlay/embeddedLoginPromptOverlay';
|
|
9
|
+
|
|
10
|
+
const LOGIN_PROMPT_WINDOW_HEIGHT = 660;
|
|
11
|
+
const LOGIN_PROMPT_WINDOW_WIDTH = 440;
|
|
12
|
+
const LOGIN_PROMPT_WINDOW_BORDER_RADIUS = '16px';
|
|
13
|
+
const LOGIN_PROMPT_KEYFRAME_STYLES_ID = 'passport-embedded-login-keyframes';
|
|
14
|
+
const LOGIN_PROMPT_IFRAME_ID = 'passport-embedded-login-iframe';
|
|
15
|
+
|
|
16
|
+
export default class EmbeddedLoginPrompt {
|
|
17
|
+
private config: IAuthConfiguration;
|
|
18
|
+
|
|
19
|
+
constructor(config: IAuthConfiguration) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private getHref = (anonymousId?: string) => {
|
|
24
|
+
let href = `${this.config.authenticationDomain}/im-embedded-login-prompt`
|
|
25
|
+
+ `?client_id=${this.config.oidcConfiguration.clientId}`
|
|
26
|
+
+ `&rid=${getDetail(Detail.RUNTIME_ID)}`;
|
|
27
|
+
|
|
28
|
+
if (anonymousId) {
|
|
29
|
+
href += `&third_party_a_id=${anonymousId}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return href;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
private static appendIFrameStylesIfNeeded = () => {
|
|
36
|
+
if (document.getElementById(LOGIN_PROMPT_KEYFRAME_STYLES_ID)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const style = document.createElement('style');
|
|
41
|
+
style.id = LOGIN_PROMPT_KEYFRAME_STYLES_ID;
|
|
42
|
+
style.textContent = `
|
|
43
|
+
@keyframes passportEmbeddedLoginPromptPopBounceIn {
|
|
44
|
+
0% {
|
|
45
|
+
opacity: 0.5;
|
|
46
|
+
}
|
|
47
|
+
50% {
|
|
48
|
+
opacity: 1;
|
|
49
|
+
transform: scale(1.05);
|
|
50
|
+
}
|
|
51
|
+
75% {
|
|
52
|
+
transform: scale(0.98);
|
|
53
|
+
}
|
|
54
|
+
100% {
|
|
55
|
+
opacity: 1;
|
|
56
|
+
transform: scale(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@media (max-height: 400px) {
|
|
61
|
+
#${LOGIN_PROMPT_IFRAME_ID} {
|
|
62
|
+
width: 100% !important;
|
|
63
|
+
max-width: none !important;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@keyframes passportEmbeddedLoginPromptOverlayFadeIn {
|
|
68
|
+
from {
|
|
69
|
+
opacity: 0;
|
|
70
|
+
}
|
|
71
|
+
to {
|
|
72
|
+
opacity: 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
document.head.appendChild(style);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
private getEmbeddedLoginIFrame = (anonymousId?: string) => {
|
|
81
|
+
const embeddedLoginPrompt = document.createElement('iframe');
|
|
82
|
+
embeddedLoginPrompt.id = LOGIN_PROMPT_IFRAME_ID;
|
|
83
|
+
embeddedLoginPrompt.src = this.getHref(anonymousId);
|
|
84
|
+
embeddedLoginPrompt.style.height = '100vh';
|
|
85
|
+
embeddedLoginPrompt.style.width = '100vw';
|
|
86
|
+
embeddedLoginPrompt.style.maxHeight = `${LOGIN_PROMPT_WINDOW_HEIGHT}px`;
|
|
87
|
+
embeddedLoginPrompt.style.maxWidth = `${LOGIN_PROMPT_WINDOW_WIDTH}px`;
|
|
88
|
+
embeddedLoginPrompt.style.borderRadius = LOGIN_PROMPT_WINDOW_BORDER_RADIUS;
|
|
89
|
+
|
|
90
|
+
// Animation styles
|
|
91
|
+
embeddedLoginPrompt.style.opacity = '0';
|
|
92
|
+
embeddedLoginPrompt.style.transform = 'scale(0.6)';
|
|
93
|
+
embeddedLoginPrompt.style.animation = 'passportEmbeddedLoginPromptPopBounceIn 1s ease forwards';
|
|
94
|
+
EmbeddedLoginPrompt.appendIFrameStylesIfNeeded();
|
|
95
|
+
|
|
96
|
+
return embeddedLoginPrompt;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
public displayEmbeddedLoginPrompt(anonymousId?: string): Promise<EmbeddedLoginPromptResult> {
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const embeddedLoginPrompt = this.getEmbeddedLoginIFrame(anonymousId);
|
|
102
|
+
const messageHandler = ({ data, origin }: MessageEvent) => {
|
|
103
|
+
if (
|
|
104
|
+
origin !== this.config.authenticationDomain
|
|
105
|
+
|| data.eventType !== EMBEDDED_LOGIN_PROMPT_EVENT_TYPE
|
|
106
|
+
) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
switch (data.messageType as EmbeddedLoginPromptReceiveMessage) {
|
|
111
|
+
case EmbeddedLoginPromptReceiveMessage.LOGIN_METHOD_SELECTED: {
|
|
112
|
+
const result = data.payload as EmbeddedLoginPromptResult;
|
|
113
|
+
window.removeEventListener('message', messageHandler);
|
|
114
|
+
EmbeddedLoginPromptOverlay.remove();
|
|
115
|
+
resolve(result);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case EmbeddedLoginPromptReceiveMessage.LOGIN_PROMPT_ERROR: {
|
|
119
|
+
window.removeEventListener('message', messageHandler);
|
|
120
|
+
EmbeddedLoginPromptOverlay.remove();
|
|
121
|
+
reject(new Error('Error during embedded login prompt', { cause: data.payload }));
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case EmbeddedLoginPromptReceiveMessage.LOGIN_PROMPT_CLOSED: {
|
|
125
|
+
window.removeEventListener('message', messageHandler);
|
|
126
|
+
EmbeddedLoginPromptOverlay.remove();
|
|
127
|
+
reject(new Error('Popup closed by user'));
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
default:
|
|
131
|
+
window.removeEventListener('message', messageHandler);
|
|
132
|
+
EmbeddedLoginPromptOverlay.remove();
|
|
133
|
+
reject(new Error(`Unsupported message type: ${data.messageType}`));
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
window.addEventListener('message', messageHandler);
|
|
139
|
+
EmbeddedLoginPromptOverlay.appendOverlay(embeddedLoginPrompt, () => {
|
|
140
|
+
window.removeEventListener('message', messageHandler);
|
|
141
|
+
EmbeddedLoginPromptOverlay.remove();
|
|
142
|
+
reject(new Error('Popup closed by user'));
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type PopUpProps = {
|
|
2
|
+
url: string;
|
|
3
|
+
title: string;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
query?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const openPopupCenter = ({
|
|
10
|
+
url,
|
|
11
|
+
title,
|
|
12
|
+
width,
|
|
13
|
+
height,
|
|
14
|
+
}: PopUpProps): Window => {
|
|
15
|
+
const left = Math.max(
|
|
16
|
+
0,
|
|
17
|
+
Math.round(window.screenX + (window.outerWidth - width) / 2),
|
|
18
|
+
);
|
|
19
|
+
const top = Math.max(
|
|
20
|
+
0,
|
|
21
|
+
Math.round(window.screenY + (window.outerHeight - height) / 2),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const newWindow = window.open(
|
|
25
|
+
url,
|
|
26
|
+
title,
|
|
27
|
+
`
|
|
28
|
+
scrollbars=yes,
|
|
29
|
+
width=${width},
|
|
30
|
+
height=${height},
|
|
31
|
+
top=${top},
|
|
32
|
+
left=${left}
|
|
33
|
+
`,
|
|
34
|
+
);
|
|
35
|
+
if (!newWindow) {
|
|
36
|
+
throw new Error('Failed to open confirmation screen');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
newWindow.focus();
|
|
40
|
+
return newWindow;
|
|
41
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { DirectLoginMethod, MarketingConsentStatus } from '../types';
|
|
2
|
+
|
|
3
|
+
export enum ConfirmationSendMessage {
|
|
4
|
+
CONFIRMATION_START = 'confirmation_start',
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export enum ConfirmationReceiveMessage {
|
|
8
|
+
CONFIRMATION_WINDOW_READY = 'confirmation_window_ready',
|
|
9
|
+
TRANSACTION_CONFIRMED = 'transaction_confirmed',
|
|
10
|
+
TRANSACTION_ERROR = 'transaction_error',
|
|
11
|
+
TRANSACTION_REJECTED = 'transaction_rejected',
|
|
12
|
+
MESSAGE_CONFIRMED = 'message_confirmed',
|
|
13
|
+
MESSAGE_ERROR = 'message_error',
|
|
14
|
+
MESSAGE_REJECTED = 'message_rejected',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export enum EmbeddedLoginPromptReceiveMessage {
|
|
18
|
+
LOGIN_METHOD_SELECTED = 'login_method_selected',
|
|
19
|
+
LOGIN_PROMPT_ERROR = 'login_prompt_error',
|
|
20
|
+
LOGIN_PROMPT_CLOSED = 'login_prompt_closed',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type ConfirmationResult = {
|
|
24
|
+
confirmed: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type EmbeddedLoginPromptResult = {
|
|
28
|
+
marketingConsentStatus: MarketingConsentStatus;
|
|
29
|
+
imPassportTraceId: string;
|
|
30
|
+
} & (
|
|
31
|
+
| { directLoginMethod: 'email'; email: string }
|
|
32
|
+
| { directLoginMethod: Exclude<DirectLoginMethod, 'email'>; email?: never }
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export const PASSPORT_CONFIRMATION_EVENT_TYPE = 'imx_passport_confirmation';
|
|
36
|
+
export const EMBEDDED_LOGIN_PROMPT_EVENT_TYPE = 'im_passport_embedded_login_prompt';
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { isAxiosError } from 'axios';
|
|
2
|
+
import { imx } from '@imtbl/generated-clients';
|
|
3
|
+
|
|
4
|
+
export enum PassportErrorType {
|
|
5
|
+
AUTHENTICATION_ERROR = 'AUTHENTICATION_ERROR',
|
|
6
|
+
INVALID_CONFIGURATION = 'INVALID_CONFIGURATION',
|
|
7
|
+
WALLET_CONNECTION_ERROR = 'WALLET_CONNECTION_ERROR',
|
|
8
|
+
NOT_LOGGED_IN_ERROR = 'NOT_LOGGED_IN_ERROR',
|
|
9
|
+
SILENT_LOGIN_ERROR = 'SILENT_LOGIN_ERROR',
|
|
10
|
+
REFRESH_TOKEN_ERROR = 'REFRESH_TOKEN_ERROR',
|
|
11
|
+
USER_REGISTRATION_ERROR = 'USER_REGISTRATION_ERROR',
|
|
12
|
+
USER_NOT_REGISTERED_ERROR = 'USER_NOT_REGISTERED_ERROR',
|
|
13
|
+
LOGOUT_ERROR = 'LOGOUT_ERROR',
|
|
14
|
+
TRANSFER_ERROR = 'TRANSFER_ERROR',
|
|
15
|
+
CREATE_ORDER_ERROR = 'CREATE_ORDER_ERROR',
|
|
16
|
+
CANCEL_ORDER_ERROR = 'CANCEL_ORDER_ERROR',
|
|
17
|
+
EXCHANGE_TRANSFER_ERROR = 'EXCHANGE_TRANSFER_ERROR',
|
|
18
|
+
CREATE_TRADE_ERROR = 'CREATE_TRADE_ERROR',
|
|
19
|
+
OPERATION_NOT_SUPPORTED_ERROR = 'OPERATION_NOT_SUPPORTED_ERROR',
|
|
20
|
+
LINK_WALLET_ALREADY_LINKED_ERROR = 'LINK_WALLET_ALREADY_LINKED_ERROR',
|
|
21
|
+
LINK_WALLET_MAX_WALLETS_LINKED_ERROR = 'LINK_WALLET_MAX_WALLETS_LINKED_ERROR',
|
|
22
|
+
LINK_WALLET_VALIDATION_ERROR = 'LINK_WALLET_VALIDATION_ERROR',
|
|
23
|
+
LINK_WALLET_DUPLICATE_NONCE_ERROR = 'LINK_WALLET_DUPLICATE_NONCE_ERROR',
|
|
24
|
+
LINK_WALLET_GENERIC_ERROR = 'LINK_WALLET_GENERIC_ERROR',
|
|
25
|
+
SERVICE_UNAVAILABLE_ERROR = 'SERVICE_UNAVAILABLE_ERROR',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isAPIError(error: any): error is imx.APIError {
|
|
29
|
+
return 'code' in error && 'message' in error;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class PassportError extends Error {
|
|
33
|
+
public type: PassportErrorType;
|
|
34
|
+
|
|
35
|
+
constructor(message: string, type: PassportErrorType) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.type = type;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const withPassportError = async <T>(
|
|
42
|
+
fn: () => Promise<T>,
|
|
43
|
+
customErrorType: PassportErrorType,
|
|
44
|
+
): Promise<T> => {
|
|
45
|
+
try {
|
|
46
|
+
return await fn();
|
|
47
|
+
} catch (error) {
|
|
48
|
+
let errorMessage: string;
|
|
49
|
+
|
|
50
|
+
if (error instanceof PassportError && error.type === PassportErrorType.SERVICE_UNAVAILABLE_ERROR) {
|
|
51
|
+
throw new PassportError(error.message, error.type);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (isAxiosError(error) && error.response?.data && isAPIError(error.response.data)) {
|
|
55
|
+
errorMessage = error.response.data.message;
|
|
56
|
+
} else {
|
|
57
|
+
errorMessage = (error as Error).message;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
throw new PassportError(errorMessage, customErrorType);
|
|
61
|
+
}
|
|
62
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Export AuthManager for use by other packages
|
|
2
|
+
export { default as AuthManager } from './authManager';
|
|
3
|
+
|
|
4
|
+
// Export configuration
|
|
5
|
+
export { AuthConfiguration, type IAuthConfiguration } from './config';
|
|
6
|
+
|
|
7
|
+
// Export types
|
|
8
|
+
export type {
|
|
9
|
+
User,
|
|
10
|
+
UserProfile,
|
|
11
|
+
UserImx,
|
|
12
|
+
UserZkEvm,
|
|
13
|
+
DirectLoginMethod,
|
|
14
|
+
DirectLoginOptions,
|
|
15
|
+
DeviceTokenResponse,
|
|
16
|
+
OidcConfiguration,
|
|
17
|
+
AuthModuleConfiguration,
|
|
18
|
+
PopupOverlayOptions,
|
|
19
|
+
PassportMetadata,
|
|
20
|
+
IdTokenPayload,
|
|
21
|
+
PKCEData,
|
|
22
|
+
} from './types';
|
|
23
|
+
export {
|
|
24
|
+
isUserZkEvm, isUserImx, RollupType, MarketingConsentStatus,
|
|
25
|
+
} from './types';
|
|
26
|
+
|
|
27
|
+
// Export errors
|
|
28
|
+
export { PassportError, PassportErrorType, withPassportError } from './errors';
|
|
29
|
+
|
|
30
|
+
// Export confirmation and overlay classes
|
|
31
|
+
export { default as ConfirmationScreen } from './confirmation/confirmation';
|
|
32
|
+
export { default as EmbeddedLoginPrompt } from './confirmation/embeddedLoginPrompt';
|
|
33
|
+
export { default as ConfirmationOverlay } from './overlay/confirmationOverlay';
|