cdp-tunnel 1.0.13 → 1.0.14

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.
Files changed (68) hide show
  1. package/extension-new/background.js +6 -1
  2. package/extension-new/cdp/handler/special.js +47 -27
  3. package/extension-new/core/state.js +64 -6
  4. package/extension-new/core/websocket.js +124 -19
  5. package/package.json +9 -2
  6. package/server/proxy-server.js +45 -34
  7. package/.github/workflows/publish.yml +0 -92
  8. package/.github/workflows/release-assets.yml +0 -50
  9. package/PUBLISH.md +0 -65
  10. package/console-test.js +0 -52
  11. package/docs/README_CN.md +0 -204
  12. package/docs/config-page-screenshot.png +0 -0
  13. package/final-console-test.js +0 -105
  14. package/simple-tab-group-test.js +0 -56
  15. package/test-cdp-connection.js +0 -85
  16. package/test-cdp-groups.js +0 -71
  17. package/test-check-newtab.js +0 -144
  18. package/test-chrome-native.js +0 -140
  19. package/test-client-connected.js +0 -99
  20. package/test-compare-formats.js +0 -88
  21. package/test-context-features.js +0 -113
  22. package/test-create-tab.js +0 -113
  23. package/test-debug-broadcast.js +0 -52
  24. package/test-debug-targets.js +0 -127
  25. package/test-expose-newtab.js +0 -164
  26. package/test-expose-shared.js +0 -189
  27. package/test-final-logs.js +0 -110
  28. package/test-fresh-chromium.js +0 -153
  29. package/test-init-script.js +0 -128
  30. package/test-keepalive.js +0 -89
  31. package/test-launch-chromium.js +0 -140
  32. package/test-launch-vs-connect.js +0 -149
  33. package/test-listen-events.js +0 -102
  34. package/test-monitor.js +0 -83
  35. package/test-multiple-cdp-groups.js +0 -78
  36. package/test-native.js +0 -96
  37. package/test-page-connection.js +0 -74
  38. package/test-playwright-connection.js +0 -45
  39. package/test-playwright-groups.js +0 -47
  40. package/test-playwright-pages.js +0 -47
  41. package/test-playwright-sequence.js +0 -81
  42. package/test-proper-context.js +0 -129
  43. package/test-real-final.js +0 -251
  44. package/test-real-scenario-v2.js +0 -166
  45. package/test-real-scenario-v3.js +0 -231
  46. package/test-real-scenario.js +0 -104
  47. package/test-server-logs.js +0 -98
  48. package/test-session-id.js +0 -91
  49. package/test-simple-cdp-groups.js +0 -44
  50. package/test-simple-context.js +0 -137
  51. package/test-tab-group-simple.js +0 -58
  52. package/test-tab-grouping.js +0 -48
  53. package/test-three-pages.js +0 -192
  54. package/test-wait-for-page.js +0 -95
  55. package/test-with-logs.js +0 -118
  56. package/test-ws-groups.js +0 -59
  57. package/tests/e2e-auto-test.js +0 -304
  58. package/tests/iframe-test-page.html +0 -89
  59. package/tests/playwright-demo.js +0 -45
  60. package/tests/playwright-interactive.js +0 -261
  61. package/tests/playwright-multi-demo.js +0 -60
  62. package/tests/playwright-multi.js +0 -85
  63. package/tests/playwright-single.js +0 -41
  64. package/tests/screenshot-config.js +0 -35
  65. package/tests/test-client.js +0 -89
  66. package/tests/test-douyin-iframe.js +0 -171
  67. package/tests/test-iframe-debug.js +0 -204
  68. package/tests/test-multi-client.js +0 -129
@@ -1,129 +0,0 @@
1
- const { chromium } = require('playwright');
2
-
3
- async function testContextFeaturesProper(port, label) {
4
- console.log(`\n${'='.repeat(60)}`);
5
- console.log(`Testing ${label} (port ${port})`);
6
- console.log('='.repeat(60));
7
-
8
- try {
9
- const browser = await chromium.connectOverCDP(`http://localhost:${port}`);
10
- console.log(`[${label}] Connected successfully!`);
11
-
12
- console.log(`[${label}] Creating new context...`);
13
- const context = await browser.newContext();
14
-
15
- console.log(`[${label}] Testing exposeFunction...`);
16
- await context.exposeFunction('myCustomFunction', (arg) => {
17
- console.log(`[${label}] myCustomFunction called with:`, arg);
18
- return `Hello from ${label}: ${arg}`;
19
- });
20
-
21
- console.log(`[${label}] Testing addInitScript...`);
22
- await context.addInitScript(() => {
23
- window.myInitScript = 'This is from addInitScript!';
24
- });
25
-
26
- console.log(`[${label}] Creating first page...`);
27
- const page1 = await context.newPage();
28
- await page1.goto('about:blank');
29
-
30
- console.log(`[${label}] Testing in first page...`);
31
- const result1 = await page1.evaluate(async () => {
32
- try {
33
- const funcResult = await window.myCustomFunction('page1');
34
- const initResult = window.myInitScript;
35
- return { success: true, funcResult, initResult };
36
- } catch (e) {
37
- return { success: false, error: e.message };
38
- }
39
- });
40
- console.log(`[${label}] First page result:`, result1);
41
-
42
- if (!result1.success) {
43
- console.log(`[${label}] ✗ First page test failed!`);
44
- await browser.close();
45
- return false;
46
- }
47
-
48
- console.log(`[${label}] Creating second page (simulating new tab)...`);
49
- const page2 = await context.newPage();
50
- await page2.goto('about:blank');
51
-
52
- console.log(`[${label}] Testing in second page (should persist)...`);
53
- const result2 = await page2.evaluate(async () => {
54
- try {
55
- const funcResult = await window.myCustomFunction('page2');
56
- const initResult = window.myInitScript;
57
- return { success: true, funcResult, initResult };
58
- } catch (e) {
59
- return { success: false, error: e.message };
60
- }
61
- });
62
- console.log(`[${label}] Second page result:`, result2);
63
-
64
- if (!result2.success) {
65
- console.log(`[${label}] ✗ Second page test failed - Context features not persisted!`);
66
- await browser.close();
67
- return false;
68
- }
69
-
70
- console.log(`[${label}] Creating third page...`);
71
- const page3 = await context.newPage();
72
- await page3.goto('about:blank');
73
-
74
- console.log(`[${label}] Testing in third page...`);
75
- const result3 = await page3.evaluate(async () => {
76
- try {
77
- const funcResult = await window.myCustomFunction('page3');
78
- const initResult = window.myInitScript;
79
- return { success: true, funcResult, initResult };
80
- } catch (e) {
81
- return { success: false, error: e.message };
82
- }
83
- });
84
- console.log(`[${label}] Third page result:`, result3);
85
-
86
- if (!result3.success) {
87
- console.log(`[${label}] ✗ Third page test failed!`);
88
- await browser.close();
89
- return false;
90
- }
91
-
92
- console.log(`[${label}] ✓ All tests passed!`);
93
-
94
- await browser.close();
95
-
96
- return true;
97
- } catch (error) {
98
- console.error(`[${label}] ✗ Error:`, error.message);
99
- console.error(`[${label}] Stack:`, error.stack);
100
- return false;
101
- }
102
- }
103
-
104
- async function main() {
105
- console.log('Testing Context-level features persistence across tabs');
106
- console.log('This tests if exposeFunction and addInitScript persist across different pages\n');
107
-
108
- const nativeResult = await testContextFeaturesProper(9333, 'Native CDP');
109
- const tunnelResult = await testContextFeaturesProper(9221, 'CDP Tunnel');
110
-
111
- console.log('\n' + '='.repeat(60));
112
- console.log('COMPARISON RESULTS');
113
- console.log('='.repeat(60));
114
- console.log(`Native CDP (port 9333): ${nativeResult ? '✓ PASS' : '✗ FAIL'}`);
115
- console.log(`CDP Tunnel (port 9221): ${tunnelResult ? '✓ PASS' : '✗ FAIL'}`);
116
-
117
- if (nativeResult && tunnelResult) {
118
- console.log('\n✓ Both implementations behave identically!');
119
- console.log('✓ Context-level features (exposeFunction/addInitScript) work correctly!');
120
- } else if (nativeResult && !tunnelResult) {
121
- console.log('\n✗ CDP Tunnel has issues with Context-level features!');
122
- } else if (!nativeResult && tunnelResult) {
123
- console.log('\n? CDP Tunnel works but Native CDP has issues (unexpected)!');
124
- } else {
125
- console.log('\n✗ Both implementations have issues!');
126
- }
127
- }
128
-
129
- main();
@@ -1,251 +0,0 @@
1
- const { chromium } = require('playwright');
2
- const { spawn } = require('child_process');
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- async function testRealScenario(port, label) {
7
- console.log(`\n${'='.repeat(60)}`);
8
- console.log(`Testing ${label} (port ${port})`);
9
- console.log('='.repeat(60));
10
-
11
- try {
12
- const htmlContent1 = `
13
- <!DOCTYPE html>
14
- <html>
15
- <head><title>Page 1</title></head>
16
- <body>
17
- <h1>Page 1 - Source</h1>
18
- <p>This is the source page.</p>
19
- <a href="page2.html" target="_blank">Open Result 1</a>
20
- <a href="page3.html" target="_blank">Open Result 2</a>
21
- </body>
22
- </html>
23
- `;
24
-
25
- const htmlContent2 = `
26
- <!DOCTYPE html>
27
- <html>
28
- <head><title>Page 2</title></head>
29
- <body>
30
- <h1>Page 2 - Result 1</h1>
31
- <p>This is result page 1.</p>
32
- <a href="page3.html" target="_blank">Open Result 2</a>
33
- </body>
34
- </html>
35
- `;
36
-
37
- const htmlContent3 = `
38
- <!DOCTYPE html>
39
- <html>
40
- <head><title>Page 3</title></head>
41
- <body>
42
- <h1>Page 3 - Result 2</h1>
43
- <p>This is result page 2.</p>
44
- </body>
45
- </html>
46
- `;
47
-
48
- const serverDir = '/tmp/test-pages-v2';
49
- if (!fs.existsSync(serverDir)) {
50
- fs.mkdirSync(serverDir, { recursive: true });
51
- }
52
- fs.writeFileSync(path.join(serverDir, 'page1.html'), htmlContent1);
53
- fs.writeFileSync(path.join(serverDir, 'page2.html'), htmlContent2);
54
- fs.writeFileSync(path.join(serverDir, 'page3.html'), htmlContent3);
55
-
56
- const http = require('http');
57
- const server = http.createServer((req, res) => {
58
- let filePath = path.join(serverDir, req.url === '/' ? 'page1.html' : req.url);
59
- if (fs.existsSync(filePath)) {
60
- const content = fs.readFileSync(filePath);
61
- res.writeHead(200, { 'Content-Type': 'text/html' });
62
- res.end(content);
63
- } else {
64
- res.writeHead(404);
65
- res.end('Not found');
66
- }
67
- });
68
-
69
- await new Promise(resolve => server.listen(8766, resolve));
70
- console.log(`[${label}] Test server started on port 8766`);
71
-
72
- const browser = await chromium.connectOverCDP(`http://localhost:${port}`);
73
- console.log(`[${label}] Connected successfully!`);
74
-
75
- console.log(`[${label}] Creating new context...`);
76
- const context = await browser.newContext();
77
-
78
- console.log(`[${label}] Testing addInitScript - set unique value per page...`);
79
- await context.addInitScript(() => {
80
- window.pageLoadedAt = Date.now();
81
- window.testValue = 'init-script-executed';
82
- console.log('[InitScript] Executed at:', window.pageLoadedAt);
83
- });
84
-
85
- console.log(`[${label}] Creating first page...`);
86
- const page1 = await context.newPage();
87
- await page1.goto('http://localhost:8766/page1.html', { waitUntil: 'domcontentloaded' });
88
- await page1.waitForTimeout(500);
89
-
90
- console.log(`[${label}] Testing in first page...`);
91
- const result1 = await page1.evaluate(() => {
92
- return {
93
- url: window.location.href,
94
- testValue: window.testValue,
95
- pageLoadedAt: window.pageLoadedAt
96
- };
97
- });
98
- console.log(`[${label}] First page:`, {
99
- url: result1.url,
100
- testValue: result1.testValue,
101
- pageLoadedAt: new Date(result1.pageLoadedAt).toLocaleTimeString()
102
- });
103
-
104
- console.log(`[${label}] Clicking first link (opens new tab)...`);
105
- try {
106
- const [page2] = await Promise.all([
107
- context.waitForEvent('page', { timeout: 10000 }),
108
- page1.click('a:first-child')
109
- ]);
110
-
111
- console.log(`[${label}] Waiting for new tab to load...`);
112
- await page2.waitForLoadState('domcontentloaded');
113
- await page2.waitForTimeout(500);
114
- console.log(`[${label}] New tab URL: ${page2.url()}`);
115
-
116
- console.log(`[${label}] Testing in second page (should have init script)...`);
117
- const result2 = await page2.evaluate(() => {
118
- return {
119
- url: window.location.href,
120
- testValue: window.testValue,
121
- pageLoadedAt: window.pageLoadedAt
122
- };
123
- });
124
- console.log(`[${label}] Second page:`, {
125
- url: result2.url,
126
- testValue: result2.testValue,
127
- pageLoadedAt: new Date(result2.pageLoadedAt).toLocaleTimeString()
128
- });
129
-
130
- console.log(`[${label}] Clicking another link...`);
131
- try {
132
- const [page3] = await Promise.all([
133
- context.waitForEvent('page', { timeout: 10000 }),
134
- page2.click('a:first-child')
135
- ]);
136
-
137
- console.log(`[${label}] Waiting for third tab...`);
138
- await page3.waitForLoadState('domcontentloaded');
139
- await page3.waitForTimeout(500);
140
-
141
- console.log(`[${label}] Testing in third page...`);
142
- const result3 = await page3.evaluate(() => {
143
- return {
144
- url: window.location.href,
145
- testValue: window.testValue,
146
- pageLoadedAt: window.pageLoadedAt
147
- };
148
- });
149
- console.log(`[${label}] Third page:`, {
150
- url: result3.url,
151
- testValue: result3.testValue,
152
- pageLoadedAt: new Date(result3.pageLoadedAt).toLocaleTimeString()
153
- });
154
- } catch (e) {
155
- console.log(`[${label}] No more links to click`);
156
- }
157
- } catch (e) {
158
- console.log(`[${label}] Error waiting for new tab: ${e.message}`);
159
- }
160
-
161
- console.log(`[${label}] Switching back to first page...`);
162
- await page1.bringToFront();
163
- await page1.waitForTimeout(500);
164
-
165
- console.log(`[${label}] Testing in first page again...`);
166
- const result1Again = await page1.evaluate(() => {
167
- return {
168
- url: window.location.href,
169
- testValue: window.testValue,
170
- pageLoadedAt: window.pageLoadedAt
171
- };
172
- });
173
- console.log(`[${label}] First page (after switch):`, {
174
- url: result1Again.url,
175
- testValue: result1Again.testValue,
176
- pageLoadedAt: new Date(result1Again.pageLoadedAt).toLocaleTimeString()
177
- });
178
-
179
- const success = result1.testValue === 'init-script-executed' &&
180
- result2?.testValue === 'init-script-executed' &&
181
- result1.pageLoadedAt !== result2?.pageLoadedAt;
182
-
183
- server.close();
184
-
185
- if (success) {
186
- console.log(`[${label}] ✓ All tests passed!`);
187
- console.log(`[${label}] ✓ addInitScript executed in ALL new pages!`);
188
- } else {
189
- console.log(`[${label}] ✗ Some tests failed!`);
190
- }
191
-
192
- await browser.close();
193
-
194
- return success;
195
- } catch (error) {
196
- console.error(`[${label}] ✗ Error:`, error.message);
197
- console.error(`[${label}] Stack:`, error.stack);
198
- return false;
199
- }
200
- }
201
-
202
- async function main() {
203
- console.log('Testing: Page1 -> Click link (new tab) -> Click link (another new tab)');
204
- console.log('Expected: addInitScript executes in EVERY new page (different timestamps)\n');
205
-
206
- console.log('Step 1: Starting fresh Chromium instance on port 9228...');
207
- const chromiumProcess = spawn('/Applications/Chromium.app/Contents/MacOS/Chromium', [
208
- '--remote-debugging-port=9228',
209
- '--user-data-dir=/tmp/chromium-test-final',
210
- '--no-first-run',
211
- '--no-default-browser-check'
212
- ], {
213
- detached: true,
214
- stdio: 'ignore'
215
- });
216
-
217
- console.log('Waiting for Chromium to start...');
218
- await new Promise(resolve => setTimeout(resolve, 3000));
219
-
220
- console.log('\nStep 2: Testing Native CDP...');
221
- const nativeResult = await testRealScenario(9228, 'Native CDP');
222
-
223
- console.log('\nStep 3: Testing CDP Tunnel...');
224
- const tunnelResult = await testRealScenario(9221, 'CDP Tunnel');
225
-
226
- console.log('\nStep 4: Cleaning up...');
227
- try {
228
- process.kill(-chromiumProcess.pid);
229
- } catch (e) {}
230
-
231
- console.log('\n' + '='.repeat(60));
232
- console.log('COMPARISON RESULTS');
233
- console.log('='.repeat(60));
234
- console.log(`Native CDP (port 9228): ${nativeResult ? '✓ PASS' : '✗ FAIL'}`);
235
- console.log(`CDP Tunnel (port 9221): ${tunnelResult ? '✓ PASS' : '✗ FAIL'}`);
236
-
237
- if (nativeResult && tunnelResult) {
238
- console.log('\n✓ Both implementations behave identically!');
239
- console.log('✓ addInitScript executes in EVERY new page!');
240
- } else if (nativeResult && !tunnelResult) {
241
- console.log('\n✗ CDP Tunnel has issues with addInitScript!');
242
- } else if (!nativeResult && tunnelResult) {
243
- console.log('\n? CDP Tunnel works but Native CDP has issues (unexpected)!');
244
- } else {
245
- console.log('\n✗ Both implementations have issues!');
246
- }
247
-
248
- process.exit(0);
249
- }
250
-
251
- main();
@@ -1,166 +0,0 @@
1
- const { chromium } = require('playwright');
2
- const { spawn } = require('child_process');
3
-
4
- async function testRealScenario(port, label) {
5
- console.log(`\n${'='.repeat(60)}`);
6
- console.log(`Testing ${label} (port ${port})`);
7
- console.log('='.repeat(60));
8
-
9
- try {
10
- const browser = await chromium.connectOverCDP(`http://localhost:${port}`);
11
- console.log(`[${label}] Connected successfully!`);
12
-
13
- console.log(`[${label}] Creating new context...`);
14
- const context = await browser.newContext();
15
-
16
- console.log(`[${label}] Testing addInitScript with counter...`);
17
- await context.addInitScript(() => {
18
- window.myInitScript = 'This is from addInitScript!';
19
- window.myCounter = (window.myCounter || 0) + 1;
20
- console.log('[InitScript] myCounter:', window.myCounter);
21
- });
22
-
23
- console.log(`[${label}] Creating first page and navigating to Baidu...`);
24
- const page1 = await context.newPage();
25
- await page1.goto('https://www.baidu.com', { waitUntil: 'domcontentloaded', timeout: 30000 });
26
-
27
- console.log(`[${label}] Testing in Baidu page (counter should be 1)...`);
28
- const result1 = await page1.evaluate(() => {
29
- return {
30
- initScript: window.myInitScript,
31
- counter: window.myCounter
32
- };
33
- });
34
- console.log(`[${label}] Baidu page result:`, result1);
35
-
36
- console.log(`[${label}] Searching for "test"...`);
37
- await page1.fill('#kw', 'test');
38
- await page1.click('#su');
39
- await page1.waitForSelector('#content_left', { timeout: 10000 });
40
-
41
- console.log(`[${label}] Clicking first search result (opens new tab)...`);
42
- const [page2] = await Promise.all([
43
- context.waitForEvent('page'),
44
- page1.click('#content_left a')
45
- ]);
46
-
47
- console.log(`[${label}] Waiting for new tab to load...`);
48
- await page2.waitForLoadState('domcontentloaded', { timeout: 15000 });
49
- console.log(`[${label}] New tab URL: ${page2.url()}`);
50
-
51
- console.log(`[${label}] Testing in new tab (counter should be 2 - incremented)...`);
52
- const result2 = await page2.evaluate(() => {
53
- return {
54
- initScript: window.myInitScript,
55
- counter: window.myCounter
56
- };
57
- });
58
- console.log(`[${label}] New tab result:`, result2);
59
-
60
- console.log(`[${label}] Clicking another link in new tab...`);
61
- try {
62
- const links = await page2.locator('a').first();
63
- if (links) {
64
- const [page3] = await Promise.all([
65
- context.waitForEvent('page'),
66
- links.click()
67
- ]);
68
-
69
- console.log(`[${label}] Waiting for second new tab...`);
70
- await page3.waitForLoadState('domcontentloaded', { timeout: 15000 });
71
- console.log(`[${label}] Second new tab URL: ${page3.url()}`);
72
-
73
- console.log(`[${label}] Testing in second new tab (counter should be 3)...`);
74
- const result3 = await page3.evaluate(() => {
75
- return {
76
- initScript: window.myInitScript,
77
- counter: window.myCounter
78
- };
79
- });
80
- console.log(`[${label}] Second new tab result:`, result3);
81
- }
82
- } catch (e) {
83
- console.log(`[${label}] No more links to click, skipping second new tab test`);
84
- }
85
-
86
- console.log(`[${label}] Switching back to first page (Baidu)...`);
87
- await page1.bringToFront();
88
- await page1.waitForLoadState('domcontentloaded');
89
-
90
- console.log(`[${label}] Testing in Baidu page again (counter should still be 1)...`);
91
- const result1Again = await page1.evaluate(() => {
92
- return {
93
- initScript: window.myInitScript,
94
- counter: window.myCounter
95
- };
96
- });
97
- console.log(`[${label}] Baidu page (after switch) result:`, result1Again);
98
-
99
- const success = result1.counter === 1 && result2.counter === 2;
100
-
101
- if (success) {
102
- console.log(`[${label}] ✓ All tests passed!`);
103
- } else {
104
- console.log(`[${label}] ✗ Counter not incrementing correctly!`);
105
- }
106
-
107
- await browser.close();
108
-
109
- return success;
110
- } catch (error) {
111
- console.error(`[${label}] ✗ Error:`, error.message);
112
- console.error(`[${label}] Stack:`, error.stack);
113
- return false;
114
- }
115
- }
116
-
117
- async function main() {
118
- console.log('Testing real scenario: Baidu -> Search -> Click result -> New tab');
119
- console.log('Expected: Counter increments with each new page\n');
120
-
121
- console.log('Step 1: Starting fresh Chromium instance on port 9226...');
122
- const chromiumProcess = spawn('/Applications/Chromium.app/Contents/MacOS/Chromium', [
123
- '--remote-debugging-port=9226',
124
- '--user-data-dir=/tmp/chromium-test-scenario',
125
- '--no-first-run',
126
- '--no-default-browser-check'
127
- ], {
128
- detached: true,
129
- stdio: 'ignore'
130
- });
131
-
132
- console.log('Waiting for Chromium to start...');
133
- await new Promise(resolve => setTimeout(resolve, 3000));
134
-
135
- console.log('\nStep 2: Testing Native CDP...');
136
- const nativeResult = await testRealScenario(9226, 'Native CDP');
137
-
138
- console.log('\nStep 3: Testing CDP Tunnel...');
139
- const tunnelResult = await testRealScenario(9221, 'CDP Tunnel');
140
-
141
- console.log('\nStep 4: Cleaning up...');
142
- try {
143
- process.kill(-chromiumProcess.pid);
144
- } catch (e) {}
145
-
146
- console.log('\n' + '='.repeat(60));
147
- console.log('COMPARISON RESULTS');
148
- console.log('='.repeat(60));
149
- console.log(`Native CDP (port 9226): ${nativeResult ? '✓ PASS' : '✗ FAIL'}`);
150
- console.log(`CDP Tunnel (port 9221): ${tunnelResult ? '✓ PASS' : '✗ FAIL'}`);
151
-
152
- if (nativeResult && tunnelResult) {
153
- console.log('\n✓ Both implementations behave identically!');
154
- console.log('✓ addInitScript works correctly and counter increments across tabs!');
155
- } else if (nativeResult && !tunnelResult) {
156
- console.log('\n✗ CDP Tunnel has issues with addInitScript counter!');
157
- } else if (!nativeResult && tunnelResult) {
158
- console.log('\n? CDP Tunnel works but Native CDP has issues (unexpected)!');
159
- } else {
160
- console.log('\n✗ Both implementations have issues!');
161
- }
162
-
163
- process.exit(0);
164
- }
165
-
166
- main();