dappress 0.3.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/README.md +59 -13
- package/dist/actions.d.ts +2 -0
- package/dist/actions.js +114 -0
- package/dist/browser.d.ts +7 -0
- package/dist/browser.js +45 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.js +82 -0
- package/dist/download.d.ts +7 -0
- package/dist/download.js +48 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +55 -0
- package/dist/metamask-pages.d.ts +17 -0
- package/dist/metamask-pages.js +143 -0
- package/dist/metamask.d.ts +53 -0
- package/dist/metamask.js +830 -0
- package/dist/page-helpers.d.ts +33 -0
- package/dist/page-helpers.js +199 -0
- package/dist/profile.d.ts +15 -0
- package/dist/profile.js +165 -0
- package/dist/support.d.ts +45 -0
- package/dist/support.js +113 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.js +4 -0
- package/package.json +27 -15
- package/src/actions.js +0 -67
- package/src/browser.js +0 -42
- package/src/config.js +0 -78
- package/src/download.js +0 -50
- package/src/index.js +0 -56
- package/src/metamask-pages.js +0 -121
- package/src/metamask.js +0 -427
- package/src/page-helpers.js +0 -194
- package/src/profile.js +0 -125
- package/src/support.js +0 -90
- package/types/index.d.ts +0 -55
- package/types/support.d.ts +0 -42
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Find MetaMask's pages in the browser Cypress launched: the full-screen home
|
|
3
|
+
// page, and the surface where dapp requests show up. On Chrome that surface
|
|
4
|
+
// is the side panel when it is open, and the "MetaMask Dialog" popup otherwise.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.findExtensionId = findExtensionId;
|
|
7
|
+
exports.getHomePage = getHomePage;
|
|
8
|
+
exports.getRequestPages = getRequestPages;
|
|
9
|
+
exports.getConfirmationPage = getConfirmationPage;
|
|
10
|
+
exports.withUsableWindow = withUsableWindow;
|
|
11
|
+
const page_helpers_1 = require("./page-helpers");
|
|
12
|
+
const HOME_PATH = '/home.html';
|
|
13
|
+
const CONFIRMATION_PATHS = ['/notification.html', '/sidepanel.html'];
|
|
14
|
+
function extensionIdOf(url) {
|
|
15
|
+
const match = /^chrome-extension:\/\/([a-z]{32})\//.exec(url);
|
|
16
|
+
return match ? match[1] : null;
|
|
17
|
+
}
|
|
18
|
+
function isHomeRoute(url) {
|
|
19
|
+
return url.includes('/notification.html') && /notification\.html(#\/?)?$/.test(url);
|
|
20
|
+
}
|
|
21
|
+
function pagesOf(browser, extensionId, pathname) {
|
|
22
|
+
return browser.targets().filter((target) => target.type() === 'page' && target.url().startsWith(`chrome-extension://${extensionId}${pathname}`));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* MetaMask's extension id. Cypress loads its own extension in headed
|
|
26
|
+
* Chromium browsers, so the id is the one whose home page is titled "MetaMask".
|
|
27
|
+
*/
|
|
28
|
+
async function findExtensionId(browser, timeout = 30000) {
|
|
29
|
+
const deadline = Date.now() + timeout;
|
|
30
|
+
while (Date.now() < deadline) {
|
|
31
|
+
const ids = new Set(browser
|
|
32
|
+
.targets()
|
|
33
|
+
.map((target) => extensionIdOf(target.url()))
|
|
34
|
+
.filter((id) => id !== null));
|
|
35
|
+
for (const id of ids) {
|
|
36
|
+
if (await isMetaMask(browser, id))
|
|
37
|
+
return id;
|
|
38
|
+
}
|
|
39
|
+
await (0, page_helpers_1.sleep)(500);
|
|
40
|
+
}
|
|
41
|
+
throw new Error('[dappress] MetaMask extension not found in the browser. Is it loaded in before:browser:launch?');
|
|
42
|
+
}
|
|
43
|
+
async function isMetaMask(browser, extensionId) {
|
|
44
|
+
const [target] = pagesOf(browser, extensionId, HOME_PATH);
|
|
45
|
+
if (target) {
|
|
46
|
+
// A page still loading or redirecting loses its execution context while its
|
|
47
|
+
// title is read; not MetaMask yet, findExtensionId looks again
|
|
48
|
+
try {
|
|
49
|
+
const page = await pageOf(target);
|
|
50
|
+
return (await page.title()).startsWith('MetaMask');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const page = await browser.newPage();
|
|
57
|
+
try {
|
|
58
|
+
const response = await page.goto(`chrome-extension://${extensionId}${HOME_PATH}`, { waitUntil: 'domcontentloaded' });
|
|
59
|
+
return Boolean(response && response.ok()) && (await page.title()).startsWith('MetaMask');
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
await page.close();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// A target of type "page" has a page; Puppeteer types it as one that may not
|
|
69
|
+
async function pageOf(target) {
|
|
70
|
+
const page = await target.page();
|
|
71
|
+
if (!page)
|
|
72
|
+
throw new Error(`[dappress] No page behind ${target.url()}`);
|
|
73
|
+
return page;
|
|
74
|
+
}
|
|
75
|
+
/** The full-screen MetaMask page, opened if it isn't already. */
|
|
76
|
+
async function getHomePage(browser, extensionId) {
|
|
77
|
+
const [target] = pagesOf(browser, extensionId, HOME_PATH);
|
|
78
|
+
if (target)
|
|
79
|
+
return pageOf(target);
|
|
80
|
+
const page = await browser.newPage();
|
|
81
|
+
await page.goto(`chrome-extension://${extensionId}${HOME_PATH}`, { waitUntil: 'domcontentloaded' });
|
|
82
|
+
return page;
|
|
83
|
+
}
|
|
84
|
+
/** The pages open where dapp requests show up: the side panel, a popup. */
|
|
85
|
+
async function getRequestPages(browser, extensionId) {
|
|
86
|
+
const pages = [];
|
|
87
|
+
for (const pathname of CONFIRMATION_PATHS) {
|
|
88
|
+
for (const target of pagesOf(browser, extensionId, pathname)) {
|
|
89
|
+
const page = await target.page();
|
|
90
|
+
if (page)
|
|
91
|
+
pages.push(pathname === '/notification.html' ? await withUsableWindow(page) : page);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return pages;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The page a dapp request is shown on. Waits for the popup to open, or takes
|
|
98
|
+
* the side panel. A popup still on its home route is one closing after the
|
|
99
|
+
* previous request, or one that hasn't routed to the request yet: skipped.
|
|
100
|
+
*/
|
|
101
|
+
async function getConfirmationPage(browser, extensionId, timeout) {
|
|
102
|
+
const deadline = Date.now() + timeout;
|
|
103
|
+
while (Date.now() < deadline) {
|
|
104
|
+
for (const pathname of CONFIRMATION_PATHS) {
|
|
105
|
+
const [target] = pagesOf(browser, extensionId, pathname).filter((candidate) => !isHomeRoute(candidate.url()));
|
|
106
|
+
if (target)
|
|
107
|
+
return pathname === '/notification.html' ? withUsableWindow(await pageOf(target)) : pageOf(target);
|
|
108
|
+
}
|
|
109
|
+
await (0, page_helpers_1.sleep)(250);
|
|
110
|
+
}
|
|
111
|
+
const seen = browser
|
|
112
|
+
.targets()
|
|
113
|
+
.filter((target) => extensionIdOf(target.url()) === extensionId)
|
|
114
|
+
.map((target) => `${target.type()} ${target.url()}`)
|
|
115
|
+
.join(', ');
|
|
116
|
+
throw new Error(`[dappress] MetaMask showed no confirmation within ${timeout}ms. Did the dapp send a request? MetaMask pages seen: ${seen || 'none'}`);
|
|
117
|
+
}
|
|
118
|
+
// MetaMask asks Chrome for a 400x620 popup. Under Xvfb on a GitHub runner the
|
|
119
|
+
// window it gets is 1x1: nothing is laid out, the pane of a confirmation that
|
|
120
|
+
// must be read to the end has no height, so it can never be scrolled there and
|
|
121
|
+
// its Confirm button stays disabled. The popup is given its size back first.
|
|
122
|
+
const POPUP = { width: 400, height: 620 };
|
|
123
|
+
async function withUsableWindow(page) {
|
|
124
|
+
const session = await page.target().createCDPSession();
|
|
125
|
+
try {
|
|
126
|
+
const { windowId, bounds } = await session.send('Browser.getWindowForTarget');
|
|
127
|
+
if (bounds.windowState === 'normal' && (bounds.width ?? 0) >= 200 && (bounds.height ?? 0) >= 200)
|
|
128
|
+
return page;
|
|
129
|
+
console.log(`[dappress] MetaMask's popup is ${bounds.width}x${bounds.height} (${bounds.windowState}); resizing it to ${POPUP.width}x${POPUP.height}`);
|
|
130
|
+
if (bounds.windowState !== 'normal')
|
|
131
|
+
await session.send('Browser.setWindowBounds', { windowId, bounds: { windowState: 'normal' } });
|
|
132
|
+
await session.send('Browser.setWindowBounds', { windowId, bounds: { ...POPUP } });
|
|
133
|
+
// Let the page lay itself out at its new size before anything is looked for in it
|
|
134
|
+
await (0, page_helpers_1.sleep)(300);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.warn(`[dappress] Could not check the popup window's size: ${error.message}`);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
await session.detach().catch(() => { });
|
|
141
|
+
}
|
|
142
|
+
return page;
|
|
143
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Page } from 'puppeteer-core';
|
|
2
|
+
import type { ConnectOptions, ResolvedOptions, TransactionOptions, WalletState } from './types';
|
|
3
|
+
export declare const decisions: {
|
|
4
|
+
connectToDapp: string[];
|
|
5
|
+
rejectConnection: string[];
|
|
6
|
+
approveNewNetwork: string[];
|
|
7
|
+
rejectNewNetwork: string[];
|
|
8
|
+
approveSwitchNetwork: string[];
|
|
9
|
+
rejectSwitchNetwork: string[];
|
|
10
|
+
approveAddToken: string[];
|
|
11
|
+
rejectAddToken: string[];
|
|
12
|
+
confirmSignature: string[];
|
|
13
|
+
rejectSignature: string[];
|
|
14
|
+
confirmTransaction: string[];
|
|
15
|
+
rejectTransaction: string[];
|
|
16
|
+
};
|
|
17
|
+
export type Decision = keyof typeof decisions;
|
|
18
|
+
/** Which screen the extension home page shows, once MetaMask has started. */
|
|
19
|
+
export declare function walletState(page: Page): Promise<WalletState | 'unknown'>;
|
|
20
|
+
/** Import a wallet from its seed phrase on a fresh MetaMask install. */
|
|
21
|
+
export declare function onboard(page: Page, { seedPhrase, password, backupAndSync }: Pick<ResolvedOptions, 'seedPhrase' | 'password' | 'backupAndSync'>): Promise<void>;
|
|
22
|
+
export declare function unlock(page: Page, { password }: Pick<ResolvedOptions, 'password'>): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* A page that was showing the unlock form when the wallet got unlocked from
|
|
25
|
+
* another one keeps showing it, and the dapp's requests wait behind it. The
|
|
26
|
+
* password is given there too, as a user would; what follows is the wallet
|
|
27
|
+
* or a request, so only the form is watched.
|
|
28
|
+
*/
|
|
29
|
+
export declare function leaveUnlockForm(page: Page, { password }: Pick<ResolvedOptions, 'password'>): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Add an account to the wallet and select it. Yields its name, "Account N".
|
|
32
|
+
* A click that lands while the list is still settling is lost, so the button
|
|
33
|
+
* is pressed again when no account shows up.
|
|
34
|
+
*/
|
|
35
|
+
export declare function addAccount(page: Page): Promise<string>;
|
|
36
|
+
/** Make `name` the selected account of the wallet. */
|
|
37
|
+
export declare function switchAccount(page: Page, name: string): Promise<void>;
|
|
38
|
+
/** Import an account from its private key and select it. Yields its name. */
|
|
39
|
+
export declare function importAccount(page: Page, privateKey: string): Promise<string>;
|
|
40
|
+
/** Lock the wallet from the menu of its home page. Nothing to do on a wallet already locked. */
|
|
41
|
+
export declare function lock(page: Page): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Disconnect the site at `origin` from the wallet, on its page of the
|
|
44
|
+
* "Permissions" screen. The dapp gets an empty accountsChanged.
|
|
45
|
+
*/
|
|
46
|
+
export declare function disconnectSite(page: Page, origin: string): Promise<void>;
|
|
47
|
+
/** The options a command took from the test: those of the commands that take any. */
|
|
48
|
+
type CommandOptions = ConnectOptions & TransactionOptions;
|
|
49
|
+
/** Press the button of `decision` on the confirmation shown on `page`, once it is set as `options` ask. */
|
|
50
|
+
export declare function decide(decision: Decision, page: Page, timeout: number, options?: CommandOptions): Promise<void>;
|
|
51
|
+
/** wallet_addEthereumChain on a network MetaMask knows behaves like a switch: either prompt may show. */
|
|
52
|
+
export declare function approveNetworkChange(page: Page, timeout: number): Promise<void>;
|
|
53
|
+
export {};
|