Yury Semikhatsky commited on
Commit
9b5f97b
·
unverified ·
1 Parent(s): 04988d8

chore(extension): use react for connect dialog (#777)

Browse files
extension/connect.html CHANGED
@@ -17,18 +17,11 @@
17
  <html>
18
  <head>
19
  <title>Playwright MCP extension</title>
 
 
20
  </head>
21
  <body>
22
- <h3>Playwright MCP extension</h3>
23
- <div id="status-container"></div>
24
- <div class="button-row">
25
- <button id="continue-btn">Continue</button>
26
- <button id="reject-btn">Reject</button>
27
- </div>
28
- <div id="tab-list-container">
29
- <h4>Select page to expose to MCP server:</h4>
30
- <div id="tab-list"></div>
31
- </div>
32
- <script src="lib/connect.js"></script>
33
  </body>
34
  </html>
 
17
  <html>
18
  <head>
19
  <title>Playwright MCP extension</title>
20
+ <meta name="viewport" content="width=device-width, initial-scale=1">
21
+ <link rel="stylesheet" href="src/ui/connect.css">
22
  </head>
23
  <body>
24
+ <div id="root"></div>
25
+ <script type="module" src="src/ui/connect.tsx"></script>
 
 
 
 
 
 
 
 
 
26
  </body>
27
  </html>
extension/src/connect.ts DELETED
@@ -1,172 +0,0 @@
1
- /**
2
- * Copyright (c) Microsoft Corporation.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
-
17
- interface TabInfo {
18
- id: number;
19
- windowId: number;
20
- title: string;
21
- url: string;
22
- favIconUrl?: string;
23
- }
24
-
25
- class ConnectPage {
26
- private _tabList: HTMLElement;
27
- private _tabListContainer: HTMLElement;
28
- private _statusContainer: HTMLElement;
29
- private _selectedTab: TabInfo | undefined;
30
-
31
- constructor() {
32
- this._tabList = document.getElementById('tab-list')!;
33
- this._tabListContainer = document.getElementById('tab-list-container')!;
34
- this._statusContainer = document.getElementById('status-container') as HTMLElement;
35
- this._addButtonHandlers();
36
- void this._loadTabs();
37
- }
38
-
39
- private _addButtonHandlers() {
40
- const continueBtn = document.getElementById('continue-btn') as HTMLButtonElement;
41
- const rejectBtn = document.getElementById('reject-btn') as HTMLButtonElement;
42
- const buttonRow = document.querySelector('.button-row') as HTMLElement;
43
-
44
- const params = new URLSearchParams(window.location.search);
45
- const mcpRelayUrl = params.get('mcpRelayUrl');
46
-
47
- if (!mcpRelayUrl) {
48
- buttonRow.style.display = 'none';
49
- this._showStatus('error', 'Missing mcpRelayUrl parameter in URL.');
50
- return;
51
- }
52
-
53
- let clientInfo = 'unknown';
54
- try {
55
- const client = JSON.parse(params.get('client') || '{}');
56
- clientInfo = `${client.name}/${client.version}`;
57
- } catch (e) {
58
- this._showStatus('error', 'Failed to parse client version.');
59
- return;
60
- }
61
-
62
- this._showStatus('connecting', `MCP client "${clientInfo}" is trying to connect. Do you want to continue?`);
63
-
64
- rejectBtn.addEventListener('click', async () => {
65
- buttonRow.style.display = 'none';
66
- this._tabListContainer.style.display = 'none';
67
- this._showStatus('error', 'Connection rejected. This tab can be closed.');
68
- });
69
-
70
- continueBtn.addEventListener('click', async () => {
71
- buttonRow.style.display = 'none';
72
- this._tabListContainer.style.display = 'none';
73
- try {
74
- const selectedTab = this._selectedTab;
75
- if (!selectedTab) {
76
- this._showStatus('error', 'Tab not selected.');
77
- return;
78
- }
79
- const response = await chrome.runtime.sendMessage({
80
- type: 'connectToMCPRelay',
81
- mcpRelayUrl,
82
- tabId: selectedTab.id,
83
- windowId: selectedTab.windowId,
84
- });
85
- if (response?.success)
86
- this._showStatus('connected', `MCP client "${clientInfo}" connected.`);
87
- else
88
- this._showStatus('error', response?.error || `MCP client "${clientInfo}" failed to connect.`);
89
- } catch (e) {
90
- this._showStatus('error', `MCP client "${clientInfo}" failed to connect: ${e}`);
91
- }
92
- });
93
- }
94
-
95
- private async _loadTabs(): Promise<void> {
96
- try {
97
- const response = await chrome.runtime.sendMessage({ type: 'getTabs' });
98
- if (response.success)
99
- this._populateTabList(response.tabs, response.currentTabId);
100
- else
101
- this._showStatus('error', 'Failed to load tabs: ' + response.error);
102
- } catch (error) {
103
- this._showStatus('error', 'Failed to communicate with background script: ' + error);
104
- }
105
- }
106
-
107
- private _populateTabList(tabs: TabInfo[], currentTabId: number): void {
108
- this._tabList.replaceChildren();
109
- this._selectedTab = tabs.find(tab => tab.id === currentTabId);
110
-
111
- tabs.forEach((tab, index) => {
112
- const tabElement = this._createTabElement(tab);
113
- this._tabList.appendChild(tabElement);
114
- });
115
- }
116
-
117
- private _createTabElement(tab: TabInfo): HTMLElement {
118
- const disabled = tab.url.startsWith('chrome://');
119
-
120
- const tabInfoDiv = document.createElement('div');
121
- tabInfoDiv.className = 'tab-info';
122
- tabInfoDiv.style.padding = '5px';
123
- if (disabled)
124
- tabInfoDiv.style.opacity = '0.5';
125
-
126
- const radioButton = document.createElement('input');
127
- radioButton.type = 'radio';
128
- radioButton.name = 'tab-selection';
129
- radioButton.checked = tab.id === this._selectedTab?.id;
130
- radioButton.id = `tab-${tab.id}`;
131
- radioButton.addEventListener('change', e => {
132
- if (radioButton.checked)
133
- this._selectedTab = tab;
134
- });
135
- if (disabled)
136
- radioButton.disabled = true;
137
-
138
- const favicon = document.createElement('img');
139
- favicon.className = 'tab-favicon';
140
- if (tab.favIconUrl)
141
- favicon.src = tab.favIconUrl;
142
- favicon.alt = '';
143
- favicon.style.height = '16px';
144
- favicon.style.width = '16px';
145
-
146
- const title = document.createElement('span');
147
- title.style.paddingLeft = '5px';
148
- title.className = 'tab-title';
149
- title.textContent = tab.title || 'Untitled';
150
-
151
- const url = document.createElement('span');
152
- url.style.paddingLeft = '5px';
153
- url.className = 'tab-url';
154
- url.textContent = tab.url;
155
-
156
- tabInfoDiv.appendChild(radioButton);
157
- tabInfoDiv.appendChild(favicon);
158
- tabInfoDiv.appendChild(title);
159
- tabInfoDiv.appendChild(url);
160
-
161
- return tabInfoDiv;
162
- }
163
-
164
- private _showStatus(type: 'connected' | 'error' | 'connecting', message: string) {
165
- const div = document.createElement('div');
166
- div.className = `status ${type}`;
167
- div.textContent = message;
168
- this._statusContainer.replaceChildren(div);
169
- }
170
- }
171
-
172
- new ConnectPage();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
extension/src/ui/connect.css ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Copyright (c) Microsoft Corporation.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ body {
18
+ margin: 0;
19
+ padding: 0;
20
+ }
21
+
22
+ /* Base styles */
23
+ .app-container {
24
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
25
+ background-color: #ffffff;
26
+ color: #1f2328;
27
+ margin: 0;
28
+ padding: 24px;
29
+ min-height: 100vh;
30
+ font-size: 14px;
31
+ line-height: 1.5;
32
+ }
33
+
34
+ .content-wrapper {
35
+ max-width: 600px;
36
+ margin: 0 auto;
37
+ }
38
+
39
+ .main-title {
40
+ font-size: 32px;
41
+ font-weight: 600;
42
+ margin-bottom: 8px;
43
+ color: #1f2328;
44
+ }
45
+
46
+ /* Status Banner */
47
+ .status-banner {
48
+ padding: 16px;
49
+ margin-bottom: 24px;
50
+ border-radius: 6px;
51
+ border: 1px solid;
52
+ font-size: 14px;
53
+ font-weight: 500;
54
+ }
55
+
56
+ .status-banner.connected {
57
+ background-color: #dafbe1;
58
+ border-color: #1a7f37;
59
+ color: #0d5a23;
60
+ }
61
+
62
+ .status-banner.error {
63
+ background-color: #ffebe9;
64
+ border-color: #da3633;
65
+ color: #a40e26;
66
+ }
67
+
68
+ .status-banner.connecting {
69
+ background-color: #fff8c5;
70
+ border-color: #d1b500;
71
+ color: #7a5c00;
72
+ }
73
+
74
+ /* Buttons */
75
+ .button-container {
76
+ margin-bottom: 24px;
77
+ }
78
+
79
+ .button {
80
+ padding: 8px 16px;
81
+ border-radius: 6px;
82
+ border: 1px solid;
83
+ font-size: 14px;
84
+ font-weight: 500;
85
+ cursor: pointer;
86
+ display: inline-flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ text-decoration: none;
90
+ margin-right: 8px;
91
+ }
92
+
93
+ .button.primary {
94
+ background-color: #2da44e;
95
+ border-color: #2da44e;
96
+ color: #ffffff;
97
+ }
98
+
99
+ .button.primary:hover {
100
+ background-color: #2c974b;
101
+ }
102
+
103
+ .button.default {
104
+ background-color: #f6f8fa;
105
+ border-color: #d1d9e0;
106
+ color: #24292f;
107
+ }
108
+
109
+ .button.default:hover {
110
+ background-color: #f3f4f6;
111
+ border-color: #c7d2da;
112
+ }
113
+
114
+ /* Tab selection */
115
+ .tab-section-title {
116
+ font-size: 20px;
117
+ font-weight: 600;
118
+ margin-bottom: 16px;
119
+ color: #1f2328;
120
+ }
121
+
122
+ .tab-item {
123
+ display: flex;
124
+ align-items: center;
125
+ padding: 12px;
126
+ border: 1px solid #d1d9e0;
127
+ border-radius: 6px;
128
+ margin-bottom: 8px;
129
+ background-color: #ffffff;
130
+ cursor: pointer;
131
+ }
132
+
133
+ .tab-item.selected {
134
+ background-color: #f6f8fa;
135
+ }
136
+
137
+ .tab-item.disabled {
138
+ cursor: not-allowed;
139
+ opacity: 0.5;
140
+ }
141
+
142
+ .tab-radio {
143
+ margin-right: 12px;
144
+ flex-shrink: 0;
145
+ }
146
+
147
+ .tab-favicon {
148
+ width: 16px;
149
+ height: 16px;
150
+ margin-right: 8px;
151
+ flex-shrink: 0;
152
+ }
153
+
154
+ .tab-content {
155
+ flex: 1;
156
+ min-width: 0;
157
+ }
158
+
159
+ .tab-title {
160
+ font-weight: 500;
161
+ color: #1f2328;
162
+ margin-bottom: 2px;
163
+ white-space: nowrap;
164
+ overflow: hidden;
165
+ text-overflow: ellipsis;
166
+ }
167
+
168
+ .tab-url {
169
+ font-size: 12px;
170
+ color: #656d76;
171
+ white-space: nowrap;
172
+ overflow: hidden;
173
+ text-overflow: ellipsis;
174
+ }
extension/src/ui/connect.tsx ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import React, { useState, useEffect, useCallback } from 'react';
18
+ import { createRoot } from 'react-dom/client';
19
+ import './connect.css';
20
+
21
+ interface TabInfo {
22
+ id: number;
23
+ windowId: number;
24
+ title: string;
25
+ url: string;
26
+ favIconUrl?: string;
27
+ }
28
+
29
+ type StatusType = 'connected' | 'error' | 'connecting';
30
+
31
+ const ConnectApp: React.FC = () => {
32
+ const [tabs, setTabs] = useState<TabInfo[]>([]);
33
+ const [selectedTab, setSelectedTab] = useState<TabInfo | undefined>();
34
+ const [status, setStatus] = useState<{ type: StatusType; message: string } | null>(null);
35
+ const [showButtons, setShowButtons] = useState(true);
36
+ const [showTabList, setShowTabList] = useState(true);
37
+ const [clientInfo, setClientInfo] = useState('unknown');
38
+ const [mcpRelayUrl, setMcpRelayUrl] = useState('');
39
+
40
+ useEffect(() => {
41
+ const params = new URLSearchParams(window.location.search);
42
+ const relayUrl = params.get('mcpRelayUrl');
43
+
44
+ if (!relayUrl) {
45
+ setShowButtons(false);
46
+ setStatus({ type: 'error', message: 'Missing mcpRelayUrl parameter in URL.' });
47
+ return;
48
+ }
49
+
50
+ setMcpRelayUrl(relayUrl);
51
+
52
+ try {
53
+ const client = JSON.parse(params.get('client') || '{}');
54
+ const info = `${client.name}/${client.version}`;
55
+ setClientInfo(info);
56
+ setStatus({
57
+ type: 'connecting',
58
+ message: `MCP client "${info}" is trying to connect. Do you want to continue?`
59
+ });
60
+ } catch (e) {
61
+ setStatus({ type: 'error', message: 'Failed to parse client version.' });
62
+ return;
63
+ }
64
+
65
+ void loadTabs();
66
+ }, []);
67
+
68
+ const loadTabs = async () => {
69
+ const response = await chrome.runtime.sendMessage({ type: 'getTabs' });
70
+ if (response.success) {
71
+ setTabs(response.tabs);
72
+ const currentTab = response.tabs.find((tab: TabInfo) => tab.id === response.currentTabId);
73
+ setSelectedTab(currentTab);
74
+ } else {
75
+ setStatus({ type: 'error', message: 'Failed to load tabs: ' + response.error });
76
+ }
77
+ };
78
+
79
+ const handleContinue = useCallback(async () => {
80
+ setShowButtons(false);
81
+ setShowTabList(false);
82
+
83
+ if (!selectedTab) {
84
+ setStatus({ type: 'error', message: 'Tab not selected.' });
85
+ return;
86
+ }
87
+
88
+ try {
89
+ const response = await chrome.runtime.sendMessage({
90
+ type: 'connectToMCPRelay',
91
+ mcpRelayUrl,
92
+ tabId: selectedTab.id,
93
+ windowId: selectedTab.windowId,
94
+ });
95
+
96
+ if (response?.success) {
97
+ setStatus({ type: 'connected', message: `MCP client "${clientInfo}" connected.` });
98
+ } else {
99
+ setStatus({
100
+ type: 'error',
101
+ message: response?.error || `MCP client "${clientInfo}" failed to connect.`
102
+ });
103
+ }
104
+ } catch (e) {
105
+ setStatus({
106
+ type: 'error',
107
+ message: `MCP client "${clientInfo}" failed to connect: ${e}`
108
+ });
109
+ }
110
+ }, [selectedTab, clientInfo, mcpRelayUrl]);
111
+
112
+ const handleReject = useCallback(() => {
113
+ setShowButtons(false);
114
+ setShowTabList(false);
115
+ setStatus({ type: 'error', message: 'Connection rejected. This tab can be closed.' });
116
+ }, []);
117
+
118
+ return (
119
+ <div className='app-container'>
120
+ <div className='content-wrapper'>
121
+ <h1 className='main-title'>
122
+ Playwright MCP Extension
123
+ </h1>
124
+
125
+ {status && <StatusBanner type={status.type} message={status.message} />}
126
+
127
+ {showButtons && (
128
+ <div className='button-container'>
129
+ <Button variant='primary' onClick={handleContinue}>
130
+ Continue
131
+ </Button>
132
+ <Button variant='default' onClick={handleReject}>
133
+ Reject
134
+ </Button>
135
+ </div>
136
+ )}
137
+
138
+
139
+ {showTabList && (
140
+ <div>
141
+ <h2 className='tab-section-title'>
142
+ Select page to expose to MCP server:
143
+ </h2>
144
+ <div>
145
+ {tabs.map(tab => (
146
+ <TabItem
147
+ key={tab.id}
148
+ tab={tab}
149
+ isSelected={selectedTab?.id === tab.id}
150
+ onSelect={() => setSelectedTab(tab)}
151
+ />
152
+ ))}
153
+ </div>
154
+ </div>
155
+ )}
156
+ </div>
157
+ </div>
158
+ );
159
+ };
160
+
161
+ const StatusBanner: React.FC<{ type: StatusType; message: string }> = ({ type, message }) => {
162
+ return <div className={`status-banner ${type}`}>{message}</div>;
163
+ };
164
+
165
+ const Button: React.FC<{ variant: 'primary' | 'default'; onClick: () => void; children: React.ReactNode }> = ({
166
+ variant,
167
+ onClick,
168
+ children
169
+ }) => {
170
+ return (
171
+ <button className={`button ${variant}`} onClick={onClick}>
172
+ {children}
173
+ </button>
174
+ );
175
+ };
176
+
177
+ const TabItem: React.FC<{ tab: TabInfo; isSelected: boolean; onSelect: () => void }> = ({
178
+ tab,
179
+ isSelected,
180
+ onSelect
181
+ }) => {
182
+ const disabled = tab.url.startsWith('chrome://');
183
+
184
+ const className = `tab-item ${isSelected ? 'selected' : ''} ${disabled ? 'disabled' : ''}`.trim();
185
+
186
+ return (
187
+ <div className={className} onClick={disabled ? undefined : onSelect}>
188
+ <input
189
+ type='radio'
190
+ className='tab-radio'
191
+ checked={isSelected}
192
+ disabled={disabled}
193
+ />
194
+ <img
195
+ src={tab.favIconUrl || 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect width="16" height="16" fill="%23f6f8fa"/></svg>'}
196
+ alt=''
197
+ className='tab-favicon'
198
+ />
199
+ <div className='tab-content'>
200
+ <div className='tab-title'>{tab.title || 'Untitled'}</div>
201
+ <div className='tab-url'>{tab.url}</div>
202
+ </div>
203
+ </div>
204
+ );
205
+ };
206
+
207
+
208
+ // Initialize the React app
209
+ const container = document.getElementById('root');
210
+ if (container) {
211
+ const root = createRoot(container);
212
+ root.render(<ConnectApp />);
213
+ }
extension/tsconfig.json CHANGED
@@ -8,8 +8,13 @@
8
  "rootDir": "src",
9
  "outDir": "./lib",
10
  "resolveJsonModule": true,
 
 
11
  },
12
  "include": [
13
  "src",
14
  ],
 
 
 
15
  }
 
8
  "rootDir": "src",
9
  "outDir": "./lib",
10
  "resolveJsonModule": true,
11
+ "jsx": "react-jsx",
12
+ "jsxImportSource": "react"
13
  },
14
  "include": [
15
  "src",
16
  ],
17
+ "exclude": [
18
+ "src/ui",
19
+ ]
20
  }
extension/tsconfig.ui.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "esModuleInterop": true,
5
+ "moduleResolution": "node",
6
+ "strict": true,
7
+ "module": "ESNext",
8
+ "rootDir": "src",
9
+ "outDir": "./lib",
10
+ "resolveJsonModule": true,
11
+ "jsx": "react-jsx",
12
+ "jsxImportSource": "react",
13
+ "noEmit": true,
14
+ },
15
+ "include": [
16
+ "src/ui",
17
+ ],
18
+ }
extension/vite.config.ts ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { resolve } from 'path';
18
+ import { defineConfig } from 'vite';
19
+ import react from '@vitejs/plugin-react';
20
+
21
+ // https://vitejs.dev/config/
22
+ export default defineConfig({
23
+ plugins: [react()],
24
+ base: '/lib/ui/',
25
+ build: {
26
+ outDir: resolve(__dirname, 'lib/ui'),
27
+ emptyOutDir: true,
28
+ minify: false,
29
+ rollupOptions: {
30
+ input: resolve(__dirname, 'connect.html'),
31
+ output: {
32
+ manualChunks: undefined,
33
+ inlineDynamicImports: true,
34
+ entryFileNames: '[name].js',
35
+ chunkFileNames: '[name].js',
36
+ assetFileNames: '[name].[ext]'
37
+ }
38
+ }
39
+ }
40
+ });
package-lock.json CHANGED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -17,12 +17,13 @@
17
  "license": "Apache-2.0",
18
  "scripts": {
19
  "build": "tsc",
20
- "build:extension": "tsc --project extension",
21
  "lint": "npm run update-readme && eslint . && tsc --noEmit",
22
  "lint-fix": "eslint . --fix",
23
  "update-readme": "node utils/update-readme.js",
24
  "watch": "tsc --watch",
25
- "watch:extension": "tsc --watch --project extension",
 
26
  "test": "playwright test",
27
  "ctest": "playwright test --project=chrome",
28
  "ftest": "playwright test --project=firefox",
@@ -58,14 +59,21 @@
58
  "@types/chrome": "^0.0.315",
59
  "@types/debug": "^4.1.12",
60
  "@types/node": "^22.13.10",
 
 
61
  "@types/ws": "^8.18.1",
62
  "@typescript-eslint/eslint-plugin": "^8.26.1",
63
  "@typescript-eslint/parser": "^8.26.1",
64
  "@typescript-eslint/utils": "^8.26.1",
 
65
  "eslint": "^9.19.0",
66
  "eslint-plugin-import": "^2.31.0",
 
 
67
  "eslint-plugin-notice": "^1.0.0",
68
  "openai": "^5.10.2",
 
 
69
  "typescript": "^5.8.2"
70
  },
71
  "bin": {
 
17
  "license": "Apache-2.0",
18
  "scripts": {
19
  "build": "tsc",
20
+ "build:extension": "tsc --project extension && tsc --project extension && vite build extension",
21
  "lint": "npm run update-readme && eslint . && tsc --noEmit",
22
  "lint-fix": "eslint . --fix",
23
  "update-readme": "node utils/update-readme.js",
24
  "watch": "tsc --watch",
25
+ "watch:extension": "tsc --watch --project extension & tsc --watch --project extension/tsconfig.ui.json & vite build extension --watch",
26
+ "dev:extension": "vite build --watch",
27
  "test": "playwright test",
28
  "ctest": "playwright test --project=chrome",
29
  "ftest": "playwright test --project=firefox",
 
59
  "@types/chrome": "^0.0.315",
60
  "@types/debug": "^4.1.12",
61
  "@types/node": "^22.13.10",
62
+ "@types/react": "^18.2.66",
63
+ "@types/react-dom": "^18.2.22",
64
  "@types/ws": "^8.18.1",
65
  "@typescript-eslint/eslint-plugin": "^8.26.1",
66
  "@typescript-eslint/parser": "^8.26.1",
67
  "@typescript-eslint/utils": "^8.26.1",
68
+ "esbuild": "^0.20.1",
69
  "eslint": "^9.19.0",
70
  "eslint-plugin-import": "^2.31.0",
71
+ "vite": "^5.0.0",
72
+ "@vitejs/plugin-react": "^4.0.0",
73
  "eslint-plugin-notice": "^1.0.0",
74
  "openai": "^5.10.2",
75
+ "react": "^18.2.0",
76
+ "react-dom": "^18.2.0",
77
  "typescript": "^5.8.2"
78
  },
79
  "bin": {
src/extension/cdpRelay.ts CHANGED
@@ -103,7 +103,7 @@ export class CDPRelayServer {
103
  private async _connectBrowser(clientInfo: { name: string, version: string }) {
104
  const mcpRelayEndpoint = `${this._wsHost}${this._extensionPath}`;
105
  // Need to specify "key" in the manifest.json to make the id stable when loading from file.
106
- const url = new URL('chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html');
107
  url.searchParams.set('mcpRelayUrl', mcpRelayEndpoint);
108
  url.searchParams.set('client', JSON.stringify(clientInfo));
109
  const href = url.toString();
 
103
  private async _connectBrowser(clientInfo: { name: string, version: string }) {
104
  const mcpRelayEndpoint = `${this._wsHost}${this._extensionPath}`;
105
  // Need to specify "key" in the manifest.json to make the id stable when loading from file.
106
+ const url = new URL('chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/lib/ui/connect.html');
107
  url.searchParams.set('mcpRelayUrl', mcpRelayEndpoint);
108
  url.searchParams.set('client', JSON.stringify(clientInfo));
109
  const href = url.toString();
tsconfig.all.json CHANGED
@@ -1,4 +1,4 @@
1
  {
2
  "extends": "./tsconfig.json",
3
- "include": ["**/*.ts", "**/*.js"],
4
  }
 
1
  {
2
  "extends": "./tsconfig.json",
3
+ "include": ["**/*.ts", "**/*.tsx", "**/*.js"],
4
  }