@imtbl/auth 2.10.7-alpha.3 → 2.10.7-alpha.5
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/browser/index.js +31 -37
- package/dist/node/index.cjs +44 -50
- package/dist/node/index.js +32 -37
- package/dist/types/Auth.d.ts +129 -0
- package/dist/types/authManager.d.ts +5 -6
- package/dist/types/index.d.ts +4 -5
- package/dist/types/{confirmation → login}/embeddedLoginPrompt.d.ts +1 -1
- package/dist/types/overlay/{confirmationOverlay.d.ts → loginPopupOverlay.d.ts} +1 -1
- package/dist/types/types.d.ts +36 -16
- package/dist/types/utils/metrics.d.ts +2 -0
- package/dist/types/utils/typedEventEmitter.d.ts +6 -0
- package/package.json +4 -4
- package/src/Auth.ts +264 -0
- package/src/authManager.ts +17 -33
- package/src/index.ts +9 -7
- package/src/{confirmation → login}/embeddedLoginPrompt.ts +6 -10
- package/src/overlay/{confirmationOverlay.ts → loginPopupOverlay.ts} +1 -1
- package/src/types.ts +36 -16
- package/src/utils/metrics.ts +29 -0
- package/src/utils/typedEventEmitter.ts +26 -0
- package/dist/types/confirmation/confirmation.d.ts +0 -28
- package/dist/types/confirmation/index.d.ts +0 -3
- package/dist/types/confirmation/popup.d.ts +0 -8
- package/src/confirmation/confirmation.ts +0 -275
- package/src/confirmation/index.ts +0 -3
- package/src/confirmation/popup.ts +0 -41
- /package/dist/types/{confirmation → login}/types.d.ts +0 -0
- /package/src/{confirmation → login}/types.ts +0 -0
package/src/authManager.ts
CHANGED
|
@@ -24,13 +24,11 @@ import {
|
|
|
24
24
|
OidcConfiguration,
|
|
25
25
|
UserZkEvm,
|
|
26
26
|
isUserZkEvm,
|
|
27
|
-
UserImx,
|
|
28
|
-
isUserImx,
|
|
29
27
|
} from './types';
|
|
30
28
|
import { IAuthConfiguration } from './config';
|
|
31
|
-
import
|
|
29
|
+
import LoginPopupOverlay from './overlay/loginPopupOverlay';
|
|
30
|
+
import EmbeddedLoginPrompt from './login/embeddedLoginPrompt';
|
|
32
31
|
import { LocalForageAsyncStorage } from './storage/LocalForageAsyncStorage';
|
|
33
|
-
import { EmbeddedLoginPrompt } from './confirmation';
|
|
34
32
|
|
|
35
33
|
const LOGIN_POPUP_CLOSED_POLLING_DURATION = 500;
|
|
36
34
|
|
|
@@ -94,7 +92,7 @@ const getAuthConfiguration = (config: IAuthConfiguration): UserManagerSettings =
|
|
|
94
92
|
return baseConfiguration;
|
|
95
93
|
};
|
|
96
94
|
|
|
97
|
-
function base64URLEncode(str: ArrayBuffer) {
|
|
95
|
+
function base64URLEncode(str: ArrayBuffer | Uint8Array) {
|
|
98
96
|
return btoa(String.fromCharCode(...new Uint8Array(str)))
|
|
99
97
|
.replace(/\+/g, '-')
|
|
100
98
|
.replace(/\//g, '_')
|
|
@@ -148,17 +146,10 @@ export default class AuthManager {
|
|
|
148
146
|
nickname: oidcUser.profile.nickname,
|
|
149
147
|
},
|
|
150
148
|
};
|
|
151
|
-
if (passport?.
|
|
152
|
-
user.imx = {
|
|
153
|
-
ethAddress: passport.imx_eth_address,
|
|
154
|
-
starkAddress: passport.imx_stark_address,
|
|
155
|
-
userAdminAddress: passport.imx_user_admin_address,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
if (passport?.zkevm_eth_address) {
|
|
149
|
+
if (passport?.zkevm_eth_address && passport?.zkevm_user_admin_address) {
|
|
159
150
|
user.zkEvm = {
|
|
160
|
-
ethAddress: passport
|
|
161
|
-
userAdminAddress: passport
|
|
151
|
+
ethAddress: passport.zkevm_eth_address,
|
|
152
|
+
userAdminAddress: passport.zkevm_user_admin_address,
|
|
162
153
|
};
|
|
163
154
|
}
|
|
164
155
|
return user;
|
|
@@ -186,14 +177,13 @@ export default class AuthManager {
|
|
|
186
177
|
};
|
|
187
178
|
|
|
188
179
|
private buildExtraQueryParams(
|
|
189
|
-
anonymousId?: string,
|
|
190
180
|
directLoginOptions?: DirectLoginOptions,
|
|
191
181
|
imPassportTraceId?: string,
|
|
192
182
|
): Record<string, string> {
|
|
193
183
|
const params: Record<string, string> = {
|
|
194
184
|
...(this.userManager.settings?.extraQueryParams ?? {}),
|
|
195
185
|
rid: getDetail(Detail.RUNTIME_ID) || '',
|
|
196
|
-
third_party_a_id:
|
|
186
|
+
third_party_a_id: '',
|
|
197
187
|
};
|
|
198
188
|
|
|
199
189
|
if (directLoginOptions) {
|
|
@@ -221,10 +211,14 @@ export default class AuthManager {
|
|
|
221
211
|
return params;
|
|
222
212
|
}
|
|
223
213
|
|
|
224
|
-
public async
|
|
214
|
+
public async getClientId(): Promise<string> {
|
|
215
|
+
return this.config.oidcConfiguration.clientId;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public async loginWithRedirect(directLoginOptions?: DirectLoginOptions): Promise<void> {
|
|
225
219
|
await this.userManager.clearStaleState();
|
|
226
220
|
return withPassportError<void>(async () => {
|
|
227
|
-
const extraQueryParams = this.buildExtraQueryParams(
|
|
221
|
+
const extraQueryParams = this.buildExtraQueryParams(directLoginOptions);
|
|
228
222
|
|
|
229
223
|
await this.userManager.signinRedirect({
|
|
230
224
|
extraQueryParams,
|
|
@@ -234,13 +228,12 @@ export default class AuthManager {
|
|
|
234
228
|
|
|
235
229
|
/**
|
|
236
230
|
* login
|
|
237
|
-
* @param anonymousId Caller can pass an anonymousId if they want to associate their user's identity with immutable's internal instrumentation.
|
|
238
231
|
* @param directLoginOptions If provided, contains login method and marketing consent options
|
|
239
232
|
* @param directLoginOptions.directLoginMethod The login method to use (e.g., 'google', 'apple', 'email')
|
|
240
233
|
* @param directLoginOptions.marketingConsentStatus Marketing consent status ('opted_in' or 'unsubscribed')
|
|
241
234
|
* @param directLoginOptions.email Required when directLoginMethod is 'email'
|
|
242
235
|
*/
|
|
243
|
-
public async login(
|
|
236
|
+
public async login(directLoginOptions?: DirectLoginOptions): Promise<User> {
|
|
244
237
|
return withPassportError<User>(async () => {
|
|
245
238
|
// If directLoginOptions are provided, then the consumer has rendered their own initial login screen.
|
|
246
239
|
// If not, display the embedded login prompt and pass the returned direct login options and imPassportTraceId to the login popup.
|
|
@@ -252,14 +245,14 @@ export default class AuthManager {
|
|
|
252
245
|
const {
|
|
253
246
|
imPassportTraceId: embeddedLoginPromptImPassportTraceId,
|
|
254
247
|
...embeddedLoginPromptDirectLoginOptions
|
|
255
|
-
} = await this.embeddedLoginPrompt.displayEmbeddedLoginPrompt(
|
|
248
|
+
} = await this.embeddedLoginPrompt.displayEmbeddedLoginPrompt();
|
|
256
249
|
directLoginOptionsToUse = embeddedLoginPromptDirectLoginOptions;
|
|
257
250
|
imPassportTraceId = embeddedLoginPromptImPassportTraceId;
|
|
258
251
|
}
|
|
259
252
|
|
|
260
253
|
const popupWindowTarget = window.crypto.randomUUID();
|
|
261
254
|
const signinPopup = async () => {
|
|
262
|
-
const extraQueryParams = this.buildExtraQueryParams(
|
|
255
|
+
const extraQueryParams = this.buildExtraQueryParams(directLoginOptionsToUse, imPassportTraceId);
|
|
263
256
|
|
|
264
257
|
const userPromise = this.userManager.signinPopup({
|
|
265
258
|
extraQueryParams,
|
|
@@ -316,7 +309,7 @@ export default class AuthManager {
|
|
|
316
309
|
|
|
317
310
|
// Popup was blocked; append the blocked popup overlay to allow the user to try again.
|
|
318
311
|
let popupHasBeenOpened: boolean = false;
|
|
319
|
-
const overlay = new
|
|
312
|
+
const overlay = new LoginPopupOverlay(this.config.popupOverlayOptions || {}, true);
|
|
320
313
|
overlay.append(
|
|
321
314
|
async () => {
|
|
322
315
|
try {
|
|
@@ -647,13 +640,4 @@ export default class AuthManager {
|
|
|
647
640
|
|
|
648
641
|
return user;
|
|
649
642
|
}
|
|
650
|
-
|
|
651
|
-
public async getUserImx(): Promise<UserImx> {
|
|
652
|
-
const user = await this.getUser(isUserImx);
|
|
653
|
-
if (!user) {
|
|
654
|
-
throw new Error('Failed to obtain a User with the required IMX attributes');
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
return user;
|
|
658
|
-
}
|
|
659
643
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Export Auth class (public API)
|
|
2
|
+
export { Auth } from './Auth';
|
|
3
|
+
|
|
1
4
|
// Export AuthManager for use by other packages
|
|
2
5
|
export { default as AuthManager } from './authManager';
|
|
3
6
|
|
|
@@ -8,10 +11,10 @@ export { AuthConfiguration, type IAuthConfiguration } from './config';
|
|
|
8
11
|
export type {
|
|
9
12
|
User,
|
|
10
13
|
UserProfile,
|
|
11
|
-
UserImx,
|
|
12
14
|
UserZkEvm,
|
|
13
15
|
DirectLoginMethod,
|
|
14
16
|
DirectLoginOptions,
|
|
17
|
+
LoginOptions,
|
|
15
18
|
DeviceTokenResponse,
|
|
16
19
|
OidcConfiguration,
|
|
17
20
|
AuthModuleConfiguration,
|
|
@@ -19,15 +22,14 @@ export type {
|
|
|
19
22
|
PassportMetadata,
|
|
20
23
|
IdTokenPayload,
|
|
21
24
|
PKCEData,
|
|
25
|
+
AuthEventMap,
|
|
22
26
|
} from './types';
|
|
23
27
|
export {
|
|
24
|
-
isUserZkEvm,
|
|
28
|
+
isUserZkEvm, RollupType, MarketingConsentStatus, AuthEvents,
|
|
25
29
|
} from './types';
|
|
26
30
|
|
|
31
|
+
// Export TypedEventEmitter
|
|
32
|
+
export { default as TypedEventEmitter } from './utils/typedEventEmitter';
|
|
33
|
+
|
|
27
34
|
// Export errors
|
|
28
35
|
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';
|
|
@@ -20,15 +20,11 @@ export default class EmbeddedLoginPrompt {
|
|
|
20
20
|
this.config = config;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
private getHref = (
|
|
24
|
-
|
|
23
|
+
private getHref = () => {
|
|
24
|
+
const href = `${this.config.authenticationDomain}/im-embedded-login-prompt`
|
|
25
25
|
+ `?client_id=${this.config.oidcConfiguration.clientId}`
|
|
26
26
|
+ `&rid=${getDetail(Detail.RUNTIME_ID)}`;
|
|
27
27
|
|
|
28
|
-
if (anonymousId) {
|
|
29
|
-
href += `&third_party_a_id=${anonymousId}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
28
|
return href;
|
|
33
29
|
};
|
|
34
30
|
|
|
@@ -77,10 +73,10 @@ export default class EmbeddedLoginPrompt {
|
|
|
77
73
|
document.head.appendChild(style);
|
|
78
74
|
};
|
|
79
75
|
|
|
80
|
-
private getEmbeddedLoginIFrame = (
|
|
76
|
+
private getEmbeddedLoginIFrame = () => {
|
|
81
77
|
const embeddedLoginPrompt = document.createElement('iframe');
|
|
82
78
|
embeddedLoginPrompt.id = LOGIN_PROMPT_IFRAME_ID;
|
|
83
|
-
embeddedLoginPrompt.src = this.getHref(
|
|
79
|
+
embeddedLoginPrompt.src = this.getHref();
|
|
84
80
|
embeddedLoginPrompt.style.height = '100vh';
|
|
85
81
|
embeddedLoginPrompt.style.width = '100vw';
|
|
86
82
|
embeddedLoginPrompt.style.maxHeight = `${LOGIN_PROMPT_WINDOW_HEIGHT}px`;
|
|
@@ -96,9 +92,9 @@ export default class EmbeddedLoginPrompt {
|
|
|
96
92
|
return embeddedLoginPrompt;
|
|
97
93
|
};
|
|
98
94
|
|
|
99
|
-
public displayEmbeddedLoginPrompt(
|
|
95
|
+
public displayEmbeddedLoginPrompt(): Promise<EmbeddedLoginPromptResult> {
|
|
100
96
|
return new Promise((resolve, reject) => {
|
|
101
|
-
const embeddedLoginPrompt = this.getEmbeddedLoginIFrame(
|
|
97
|
+
const embeddedLoginPrompt = this.getEmbeddedLoginIFrame();
|
|
102
98
|
const messageHandler = ({ data, origin }: MessageEvent) => {
|
|
103
99
|
if (
|
|
104
100
|
origin !== this.config.authenticationDomain
|
|
@@ -2,7 +2,7 @@ import { PopupOverlayOptions } from '../types';
|
|
|
2
2
|
import { PASSPORT_OVERLAY_CLOSE_ID, PASSPORT_OVERLAY_TRY_AGAIN_ID } from './constants';
|
|
3
3
|
import { addLink, getBlockedOverlay, getGenericOverlay } from './elements';
|
|
4
4
|
|
|
5
|
-
export default class
|
|
5
|
+
export default class LoginPopupOverlay {
|
|
6
6
|
private disableGenericPopupOverlay: boolean;
|
|
7
7
|
|
|
8
8
|
private disableBlockedPopupOverlay: boolean;
|
package/src/types.ts
CHANGED
|
@@ -12,7 +12,6 @@ export type UserProfile = {
|
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export enum RollupType {
|
|
15
|
-
IMX = 'imx',
|
|
16
15
|
ZKEVM = 'zkEvm',
|
|
17
16
|
}
|
|
18
17
|
|
|
@@ -22,11 +21,6 @@ export type User = {
|
|
|
22
21
|
refreshToken?: string;
|
|
23
22
|
profile: UserProfile;
|
|
24
23
|
expired?: boolean;
|
|
25
|
-
[RollupType.IMX]?: {
|
|
26
|
-
ethAddress: string;
|
|
27
|
-
starkAddress: string;
|
|
28
|
-
userAdminAddress: string;
|
|
29
|
-
};
|
|
30
24
|
[RollupType.ZKEVM]?: {
|
|
31
25
|
ethAddress: string;
|
|
32
26
|
userAdminAddress: string;
|
|
@@ -34,11 +28,8 @@ export type User = {
|
|
|
34
28
|
};
|
|
35
29
|
|
|
36
30
|
export type PassportMetadata = {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
imx_user_admin_address: string;
|
|
40
|
-
zkevm_eth_address: string;
|
|
41
|
-
zkevm_user_admin_address: string;
|
|
31
|
+
zkevm_eth_address?: string;
|
|
32
|
+
zkevm_user_admin_address?: string;
|
|
42
33
|
};
|
|
43
34
|
|
|
44
35
|
export interface OidcConfiguration {
|
|
@@ -82,11 +73,9 @@ export interface AuthModuleConfiguration extends OidcConfiguration {
|
|
|
82
73
|
|
|
83
74
|
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
|
|
84
75
|
|
|
85
|
-
export type UserImx = WithRequired<User, RollupType.IMX>;
|
|
86
76
|
export type UserZkEvm = WithRequired<User, RollupType.ZKEVM>;
|
|
87
77
|
|
|
88
78
|
export const isUserZkEvm = (user: User): user is UserZkEvm => !!user[RollupType.ZKEVM];
|
|
89
|
-
export const isUserImx = (user: User): user is UserImx => !!user[RollupType.IMX];
|
|
90
79
|
|
|
91
80
|
export type DeviceTokenResponse = {
|
|
92
81
|
access_token: string;
|
|
@@ -122,7 +111,38 @@ export enum MarketingConsentStatus {
|
|
|
122
111
|
}
|
|
123
112
|
|
|
124
113
|
export type DirectLoginOptions = {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
email
|
|
114
|
+
marketingConsentStatus: MarketingConsentStatus;
|
|
115
|
+
} & (
|
|
116
|
+
| { directLoginMethod: 'email'; email: string }
|
|
117
|
+
| { directLoginMethod: Exclude<DirectLoginMethod, 'email'>; email?: never }
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Extended login options with caching and silent login support
|
|
122
|
+
*/
|
|
123
|
+
export type LoginOptions = {
|
|
124
|
+
/** If true, attempts to use cached session without user interaction */
|
|
125
|
+
useCachedSession?: boolean;
|
|
126
|
+
/** If true, attempts silent authentication (force token refresh) */
|
|
127
|
+
useSilentLogin?: boolean;
|
|
128
|
+
/** If true, uses redirect flow instead of popup flow */
|
|
129
|
+
useRedirectFlow?: boolean;
|
|
130
|
+
/** Direct login options (social provider, email, etc.) */
|
|
131
|
+
directLoginOptions?: DirectLoginOptions;
|
|
128
132
|
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Authentication events emitted by the Auth class
|
|
136
|
+
*/
|
|
137
|
+
export enum AuthEvents {
|
|
138
|
+
LOGGED_OUT = 'loggedOut',
|
|
139
|
+
LOGGED_IN = 'loggedIn',
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Event map for typed event emitter
|
|
144
|
+
*/
|
|
145
|
+
export interface AuthEventMap extends Record<string, any> {
|
|
146
|
+
[AuthEvents.LOGGED_OUT]: [];
|
|
147
|
+
[AuthEvents.LOGGED_IN]: [User];
|
|
148
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Flow, trackError, trackFlow } from '@imtbl/metrics';
|
|
2
|
+
|
|
3
|
+
export const withMetricsAsync = async <T>(
|
|
4
|
+
fn: (flow: Flow) => Promise<T>,
|
|
5
|
+
flowName: string,
|
|
6
|
+
trackStartEvent: boolean = true,
|
|
7
|
+
trackEndEvent: boolean = true,
|
|
8
|
+
): Promise<T> => {
|
|
9
|
+
const flow: Flow = trackFlow(
|
|
10
|
+
'passport',
|
|
11
|
+
flowName,
|
|
12
|
+
trackStartEvent,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
return await fn(flow);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error instanceof Error) {
|
|
19
|
+
trackError('passport', flowName, error, { flowId: flow.details.flowId });
|
|
20
|
+
} else {
|
|
21
|
+
flow.addEvent('errored');
|
|
22
|
+
}
|
|
23
|
+
throw error;
|
|
24
|
+
} finally {
|
|
25
|
+
if (trackEndEvent) {
|
|
26
|
+
flow.addEvent('End');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
|
|
3
|
+
export default class TypedEventEmitter<TEvents extends Record<string, any>> {
|
|
4
|
+
private emitter = new EventEmitter();
|
|
5
|
+
|
|
6
|
+
emit<TEventName extends keyof TEvents & string>(
|
|
7
|
+
eventName: TEventName,
|
|
8
|
+
...eventArg: TEvents[TEventName]
|
|
9
|
+
) {
|
|
10
|
+
this.emitter.emit(eventName, ...(eventArg as []));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
on<TEventName extends keyof TEvents & string>(
|
|
14
|
+
eventName: TEventName,
|
|
15
|
+
handler: (...eventArg: TEvents[TEventName]) => void,
|
|
16
|
+
) {
|
|
17
|
+
this.emitter.on(eventName, handler as any);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
removeListener<TEventName extends keyof TEvents & string>(
|
|
21
|
+
eventName: TEventName,
|
|
22
|
+
handler: (...eventArg: TEvents[TEventName]) => void,
|
|
23
|
+
) {
|
|
24
|
+
this.emitter.removeListener(eventName, handler as any);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import * as GeneratedClients from '@imtbl/generated-clients';
|
|
2
|
-
import { ConfirmationResult } from './types';
|
|
3
|
-
import { IAuthConfiguration } from '../config';
|
|
4
|
-
export declare const CONFIRMATION_IFRAME_ID = "passport-confirm";
|
|
5
|
-
export declare const CONFIRMATION_IFRAME_STYLE = "display: none; position: absolute;width:0px;height:0px;border:0;";
|
|
6
|
-
type MessageHandler = (arg0: MessageEvent) => void;
|
|
7
|
-
type MessageType = 'erc191' | 'eip712';
|
|
8
|
-
export default class ConfirmationScreen {
|
|
9
|
-
private config;
|
|
10
|
-
private confirmationWindow;
|
|
11
|
-
private popupOptions;
|
|
12
|
-
private overlay;
|
|
13
|
-
private overlayClosed;
|
|
14
|
-
private timer;
|
|
15
|
-
constructor(config: IAuthConfiguration);
|
|
16
|
-
private getHref;
|
|
17
|
-
requestConfirmation(transactionId: string, etherAddress: string, chainType: GeneratedClients.mr.TransactionApprovalRequestChainTypeEnum, chainId?: string): Promise<ConfirmationResult>;
|
|
18
|
-
requestMessageConfirmation(messageID: string, etherAddress: string, messageType?: MessageType): Promise<ConfirmationResult>;
|
|
19
|
-
showServiceUnavailable(): Promise<void>;
|
|
20
|
-
loading(popupOptions?: {
|
|
21
|
-
width: number;
|
|
22
|
-
height: number;
|
|
23
|
-
}): void;
|
|
24
|
-
closeWindow(): void;
|
|
25
|
-
showConfirmationScreen(href: string, messageHandler: MessageHandler, resolve: Function): void;
|
|
26
|
-
private recreateConfirmationWindow;
|
|
27
|
-
}
|
|
28
|
-
export {};
|
|
@@ -1,275 +0,0 @@
|
|
|
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
|
-
}
|