Skip to content

Commit

Permalink
🔄 synced file(s) with circlefin/w3s-pw-web-sdk-internal (#37)
Browse files Browse the repository at this point in the history
synced local file(s) with
[circlefin/w3s-pw-web-sdk-internal](https://github.com/circlefin/w3s-pw-web-sdk-internal).



<details>
<summary>Changed files</summary>
<ul>
<li>synced local <code>package.json</code> with remote
<code>package.json</code></li><li>synced local directory
<code>src/</code> with remote directory <code>src/</code></li>
</ul>
</details>

---

This PR was created automatically by the
[repo-file-sync-action](https://github.com/BetaHuhn/repo-file-sync-action)
workflow run
[#12350442693](https://github.com/circlefin/w3s-pw-web-sdk-internal/actions/runs/12350442693)
  • Loading branch information
superandydong authored Dec 16, 2024
2 parents a1158c2 + fa10d39 commit 655fb0a
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 8 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@circle-fin/w3s-pw-web-sdk",
"version": "1.1.6",
"version": "1.1.8",
"description": "Javascript/Typescript SDK for Circle Programmable Wallets",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
Expand Down
198 changes: 197 additions & 1 deletion src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,25 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */

/**
* @jest-environment jsdom
*/
import { FirebaseError } from 'firebase/app'
import * as firebaseAuth from 'firebase/auth'

import { SocialLoginProvider } from './types'

import { W3SSdk } from './'

import type { ChallengeResult, Error } from './types'
import type { ChallengeResult, Configs, Error } from './types'

jest.mock('firebase/auth', () => ({
...jest.requireActual('firebase/auth'),
signInWithPopup: jest.fn(),
getAuth: jest.fn(),
}))

describe('W3SSdk', () => {
let sdk: W3SSdk
Expand Down Expand Up @@ -101,3 +112,188 @@ describe('W3SSdk', () => {
)
})
})

describe('W3SSdk > Apple OAuth', () => {
const localStorageMock = (() => {
let store: { [key: string]: string } = {}

return {
getItem(key: string): string {
return store[key] || ''
},
setItem(key: string, value: string): void {
store[key] = value
},
removeItem(key: string): void {
delete store[key]
},
clear(): void {
store = {}
},
length: 0,
key(index: number): string | null {
return Object.keys(store)[index] || null
},
}
})()

Object.defineProperty(window, 'localStorage', { value: localStorageMock })

let sdk: W3SSdk
const configs: Configs = {
appSettings: {
appId: 'test-app-id',
},
loginConfigs: {
deviceToken: 'device-token',
deviceEncryptionKey: 'device-encryption-key',
apple: {
apiKey: 'test-api-key',
authDomain: 'test-auth-domain',
projectId: 'test-project-id',
storageBucket: 'test-storage-bucket',
messagingSenderId: 'test-messaging-sender-id',
appId: 'test-app-id',
},
},
}

beforeEach(() => {
jest.clearAllMocks()
window.localStorage.clear()
})

it('should perform Apple login successfully', async () => {
const onLoginComplete = jest.fn()
sdk = new W3SSdk(configs, onLoginComplete)

const mockFirebaseApp = {}
Object.defineProperty(sdk, 'firebaseApp', {
get: jest.fn(() => mockFirebaseApp),
configurable: true,
})

// Mock signInWithPopup to resolve to a UserCredential
const userCredentialMock = {
user: { uid: 'test-uid' },
credential: { idToken: 'test-id-token' },
}
;(firebaseAuth.signInWithPopup as jest.Mock).mockResolvedValueOnce(
userCredentialMock,
)

// Mock extractTokenFromResultAndSave to return true
const extractTokenSpy = jest
.spyOn(sdk as any, 'extractTokenFromResultAndSave')
.mockReturnValue(true)

// Mock verifyTokenViaService
const verifyTokenSpy = jest
.spyOn(sdk as any, 'verifyTokenViaService')
.mockImplementation(() => {})

await sdk.performLogin(SocialLoginProvider.APPLE)

expect(firebaseAuth.signInWithPopup).toHaveBeenCalled()
expect(extractTokenSpy).toHaveBeenCalledWith(userCredentialMock)
expect(verifyTokenSpy).toHaveBeenCalled()

expect(window.localStorage.getItem('socialLoginProvider')).toBe('')
})

it('should handle signInWithPopup error during Apple login', async () => {
const onLoginComplete = jest.fn()
sdk = new W3SSdk(configs, onLoginComplete)

// Simulate firebaseApp being initialized
const mockFirebaseApp = {}
Object.defineProperty(sdk, 'firebaseApp', {
get: jest.fn(() => mockFirebaseApp),
configurable: true,
})

// Mock getAuth
const mockAuth = { getProvider: jest.fn() }
;(firebaseAuth.getAuth as jest.Mock).mockReturnValue(mockAuth)

// Mock signInWithPopup to reject
const error = new Error('sign in error')
;(firebaseAuth.signInWithPopup as jest.Mock).mockRejectedValueOnce(error)

// Mock handleLoginFailure
const handleLoginFailureSpy = jest
.spyOn(sdk as any, 'handleLoginFailure')
.mockImplementation(() => {})

await sdk.performLogin(SocialLoginProvider.APPLE)

expect(firebaseAuth.signInWithPopup).toHaveBeenCalled()
expect(handleLoginFailureSpy).toHaveBeenCalled()
})

it('should not handle signInWithPopup auth/cancelled-popup-request error during Apple login', async () => {
const onLoginComplete = jest.fn()
sdk = new W3SSdk(configs, onLoginComplete)

// Simulate firebaseApp being initialized
const mockFirebaseApp = {}
Object.defineProperty(sdk, 'firebaseApp', {
get: jest.fn(() => mockFirebaseApp),
configurable: true,
})

// Mock getAuth
const mockAuth = { getProvider: jest.fn() }
;(firebaseAuth.getAuth as jest.Mock).mockReturnValue(mockAuth)

// Mock signInWithPopup to reject
const error = new FirebaseError(
'auth/cancelled-popup-request',
'sign in error',
)
;(firebaseAuth.signInWithPopup as jest.Mock).mockRejectedValueOnce(error)

// Mock handleLoginFailure
const handleLoginFailureSpy = jest
.spyOn(sdk as any, 'handleLoginFailure')
.mockImplementation(() => {})

await sdk.performLogin(SocialLoginProvider.APPLE)

expect(firebaseAuth.signInWithPopup).toHaveBeenCalled()
expect(handleLoginFailureSpy).toHaveBeenCalledTimes(0)
})

it('should not handle signInWithPopup auth/popup-closed-by-user error during Apple login', async () => {
const onLoginComplete = jest.fn()
sdk = new W3SSdk(configs, onLoginComplete)

// Simulate firebaseApp being initialized
const mockFirebaseApp = {}
Object.defineProperty(sdk, 'firebaseApp', {
get: jest.fn(() => mockFirebaseApp),
configurable: true,
})

// Mock getAuth
const mockAuth = { getProvider: jest.fn() }
;(firebaseAuth.getAuth as jest.Mock).mockReturnValue(mockAuth)

// Mock signInWithPopup to reject
const error = new FirebaseError(
'auth/popup-closed-by-user',
'sign in error',
)
;(firebaseAuth.signInWithPopup as jest.Mock).mockRejectedValueOnce(error)

// Mock handleLoginFailure
const handleLoginFailureSpy = jest
.spyOn(sdk as any, 'handleLoginFailure')
.mockImplementation(() => {})

await sdk.performLogin(SocialLoginProvider.APPLE)

expect(firebaseAuth.signInWithPopup).toHaveBeenCalled()
expect(handleLoginFailureSpy).toHaveBeenCalledTimes(0)
})
})
31 changes: 25 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { getApps, initializeApp } from 'firebase/app'
import { FirebaseError, getApps, initializeApp } from 'firebase/app'
import {
OAuthProvider,
getAuth,
getRedirectResult,
signInWithRedirect,
signInWithPopup,
} from 'firebase/auth'
import { decode } from 'jsonwebtoken'
import { v4 as uuidv4 } from 'uuid'
Expand Down Expand Up @@ -168,13 +168,13 @@ export class W3SSdk {
* Performs social login.
* @param provider - Social login provider.
*/
performLogin(provider: SocialLoginProvider): void {
async performLogin(provider: SocialLoginProvider): Promise<void> {
if (provider === SocialLoginProvider.GOOGLE) {
this.performGoogleLogin()
} else if (provider === SocialLoginProvider.FACEBOOK) {
this.performFacebookLogin()
} else if (provider === SocialLoginProvider.APPLE) {
this.performAppleLogin()
await this.performAppleLogin()
} else {
void this.onLoginComplete?.(
{
Expand Down Expand Up @@ -381,7 +381,7 @@ export class W3SSdk {
}, 1000 * 10)
}

private performAppleLogin() {
private async performAppleLogin() {
if (!this.firebaseApp) {
void this.onLoginComplete?.(
{
Expand All @@ -398,7 +398,26 @@ export class W3SSdk {
const provider = new OAuthProvider('apple.com')
const auth = getAuth(this.firebaseApp)

void signInWithRedirect(auth, provider)
try {
const cred = await signInWithPopup(auth, provider)

if (!this.extractTokenFromResultAndSave(cred)) {
return
}

// Send the token to the verification service and reset the social login provider
this.verifyTokenViaService()
this.window.localStorage.setItem('socialLoginProvider', '')
} catch (error) {
if (
(error instanceof FirebaseError &&
error.code !== 'auth/cancelled-popup-request' &&
error.code !== 'auth/popup-closed-by-user') ||
!(error instanceof FirebaseError)
) {
this.handleLoginFailure()
}
}
}

private performFacebookLogin() {
Expand Down

0 comments on commit 655fb0a

Please sign in to comment.