Skip to content

Commit 4f9aef9

Browse files
authored
remote: add resource provider api for embedders (microsoft#181969)
Initially I was looking at VS Code providing some service worker code to plop in in an opaque way for embedders. But that was convoluted both for an API and consumers. For example, if we provided an agnostic handler for this, it would clash with the Workbox types (a popular service worker toolkit we use in vscode.dev) since they don't expose the original events. Instead, take a more minimal approach, allowing embedders to hook in themselves and provide a route that they want to use for the 'proxy'. This was quite easy to implement as a variation in vscode.dev. Example of extension icons loading: ![](https://memes.peet.io/img/23-05-8da09054-2717-4fca-b9b9-c605718f22f0.png)
1 parent 31d41d7 commit 4f9aef9

File tree

4 files changed

+100
-2
lines changed

4 files changed

+100
-2
lines changed

src/vs/platform/remote/browser/remoteAuthorityResolverService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,9 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
104104
_setResolvedAuthority(resolvedAuthority: ResolvedAuthority, options?: ResolvedOptions): void {
105105
if (this._resolveAuthorityRequests.has(resolvedAuthority.authority)) {
106106
const request = this._resolveAuthorityRequests.get(resolvedAuthority.authority)!;
107+
// For non-websocket types, it's expected the embedder passes a `remoteResourceProvider`
108+
// which is wrapped to a `IResourceUriProvider` and is not handled here.
107109
if (resolvedAuthority.connectTo.type === RemoteConnectionType.WebSocket) {
108-
// todo@connor4312 need to implement some kind of loopback for ext host based messaging
109110
RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.connectTo.host, resolvedAuthority.connectTo.port);
110111
}
111112
if (resolvedAuthority.connectionToken) {

src/vs/workbench/browser/web.api.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,15 @@ export interface IWorkbenchConstructionOptions {
173173
*/
174174
readonly editSessionId?: string;
175175

176+
/**
177+
* Resource delegation handler that allows for loading of resources when
178+
* using remote resolvers.
179+
*
180+
* This is exclusive with {@link resourceUriProvider}. `resourceUriProvider`
181+
* should be used if a {@link webSocketFactory} is used, and will be preferred.
182+
*/
183+
readonly remoteResourceProvider?: IRemoteResourceProvider;
184+
176185
//#endregion
177186

178187

@@ -760,3 +769,38 @@ export interface IDevelopmentOptions {
760769
*/
761770
readonly enableSmokeTestDriver?: boolean;
762771
}
772+
773+
/**
774+
* Utility provided in the {@link WorkbenchOptions} which allows loading resources
775+
* when remote resolvers are used in the web.
776+
*/
777+
export interface IRemoteResourceProvider {
778+
/**
779+
* Path the workbench should delegate requests to. The embedder should
780+
* install a service worker on this path and emit {@link onDidReceiveRequest}
781+
* events when requests come in for that path.
782+
*/
783+
readonly path: string;
784+
785+
/**
786+
* Event that should fire when requests are made on the {@link pathPrefix}.
787+
*/
788+
readonly onDidReceiveRequest: Event<IRemoteResourceRequest>;
789+
}
790+
791+
/**
792+
* todo@connor4312: this may eventually gain more properties like method and
793+
* headers, but for now we only deal with GET requests.
794+
*/
795+
export interface IRemoteResourceRequest {
796+
/**
797+
* Request URI. Generally will begin with the current
798+
* origin and {@link IRemoteResourceProvider.pathPrefix}.
799+
*/
800+
uri: URI;
801+
802+
/**
803+
* A method called by the editor to issue a response to the request.
804+
*/
805+
respondWith(statusCode: number, body: Uint8Array): void;
806+
}

src/vs/workbench/browser/web.main.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
8888
import { IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces';
8989
import { UserDataProfileInitializer } from 'vs/workbench/services/userDataProfile/browser/userDataProfileInit';
9090
import { UserDataSyncInitializer } from 'vs/workbench/services/userDataSync/browser/userDataSyncInit';
91+
import { BrowserRemoteResourceLoader } from 'vs/workbench/services/remote/browser/browserRemoteResourceHandler';
9192
import { BufferLogger } from 'vs/platform/log/common/bufferLog';
9293
import { FileLoggerService } from 'vs/platform/log/common/fileLog';
9394

@@ -275,7 +276,9 @@ export class BrowserMain extends Disposable {
275276

276277
// Remote
277278
const connectionToken = environmentService.options.connectionToken || getCookieValue(connectionTokenCookieName);
278-
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(!environmentService.expectsResolverExtension, connectionToken, this.configuration.resourceUriProvider, productService, logService);
279+
const remoteResourceLoader = this.configuration.remoteResourceProvider ? new BrowserRemoteResourceLoader(fileService, this.configuration.remoteResourceProvider) : undefined;
280+
const resourceUriProvider = this.configuration.resourceUriProvider ?? remoteResourceLoader?.getResourceUriProvider();
281+
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(!environmentService.expectsResolverExtension, connectionToken, resourceUriProvider, productService, logService);
279282
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
280283

281284
// Signing
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { VSBuffer } from 'vs/base/common/buffer';
7+
import { Disposable } from 'vs/base/common/lifecycle';
8+
import { URI, UriComponents } from 'vs/base/common/uri';
9+
import { FileOperationError, FileOperationResult, IFileContent, IFileService } from 'vs/platform/files/common/files';
10+
import { IRemoteResourceProvider, IResourceUriProvider } from 'vs/workbench/browser/web.api';
11+
12+
export class BrowserRemoteResourceLoader extends Disposable {
13+
constructor(
14+
@IFileService fileService: IFileService,
15+
private readonly provider: IRemoteResourceProvider,
16+
) {
17+
super();
18+
19+
this._register(provider.onDidReceiveRequest(async request => {
20+
let uri: UriComponents;
21+
try {
22+
uri = JSON.parse(decodeURIComponent(request.uri.query));
23+
} catch {
24+
return request.respondWith(404, new Uint8Array());
25+
}
26+
27+
let content: IFileContent;
28+
try {
29+
content = await fileService.readFile(URI.from(uri, true));
30+
} catch (e) {
31+
const str = VSBuffer.fromString(e.message).buffer;
32+
if (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
33+
return request.respondWith(404, str);
34+
} else {
35+
return request.respondWith(500, str);
36+
}
37+
}
38+
39+
request.respondWith(200, content.value.buffer);
40+
}));
41+
}
42+
43+
public getResourceUriProvider(): IResourceUriProvider {
44+
const baseUri = URI.parse(document.location.href);
45+
return uri => baseUri.with({
46+
path: this.provider.path,
47+
query: JSON.stringify(uri),
48+
});
49+
}
50+
}

0 commit comments

Comments
 (0)