Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@
"allowScripts": {
"@lavamoat/preinstall-always-fail": false,
"vite>esbuild": false,
"@biomejs/biome": false
"@biomejs/biome": false,
"vitest>vite-node>vite>esbuild": false,
"vitest>vite>esbuild": false
}
}
}
53 changes: 53 additions & 0 deletions src/helpers/metamaskExtensionId.ts → src/helpers/metamask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,56 @@ function isProviderMessage(event: MessageEvent) {
const { target, data } = event.data;
return target === INPAGE && data?.name === METAMASK_PROVIDER_STREAM_NAME && event.origin === location.origin;
}

// EIP-6963 types
interface EIP6963ProviderInfo {
uuid: string;
name: string;
icon: string;
rdns: string;
}

interface EIP6963ProviderDetail {
info: EIP6963ProviderInfo;
provider: unknown;
}

interface EIP6963AnnounceProviderEvent extends CustomEvent {
type: 'eip6963:announceProvider';
detail: EIP6963ProviderDetail;
}

// Augment WindowEventMap to avoid type casting
declare global {
interface WindowEventMap {
'eip6963:announceProvider': EIP6963AnnounceProviderEvent;
'eip6963:requestProvider': Event;
}
}

/**
* Checks if MetaMask is installed by listening for EIP-6963 provider announcements
* @param timeout - Maximum time to wait for the announcement (in ms)
* @returns Promise that resolves to true if MetaMask is installed, false otherwise
*/
export async function isMetamaskInstalled({ timeout = 2000 }: { timeout?: number } = {}): Promise<boolean> {
return new Promise((resolve) => {
const handleEip6963Announcement = (event: EIP6963AnnounceProviderEvent) => {
if (event.detail.info.rdns.includes('io.metamask')) {
window.removeEventListener('eip6963:announceProvider', handleEip6963Announcement);
clearTimeout(timeoutId);
resolve(true);
}
};

const timeoutId = setTimeout(() => {
window.removeEventListener('eip6963:announceProvider', handleEip6963Announcement);
resolve(false);
}, timeout);

// DApp MUST set up listener BEFORE dispatching request event
window.addEventListener('eip6963:announceProvider', handleEip6963Announcement);
// Dispatch request event to trigger announcements from wallets that loaded earlier
window.dispatchEvent(new Event('eip6963:requestProvider'));
});
}
2 changes: 1 addition & 1 deletion src/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const getUniqueId = (): number => {
* Detects if we're in a Chrome-like environment with extension support
*/
export const isChromeRuntime = (): boolean => {
return typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.connect === 'function';
return typeof chrome !== 'undefined' && !!chrome.runtime && typeof chrome.runtime.connect === 'function';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was returning undefined instead of a boolean in case chrome.runtime was undefined

};

/**
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ export type * from './types/session';
export type * from './types/multichainApi';
export type * from './types/scopes';
export * from './types/errors';
export { isMetamaskInstalled } from './helpers/metamask';
4 changes: 2 additions & 2 deletions src/transports/externallyConnectableTransport.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { type MockPort, mockSession } from '../../tests/mocks';
import * as metamaskExtensionId from '../helpers/metamaskExtensionId';
import * as metamaskHelper from '../helpers/metamask';
import * as utils from '../helpers/utils';
import { TransportError } from '../types/errors';
import { getExternallyConnectableTransport } from './externallyConnectableTransport';
Expand Down Expand Up @@ -60,7 +60,7 @@ describe('ExternallyConnectableTransport', () => {
});

it('should fetch extension id when not provided', async () => {
const detectSpy = vi.spyOn(metamaskExtensionId, 'detectMetamaskExtensionId').mockResolvedValue(testExtensionId);
const detectSpy = vi.spyOn(metamaskHelper, 'detectMetamaskExtensionId').mockResolvedValue(testExtensionId);

const newTransport = getExternallyConnectableTransport({});
await newTransport.connect();
Expand Down
2 changes: 1 addition & 1 deletion src/transports/externallyConnectableTransport.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { detectMetamaskExtensionId } from '../helpers/metamaskExtensionId';
import { detectMetamaskExtensionId } from '../helpers/metamask';
import { getUniqueId, withTimeout } from '../helpers/utils';
import { TransportError, TransportTimeoutError } from '../types/errors';
import type { Transport, TransportResponse } from '../types/transport';
Expand Down
Loading
Loading