From 55ea25402ea9ead2d12a2587ec5887b55387cdc2 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 1 Mar 2024 07:17:09 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=94=84=20synced=20local=20'README.md'?= =?UTF-8?q?=20with=20remote=20'README.md'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 689766a..b3de7cb 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ sdk.execute(challengeId, (error, result) => { Check out the developer documentations below for an easy-breezy setup and smoother usage experience. - [Web SDK doc](https://developers.circle.com/w3s/docs/web) -- [Web SDK UI Customization API doc](https://developers.circle.com/w3s/docs/web-sdk-ui-customization-api) +- [Web SDK UI Customization API doc](https://developers.circle.com/w3s/docs/web-sdk-ui-customizations) ## Examples From 15e8425828f6215ce5c4e3df0b573567453d268c Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 1 Mar 2024 07:17:09 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=94=84=20synced=20local=20'package.js?= =?UTF-8?q?on'=20with=20remote=20'package.json'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 60eafa1..d2f3dd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@circle-fin/w3s-pw-web-sdk", - "version": "1.0.2", + "version": "1.0.7", "description": "Javascript/Typescript SDK for Circle Programmable Wallets", "main": "dist/index.js", "types": "dist/index.d.ts", From 2e218f105f7bfe1677a2a1f8dd1ac903283e3c28 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 1 Mar 2024 07:17:09 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=94=84=20synced=20local=20'src/'=20wi?= =?UTF-8?q?th=20remote=20'src/'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 169 ++++++++++++++++++++++++++++++++++----------------- src/types.ts | 83 ++++++++++++++++++++----- 2 files changed, 183 insertions(+), 69 deletions(-) diff --git a/src/index.ts b/src/index.ts index 54b6c4f..3bf086c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,16 +16,13 @@ import type { AppSettings, Authentication, Challenge, - ChallengeResult, - ChallengeStatus, - ChallengeType, + ChallengeCompleteCallback, CustomLinks, - Error, Localizations, PostMessageEvent, Resources, SecurityQuestion, - SignMessageResult, + SsoSettings, ThemeColor, } from './types' @@ -44,20 +41,25 @@ export class W3SSdk { private themeColor?: ThemeColor private resources?: Resources private customLinks?: CustomLinks + private ssoSettings?: SsoSettings /** * Callback function that is called when the challenge is completed. */ - private onComplete?: ( - error: Error | undefined, - result: ChallengeResult | undefined - ) => Promise | void - + private onComplete?: ChallengeCompleteCallback private shouldCloseModalOnForgotPin = false /** * Callback function that is called when the user clicks the forgot pin button. */ private onForgotPin?: () => void private receivedResponseFromService = false + /** + * Promise that is resolved when the device ID is received. + */ + private resolveDeviceIdPromise?: (deviceId: string) => void + /** + * Promise that is rejected when the device ID is not received. + */ + private rejectDeviceIdPromise?: (reason: string) => void constructor() { if (W3SSdk.instance != null) { @@ -76,46 +78,48 @@ export class W3SSdk { * @param challengeId - Challenge ID. * @param onCompleted - Callback function that is called when the challenge is completed. */ - execute( - challengeId: string, - onCompleted?: ( - error: Error | undefined, - result: ChallengeResult | undefined - ) => Promise | void - ): void { + execute(challengeId: string, onCompleted?: ChallengeCompleteCallback): void { this.subscribeMessage() this.setChallenge({ challengeId }) + this.exec(onCompleted) + } - const protocol = this.window.location.protocol - const host = this.window.location.host - const fullDomainWithProtocol = `${protocol}//${host}` - - this.iframe.src = `${this.serviceUrl}/?origin=${fullDomainWithProtocol}` - this.iframe.id = 'sdkIframe' - this.iframe.width = '100%' - this.iframe.height = '100%' - - this.iframe.style.position = 'fixed' - this.iframe.style.top = '50%' - this.iframe.style.left = '50%' - this.iframe.style.transform = 'translate(-50%, -50%)' - this.iframe.style.zIndex = '2147483647' - - document.body.appendChild(this.iframe) - - this.onComplete = onCompleted + /** + * Executes the challenge with userSecret. This is used for SSO challenges. + * @param challengeId - Challenge ID. + * @param userSecret - User secret. + * @param onCompleted - Callback function that is called when the challenge is completed. + */ + executeWithKeyShare( + challengeId: string, + userSecret: string, + onCompleted?: ChallengeCompleteCallback + ) { + this.subscribeMessage() + this.setChallenge({ challengeId, userSecret }) + this.exec(onCompleted, !this.ssoSettings?.disableConfirmationUI) + } - setTimeout(() => { - if (!this.receivedResponseFromService) { - void this.onComplete?.( - { - code: 155706, - message: 'Network error', - }, - undefined - ) - } - }, 1000 * 10) + /** + * Gets the device ID. + * @returns Promise - Device ID. + */ + getDeviceId(): Promise { + return new Promise((resolve, reject) => { + this.resolveDeviceIdPromise = resolve + this.rejectDeviceIdPromise = reject + + this.subscribeMessage() + this.appendIframe(false, 'device-id') + + setTimeout(() => { + if (!this.receivedResponseFromService) { + this.rejectDeviceIdPromise?.('Failed to receive deviceId') + this.closeModal() + this.unSubscribeMessage() + } + }, 1000 * 10) + }) } /** @@ -187,6 +191,14 @@ export class W3SSdk { this.customLinks = customLinks } + /** + * Sets the SSO settings. + * @param ssoSettings - SSO settings. + */ + setSsoSettings(ssoSettings: SsoSettings): void { + this.ssoSettings = ssoSettings + } + /** * Sets the callback function that is called when the user clicks the forgot pin button. * @param onForgotPin - Callback function that is called when the user clicks the forgot pin button. @@ -215,6 +227,56 @@ export class W3SSdk { this.challenge = challenge } + /** + * Appends the iframe to the document body. + * @param showIframe - Indicates whether the iframe should be shown. Default is true. + * @param subRoute - Sub route. + */ + private appendIframe(showIframe = true, subRoute = '') { + const protocol = this.window.location.protocol + const host = this.window.location.host + const fullDomainWithProtocol = `${protocol}//${host}` + + this.iframe.src = `${this.serviceUrl}/${subRoute}?origin=${fullDomainWithProtocol}` + this.iframe.id = 'sdkIframe' + this.iframe.width = showIframe ? '100%' : '0%' + this.iframe.height = showIframe ? '100%' : '0%' + this.iframe.style.zIndex = showIframe ? '2147483647' : '-1' + this.iframe.style.display = 'none' + + if (showIframe) { + this.iframe.style.position = 'fixed' + this.iframe.style.top = '50%' + this.iframe.style.left = '50%' + this.iframe.style.transform = 'translate(-50%, -50%)' + this.iframe.style.display = '' + } + + document.body.appendChild(this.iframe) + } + + /** + * Executes the challenge. + * @param onCompleted - Callback function that is called when the challenge is completed. + * @param showIframe - Indicates whether the iframe should be shown. Default is true. + */ + private exec(onCompleted?: ChallengeCompleteCallback, showIframe = true) { + this.appendIframe(showIframe) + this.onComplete = onCompleted + + setTimeout(() => { + if (!this.receivedResponseFromService) { + void this.onComplete?.( + { + code: 155706, + message: 'Network error', + }, + undefined + ) + } + }, 1000 * 10) + } + /** * Handles the postMessage event. * @param event - PostMessageEvent. @@ -245,6 +307,7 @@ export class W3SSdk { localizations: this.localizations, resources: this.resources, customLinks: this.customLinks, + ssoSettings: this.ssoSettings, }, }, }, @@ -258,16 +321,12 @@ export class W3SSdk { ) as HTMLIFrameElement iframe?.parentNode?.removeChild(iframe) - const result: ChallengeResult | undefined = - event.data?.result != null - ? { - type: event.data?.result.type as ChallengeType, - status: event.data?.result.status as ChallengeStatus, - data: event.data?.result.data as SignMessageResult, - } - : undefined + void this.onComplete?.(undefined, event.data?.result) + } else if (event.data?.deviceId) { + this.resolveDeviceIdPromise?.(event.data.deviceId) - void this.onComplete?.(undefined, result) + this.closeModal() + this.unSubscribeMessage() } else if (event.data?.onError) { void this.onComplete?.(event.data?.error, undefined) } else if (event.data?.onClose) { diff --git a/src/types.ts b/src/types.ts index 2e94ec5..ac64625 100644 --- a/src/types.ts +++ b/src/types.ts @@ -27,6 +27,7 @@ export enum ChallengeType { CANCEL_TRANSACTION = 'CANCEL_TRANSACTION', SIGN_MESSAGE = 'SIGN_MESSAGE', SIGN_TYPEDDATA = 'SIGN_TYPEDDATA', + SIGN_TRANSACTION = 'SIGN_TRANSACTION', UNKNOWN = 'UNKNOWN', } @@ -125,6 +126,7 @@ export enum ErrorCode { biometricsUserLockoutPermanent = 155714, biometricsUserNotAllowPermission = 155715, biometricsInternalError = 155716, + invalidUserSecret= 155718, walletIdNotFound = 156001, tokenIdNotFound = 156002, transactionIdNotFound = 156003, @@ -157,22 +159,64 @@ export interface Challenge { * Challenge ID. */ challengeId: string + /** + * SSO user secret. + */ + userSecret?: string +} + +/** + * Base challenge result interface. Holds the challenge type and status. + */ +export interface ChallengeResult { + /** + * Challenge type. + */ + type: ChallengeType + /** + * Challenge status. + */ + status: ChallengeStatus } /** * Result for sign message and sign typed-data challenges. */ -export interface SignMessageResult { +export interface SignMessageResult extends ChallengeResult { /** - * Signature. + * Challenge type. */ - signature: string + type: ChallengeType.SIGN_MESSAGE | ChallengeType.SIGN_TYPEDDATA + data?: { + /** + * Signature. + */ + signature: string + } } -export interface ChallengeResult { - type: ChallengeType - status: ChallengeStatus - data?: SignMessageResult +/** + * Result for sign transaction challenge. + */ +export interface SignTransactionResult extends ChallengeResult { + /** + * Challenge type. + */ + type: ChallengeType.SIGN_TRANSACTION + data?: { + /** + * Signature. + */ + signature: string + /** + * Transaction hash. + */ + txHash: string + /** + * Signed transaction. + */ + signedTransaction: string + } } export interface SecurityQuestion { @@ -198,6 +242,15 @@ export interface Error { message: string } +export type ChallengeCompleteCallback = ( + error: Error | undefined, + result: + | ChallengeResult + | SignMessageResult + | SignTransactionResult + | undefined +) => Promise | void + export interface PostMessageEvent extends MessageEvent { data: { onFrameReady?: boolean @@ -206,14 +259,9 @@ export interface PostMessageEvent extends MessageEvent { onLearnMore?: boolean onError?: boolean onClose?: boolean + deviceId?: string error?: Error - result?: { - type: string - status: string - data?: { - signature: string - } - } + result?: ChallengeResult | SignMessageResult | SignTransactionResult } } @@ -523,3 +571,10 @@ export interface CustomLinks { */ learnMoreUrl?: string } + +export interface SsoSettings { + /** + * Controls whether the SSO UI is disabled. + */ + disableConfirmationUI?: boolean +}