From cf1fcb48efcf0abd630b031b2db346b702f3b6b6 Mon Sep 17 00:00:00 2001 From: janniks <6362150+janniks@users.noreply.github.com> Date: Fri, 28 Feb 2025 00:51:55 +0700 Subject: [PATCH] fix: add params to connect method (#429) * fix: update connect alias * fix: update xverse address storage * chore: add changesets * fix: update options check * test: update demo app for local storage * fix: add authResponsePayload again for backwards compatibility * fix: strip public key and derivation path * docs: add connect react note --------- Co-authored-by: janniks --- .changeset/gentle-colts-dress.md | 5 +++ .changeset/good-cheetahs-sip.md | 5 +++ .changeset/great-pugs-cheer.md | 5 +++ .changeset/little-toys-happen.md | 5 +++ README.md | 2 +- packages/connect-react/README.md | 6 +-- packages/connect/src/auth.ts | 2 +- packages/connect/src/methods.ts | 2 + packages/connect/src/request.ts | 26 ++++++++++-- packages/connect/src/storage.ts | 7 +++- packages/connect/src/stories/ConnectPage.tsx | 28 +++++++++++-- packages/connect/src/types/auth.ts | 43 +++++++++++++------- 12 files changed, 107 insertions(+), 29 deletions(-) create mode 100644 .changeset/gentle-colts-dress.md create mode 100644 .changeset/good-cheetahs-sip.md create mode 100644 .changeset/great-pugs-cheer.md create mode 100644 .changeset/little-toys-happen.md diff --git a/.changeset/gentle-colts-dress.md b/.changeset/gentle-colts-dress.md new file mode 100644 index 00000000..e525c355 --- /dev/null +++ b/.changeset/gentle-colts-dress.md @@ -0,0 +1,5 @@ +--- +'@stacks/connect': patch +--- + +Add authResponsePayload to authentication result for more backwards compatibility diff --git a/.changeset/good-cheetahs-sip.md b/.changeset/good-cheetahs-sip.md new file mode 100644 index 00000000..b691dda8 --- /dev/null +++ b/.changeset/good-cheetahs-sip.md @@ -0,0 +1,5 @@ +--- +'@stacks/connect': patch +--- + +Allow `connect()` to take any `getAddresses` params as options diff --git a/.changeset/great-pugs-cheer.md b/.changeset/great-pugs-cheer.md new file mode 100644 index 00000000..60b2e4be --- /dev/null +++ b/.changeset/great-pugs-cheer.md @@ -0,0 +1,5 @@ +--- +'@stacks/connect': patch +--- + +Fix address storage issue for Xverse-like wallets diff --git a/.changeset/little-toys-happen.md b/.changeset/little-toys-happen.md new file mode 100644 index 00000000..ecb16c69 --- /dev/null +++ b/.changeset/little-toys-happen.md @@ -0,0 +1,5 @@ +--- +'@stacks/connect': patch +--- + +Remove potentially unwanted private information from being stored in local storage diff --git a/README.md b/README.md index c457f4b3..35213e0f 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,8 @@ Follow the **Getting Started** section of the [`@stacks/connect` README](https:/ This repository includes three packages: - [`@stacks/connect`](./packages/connect): The one-stop-shop tool for letting web-apps interact with Stacks web wallets. -- [`@stacks/connect-react`](./packages/connect-react): A wrapper library for making `@stacks/connect` use in React even easier - [`@stacks/connect-ui`](./packages/connect-ui): A web-component UI for displaying an intro modal in Stacks web-apps during authentication _(used in the background by `@stacks/connect`)_. +- ~~[`@stacks/connect-react`](./packages/connect-react): A wrapper library for making `@stacks/connect` use in React even easier~~ ## 🛠️ Wallet Implementation Guide diff --git a/packages/connect-react/README.md b/packages/connect-react/README.md index dd1eee0d..a58b54da 100644 --- a/packages/connect-react/README.md +++ b/packages/connect-react/README.md @@ -1,5 +1,3 @@ -# `@stacks/connect` +# `@stacks/connect-react` -A library for building excellent user experiences with [Blockstack](https://blockstack.org/). - -:blue_book: [View documentation](https://docs.blockstack.org/develop/connect/overview.html) +A new `@stacks/react` experience is coming soon 🤫 diff --git a/packages/connect/src/auth.ts b/packages/connect/src/auth.ts index 611ee311..fd8f0c5b 100644 --- a/packages/connect/src/auth.ts +++ b/packages/connect/src/auth.ts @@ -74,7 +74,7 @@ export const authenticate = async (authOptions: AuthOptions, provider?: StacksPr userSession.store.setSessionData(sessionData); - onFinish?.({ userSession }); + onFinish?.({ userSession, authResponsePayload: sessionData.userData }); } catch (error) { console.error('[Connect] Error during auth request', error); onCancel?.(error); diff --git a/packages/connect/src/methods.ts b/packages/connect/src/methods.ts index 3ad19d73..393b9f5b 100644 --- a/packages/connect/src/methods.ts +++ b/packages/connect/src/methods.ts @@ -110,6 +110,8 @@ export interface SignStructuredMessageParams { export interface GetAddressesParams { network?: NetworkString; + // When updating this interface, make sure to update the `connect` function + // in `request.ts`, to pass all available params to the wrapped method. } export interface SendTransferParams { diff --git a/packages/connect/src/request.ts b/packages/connect/src/request.ts index 1352dd04..a9f5fce9 100644 --- a/packages/connect/src/request.ts +++ b/packages/connect/src/request.ts @@ -11,6 +11,7 @@ import { defineCustomElements } from '@stacks/connect-ui/loader'; import { Cl, PostCondition, postConditionToHex } from '@stacks/transactions'; import { JsonRpcError, JsonRpcErrorCode } from './errors'; import { + AddressEntry, MethodParams, MethodParamsRaw, MethodResult, @@ -95,8 +96,11 @@ function createRequestWithStorage(enableLocalStorage: boolean): typeof requestRa params?: MethodParamsRaw ): Promise> { const result = await requestRaw(provider, method, params); - if (method === 'getAddresses' && 'addresses' in result) { - const { stx, btc } = result.addresses.reduce( + if ( + (method === 'getAddresses' || (method as string) === 'wallet_connect') && + 'addresses' in result + ) { + const { stx, btc } = sortAddresses(result.addresses).reduce( (acc, addr) => { acc[addr.address.startsWith('S') ? 'stx' : 'btc'].push(addr); return acc; @@ -251,8 +255,9 @@ function requestArgs( * Initiate a wallet connection and request addresses. * Alias for `request` to `getAddresses` with `forceWalletSelect: true`. */ -export function connect(options?: ConnectRequestOptions) { - return request({ ...options, forceWalletSelect: true }, 'getAddresses'); +export function connect(options?: ConnectRequestOptions & MethodParams<'getAddresses'>) { + const params = options && 'network' in options ? { network: options.network } : undefined; + return request({ ...options, forceWalletSelect: true }, 'getAddresses', params); } /** @@ -478,3 +483,16 @@ function createPersistProviderProxy(shouldPersist: boolean, providerId: string) return result; }; } + +/** @internal */ +function sortAddresses(addresses: AddressEntry[]) { + return addresses.slice().sort((a, b) => { + const aPayment = 'purpose' in a && a.purpose === 'payment'; + const bPayment = 'purpose' in b && b.purpose === 'payment'; + + // Move payment addresses to the beginning + if (aPayment && !bPayment) return -1; + if (!aPayment && bPayment) return 1; + return 0; + }); +} diff --git a/packages/connect/src/storage.ts b/packages/connect/src/storage.ts index eecd70a2..cb667823 100644 --- a/packages/connect/src/storage.ts +++ b/packages/connect/src/storage.ts @@ -30,7 +30,12 @@ export const normalizeAddresses = ( addresses: AddressEntry[] ): Omit[] => { const deduped = [...new Map(addresses.map(a => [a.address, a])).values()]; - return deduped.map(({ publicKey, ...rest }) => rest); + return deduped.map(({ ...a }) => { + if ('publicKey' in a) delete a.publicKey; + if ('derivationPath' in a) delete a.derivationPath; + if ('tweakedPublicKey' in a) delete a.tweakedPublicKey; + return a; + }); }; /** diff --git a/packages/connect/src/stories/ConnectPage.tsx b/packages/connect/src/stories/ConnectPage.tsx index cab6cd21..671f9b12 100644 --- a/packages/connect/src/stories/ConnectPage.tsx +++ b/packages/connect/src/stories/ConnectPage.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { getSelectedProviderId } from '@stacks/connect-ui'; import { Cl } from '@stacks/transactions'; -import { useReducer, useState } from 'react'; +import { useReducer, useState, useEffect } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { AppConfig, UserSession } from '../auth'; import { connect, request } from '../request'; @@ -15,7 +15,7 @@ import { showSTXTransfer, } from '../ui'; import './connect.css'; -import { disconnect, isConnected } from '../storage'; +import { disconnect, isConnected, getLocalStorage } from '../storage'; declare global { interface BigInt { @@ -1025,6 +1025,11 @@ export const ConnectPage = ({ children }: { children?: any }) => { const refresh = useReducer(x => x + 1, 0)[1]; const isSignedIn = userSession.isUserSignedIn(); const [connectResponse, setConnectResponse] = useState(null); + const [localStorageData, setLocalStorageData] = useState(null); + + useEffect(() => { + setLocalStorageData(getLocalStorage()); + }, [refresh]); const appDetails = { name: 'Connect', @@ -1041,12 +1046,16 @@ export const ConnectPage = ({ children }: { children?: any }) => { if (isSignedIn) { disconnect(); setConnectResponse(null); + setLocalStorageData(null); + refresh(); } else { void showConnect({ appDetails, userSession, onFinish: d => { setConnectResponse(d); + // Explicitly update localStorage data after successful connection + setLocalStorageData(getLocalStorage()); refresh(); }, onCancel: () => { @@ -1055,7 +1064,6 @@ export const ConnectPage = ({ children }: { children?: any }) => { }, }); } - refresh(); }} style={{ backgroundColor: isSignedIn ? 'grey' : 'black', @@ -1070,11 +1078,19 @@ export const ConnectPage = ({ children }: { children?: any }) => { if (isConnected()) { disconnect(); setConnectResponse(null); + // Explicitly clear localStorage data display after disconnect + setLocalStorageData(null); refresh(); return; } - void connect().then(setConnectResponse).then(refresh); + void connect() + .then(setConnectResponse) + .then(() => { + // Explicitly update the localStorage data after successful connection + setLocalStorageData(getLocalStorage()); + refresh(); + }); }} style={{ backgroundColor: isConnected() ? 'grey' : 'black', @@ -1097,6 +1113,10 @@ export const ConnectPage = ({ children }: { children?: any }) => {
{JSON.stringify(connectResponse, null, 2)}
)} +
+

LocalStorage Content

+
{JSON.stringify(localStorageData, null, 2)}
+
{(isSignedIn || isConnected()) && ( diff --git a/packages/connect/src/types/auth.ts b/packages/connect/src/types/auth.ts index ff244fd2..b9efb873 100644 --- a/packages/connect/src/types/auth.ts +++ b/packages/connect/src/types/auth.ts @@ -2,21 +2,36 @@ import { UserSession } from '../auth'; /** @deprecated */ export interface AuthResponsePayload { - private_key: string; - username: string | null; - hubUrl: string; - associationToken: string; - blockstackAPIUrl: string | null; - core_token: string | null; - email: string | null; - exp: number; - iat: number; - iss: string; - jti: string; - version: string; profile: any; - profile_url: string; - public_keys: string[]; + + /** @deprecated Not set in the `request` flow anymore. */ + private_key?: string; + /** @deprecated Not set in the `request` flow anymore. */ + username?: string | null; + /** @deprecated Not set in the `request` flow anymore. */ + hubUrl?: string; + /** @deprecated Not set in the `request` flow anymore. */ + associationToken?: string; + /** @deprecated Not set in the `request` flow anymore. */ + blockstackAPIUrl?: string | null; + /** @deprecated Not set in the `request` flow anymore. */ + core_token?: string | null; + /** @deprecated Not set in the `request` flow anymore. */ + email?: string | null; + /** @deprecated Not set in the `request` flow anymore. */ + exp?: number; + /** @deprecated Not set in the `request` flow anymore. */ + iat?: number; + /** @deprecated Not set in the `request` flow anymore. */ + iss?: string; + /** @deprecated Not set in the `request` flow anymore. */ + jti?: string; + /** @deprecated Not set in the `request` flow anymore. */ + version?: string; + /** @deprecated Not set in the `request` flow anymore. */ + profile_url?: string; + /** @deprecated Not set in the `request` flow anymore. */ + public_keys?: string[]; } /** @deprecated */