@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.
@@ -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 ConfirmationOverlay from './overlay/confirmationOverlay';
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?.imx_eth_address) {
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?.zkevm_eth_address,
161
- userAdminAddress: passport?.zkevm_user_admin_address,
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: anonymousId || '',
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 loginWithRedirect(anonymousId?: string, directLoginOptions?: DirectLoginOptions): Promise<void> {
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(anonymousId, directLoginOptions);
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(anonymousId?: string, directLoginOptions?: DirectLoginOptions): Promise<User> {
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(anonymousId);
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(anonymousId, directLoginOptionsToUse, imPassportTraceId);
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 ConfirmationOverlay(this.config.popupOverlayOptions || {}, true);
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, isUserImx, RollupType, MarketingConsentStatus,
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 = (anonymousId?: string) => {
24
- let href = `${this.config.authenticationDomain}/im-embedded-login-prompt`
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 = (anonymousId?: string) => {
76
+ private getEmbeddedLoginIFrame = () => {
81
77
  const embeddedLoginPrompt = document.createElement('iframe');
82
78
  embeddedLoginPrompt.id = LOGIN_PROMPT_IFRAME_ID;
83
- embeddedLoginPrompt.src = this.getHref(anonymousId);
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(anonymousId?: string): Promise<EmbeddedLoginPromptResult> {
95
+ public displayEmbeddedLoginPrompt(): Promise<EmbeddedLoginPromptResult> {
100
96
  return new Promise((resolve, reject) => {
101
- const embeddedLoginPrompt = this.getEmbeddedLoginIFrame(anonymousId);
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 ConfirmationOverlay {
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
- imx_eth_address: string;
38
- imx_stark_address: string;
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
- directLoginMethod: DirectLoginMethod;
126
- marketingConsentStatus?: MarketingConsentStatus;
127
- email?: string;
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,3 +0,0 @@
1
- export { default as ConfirmationScreen } from './confirmation';
2
- export { default as EmbeddedLoginPrompt } from './embeddedLoginPrompt';
3
- export * from './types';
@@ -1,8 +0,0 @@
1
- export type PopUpProps = {
2
- url: string;
3
- title: string;
4
- width: number;
5
- height: number;
6
- query?: string;
7
- };
8
- export declare const openPopupCenter: ({ url, title, width, height, }: PopUpProps) => Window;
@@ -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
- }