|
8 | 8 | * is ready and to avoid race conditions during test execution. |
9 | 9 | */ |
10 | 10 |
|
11 | | -import path from 'path'; |
12 | | -import { fileURLToPath } from 'url'; |
13 | | -import extract from 'extract-zip'; |
14 | | -import fs from 'fs-extra'; |
15 | | -import fetch from 'node-fetch'; |
| 11 | +import path from "path" |
| 12 | +import { fileURLToPath } from "url" |
| 13 | +import extract from "extract-zip" |
| 14 | +import fs from "fs-extra" |
| 15 | +import fetch from "node-fetch" |
16 | 16 |
|
17 | 17 | // Support for ES modules |
18 | | -const __filename = fileURLToPath(import.meta.url); |
19 | | -const __dirname = path.dirname(__filename); |
| 18 | +const __filename = fileURLToPath(import.meta.url) |
| 19 | +const __dirname = path.dirname(__filename) |
20 | 20 |
|
21 | 21 | // Constants for Coinbase Wallet |
22 | | -const COINBASE_VERSION = '3.117.1'; |
23 | | -const EXTENSION_ID = 'hnfanknocfeofbddgcijnmhnfnkdnaad'; |
24 | | -const DOWNLOAD_URL = `https://update.googleapis.com/service/update2/crx?response=redirect&os=win&arch=x64&os_arch=x86_64&nacl_arch=x86-64&prod=chromiumcrx&prodchannel=unknown&prodversion=120.0.0.0&acceptformat=crx3&x=id%3D${EXTENSION_ID}%26uc`; |
25 | | -const EXTRACTION_COMPLETE_FLAG = '.extraction_complete'; |
| 22 | +const COINBASE_VERSION = "3.117.1" |
| 23 | +const EXTENSION_ID = "hnfanknocfeofbddgcijnmhnfnkdnaad" |
| 24 | +const DOWNLOAD_URL = `https://update.googleapis.com/service/update2/crx?response=redirect&os=win&arch=x64&os_arch=x86_64&nacl_arch=x86-64&prod=chromiumcrx&prodchannel=unknown&prodversion=120.0.0.0&acceptformat=crx3&x=id%3D${EXTENSION_ID}%26uc` |
| 25 | +const EXTRACTION_COMPLETE_FLAG = ".extraction_complete" |
26 | 26 |
|
27 | 27 | /** |
28 | 28 | * Set up the cache directory structure |
29 | 29 | */ |
30 | 30 | async function setupCacheDir(cacheDirName) { |
31 | | - const projectRoot = process.cwd(); |
32 | | - const cacheDirPath = path.join(projectRoot, 'e2e', '.cache', cacheDirName); |
| 31 | + const projectRoot = process.cwd() |
| 32 | + const cacheDirPath = path.join(projectRoot, "e2e", ".cache", cacheDirName) |
33 | 33 |
|
34 | 34 | // Ensure the cache directory exists |
35 | | - await fs.ensureDir(cacheDirPath); |
| 35 | + await fs.ensureDir(cacheDirPath) |
36 | 36 |
|
37 | | - return cacheDirPath; |
| 37 | + return cacheDirPath |
38 | 38 | } |
39 | 39 |
|
40 | 40 | /** |
41 | 41 | * Download with retry logic for CI environments |
42 | 42 | */ |
43 | 43 | async function downloadWithRetry(url, options, maxRetries = 3) { |
44 | | - let lastError; |
| 44 | + let lastError |
45 | 45 |
|
46 | 46 | for (let attempt = 1; attempt <= maxRetries; attempt++) { |
47 | 47 | try { |
48 | | - console.log(`Download attempt ${attempt} of ${maxRetries}...`); |
49 | | - const response = await fetch(url, options); |
| 48 | + console.log(`Download attempt ${attempt} of ${maxRetries}...`) |
| 49 | + const response = await fetch(url, options) |
50 | 50 |
|
51 | 51 | if (!response.ok) { |
52 | | - throw new Error(`HTTP ${response.status}: ${response.statusText}`); |
| 52 | + throw new Error(`HTTP ${response.status}: ${response.statusText}`) |
53 | 53 | } |
54 | 54 |
|
55 | | - return response; |
| 55 | + return response |
56 | 56 | } catch (error) { |
57 | | - lastError = error; |
58 | | - console.error(`Attempt ${attempt} failed: ${error.message}`); |
| 57 | + lastError = error |
| 58 | + console.error(`Attempt ${attempt} failed: ${error.message}`) |
59 | 59 |
|
60 | 60 | if (attempt < maxRetries) { |
61 | | - const waitTime = Math.min(1000 * 2 ** (attempt - 1), 10000); |
62 | | - console.log(`Waiting ${waitTime}ms before retry...`); |
63 | | - await new Promise((resolve) => setTimeout(resolve, waitTime)); |
| 61 | + const waitTime = Math.min(1000 * 2 ** (attempt - 1), 10000) |
| 62 | + console.log(`Waiting ${waitTime}ms before retry...`) |
| 63 | + await new Promise(resolve => setTimeout(resolve, waitTime)) |
64 | 64 | } |
65 | 65 | } |
66 | 66 | } |
67 | 67 |
|
68 | | - throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`); |
| 68 | + throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`) |
69 | 69 | } |
70 | 70 |
|
71 | 71 | /** |
72 | 72 | * Prepares the Coinbase Wallet extension by downloading and extracting it |
73 | 73 | */ |
74 | 74 | async function setupCoinbaseExtraction() { |
75 | | - console.log(`Preparing Coinbase Wallet extension v${COINBASE_VERSION}...`); |
| 75 | + console.log(`Preparing Coinbase Wallet extension v${COINBASE_VERSION}...`) |
76 | 76 |
|
77 | 77 | // Set up the cache directory |
78 | | - const cacheDir = await setupCacheDir('coinbase-extension'); |
79 | | - const extractionPath = path.join(cacheDir, `coinbase-${COINBASE_VERSION}`); |
80 | | - const flagPath = path.join(extractionPath, EXTRACTION_COMPLETE_FLAG); |
| 78 | + const cacheDir = await setupCacheDir("coinbase-extension") |
| 79 | + const extractionPath = path.join(cacheDir, `coinbase-${COINBASE_VERSION}`) |
| 80 | + const flagPath = path.join(extractionPath, EXTRACTION_COMPLETE_FLAG) |
81 | 81 |
|
82 | 82 | // Download the CRX file |
83 | | - const crxPath = path.join(cacheDir, `coinbase-${COINBASE_VERSION}.crx`); |
84 | | - console.log(`Downloading Coinbase Wallet extension to: ${crxPath}`); |
| 83 | + const crxPath = path.join(cacheDir, `coinbase-${COINBASE_VERSION}.crx`) |
| 84 | + console.log(`Downloading Coinbase Wallet extension to: ${crxPath}`) |
85 | 85 |
|
86 | 86 | // Check if file already exists |
87 | | - let cached = false; |
| 87 | + let cached = false |
88 | 88 | if (await fs.pathExists(crxPath)) { |
89 | | - const stats = await fs.stat(crxPath); |
| 89 | + const stats = await fs.stat(crxPath) |
90 | 90 | if (stats.size > 0) { |
91 | | - console.log(`Using cached download at ${crxPath}`); |
92 | | - cached = true; |
| 91 | + console.log(`Using cached download at ${crxPath}`) |
| 92 | + cached = true |
93 | 93 | } |
94 | 94 | } |
95 | 95 |
|
96 | 96 | // Download if not cached |
97 | 97 | if (!cached) { |
98 | | - console.log('Downloading from Chrome Web Store...'); |
| 98 | + console.log("Downloading from Chrome Web Store...") |
99 | 99 | try { |
100 | 100 | // Attempt download with retry logic |
101 | 101 | const response = await downloadWithRetry( |
102 | 102 | DOWNLOAD_URL, |
103 | 103 | { |
104 | | - redirect: 'follow', |
| 104 | + redirect: "follow", |
105 | 105 | follow: 20, |
106 | 106 | }, |
107 | 107 | 3, |
108 | | - ); |
| 108 | + ) |
109 | 109 |
|
110 | | - const contentType = response.headers.get('content-type'); |
111 | | - console.log(`Response content-type: ${contentType}`); |
| 110 | + const contentType = response.headers.get("content-type") |
| 111 | + console.log(`Response content-type: ${contentType}`) |
112 | 112 |
|
113 | | - const buffer = await response.arrayBuffer(); |
| 113 | + const buffer = await response.arrayBuffer() |
114 | 114 |
|
115 | | - await fs.writeFile(crxPath, Buffer.from(buffer)); |
116 | | - console.log('Download complete'); |
| 115 | + await fs.writeFile(crxPath, Buffer.from(buffer)) |
| 116 | + console.log("Download complete") |
117 | 117 |
|
118 | 118 | // Verify the download |
119 | | - const downloadedStats = await fs.stat(crxPath); |
| 119 | + const downloadedStats = await fs.stat(crxPath) |
120 | 120 | if (downloadedStats.size === 0) { |
121 | | - await fs.remove(crxPath); |
| 121 | + await fs.remove(crxPath) |
122 | 122 | throw new Error( |
123 | | - 'Downloaded file is empty. The Chrome Web Store might be blocking automated downloads.', |
124 | | - ); |
| 123 | + "Downloaded file is empty. The Chrome Web Store might be blocking automated downloads.", |
| 124 | + ) |
125 | 125 | } |
126 | 126 | } catch (error) { |
127 | | - throw new Error(`Extension download failed: ${error.message}`); |
| 127 | + throw new Error(`Extension download failed: ${error.message}`) |
128 | 128 | } |
129 | 129 | } |
130 | 130 |
|
131 | 131 | // Clean any existing extraction directory |
132 | 132 | if (await fs.pathExists(extractionPath)) { |
133 | | - console.log(`Cleaning existing directory: ${extractionPath}`); |
134 | | - await fs.emptyDir(extractionPath); |
| 133 | + console.log(`Cleaning existing directory: ${extractionPath}`) |
| 134 | + await fs.emptyDir(extractionPath) |
135 | 135 | } |
136 | 136 |
|
137 | 137 | // Extract the CRX file |
138 | | - console.log(`Extracting to: ${extractionPath}`); |
| 138 | + console.log(`Extracting to: ${extractionPath}`) |
139 | 139 | try { |
140 | 140 | // Read the CRX file |
141 | | - const crxBuffer = await fs.readFile(crxPath); |
142 | | - console.log(`CRX file size: ${crxBuffer.length} bytes`); |
| 141 | + const crxBuffer = await fs.readFile(crxPath) |
| 142 | + console.log(`CRX file size: ${crxBuffer.length} bytes`) |
143 | 143 |
|
144 | 144 | // CRX3 files start with "Cr24" magic number |
145 | | - const crx3Magic = Buffer.from('Cr24'); |
| 145 | + const crx3Magic = Buffer.from("Cr24") |
146 | 146 |
|
147 | | - let zipStart = -1; |
| 147 | + let zipStart = -1 |
148 | 148 |
|
149 | 149 | if (crxBuffer.subarray(0, 4).equals(crx3Magic)) { |
150 | | - console.log('Detected CRX3 format'); |
| 150 | + console.log("Detected CRX3 format") |
151 | 151 | // CRX3 format: |
152 | 152 | // 4 bytes: "Cr24" magic |
153 | 153 | // 4 bytes: version (3) |
154 | 154 | // 4 bytes: header length |
155 | | - const version = crxBuffer.readUInt32LE(4); |
156 | | - const headerLength = crxBuffer.readUInt32LE(8); |
157 | | - console.log(`CRX version: ${version}, header length: ${headerLength}`); |
| 155 | + const version = crxBuffer.readUInt32LE(4) |
| 156 | + const headerLength = crxBuffer.readUInt32LE(8) |
| 157 | + console.log(`CRX version: ${version}, header length: ${headerLength}`) |
158 | 158 |
|
159 | 159 | // ZIP content starts after the header |
160 | | - zipStart = 12 + headerLength; |
| 160 | + zipStart = 12 + headerLength |
161 | 161 | } else { |
162 | | - throw new Error("Invalid CRX file format - expected CRX3 with 'Cr24' header"); |
| 162 | + throw new Error( |
| 163 | + "Invalid CRX file format - expected CRX3 with 'Cr24' header", |
| 164 | + ) |
163 | 165 | } |
164 | 166 |
|
165 | 167 | // Extract the ZIP portion |
166 | | - const zipBuffer = crxBuffer.subarray(zipStart); |
167 | | - console.log(`Extracting ZIP content from offset ${zipStart}, size: ${zipBuffer.length} bytes`); |
| 168 | + const zipBuffer = crxBuffer.subarray(zipStart) |
| 169 | + console.log( |
| 170 | + `Extracting ZIP content from offset ${zipStart}, size: ${zipBuffer.length} bytes`, |
| 171 | + ) |
168 | 172 |
|
169 | | - const zipPath = path.join(cacheDir, `coinbase-${COINBASE_VERSION}.zip`); |
170 | | - await fs.writeFile(zipPath, zipBuffer); |
| 173 | + const zipPath = path.join(cacheDir, `coinbase-${COINBASE_VERSION}.zip`) |
| 174 | + await fs.writeFile(zipPath, zipBuffer) |
171 | 175 |
|
172 | 176 | // Now extract the ZIP file |
173 | | - await extract(zipPath, { dir: extractionPath }); |
| 177 | + await extract(zipPath, { dir: extractionPath }) |
174 | 178 |
|
175 | 179 | // Clean up the temporary ZIP file |
176 | | - await fs.remove(zipPath); |
| 180 | + await fs.remove(zipPath) |
177 | 181 |
|
178 | 182 | // Verify extraction succeeded |
179 | | - const manifestPath = path.join(extractionPath, 'manifest.json'); |
| 183 | + const manifestPath = path.join(extractionPath, "manifest.json") |
180 | 184 | if (!(await fs.pathExists(manifestPath))) { |
181 | | - throw new Error(`Extraction failed: manifest.json not found at ${manifestPath}`); |
| 185 | + throw new Error( |
| 186 | + `Extraction failed: manifest.json not found at ${manifestPath}`, |
| 187 | + ) |
182 | 188 | } |
183 | 189 |
|
184 | 190 | // Create flag file to indicate successful extraction |
185 | | - await fs.writeFile(flagPath, new Date().toISOString()); |
| 191 | + await fs.writeFile(flagPath, new Date().toISOString()) |
186 | 192 |
|
187 | | - console.log(`Coinbase Wallet extension successfully prepared at: ${extractionPath}`); |
188 | | - return extractionPath; |
| 193 | + console.log( |
| 194 | + `Coinbase Wallet extension successfully prepared at: ${extractionPath}`, |
| 195 | + ) |
| 196 | + return extractionPath |
189 | 197 | } catch (error) { |
190 | | - console.error(`Error extracting CRX: ${error.message}`); |
| 198 | + console.error(`Error extracting CRX: ${error.message}`) |
191 | 199 | // Clean up on failure |
192 | 200 | try { |
193 | | - await fs.emptyDir(extractionPath); |
| 201 | + await fs.emptyDir(extractionPath) |
194 | 202 | } catch (cleanupError) { |
195 | | - console.error(`Failed to clean up: ${cleanupError.message}`); |
| 203 | + console.error(`Failed to clean up: ${cleanupError.message}`) |
196 | 204 | } |
197 | 205 |
|
198 | | - throw new Error(`Extraction failed: ${error.message}`); |
| 206 | + throw new Error(`Extraction failed: ${error.message}`) |
199 | 207 | } |
200 | 208 | } |
201 | 209 |
|
202 | 210 | async function main() { |
203 | 211 | try { |
204 | | - console.log('Preparing Coinbase Wallet extension for tests...'); |
| 212 | + console.log("Preparing Coinbase Wallet extension for tests...") |
205 | 213 |
|
206 | 214 | // Run the setup function |
207 | | - const extensionPath = await setupCoinbaseExtraction(); |
208 | | - console.log(`Coinbase Wallet extension prepared successfully at: ${extensionPath}`); |
209 | | - console.log('You can now run the tests!'); |
| 215 | + const extensionPath = await setupCoinbaseExtraction() |
| 216 | + console.log( |
| 217 | + `Coinbase Wallet extension prepared successfully at: ${extensionPath}`, |
| 218 | + ) |
| 219 | + console.log("You can now run the tests!") |
210 | 220 |
|
211 | | - process.exit(0); |
| 221 | + process.exit(0) |
212 | 222 | } catch (error) { |
213 | | - console.error('Failed to prepare Coinbase Wallet extension:'); |
214 | | - console.error(error); |
215 | | - process.exit(1); |
| 223 | + console.error("Failed to prepare Coinbase Wallet extension:") |
| 224 | + console.error(error) |
| 225 | + process.exit(1) |
216 | 226 | } |
217 | 227 | } |
218 | 228 |
|
219 | 229 | // Run the main function with proper promise handling |
220 | | -main().catch((error) => { |
221 | | - console.error('Unhandled error in main function:', error); |
222 | | - process.exit(1); |
223 | | -}); |
| 230 | +main().catch(error => { |
| 231 | + console.error("Unhandled error in main function:", error) |
| 232 | + process.exit(1) |
| 233 | +}) |
0 commit comments