From cfab98efd97672c8eed9fffbc7d8821688cd491c Mon Sep 17 00:00:00 2001 From: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:02:53 -0500 Subject: [PATCH] fix: allow passing kit functions by reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What ---- Define methods using `methodName = async () => {…}` syntax, rather than `async methodName() {…}` Why --- This allows for better APIs when using StellarWalletsKit. For example, given a `stellar-wallets-kit.ts` file in my own project with: ```ts const kit: StellarWalletsKit = new StellarWalletsKit({…}); export const signTransaction = kit.signTransaction; ``` And I then use this in my app: ```ts const tx = await incrementor.increment(); const { result } = await tx.signAndSend({ signTransaction }) ``` Today, the `signAndSend` will throw a runtime error: ``` TypeError: Cannot read properties of undefined (reading 'selectedModule') ``` This is because JavaScript's default behavior is coo coo bananas, no one understands it, and `this` ends up getting bound to `undefined` if you use the `async methodName() {…}` syntax and then pass `methodName` as a reference the way I did. Today, in order to make my code work, I would need to export the whole `kit` and then change my `signAndSend` line to: ```ts const { result } = await tx.signAndSend({ signTransaction: async (xdr) => { return await kit.signTransaction(xdr); }, }); ``` I don't like this because A) it's ugly and B) I don't think it's good practice to export the whole `kit`. Within my app, I want to have the ability to wrap interfaces like `signTransaction`, so that I can always make sure app-specific logic gets taken care of. Exporting all of `kit` adds more room for error. The Fix ------- Using the arrow syntax with `methodName = async (…) => {…}` makes JS use similar `this`-binding logic to every other language, and makes my pass-by-reference use-case possible. --- src/stellar-wallets-kit.ts | 58 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/stellar-wallets-kit.ts b/src/stellar-wallets-kit.ts index a68261b..7b75572 100644 --- a/src/stellar-wallets-kit.ts +++ b/src/stellar-wallets-kit.ts @@ -65,7 +65,7 @@ export class StellarWalletsKit implements KitActions { * This method will return an array with all wallets supported by this kit but will let you know those the user have already installed/has access to * There are wallets that are by default available since they either don't need to be installed or have a fallback */ - public async getSupportedWallets(): Promise { + public getSupportedWallets = async (): Promise => { return Promise.all( this.modules.map(async (mod: ModuleInterface): Promise => { const timer: Promise = new Promise(r => setTimeout(() => r(false), 500)); @@ -79,9 +79,9 @@ export class StellarWalletsKit implements KitActions { }; }) ); - } + }; - public setWallet(id: string): void { + public setWallet = (id: string): void => { const target: ModuleInterface | undefined = this.modules.find( (mod: ModuleInterface): boolean => mod.productId === id ); @@ -91,15 +91,15 @@ export class StellarWalletsKit implements KitActions { } setSelectedModuleId(target.productId); - } + }; - public async getAddress(params?: { path?: string }): Promise<{ address: string }> { + public getAddress = async (params?: { path?: string }): Promise<{ address: string }> => { const { address } = await this.selectedModule.getAddress(params); setAddress(address); return { address }; - } + }; - public async signTransaction( + public signTransaction = ( xdr: string, opts?: { networkPassphrase?: string; @@ -108,61 +108,61 @@ export class StellarWalletsKit implements KitActions { submit?: boolean; submitUrl?: string; } - ): Promise<{ signedTxXdr: string; signerAddress?: string }> { + ): Promise<{ signedTxXdr: string; signerAddress?: string }> => { return this.selectedModule.signTransaction(xdr, { ...opts, networkPassphrase: opts?.networkPassphrase || store.getValue().selectedNetwork, }); - } + }; - public async signAuthEntry( + public signAuthEntry = ( authEntry: string, opts?: { networkPassphrase?: string; address?: string; path?: string; } - ): Promise<{ signedAuthEntry: string; signerAddress?: string }> { + ): Promise<{ signedAuthEntry: string; signerAddress?: string }> => { return this.selectedModule.signAuthEntry(authEntry, { ...opts, networkPassphrase: opts?.networkPassphrase || store.getValue().selectedNetwork, }); - } + }; - public async signMessage( + public signMessage = ( message: string, opts?: { networkPassphrase?: string; address?: string; path?: string; } - ): Promise<{ signedMessage: string; signerAddress?: string }> { + ): Promise<{ signedMessage: string; signerAddress?: string }> => { return this.selectedModule.signMessage(message, { ...opts, networkPassphrase: opts?.networkPassphrase || store.getValue().selectedNetwork, }); - } + }; - async getNetwork(): Promise<{ network: string; networkPassphrase: string }> { + public getNetwork = (): Promise<{ network: string; networkPassphrase: string }> => { return this.selectedModule.getNetwork(); - } + }; - async disconnect(): Promise { + public disconnect = async (): Promise => { removeAddress(); - } + }; // ---- Button methods - public isButtonCreated(): boolean { + public isButtonCreated = (): boolean => { return !!this.buttonElement; - } + }; - public async createButton(params: { + public createButton = async (params: { container: HTMLElement; onConnect: (response: { address: string }) => void; onDisconnect: () => void; horizonUrl?: string; buttonText?: string; - }): Promise { + }): Promise => { if (this.buttonElement) { throw new Error(`Stellar Wallets Kit button is already created`); } @@ -203,14 +203,14 @@ export class StellarWalletsKit implements KitActions { }, false ); - } + }; /** * Removes the button elements from the HTML and from the kit's instance. * * @param params.skipDisconnect - Set this to `true` if you want to prevent that we disconnect (for example, disconnecting WalletConnect or removing the address) */ - public async removeButton(params?: { skipDisconnect?: boolean }): Promise { + public removeButton = async (params?: { skipDisconnect?: boolean }): Promise => { if (!this.buttonElement) { throw new Error(`Stellar Wallets Kit button hasn't been created yet`); } @@ -221,16 +221,16 @@ export class StellarWalletsKit implements KitActions { this.buttonElement.remove(); delete this.buttonElement; - } + }; // ---- END Button methods // ---- Modal methods - public async openModal(params: { + public openModal = async (params: { onWalletSelected: (option: ISupportedWallet) => void; onClosed?: (err: Error) => void; modalTitle?: string; notAvailableText?: string; - }): Promise { + }): Promise => { if (this.modalElement && !this.buttonElement) { throw new Error(`Stellar Wallets Kit modal is already open`); } else { @@ -281,6 +281,6 @@ export class StellarWalletsKit implements KitActions { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this.modalElement.addEventListener('modal-closed', errorListener, false); - } + }; // ---- END Modal methods }