@rushstack/playwright-browser-tunnel 0.2.4 → 0.3.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.
Files changed (80) hide show
  1. package/.rush/temp/chunked-rush-logs/playwright-browser-tunnel._phase_build.chunks.jsonl +2 -2
  2. package/.rush/temp/operation/_phase_build/all.log +2 -2
  3. package/.rush/temp/operation/_phase_build/log-chunks.jsonl +2 -2
  4. package/.rush/temp/operation/_phase_build/state.json +1 -1
  5. package/.rush/temp/rushstack+playwright-browser-tunnel-_phase_build-7739ed2cac57efca3ad3fa4de550f08e1f482854.tar.log +84 -0
  6. package/CHANGELOG.json +26 -0
  7. package/CHANGELOG.md +8 -1
  8. package/config/api-extractor.json +1 -1
  9. package/lib-esm/HttpServer.js +70 -0
  10. package/lib-esm/HttpServer.js.map +1 -0
  11. package/lib-esm/LaunchOptionsValidator.js +156 -0
  12. package/lib-esm/LaunchOptionsValidator.js.map +1 -0
  13. package/lib-esm/PlaywrightBrowserTunnel.js +467 -0
  14. package/lib-esm/PlaywrightBrowserTunnel.js.map +1 -0
  15. package/lib-esm/index.js +22 -0
  16. package/lib-esm/index.js.map +1 -0
  17. package/lib-esm/tunneledBrowserConnection/ITunneledBrowser.js +4 -0
  18. package/lib-esm/tunneledBrowserConnection/ITunneledBrowser.js.map +1 -0
  19. package/lib-esm/tunneledBrowserConnection/ITunneledBrowserConnection.js +4 -0
  20. package/lib-esm/tunneledBrowserConnection/ITunneledBrowserConnection.js.map +1 -0
  21. package/lib-esm/tunneledBrowserConnection/TunneledBrowser.js +36 -0
  22. package/lib-esm/tunneledBrowserConnection/TunneledBrowser.js.map +1 -0
  23. package/lib-esm/tunneledBrowserConnection/TunneledBrowserConnection.js +195 -0
  24. package/lib-esm/tunneledBrowserConnection/TunneledBrowserConnection.js.map +1 -0
  25. package/lib-esm/tunneledBrowserConnection/constants.js +5 -0
  26. package/lib-esm/tunneledBrowserConnection/constants.js.map +1 -0
  27. package/lib-esm/tunneledBrowserConnection/index.js +5 -0
  28. package/lib-esm/tunneledBrowserConnection/index.js.map +1 -0
  29. package/lib-esm/utilities.js +96 -0
  30. package/lib-esm/utilities.js.map +1 -0
  31. package/package.json +29 -6
  32. package/rush-logs/playwright-browser-tunnel._phase_build.cache.log +2 -2
  33. package/rush-logs/playwright-browser-tunnel._phase_build.log +2 -2
  34. package/temp/build/typescript/{ts_l9Fw4VUO.json → ts_lnwgbP5O.json} +1 -1
  35. package/.rush/temp/rushstack+playwright-browser-tunnel-_phase_build-517a2999ebe527b217bbfe2e265943b73bf3856b.tar.log +0 -62
  36. /package/{lib → lib-commonjs}/HttpServer.js +0 -0
  37. /package/{lib → lib-commonjs}/HttpServer.js.map +0 -0
  38. /package/{lib → lib-commonjs}/LaunchOptionsValidator.js +0 -0
  39. /package/{lib → lib-commonjs}/LaunchOptionsValidator.js.map +0 -0
  40. /package/{lib → lib-commonjs}/PlaywrightBrowserTunnel.js +0 -0
  41. /package/{lib → lib-commonjs}/PlaywrightBrowserTunnel.js.map +0 -0
  42. /package/{lib → lib-commonjs}/index.js +0 -0
  43. /package/{lib → lib-commonjs}/index.js.map +0 -0
  44. /package/{lib → lib-commonjs}/tunneledBrowserConnection/ITunneledBrowser.js +0 -0
  45. /package/{lib → lib-commonjs}/tunneledBrowserConnection/ITunneledBrowser.js.map +0 -0
  46. /package/{lib → lib-commonjs}/tunneledBrowserConnection/ITunneledBrowserConnection.js +0 -0
  47. /package/{lib → lib-commonjs}/tunneledBrowserConnection/ITunneledBrowserConnection.js.map +0 -0
  48. /package/{lib → lib-commonjs}/tunneledBrowserConnection/TunneledBrowser.js +0 -0
  49. /package/{lib → lib-commonjs}/tunneledBrowserConnection/TunneledBrowser.js.map +0 -0
  50. /package/{lib → lib-commonjs}/tunneledBrowserConnection/TunneledBrowserConnection.js +0 -0
  51. /package/{lib → lib-commonjs}/tunneledBrowserConnection/TunneledBrowserConnection.js.map +0 -0
  52. /package/{lib → lib-commonjs}/tunneledBrowserConnection/constants.js +0 -0
  53. /package/{lib → lib-commonjs}/tunneledBrowserConnection/constants.js.map +0 -0
  54. /package/{lib → lib-commonjs}/tunneledBrowserConnection/index.js +0 -0
  55. /package/{lib → lib-commonjs}/tunneledBrowserConnection/index.js.map +0 -0
  56. /package/{lib → lib-commonjs}/utilities.js +0 -0
  57. /package/{lib → lib-commonjs}/utilities.js.map +0 -0
  58. /package/{lib → lib-dts}/HttpServer.d.ts +0 -0
  59. /package/{lib → lib-dts}/HttpServer.d.ts.map +0 -0
  60. /package/{lib → lib-dts}/LaunchOptionsValidator.d.ts +0 -0
  61. /package/{lib → lib-dts}/LaunchOptionsValidator.d.ts.map +0 -0
  62. /package/{lib → lib-dts}/PlaywrightBrowserTunnel.d.ts +0 -0
  63. /package/{lib → lib-dts}/PlaywrightBrowserTunnel.d.ts.map +0 -0
  64. /package/{lib → lib-dts}/index.d.ts +0 -0
  65. /package/{lib → lib-dts}/index.d.ts.map +0 -0
  66. /package/{lib → lib-dts}/tsdoc-metadata.json +0 -0
  67. /package/{lib → lib-dts}/tunneledBrowserConnection/ITunneledBrowser.d.ts +0 -0
  68. /package/{lib → lib-dts}/tunneledBrowserConnection/ITunneledBrowser.d.ts.map +0 -0
  69. /package/{lib → lib-dts}/tunneledBrowserConnection/ITunneledBrowserConnection.d.ts +0 -0
  70. /package/{lib → lib-dts}/tunneledBrowserConnection/ITunneledBrowserConnection.d.ts.map +0 -0
  71. /package/{lib → lib-dts}/tunneledBrowserConnection/TunneledBrowser.d.ts +0 -0
  72. /package/{lib → lib-dts}/tunneledBrowserConnection/TunneledBrowser.d.ts.map +0 -0
  73. /package/{lib → lib-dts}/tunneledBrowserConnection/TunneledBrowserConnection.d.ts +0 -0
  74. /package/{lib → lib-dts}/tunneledBrowserConnection/TunneledBrowserConnection.d.ts.map +0 -0
  75. /package/{lib → lib-dts}/tunneledBrowserConnection/constants.d.ts +0 -0
  76. /package/{lib → lib-dts}/tunneledBrowserConnection/constants.d.ts.map +0 -0
  77. /package/{lib → lib-dts}/tunneledBrowserConnection/index.d.ts +0 -0
  78. /package/{lib → lib-dts}/tunneledBrowserConnection/index.d.ts.map +0 -0
  79. /package/{lib → lib-dts}/utilities.d.ts +0 -0
  80. /package/{lib → lib-dts}/utilities.d.ts.map +0 -0
@@ -0,0 +1,36 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import playwright from 'playwright-core';
4
+ import { ConsoleTerminalProvider, Terminal } from '@rushstack/terminal';
5
+ import { DEFAULT_LISTEN_PORT } from './constants';
6
+ import { tunneledBrowserConnection } from './TunneledBrowserConnection';
7
+ /**
8
+ * Creates a Playwright Browser instance connected via a tunneled WebSocket connection.
9
+ * @beta
10
+ */
11
+ export async function createTunneledBrowserAsync(browserName, launchOptions, logger, port = DEFAULT_LISTEN_PORT) {
12
+ // Establish the tunnel first (remoteEndpoint here refers to local proxy endpoint for connect())
13
+ if (!logger) {
14
+ const terminalProvider = new ConsoleTerminalProvider();
15
+ logger = new Terminal(terminalProvider);
16
+ }
17
+ const connection = await tunneledBrowserConnection(logger, port);
18
+ const { remoteEndpoint } = connection;
19
+ // Append query params for browser and launchOptions
20
+ const urlObj = new URL(remoteEndpoint);
21
+ urlObj.searchParams.set('browser', browserName);
22
+ urlObj.searchParams.set('launchOptions', JSON.stringify(launchOptions || {}));
23
+ const connectEndpoint = urlObj.toString();
24
+ const browser = await playwright[browserName].connect(connectEndpoint);
25
+ logger.writeLine(`Connected to remote browser at ${connectEndpoint}`);
26
+ return {
27
+ browser,
28
+ async [Symbol.asyncDispose]() {
29
+ logger.writeLine('Disposing browser');
30
+ await browser.close();
31
+ // Dispose the tunnel connection after browser is closed
32
+ connection[Symbol.dispose]();
33
+ }
34
+ };
35
+ }
36
+ //# sourceMappingURL=TunneledBrowser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TunneledBrowser.js","sourceRoot":"","sources":["../../src/tunneledBrowserConnection/TunneledBrowser.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAG3D,OAAO,UAAU,MAAM,iBAAiB,CAAC;AAGzC,OAAO,EAAE,uBAAuB,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAExE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,WAAwB,EACxB,aAA4B,EAC5B,MAAkB,EAClB,OAAe,mBAAmB;IAElC,gGAAgG;IAEhG,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,gBAAgB,GAA4B,IAAI,uBAAuB,EAAE,CAAC;QAChF,MAAM,GAAG,IAAI,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,UAAU,GAAyC,MAAM,yBAAyB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACvG,MAAM,EAAE,cAAc,EAAE,GAAG,UAAU,CAAC;IACtC,oDAAoD;IACpD,MAAM,MAAM,GAAQ,IAAI,GAAG,CAAC,cAAc,CAAC,CAAC;IAC5C,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAChD,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9E,MAAM,eAAe,GAAW,MAAM,CAAC,QAAQ,EAAE,CAAC;IAClD,MAAM,OAAO,GAAY,MAAM,UAAU,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAChF,MAAM,CAAC,SAAS,CAAC,kCAAkC,eAAe,EAAE,CAAC,CAAC;IAEtE,OAAO;QACL,OAAO;QACP,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC;YACzB,MAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;YACtC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACtB,wDAAwD;YACxD,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/B,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { Browser, LaunchOptions } from 'playwright-core';\nimport playwright from 'playwright-core';\n\nimport type { ITerminal } from '@rushstack/terminal';\nimport { ConsoleTerminalProvider, Terminal } from '@rushstack/terminal';\n\nimport type { BrowserName } from '../PlaywrightBrowserTunnel';\nimport { DEFAULT_LISTEN_PORT } from './constants';\nimport type { IDisposableTunneledBrowser } from './ITunneledBrowser';\nimport type { IDisposableTunneledBrowserConnection } from './ITunneledBrowserConnection';\nimport { tunneledBrowserConnection } from './TunneledBrowserConnection';\n\n/**\n * Creates a Playwright Browser instance connected via a tunneled WebSocket connection.\n * @beta\n */\nexport async function createTunneledBrowserAsync(\n browserName: BrowserName,\n launchOptions: LaunchOptions,\n logger?: ITerminal,\n port: number = DEFAULT_LISTEN_PORT\n): Promise<IDisposableTunneledBrowser> {\n // Establish the tunnel first (remoteEndpoint here refers to local proxy endpoint for connect())\n\n if (!logger) {\n const terminalProvider: ConsoleTerminalProvider = new ConsoleTerminalProvider();\n logger = new Terminal(terminalProvider);\n }\n\n const connection: IDisposableTunneledBrowserConnection = await tunneledBrowserConnection(logger, port);\n const { remoteEndpoint } = connection;\n // Append query params for browser and launchOptions\n const urlObj: URL = new URL(remoteEndpoint);\n urlObj.searchParams.set('browser', browserName);\n urlObj.searchParams.set('launchOptions', JSON.stringify(launchOptions || {}));\n const connectEndpoint: string = urlObj.toString();\n const browser: Browser = await playwright[browserName].connect(connectEndpoint);\n logger.writeLine(`Connected to remote browser at ${connectEndpoint}`);\n\n return {\n browser,\n async [Symbol.asyncDispose]() {\n logger.writeLine('Disposing browser');\n await browser.close();\n // Dispose the tunnel connection after browser is closed\n connection[Symbol.dispose]();\n }\n };\n}\n"]}
@@ -0,0 +1,195 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import playwrightPackageJson from 'playwright-core/package.json';
4
+ import { WebSocket, WebSocketServer } from 'ws';
5
+ import { HttpServer } from '../HttpServer';
6
+ import { getNormalizedErrorString, getWebSocketCloseReason, getWebSocketReadyStateString, WebSocketCloseCode } from '../utilities';
7
+ import { DEFAULT_LISTEN_PORT, SUPPORTED_BROWSER_NAMES } from './constants';
8
+ const { version: playwrightVersion } = playwrightPackageJson;
9
+ /**
10
+ * Creates a tunneled WebSocket endpoint that a local Playwright client can connect to.
11
+ * @beta
12
+ */
13
+ export async function tunneledBrowserConnection(logger, port = DEFAULT_LISTEN_PORT) {
14
+ // Server that remote peer (actual browser host) connects to
15
+ const remoteWsServer = new WebSocketServer({ port });
16
+ // Local HTTP + WebSocket server where the playwright client will connect providing params
17
+ const httpServer = new HttpServer(logger);
18
+ await httpServer.listenAsync();
19
+ logger.writeLine(`Remote WebSocket server listening on ws://localhost:${port}`);
20
+ const localProxyWs = httpServer.wsServer;
21
+ const localProxyWsEndpoint = httpServer.endpoint;
22
+ let browserName;
23
+ let launchOptions;
24
+ let remoteSocket;
25
+ let handshakeAck = false;
26
+ let handshakeSent = false;
27
+ function maybeSendHandshake() {
28
+ if (!handshakeSent && remoteSocket && browserName && launchOptions) {
29
+ const handshake = {
30
+ action: 'handshake',
31
+ browserName,
32
+ launchOptions,
33
+ playwrightVersion
34
+ };
35
+ // Log handshake without 'headless' to avoid confusion (tunnel enforces headless: false)
36
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
37
+ const { headless, ...logOptions } = launchOptions;
38
+ const logHandshake = {
39
+ ...handshake,
40
+ launchOptions: logOptions
41
+ };
42
+ logger.writeLine(`Sending handshake to remote: ${JSON.stringify(logHandshake)}`);
43
+ handshakeSent = true;
44
+ remoteSocket.send(JSON.stringify(handshake));
45
+ }
46
+ }
47
+ return await new Promise((resolve) => {
48
+ remoteWsServer.on('error', (error) => {
49
+ logger.writeErrorLine(`Remote WebSocket server error: ${error}`);
50
+ });
51
+ remoteWsServer.on('close', () => {
52
+ logger.writeLine('Remote WebSocket server closed');
53
+ });
54
+ const bufferedLocalMessages = [];
55
+ remoteWsServer.on('connection', (ws) => {
56
+ logger.writeLine('Remote websocket connected');
57
+ remoteSocket = ws;
58
+ handshakeAck = false;
59
+ maybeSendHandshake();
60
+ ws.on('message', (message) => {
61
+ if (!handshakeAck) {
62
+ try {
63
+ const receivedHandshake = JSON.parse(message.toString());
64
+ if (receivedHandshake.action === 'handshakeAck') {
65
+ handshakeAck = true;
66
+ logger.writeLine('Received handshakeAck from remote');
67
+ }
68
+ else {
69
+ logger.writeErrorLine('Invalid handshake ack message');
70
+ ws.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Invalid handshake ack');
71
+ return;
72
+ }
73
+ }
74
+ catch (e) {
75
+ logger.writeErrorLine(`Failed parsing handshake ack: ${e}`);
76
+ ws.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Failed parsing handshake');
77
+ return;
78
+ }
79
+ // Resolve only once local proxy available and handshake acknowledged
80
+ if (handshakeAck) {
81
+ // Flush any buffered local messages now that tunnel is active
82
+ const activeRemote = remoteSocket;
83
+ if (activeRemote && activeRemote.readyState === WebSocket.OPEN) {
84
+ while (bufferedLocalMessages.length > 0) {
85
+ const m = bufferedLocalMessages.shift();
86
+ if (m !== undefined) {
87
+ logger.writeLine(`Flushing buffered local message to remote: ${m}`);
88
+ activeRemote.send(m);
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ else {
95
+ // Forward from remote to all local clients
96
+ localProxyWs.clients.forEach((client) => {
97
+ if (client.readyState === WebSocket.OPEN) {
98
+ client.send(message);
99
+ }
100
+ });
101
+ }
102
+ });
103
+ ws.on('close', (code, reason) => {
104
+ const reasonStr = reason.toString() || 'no reason provided';
105
+ const codeDescription = getWebSocketCloseReason(code);
106
+ logger.writeDebugLine(`Remote websocket closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`);
107
+ logger.writeDebugLine(` Connection state at close: handshakeSent=${handshakeSent}, handshakeAck=${handshakeAck}`);
108
+ logger.writeDebugLine(` Buffered messages pending: ${bufferedLocalMessages.length}`);
109
+ });
110
+ ws.on('error', (err) => {
111
+ logger.writeErrorLine(`Remote websocket error: ${getNormalizedErrorString(err)}`);
112
+ logger.writeErrorLine(` Socket readyState: ${getWebSocketReadyStateString(ws.readyState)}`);
113
+ });
114
+ });
115
+ localProxyWs.on('connection', (localWs, request) => {
116
+ try {
117
+ const urlString = request === null || request === void 0 ? void 0 : request.url;
118
+ if (urlString) {
119
+ const parsed = new URL(urlString, 'http://localhost');
120
+ logger.writeLine(`Local client connected with query params: ${parsed.searchParams.toString()}`);
121
+ const bName = parsed.searchParams.get('browser');
122
+ if (bName && SUPPORTED_BROWSER_NAMES.has(bName)) {
123
+ browserName = bName;
124
+ }
125
+ const launchOptionsParam = parsed.searchParams.get('launchOptions');
126
+ if (launchOptionsParam) {
127
+ try {
128
+ launchOptions = JSON.parse(launchOptionsParam);
129
+ }
130
+ catch (e) {
131
+ logger.writeErrorLine('Invalid launchOptions JSON provided');
132
+ }
133
+ }
134
+ }
135
+ }
136
+ catch (e) {
137
+ logger.writeErrorLine(`Error parsing local connection query params: ${e}`);
138
+ }
139
+ if (!browserName) {
140
+ const supportedBrowsersString = Array.from(SUPPORTED_BROWSER_NAMES).join('|');
141
+ logger.writeErrorLine(`browser query param required (${supportedBrowsersString})`);
142
+ localWs.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Missing browser param');
143
+ return;
144
+ }
145
+ if (!launchOptions) {
146
+ launchOptions = {}; // default empty if not provided
147
+ }
148
+ maybeSendHandshake();
149
+ localWs.on('message', (message) => {
150
+ if (handshakeAck && (remoteSocket === null || remoteSocket === void 0 ? void 0 : remoteSocket.readyState) === WebSocket.OPEN) {
151
+ remoteSocket.send(message);
152
+ }
153
+ else {
154
+ // Buffer until handshakeAck to avoid losing early protocol messages from Playwright
155
+ bufferedLocalMessages.push(message);
156
+ }
157
+ });
158
+ localWs.on('close', (code, reason) => {
159
+ const reasonStr = reason.toString() || 'no reason provided';
160
+ const codeDescription = getWebSocketCloseReason(code);
161
+ logger.writeDebugLine(`Local client websocket closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`);
162
+ logger.writeDebugLine(` Remote socket state: ${remoteSocket ? getWebSocketReadyStateString(remoteSocket.readyState) : 'undefined'}`);
163
+ logger.writeDebugLine(` handshakeAck: ${handshakeAck}`);
164
+ });
165
+ localWs.on('error', (err) => {
166
+ logger.writeErrorLine(`Local client websocket error: ${getNormalizedErrorString(err)}`);
167
+ });
168
+ });
169
+ // Resolve immediately so caller can initiate local connection with query params (handshake completes later)
170
+ resolve({
171
+ remoteEndpoint: localProxyWsEndpoint,
172
+ [Symbol.dispose]() {
173
+ try {
174
+ remoteWsServer.close();
175
+ }
176
+ catch (_a) {
177
+ // ignore errors during remote WebSocket server shutdown
178
+ }
179
+ try {
180
+ httpServer[Symbol.dispose]();
181
+ }
182
+ catch (_b) {
183
+ // ignore errors during HTTP/WebSocket server shutdown
184
+ }
185
+ },
186
+ // eslint-disable-next-line promise/param-names
187
+ closePromise: new Promise((resolve2) => {
188
+ remoteWsServer.once('close', () => {
189
+ resolve2();
190
+ });
191
+ })
192
+ });
193
+ });
194
+ }
195
+ //# sourceMappingURL=TunneledBrowserConnection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TunneledBrowserConnection.js","sourceRoot":"","sources":["../../src/tunneledBrowserConnection/TunneledBrowserConnection.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { LaunchOptions } from 'playwright-core';\nimport playwrightPackageJson from 'playwright-core/package.json';\nimport type { RawData } from 'ws';\nimport { WebSocket, WebSocketServer } from 'ws';\n\nimport type { ITerminal } from '@rushstack/terminal';\n\nimport { HttpServer } from '../HttpServer';\nimport type { BrowserName } from '../PlaywrightBrowserTunnel';\nimport {\n getNormalizedErrorString,\n getWebSocketCloseReason,\n getWebSocketReadyStateString,\n WebSocketCloseCode\n} from '../utilities';\nimport type {\n IDisposableTunneledBrowserConnection,\n IHandshake,\n IHandshakeAck\n} from './ITunneledBrowserConnection';\nimport { DEFAULT_LISTEN_PORT, SUPPORTED_BROWSER_NAMES } from './constants';\n\nconst { version: playwrightVersion } = playwrightPackageJson;\n\n/**\n * Creates a tunneled WebSocket endpoint that a local Playwright client can connect to.\n * @beta\n */\nexport async function tunneledBrowserConnection(\n logger: ITerminal,\n port: number = DEFAULT_LISTEN_PORT\n): Promise<IDisposableTunneledBrowserConnection> {\n // Server that remote peer (actual browser host) connects to\n const remoteWsServer: WebSocketServer = new WebSocketServer({ port });\n // Local HTTP + WebSocket server where the playwright client will connect providing params\n const httpServer: HttpServer = new HttpServer(logger);\n await httpServer.listenAsync();\n logger.writeLine(`Remote WebSocket server listening on ws://localhost:${port}`);\n\n const localProxyWs: WebSocketServer = httpServer.wsServer;\n const localProxyWsEndpoint: string = httpServer.endpoint;\n\n let browserName: BrowserName | undefined;\n let launchOptions: LaunchOptions | undefined;\n let remoteSocket: WebSocket | undefined;\n let handshakeAck: boolean = false;\n let handshakeSent: boolean = false;\n\n function maybeSendHandshake(): void {\n if (!handshakeSent && remoteSocket && browserName && launchOptions) {\n const handshake: IHandshake = {\n action: 'handshake',\n browserName,\n launchOptions,\n playwrightVersion\n };\n // Log handshake without 'headless' to avoid confusion (tunnel enforces headless: false)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { headless, ...logOptions } = launchOptions;\n const logHandshake: Omit<IHandshake, 'launchOptions'> & {\n launchOptions: Omit<LaunchOptions, 'headless'>;\n } = {\n ...handshake,\n launchOptions: logOptions\n };\n logger.writeLine(`Sending handshake to remote: ${JSON.stringify(logHandshake)}`);\n handshakeSent = true;\n remoteSocket.send(JSON.stringify(handshake));\n }\n }\n\n return await new Promise((resolve) => {\n remoteWsServer.on('error', (error) => {\n logger.writeErrorLine(`Remote WebSocket server error: ${error}`);\n });\n\n remoteWsServer.on('close', () => {\n logger.writeLine('Remote WebSocket server closed');\n });\n\n const bufferedLocalMessages: Array<RawData> = [];\n\n remoteWsServer.on('connection', (ws) => {\n logger.writeLine('Remote websocket connected');\n remoteSocket = ws;\n handshakeAck = false;\n maybeSendHandshake();\n\n ws.on('message', (message) => {\n if (!handshakeAck) {\n try {\n const receivedHandshake: IHandshakeAck = JSON.parse(message.toString());\n if (receivedHandshake.action === 'handshakeAck') {\n handshakeAck = true;\n logger.writeLine('Received handshakeAck from remote');\n } else {\n logger.writeErrorLine('Invalid handshake ack message');\n ws.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Invalid handshake ack');\n return;\n }\n } catch (e) {\n logger.writeErrorLine(`Failed parsing handshake ack: ${e}`);\n ws.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Failed parsing handshake');\n return;\n }\n // Resolve only once local proxy available and handshake acknowledged\n if (handshakeAck) {\n // Flush any buffered local messages now that tunnel is active\n const activeRemote: WebSocket | undefined = remoteSocket;\n if (activeRemote && activeRemote.readyState === WebSocket.OPEN) {\n while (bufferedLocalMessages.length > 0) {\n const m: Buffer | ArrayBuffer | Buffer[] | string | undefined = bufferedLocalMessages.shift();\n if (m !== undefined) {\n logger.writeLine(`Flushing buffered local message to remote: ${m}`);\n activeRemote.send(m);\n }\n }\n }\n }\n } else {\n // Forward from remote to all local clients\n localProxyWs.clients.forEach((client) => {\n if (client.readyState === WebSocket.OPEN) {\n client.send(message);\n }\n });\n }\n });\n\n ws.on('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n logger.writeDebugLine(\n `Remote websocket closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n logger.writeDebugLine(\n ` Connection state at close: handshakeSent=${handshakeSent}, handshakeAck=${handshakeAck}`\n );\n logger.writeDebugLine(` Buffered messages pending: ${bufferedLocalMessages.length}`);\n });\n ws.on('error', (err: Error) => {\n logger.writeErrorLine(`Remote websocket error: ${getNormalizedErrorString(err)}`);\n logger.writeErrorLine(` Socket readyState: ${getWebSocketReadyStateString(ws.readyState)}`);\n });\n });\n\n localProxyWs.on('connection', (localWs, request) => {\n try {\n const urlString: string | undefined = request?.url;\n if (urlString) {\n const parsed: URL = new URL(urlString, 'http://localhost');\n logger.writeLine(`Local client connected with query params: ${parsed.searchParams.toString()}`);\n const bName: string | null = parsed.searchParams.get('browser');\n if (bName && SUPPORTED_BROWSER_NAMES.has(bName)) {\n browserName = bName as BrowserName;\n }\n const launchOptionsParam: string | null = parsed.searchParams.get('launchOptions');\n if (launchOptionsParam) {\n try {\n launchOptions = JSON.parse(launchOptionsParam);\n } catch (e) {\n logger.writeErrorLine('Invalid launchOptions JSON provided');\n }\n }\n }\n } catch (e) {\n logger.writeErrorLine(`Error parsing local connection query params: ${e}`);\n }\n\n if (!browserName) {\n const supportedBrowsersString: string = Array.from(SUPPORTED_BROWSER_NAMES).join('|');\n logger.writeErrorLine(`browser query param required (${supportedBrowsersString})`);\n localWs.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Missing browser param');\n return;\n }\n if (!launchOptions) {\n launchOptions = {} as LaunchOptions; // default empty if not provided\n }\n\n maybeSendHandshake();\n\n localWs.on('message', (message) => {\n if (handshakeAck && remoteSocket?.readyState === WebSocket.OPEN) {\n remoteSocket.send(message);\n } else {\n // Buffer until handshakeAck to avoid losing early protocol messages from Playwright\n bufferedLocalMessages.push(message);\n }\n });\n localWs.on('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n logger.writeDebugLine(\n `Local client websocket closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n logger.writeDebugLine(\n ` Remote socket state: ${remoteSocket ? getWebSocketReadyStateString(remoteSocket.readyState) : 'undefined'}`\n );\n logger.writeDebugLine(` handshakeAck: ${handshakeAck}`);\n });\n localWs.on('error', (err: Error) => {\n logger.writeErrorLine(`Local client websocket error: ${getNormalizedErrorString(err)}`);\n });\n });\n\n // Resolve immediately so caller can initiate local connection with query params (handshake completes later)\n resolve({\n remoteEndpoint: localProxyWsEndpoint,\n [Symbol.dispose]() {\n try {\n remoteWsServer.close();\n } catch {\n // ignore errors during remote WebSocket server shutdown\n }\n try {\n httpServer[Symbol.dispose]();\n } catch {\n // ignore errors during HTTP/WebSocket server shutdown\n }\n },\n // eslint-disable-next-line promise/param-names\n closePromise: new Promise<void>((resolve2) => {\n remoteWsServer.once('close', () => {\n resolve2();\n });\n })\n });\n });\n}\n"]}
@@ -0,0 +1,5 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ export const SUPPORTED_BROWSER_NAMES = new Set(['chromium', 'firefox', 'webkit']);
4
+ export const DEFAULT_LISTEN_PORT = 56767;
5
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/tunneledBrowserConnection/constants.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,MAAM,CAAC,MAAM,uBAAuB,GAAgB,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC/F,MAAM,CAAC,MAAM,mBAAmB,GAAW,KAAK,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nexport const SUPPORTED_BROWSER_NAMES: Set<string> = new Set(['chromium', 'firefox', 'webkit']);\nexport const DEFAULT_LISTEN_PORT: number = 56767;\n"]}
@@ -0,0 +1,5 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ export { createTunneledBrowserAsync } from './TunneledBrowser';
4
+ export { tunneledBrowserConnection } from './TunneledBrowserConnection';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tunneledBrowserConnection/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nexport { createTunneledBrowserAsync } from './TunneledBrowser';\nexport { tunneledBrowserConnection } from './TunneledBrowserConnection';\n\nexport type { IDisposableTunneledBrowser } from './ITunneledBrowser';\nexport type { IDisposableTunneledBrowserConnection } from './ITunneledBrowserConnection';\n"]}
@@ -0,0 +1,96 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import { tmpdir } from 'node:os';
4
+ import { FileSystem } from '@rushstack/node-core-library';
5
+ /**
6
+ * The filename used to indicate that the Playwright Local Browser Server extension is installed.
7
+ * @beta
8
+ */
9
+ export const EXTENSION_INSTALLED_FILENAME = '.playwright-local-browser-server-extension-installed.txt';
10
+ /**
11
+ * Helper to determine if the Playwright Local Browser Server extension is installed. This checks for the
12
+ * existence of a well-known file in the OS temp directory.
13
+ * @beta
14
+ */
15
+ export async function isExtensionInstalledAsync() {
16
+ // Read file from os.tempdir() + '/.playwright-local-browser-server-extension-installed'
17
+ const tempDir = tmpdir();
18
+ const extensionInstalledFilePath = `${tempDir}/${EXTENSION_INSTALLED_FILENAME}`;
19
+ const doesExist = FileSystem.exists(extensionInstalledFilePath);
20
+ // check if file exists
21
+ return doesExist;
22
+ }
23
+ /**
24
+ * Normalizes an error to a string for logging purposes.
25
+ * @beta
26
+ */
27
+ export function getNormalizedErrorString(error) {
28
+ if (error instanceof Error) {
29
+ if (error.stack) {
30
+ return error.stack;
31
+ }
32
+ return error.message;
33
+ }
34
+ return String(error);
35
+ }
36
+ /**
37
+ * WebSocket close codes as defined by RFC 6455.
38
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc6455#section-11.7}
39
+ * @beta
40
+ */
41
+ export const WebSocketCloseCode = {
42
+ NORMAL_CLOSURE: 1000,
43
+ GOING_AWAY: 1001,
44
+ PROTOCOL_ERROR: 1002,
45
+ UNSUPPORTED_DATA: 1003,
46
+ NO_STATUS_RECEIVED: 1005,
47
+ ABNORMAL_CLOSURE: 1006,
48
+ INVALID_PAYLOAD: 1007,
49
+ POLICY_VIOLATION: 1008,
50
+ MESSAGE_TOO_BIG: 1009,
51
+ INTERNAL_ERROR: 1011,
52
+ TLS_HANDSHAKE_FAILED: 1015
53
+ };
54
+ /**
55
+ * Human-readable descriptions for WebSocket close codes.
56
+ * @beta
57
+ */
58
+ export const WebSocketCloseCodeDescriptions = {
59
+ [WebSocketCloseCode.NORMAL_CLOSURE]: 'Normal Closure',
60
+ [WebSocketCloseCode.GOING_AWAY]: 'Going Away',
61
+ [WebSocketCloseCode.PROTOCOL_ERROR]: 'Protocol Error',
62
+ [WebSocketCloseCode.UNSUPPORTED_DATA]: 'Unsupported Data',
63
+ [WebSocketCloseCode.NO_STATUS_RECEIVED]: 'No Status Received',
64
+ [WebSocketCloseCode.ABNORMAL_CLOSURE]: 'Abnormal Closure (connection lost)',
65
+ [WebSocketCloseCode.INVALID_PAYLOAD]: 'Invalid Payload',
66
+ [WebSocketCloseCode.POLICY_VIOLATION]: 'Policy Violation',
67
+ [WebSocketCloseCode.MESSAGE_TOO_BIG]: 'Message Too Big',
68
+ [WebSocketCloseCode.INTERNAL_ERROR]: 'Internal Error',
69
+ [WebSocketCloseCode.TLS_HANDSHAKE_FAILED]: 'TLS Handshake Failed'
70
+ };
71
+ /**
72
+ * Returns a human-readable description for a WebSocket close code.
73
+ * @beta
74
+ */
75
+ export function getWebSocketCloseReason(code) {
76
+ return WebSocketCloseCodeDescriptions[code] || 'Unknown';
77
+ }
78
+ /**
79
+ * Returns a human-readable string for a WebSocket ready state.
80
+ * @beta
81
+ */
82
+ export function getWebSocketReadyStateString(readyState) {
83
+ switch (readyState) {
84
+ case 0:
85
+ return 'CONNECTING';
86
+ case 1:
87
+ return 'OPEN';
88
+ case 2:
89
+ return 'CLOSING';
90
+ case 3:
91
+ return 'CLOSED';
92
+ default:
93
+ return `UNKNOWN(${readyState})`;
94
+ }
95
+ }
96
+ //# sourceMappingURL=utilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utilities.js","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE1D;;;GAGG;AACH,MAAM,CAAC,MAAM,4BAA4B,GACvC,0DAA0D,CAAC;AAE7D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB;IAC7C,wFAAwF;IACxF,MAAM,OAAO,GAAW,MAAM,EAAE,CAAC;IAEjC,MAAM,0BAA0B,GAAW,GAAG,OAAO,IAAI,4BAA4B,EAAE,CAAC;IACxF,MAAM,SAAS,GAAY,UAAU,CAAC,MAAM,CAAC,0BAA0B,CAAC,CAAC;IAEzE,uBAAuB;IACvB,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAChB,OAAO,KAAK,CAAC,KAAK,CAAC;QACrB,CAAC;QACD,OAAO,KAAK,CAAC,OAAO,CAAC;IACvB,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAuB3B;IACF,cAAc,EAAE,IAAI;IACpB,UAAU,EAAE,IAAI;IAChB,cAAc,EAAE,IAAI;IACpB,gBAAgB,EAAE,IAAI;IACtB,kBAAkB,EAAE,IAAI;IACxB,gBAAgB,EAAE,IAAI;IACtB,eAAe,EAAE,IAAI;IACrB,gBAAgB,EAAE,IAAI;IACtB,eAAe,EAAE,IAAI;IACrB,cAAc,EAAE,IAAI;IACpB,oBAAoB,EAAE,IAAI;CAC3B,CAAC;AAQF;;;GAGG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAA4C;IACrF,CAAC,kBAAkB,CAAC,cAAc,CAAC,EAAE,gBAAgB;IACrD,CAAC,kBAAkB,CAAC,UAAU,CAAC,EAAE,YAAY;IAC7C,CAAC,kBAAkB,CAAC,cAAc,CAAC,EAAE,gBAAgB;IACrD,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,EAAE,kBAAkB;IACzD,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,EAAE,oBAAoB;IAC7D,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,EAAE,oCAAoC;IAC3E,CAAC,kBAAkB,CAAC,eAAe,CAAC,EAAE,iBAAiB;IACvD,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,EAAE,kBAAkB;IACzD,CAAC,kBAAkB,CAAC,eAAe,CAAC,EAAE,iBAAiB;IACvD,CAAC,kBAAkB,CAAC,cAAc,CAAC,EAAE,gBAAgB;IACrD,CAAC,kBAAkB,CAAC,oBAAoB,CAAC,EAAE,sBAAsB;CAClE,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,OAAO,8BAA8B,CAAC,IAA+B,CAAC,IAAI,SAAS,CAAC;AACtF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAAC,UAAkB;IAC7D,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,CAAC;YACJ,OAAO,YAAY,CAAC;QACtB,KAAK,CAAC;YACJ,OAAO,MAAM,CAAC;QAChB,KAAK,CAAC;YACJ,OAAO,SAAS,CAAC;QACnB,KAAK,CAAC;YACJ,OAAO,QAAQ,CAAC;QAClB;YACE,OAAO,WAAW,UAAU,GAAG,CAAC;IACpC,CAAC;AACH,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { tmpdir } from 'node:os';\n\nimport { FileSystem } from '@rushstack/node-core-library';\n\n/**\n * The filename used to indicate that the Playwright Local Browser Server extension is installed.\n * @beta\n */\nexport const EXTENSION_INSTALLED_FILENAME: string =\n '.playwright-local-browser-server-extension-installed.txt';\n\n/**\n * Helper to determine if the Playwright Local Browser Server extension is installed. This checks for the\n * existence of a well-known file in the OS temp directory.\n * @beta\n */\nexport async function isExtensionInstalledAsync(): Promise<boolean> {\n // Read file from os.tempdir() + '/.playwright-local-browser-server-extension-installed'\n const tempDir: string = tmpdir();\n\n const extensionInstalledFilePath: string = `${tempDir}/${EXTENSION_INSTALLED_FILENAME}`;\n const doesExist: boolean = FileSystem.exists(extensionInstalledFilePath);\n\n // check if file exists\n return doesExist;\n}\n\n/**\n * Normalizes an error to a string for logging purposes.\n * @beta\n */\nexport function getNormalizedErrorString(error: unknown): string {\n if (error instanceof Error) {\n if (error.stack) {\n return error.stack;\n }\n return error.message;\n }\n return String(error);\n}\n\n/**\n * WebSocket close codes as defined by RFC 6455.\n * @see {@link https://datatracker.ietf.org/doc/html/rfc6455#section-11.7}\n * @beta\n */\nexport const WebSocketCloseCode: {\n /** Normal closure; the connection successfully completed. */\n readonly NORMAL_CLOSURE: 1000;\n /** Endpoint is going away (e.g., server shutting down, browser navigating away). */\n readonly GOING_AWAY: 1001;\n /** Protocol error encountered. */\n readonly PROTOCOL_ERROR: 1002;\n /** Received data type that cannot be accepted (e.g., text-only endpoint received binary). */\n readonly UNSUPPORTED_DATA: 1003;\n /** No status code was provided even though one was expected. */\n readonly NO_STATUS_RECEIVED: 1005;\n /** Connection was closed abnormally (e.g., without sending a close frame). */\n readonly ABNORMAL_CLOSURE: 1006;\n /** Received message data inconsistent with the message type. */\n readonly INVALID_PAYLOAD: 1007;\n /** Received a message that violates policy. */\n readonly POLICY_VIOLATION: 1008;\n /** Received a message that is too big to process. */\n readonly MESSAGE_TOO_BIG: 1009;\n /** Server encountered an unexpected condition that prevented it from fulfilling the request. */\n readonly INTERNAL_ERROR: 1011;\n /** Connection was closed due to TLS handshake failure. */\n readonly TLS_HANDSHAKE_FAILED: 1015;\n} = {\n NORMAL_CLOSURE: 1000,\n GOING_AWAY: 1001,\n PROTOCOL_ERROR: 1002,\n UNSUPPORTED_DATA: 1003,\n NO_STATUS_RECEIVED: 1005,\n ABNORMAL_CLOSURE: 1006,\n INVALID_PAYLOAD: 1007,\n POLICY_VIOLATION: 1008,\n MESSAGE_TOO_BIG: 1009,\n INTERNAL_ERROR: 1011,\n TLS_HANDSHAKE_FAILED: 1015\n};\n\n/**\n * Type for WebSocket close code values.\n * @beta\n */\nexport type WebSocketCloseCodeValue = (typeof WebSocketCloseCode)[keyof typeof WebSocketCloseCode];\n\n/**\n * Human-readable descriptions for WebSocket close codes.\n * @beta\n */\nexport const WebSocketCloseCodeDescriptions: Record<WebSocketCloseCodeValue, string> = {\n [WebSocketCloseCode.NORMAL_CLOSURE]: 'Normal Closure',\n [WebSocketCloseCode.GOING_AWAY]: 'Going Away',\n [WebSocketCloseCode.PROTOCOL_ERROR]: 'Protocol Error',\n [WebSocketCloseCode.UNSUPPORTED_DATA]: 'Unsupported Data',\n [WebSocketCloseCode.NO_STATUS_RECEIVED]: 'No Status Received',\n [WebSocketCloseCode.ABNORMAL_CLOSURE]: 'Abnormal Closure (connection lost)',\n [WebSocketCloseCode.INVALID_PAYLOAD]: 'Invalid Payload',\n [WebSocketCloseCode.POLICY_VIOLATION]: 'Policy Violation',\n [WebSocketCloseCode.MESSAGE_TOO_BIG]: 'Message Too Big',\n [WebSocketCloseCode.INTERNAL_ERROR]: 'Internal Error',\n [WebSocketCloseCode.TLS_HANDSHAKE_FAILED]: 'TLS Handshake Failed'\n};\n\n/**\n * Returns a human-readable description for a WebSocket close code.\n * @beta\n */\nexport function getWebSocketCloseReason(code: number): string {\n return WebSocketCloseCodeDescriptions[code as WebSocketCloseCodeValue] || 'Unknown';\n}\n\n/**\n * Returns a human-readable string for a WebSocket ready state.\n * @beta\n */\nexport function getWebSocketReadyStateString(readyState: number): string {\n switch (readyState) {\n case 0:\n return 'CONNECTING';\n case 1:\n return 'OPEN';\n case 2:\n return 'CLOSING';\n case 3:\n return 'CLOSED';\n default:\n return `UNKNOWN(${readyState})`;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rushstack/playwright-browser-tunnel",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Run a remote Playwright Browser Tunnel. Useful in remote development environments.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -8,7 +8,29 @@
8
8
  "url": "https://github.com/microsoft/rushstack.git",
9
9
  "directory": "apps/playwright-browser-tunnel"
10
10
  },
11
- "main": "lib/index.js",
11
+ "main": "./lib-commonjs/index.js",
12
+ "module": "./lib-esm/index.js",
13
+ "types": "./lib-dts/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./lib-dts/index.d.ts",
17
+ "import": "./lib-esm/index.js",
18
+ "require": "./lib-commonjs/index.js"
19
+ },
20
+ "./lib/*": {
21
+ "types": "./lib-dts/*.d.ts",
22
+ "import": "./lib-esm/*.js",
23
+ "require": "./lib-commonjs/*.js"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "typesVersions": {
28
+ "*": {
29
+ "lib/*": [
30
+ "lib-dts/*"
31
+ ]
32
+ }
33
+ },
12
34
  "engines": {
13
35
  "node": ">=20.0.0"
14
36
  },
@@ -19,9 +41,9 @@
19
41
  "semver": "~7.5.4",
20
42
  "ws": "~8.14.1",
21
43
  "playwright": "1.56.1",
22
- "@rushstack/terminal": "0.21.0",
23
- "@rushstack/ts-command-line": "5.2.0",
24
- "@rushstack/node-core-library": "5.19.1"
44
+ "@rushstack/terminal": "0.22.0",
45
+ "@rushstack/node-core-library": "5.20.0",
46
+ "@rushstack/ts-command-line": "5.3.0"
25
47
  },
26
48
  "devDependencies": {
27
49
  "eslint": "~9.37.0",
@@ -30,12 +52,13 @@
30
52
  "playwright-core": "~1.56.1",
31
53
  "@playwright/test": "~1.56.1",
32
54
  "@types/node": "20.17.19",
33
- "@rushstack/heft": "1.1.14",
55
+ "@rushstack/heft": "1.2.0",
34
56
  "local-node-rig": "1.0.0"
35
57
  },
36
58
  "peerDependencies": {
37
59
  "playwright-core": "~1.56.1"
38
60
  },
61
+ "sideEffects": false,
39
62
  "scripts": {
40
63
  "build": "heft build --clean",
41
64
  "_phase:build": "heft run --only build -- --clean",
@@ -1,3 +1,3 @@
1
- Caching build output folders: dist, lib, temp, .rush/temp/operation/_phase_build
1
+ Caching build output folders: dist, lib-commonjs, temp, lib-dts, lib-esm, .rush/temp/operation/_phase_build
2
2
  Successfully set cache entry.
3
- Cache key: rushstack+playwright-browser-tunnel-_phase_build-517a2999ebe527b217bbfe2e265943b73bf3856b
3
+ Cache key: rushstack+playwright-browser-tunnel-_phase_build-7739ed2cac57efca3ad3fa4de550f08e1f482854
@@ -4,5 +4,5 @@ Invoking: heft run --only build -- --clean --production
4
4
  [build:lint] Using ESLint version 9.37.0
5
5
  [build:api-extractor] Using API Extractor version 7.56.3
6
6
  [build:api-extractor] Analysis will use the bundled TypeScript version 5.8.2
7
- ---- build finished (22.551s) ----
8
- -------------------- Finished (22.555s) --------------------
7
+ ---- build finished (22.938s) ----
8
+ -------------------- Finished (22.943s) --------------------