diff --git a/packages/@magic-ext/oauth2/src/index.ts b/packages/@magic-ext/oauth2/src/index.ts index e7ddde91c..b706a11ce 100644 --- a/packages/@magic-ext/oauth2/src/index.ts +++ b/packages/@magic-ext/oauth2/src/index.ts @@ -9,7 +9,14 @@ import { OAuthPopupConfiguration, OAuthVerificationConfiguration, } from './types'; -import { OAuthPopupEventEmit, OAuthPopupEventHandlers, OAuthPopupEventOnReceived } from '@magic-sdk/types'; +import { + OAuthMFAEventEmit, + OAuthMFAEventOnReceived, + OAuthPopupEventEmit, + OAuthPopupEventHandlers, + OAuthPopupEventOnReceived, + OAuthGetResultEventHandlers, +} from '@magic-sdk/types'; declare global { interface Window { @@ -83,10 +90,11 @@ export class OAuthExtension extends Extension.Internal<'oauth2'> { const urlWithoutQuery = window.location.origin + window.location.pathname; window.history.replaceState(null, '', urlWithoutQuery); - return getResult.call(this, configuration, queryString); + return this.getResult(configuration, queryString); } public loginWithPopup(configuration: OAuthPopupConfiguration) { + const { showUI } = configuration; const requestPayload = this.utils.createJsonRpcRequestPayload(OAuthPayloadMethods.Popup, [ { ...configuration, @@ -102,33 +110,144 @@ export class OAuthExtension extends Extension.Internal<'oauth2'> { requestPayload, ); + /** + * Attach Event listeners + */ const redirectEvent = (event: MessageEvent) => { this.createIntermediaryEvent(OAuthPopupEventEmit.PopupEvent, requestPayload.id as string)(event.data); }; - if (configuration.shouldReturnURI) { + if (configuration.shouldReturnURI && oauthPopupRequest) { oauthPopupRequest.on(OAuthPopupEventOnReceived.PopupUrl, popupUrl => { window.addEventListener('message', redirectEvent); promiEvent.emit(OAuthPopupEventOnReceived.PopupUrl, popupUrl); }); } + if (!showUI) { + oauthPopupRequest.on(OAuthMFAEventOnReceived.MfaSentHandle, () => { + promiEvent.emit(OAuthMFAEventOnReceived.MfaSentHandle); + }); + oauthPopupRequest.on(OAuthMFAEventOnReceived.InvalidMfaOtp, () => { + promiEvent.emit(OAuthMFAEventOnReceived.InvalidMfaOtp); + }); + oauthPopupRequest.on(OAuthMFAEventOnReceived.RecoveryCodeSentHandle, () => { + promiEvent.emit(OAuthMFAEventOnReceived.RecoveryCodeSentHandle); + }); + oauthPopupRequest.on(OAuthMFAEventOnReceived.InvalidRecoveryCode, () => { + promiEvent.emit(OAuthMFAEventOnReceived.InvalidRecoveryCode); + }); + oauthPopupRequest.on(OAuthMFAEventOnReceived.RecoveryCodeSuccess, () => { + promiEvent.emit(OAuthMFAEventOnReceived.RecoveryCodeSuccess); + }); + } + const result = await oauthPopupRequest; window.removeEventListener('message', redirectEvent); - if ((result as OAuthRedirectError).error) { - reject(result); + const maybeResult = result as OAuthRedirectResult; + const maybeError = result as OAuthRedirectError; + + if (maybeError.error) { + reject( + this.createError(maybeError.error, maybeError.error_description ?? 'An error occurred.', { + errorURI: maybeError.error_uri, + provider: maybeError.provider, + }), + ); } else { - resolve(result as OAuthRedirectResult); + resolve(maybeResult); } } catch (error) { reject(error); } }); - promiEvent.on(OAuthPopupEventEmit.Cancel, () => { - this.createIntermediaryEvent(OAuthPopupEventEmit.Cancel, requestPayload.id as string)(); - }); + if (!showUI && promiEvent) { + promiEvent.on(OAuthMFAEventEmit.VerifyMFACode, (mfa: string) => { + this.createIntermediaryEvent(OAuthMFAEventEmit.VerifyMFACode, requestPayload.id as string)(mfa); + }); + promiEvent.on(OAuthMFAEventEmit.LostDevice, () => { + this.createIntermediaryEvent(OAuthMFAEventEmit.LostDevice, requestPayload.id as string)(); + }); + promiEvent.on(OAuthMFAEventEmit.VerifyRecoveryCode, (recoveryCode: string) => { + this.createIntermediaryEvent(OAuthMFAEventEmit.VerifyRecoveryCode, requestPayload.id as string)(recoveryCode); + }); + promiEvent.on(OAuthMFAEventEmit.Cancel, () => { + this.createIntermediaryEvent(OAuthMFAEventEmit.Cancel, requestPayload.id as string)(); + }); + } + + return promiEvent; + } + + private getResult(configuration: OAuthVerificationConfiguration, queryString: string) { + const { showUI } = configuration; + const requestPayload = this.utils.createJsonRpcRequestPayload(OAuthPayloadMethods.Verify, [ + { + authorizationResponseParams: queryString, + magicApiKey: this.sdk.apiKey, + platform: 'web', + ...configuration, + }, + ]); + + const promiEvent = this.utils.createPromiEvent( + async (resolve, reject) => { + const getResultRequest = this.request( + requestPayload, + ); + + if (!showUI) { + getResultRequest.on(OAuthMFAEventOnReceived.MfaSentHandle, () => { + promiEvent.emit(OAuthMFAEventOnReceived.MfaSentHandle); + }); + getResultRequest.on(OAuthMFAEventOnReceived.InvalidMfaOtp, () => { + promiEvent.emit(OAuthMFAEventOnReceived.InvalidMfaOtp); + }); + getResultRequest.on(OAuthMFAEventOnReceived.RecoveryCodeSentHandle, () => { + promiEvent.emit(OAuthMFAEventOnReceived.RecoveryCodeSentHandle); + }); + getResultRequest.on(OAuthMFAEventOnReceived.InvalidRecoveryCode, () => { + promiEvent.emit(OAuthMFAEventOnReceived.InvalidRecoveryCode); + }); + getResultRequest.on(OAuthMFAEventOnReceived.RecoveryCodeSuccess, () => { + promiEvent.emit(OAuthMFAEventOnReceived.RecoveryCodeSuccess); + }); + } + + // Parse the result, which may contain an OAuth-formatted error. + const resultOrError = await getResultRequest; + const maybeResult = resultOrError as OAuthRedirectResult; + const maybeError = resultOrError as OAuthRedirectError; + + if (maybeError.error) { + reject( + this.createError(maybeError.error, maybeError.error_description ?? 'An error occurred.', { + errorURI: maybeError.error_uri, + provider: maybeError.provider, + }), + ); + } + + resolve(maybeResult); + }, + ); + + if (!showUI && promiEvent) { + promiEvent.on(OAuthMFAEventEmit.VerifyMFACode, (mfa: string) => { + this.createIntermediaryEvent(OAuthMFAEventEmit.VerifyMFACode, requestPayload.id as string)(mfa); + }); + promiEvent.on(OAuthMFAEventEmit.LostDevice, () => { + this.createIntermediaryEvent(OAuthMFAEventEmit.LostDevice, requestPayload.id as string)(); + }); + promiEvent.on(OAuthMFAEventEmit.VerifyRecoveryCode, (recoveryCode: string) => { + this.createIntermediaryEvent(OAuthMFAEventEmit.VerifyRecoveryCode, requestPayload.id as string)(recoveryCode); + }); + promiEvent.on(OAuthMFAEventEmit.Cancel, () => { + this.createIntermediaryEvent(OAuthMFAEventEmit.Cancel, requestPayload.id as string)(); + }); + } return promiEvent; } @@ -160,33 +279,4 @@ export class OAuthExtension extends Extension.Internal<'oauth2'> { } } -function getResult(this: OAuthExtension, configuration: OAuthVerificationConfiguration, queryString: string) { - return this.utils.createPromiEvent(async (resolve, reject) => { - const parseRedirectResult = this.utils.createJsonRpcRequestPayload(OAuthPayloadMethods.Verify, [ - { - authorizationResponseParams: queryString, - magicApiKey: this.sdk.apiKey, - platform: 'web', - ...configuration, - }, - ]); - - // Parse the result, which may contain an OAuth-formatted error. - const resultOrError = await this.request(parseRedirectResult); - const maybeResult = resultOrError as OAuthRedirectResult; - const maybeError = resultOrError as OAuthRedirectError; - - if (maybeError.error) { - reject( - this.createError(maybeError.error, maybeError.error_description ?? 'An error occurred.', { - errorURI: maybeError.error_uri, - provider: maybeError.provider, - }), - ); - } - - resolve(maybeResult); - }); -} - export * from './types'; diff --git a/packages/@magic-ext/oauth2/src/types.ts b/packages/@magic-ext/oauth2/src/types.ts index 7afa83b85..127b5476c 100644 --- a/packages/@magic-ext/oauth2/src/types.ts +++ b/packages/@magic-ext/oauth2/src/types.ts @@ -109,6 +109,7 @@ export interface OAuthVerificationConfiguration { lifespan?: number; optionalQueryString?: string; skipDIDToken?: boolean; + showUI?: boolean; } export interface OAuthPopupConfiguration { @@ -117,6 +118,7 @@ export interface OAuthPopupConfiguration { loginHint?: string; providerParams?: Record; shouldReturnURI?: boolean; + showUI?: boolean; } export enum OAuthErrorCode { diff --git a/packages/@magic-ext/wallet-kit/package.json b/packages/@magic-ext/wallet-kit/package.json index 26fb6f35d..71a98ee15 100644 --- a/packages/@magic-ext/wallet-kit/package.json +++ b/packages/@magic-ext/wallet-kit/package.json @@ -53,6 +53,7 @@ "@reown/appkit-adapter-wagmi": "^1.8.0", "@wagmi/core": "^2.0.0", "@walletconnect/ethereum-provider": "^2.23.0", + "libphonenumber-js": "^1.12.37", "wagmi": "^2.0.0" }, "peerDependencies": { diff --git a/packages/@magic-ext/wallet-kit/src/MagicWidget.tsx b/packages/@magic-ext/wallet-kit/src/MagicWidget.tsx index 379e1c5a1..a32269684 100644 --- a/packages/@magic-ext/wallet-kit/src/MagicWidget.tsx +++ b/packages/@magic-ext/wallet-kit/src/MagicWidget.tsx @@ -11,14 +11,19 @@ import { OAuthPendingView } from './views/OAuthPendingView'; import AdditionalProvidersView from './views/AdditionalProvidersView'; import { getExtensionInstance } from './extension'; import { EmailLoginProvider } from './context/EmailLoginContext'; +import { OAuthLoginProvider } from './context/OAuthLoginContext'; +import { SmsLoginProvider } from './context/SmsLoginContext'; +import { WebAuthnLoginProvider } from './context/WebAuthnLoginContext'; import { WidgetConfigProvider } from './context/WidgetConfigContext'; -import { EmailOTPView } from './views/EmailOTPView'; +import { OtpView } from './views/OtpView'; import { DeviceVerificationView } from './views/DeviceVerificationView'; import { LoginSuccessView } from './views/LoginSuccessView'; import { MFAView } from './views/MfaView'; import { RecoveryCodeView } from './views/RecoveryCode'; import { LostRecoveryCode } from './views/LostRecoveryCode'; import { WalletConnectView } from './views/WalletConnectView'; +import { SmsLoginView } from './views/SmsLoginView'; +import { WebAuthnLoginView } from './views/WebAuthnLoginView'; import { FarcasterPendingView } from './views/FarcasterPendingView'; import { FarcasterSuccessView } from './views/FarcasterSuccessView'; import { FarcasterFailedView } from './views/FarcasterFailedView'; @@ -56,10 +61,14 @@ function WidgetContent({ const renderView = () => { switch (state.view) { case 'login': - return ; + return ; + case 'sms_login': + return ; + case 'webauthn_login': + return ; case 'wallet_pending': if (!state.selectedProvider) { - return ; + return ; } return ( ; case 'oauth_pending': if (!state.selectedProvider) { - return ; + return ; } return ( ); case 'additional_providers': return ; - case 'email_otp_pending': - return ; + case 'otp_pending': + return ; case 'device_verification': return ; case 'mfa_pending': @@ -103,18 +113,24 @@ function WidgetContent({ case 'farcaster_failed': return ; default: - return ; + return ; } }; return ( - - - {renderView()} -