From 5e884fb1b1ab8efe87ae6d98dbcf28b49f2d87f5 Mon Sep 17 00:00:00 2001 From: Tomasz Bakula Date: Tue, 17 Sep 2024 22:50:36 +0200 Subject: [PATCH 1/3] Add sessionToken parameter support to initOnRamp --- src/types/widget.ts | 17 +++++++++++++++-- src/utils/CoinbasePixel.test.ts | 6 ++++++ src/utils/CoinbasePixel.ts | 13 +++++++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/types/widget.ts b/src/types/widget.ts index d025128..cd09640 100644 --- a/src/types/widget.ts +++ b/src/types/widget.ts @@ -20,10 +20,19 @@ export type EmbeddedContentStyles = { top?: string; }; -export type CBPayExperienceOptions = { +export type CBPayExperienceWithAppId = { + appId: string; + sessionToken?: never; +} & CBPayExperienceBaseOptions; + +export type CBPayExperienceWithSessionToken = { + sessionToken: string; + appId?: never; +} & CBPayExperienceBaseOptions; + +type CBPayExperienceBaseOptions = { widgetParameters: T; target?: string; - appId: string; host?: string; debug?: boolean; theme?: Theme; @@ -37,3 +46,7 @@ export type CBPayExperienceOptions = { experienceLoggedIn?: Experience; experienceLoggedOut?: Experience; }; + +export type CBPayExperienceOptions = + | CBPayExperienceWithAppId + | CBPayExperienceWithSessionToken; diff --git a/src/utils/CoinbasePixel.test.ts b/src/utils/CoinbasePixel.test.ts index 7592f51..bbd970e 100644 --- a/src/utils/CoinbasePixel.test.ts +++ b/src/utils/CoinbasePixel.test.ts @@ -50,6 +50,12 @@ describe('CoinbasePixel', () => { expect(instance.appParams).toEqual(defaultArgs.appParams); }); + it('should initialize with sessionToken', () => { + const instance = createUntypedPixel({ sessionToken: 'test', appParams: defaultAppParams }); + + expect(instance.sessionToken).toEqual('test'); + }); + it('should handle opening the embedded experience when logged out', () => { const instance = createUntypedPixel(defaultArgs); diff --git a/src/utils/CoinbasePixel.ts b/src/utils/CoinbasePixel.ts index 6b96e3e..7cf4d2d 100644 --- a/src/utils/CoinbasePixel.ts +++ b/src/utils/CoinbasePixel.ts @@ -26,7 +26,8 @@ export type ExperienceListeners = { export type CoinbasePixelConstructorParams = { host?: string; - appId: string; + appId?: string; + sessionToken?: string; appParams: JsonObject; debug?: boolean; theme?: Theme; @@ -42,7 +43,8 @@ export type OpenExperienceOptions = { export class CoinbasePixel { private debug: boolean; private host: string; - private appId: string; + private appId: string | undefined; + private sessionToken: string | undefined; private eventStreamListeners: Partial void)[]>> = {}; private unsubs: (() => void)[] = []; private appParams: JsonObject; @@ -52,12 +54,14 @@ export class CoinbasePixel { constructor({ host = DEFAULT_HOST, appId, + sessionToken, appParams, debug, theme, }: CoinbasePixelConstructorParams) { this.host = host; this.appId = appId; + this.sessionToken = sessionToken; this.appParams = appParams; this.debug = debug || false; this.theme = theme; @@ -75,6 +79,7 @@ export class CoinbasePixel { const url = generateOnRampURL({ appId: this.appId, + sessionToken: this.sessionToken, host: this.host, theme: this.theme ?? undefined, ...this.appParams, @@ -171,6 +176,10 @@ export class CoinbasePixel { }; private startDirectSignin = (callback: () => void) => { + if (!this.appId) { + throw new Error('appId is required for direct signin'); + } + const queryParams = new URLSearchParams(); queryParams.set('appId', this.appId); queryParams.set('type', 'direct'); From f7baf91185b362c5c5777118e4b23101294e2950 Mon Sep 17 00:00:00 2001 From: Tomasz Bakula Date: Thu, 19 Sep 2024 14:25:00 +0200 Subject: [PATCH 2/3] Adjust generateOnRampURL function to support sessionToken --- src/onramp/generateOnRampURL.test.ts | 21 ++++++++++++++++++++- src/onramp/generateOnRampURL.ts | 16 ++++++++++++---- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/onramp/generateOnRampURL.test.ts b/src/onramp/generateOnRampURL.test.ts index 2cea051..dc05875 100644 --- a/src/onramp/generateOnRampURL.test.ts +++ b/src/onramp/generateOnRampURL.test.ts @@ -19,7 +19,6 @@ describe('generateOnrampURL', () => { const url = new URL( generateOnRampURL({ sessionToken: 'test', - destinationWallets: [], }), ); expect(url.origin).toEqual('https://pay.coinbase.com'); @@ -27,6 +26,26 @@ describe('generateOnrampURL', () => { expect(url.searchParams.get('sessionToken')).toEqual('test'); }); + it('should throw when both sessionToken and destinationWallets or addresses are provided', () => { + expect(() => + generateOnRampURL({ + sessionToken: 'test', + destinationWallets: [], + }), + ).toThrowError( + 'When sessionToken is provided, destinationWallets and addresses should not be specified', + ); + + expect(() => + generateOnRampURL({ + sessionToken: 'test', + addresses: {}, + }), + ).toThrowError( + 'When sessionToken is provided, destinationWallets and addresses should not be specified', + ); + }); + it('generates URL with empty destination wallets', () => { const url = new URL( generateOnRampURL({ diff --git a/src/onramp/generateOnRampURL.ts b/src/onramp/generateOnRampURL.ts index f607fdf..0210fe5 100644 --- a/src/onramp/generateOnRampURL.ts +++ b/src/onramp/generateOnRampURL.ts @@ -18,10 +18,18 @@ export const generateOnRampURL = ({ const url = new URL(host); url.pathname = '/buy/select-asset'; - if (props.destinationWallets && props.addresses) { - throw new Error('Only one of destinationWallets or addresses can be provided'); - } else if (!props.destinationWallets && !props.addresses) { - throw new Error('One of destinationWallets or addresses must be provided'); + if (props.sessionToken) { + if (props.destinationWallets || props.addresses) { + throw new Error( + 'When sessionToken is provided, destinationWallets and addresses should not be specified', + ); + } + } else { + if (props.destinationWallets && props.addresses) { + throw new Error('Only one of destinationWallets or addresses can be provided'); + } else if (!props.destinationWallets && !props.addresses) { + throw new Error('One of destinationWallets or addresses must be provided'); + } } (Object.keys(props) as (keyof typeof props)[]).forEach((key) => { From f02a5edf7a4b5ba1b974499c22a56ac326ad7bd2 Mon Sep 17 00:00:00 2001 From: Tomasz Bakula Date: Fri, 27 Sep 2024 22:37:42 +0200 Subject: [PATCH 3/3] simplify implementation --- src/types/onramp.ts | 7 +++++++ src/types/widget.ts | 17 ++--------------- src/utils/CoinbasePixel.test.ts | 9 +++++++-- src/utils/CoinbasePixel.ts | 13 ++----------- 4 files changed, 18 insertions(+), 28 deletions(-) diff --git a/src/types/onramp.ts b/src/types/onramp.ts index 59f5308..f534ba8 100644 --- a/src/types/onramp.ts +++ b/src/types/onramp.ts @@ -60,6 +60,13 @@ type BaseOnRampAppParams = { * `{ "0x1": ["base"] }` */ addresses?: Record; + /** + * The session token used to initialize the Coinbase Onramp experience. + * This token expires after a short period of time and can only be used once. + * A new token must be obtained for every new session. + * @see https://docs.cdp.coinbase.com/onramp/docs/api-initializing#getting-an-onramp-session-token + */ + sessionToken?: string; /** * This optional parameter will restrict the assets available for the user to buy/send. It acts as a filter on the * networks specified in the {addresses} param. diff --git a/src/types/widget.ts b/src/types/widget.ts index cd09640..d025128 100644 --- a/src/types/widget.ts +++ b/src/types/widget.ts @@ -20,19 +20,10 @@ export type EmbeddedContentStyles = { top?: string; }; -export type CBPayExperienceWithAppId = { - appId: string; - sessionToken?: never; -} & CBPayExperienceBaseOptions; - -export type CBPayExperienceWithSessionToken = { - sessionToken: string; - appId?: never; -} & CBPayExperienceBaseOptions; - -type CBPayExperienceBaseOptions = { +export type CBPayExperienceOptions = { widgetParameters: T; target?: string; + appId: string; host?: string; debug?: boolean; theme?: Theme; @@ -46,7 +37,3 @@ type CBPayExperienceBaseOptions = { experienceLoggedIn?: Experience; experienceLoggedOut?: Experience; }; - -export type CBPayExperienceOptions = - | CBPayExperienceWithAppId - | CBPayExperienceWithSessionToken; diff --git a/src/utils/CoinbasePixel.test.ts b/src/utils/CoinbasePixel.test.ts index bbd970e..9182c26 100644 --- a/src/utils/CoinbasePixel.test.ts +++ b/src/utils/CoinbasePixel.test.ts @@ -51,9 +51,14 @@ describe('CoinbasePixel', () => { }); it('should initialize with sessionToken', () => { - const instance = createUntypedPixel({ sessionToken: 'test', appParams: defaultAppParams }); + const sessionToken = 'test-session-token'; + const instance = createUntypedPixel({ + appId: 'test', + appParams: { ...defaultAppParams, sessionToken }, + }); - expect(instance.sessionToken).toEqual('test'); + expect(instance.appId).toEqual('test'); + expect(instance.appParams.sessionToken).toEqual(sessionToken); }); it('should handle opening the embedded experience when logged out', () => { diff --git a/src/utils/CoinbasePixel.ts b/src/utils/CoinbasePixel.ts index 7cf4d2d..6b96e3e 100644 --- a/src/utils/CoinbasePixel.ts +++ b/src/utils/CoinbasePixel.ts @@ -26,8 +26,7 @@ export type ExperienceListeners = { export type CoinbasePixelConstructorParams = { host?: string; - appId?: string; - sessionToken?: string; + appId: string; appParams: JsonObject; debug?: boolean; theme?: Theme; @@ -43,8 +42,7 @@ export type OpenExperienceOptions = { export class CoinbasePixel { private debug: boolean; private host: string; - private appId: string | undefined; - private sessionToken: string | undefined; + private appId: string; private eventStreamListeners: Partial void)[]>> = {}; private unsubs: (() => void)[] = []; private appParams: JsonObject; @@ -54,14 +52,12 @@ export class CoinbasePixel { constructor({ host = DEFAULT_HOST, appId, - sessionToken, appParams, debug, theme, }: CoinbasePixelConstructorParams) { this.host = host; this.appId = appId; - this.sessionToken = sessionToken; this.appParams = appParams; this.debug = debug || false; this.theme = theme; @@ -79,7 +75,6 @@ export class CoinbasePixel { const url = generateOnRampURL({ appId: this.appId, - sessionToken: this.sessionToken, host: this.host, theme: this.theme ?? undefined, ...this.appParams, @@ -176,10 +171,6 @@ export class CoinbasePixel { }; private startDirectSignin = (callback: () => void) => { - if (!this.appId) { - throw new Error('appId is required for direct signin'); - } - const queryParams = new URLSearchParams(); queryParams.set('appId', this.appId); queryParams.set('type', 'direct');