Skip to content

Commit db53c60

Browse files
author
Tal Hadad
committed
fix webview on local html files by sending the HTML files content via TCP
1 parent 9f300ee commit db53c60

4 files changed

Lines changed: 74 additions & 22 deletions

File tree

R/session/vsc.R

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -878,8 +878,30 @@ show_webview <- function(url, title, ..., viewer) {
878878
request_browser(url = url, title = title, ..., viewer = FALSE)
879879
} else if (file.exists(url)) {
880880
file <- normalizePath(url, "/", mustWork = TRUE)
881-
# TODO: On TCP connection, need to send file content via TCP and display it
882-
request("webview", file = file, title = title, viewer = viewer, ...)
881+
file_basename <- basename(file)
882+
dir <- dirname(file)
883+
lib_dir <- file.path(dir, "lib")
884+
if (!is.na(request_tcp_connection) && file_basename == "index.html" && file.exists(lib_dir)) {
885+
# pass detailed content via TCP, instead of just give a path to HTML
886+
lib_dir_absolute <- normalizePath(path.expand(lib_dir), "/", mustWork = TRUE)
887+
lib_files_path_relative <- file.path(
888+
"lib",
889+
list.files(lib_dir_absolute, all.files = TRUE, recursive = TRUE)
890+
)
891+
files_path_relative <- c(lib_files_path_relative, file_basename)
892+
893+
files_content_base64 <- setNames(
894+
lapply(files_path_relative, function(file_path) {
895+
raw_content <- readr::read_file_raw(file.path(dir, file_path))
896+
jsonlite::base64_enc(raw_content)
897+
}),
898+
files_path_relative
899+
)
900+
request("webview", file = file_basename, files_content_base64 = files_content_base64,
901+
title = title, viewer = viewer, ...)
902+
} else {
903+
request("webview", file = file, title = title, viewer = viewer, ...)
904+
}
883905
} else {
884906
stop("File not exists")
885907
}

src/liveShare/shareSession.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export type IRequest = {
2727
type?: string;
2828
title?: string;
2929
file?: string;
30+
files_content_base64?: Record<string, string>;
3031
viewer?: string;
3132
plot?: string;
3233
format?: string;
@@ -152,7 +153,7 @@ export async function updateGuestRequest(file: string, force: boolean = false):
152153
}
153154
case 'webview': {
154155
if (request.file && request.title && request.viewer !== undefined) {
155-
await showWebView(request.file, request.title, request.viewer);
156+
await showWebView(request.file, request.files_content_base64, request.title, request.viewer);
156157
}
157158
break;
158159
}

src/session.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ import * as fs from 'fs-extra';
44
import * as os from 'os';
55
import * as path from 'path';
66
import { Agent } from 'http';
7-
import * as tmp from 'tmp';
87
import { AddressInfo, Server, Socket } from 'node:net';
98
import { PromiseSocket } from 'promise-socket';
109
import fetch from 'node-fetch';
1110
import { commands, StatusBarItem, Uri, ViewColumn, Webview, window, workspace, env, WebviewPanelOnDidChangeViewStateEvent, WebviewPanel } from 'vscode';
1211

1312
import { runTextInTerm } from './rTerminal';
1413
import { FSWatcher } from 'fs-extra';
15-
import { config, createWaiterForInvoker, readContent, setContext, UriIcon } from './util';
14+
import { config, createTempDir2, createTempFile, createWaiterForInvoker, readContent, setContext, UriIcon } from './util';
1615
import { purgeAddinPickerItems, dispatchRStudioAPICall } from './rstudioapi';
1716

1817
import { IRequest } from './liveShare/shareSession';
@@ -383,12 +382,29 @@ export function openExternalBrowser(): void {
383382
}
384383
}
385384

386-
export async function showWebView(file: string, title: string, viewer: string | boolean): Promise<void> {
385+
export async function showWebView(file: string, files_content_base64: Record<string, string> | undefined,
386+
title: string, viewer: string | boolean): Promise<void> {
387387
console.info(`[showWebView] file: ${file}, viewer: ${viewer.toString()}`);
388388
if (viewer === false) {
389389
void env.openExternal(Uri.file(file));
390390
} else {
391-
const dir = path.dirname(file);
391+
let dir: string;
392+
if (files_content_base64 !== undefined) {
393+
dir = (await createTempDir2()).path;
394+
const subdirs = new Set(Object.keys(files_content_base64).map((relativePath) => path.dirname(relativePath)));
395+
subdirs.delete('');
396+
subdirs.delete('.');
397+
await Promise.all(
398+
Array.from(subdirs).map((subdir) => fs.mkdir(path.join(dir, subdir), { recursive: true }))
399+
);
400+
await Promise.all(Object.entries(files_content_base64).map(async ([realtivePath, contentBase64]) => {
401+
const arrayData = Buffer.from(contentBase64, 'base64');
402+
return fs.writeFile(path.join(dir, realtivePath), arrayData);
403+
}));
404+
file = path.join(dir, file);
405+
} else {
406+
dir = path.dirname(file);
407+
}
392408
const webviewDir = extensionContext.asAbsolutePath('html/session/webview/');
393409
const panel = window.createWebviewPanel('webview', title,
394410
{
@@ -945,18 +961,6 @@ function startIncomingRequestServer(sessionStatusBarItem: StatusBarItem) {
945961
return server;
946962
}
947963

948-
const create_tmp_file: (options: tmp.FileOptions) => Promise<{ name: string, fd: number, removeCallback: () => void }> =
949-
(options) => new Promise((resolve, reject) => {
950-
tmp.file(options, (err, name, fd, removeCallback) => {
951-
if (err) {
952-
reject(err);
953-
} else {
954-
resolve({ name, fd, removeCallback });
955-
}
956-
});
957-
}
958-
);
959-
960964
export async function processRequest(request: ISessionRequest, socket: Socket | null, sessionStatusBarItem: StatusBarItem) {
961965
switch (request.command) {
962966
case 'help': {
@@ -1033,7 +1037,7 @@ export async function processRequest(request: ISessionRequest, socket: Socket |
10331037
}
10341038
case 'webview': {
10351039
if (request.file && request.title && request.viewer !== undefined) {
1036-
await showWebView(request.file, request.title, request.viewer);
1040+
await showWebView(request.file, request.files_content_base64, request.title, request.viewer);
10371041
}
10381042
break;
10391043
}
@@ -1051,7 +1055,7 @@ export async function processRequest(request: ISessionRequest, socket: Socket |
10511055
}
10521056

10531057
if (request.plot_base64) {
1054-
const { name: filePath, fd } = await create_tmp_file({ postfix: '.png' });
1058+
const { name: filePath, fd } = await createTempFile({ postfix: '.png' });
10551059
const arrayData = Buffer.from(request.plot_base64, 'base64');
10561060
await fs.writeFile(fd, arrayData);
10571061
showPlot(filePath);

src/util.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { existsSync, PathLike, readFile } from 'fs-extra';
44
import * as fs from 'fs';
5+
import * as tmp from 'tmp';
56
import winreg = require('winreg');
67
import * as path from 'path';
78
import * as vscode from 'vscode';
@@ -663,4 +664,28 @@ export function createWaiterForInvoker() {
663664
});
664665

665666
return { invoker, waiter };
666-
}
667+
}
668+
669+
export const createTempFile: (options: tmp.FileOptions) => Promise<{ name: string, fd: number, removeCallback: () => void }> =
670+
(options) => new Promise((resolve, reject) => {
671+
tmp.file(options, (err, name, fd, removeCallback) => {
672+
if (err) {
673+
reject(err);
674+
} else {
675+
resolve({ name, fd, removeCallback });
676+
}
677+
});
678+
}
679+
);
680+
681+
export const createTempDir2: () => Promise<{ path: string, removeCallback: () => void }> =
682+
() => new Promise((resolve, reject) => {
683+
tmp.dir((err, path, removeCallback) => {
684+
if (err) {
685+
reject(err);
686+
} else {
687+
resolve({ path, removeCallback });
688+
}
689+
});
690+
}
691+
);

0 commit comments

Comments
 (0)