Skip to content

Commit

Permalink
0.0.9 - Add support for URL encoded requests
Browse files Browse the repository at this point in the history
  • Loading branch information
Darien Pardinas Diaz authored and dpar39 committed Aug 22, 2024
1 parent 34c40a1 commit e6a5a2f
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 34 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## [0.0.9]

- Add support for processing URL encoded requests in the form `?command=value&args=urlencoded-of-json-representation-of-args`.

## [0.0.8]

- Assign the remote control port based on a hash of the path to the workspace or open folders if such port is available.
Expand Down
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,20 @@ When you install this extension, it will automatically try to start a HTTP serve
![status bar listening message](assets/statusbar-item.png)


Once installed, you can execute vscode `commands` by making HTTP requests. Here are few examples using `curl`, assuming VSCode is listening on port `37100`:
Once installed, you can execute vscode `commands` by making HTTP requests. The HTTP verb is currently ignored. Here are few examples using `curl`, assuming VSCode is listening on port `37100`:

```bash
# Create a new terminal
curl -X POST http://localhost:37100 -d '{"command":"workbench.action.terminal.new"}'
curl http://localhost:37100 -d '{"command":"workbench.action.terminal.new"}'
# or curl http://localhost:37100/?command=workbench.action.terminal.new

# Run `pwd` in the currently active terminal
curl -X POST http://localhost:37100 -d '{"command":"custom.runInTerminal", "args": ["pwd"]}'
curl http://localhost:37100 -d '{"command":"custom.runInTerminal", "args": ["pwd"]}'
# or curl http://localhost:37100/?command=custom.runInTerminal&args=%5B%22pwd%22%5D

# Kill all terminals
curl -X POST http://localhost:37100 -d '{"command":"workbench.action.terminal.killAll"}'
curl http://localhost:37100 -d '{"command":"workbench.action.terminal.killAll"}'
# or curl http://localhost:37100/?command=workbench.action.terminal.killAll
```

All requests are expected to be in a JSON HTTP request body in the form:
Expand All @@ -60,6 +63,7 @@ All requests are expected to be in a JSON HTTP request body in the form:
"args": ["<arg1>", "<arg2>", "...", "<argN>"]
}
```
or URL encoded as `?command=<command-id>&args=<url-encoded-of-json-string-of-args>`.

Some VSCode commands expect VSCode's defined types such as [Range](https://code.visualstudio.com/api/references/vscode-api#Range), [Uri](https://code.visualstudio.com/api/references/vscode-api#Uri), [Position](https://code.visualstudio.com/api/references/vscode-api#Position) and [Location](https://code.visualstudio.com/api/references/vscode-api#Location). To accommodate for those, such arguments can be passed as a special types, see the example below which effectively invokes `editor.action.goToLocations` with `Uri`, `Position` and an array of `Location`s:

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "This extension allows you to remotely control Visual Studio Code via a REST endpoint, taking automation to the next level.",
"publisher": "dpar39",
"license": "MIT",
"version": "0.0.8",
"version": "0.0.9",
"engines": {
"vscode": "^1.55.0"
},
Expand Down
51 changes: 36 additions & 15 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Logger } from "./services/logger";
import { IncomingMessage, ServerResponse } from "http";
import * as http from "http";
import { AddressInfo } from "net";
import { ControlRequest } from "./models/controlRequest";
import { processRemoteControlRequest } from "./services/requestProcessor";

let server: http.Server;
Expand Down Expand Up @@ -61,27 +60,49 @@ const startHttpServer = async (
}
}

const endBadRequest = (err: any, res: ServerResponse) => {
res.statusCode = 400;
const errStringJson = JSON.stringify(err, Object.getOwnPropertyNames(err));
res.write(errStringJson);
res.end();
Logger.error(errStringJson);
};

const processRequest = (cmd: string, args: string[], res: ServerResponse) => {
processRemoteControlRequest(cmd, args)
.then((data) => {
res.setHeader("Content-Type", "application/json");
res.write(JSON.stringify(data || null));
res.end();
})
.catch((err) => endBadRequest(err, res));
};

const requestHandler = (req: IncomingMessage, res: ServerResponse) => {
let body = "";
let controlCommand: any = {};
if (req.url && req.url.indexOf("?") >= 0) {
const url = new URL(req.url, `http://${req.headers.host}/`);
const queryParams = new URLSearchParams(url.search);
try {
const cmd = queryParams.get("command");
const args = queryParams.has("args")
? JSON.parse(decodeURIComponent(queryParams.get("args")!))
: [];
Logger.info(`Remote request command=${cmd}, args=${args}`);
processRequest(cmd!, args, res);
} catch (err) {
endBadRequest(err, res);
}
}

req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () => {
Logger.info(`Remote request payload: ${body}`);
const reqData = JSON.parse(body);
processRemoteControlRequest(reqData as ControlRequest)
.then((data) => {
res.setHeader("Content-Type", "application/json");
res.write(JSON.stringify(data || null));
res.end();
})
.catch((err) => {
res.statusCode = 400;
const errStringJson = JSON.stringify(err, Object.getOwnPropertyNames(err));
res.write(errStringJson);
res.end();
Logger.error(errStringJson);
});
const reqData = body ? JSON.parse(body) : controlCommand;
processRequest(reqData.command, reqData.args || [], res);
});
};
// Start the HTTP server
Expand Down
4 changes: 0 additions & 4 deletions src/models/controlRequest.ts

This file was deleted.

6 changes: 1 addition & 5 deletions src/services/requestProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as vscode from "vscode";
import * as path from "path";

import { quickPick } from "./quickPick";
import { ControlRequest } from "../models/controlRequest";

function createObject(arg: any): any {
if (typeof arg === "object" && arg.hasOwnProperty("__type__")) {
Expand Down Expand Up @@ -34,10 +33,7 @@ function createArguments(args?: any[]): any[] {
return args2;
}

export async function processRemoteControlRequest(requestObject: ControlRequest): Promise<any> {
const command = requestObject.command;
const args = createArguments(requestObject.args);

export async function processRemoteControlRequest(command: string, args: any[]): Promise<any> {
if (command === "custom.runInTerminal") {
const terminal = vscode.window.activeTerminal;
if (terminal) {
Expand Down
2 changes: 1 addition & 1 deletion src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ suite("Extension Test Suite", () => {
assert(ws.uri.startsWith("file://"));
assert(ws.uri.endsWith("/workspace1"));

const workspaceFile = await makeRequest("custom.workspaceFile") as any;
const workspaceFile = await makeRequest("custom.workspaceFile", undefined, undefined, false) as any;
assert(workspaceFile === null); // no workspace file
});

Expand Down
18 changes: 14 additions & 4 deletions src/test/suite/sendPostRequest.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import * as http from "http";
import { getListeningPort } from "../../extension";

export async function makeRequest(command: string, args: any[] = [], port: number = 0) {
export async function makeRequest(
command: string,
args: any[] = [],
port: number = 0,
urlEncoded = true
) {
return new Promise((resolve, reject) => {
const path = urlEncoded
? `/?command=${command}&args=${encodeURIComponent(JSON.stringify(args))}`
: '/';
const req = http.request(
{
method: "POST",
hostname: "localhost",
port: port || getListeningPort(),
path: "/",
path: path
},
(res) => {
const chunks: any[] = [];
Expand All @@ -20,8 +28,10 @@ export async function makeRequest(command: string, args: any[] = [], port: numbe
}
);
req.on("error", reject);
const body = JSON.stringify({ command: command, args: args });
req.write(body);
if (!urlEncoded) {
const body = JSON.stringify({ command: command, args: args });
req.write(body);
}
req.end();
});
}

0 comments on commit e6a5a2f

Please sign in to comment.