How to intercept NodeRequestService?

In the plugin of ovsx-client there is

 protected async buildSearchUri(param?: VSXSearchParam): Promise<string> {
        return this.buildUri('api/-/search', param);
    }

    protected buildQueryUri(param?: VSXQueryParam): string {
        return this.buildUri('api/-/query', param);
    }

These two requests, at present I want to add userId and token parameters to the request, how can I achieve this?

Hey @wenqi,

you should be able to simply override the NodeRequestService#processOptions method to add your own headers in there (assuming you want to transmit the information using headers). If you want to add the information to the URL, you’ll have to override the OVSXClient class.

Note that there are always two request services: One for the backend (NodeRequestService) and one for the frontend (BrowserRequestService).

@injectable()
export class OverrideProcessOptionsBackendContribution implements BackendApplicationContribution {
  async configure() {
    (NodeRequestService as any).processOptions = async function() {
      // to do something
    }
  }
}
Does this cover it? It’s hard to believe that I’m just new to theia, and I’m not familiar with many of them. Can you write a demo?

@wenqi You can override an existing service of Theia using the following way:

@injectable()
export class CustomNodeRequestService extends NodeRequestService {
  async processOptions() {...}
}

You’ll also need to rebind it in your backend module:

export new ContainerModule((bind, unbind, isBound, rebind) => {
  rebind(NodeRequestService).to(CustomNodeRequestService).inSingletonScope();
});

There is a problem here,
but I can’t get the ovsx-client plugin to use the CustomNodeRequestService

Yeah, I just noticed that you’ll actually need to rebind the RequestService symbol instead of the class. Anyway, the OVSXClient lives once in the frontend and once in the backend, which is why you’ll need to create an override of the XHRBrowserRequestService as well:

index.js:97 Error: Ambiguous match found for serviceIdentifier: Symbol(RequestService)
    at _validateActiveBindingCount (planner.ts:127:23)
    at _getActiveBindings (planner.ts:91:5)
    at _createSubRequests (planner.ts:163:26)
    at planner.ts:197:17
    at Array.forEach (<anonymous>)
    at planner.ts:196:26
    at Array.forEach (<anonymous>)
    at _createSubRequests (planner.ts:167:20)
    at planner.ts:197:17
    at Array.forEach (<anonymous>)
start	@	index.js:97
Promise.then (async)		
(anonymous)	@	index.js:83
Promise.then (async)		
./src-gen/frontend/index.js	@	index.js:30
__webpack_require__	@	bootstrap:19
(anonymous)	@	startup:4
(anonymous)	@	startup:4

I created a new request.ts,

import { injectable } from '@theia/core/shared/inversify';
import { ProxyingBrowserRequestService } from '@theia/core/lib/browser/request/browser-request-service'
import { RequestConfiguration, RequestContext, RequestOptions, CancellationToken } from '@theia/request';

@injectable()
export class XHRBrowserRequestService extends ProxyingBrowserRequestService {

    protected authorization?: string;

    override configure(config: RequestConfiguration): Promise<void> {
        if (config.proxyAuthorization !== undefined) {
            this.authorization = config.proxyAuthorization;
        }
        return super.configure(config);
    }

    override async request(options: RequestOptions, token?: CancellationToken): Promise<RequestContext> {
        try {
            const xhrResult = await this.xhrRequest(options, token);
            const statusCode = xhrResult.res.statusCode ?? 200;
            if (statusCode >= 400) {
                // We might've been blocked by the firewall
                // Try it through the backend request service
                return super.request(options);
            }
            return xhrResult;
        } catch {
            return super.request(options);
        }
    }

    protected xhrRequest(options: RequestOptions, token?: CancellationToken): Promise<RequestContext> {
        const authorization = this.authorization || options.proxyAuthorization;
        if (authorization) {
            options.headers = {
                ...(options.headers || {}),
                'Proxy-Authorization': authorization
            };
        }

        const xhr = new XMLHttpRequest();
        return new Promise<RequestContext>((resolve, reject) => {

            xhr.open(options.type || 'GET', options.url || '', true, options.user, options.password);
            this.setRequestHeaders(xhr, options);

            xhr.responseType = 'arraybuffer';
            xhr.onerror = () => reject(new Error(xhr.statusText && ('XHR failed: ' + xhr.statusText) || 'XHR failed'));
            xhr.onload = () => {
                resolve({
                    url: options.url,
                    res: {
                        statusCode: xhr.status,
                        headers: this.getResponseHeaders(xhr)
                    },
                    buffer: new Uint8Array(xhr.response)
                });
            };
            xhr.ontimeout = e => reject(new Error(`XHR timeout: ${options.timeout}ms`));

            if (options.timeout) {
                xhr.timeout = options.timeout;
            }

            xhr.send(options.data);

            token?.onCancellationRequested(() => {
                xhr.abort();
                reject();
            });
        });
    }

    protected setRequestHeaders(xhr: XMLHttpRequest, options: RequestOptions): void {
        if (options.headers) {
            for (const k of Object.keys(options.headers)) {
                switch (k) {
                    case 'User-Agent':
                    case 'Accept-Encoding':
                    case 'Content-Length':
                        // unsafe headers
                        continue;
                }
                xhr.setRequestHeader(k, options.headers[k]);
            }
        }
        xhr.setRequestHeader('test', '111111');
    }

    protected getResponseHeaders(xhr: XMLHttpRequest): { [name: string]: string } {
        const headers: { [name: string]: string } = {};
        for (const line of xhr.getAllResponseHeaders().split(/\r\n|\n|\r/g)) {
            if (line) {
                const idx = line.indexOf(':');
                headers[line.substring(0, idx).trim().toLowerCase()] = line.substring(idx + 1).trim();
            }
        }
        return headers;
    }
}

then then i have this file frontend-module.ts

    bind(RequestService).to(XHRBrowserRequestService).inSingletonScope();

I rebuild and got the above error

@wenqi you need to rebind, not bind. Adding another binding makes the binding ambiguous, thus leading to the error you are experiencing. See my answer above How to intercept NodeRequestService? - #4 by msujew