Yury Semikhatsky commited on
Commit
711089d
·
unverified ·
1 Parent(s): 4a7be8d

chore: add auth token example (#1070)

Browse files

Fixes https://github.com/microsoft/playwright-mcp/issues/1069

extension/src/ui/authToken.css CHANGED
@@ -68,3 +68,75 @@
68
  .auth-token-refresh:not(:disabled):hover {
69
  background-color: var(--color-btn-selected-bg);
70
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  .auth-token-refresh:not(:disabled):hover {
69
  background-color: var(--color-btn-selected-bg);
70
  }
71
+
72
+ .auth-token-example-section {
73
+ margin-top: 16px;
74
+ }
75
+
76
+ .auth-token-example-toggle {
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 8px;
80
+ background: none;
81
+ border: none;
82
+ padding: 8px 0;
83
+ font-size: 12px;
84
+ color: #656d76;
85
+ cursor: pointer;
86
+ outline: none;
87
+ text-align: left;
88
+ width: 100%;
89
+ }
90
+
91
+ .auth-token-example-toggle:hover {
92
+ color: #1f2328;
93
+ }
94
+
95
+ .auth-token-chevron {
96
+ display: inline-flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ transform: rotate(-90deg);
100
+ flex-shrink: 0;
101
+ }
102
+
103
+ .auth-token-chevron.expanded {
104
+ transform: rotate(0deg);
105
+ }
106
+
107
+ .auth-token-chevron svg {
108
+ width: 12px;
109
+ height: 12px;
110
+ }
111
+
112
+ .auth-token-chevron .octicon {
113
+ margin: 0px;
114
+ }
115
+
116
+ .auth-token-example-content {
117
+ margin-top: 12px;
118
+ padding: 12px 0;
119
+ }
120
+
121
+ .auth-token-example-description {
122
+ font-size: 12px;
123
+ color: #656d76;
124
+ margin-bottom: 12px;
125
+ }
126
+
127
+ .auth-token-example-config {
128
+ display: flex;
129
+ align-items: flex-start;
130
+ gap: 8px;
131
+ background-color: #ffffff;
132
+ padding: 12px;
133
+ }
134
+
135
+ .auth-token-example-code {
136
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
137
+ font-size: 11px;
138
+ color: #1f2328;
139
+ white-space: pre;
140
+ flex: 1;
141
+ line-height: 1.4;
142
+ }
extension/src/ui/authToken.tsx CHANGED
@@ -21,6 +21,7 @@ import './authToken.css';
21
 
22
  export const AuthTokenSection: React.FC<{}> = ({}) => {
23
  const [authToken, setAuthToken] = useState<string>(getOrCreateAuthToken);
 
24
 
25
  const onRegenerateToken = useCallback(() => {
26
  const newToken = generateAuthToken();
@@ -28,20 +29,69 @@ export const AuthTokenSection: React.FC<{}> = ({}) => {
28
  setAuthToken(newToken);
29
  }, []);
30
 
 
 
 
 
31
  return (
32
  <div className='auth-token-section'>
33
  <div className='auth-token-description'>
34
  Set this environment variable to bypass the connection dialog:
35
  </div>
36
  <div className='auth-token-container'>
37
- <code className='auth-token-code'>PLAYWRIGHT_MCP_EXTENSION_TOKEN={authToken}</code>
38
  <button className='auth-token-refresh' title='Generate new token' aria-label='Generate new token'onClick={onRegenerateToken}>{icons.refresh()}</button>
39
- <CopyToClipboard value={authToken} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  </div>
41
  </div>
42
  );
43
  };
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  function generateAuthToken(): string {
46
  // Generate a cryptographically secure random token
47
  const array = new Uint8Array(32);
 
21
 
22
  export const AuthTokenSection: React.FC<{}> = ({}) => {
23
  const [authToken, setAuthToken] = useState<string>(getOrCreateAuthToken);
24
+ const [isExampleExpanded, setIsExampleExpanded] = useState<boolean>(false);
25
 
26
  const onRegenerateToken = useCallback(() => {
27
  const newToken = generateAuthToken();
 
29
  setAuthToken(newToken);
30
  }, []);
31
 
32
+ const toggleExample = useCallback(() => {
33
+ setIsExampleExpanded(!isExampleExpanded);
34
+ }, [isExampleExpanded]);
35
+
36
  return (
37
  <div className='auth-token-section'>
38
  <div className='auth-token-description'>
39
  Set this environment variable to bypass the connection dialog:
40
  </div>
41
  <div className='auth-token-container'>
42
+ <code className='auth-token-code'>{authTokenCode(authToken)}</code>
43
  <button className='auth-token-refresh' title='Generate new token' aria-label='Generate new token'onClick={onRegenerateToken}>{icons.refresh()}</button>
44
+ <CopyToClipboard value={authTokenCode(authToken)} />
45
+ </div>
46
+
47
+ <div className='auth-token-example-section'>
48
+ <button
49
+ className='auth-token-example-toggle'
50
+ onClick={toggleExample}
51
+ aria-expanded={isExampleExpanded}
52
+ title={isExampleExpanded ? 'Hide example config' : 'Show example config'}
53
+ >
54
+ <span className={`auth-token-chevron ${isExampleExpanded ? 'expanded' : ''}`}>
55
+ {icons.chevronDown()}
56
+ </span>
57
+ Example MCP server configuration
58
+ </button>
59
+
60
+ {isExampleExpanded && (
61
+ <div className='auth-token-example-content'>
62
+ <div className='auth-token-example-description'>
63
+ Add this configuration to your MCP client (e.g., VS Code) to connect to the Playwright MCP Bridge:
64
+ </div>
65
+ <div className='auth-token-example-config'>
66
+ <code className='auth-token-example-code'>{exampleConfig(authToken)}</code>
67
+ <CopyToClipboard value={exampleConfig(authToken)} />
68
+ </div>
69
+ </div>
70
+ )}
71
  </div>
72
  </div>
73
  );
74
  };
75
 
76
+ function authTokenCode(authToken: string) {
77
+ return `PLAYWRIGHT_MCP_EXTENSION_TOKEN=${authToken}`;
78
+ }
79
+
80
+ function exampleConfig(authToken: string) {
81
+ return `{
82
+ "mcpServers": {
83
+ "playwright": {
84
+ "command": "npx",
85
+ "args": ["@playwright/mcp@latest", "--extension"],
86
+ "env": {
87
+ "PLAYWRIGHT_MCP_EXTENSION_TOKEN":
88
+ "${authToken}"
89
+ }
90
+ }
91
+ }
92
+ }`;
93
+ }
94
+
95
  function generateAuthToken(): string {
96
  // Generate a cryptographically secure random token
97
  const array = new Uint8Array(32);
extension/src/ui/icons.tsx CHANGED
@@ -41,3 +41,9 @@ export const refresh = () => {
41
  <path d="M1.705 8.005a.75.75 0 0 1 .834.656 5.5 5.5 0 0 0 9.592 2.97l-1.204-1.204a.25.25 0 0 1 .177-.427h3.646a.25.25 0 0 1 .25.25v3.646a.25.25 0 0 1-.427.177l-1.38-1.38A7.002 7.002 0 0 1 1.05 8.84a.75.75 0 0 1 .656-.834ZM8 2.5a5.487 5.487 0 0 0-4.131 1.869l1.204 1.204A.25.25 0 0 1 4.896 6H1.25A.25.25 0 0 1 1 5.75V2.104a.25.25 0 0 1 .427-.177l1.38 1.38A7.002 7.002 0 0 1 14.95 7.16a.75.75 0 0 1-1.49.178A5.5 5.5 0 0 0 8 2.5Z"></path>
42
  </svg>;
43
  };
 
 
 
 
 
 
 
41
  <path d="M1.705 8.005a.75.75 0 0 1 .834.656 5.5 5.5 0 0 0 9.592 2.97l-1.204-1.204a.25.25 0 0 1 .177-.427h3.646a.25.25 0 0 1 .25.25v3.646a.25.25 0 0 1-.427.177l-1.38-1.38A7.002 7.002 0 0 1 1.05 8.84a.75.75 0 0 1 .656-.834ZM8 2.5a5.487 5.487 0 0 0-4.131 1.869l1.204 1.204A.25.25 0 0 1 4.896 6H1.25A.25.25 0 0 1 1 5.75V2.104a.25.25 0 0 1 .427-.177l1.38 1.38A7.002 7.002 0 0 1 14.95 7.16a.75.75 0 0 1-1.49.178A5.5 5.5 0 0 0 8 2.5Z"></path>
42
  </svg>;
43
  };
44
+
45
+ export const chevronDown = () => {
46
+ return <svg className='octicon' viewBox="0 0 16 16" width="16" height="16" aria-hidden='true'>
47
+ <path d="M12.78 5.22a.749.749 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.06 0L3.22 6.28a.749.749 0 1 1 1.06-1.06L8 8.939l3.72-3.719a.749.749 0 0 1 1.06 0Z"></path>
48
+ </svg>;
49
+ };