From ceac85dda940b89c7622b18c60f668ac600a3c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Varga-Somogyi=20=C3=81kos?= Date: Mon, 9 Oct 2023 19:50:44 +0200 Subject: [PATCH 1/9] Use an internal MetaMask extension provider --- packages/wallets/solflare/package.json | 4 +- .../src/metamask/WindowPostMessageStream.ts | 128 ++++++++++++++++++ .../wallets/solflare/src/metamask/detect.ts | 53 ++++---- pnpm-lock.yaml | 107 +++++++++++++++ 4 files changed, 266 insertions(+), 26 deletions(-) create mode 100644 packages/wallets/solflare/src/metamask/WindowPostMessageStream.ts diff --git a/packages/wallets/solflare/package.json b/packages/wallets/solflare/package.json index 5765342e8..1b1fb183f 100644 --- a/packages/wallets/solflare/package.json +++ b/packages/wallets/solflare/package.json @@ -35,11 +35,13 @@ "@solana/web3.js": "^1.77.3" }, "dependencies": { + "@metamask/providers": "^12.0.0", "@solana/wallet-adapter-base": "workspace:^", "@solana/wallet-standard-chains": "^1.1.0", "@solflare-wallet/metamask-sdk": "^1.0.2", "@solflare-wallet/sdk": "^1.3.0", - "@wallet-standard/wallet": "^1.0.1" + "@wallet-standard/wallet": "^1.0.1", + "readable-stream": "^3.6.2" }, "devDependencies": { "@solana/web3.js": "^1.77.3", diff --git a/packages/wallets/solflare/src/metamask/WindowPostMessageStream.ts b/packages/wallets/solflare/src/metamask/WindowPostMessageStream.ts new file mode 100644 index 000000000..3863d5faa --- /dev/null +++ b/packages/wallets/solflare/src/metamask/WindowPostMessageStream.ts @@ -0,0 +1,128 @@ +import { Duplex } from 'readable-stream'; + +type StreamData = number | string | Record | unknown[]; + +interface StreamMessage { + data: StreamData; + [key: string]: unknown; +} + +export interface PostMessageEvent { + data?: StreamData; + origin: string; + source: typeof window; +} + +interface WindowPostMessageStreamArgs { + name: string; + target: string; + targetOrigin?: string; + targetWindow?: Window; +} + +function isValidStreamMessage(message: unknown): message is StreamMessage { + return ( + isObject(message) && + Boolean(message.data) && + (typeof message.data === 'number' || typeof message.data === 'object' || typeof message.data === 'string') + ); +} + +function isObject(value: unknown): value is Record { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value); +} + +const getSource = Object.getOwnPropertyDescriptor(MessageEvent.prototype, 'source')?.get; + +const getOrigin = Object.getOwnPropertyDescriptor(MessageEvent.prototype, 'origin')?.get; + +export class WindowPostMessageStream extends Duplex { + private _name: string; + + private _target: string; + + private _targetOrigin: string; + + private _targetWindow: Window; + + /** + * Creates a stream for communicating with other streams across the same or + * different `window` objects. + * + * @param args - Options bag. + * @param args.name - The name of the stream. Used to differentiate between + * multiple streams sharing the same window object. + * @param args.target - The name of the stream to exchange messages with. + * @param args.targetOrigin - The origin of the target. Defaults to + * `location.origin`, '*' is permitted. + * @param args.targetWindow - The window object of the target stream. Defaults + * to `window`. + */ + constructor({ name, target, targetOrigin = location.origin, targetWindow = window }: WindowPostMessageStreamArgs) { + super({ + objectMode: true, + }); + + if (typeof window === 'undefined' || typeof window.postMessage !== 'function') { + throw new Error( + 'window.postMessage is not a function. This class should only be instantiated in a Window.' + ); + } + + this._name = name; + this._target = target; + this._targetOrigin = targetOrigin; + this._targetWindow = targetWindow; + this._onMessage = this._onMessage.bind(this); + + window.addEventListener('message', this._onMessage as any, false); + } + + protected _onData(data: StreamData): void { + try { + this.push(data); + } catch (err) { + this.emit('error', err); + } + } + + _read(): void { + return undefined; + } + + _write(data: StreamData, _encoding: string | null, cb: () => void): void { + this._postMessage(data); + cb(); + } + + protected _postMessage(data: unknown): void { + this._targetWindow.postMessage( + { + target: this._target, + data, + }, + this._targetOrigin + ); + } + + private _onMessage(event: PostMessageEvent): void { + const message = event.data; + + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + if ( + (this._targetOrigin !== '*' && getOrigin!.call(event) !== this._targetOrigin) || + getSource!.call(event) !== this._targetWindow || + !isValidStreamMessage(message) || + message.target !== this._name + ) { + return; + } + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + + this._onData(message.data); + } + + _destroy(): void { + window.removeEventListener('message', this._onMessage as any, false); + } +} diff --git a/packages/wallets/solflare/src/metamask/detect.ts b/packages/wallets/solflare/src/metamask/detect.ts index 88fb60bf5..21cde4bb2 100644 --- a/packages/wallets/solflare/src/metamask/detect.ts +++ b/packages/wallets/solflare/src/metamask/detect.ts @@ -1,7 +1,9 @@ -import type { EthereumProvider, WindowWithEthereum } from '@solflare-wallet/metamask-sdk'; +import type { MetaMaskInpageProvider } from '@metamask/providers'; import { registerWallet } from '@wallet-standard/wallet'; import { SolflareMetaMaskWallet } from './wallet.js'; +let providerInstance: MetaMaskInpageProvider | null = null; + let stopPolling = false; /** @internal */ @@ -11,7 +13,7 @@ export function detectAndRegisterSolflareMetaMaskWallet(): boolean { (async function () { try { // Try to detect, stop polling if detected, and register the wallet. - if (await isSnapProviderDetected()) { + if (await isSnapSupported()) { if (!stopPolling) { stopPolling = true; registerWallet(new SolflareMetaMaskWallet()); @@ -26,35 +28,36 @@ export function detectAndRegisterSolflareMetaMaskWallet(): boolean { return false; } -async function isSnapProviderDetected(): Promise { - try { - const provider = (window as WindowWithEthereum).ethereum; - if (!provider) return false; +async function getMetamaskProvider(): Promise { + if (providerInstance) { + return providerInstance; + } - const providerProviders = provider.providers; - if (providerProviders && Array.isArray(providerProviders)) { - for (const provider of providerProviders) { - if (await isSnapSupported(provider)) return true; - } - } + const { WindowPostMessageStream } = await import('./WindowPostMessageStream.js'); + const { MetaMaskInpageProvider } = await import('@metamask/providers'); - const providerDetected = provider.detected; - if (providerDetected && Array.isArray(providerDetected)) { - for (const provider of providerDetected) { - if (await isSnapSupported(provider)) return true; - } - } + const metamaskStream = new WindowPostMessageStream({ + name: 'metamask-inpage', + target: 'metamask-contentscript', + }) as any; - return await isSnapSupported(provider); - } catch (error) { - return false; - } + providerInstance = new MetaMaskInpageProvider(metamaskStream, { + shouldSendMetadata: false, + }); + + return providerInstance; } -async function isSnapSupported(provider: EthereumProvider): Promise { +async function isSnapSupported(): Promise { try { - await provider.request({ method: 'wallet_getSnaps' }); - return true; + const provider = await getMetamaskProvider(); + + const snaps = await Promise.race([ + provider.request({ method: 'wallet_getSnaps' }), + new Promise((resolve, reject) => setTimeout(() => reject('MetaMask provider not found'), 1000)), + ]); + + return typeof snaps === 'object'; } catch (error) { return false; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df6494fbc..4267bf389 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -988,6 +988,9 @@ importers: packages/wallets/solflare: dependencies: + '@metamask/providers': + specifier: ^12.0.0 + version: 12.0.0 '@solana/wallet-adapter-base': specifier: workspace:^ version: link:../../core/base @@ -1003,6 +1006,9 @@ importers: '@wallet-standard/wallet': specifier: ^1.0.1 version: 1.0.1 + readable-stream: + specifier: ^3.6.2 + version: 3.6.2 devDependencies: '@solana/web3.js': specifier: ^1.77.3 @@ -4132,6 +4138,46 @@ packages: read-yaml-file: 1.1.0 dev: true + /@metamask/json-rpc-engine@7.1.1: + resolution: {integrity: sha512-wPB8Or74OqMwcxa87JPOEjXwtgpyHPEXiLKblKRAtCjTJNQFp1Co//1CgFm5xj4Z5JbBGfGFiQNnj09Et40sig==} + engines: {node: '>=16.0.0'} + dependencies: + '@metamask/rpc-errors': 6.0.0 + '@metamask/safe-event-emitter': 3.0.0 + '@metamask/utils': 8.1.0 + transitivePeerDependencies: + - supports-color + dev: false + + /@metamask/object-multiplex@1.2.0: + resolution: {integrity: sha512-hksV602d3NWE2Q30Mf2Np1WfVKaGqfJRy9vpHAmelbaD0OkDt06/0KQkRR6UVYdMbTbkuEu8xN5JDUU80inGwQ==} + engines: {node: '>=12.0.0'} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + readable-stream: 2.3.8 + dev: false + + /@metamask/providers@12.0.0: + resolution: {integrity: sha512-NkrSvOF8v8kDz9f2TY1AYK19hJdpYbYhbXWhjmmmXrSMYotn+o7ZV1b1Yd0fqD/HKVL0Vd2BWBUT9U0ggIDTEA==} + engines: {node: '>=16.0.0'} + dependencies: + '@metamask/json-rpc-engine': 7.1.1 + '@metamask/object-multiplex': 1.2.0 + '@metamask/rpc-errors': 6.0.0 + '@metamask/safe-event-emitter': 3.0.0 + '@metamask/utils': 8.1.0 + detect-browser: 5.3.0 + extension-port-stream: 2.1.1 + fast-deep-equal: 3.1.3 + is-stream: 2.0.1 + json-rpc-middleware-stream: 4.2.3 + pump: 3.0.0 + webextension-polyfill: 0.10.0 + transitivePeerDependencies: + - supports-color + dev: false + /@metamask/rpc-errors@5.1.1: resolution: {integrity: sha512-JjZnDi2y2CfvbohhBl+FOQRzmFlJpybcQlIk37zEX8B96eVSPbH/T8S0p7cSF8IE33IWx6JkD8Ycsd+2TXFxCw==} engines: {node: '>=16.0.0'} @@ -4142,6 +4188,25 @@ packages: - supports-color dev: false + /@metamask/rpc-errors@6.0.0: + resolution: {integrity: sha512-sAZwcdmidJDPbZV3XSKcWZC7CSTdjqDNRsDDdb2SstCOLEJtNqHpx32FWgwWB0arqWxUcUxYxgR39edUbsWz7A==} + engines: {node: '>=16.0.0'} + dependencies: + '@metamask/utils': 8.1.0 + fast-safe-stringify: 2.1.1 + transitivePeerDependencies: + - supports-color + dev: false + + /@metamask/safe-event-emitter@2.0.0: + resolution: {integrity: sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==} + dev: false + + /@metamask/safe-event-emitter@3.0.0: + resolution: {integrity: sha512-j6Z47VOmVyGMlnKXZmL0fyvWfEYtKWCA9yGZkU3FCsGZUT5lHGmvaV9JA5F2Y+010y7+ROtR3WMXIkvl/nVzqQ==} + engines: {node: '>=12.0.0'} + dev: false + /@metamask/utils@5.0.2: resolution: {integrity: sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g==} engines: {node: '>=14.0.0'} @@ -4155,6 +4220,20 @@ packages: - supports-color dev: false + /@metamask/utils@8.1.0: + resolution: {integrity: sha512-sFNpzBKRicDgM2ZuU6vrPROlqNGm8/jDsjc5WrU1RzCkAMc4Xr3vUUf8p59uQ6B09etUWNb8d2GTCbISdmH/Ug==} + engines: {node: '>=16.0.0'} + dependencies: + '@ethereumjs/tx': 4.2.0 + '@noble/hashes': 1.3.1 + '@types/debug': 4.1.8 + debug: 4.3.4 + semver: 7.5.4 + superstruct: 1.0.3 + transitivePeerDependencies: + - supports-color + dev: false + /@mischnic/json-sourcemap@0.1.0: resolution: {integrity: sha512-dQb3QnfNqmQNYA4nFSN/uLaByIic58gOXq4Y4XqLOWmOrw73KmJPt/HLyG0wvn1bnR6mBKs/Uwvkh+Hns1T0XA==} engines: {node: '>=12.0.0'} @@ -10469,6 +10548,13 @@ packages: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} dev: true + /extension-port-stream@2.1.1: + resolution: {integrity: sha512-qknp5o5rj2J9CRKfVB8KJr+uXQlrojNZzdESUPhKYLXf97TPcGf6qWWKmpsNNtUyOdzFhab1ON0jzouNxHHvow==} + engines: {node: '>=12.0.0'} + dependencies: + webextension-polyfill: 0.10.0 + dev: false + /external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} @@ -13036,6 +13122,23 @@ packages: /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + /json-rpc-engine@6.1.0: + resolution: {integrity: sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ==} + engines: {node: '>=10.0.0'} + dependencies: + '@metamask/safe-event-emitter': 2.0.0 + eth-rpc-errors: 4.0.3 + dev: false + + /json-rpc-middleware-stream@4.2.3: + resolution: {integrity: sha512-4iFb0yffm5vo3eFKDbQgke9o17XBcLQ2c3sONrXSbcOLzP8LTojqo8hRGVgtJShhm5q4ZDSNq039fAx9o65E1w==} + engines: {node: '>=14.0.0'} + dependencies: + '@metamask/safe-event-emitter': 3.0.0 + json-rpc-engine: 6.1.0 + readable-stream: 2.3.8 + dev: false + /json-rpc-random-id@1.0.1: resolution: {integrity: sha512-RJ9YYNCkhVDBuP4zN5BBtYAzEl03yq/jIIsyif0JY9qyJuQQZNeDK7anAPKKlyEtLSj2s8h6hNh2F8zO5q7ScA==} dev: false @@ -18718,6 +18821,10 @@ packages: resolution: {integrity: sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==} dev: false + /webextension-polyfill@0.10.0: + resolution: {integrity: sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==} + dev: false + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} From 768daf91248504b3b13c732826e910783116da10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Varga-Somogyi=20=C3=81kos?= Date: Mon, 9 Oct 2023 20:19:54 +0200 Subject: [PATCH 2/9] Instance guard --- packages/wallets/solflare/src/metamask/detect.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/wallets/solflare/src/metamask/detect.ts b/packages/wallets/solflare/src/metamask/detect.ts index 21cde4bb2..3e5c04353 100644 --- a/packages/wallets/solflare/src/metamask/detect.ts +++ b/packages/wallets/solflare/src/metamask/detect.ts @@ -29,13 +29,13 @@ export function detectAndRegisterSolflareMetaMaskWallet(): boolean { } async function getMetamaskProvider(): Promise { + const { WindowPostMessageStream } = await import('./WindowPostMessageStream.js'); + const { MetaMaskInpageProvider } = await import('@metamask/providers'); + if (providerInstance) { return providerInstance; } - const { WindowPostMessageStream } = await import('./WindowPostMessageStream.js'); - const { MetaMaskInpageProvider } = await import('@metamask/providers'); - const metamaskStream = new WindowPostMessageStream({ name: 'metamask-inpage', target: 'metamask-contentscript', From ec7904abec0646ae0fd2ad99189ff29dc2075a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Varga-Somogyi=20=C3=81kos?= Date: Tue, 10 Oct 2023 12:08:13 +0200 Subject: [PATCH 3/9] Rewrite to low level postMessage --- packages/wallets/solflare/package.json | 4 +- .../src/metamask/WindowPostMessageStream.ts | 128 ------------------ .../wallets/solflare/src/metamask/detect.ts | 60 +++++--- pnpm-lock.yaml | 107 --------------- 4 files changed, 40 insertions(+), 259 deletions(-) delete mode 100644 packages/wallets/solflare/src/metamask/WindowPostMessageStream.ts diff --git a/packages/wallets/solflare/package.json b/packages/wallets/solflare/package.json index 1b1fb183f..5765342e8 100644 --- a/packages/wallets/solflare/package.json +++ b/packages/wallets/solflare/package.json @@ -35,13 +35,11 @@ "@solana/web3.js": "^1.77.3" }, "dependencies": { - "@metamask/providers": "^12.0.0", "@solana/wallet-adapter-base": "workspace:^", "@solana/wallet-standard-chains": "^1.1.0", "@solflare-wallet/metamask-sdk": "^1.0.2", "@solflare-wallet/sdk": "^1.3.0", - "@wallet-standard/wallet": "^1.0.1", - "readable-stream": "^3.6.2" + "@wallet-standard/wallet": "^1.0.1" }, "devDependencies": { "@solana/web3.js": "^1.77.3", diff --git a/packages/wallets/solflare/src/metamask/WindowPostMessageStream.ts b/packages/wallets/solflare/src/metamask/WindowPostMessageStream.ts deleted file mode 100644 index 3863d5faa..000000000 --- a/packages/wallets/solflare/src/metamask/WindowPostMessageStream.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Duplex } from 'readable-stream'; - -type StreamData = number | string | Record | unknown[]; - -interface StreamMessage { - data: StreamData; - [key: string]: unknown; -} - -export interface PostMessageEvent { - data?: StreamData; - origin: string; - source: typeof window; -} - -interface WindowPostMessageStreamArgs { - name: string; - target: string; - targetOrigin?: string; - targetWindow?: Window; -} - -function isValidStreamMessage(message: unknown): message is StreamMessage { - return ( - isObject(message) && - Boolean(message.data) && - (typeof message.data === 'number' || typeof message.data === 'object' || typeof message.data === 'string') - ); -} - -function isObject(value: unknown): value is Record { - return Boolean(value) && typeof value === 'object' && !Array.isArray(value); -} - -const getSource = Object.getOwnPropertyDescriptor(MessageEvent.prototype, 'source')?.get; - -const getOrigin = Object.getOwnPropertyDescriptor(MessageEvent.prototype, 'origin')?.get; - -export class WindowPostMessageStream extends Duplex { - private _name: string; - - private _target: string; - - private _targetOrigin: string; - - private _targetWindow: Window; - - /** - * Creates a stream for communicating with other streams across the same or - * different `window` objects. - * - * @param args - Options bag. - * @param args.name - The name of the stream. Used to differentiate between - * multiple streams sharing the same window object. - * @param args.target - The name of the stream to exchange messages with. - * @param args.targetOrigin - The origin of the target. Defaults to - * `location.origin`, '*' is permitted. - * @param args.targetWindow - The window object of the target stream. Defaults - * to `window`. - */ - constructor({ name, target, targetOrigin = location.origin, targetWindow = window }: WindowPostMessageStreamArgs) { - super({ - objectMode: true, - }); - - if (typeof window === 'undefined' || typeof window.postMessage !== 'function') { - throw new Error( - 'window.postMessage is not a function. This class should only be instantiated in a Window.' - ); - } - - this._name = name; - this._target = target; - this._targetOrigin = targetOrigin; - this._targetWindow = targetWindow; - this._onMessage = this._onMessage.bind(this); - - window.addEventListener('message', this._onMessage as any, false); - } - - protected _onData(data: StreamData): void { - try { - this.push(data); - } catch (err) { - this.emit('error', err); - } - } - - _read(): void { - return undefined; - } - - _write(data: StreamData, _encoding: string | null, cb: () => void): void { - this._postMessage(data); - cb(); - } - - protected _postMessage(data: unknown): void { - this._targetWindow.postMessage( - { - target: this._target, - data, - }, - this._targetOrigin - ); - } - - private _onMessage(event: PostMessageEvent): void { - const message = event.data; - - /* eslint-disable @typescript-eslint/no-non-null-assertion */ - if ( - (this._targetOrigin !== '*' && getOrigin!.call(event) !== this._targetOrigin) || - getSource!.call(event) !== this._targetWindow || - !isValidStreamMessage(message) || - message.target !== this._name - ) { - return; - } - /* eslint-enable @typescript-eslint/no-non-null-assertion */ - - this._onData(message.data); - } - - _destroy(): void { - window.removeEventListener('message', this._onMessage as any, false); - } -} diff --git a/packages/wallets/solflare/src/metamask/detect.ts b/packages/wallets/solflare/src/metamask/detect.ts index 3e5c04353..c96eb0c7f 100644 --- a/packages/wallets/solflare/src/metamask/detect.ts +++ b/packages/wallets/solflare/src/metamask/detect.ts @@ -1,17 +1,16 @@ -import type { MetaMaskInpageProvider } from '@metamask/providers'; import { registerWallet } from '@wallet-standard/wallet'; import { SolflareMetaMaskWallet } from './wallet.js'; -let providerInstance: MetaMaskInpageProvider | null = null; - let stopPolling = false; +let counter = 10; /** @internal */ export function detectAndRegisterSolflareMetaMaskWallet(): boolean { // If detected, stop polling. - if (stopPolling) return true; + if (stopPolling || counter <= 0) return true; (async function () { try { + counter--; // Try to detect, stop polling if detected, and register the wallet. if (await isSnapSupported()) { if (!stopPolling) { @@ -28,32 +27,51 @@ export function detectAndRegisterSolflareMetaMaskWallet(): boolean { return false; } -async function getMetamaskProvider(): Promise { - const { WindowPostMessageStream } = await import('./WindowPostMessageStream.js'); - const { MetaMaskInpageProvider } = await import('@metamask/providers'); +async function metamaskRequest(request: Record) { + return new Promise((resolve, reject) => { + const id = Math.floor(Math.random() * 1000000).toString(); - if (providerInstance) { - return providerInstance; - } + function handleMessage(event: MessageEvent) { + const message = event.data; - const metamaskStream = new WindowPostMessageStream({ - name: 'metamask-inpage', - target: 'metamask-contentscript', - }) as any; + if ( + message?.target === 'metamask-inpage' && + message.data?.name === 'metamask-provider' && + message.data.data?.id === id + ) { + window.removeEventListener('message', handleMessage); - providerInstance = new MetaMaskInpageProvider(metamaskStream, { - shouldSendMetadata: false, - }); + if (message?.data.data.error) { + reject(message.data.data.error.message); + } else { + resolve(message.data.data.result); + } + } + } + + window.addEventListener('message', handleMessage); - return providerInstance; + window.postMessage( + { + target: 'metamask-contentscript', + data: { + name: 'metamask-provider', + data: { + ...request, + jsonrpc: '2.0', + id, + }, + }, + }, + window.location.origin + ); + }); } async function isSnapSupported(): Promise { try { - const provider = await getMetamaskProvider(); - const snaps = await Promise.race([ - provider.request({ method: 'wallet_getSnaps' }), + metamaskRequest({ method: 'wallet_getSnaps' }), new Promise((resolve, reject) => setTimeout(() => reject('MetaMask provider not found'), 1000)), ]); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4267bf389..df6494fbc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -988,9 +988,6 @@ importers: packages/wallets/solflare: dependencies: - '@metamask/providers': - specifier: ^12.0.0 - version: 12.0.0 '@solana/wallet-adapter-base': specifier: workspace:^ version: link:../../core/base @@ -1006,9 +1003,6 @@ importers: '@wallet-standard/wallet': specifier: ^1.0.1 version: 1.0.1 - readable-stream: - specifier: ^3.6.2 - version: 3.6.2 devDependencies: '@solana/web3.js': specifier: ^1.77.3 @@ -4138,46 +4132,6 @@ packages: read-yaml-file: 1.1.0 dev: true - /@metamask/json-rpc-engine@7.1.1: - resolution: {integrity: sha512-wPB8Or74OqMwcxa87JPOEjXwtgpyHPEXiLKblKRAtCjTJNQFp1Co//1CgFm5xj4Z5JbBGfGFiQNnj09Et40sig==} - engines: {node: '>=16.0.0'} - dependencies: - '@metamask/rpc-errors': 6.0.0 - '@metamask/safe-event-emitter': 3.0.0 - '@metamask/utils': 8.1.0 - transitivePeerDependencies: - - supports-color - dev: false - - /@metamask/object-multiplex@1.2.0: - resolution: {integrity: sha512-hksV602d3NWE2Q30Mf2Np1WfVKaGqfJRy9vpHAmelbaD0OkDt06/0KQkRR6UVYdMbTbkuEu8xN5JDUU80inGwQ==} - engines: {node: '>=12.0.0'} - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - readable-stream: 2.3.8 - dev: false - - /@metamask/providers@12.0.0: - resolution: {integrity: sha512-NkrSvOF8v8kDz9f2TY1AYK19hJdpYbYhbXWhjmmmXrSMYotn+o7ZV1b1Yd0fqD/HKVL0Vd2BWBUT9U0ggIDTEA==} - engines: {node: '>=16.0.0'} - dependencies: - '@metamask/json-rpc-engine': 7.1.1 - '@metamask/object-multiplex': 1.2.0 - '@metamask/rpc-errors': 6.0.0 - '@metamask/safe-event-emitter': 3.0.0 - '@metamask/utils': 8.1.0 - detect-browser: 5.3.0 - extension-port-stream: 2.1.1 - fast-deep-equal: 3.1.3 - is-stream: 2.0.1 - json-rpc-middleware-stream: 4.2.3 - pump: 3.0.0 - webextension-polyfill: 0.10.0 - transitivePeerDependencies: - - supports-color - dev: false - /@metamask/rpc-errors@5.1.1: resolution: {integrity: sha512-JjZnDi2y2CfvbohhBl+FOQRzmFlJpybcQlIk37zEX8B96eVSPbH/T8S0p7cSF8IE33IWx6JkD8Ycsd+2TXFxCw==} engines: {node: '>=16.0.0'} @@ -4188,25 +4142,6 @@ packages: - supports-color dev: false - /@metamask/rpc-errors@6.0.0: - resolution: {integrity: sha512-sAZwcdmidJDPbZV3XSKcWZC7CSTdjqDNRsDDdb2SstCOLEJtNqHpx32FWgwWB0arqWxUcUxYxgR39edUbsWz7A==} - engines: {node: '>=16.0.0'} - dependencies: - '@metamask/utils': 8.1.0 - fast-safe-stringify: 2.1.1 - transitivePeerDependencies: - - supports-color - dev: false - - /@metamask/safe-event-emitter@2.0.0: - resolution: {integrity: sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==} - dev: false - - /@metamask/safe-event-emitter@3.0.0: - resolution: {integrity: sha512-j6Z47VOmVyGMlnKXZmL0fyvWfEYtKWCA9yGZkU3FCsGZUT5lHGmvaV9JA5F2Y+010y7+ROtR3WMXIkvl/nVzqQ==} - engines: {node: '>=12.0.0'} - dev: false - /@metamask/utils@5.0.2: resolution: {integrity: sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g==} engines: {node: '>=14.0.0'} @@ -4220,20 +4155,6 @@ packages: - supports-color dev: false - /@metamask/utils@8.1.0: - resolution: {integrity: sha512-sFNpzBKRicDgM2ZuU6vrPROlqNGm8/jDsjc5WrU1RzCkAMc4Xr3vUUf8p59uQ6B09etUWNb8d2GTCbISdmH/Ug==} - engines: {node: '>=16.0.0'} - dependencies: - '@ethereumjs/tx': 4.2.0 - '@noble/hashes': 1.3.1 - '@types/debug': 4.1.8 - debug: 4.3.4 - semver: 7.5.4 - superstruct: 1.0.3 - transitivePeerDependencies: - - supports-color - dev: false - /@mischnic/json-sourcemap@0.1.0: resolution: {integrity: sha512-dQb3QnfNqmQNYA4nFSN/uLaByIic58gOXq4Y4XqLOWmOrw73KmJPt/HLyG0wvn1bnR6mBKs/Uwvkh+Hns1T0XA==} engines: {node: '>=12.0.0'} @@ -10548,13 +10469,6 @@ packages: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} dev: true - /extension-port-stream@2.1.1: - resolution: {integrity: sha512-qknp5o5rj2J9CRKfVB8KJr+uXQlrojNZzdESUPhKYLXf97TPcGf6qWWKmpsNNtUyOdzFhab1ON0jzouNxHHvow==} - engines: {node: '>=12.0.0'} - dependencies: - webextension-polyfill: 0.10.0 - dev: false - /external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} @@ -13122,23 +13036,6 @@ packages: /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - /json-rpc-engine@6.1.0: - resolution: {integrity: sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ==} - engines: {node: '>=10.0.0'} - dependencies: - '@metamask/safe-event-emitter': 2.0.0 - eth-rpc-errors: 4.0.3 - dev: false - - /json-rpc-middleware-stream@4.2.3: - resolution: {integrity: sha512-4iFb0yffm5vo3eFKDbQgke9o17XBcLQ2c3sONrXSbcOLzP8LTojqo8hRGVgtJShhm5q4ZDSNq039fAx9o65E1w==} - engines: {node: '>=14.0.0'} - dependencies: - '@metamask/safe-event-emitter': 3.0.0 - json-rpc-engine: 6.1.0 - readable-stream: 2.3.8 - dev: false - /json-rpc-random-id@1.0.1: resolution: {integrity: sha512-RJ9YYNCkhVDBuP4zN5BBtYAzEl03yq/jIIsyif0JY9qyJuQQZNeDK7anAPKKlyEtLSj2s8h6hNh2F8zO5q7ScA==} dev: false @@ -18821,10 +18718,6 @@ packages: resolution: {integrity: sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==} dev: false - /webextension-polyfill@0.10.0: - resolution: {integrity: sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==} - dev: false - /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} From 65b7cc0a3166a073a05f0aae5fc784ad3cf28236 Mon Sep 17 00:00:00 2001 From: Jordan Sexton Date: Tue, 10 Oct 2023 10:15:53 -0600 Subject: [PATCH 4/9] lint --- packages/wallets/solflare/src/adapter.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/wallets/solflare/src/adapter.ts b/packages/wallets/solflare/src/adapter.ts index 5d509c0c0..8aaa0dff1 100644 --- a/packages/wallets/solflare/src/adapter.ts +++ b/packages/wallets/solflare/src/adapter.ts @@ -1,10 +1,6 @@ import type { WalletAdapterNetwork, WalletName } from '@solana/wallet-adapter-base'; import { BaseMessageSignerWalletAdapter, - isIosAndRedirectable, - isVersionedTransaction, - scopePollingDetectionStrategy, - type SendTransactionOptions, WalletConfigError, WalletConnectionError, WalletDisconnectedError, @@ -18,9 +14,13 @@ import { WalletSendTransactionError, WalletSignMessageError, WalletSignTransactionError, + isIosAndRedirectable, + isVersionedTransaction, + scopePollingDetectionStrategy, + type SendTransactionOptions, } from '@solana/wallet-adapter-base'; import type { Transaction, TransactionVersion, VersionedTransaction } from '@solana/web3.js'; -import { type Connection, PublicKey, type TransactionSignature } from '@solana/web3.js'; +import { PublicKey, type Connection, type TransactionSignature } from '@solana/web3.js'; import type { default as Solflare } from '@solflare-wallet/sdk'; import { detectAndRegisterSolflareMetaMaskWallet } from './metamask/detect.js'; From 5615cab058d962340e6f61e55a2598efccce1867 Mon Sep 17 00:00:00 2001 From: Jordan Sexton Date: Tue, 10 Oct 2023 10:16:04 -0600 Subject: [PATCH 5/9] simplify detection --- packages/wallets/solflare/src/adapter.ts | 2 +- .../wallets/solflare/src/metamask/detect.ts | 97 ++++++------------- 2 files changed, 29 insertions(+), 70 deletions(-) diff --git a/packages/wallets/solflare/src/adapter.ts b/packages/wallets/solflare/src/adapter.ts index 8aaa0dff1..089773327 100644 --- a/packages/wallets/solflare/src/adapter.ts +++ b/packages/wallets/solflare/src/adapter.ts @@ -71,7 +71,7 @@ export class SolflareWalletAdapter extends BaseMessageSignerWalletAdapter { } return false; }); - scopePollingDetectionStrategy(detectAndRegisterSolflareMetaMaskWallet); + detectAndRegisterSolflareMetaMaskWallet(); } } diff --git a/packages/wallets/solflare/src/metamask/detect.ts b/packages/wallets/solflare/src/metamask/detect.ts index c96eb0c7f..02c6150a5 100644 --- a/packages/wallets/solflare/src/metamask/detect.ts +++ b/packages/wallets/solflare/src/metamask/detect.ts @@ -1,82 +1,41 @@ import { registerWallet } from '@wallet-standard/wallet'; import { SolflareMetaMaskWallet } from './wallet.js'; -let stopPolling = false; -let counter = 10; - /** @internal */ -export function detectAndRegisterSolflareMetaMaskWallet(): boolean { - // If detected, stop polling. - if (stopPolling || counter <= 0) return true; - (async function () { - try { - counter--; - // Try to detect, stop polling if detected, and register the wallet. - if (await isSnapSupported()) { - if (!stopPolling) { - stopPolling = true; - registerWallet(new SolflareMetaMaskWallet()); - } - } - } catch (error) { - // Stop polling on unhandled errors (this should never happen). - stopPolling = true; - } - })(); - // Keep polling. - return false; -} - -async function metamaskRequest(request: Record) { - return new Promise((resolve, reject) => { - const id = Math.floor(Math.random() * 1000000).toString(); - - function handleMessage(event: MessageEvent) { - const message = event.data; - - if ( - message?.target === 'metamask-inpage' && - message.data?.name === 'metamask-provider' && - message.data.data?.id === id - ) { - window.removeEventListener('message', handleMessage); - - if (message?.data.data.error) { - reject(message.data.data.error.message); - } else { - resolve(message.data.data.result); - } +export async function detectAndRegisterSolflareMetaMaskWallet(): Promise { + const id = 'solflare-detect-metamask'; + + function onMessage(event: MessageEvent) { + const message = event.data; + if ( + message?.target === 'metamask-inpage' && + message.data?.name === 'metamask-provider' && + message.data.data?.id === id + ) { + window.removeEventListener('message', onMessage); + + if (!message.data.data.error) { + registerWallet(new SolflareMetaMaskWallet()); } } + } - window.addEventListener('message', handleMessage); + window.addEventListener('message', onMessage); - window.postMessage( - { - target: 'metamask-contentscript', + window.postMessage( + { + target: 'metamask-contentscript', + data: { + name: 'metamask-provider', data: { - name: 'metamask-provider', - data: { - ...request, - jsonrpc: '2.0', - id, - }, + id, + jsonrpc: '2.0', + method: 'wallet_getSnaps', }, }, - window.location.origin - ); - }); -} - -async function isSnapSupported(): Promise { - try { - const snaps = await Promise.race([ - metamaskRequest({ method: 'wallet_getSnaps' }), - new Promise((resolve, reject) => setTimeout(() => reject('MetaMask provider not found'), 1000)), - ]); + }, + window.location.origin + ); - return typeof snaps === 'object'; - } catch (error) { - return false; - } + setTimeout(() => window.removeEventListener('message', onMessage), 5000); } From b9634cb03467ed04850811050c0b2ff93954f8e0 Mon Sep 17 00:00:00 2001 From: Jordan Sexton Date: Tue, 10 Oct 2023 10:18:47 -0600 Subject: [PATCH 6/9] explicit global --- packages/wallets/solflare/src/metamask/detect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wallets/solflare/src/metamask/detect.ts b/packages/wallets/solflare/src/metamask/detect.ts index 02c6150a5..0f3589ab0 100644 --- a/packages/wallets/solflare/src/metamask/detect.ts +++ b/packages/wallets/solflare/src/metamask/detect.ts @@ -37,5 +37,5 @@ export async function detectAndRegisterSolflareMetaMaskWallet(): Promise { window.location.origin ); - setTimeout(() => window.removeEventListener('message', onMessage), 5000); + window.setTimeout(() => window.removeEventListener('message', onMessage), 5000); } From db62745f22880b29dc67ec47939dfc8b4270b669 Mon Sep 17 00:00:00 2001 From: Jordan Sexton Date: Tue, 10 Oct 2023 10:29:28 -0600 Subject: [PATCH 7/9] send message on any message received --- .../wallets/solflare/src/metamask/detect.ts | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/packages/wallets/solflare/src/metamask/detect.ts b/packages/wallets/solflare/src/metamask/detect.ts index 0f3589ab0..e9746579b 100644 --- a/packages/wallets/solflare/src/metamask/detect.ts +++ b/packages/wallets/solflare/src/metamask/detect.ts @@ -5,37 +5,40 @@ import { SolflareMetaMaskWallet } from './wallet.js'; export async function detectAndRegisterSolflareMetaMaskWallet(): Promise { const id = 'solflare-detect-metamask'; + function postMessage() { + window.postMessage( + { + target: 'metamask-contentscript', + data: { + name: 'metamask-provider', + data: { + id, + jsonrpc: '2.0', + method: 'wallet_getSnaps', + }, + }, + }, + window.location.origin + ); + } + function onMessage(event: MessageEvent) { const message = event.data; - if ( - message?.target === 'metamask-inpage' && - message.data?.name === 'metamask-provider' && - message.data.data?.id === id - ) { - window.removeEventListener('message', onMessage); + if (message?.target === 'metamask-inpage' && message.data?.name === 'metamask-provider') { + if (message.data.data?.id === id) { + window.removeEventListener('message', onMessage); - if (!message.data.data.error) { - registerWallet(new SolflareMetaMaskWallet()); + if (!message.data.data.error) { + registerWallet(new SolflareMetaMaskWallet()); + } + } else { + postMessage(); } } } window.addEventListener('message', onMessage); - - window.postMessage( - { - target: 'metamask-contentscript', - data: { - name: 'metamask-provider', - data: { - id, - jsonrpc: '2.0', - method: 'wallet_getSnaps', - }, - }, - }, - window.location.origin - ); - window.setTimeout(() => window.removeEventListener('message', onMessage), 5000); + + postMessage(); } From ae3cc2636b63304f7b92bdb6343f78ef9dcd3078 Mon Sep 17 00:00:00 2001 From: Jordan Sexton Date: Tue, 10 Oct 2023 10:38:32 -0600 Subject: [PATCH 8/9] only register once --- packages/wallets/solflare/src/metamask/detect.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/wallets/solflare/src/metamask/detect.ts b/packages/wallets/solflare/src/metamask/detect.ts index e9746579b..0990c1213 100644 --- a/packages/wallets/solflare/src/metamask/detect.ts +++ b/packages/wallets/solflare/src/metamask/detect.ts @@ -1,6 +1,14 @@ import { registerWallet } from '@wallet-standard/wallet'; import { SolflareMetaMaskWallet } from './wallet.js'; +let registered = false; + +function register() { + if (registered) return; + registerWallet(new SolflareMetaMaskWallet()); + registered = true; +} + /** @internal */ export async function detectAndRegisterSolflareMetaMaskWallet(): Promise { const id = 'solflare-detect-metamask'; @@ -29,7 +37,7 @@ export async function detectAndRegisterSolflareMetaMaskWallet(): Promise { window.removeEventListener('message', onMessage); if (!message.data.data.error) { - registerWallet(new SolflareMetaMaskWallet()); + register(); } } else { postMessage(); From 3d2e0cd542a0a150107bb5fa876dee511b9fd618 Mon Sep 17 00:00:00 2001 From: Jordan Sexton Date: Tue, 10 Oct 2023 10:55:01 -0600 Subject: [PATCH 9/9] add changeset --- .changeset/fuzzy-cows-hope.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fuzzy-cows-hope.md diff --git a/.changeset/fuzzy-cows-hope.md b/.changeset/fuzzy-cows-hope.md new file mode 100644 index 000000000..7f6751946 --- /dev/null +++ b/.changeset/fuzzy-cows-hope.md @@ -0,0 +1,5 @@ +--- +'@solana/wallet-adapter-solflare': patch +--- + +Optimize Solflare MetaMask snap detection