Skip to content

Commit 2cb092e

Browse files
browser: Run Auth Activation in browser (#4317)
* refactor: minor code reorganization Signed-off-by: nkomonen <[email protected]> * make fs not execute on import Make modeMap a getter so that it only executes fs.constants when actually called an not on the class instantiation. We are doing this since it breaks in the browser Signed-off-by: nkomonen <[email protected]> * make AWS.Token actually exist AWS.Token did not exist when running in the browser. As a solution this is a quick hack to get it working, see the docstring. Signed-off-by: nkomonen <[email protected]> * browser: Initialize Auth in browser This runs the Auth activation method to set it up. This will run without failure, but once we try to set up auth it will break due to needing to fix the way we handle filesystem paths when in the browser. But we now have auth at least loading for the most part Signed-off-by: nkomonen <[email protected]> * refactor: convert path better in SystemUtilities Problem: In multiple methods in SystemUtilities the given path is converted to a URI if it is a string, but it does not respect the scheme of a string path and parses it as a file. This results in an incorrect scheme on the URI Solution: Create a new private method toUri() which converts a string path to a URI with the appropriate scheme, or just returns the URI if it is already a URI. Signed-off-by: nkomonen <[email protected]> * browser: debugging the browser changes - Disable CORS when using `npm run runInBrowser` so that auth (OIDC) will work - Update documentation regarding testing/debugging/running the extension in browser mode Signed-off-by: nkomonen <[email protected]> * browser: dont check icon paths when in browser Now if in browser we will not check if the paths exist for dark and light theme. Also we needed to maintain the sync of this function so we do not have a good way to do this even if the URI exists in the browser Signed-off-by: nkomonen <[email protected]> * browser: support homedir when in browser When in the browser we do not have access to the users file system. Instead we must use the vscode provided uris. So we use globalStorageUri as the homedir path. Additionally, there is a racecondition to when globals.context is defined versus accessed by defaultCacheDir, so we converted it to a function so it can be accessed lazily and globals.context should be available at that point Signed-off-by: nkomonen <[email protected]> * browser: debugging in chrome works! Signed-off-by: nkomonen <[email protected]> * rename: browser script Signed-off-by: nkomonen <[email protected]> * rename: browserRun script package.json Signed-off-by: nkomonen <[email protected]> * browser: run chrome window w/ debugging - Now we can run a Chrome window with the extension in it and debug from VS Code - Added a launch config 'Extension (Chrome)' to be able to run the extension, but read the updated doc in browser.md first Signed-off-by: nkomonen <[email protected]> * run formatter Signed-off-by: nkomonen <[email protected]> --------- Signed-off-by: nkomonen <[email protected]>
1 parent 3d6d597 commit 2cb092e

File tree

14 files changed

+251
-74
lines changed

14 files changed

+251
-74
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,5 @@ resources/css/icons.css
4545
.local.env
4646
*.config.local.json
4747

48-
# Created by `npm run runInBrowser` when testing extension in the browser
48+
# Created by `npm run browserRun` when testing extension in the browser
4949
.vscode-test-web

.vscode/launch.json

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,25 @@
2525
"preLaunchTask": "watch"
2626
},
2727
{
28-
"name": "Extension (browser)",
29-
"type": "pwa-extensionHost",
30-
"debugWebWorkerHost": true,
31-
"request": "launch",
32-
"args": ["--extensionDevelopmentPath=${workspaceFolder}", "--extensionDevelopmentKind=web"],
33-
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
34-
"preLaunchTask": "buildBrowserDebug"
28+
/** Handles the entire process of building and running the toolkit extension in the browser. **/
29+
"name": "Extension (Chrome)",
30+
"type": "chrome",
31+
"request": "attach",
32+
"port": 9222,
33+
/**
34+
To get an understanding why we need the following:
35+
- comment out the following
36+
- set a breakpoint in VS Code that gets triggerd on extension startup
37+
Now in the Chrome Developer Tools menu, the extension will load slower and open up random files.
38+
I think this is due to source maps for irrelevant code being attempted to be resolved and slowing execution.
39+
40+
What this is doing is ignoring certain modules that match the following paths, it matches the path of
41+
a file in `Developer Tools` > `Sources`.
42+
I was inspired by this: https://github.com/microsoft/vscode-test-web/blob/897bca4907a87a6bc564efc242ce6794e5da3232/.vscode/launch.json#L28
43+
**/
44+
"resolveSourceMapLocations": ["!**/node_modules/**", "!**/vs/**", "!**/extensions/**"],
45+
"preLaunchTask": "browserRun",
46+
"postDebugTask": "browserRunTerminate"
3547
},
3648
{
3749
"name": "Extension (webpack)",
@@ -91,7 +103,7 @@
91103
"${workspaceRoot}/dist/src/testFixtures/workspaceFolder"
92104
],
93105
"outFiles": ["${workspaceFolder}/dist/src/**/*.js"],
94-
"preLaunchTask": "buildBrowserDebug"
106+
"preLaunchTask": "browserWatch"
95107
},
96108
{
97109
"name": "Integration Tests",

.vscode/tasks.json

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,60 @@
3737
}
3838
},
3939
{
40-
"label": "buildBrowserDebug",
40+
"label": "browserWatch",
4141
"type": "npm",
42-
"script": "buildBrowserDebug",
43-
"detail": "Builds the webpacked browser bundle for debugging"
42+
"script": "browserWatch",
43+
"detail": "Webpacks our toolkit code (with --watch) in preparation to be run in the browser",
44+
"isBackground": true,
45+
// Since `webpack --watch` never terminates (but finishes packaging at some point),
46+
// VS Code uses this to parse the CLI output to pattern match something that indicates it is done
47+
"problemMatcher": "$ts-webpack-watch"
48+
},
49+
{
50+
"label": "browserRun",
51+
"type": "npm",
52+
"script": "browserRun",
53+
"detail": "Runs VS Code in the Chrome browser with our toolkit installed",
54+
"isBackground": true,
55+
"dependsOn": ["browserWatch"],
56+
/**
57+
We only need this problem matcher to signal when this task is completed.
58+
Since this task starts a web server it does not terminate and we need to use
59+
problemMatcher.background.endsPattern to read the CLI and know when this task
60+
can signal it is done.
61+
62+
The rest of the data in problemMatcher is required by VS Code to be "valid",
63+
but not important for what we need.
64+
65+
Doc: https://code.visualstudio.com/Docs/editor/tasks#_defining-a-problem-matcher
66+
**/
67+
"problemMatcher": {
68+
"pattern":[
69+
{
70+
"regexp": "this section irrelevant but it must exist to work",
71+
"file": 1,
72+
"location": 2,
73+
"message": 3
74+
}
75+
],
76+
"background": {
77+
"activeOnStart": true,
78+
"beginsPattern": "^@vscode\/test-web",
79+
"endsPattern": "^Listening on"
80+
},
81+
82+
},
83+
},
84+
/**
85+
After we stop debugging our browser, we also want to stop the web server.
86+
When this task is ran it will stop the web server.
87+
88+
From: https://stackoverflow.com/a/60330174
89+
**/
90+
{
91+
"label": "browserRunTerminate",
92+
"command": "echo ${input:browserRunTerminate}",
93+
"type": "shell",
4494
},
4595
{
4696
"type": "npm",
@@ -77,6 +127,12 @@
77127
"type": "command",
78128
"command": "workbench.action.tasks.terminate",
79129
"args": "terminateAll"
130+
},
131+
{
132+
"id": "browserRunTerminate",
133+
"type": "command",
134+
"command": "workbench.action.tasks.terminate",
135+
"args": "browserRun"
80136
}
81137
]
82138
}

docs/browser.md

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,72 @@
11
# Browser
22

3-
This extension can run in the web browser (eg: [vscode.dev](https://vscode.dev)), but with limited functionality.
3+
This extension can run in the web browser (eg: [vscode.dev](https://vscode.dev)), but with limited functionality compared to
4+
the desktop version.
45

56
## Running the Browser implementation
67

78
You can run the browser implementation of the extension in the following ways.
89

9-
### Running in VSCode
10+
### General Notes
1011

11-
The following steps will result in a VSCode Extension window running
12-
with the AWS Toolkit extension installed.
13-
The difference from the regular
14-
process is that in the background it is running as a browser environment
15-
so certain things like Node.js modules will not be available.
16-
17-
1. In the terminal run: `npm run buildBrowser`
12+
- To see logs, using the Command Palette search: `Toggle Developer Tools`. Then go to the `Console` tab. In browser mode VS Code seems to duplicate log messages, idk how to fix this.
13+
- The difference between browser mode and Node.js/desktop is that browser mode is in an actual browser environment so certain things like Node.js modules will **not** be available.
1814

19-
## Running in a Browser window
15+
## Running in an actual Browser
2016

2117
The following steps will result in a Chrome window running with VS Code
22-
and the Browser version of the AWS Toolkit extension installed.
18+
and the Browser version of the AWS Toolkit extension installed:
19+
20+
1. (Recommended) To have the Chrome window save your VS Code state after closing it, you need to modify the node module which executes the playwright method that opens it. In `node_modules/@vscode/test-web/out/index.js#openBrowser()` make the following change:
2321

24-
1. In the terminal run: `npm run runInBrowser`
22+
Before:
2523

26-
##### (OPTIONAL) Disabling CORS
24+
```typescript
25+
const browser = await browserType.launch({ headless, args, devtools: options.devTools })
26+
const context = await browser.newContext({ viewport: null })
27+
```
2728

28-
In the case you want to disable CORS in the browser for something like
29-
contacting the telemetry api endpoint, do the following.
29+
After:
3030

31-
The script that starts the browser does not provide a way to disable security,
32-
so we need to modify the code slightly to ensure the browser starts with CORS disabled.
31+
```typescript
32+
const tempContextDir = path.join(process.cwd(), '.vscode-test-web/aws-toolkit-user-dir')
33+
const browser = await browserType.launchPersistentContext(tempContextDir, {
34+
headless,
35+
args,
36+
devtools: options.devTools,
37+
viewport: null,
38+
})
39+
const context = browser
40+
```
41+
42+
2. In the `Run & Debug` menu select the `Extension (Chrome)` option
3343

34-
1. Go to `./node_modules/@vscode/test-web/out/index.js`
35-
2. Go to the function `openBrowser()`
36-
3. Add the line `args.push('--disable-web-security')`
44+
> Note: To stop the debug session, you need to click the read `Disconnect` button multiple times
3745

38-
Now when you run the extension in the browser it will not do CORS checks.
46+
> Note: Setting breakpoints in VS Code works
3947

40-
#### Debugging in Browser window
48+
#### (OPTIONAL) Enabling CORS
4149

42-
Debugging in the Browser is more difficult than the Node.js/Desktop
43-
version.
50+
By default, we disable CORS in browser mode since certain endpoints
51+
such as telemetry or auth do not support CORS (at the moment of writing) for the VSCode origin.
4452

45-
- Breakpoints do not work, we cannot step through the code.
53+
In the case you want to enable CORS in the browser to test CORS compatibility
54+
do the following:
4655

47-
The best we can do (as far as I know) is to read logs.
56+
- In `package.json` find the `browserRun` script
57+
- Temporarily remove `--browserOption=--disable-web-security`
4858

49-
To get to the VS Code logs go to:
59+
Now when you run the extension in the browser it will do CORS checks.
5060

51-
1. The `Output` tab
52-
2. In the top right drop-down select: `Extension Host (Worker)`
61+
## Testing in VSCode Browser Mode
5362

54-
> The VS Code logs will show errors if we try to use modules that do not exist in the Browser.
63+
The following steps will result in a VSCode Extension window running
64+
with the AWS Toolkit extension installed. While it looks the same as a typical
65+
VS Code window, in the background it is running in a Browser context.
5566

56-
To get to the AWS Toolkit logs:
67+
- In the `Run & Debug` menu run: `Extension Tests (browser)`
5768

58-
1. Open Command Palette: `cmd/ctrl` + `shift` + `p`
59-
2. Type: `AWS: View Toolkit Logs`
69+
> NOTE: Tests do not spin up an actual Browser window, but if we find a good reason to switch it will require some additional work. The current way does not require dowloading a separate browser like Chromium.
6070

6171
## Finding incompatible transitive dependencies
6272

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4242,8 +4242,8 @@
42424242
"reset": "npm run clean -- node_modules && npm install",
42434243
"copyFiles": "ts-node ./scripts/build/copyFiles.ts",
42444244
"buildScripts": "npm run generateClients && npm run generatePackage && npm run generateNonCodeFiles && npm run copyFiles",
4245-
"buildBrowserDebug": "npm run clean && npm run buildScripts && webpack --config webpack.browser.config.js",
4246-
"runInBrowser": "npm run buildBrowserDebug && npx @vscode/test-web --open-devtools --extensionDevelopmentPath=. .",
4245+
"browserWatch": "npm run clean && npm run buildScripts && webpack --config webpack.browser.config.js --watch",
4246+
"browserRun": "npx @vscode/test-web --open-devtools --browserOption=--disable-web-security --waitForDebugger=9222 --extensionDevelopmentPath=. .",
42474247
"compile": "npm run clean && npm run buildScripts && webpack --mode development && npm run copyFiles",
42484248
"watch": "npm run clean && npm run buildScripts && tsc -watch -p ./",
42494249
"postinstall": "npm run generateTelemetry && npm run generateConfigurationAttributes && npm run buildCustomLintPlugin",

src/auth/activation.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ExtensionUse } from './utils'
1414
import { isCloud9 } from '../shared/extensionUtilities'
1515
import { isInDevEnv } from '../codecatalyst/utils'
1616
import { showManageConnections } from './ui/vue/show'
17+
import { isInBrowser } from '../common/browserUtils'
1718

1819
export async function initialize(
1920
extensionContext: vscode.ExtensionContext,
@@ -40,7 +41,10 @@ export async function initialize(
4041
async function showManageConnectionsOnStartup() {
4142
// Do not show connection management to user in certain scenarios.
4243
let reason: string = ''
43-
if (!ExtensionUse.instance.isFirstUse()) {
44+
if (isInBrowser()) {
45+
// TODO: Figure out how we want users to connect to auth in browser mode
46+
reason = 'We are in the browser'
47+
} else if (!ExtensionUse.instance.isFirstUse()) {
4448
reason = 'This is not the users first use of the extension'
4549
} else if (isInDevEnv()) {
4650
reason = 'The user is in a Dev Evironment'

src/auth/sso/cache.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ export interface SsoCache {
3030
readonly registration: KeyedCache<ClientRegistration, RegistrationKey>
3131
}
3232

33-
const defaultCacheDir = path.join(SystemUtilities.getHomeDirectory(), '.aws', 'sso', 'cache')
34-
export const getCacheDir = () => DevSettings.instance.get('ssoCacheDirectory', defaultCacheDir)
33+
const defaultCacheDir = () => path.join(SystemUtilities.getHomeDirectory(), '.aws', 'sso', 'cache')
34+
export const getCacheDir = () => DevSettings.instance.get('ssoCacheDirectory', defaultCacheDir())
3535

3636
export function getCache(directory = getCacheDir()): SsoCache {
3737
return {

src/auth/sso/sdkV2Compat.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@ import * as AWS from 'aws-sdk'
77
import { Token } from 'aws-sdk/lib/token'
88
import { Connection } from '../connection'
99

10-
const TokenClass = (AWS as any).Token as typeof Token
11-
export class TokenProvider extends TokenClass {
10+
/**
11+
* {@link AWS.Token} is defined when {@link Token} is imported.
12+
* But for {@link Token} to not get tree-shaken we need to use it.
13+
* So the following simply uses it and now {@link AWS.Token} will not
14+
* be undefined anymore.
15+
*/
16+
Token
17+
18+
export class TokenProvider extends AWS.Token {
1219
public constructor(private readonly connection: Connection & { type: 'sso' }) {
1320
super({ token: '', expireTime: new Date(0) })
1421
}

src/auth/ui/vue/show.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { ClassToInterfaceType } from '../../../shared/utilities/tsUtils'
5353
import { debounce } from 'lodash'
5454
import { submitFeedback } from '../../../feedback/vue/submitFeedback'
5555
import { InvalidGrantException } from '@aws-sdk/client-sso-oidc'
56+
import { isInBrowser } from '../../../common/browserUtils'
5657

5758
export class AuthWebview extends VueWebview {
5859
public override id: string = 'authWebview'
@@ -732,7 +733,7 @@ export const showManageConnections = Commands.declare(
732733

733734
// The auth webview page does not make sense to use in C9,
734735
// so show the auth quick pick instead.
735-
if (isCloud9('any')) {
736+
if (isCloud9('any') || isInBrowser()) {
736737
if (source.toLowerCase().includes('codewhisperer')) {
737738
// Show CW specific quick pick for CW connections
738739
return showCodeWhispererConnectionPrompt()

src/extensionWeb.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import globals, { initialize } from './shared/extensionGlobals'
1515
import { registerCommands, initializeManifestPaths } from './extensionShared'
1616
import { RegionProvider, defaultRegion } from './shared/regions/regionProvider'
1717
import { DefaultAWSClientBuilder } from './shared/awsClientBuilder'
18+
import { initialize as initializeCredentials } from './auth/activation'
19+
import { LoginManager } from './auth/deprecated/loginManager'
20+
import { CredentialsStore } from './auth/credentials/store'
21+
import { initializeAwsCredentialsStatusBarItem } from './auth/ui/statusBarItem'
1822

1923
export async function activate(context: vscode.ExtensionContext) {
2024
setInBrowser(true) // THIS MUST ALWAYS BE FIRST
@@ -24,24 +28,21 @@ export async function activate(context: vscode.ExtensionContext) {
2428
)
2529

2630
try {
27-
setupGlobalStubs()
28-
2931
// Setup the logger
3032
const toolkitOutputChannel = vscode.window.createOutputChannel('AWS Toolkit', { log: true })
3133
await activateLogger(context, toolkitOutputChannel)
3234

35+
setupGlobals()
36+
3337
await initializeComputeRegion()
3438
initialize(context)
3539
initializeManifestPaths(context)
3640

37-
const awsContext = new DefaultAwsContext()
38-
globals.awsContext = awsContext
41+
await activateTelemetry(context, globals.awsContext, Settings.instance)
3942

40-
globals.sdkClientBuilder = new DefaultAWSClientBuilder(awsContext)
43+
await initializeCredentials(context, globals.awsContext, globals.loginManager)
44+
await initializeAwsCredentialsStatusBarItem(globals.awsContext, context)
4145

42-
const settings = Settings.instance
43-
44-
await activateTelemetry(context, awsContext, settings)
4546
registerCommands(context)
4647
} catch (error) {
4748
const stacktrace = (error as Error).stack?.split('\n')
@@ -54,6 +55,16 @@ export async function activate(context: vscode.ExtensionContext) {
5455
}
5556
}
5657

58+
/** Set up values for the `globals` object */
59+
function setupGlobals() {
60+
globals.awsContext = new DefaultAwsContext()
61+
globals.sdkClientBuilder = new DefaultAWSClientBuilder(globals.awsContext)
62+
63+
globals.loginManager = new LoginManager(globals.awsContext, new CredentialsStore())
64+
65+
setupGlobalsTempStubs()
66+
}
67+
5768
/**
5869
* Since we are still incrementally enabling certain functionality
5970
* in the browser, certain global variables will not have been set
@@ -65,7 +76,7 @@ export async function activate(context: vscode.ExtensionContext) {
6576
* If needed we can eventually create the real implementations instead
6677
* of stubbing.
6778
*/
68-
function setupGlobalStubs() {
79+
function setupGlobalsTempStubs() {
6980
// This is required for telemetry to run.
7081
// The default region is arbitrary for now.
7182
// We didn't create an actual instance since it

0 commit comments

Comments
 (0)