I'm trying to use React inside a vscode webview panel. I'd consider myself decently component in react, but I'm used to communicating with the backend via http.
In this project, the vscode extension is essentially the server and all communication must be done by sending events between the two.
//vscode extension (server)
//send message to react app
panel.webview.postMessage({ command: 'refactor' });
//receive messages from react app
panel.webview.onDidReceiveMessage(message => console.log(message))
//webview (react app)
//send message to extension
const vscode = acquireVsCodeApi();
vscode.postMessage({command: 'hello', text: 'world'});
//receive messages from extension
window.addEventListener('message', event => console.log(event))
My brain is struggling to determine the best way to setup 2-way communications.
For example, making a request from the webview to get data for a resource would typically that would look something like this:
public refresh = () => {
this.setState({loading: true}, async () => {
try{
const item = await (await fetch('api/items/3')).json();
this.setState({item});
}catch(e){
this.setState({loading: false, err: e});
}
})
}
But with the Event API this approach obviously doesn't work...
public refresh = () => {
// could register a listener before sending and deactivate it after, but that seems wrong!
vscode.postMessage({command: 'getItems', id: '3'});
}
I see some discussion about registering listeners in the componentDidMount
, but since the event is a generic message
then pretty much every component that communicates with server is going to be listening and then have to filter out what it cares about.
One idea at this point is to create a abstraction layer that allows the messages to be sent and the response awaited:
//track requests and wait for a response
const PENDING_REQUESTS = {};
function vscodeFetch(payload: any){
return new Promise((resolve, reject) => {
let reqId = crypto.getRandomValues(new Uint32Array(4)).join('-');
vscode.postMessage({reqId, payload);
PENDING_REQUESTS[reqId] = {resolve, reject};
});
}
//handle update resolve/reject promises on response
window.addEventListener('message', event => {
const message = event.data;
if(message.reqId){
let promise = PENDING_REQUESTS[message.reqId];
delete PENDING_REQUESTS[message.reqId];
if(message.success){
p.resolve(message.resp);
}else{
p.reject(message.err);
}
}
});
let items = await vscodeFetch({command: 'getItems', data: {id: 3}});
but this kinda feels like I'd be inventing my own protocol (sounds simple until you start considering exceptional behavior like timeouts)!