Merge pull request #261 from gnh1201/bcl-zipfile

Use BCL ZipFile class on Launcher, Add deep inspection for ShadowRoot elements
This commit is contained in:
Namhyeon Go 2025-05-24 12:25:40 +09:00 committed by GitHub
commit 4deddfd8c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 59 additions and 324 deletions

View File

@ -62,7 +62,7 @@ WelsonJS is tailored for developers who need a reliable, lightweight JavaScript
## Specifications
* Built-in transpilers: [TypeScript](https://www.typescriptlang.org/), [Rescript](https://rescript-lang.org/), [CoffeeScript 2](https://coffeescript.org/), [LiveScript](https://livescript.net/)
* **Ready to use on Windows machine immediately. No additional software installation is required.**
* **WelsonJS Launcher**: Manage instances (Like a container), User-defined variable editor, [Microsoft Monaco Editor](https://github.com/microsoft/monaco-editor) and [React](https://react.dev/) (Pre-embedded rich code editor), [Microsoft Copilot](https://copilot.microsoft.com), and [Azure AI Services](https://azure.microsoft.com/en-us/products/ai-services) on the code editor.
* **WelsonJS Launcher**: Manage instances (Like a container), User-defined variable editor, [Microsoft Monaco Editor](https://github.com/microsoft/monaco-editor) and [React](https://react.dev/) (Pre-embedded rich code editor), [Microsoft Copilot](https://copilot.microsoft.com), and [Azure AI Services](https://azure.microsoft.com/en-us/products/ai-services), Network tools (Whois, DNS Query, [Criminal IP CTI](https://www.criminalip.io/)) on the code editor.
* ES5(ECMAScript 5), XML, JSON, YAML compatibility: [core-js](https://github.com/zloirock/core-js), [JSON2.js](https://github.com/douglascrockford/JSON-js), [js-yaml](https://github.com/nodeca/js-yaml)
* HTML5 compatibility on the built-in HTML rendering engine: [html5shiv](https://github.com/aFarkas/html5shiv), [jquery-html5-placeholder-shim](https://github.com/parndt/jquery-html5-placeholder-shim), [Respond](https://github.com/scottjehl/Respond), [selectivizr](https://github.com/keithclark/selectivizr), [ExplorerCanvas](https://github.com/arv/ExplorerCanvas), [Modernizr](https://github.com/Modernizr/Modernizr)
* Classical CSS Frameworks: [cascadeframework](https://github.com/jslegers/cascadeframework), [golden-layout](https://github.com/golden-layout/golden-layout)

View File

@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Security.Principal;
using System.Threading.Tasks;
using System.Windows.Forms;
@ -13,19 +14,16 @@ namespace WelsonJS.Launcher
private string instanceId;
private readonly string entryFileName;
private string scriptName;
private readonly ZipExtractor zipExtractor;
public MainForm()
{
zipExtractor = new ZipExtractor();
entryFileName = "bootstrap.bat";
InitializeComponent();
if (IsInAdministrator())
{
Text = Text + " (Administrator)";
Text += " (Administrator)";
}
notifyIcon1.DoubleClick += OnShow;
@ -111,8 +109,10 @@ namespace WelsonJS.Launcher
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
string filePath = openFileDialog.FileName;
ExtractAndRun(filePath);
Task.Run(() => ExtractAndRun(filePath));
}
DisableUI();
}
}
@ -122,48 +122,33 @@ namespace WelsonJS.Launcher
workingDirectory = Program.GetWorkingDirectory(instanceId);
scriptName = txtUseSpecificScript.Text;
Task.Run(() =>
try
{
try
// try to validate GUID
if (Directory.Exists(workingDirectory))
{
// If exists, delete all
if (Directory.Exists(workingDirectory))
{
Directory.Delete(workingDirectory, true);
}
// try to extact ZIP compressed file
if (zipExtractor.Extract(filePath, workingDirectory))
{
// record the first deploy time
RecordFirstDeployTime(workingDirectory);
// follow the sub-directory
workingDirectory = Program.GetWorkingDirectory(instanceId, true);
// Run the application
Program.RunCommandPrompt(workingDirectory, entryFileName, scriptName, cbUseSpecificScript.Checked, cbInteractiveServiceApp.Checked);
}
else
{
MessageBox.Show("Failed to extract the ZIP file.");
}
}
catch (Exception ex)
{
SafeInvoke(() =>
{
MessageBox.Show("Error: " + ex.Message);
});
throw new InvalidOperationException("GUID validation failed. Directory already exists.");
}
// Enable UI
SafeInvoke(() => {
EnableUI();
});
});
// try to extract ZIP file
ZipFile.ExtractToDirectory(filePath, workingDirectory);
DisableUI();
// record the first deploy time
RecordFirstDeployTime(workingDirectory);
// follow the sub-directory
workingDirectory = Program.GetWorkingDirectory(instanceId, true);
// Run the appliction
Program.RunCommandPrompt(workingDirectory, entryFileName, scriptName, cbUseSpecificScript.Checked, cbInteractiveServiceApp.Checked);
}
catch (Exception ex)
{
SafeInvoke(() => MessageBox.Show($"Extraction failed: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error));
}
// Enable UI
SafeInvoke(() => EnableUI());
}
private void RecordFirstDeployTime(string directory)
@ -188,8 +173,9 @@ namespace WelsonJS.Launcher
WindowsPrincipal wp = new WindowsPrincipal(WindowsIdentity.GetCurrent());
return wp.IsInRole(WindowsBuiltInRole.Administrator);
}
catch
catch (Exception ex)
{
Trace.TraceInformation($"The current user is not an administrator, or the check failed: {ex.Message}");
return false;
}
}
@ -233,7 +219,7 @@ namespace WelsonJS.Launcher
}
catch (Exception ex)
{
MessageBox.Show("Failed to run as administrator: " + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show($"Failed to run as Administrator: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
else

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@ -56,12 +56,12 @@
<ApplicationIcon>favicon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
@ -103,7 +103,6 @@
<DependentUpon>GlobalSettingsForm.cs</DependentUpon>
</Compile>
<Compile Include="ResourceServer.cs" />
<Compile Include="ZipExtractor.cs" />
<EmbeddedResource Include="EnvForm.resx">
<DependentUpon>EnvForm.cs</DependentUpon>
</EmbeddedResource>

View File

@ -1,275 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WelsonJS.Launcher
{
public class ZipExtractor
{
class Extractor
{
public string Name { get; set; }
public string FileName { get; set; }
public string Path { get; set; }
public Func<string, string, string> ExtractCommand { get; set; }
public bool UseCmd { get; set; } = false;
}
private readonly List<Extractor> AvailableExtractors;
public ZipExtractor()
{
// Searches the computer for any known third-party ZIP utilities.
AvailableExtractors = new List<Extractor>{
new Extractor
{
Name = "7z",
FileName = "7z.exe",
ExtractCommand = (src, dest) => $"x \"{src}\" -o\"{dest}\" -y"
},
new Extractor
{
Name = "WinRAR",
FileName = "rar.exe",
ExtractCommand = (src, dest) => $"x -o+ \"{src}\" \"{dest}\\\""
},
new Extractor
{
Name = "PeaZip",
FileName = "peazip.exe",
ExtractCommand = (src, dest) => $"-ext2simple \"{src}\" \"{dest}\""
},
new Extractor
{
Name = "WinZip",
FileName = "wzunzip.exe",
ExtractCommand = (src, dest) => $"-d \"{dest}\" \"{src}\""
},
new Extractor
{
Name = "ALZip",
FileName = "ALZipcon.exe",
ExtractCommand = (src, dest) => $"-x \"{src}\" \"{dest}\""
},
new Extractor
{
Name = "Bandizip",
FileName = "Bandizip.exe",
ExtractCommand = (src, dest) => $"x -o:\"{dest}\" \"{src}\" -y"
},
new Extractor
{
Name = "IZArc",
FileName = "izarce.exe",
ExtractCommand = (src, dest) => $"-d -p\"{dest}\" \"{src}\""
},
new Extractor
{
Name = "jar (Java SDK)",
FileName = "jar.exe",
ExtractCommand = (src, dest) => $"xf \"{src}\"",
UseCmd = true
},
new Extractor
{
Name = "tar (Windows)", // Windows 10 build 17063 or later
FileName = "tar.exe",
ExtractCommand = (src, dest) => $"-xf \"{src}\" -C \"{dest}\""
},
new Extractor
{
Name = "unzip", // Info-ZIP, Cygwin
FileName = "unzip.exe",
ExtractCommand = (src, dest) => $"\"{src}\" -d \"{dest}\" -o"
}
};
Task.Run(() => CheckAvailableExtractors());
}
public bool Extract(string filePath, string workingDirectory)
{
if (!File.Exists(filePath))
throw new FileNotFoundException("The specified file does not exist.", filePath);
if (!filePath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
throw new ArgumentException("The specified file is not a ZIP archive.");
if (!IsValidFile(filePath))
throw new InvalidDataException("The specified file is not a valid ZIP archive.");
Directory.CreateDirectory(workingDirectory);
foreach (var extractor in AvailableExtractors.Where(e => e.Path != null))
{
if (RunProcess(extractor, filePath, workingDirectory, true))
return true;
}
if (ExtractUsingPowerShell(filePath, workingDirectory, true))
return true;
if (ExtractUsingShell(filePath, workingDirectory, true))
return true;
return false;
}
private bool IsValidFile(string filePath)
{
byte[] signature = new byte[4];
using (var fs = File.OpenRead(filePath))
{
if (fs.Length < 4)
return false;
int bytesRead = fs.Read(signature, 0, 4);
if (bytesRead < 4)
return false;
}
return signature.SequenceEqual(new byte[] { 0x50, 0x4B, 0x03, 0x04 });
}
private void CheckAvailableExtractors()
{
var fileNames = AvailableExtractors.Select(e => e.FileName).ToList();
// Check PATH environment variable
var paths = (Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator);
foreach (var dir in paths)
{
foreach (var fileName in fileNames)
{
var path = Path.Combine(dir.Trim(), fileName);
if (File.Exists(path))
{
var index = fileNames.IndexOf(fileName);
var extractor = AvailableExtractors[index];
extractor.Path = path;
}
}
}
// Check common install locations
var programDirs = new[]
{
Path.Combine(Program.GetAppDataPath(), "bin"), // find an extractor from APPDATA directory
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)
};
foreach (var rootDir in programDirs)
{
if (!Directory.Exists(rootDir))
continue;
try
{
foreach (var file in Directory.EnumerateFiles(rootDir, "*", SearchOption.AllDirectories))
{
var fileName = Path.GetFileName(file);
if (fileNames.Contains(fileName))
{
var index = fileNames.IndexOf(fileName);
var extractor = AvailableExtractors[index];
extractor.Path = file;
}
}
}
catch (Exception ex)
{
Trace.TraceInformation($"Ignored file or directory: {ex.Message}");
}
}
}
private bool RunProcess(string execFilePath, string arguments, string workingDirectory, bool useCmd = false, bool showConsole = false)
{
int exitCode = -1;
string fileName = useCmd ? "cmd.exe" : execFilePath;
string adjustedArguments = useCmd ? $"/c \"{execFilePath}\" {arguments}" : arguments;
var psi = new ProcessStartInfo
{
FileName = fileName,
Arguments = adjustedArguments,
WorkingDirectory = workingDirectory,
RedirectStandardOutput = !showConsole,
RedirectStandardError = !showConsole,
UseShellExecute = showConsole,
CreateNoWindow = !showConsole
};
using (var process = new Process { StartInfo = psi, EnableRaisingEvents = true })
{
process.Start();
process.WaitForExit();
exitCode = process.ExitCode;
}
if (exitCode != 0)
Trace.TraceWarning($"{fileName} exit with code {exitCode}");
else
Trace.TraceInformation($"{fileName} finished successfully");
return exitCode == 0;
}
private bool RunProcess(Extractor extractor, string filePath, string workingDirectory, bool showConsole = false)
{
return RunProcess(extractor.Path,
extractor.ExtractCommand(filePath, workingDirectory),
workingDirectory,
extractor.UseCmd,
showConsole
);
}
private bool ExtractUsingPowerShell(string filePath, string workingDirectory, bool showConsole = false)
{
var escapedSrc = filePath.Replace("'", "''");
var escapedDest = workingDirectory.Replace("'", "''");
var script = $"Expand-Archive -LiteralPath '{escapedSrc}' -DestinationPath '{escapedDest}' -Force";
return RunProcess("powershell.exe", $"-NoProfile -Command \"{script}\"", workingDirectory);
}
private bool ExtractUsingShell(string filePath, string workingDirectory, bool showConsole = false)
{
var shellAppType = Type.GetTypeFromProgID("Shell.Application");
if (shellAppType == null)
return false;
dynamic shell = Activator.CreateInstance(shellAppType);
if (shell == null)
return false;
dynamic zip = shell.NameSpace(filePath);
dynamic dest = shell.NameSpace(workingDirectory);
if (zip == null || dest == null)
return false;
int expected = zip.Items().Count;
dest.CopyHere(zip.Items(), 16);
// wait (max 30 s) until all files appear
var sw = Stopwatch.StartNew();
while (dest.Items().Count < expected && sw.Elapsed < TimeSpan.FromSeconds(30))
System.Threading.Thread.Sleep(200);
Marshal.ReleaseComObject(zip);
Marshal.ReleaseComObject(dest);
Marshal.ReleaseComObject(shell);
return dest.Items().Count == expected;
}
}
}

View File

@ -759,6 +759,31 @@ var ChromeObject = function() {
}
};
this.getDeepElementPosition = function(selectors) {
var result = this.getEvaluatedValue('(function() { var rect = __getDocument()' + this.getShadowRootSelector(selectors) + '.getBoundingClientRect(); return [parseInt(rect.left), parseInt(rect.top), parseInt(__getWindow().pageXOffset + rect.left), parseInt(__getWindow().pageYOffset + rect.top), parseInt(rect.width), parseInt(rect.height)].join(","); })();');
var pos = result.split(',');
if (pos.length == 6) {
return {
"x": parseInt(pos[0]),
"y": parseInt(pos[1]),
"a": parseInt(pos[2]),
"b": parseInt(pos[3]),
"w": parseInt(pos[4]),
"h": parseInt(pos[5])
};
} else {
return {
"x": -1,
"y": -1,
"a": -1,
"b": -1,
"g": -1,
"d": -1
};
}
};
this.getElementPositionByText = function(selector, searchText) {
var result;
var pos = -1;
@ -1423,7 +1448,7 @@ exports.startDebugInPrivate = function(url, proxy, profileName, debuggingPort, i
;
};
exports.VERSIONINFO = "Chrome Web Browser Debugging Interface (chrome.js) version 0.4.15";
exports.VERSIONINFO = "Chrome Web Browser Debugging Interface (chrome.js) version 0.4.16";
exports.AUTHOR = "abuse@catswords.net";
exports.global = global;
exports.require = global.require;