Merge pull request #400 from gnh1201/dev

All updates until 2026-04-12
This commit is contained in:
Namhyeon Go 2026-04-12 23:08:03 +09:00 committed by GitHub
commit 93ec862f81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 30190 additions and 146 deletions

View File

@ -0,0 +1,85 @@
using log4net;
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace WelsonJS.Launcher.ApiEndpoints
{
public class JsonRpc2 : IApiEndpoint
{
private readonly ResourceServer Server;
private readonly HttpClient _httpClient;
private readonly ILog _logger;
private const string Prefix = "jsonrpc2";
public JsonRpc2(ResourceServer server, HttpClient httpClient, ILog logger)
{
Server = server;
_httpClient = httpClient;
_logger = logger;
}
public bool CanHandle(HttpListenerContext context, string path)
{
if (context == null)
return false;
if (!string.Equals(context.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase))
return false;
if (path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase) == false)
return false;
string contentType = context.Request.ContentType?.ToLowerInvariant() ?? string.Empty;
if (!(contentType.StartsWith("application/json")
|| contentType.StartsWith("application/json-rpc")
|| contentType.StartsWith("application/jsonrpc")
|| contentType.StartsWith("text/json")))
return false;
if (!context.Request.HasEntityBody)
return false;
return true;
}
public async Task HandleAsync(HttpListenerContext context, string path)
{
string body;
try
{
using (var input = context.Request.InputStream)
using (var reader = new System.IO.StreamReader(input, System.Text.Encoding.UTF8))
{
body = await reader.ReadToEndAsync();
}
_logger.Debug($"[JsonRpc2] Request body received ({body.Length} bytes)");
}
catch (Exception ex)
{
_logger.Error("[JsonRpc2] Failed to read request body", ex);
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.Response.Close();
return;
}
var dispatcher = new JsonRpc2Dispatcher(_logger);
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(300)))
{
await dispatcher.HandleAsync(
body,
async (method, ser, id, ct) =>
{
throw new NotImplementedException();
},
cts.Token);
}
}
}
}

View File

@ -1,4 +1,9 @@
using System; // JsNative.cs
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
// https://github.com/gnh1201/welsonjs
//
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace WelsonJS.Launcher namespace WelsonJS.Launcher

View File

@ -44,7 +44,7 @@ namespace WelsonJS.Launcher
// Create slot and parse // Create slot and parse
// Using Object.create(null) for a clean dictionary without prototype. // Using Object.create(null) for a clean dictionary without prototype.
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append("(function(){var S=globalThis.__WJ_STORE;"); sb.Append("(function(){var S=globalThis.__WJS__;");
sb.Append("S[").Append(id.ToString(CultureInfo.InvariantCulture)).Append("]=JSON.parse(").Append(Q(json)).Append(");"); sb.Append("S[").Append(id.ToString(CultureInfo.InvariantCulture)).Append("]=JSON.parse(").Append(Q(json)).Append(");");
sb.Append("return '1';})()"); sb.Append("return '1';})()");
string r = _core.EvaluateToString(sb.ToString()); string r = _core.EvaluateToString(sb.ToString());
@ -59,7 +59,7 @@ namespace WelsonJS.Launcher
public void Unload(int id) public void Unload(int id)
{ {
EnsureStore(); EnsureStore();
string script = "(function(){var S=globalThis.__WJ_STORE; delete S[" + id.ToString(CultureInfo.InvariantCulture) + "]; return '1';})()"; string script = "(function(){var S=globalThis.__WJS__; delete S[" + id.ToString(CultureInfo.InvariantCulture) + "]; return '1';})()";
_core.EvaluateToString(script); _core.EvaluateToString(script);
} }
@ -70,7 +70,7 @@ namespace WelsonJS.Launcher
{ {
if (json == null) throw new ArgumentNullException("json"); if (json == null) throw new ArgumentNullException("json");
EnsureStore(); EnsureStore();
string script = "(function(){var S=globalThis.__WJ_STORE; S[" + id.ToString(CultureInfo.InvariantCulture) + "]=JSON.parse(" + Q(json) + "); return '1';})()"; string script = "(function(){var S=globalThis.__WJS__; S[" + id.ToString(CultureInfo.InvariantCulture) + "]=JSON.parse(" + Q(json) + "); return '1';})()";
_core.EvaluateToString(script); _core.EvaluateToString(script);
} }
@ -81,7 +81,7 @@ namespace WelsonJS.Launcher
{ {
EnsureStore(); EnsureStore();
space = Clamp(space, 0, 10); space = Clamp(space, 0, 10);
string script = "(function(){var v=globalThis.__WJ_STORE[" + id.ToString(CultureInfo.InvariantCulture) + "]; return JSON.stringify(v,null," + space.ToString(CultureInfo.InvariantCulture) + ");})()"; string script = "(function(){var v=globalThis.__WJS__[" + id.ToString(CultureInfo.InvariantCulture) + "]; return JSON.stringify(v,null," + space.ToString(CultureInfo.InvariantCulture) + ");})()";
return _core.EvaluateToString(script); return _core.EvaluateToString(script);
} }
@ -96,13 +96,13 @@ namespace WelsonJS.Launcher
string jsPath = BuildJsPath(path); string jsPath = BuildJsPath(path);
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append("(function(){var v=globalThis.__WJ_STORE[") sb.Append("(function(){var v=globalThis.__WJS__[")
.Append(id.ToString(CultureInfo.InvariantCulture)) .Append(id.ToString(CultureInfo.InvariantCulture))
.Append("];var p=").Append(jsPath).Append(";"); .Append("];var p=").Append(jsPath).Append(";");
sb.Append("for(var i=0;i<p.length;i++){var k=p[i];"); sb.Append("for(var i=0;i<p.length;i++){var k=p[i];");
sb.Append("if(Array.isArray(v) && typeof k==='number'){ v=v[k]; }"); sb.Append("if(Array.isArray(v) && typeof k==='number'){ v=v[k]; }");
sb.Append("else { v=(v==null?null:v[k]); }}"); sb.Append("else { v=(v==null?null:v[k]); }}");
sb.Append("return JSON.stringify(v);})()"); sb.Append("return (typeof v==='object'?JSON.stringify(v):String(v));})()");
return _core.EvaluateToString(sb.ToString()); return _core.EvaluateToString(sb.ToString());
} }
@ -110,11 +110,11 @@ namespace WelsonJS.Launcher
private void EnsureStore() private void EnsureStore()
{ {
if (_storeReady) return; if (_storeReady) return;
// Create a single global dictionary: globalThis.__WJ_STORE // Create a single global dictionary: globalThis.__WJS__
// Object.create(null) prevents prototype pollution and accidental hits on built-ins. // Object.create(null) prevents prototype pollution and accidental hits on built-ins.
string script = string script =
"(function(){var g=globalThis||this;" + "(function(){var g=globalThis||this;" +
"if(!g.__WJ_STORE){Object.defineProperty(g,'__WJ_STORE',{value:Object.create(null),writable:false,enumerable:false,configurable:false});}" + "if(!g.__WJS__){Object.defineProperty(g,'__WJS__',{value:Object.create(null),writable:false,enumerable:false,configurable:false});}" +
"return '1';})()"; "return '1';})()";
string r = _core.EvaluateToString(script); string r = _core.EvaluateToString(script);
_storeReady = (r == "1"); _storeReady = (r == "1");

View File

@ -0,0 +1,85 @@
// JsonRpc2Dispatcher.cs
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
// https://github.com/gnh1201/welsonjs
//
using log4net;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace WelsonJS.Launcher
{
public sealed class JsonRpc2Request
{
public string Version;
public string Method;
}
public sealed class JsonRpc2Exception : Exception
{
public JsonRpc2Exception(string message) : base(message) { }
public JsonRpc2Exception(string message, Exception inner) : base(message, inner) { }
}
public sealed class JsonRpc2Dispatcher
{
private readonly ILog _logger;
public JsonRpc2Dispatcher(ILog logger)
{
_logger = logger;
}
public async Task<string> HandleAsync(
string requestBody,
Func<string, JsSerializer, int, CancellationToken, Task<string>> dispatchMethodAsync,
CancellationToken ct)
{
if (string.IsNullOrEmpty(requestBody))
throw new JsonRpc2Exception("Empty request body");
if (dispatchMethodAsync == null)
throw new ArgumentNullException("dispatchMethodAsync");
using (var ser = new JsSerializer())
{
int id = ser.Load(requestBody);
try
{
string version = ser.ExtractFrom(id, "jsonrpc");
if (!string.Equals(version, "2.0", StringComparison.Ordinal))
throw new JsonRpc2Exception("Unsupported JSON-RPC version: " + version);
string method = ser.ExtractFrom(id, "method");
if (string.IsNullOrEmpty(method))
throw new JsonRpc2Exception("Missing method");
var req = new JsonRpc2Request
{
Version = version,
Method = method
};
return await dispatchMethodAsync(req.Method, ser, id, ct);
}
catch (JsonRpc2Exception)
{
throw;
}
catch (Exception ex)
{
if (_logger != null)
_logger.Error("[JsonRpc2] Parse error", ex);
throw new JsonRpc2Exception("Parse error", ex);
}
finally
{
ser.Unload(id);
}
}
}
}
}

View File

@ -11,8 +11,12 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace WelsonJS.Launcher namespace WelsonJS.Launcher
@ -49,7 +53,8 @@ namespace WelsonJS.Launcher
string targetFilePath = GetTargetFilePath(args); string targetFilePath = GetTargetFilePath(args);
if (!string.IsNullOrEmpty(targetFilePath)) if (!string.IsNullOrEmpty(targetFilePath))
{ {
try { try
{
HandleTargetFilePath(targetFilePath); HandleTargetFilePath(targetFilePath);
} }
catch (Exception e) catch (Exception e)
@ -59,6 +64,14 @@ namespace WelsonJS.Launcher
return; return;
} }
// if use the stdio-jsonrpc2 forwarder
if (HasArg(args, "--stdio-jsonrpc2"))
{
_logger.Info("Starting in the stdio-jsonrpc2 forwarder...");
ProcessStdioJsonRpc2().GetAwaiter().GetResult();
return;
}
// create the mutex // create the mutex
_mutex = new Mutex(true, "WelsonJS.Launcher", out bool createdNew); _mutex = new Mutex(true, "WelsonJS.Launcher", out bool createdNew);
if (!createdNew) if (!createdNew)
@ -81,6 +94,135 @@ namespace WelsonJS.Launcher
_mutex.Dispose(); _mutex.Dispose();
} }
private static bool HasArg(string[] args, string key)
{
if (args == null)
return false;
for (int i = 0; i < args.Length; i++)
if (string.Equals(args[i], key, StringComparison.OrdinalIgnoreCase))
return true;
return false;
}
private async static Task ProcessStdioJsonRpc2()
{
string serverPrefix = GetAppConfig("ResourceServerPrefix");
string endpoint = $"{serverPrefix}jsonrpc2";
int timeout = int.TryParse(GetAppConfig("HttpClientTimeout"), out timeout) ? timeout : 300;
var http = new HttpClient
{
Timeout = TimeSpan.FromSeconds(timeout)
};
using (var stdin = Console.OpenStandardInput())
using (var stdout = Console.OpenStandardOutput())
{
var buffer = new byte[8192];
while (true)
{
int read;
try
{
read = await stdin.ReadAsync(buffer, 0, buffer.Length);
}
catch (Exception ex)
{
_logger.Error("[stdio] stdin read failed", ex);
break;
}
if (read <= 0)
{
_logger.Info("[stdio] EOF received, exiting loop");
break;
}
byte[] payload = new byte[read];
Buffer.BlockCopy(buffer, 0, payload, 0, read);
_logger.Debug($"[stdio] recv {payload.Length} bytes");
_logger.Debug($"[stdio] payload preview: {SafePreviewUtf8(payload, 512)}");
try
{
using (var content = new ByteArrayContent(payload))
{
// Content-Type: application/json
content.Headers.ContentType =
new MediaTypeHeaderValue("application/json")
{
CharSet = "utf-8"
};
_logger.Debug($"[http] POST {endpoint}");
using (var response = await http.PostAsync(endpoint, content))
{
_logger.Info(
$"[http] status={(int)response.StatusCode} {response.ReasonPhrase}");
foreach (var h in response.Headers)
_logger.Debug($"[http] H {h.Key}: {string.Join(", ", h.Value)}");
foreach (var h in response.Content.Headers)
_logger.Debug($"[http] HC {h.Key}: {string.Join(", ", h.Value)}");
byte[] responseBytes =
await response.Content.ReadAsByteArrayAsync();
_logger.Debug($"[http] body {responseBytes.Length} bytes");
_logger.Debug(
$"[http] body preview: {SafePreviewUtf8(responseBytes, 2048)}");
await stdout.WriteAsync(responseBytes, 0, responseBytes.Length);
await stdout.FlushAsync();
}
}
}
catch (TaskCanceledException ex)
{
_logger.Error("[http] request timed out or canceled", ex);
}
catch (HttpRequestException ex)
{
_logger.Error("[http] request failed", ex);
}
catch (Exception ex)
{
_logger.Error("[http] unexpected error", ex);
}
}
}
}
private static string SafePreviewUtf8(byte[] bytes, int maxBytes)
{
if (bytes == null || bytes.Length == 0)
return "(empty)";
try
{
int len = Math.Min(bytes.Length, maxBytes);
string s = Encoding.UTF8.GetString(bytes, 0, len);
s = s.Replace("\r", "\\r").Replace("\n", "\\n");
if (bytes.Length > maxBytes)
s += $"...(truncated, total {bytes.Length} bytes)";
return s;
}
catch
{
return "(binary / non-utf8 response)";
}
}
private static void InitializeAssemblyLoader() private static void InitializeAssemblyLoader()
{ {
byte[] gzBytes = Properties.Resources.Phantomizer; byte[] gzBytes = Properties.Resources.Phantomizer;
@ -182,7 +324,8 @@ namespace WelsonJS.Launcher
private static string GetTargetFilePath(string[] args) private static string GetTargetFilePath(string[] args)
{ {
if (args == null || args.Length == 0) return null; if (args == null || args.Length == 0)
return null;
for (int i = 0; i < args.Length; i++) for (int i = 0; i < args.Length; i++)
{ {
@ -498,7 +641,7 @@ namespace WelsonJS.Launcher
public static void InitializeResourceServer() public static void InitializeResourceServer()
{ {
lock(typeof(Program)) lock (typeof(Program))
{ {
if (_resourceServer == null) if (_resourceServer == null)
{ {

View File

@ -19,7 +19,7 @@ namespace WelsonJS.Launcher.Properties {
// 클래스에서 자동으로 생성되었습니다. // 클래스에서 자동으로 생성되었습니다.
// 멤버를 추가하거나 제거하려면 .ResX 파일을 편집한 다음 /str 옵션을 사용하여 ResGen을 // 멤버를 추가하거나 제거하려면 .ResX 파일을 편집한 다음 /str 옵션을 사용하여 ResGen을
// 다시 실행하거나 VS 프로젝트를 다시 빌드하십시오. // 다시 실행하거나 VS 프로젝트를 다시 빌드하십시오.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources { internal class Resources {
@ -81,27 +81,27 @@ namespace WelsonJS.Launcher.Properties {
/// <summary> /// <summary>
/// 과(와) 유사한 지역화된 문자열을 찾습니다. /// 과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary> /// </summary>
internal static string AzureAiServiceApiKey { internal static string AzureCognitiveApiKey {
get { get {
return ResourceManager.GetString("AzureAiServiceApiKey", resourceCulture); return ResourceManager.GetString("AzureCognitiveApiKey", resourceCulture);
} }
} }
/// <summary> /// <summary>
/// 2024-05-01-preview과(와) 유사한 지역화된 문자열을 찾습니다. /// 2024-05-01-preview과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary> /// </summary>
internal static string AzureAiServiceApiVersion { internal static string AzureCognitiveApiVersion {
get { get {
return ResourceManager.GetString("AzureAiServiceApiVersion", resourceCulture); return ResourceManager.GetString("AzureCognitiveApiVersion", resourceCulture);
} }
} }
/// <summary> /// <summary>
/// https://ai-catswords656881030318.services.ai.azure.com/과(와) 유사한 지역화된 문자열을 찾습니다. /// https://catswords-ai-resource.services.ai.azure.com/과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary> /// </summary>
internal static string AzureAiServicePrefix { internal static string AzureCognitivePrefix {
get { get {
return ResourceManager.GetString("AzureAiServicePrefix", resourceCulture); return ResourceManager.GetString("AzureCognitivePrefix", resourceCulture);
} }
} }
@ -197,7 +197,7 @@ namespace WelsonJS.Launcher.Properties {
} }
/// <summary> /// <summary>
/// 90과(와) 유사한 지역화된 문자열을 찾습니다. /// 300과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary> /// </summary>
internal static string HttpClientTimeout { internal static string HttpClientTimeout {
get { get {
@ -407,7 +407,7 @@ namespace WelsonJS.Launcher.Properties {
} }
/// <summary> /// <summary>
/// 141.101.82.1과(와) 유사한 지역화된 문자열을 찾습니다. /// 49.8.14.101과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary> /// </summary>
internal static string WhoisClientAddress { internal static string WhoisClientAddress {
get { get {
@ -416,7 +416,7 @@ namespace WelsonJS.Launcher.Properties {
} }
/// <summary> /// <summary>
/// https://xn--c79as89aj0e29b77z.xn--3e0b707e/kor/whois/whois.jsp과(와) 유사한 지역화된 문자열을 찾습니다. /// https://whois.kr/kor/whois/whois.jsp과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary> /// </summary>
internal static string WhoisReferrerUrl { internal static string WhoisReferrerUrl {
get { get {
@ -425,7 +425,7 @@ namespace WelsonJS.Launcher.Properties {
} }
/// <summary> /// <summary>
/// https://xn--c79as89aj0e29b77z.xn--3e0b707e/kor/whois.jsc과(와) 유사한 지역화된 문자열을 찾습니다. /// https://whois.kr/kor/whois.jsc과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary> /// </summary>
internal static string WhoisServerUrl { internal static string WhoisServerUrl {
get { get {

View File

@ -160,35 +160,35 @@
<data name="ChromiumDevToolsPrefix" xml:space="preserve"> <data name="ChromiumDevToolsPrefix" xml:space="preserve">
<value>http://localhost:9222/</value> <value>http://localhost:9222/</value>
</data> </data>
<data name="AzureAiServiceApiKey" xml:space="preserve"> <data name="AzureCognitiveApiKey" xml:space="preserve">
<value /> <value />
</data> </data>
<data name="AzureAiServicePrefix" xml:space="preserve"> <data name="AzureCognitivePrefix" xml:space="preserve">
<value>https://ai-catswords656881030318.services.ai.azure.com/</value> <value>https://catswords-ai-resource.services.ai.azure.com/</value>
</data> </data>
<data name="DnsServerAddress" xml:space="preserve"> <data name="DnsServerAddress" xml:space="preserve">
<value>1.1.1.1</value> <value>1.1.1.1</value>
</data> </data>
<data name="WhoisClientAddress" xml:space="preserve"> <data name="WhoisClientAddress" xml:space="preserve">
<value>141.101.82.1</value> <value>49.8.14.101</value>
</data> </data>
<data name="WhoisReferrerUrl" xml:space="preserve"> <data name="WhoisReferrerUrl" xml:space="preserve">
<value>https://xn--c79as89aj0e29b77z.xn--3e0b707e/kor/whois/whois.jsp</value> <value>https://whois.kr/kor/whois/whois.jsp</value>
</data> </data>
<data name="WhoisServerUrl" xml:space="preserve"> <data name="WhoisServerUrl" xml:space="preserve">
<value>https://xn--c79as89aj0e29b77z.xn--3e0b707e/kor/whois.jsc</value> <value>https://whois.kr/kor/whois.jsc</value>
</data> </data>
<data name="BlobStoragePrefix" xml:space="preserve"> <data name="BlobStoragePrefix" xml:space="preserve">
<value>https://catswords.blob.core.windows.net/welsonjs/</value> <value>https://catswords.blob.core.windows.net/welsonjs/</value>
</data> </data>
<data name="AzureAiServiceApiVersion" xml:space="preserve"> <data name="AzureCognitiveApiVersion" xml:space="preserve">
<value>2024-05-01-preview</value> <value>2024-05-01-preview</value>
</data> </data>
<data name="BlobConfigUrl" xml:space="preserve"> <data name="BlobConfigUrl" xml:space="preserve">
<value>https://catswords.blob.core.windows.net/welsonjs/blob.config.xml</value> <value>https://catswords.blob.core.windows.net/welsonjs/blob.config.xml</value>
</data> </data>
<data name="HttpClientTimeout" xml:space="preserve"> <data name="HttpClientTimeout" xml:space="preserve">
<value>90</value> <value>300</value>
</data> </data>
<data name="IpQueryApiKey" xml:space="preserve"> <data name="IpQueryApiKey" xml:space="preserve">
<value /> <value />

View File

@ -4,8 +4,6 @@
// https://github.com/gnh1201/welsonjs // https://github.com/gnh1201/welsonjs
// //
using log4net; using log4net;
using log4net.Core;
using Microsoft.Isam.Esent.Interop;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -75,6 +73,7 @@ namespace WelsonJS.Launcher
_apis.Add(new ApiEndpoints.TwoFactorAuth(this, _httpClient, _logger)); _apis.Add(new ApiEndpoints.TwoFactorAuth(this, _httpClient, _logger));
_apis.Add(new ApiEndpoints.Whois(this, _httpClient, _logger)); _apis.Add(new ApiEndpoints.Whois(this, _httpClient, _logger));
_apis.Add(new ApiEndpoints.ImageColorPicker(this, _httpClient, _logger)); _apis.Add(new ApiEndpoints.ImageColorPicker(this, _httpClient, _logger));
_apis.Add(new ApiEndpoints.JsonRpc2(this, _httpClient, _logger));
// Register the prefix // Register the prefix
_listener.Prefixes.Add(prefix); _listener.Prefixes.Add(prefix);
@ -164,8 +163,8 @@ namespace WelsonJS.Launcher
// Serve from the blob server // Serve from the blob server
if (await ServeBlob(context, path)) return; if (await ServeBlob(context, path)) return;
// Fallback to serve from a resource name // Fallback to 404 (Not Found)
await ServeResource(context, GetResource(_resourceName), "text/html"); await ServeResource(context);
} }
private async Task<bool> ServeBlob(HttpListenerContext context, string path, string prefix = null) private async Task<bool> ServeBlob(HttpListenerContext context, string path, string prefix = null)
@ -398,7 +397,7 @@ namespace WelsonJS.Launcher
await ServeResource(context, Encoding.UTF8.GetBytes(data), mimeType, statusCode); await ServeResource(context, Encoding.UTF8.GetBytes(data), mimeType, statusCode);
} }
private byte[] GetResource(string resourceName) public static byte[] GetResource(string resourceName)
{ {
// Try to fetch embedded resource. // Try to fetch embedded resource.
byte[] data = GetEmbeddedResource(typeof(Program).Namespace + "." + resourceName); byte[] data = GetEmbeddedResource(typeof(Program).Namespace + "." + resourceName);
@ -408,7 +407,7 @@ namespace WelsonJS.Launcher
return GetResourceFromManager(resourceName); return GetResourceFromManager(resourceName);
} }
private byte[] GetEmbeddedResource(string fullResourceName) private static byte[] GetEmbeddedResource(string fullResourceName)
{ {
Assembly assembly = Assembly.GetExecutingAssembly(); Assembly assembly = Assembly.GetExecutingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(fullResourceName)) using (Stream stream = assembly.GetManifestResourceStream(fullResourceName))
@ -425,7 +424,7 @@ namespace WelsonJS.Launcher
return null; return null;
} }
private byte[] GetResourceFromManager(string resourceName) private static byte[] GetResourceFromManager(string resourceName)
{ {
object resourceObject = Properties.Resources.ResourceManager.GetObject(resourceName); object resourceObject = Properties.Resources.ResourceManager.GetObject(resourceName);
switch (resourceObject) switch (resourceObject)
@ -439,7 +438,7 @@ namespace WelsonJS.Launcher
} }
} }
private byte[] ConvertIconToBytes(System.Drawing.Icon icon) private static byte[] ConvertIconToBytes(System.Drawing.Icon icon)
{ {
using (MemoryStream memoryStream = new MemoryStream()) using (MemoryStream memoryStream = new MemoryStream())
{ {

View File

@ -91,9 +91,11 @@
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ApiEndpoints\JsonRpc2.cs" />
<Compile Include="IApiEndpoint.cs" /> <Compile Include="IApiEndpoint.cs" />
<Compile Include="JsCore.cs" /> <Compile Include="JsCore.cs" />
<Compile Include="JsNative.cs" /> <Compile Include="JsNative.cs" />
<Compile Include="JsonRpc2Dispatcher.cs" />
<Compile Include="JsSerializer.cs" /> <Compile Include="JsSerializer.cs" />
<Compile Include="ApiEndpoints\ImageColorPicker.cs" /> <Compile Include="ApiEndpoints\ImageColorPicker.cs" />
<Compile Include="ApiEndpoints\IpQuery.cs" /> <Compile Include="ApiEndpoints\IpQuery.cs" />

View File

@ -10,12 +10,12 @@
<add key="ChromiumDevToolsTimeout" value="5"/> <add key="ChromiumDevToolsTimeout" value="5"/>
<add key="ChromiumExecutablePath" value="msedge.exe"/> <add key="ChromiumExecutablePath" value="msedge.exe"/>
<add key="ChromiumAppMode" value="true"/> <add key="ChromiumAppMode" value="true"/>
<add key="AzureAiServicePrefix" value="https://ai-catswords656881030318.services.ai.azure.com/"/> <add key="AzureCognitivePrefix" value="https://ai-catswords656881030318.services.ai.azure.com/"/>
<add key="AzureAiServiceApiKey" value=""/> <add key="AzureCognitiveApiKey" value=""/>
<add key="AzureAiServiceApiVersion" value="2024-05-01-preview"/> <add key="AzureCognitiveApiVersion" value="2024-05-01-preview"/>
<add key="WhoisServerUrl" value="https://xn--c79as89aj0e29b77z.xn--3e0b707e/kor/whois.jsc"/> <add key="WhoisServerUrl" value="https://whois.kr/kor/whois.jsc"/>
<add key="WhoisReferrerUrl" value="https://xn--c79as89aj0e29b77z.xn--3e0b707e/kor/whois/whois.jsp"/> <add key="WhoisReferrerUrl" value="https://whois.kr/kor/whois/whois.jsp"/>
<add key="WhoisClientAddress" value="141.101.82.1"/> <add key="WhoisClientAddress" value="49.8.14.101"/>
<add key="DnsServerAddress" value="1.1.1.1"/> <add key="DnsServerAddress" value="1.1.1.1"/>
<add key="BlobStoragePrefix" value="https://catswords.blob.core.windows.net/welsonjs/"/> <add key="BlobStoragePrefix" value="https://catswords.blob.core.windows.net/welsonjs/"/>
<add key="BlobConfigUrl" value="https://catswords.blob.core.windows.net/welsonjs/blob.config.xml"/> <add key="BlobConfigUrl" value="https://catswords.blob.core.windows.net/welsonjs/blob.config.xml"/>

View File

@ -29,6 +29,10 @@
flex: 3; flex: 3;
} }
#mapView {
flex: 3;
}
#promptEditor { #promptEditor {
flex: 1; flex: 1;
} }
@ -60,6 +64,7 @@
} }
}; };
</script> </script>
<script src="http://localhost:3000/ajax/libs/leaflet/1.9.4/leaflet.min.js" integrity="sha384-NElt3Op+9NBMCYaef5HxeJmU4Xeard/Lku8ek6hoPTvYkQPh3zLIrJP7KiRocsxO" crossorigin="anonymous"></script>
<script src="http://localhost:3000/ajax/libs/lodash/4.17.21/lodash.min.js" integrity="sha384-H6KKS1H1WwuERMSm+54dYLzjg0fKqRK5ZRyASdbrI/lwrCc6bXEmtGYr5SwvP1pZ" crossorigin="anonymous"></script> <script src="http://localhost:3000/ajax/libs/lodash/4.17.21/lodash.min.js" integrity="sha384-H6KKS1H1WwuERMSm+54dYLzjg0fKqRK5ZRyASdbrI/lwrCc6bXEmtGYr5SwvP1pZ" crossorigin="anonymous"></script>
<script src="http://localhost:3000/ajax/libs/jsoneditor/10.1.3/jsoneditor.min.js" integrity="sha384-NdVVc20Tze856ZAWEoJNCk0mL4zJrGztRwULc5Hz25HUXQQgYtmjFtgVAxR4p5dD" crossorigin="anonymous"></script> <script src="http://localhost:3000/ajax/libs/jsoneditor/10.1.3/jsoneditor.min.js" integrity="sha384-NdVVc20Tze856ZAWEoJNCk0mL4zJrGztRwULc5Hz25HUXQQgYtmjFtgVAxR4p5dD" crossorigin="anonymous"></script>
<script src="http://localhost:3000/ajax/libs/react/18.3.1/umd/react.production.min.js" integrity="sha384-DGyLxAyjq0f9SPpVevD6IgztCFlnMF6oW/XQGmfe+IsZ8TqEiDrcHkMLKI6fiB/Z" crossorigin="anonymous"></script> <script src="http://localhost:3000/ajax/libs/react/18.3.1/umd/react.production.min.js" integrity="sha384-DGyLxAyjq0f9SPpVevD6IgztCFlnMF6oW/XQGmfe+IsZ8TqEiDrcHkMLKI6fiB/Z" crossorigin="anonymous"></script>
@ -103,7 +108,8 @@
loadResource("http://localhost:3000/ajax/libs/metroui/dev/lib/metro.css", "text/css", "sha384-4XgOiXH2ZMaWt5s5B35yKi7EAOabhZvx7wO8Jr71q2vZ+uONdRza/6CsK2kpyocd"), loadResource("http://localhost:3000/ajax/libs/metroui/dev/lib/metro.css", "text/css", "sha384-4XgOiXH2ZMaWt5s5B35yKi7EAOabhZvx7wO8Jr71q2vZ+uONdRza/6CsK2kpyocd"),
loadResource("http://localhost:3000/ajax/libs/metroui/dev/lib/icons.css", "text/css", "sha384-FuLND994etg+RtnpPSPMyNBvL+fEz+xGhbN61WUWuDEeZ+wJzcQ8SGqAMuI5hWrt"), loadResource("http://localhost:3000/ajax/libs/metroui/dev/lib/icons.css", "text/css", "sha384-FuLND994etg+RtnpPSPMyNBvL+fEz+xGhbN61WUWuDEeZ+wJzcQ8SGqAMuI5hWrt"),
loadResource("http://localhost:3000/ajax/libs/monaco-editor/0.52.2/min/vs/editor/editor.main.css", "text/css", "sha384-06yHXpYRlHEPaR4AS0fB/W+lMN09Zh5e1XMtfkNQdHV38OlhfkOEW5M+pCj3QskC"), loadResource("http://localhost:3000/ajax/libs/monaco-editor/0.52.2/min/vs/editor/editor.main.css", "text/css", "sha384-06yHXpYRlHEPaR4AS0fB/W+lMN09Zh5e1XMtfkNQdHV38OlhfkOEW5M+pCj3QskC"),
loadResource("http://localhost:3000/ajax/libs/jsoneditor/10.1.3/jsoneditor.min.css", "text/css", "sha384-cj1rYBc4/dVYAknZMTkVCDRL6Knzugf32igVqsuFW0iRWFHKH8Ci8+ekC8gNsFZ+") loadResource("http://localhost:3000/ajax/libs/jsoneditor/10.1.3/jsoneditor.min.css", "text/css", "sha384-cj1rYBc4/dVYAknZMTkVCDRL6Knzugf32igVqsuFW0iRWFHKH8Ci8+ekC8gNsFZ+"),
loadResource("http://localhost:3000/ajax/libs/leaflet/1.9.4/leaflet.min.css", "text/css", "sha384-c6Rcwz4e4CITMbu/NBmnNS8yN2sC3cUElMEMfP3vqqKFp7GOYaaBBCqmaWBjmkjb")
]).then(() => { ]).then(() => {
const _e = React.createElement; const _e = React.createElement;
@ -128,9 +134,9 @@
} }
function RibbonMenu({ function RibbonMenu({
onOpenFileClick, onSaveFileClick, onCopliotClick, onAzureAiClick, onOpenFileClick, onSaveFileClick, onCopliotClick, onAzureCognitiveClick,
onSavePromptClick, onLoadPromptClick, onQueryWhoisClick, onQueryDnsClick, onSavePromptClick, onLoadPromptClick, onQueryWhoisClick, onQueryDnsClick,
onQueryIpClick onQueryIpClick, onMapClick
}) { }) {
const fileButtons = [ const fileButtons = [
{ {
@ -147,9 +153,9 @@
} }
]; ];
const aiButtons = [ const cognitiveToolsButtons = [
{ id: 'btnCopilot', icon: 'mif-rocket', caption: 'Copilot', onClick: onCopliotClick }, { id: 'btnCopilot', icon: 'mif-rocket', caption: 'Copilot', onClick: onCopliotClick },
{ id: 'btnAzureAi', icon: 'mif-rocket', caption: 'Azure AI', onClick: onAzureAiClick }, { id: 'btnAzureCognitive', icon: 'mif-rocket', caption: 'Azure', onClick: onAzureCognitiveClick },
{ id: 'btnSavePrompt', icon: 'mif-floppy-disks', caption: 'Save', onClick: onSavePromptClick }, { id: 'btnSavePrompt', icon: 'mif-floppy-disks', caption: 'Save', onClick: onSavePromptClick },
{ id: 'btnLoadPrompt', icon: 'mif-file-upload', caption: 'Load', onClick: onLoadPromptClick } { id: 'btnLoadPrompt', icon: 'mif-file-upload', caption: 'Load', onClick: onLoadPromptClick }
]; ];
@ -157,7 +163,8 @@
const networkToolsButtons = [ const networkToolsButtons = [
{ id: 'btnWhois', icon: 'mif-earth', caption: 'Whois', onClick: onQueryWhoisClick }, { id: 'btnWhois', icon: 'mif-earth', caption: 'Whois', onClick: onQueryWhoisClick },
{ id: 'btnQueryDns', icon: 'mif-earth', caption: 'DNS', onClick: onQueryDnsClick }, { id: 'btnQueryDns', icon: 'mif-earth', caption: 'DNS', onClick: onQueryDnsClick },
{ id: 'btnQueryIp', icon: 'mif-user-secret', caption: 'IP', onClick: onQueryIpClick } { id: 'btnQueryIp', icon: 'mif-user-secret', caption: 'IP', onClick: onQueryIpClick },
{ id: 'btnMap', icon: 'mif-rocket', caption: 'Map', onClick: onMapClick }
]; ];
return _e( return _e(
@ -170,14 +177,14 @@
_e('div', { className: 'content-holder' }, _e('div', { className: 'content-holder' },
_e('div', { className: 'section active', id: 'editor-tab' }, _e('div', { className: 'section active', id: 'editor-tab' },
_e(Group, { title: 'File', buttons: fileButtons }), _e(Group, { title: 'File', buttons: fileButtons }),
_e(Group, { title: 'Generative AI', buttons: aiButtons }), _e(Group, { title: 'Cognitive Tools (AI)', buttons: cognitiveToolsButtons }),
_e(Group, { title: 'Network tools', buttons: networkToolsButtons }) _e(Group, { title: 'Network Tools', buttons: networkToolsButtons })
) )
) )
); );
} }
function Editor({ editorRef }) { function Editor({ editorRef, visible }) {
const containerRef = React.useRef(null); const containerRef = React.useRef(null);
const getSuggestions = (word, range) => axios.get(`/completion/${encodeURIComponent(word)}`) const getSuggestions = (word, range) => axios.get(`/completion/${encodeURIComponent(word)}`)
@ -233,6 +240,11 @@
}); });
}, []); }, []);
React.useEffect(() => {
// toggle the display style attribute
containerRef.current.style.display = (visible ? "" : "none");
}, [visible]);
return _e('div', { id: 'editor', ref: containerRef }); return _e('div', { id: 'editor', ref: containerRef });
} }
@ -282,12 +294,103 @@
return _e('div', { id: 'promptEditor', ref: containerRef }); return _e('div', { id: 'promptEditor', ref: containerRef });
} }
function useLocationMarker(mapViewRef) {
const markerRef = React.useRef(null);
const mark = React.useCallback((lat, lng, description) => {
const map = mapViewRef.current;
if (!map) return;
const latlng = L.latLng(lat, lng);
if (!markerRef.current) {
markerRef.current = L.marker(latlng).addTo(map)
.bindPopup(description || "(no description)")
.openPopup();
} else {
markerRef.current.setLatLng(latlng);
}
}, [mapViewRef]);
return { mark, markerRef };
}
function MapView({ mapViewRef, visible }) {
const containerRef = React.useRef(null);
const { mark } = useLocationMarker(mapViewRef);
// init once
React.useEffect(() => {
if (!containerRef.current)
return;
if (typeof L === "undefined" || !L || !L.map) {
console.error("[MapView] Leaflet (L) is not loaded.");
return;
}
if (mapViewRef && mapViewRef.current && mapViewRef.current._leaflet_id)
return;
const map = L.map(containerRef.current, {
zoomControl: true,
attributionControl: true
}).setView([37.5665, 126.9780], 12);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: '&copy; OpenStreetMap contributors'
}).addTo(map);
if (mapViewRef)
mapViewRef.current = map;
return function () {
try {
if (mapViewRef && mapViewRef.current === map)
mapViewRef.current = null;
map.remove();
} catch (e) {}
};
}, []);
// handle show/hide toggles
React.useEffect(() => {
// when becoming visible, leaflet needs a resize recalculation
const map = mapViewRef ? mapViewRef.current : null;
if (!map || !map.invalidateSize)
return;
// toggle the display style attribute
containerRef.current.style.display = (visible ? "block" : "none");
// defer until after layout/display change
if (visible) {
setTimeout(function () {
try {
map.invalidateSize();
navigator.geolocation.getCurrentPosition(pos => {
mark(pos.coords.latitude, pos.coords.longitude, `My Location: (${pos.coords.latitude}, ${pos.coords.longitude})`);
});
} catch (e) {}
}, 0);
}
}, [visible]);
return _e('div', { id: 'mapView', ref: containerRef });
}
function App() { function App() {
const editorRef = React.useRef(null); const editorRef = React.useRef(null);
const promptEditorRef = React.useRef(null); const promptEditorRef = React.useRef(null);
const settingsRef = React.useRef({}); const settingsRef = React.useRef({});
const fileNameRef = React.useRef('sayhello.js'); const fileNameRef = React.useRef('sayhello.js');
const promptMessagesRef = React.useRef([]); const promptMessagesRef = React.useRef([]);
const mapViewRef = React.useRef(null);
const [showMap, setShowMap] = React.useState(false);
const fetchSettings = () => axios.get(`/settings`) const fetchSettings = () => axios.get(`/settings`)
.then(response => { .then(response => {
@ -472,23 +575,23 @@
} }
}; };
const sendMessageToAzureAi = () => { const sendMessageToAzureCognitive = () => {
const promptMessage = prompt("Enter a prompt message:", ''); const promptMessage = prompt("Enter a prompt message:", '');
if (!promptMessage || promptMessage.trim() == '') { if (!promptMessage || promptMessage.trim() == '') {
alert("A prompt message is required."); alert("A prompt message is required.");
return; return;
} }
appendTextToEditor(`\n// ${promptMessage}... Generating text with Azure AI...`); appendTextToEditor(`\n// ${promptMessage}... Generating text with Azure Cognitive...`);
pushPromptMessage("user", promptMessage); pushPromptMessage("user", promptMessage);
const apiKey = settingsRef.current.AzureAiServiceApiKey; const apiKey = settingsRef.current.AzureCognitiveApiKey;
if (!apiKey || apiKey.trim() == '') { if (!apiKey || apiKey.trim() == '') {
alert("Azure AI API key is not set."); alert("Azure Cognitive API key is not set.");
return; return;
} }
const url = `${settingsRef.current.AzureAiServicePrefix}models/chat/completions?api-version=${settingsRef.current.AzureAiServiceApiVersion}`; const url = `${settingsRef.current.AzureCognitivePrefix}models/chat/completions?api-version=${settingsRef.current.AzureCognitiveApiVersion}`;
const data = { const data = {
messages: promptMessagesRef.current, messages: promptMessagesRef.current,
@ -648,6 +751,10 @@
}); });
}; };
function toggleMap() {
setShowMap(v => !v);
}
React.useEffect(() => { React.useEffect(() => {
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
resizeEditor(); resizeEditor();
@ -662,15 +769,17 @@
onOpenFileClick: openFile, onOpenFileClick: openFile,
onSaveFileClick: saveFile, onSaveFileClick: saveFile,
onCopliotClick: sendMessageToCopilot, onCopliotClick: sendMessageToCopilot,
onAzureAiClick: sendMessageToAzureAi, onAzureCognitiveClick: sendMessageToAzureCognitive,
onSavePromptClick: savePromptMessages, onSavePromptClick: savePromptMessages,
onLoadPromptClick: loadPromptMessages, onLoadPromptClick: loadPromptMessages,
onQueryWhoisClick: queryWhois, onQueryWhoisClick: queryWhois,
onQueryDnsClick: queryDns, onQueryDnsClick: queryDns,
onQueryIpClick: queryIp onQueryIpClick: queryIp,
onMapClick: toggleMap
}), }),
_e('div', { id: 'container' }, _e('div', { id: 'container' },
_e(Editor, { editorRef }), _e(Editor, { editorRef: editorRef, visible: !showMap }),
_e(MapView, { mapViewRef: mapViewRef, visible: showMap }),
_e(PromptEditor, { promptEditorRef, promptMessagesRef }) _e(PromptEditor, { promptEditorRef, promptMessagesRef })
), ),
_e('div', { className: 'banner' }, _e('a', { href: 'https://github.com/gnh1201/welsonjs' }, '❤️ Contribute this project')), _e('div', { className: 'banner' }, _e('a', { href: 'https://github.com/gnh1201/welsonjs' }, '❤️ Contribute this project')),

73
app.js
View File

@ -36,12 +36,25 @@ var console = {
} }
return res; return res;
}, },
_echoDefault: function(message) { _muted: (function() {
try {
if (typeof WScript !== "undefined")
return WScript.Arguments.Named.Exists("quiet");
} catch (e) { /* ignore */ }
return false;
})(),
_echoCallback: function(params, type) {
if (this._muted) return;
if (typeof WScript !== "undefined") { if (typeof WScript !== "undefined") {
WScript.Echo("[*] " + message) if (this._muted) {
WScript.StdErr.WriteLine("[*] " + params.message);
return;
}
WScript.StdOut.WriteLine("[*] " + params.message);
} }
}, },
_echoCallback: null,
_echo: function(args, type) { _echo: function(args, type) {
var messages = []; var messages = [];
var params = { var params = {
@ -80,19 +93,15 @@ var console = {
} }
} }
var message = messages.join(' '); params.message = messages.join(' ');
if (typeof type !== "undefined") { if (typeof type !== "undefined") {
message = type + ": " + message; params.message = type + ": " + params.message;
} }
this._echoDefault(message); this._echoCallback(params);
this._messages.push(message); this._messages.push(params.message);
if (params.scope.length > 0 && this._echoCallback != null) { if (params.scope.length > 0) {
try {
this._echoCallback(params, type); this._echoCallback(params, type);
} catch (e) {
this._echoDefault("Exception:" + e.message);
}
} }
}, },
assert: function(assertion) { assert: function(assertion) {
@ -238,7 +247,7 @@ function TraceError(severity, message, callee) {
})([ })([
function (s) { fn.call(console, s); }, // 1) severity-specific console function (s) { fn.call(console, s); }, // 1) severity-specific console
function (s) { console.log(s); }, // 2) generic console.log function (s) { console.log(s); }, // 2) generic console.log
function (s) { WScript.Echo(s); } // 3) WSH fallback function (s) { WScript.StdOut.WriteLine(s); } // 3) WSH fallback
]); ]);
} }
@ -404,7 +413,7 @@ function require(pathname) {
if (pos > -1) { if (pos > -1) {
var scheme = FN.substring(0, pos); var scheme = FN.substring(0, pos);
// load script from a remote server // request a script from a remote server
if (["http", "https"].indexOf(scheme) > -1) { if (["http", "https"].indexOf(scheme) > -1) {
require._addScriptProvider(function(url) { require._addScriptProvider(function(url) {
try { try {
@ -415,7 +424,7 @@ function require(pathname) {
}); });
} }
// load script from LIE(Language Inference Engine) service // request a script from LLM based AI services
if (["ai"].indexOf(scheme) > -1) { if (["ai"].indexOf(scheme) > -1) {
require._addScriptProvider(function(url) { require._addScriptProvider(function(url) {
try { try {
@ -472,7 +481,7 @@ function require(pathname) {
})({ })({
existsSync: function(filename) { existsSync: function(filename) {
return UseObject("Scripting.FileSystemObject", function(fso) { return UseObject("Scripting.FileSystemObject", function(fso) {
return fso.FileExists(filename); return fso.FileExists(require._getCurrentScriptDirectory() + "\\" + filename);
}); });
} }
}, { }, {
@ -715,22 +724,20 @@ require._addScriptProvider = function(f) {
} }
}; };
/////////////////////////////////////////////////////////////////////////////////
// Load script, and call app.main() // Load script, and call app.main()
/////////////////////////////////////////////////////////////////////////////////
function initializeConsole() { function initializeConsole() {
if (typeof WScript === "undefined") { if (typeof WScript === "undefined") {
console.error("This is not a console application"); console.error("This is not a console application");
return; return;
} }
var argl = WScript.arguments.length; var args = (function(acc, length) {
if (argl > 0) { for (var i = 0; i < length; i++)
var args = []; acc.push(WScript.arguments(i));
for (var i = 0; i < argl; i++) { return acc;
args.push(WScript.arguments(i)); })([], WScript.arguments.length);
}
if (args.length > 0) {
var name = args.shift(); var name = args.shift();
var app = require(name); var app = require(name);
if (app) { if (app) {
@ -779,14 +786,12 @@ function initializeWindow(name, args, w, h) {
function dispatchServiceEvent(name, eventType, w_args, argl) { function dispatchServiceEvent(name, eventType, w_args, argl) {
var app = require(name); var app = require(name);
var args = []; var args = (function(acc, length) {
for (var i = 0; i < argl; i++)
acc.push(w_args(i));
return acc;
})([], argl);
// convert the arguments to Array
for (var i = 0; i < argl; i++) {
args.push(w_args(i));
}
// load the service
if (app) { if (app) {
var bind = function(eventType) { var bind = function(eventType) {
var event_callback_name = "on" + eventType; var event_callback_name = "on" + eventType;
@ -846,8 +851,8 @@ if (typeof JSON === "undefined") {
__evalFile__("app/assets/js/json2.js"); __evalFile__("app/assets/js/json2.js");
} }
// core-js (formerly, babel-polyfill) // core-js (polyfills)
require("app/assets/js/core-js-3.38.0.minified"); require("app/assets/js/core-js-3.49.0.wsh");
// Squel.js SQL query string builder for Javascript // Squel.js SQL query string builder for Javascript
var squel = require("app/assets/js/squel-basic-5.13.0-afa1cb5.wsh"); var squel = require("app/assets/js/squel-basic-5.13.0-afa1cb5.wsh");

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -47,4 +47,4 @@ echo [*] Registering WelsonJS.Toolkit component...
:: Final step :: Final step
echo [*] Pre-configuration complete. Starting bootstrap script... echo [*] Pre-configuration complete. Starting bootstrap script...
cscript app.js bootstrap %SystemRoot%\SysWOW64\cscript.exe app.js bootstrap

7
bootstrap.js vendored
View File

@ -1,5 +1,5 @@
// bootstrap.js // bootstrap.js
// Copyright 2019-2025, Namhyeon Go <gnh1201@catswords.re.kr> and the WelsonJS contributors. // Copyright 2019-2026, Namhyeon Go <gnh1201@catswords.re.kr> and the WelsonJS contributors.
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// https://github.com/gnh1201/welsonjs // https://github.com/gnh1201/welsonjs
// //
@ -19,11 +19,6 @@ function main(args) {
console.log("Starting unlock files..."); console.log("Starting unlock files...");
PS.execCommand("dir | Unblock-File"); PS.execCommand("dir | Unblock-File");
// Allow CROS to ADO
//console.log("Adjusting CROS policy to ADO...");
//REG.write(REG.HKCU, "SOFTWARE\\Policies\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Lockdown_Zones\\4", "1406", "00000000", REG.DWORD);
//REG.write(REG.HKLM, "SOFTWARE\\Policies\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Lockdown_Zones\\4", "1406", "00000000", REG.DWORD);
// Register HTA file association // Register HTA file association
console.log("Registering HTA file association..."); console.log("Registering HTA file association...");
REG.execFile("app\\assets\\reg\\Default_HTA.reg"); REG.execFile("app\\assets\\reg\\Default_HTA.reg");

View File

@ -1,33 +1,35 @@
// coupang.js // coupang.js
// Coupang SERP API Client
// Copyright 2019-2025, Namhyeon Go <gnh1201@catswords.re.kr> and the WelsonJS contributors. // Copyright 2019-2025, Namhyeon Go <gnh1201@catswords.re.kr> and the WelsonJS contributors.
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// https://github.com/gnh1201/welsonjs // https://github.com/gnh1201/welsonjs
// //
// SECURITY NOTICE var HTTP = require("lib/http");
// Due to potential security issues, the Public API URL is not provided. If you need to request access, please refer to the project's contact information.
// You can download the server-side script that implements this functionality from the link below:
// https://github.com/gnh1201/caterpillar
//
var JsonRpc2 = require("lib/jsonrpc2");
function search(s) { function search(keyword, limit) {
var rpc = JsonRpc2.create(JsonRpc2.DEFAULT_JSONRPC2_URL); if (typeof keyword !== "string" || keyword.length === 0) {
var result = rpc.invoke("relay_invoke_method", { throw new TypeError("keyword must be a non-empty string");
"callback": "load_script", }
"requires": [
"https://scriptas.catswords.net/coupang.class.php"
],
"args": [
"$search = new CoupangProductSearch(); return $search->searchProducts('" + s + "')"
]
}, "");
return result.data; limit = Math.min(100, Math.max(1,
Math.floor((typeof limit === "number" && isFinite(limit)) ? limit : 10)
));
return HTTP.create()
.setParameters({
"keyword": keyword,
"limit": limit
})
.setDataType("json")
.open("GET", "https://cold-math-31f3.gnh1201.workers.dev/api/v1/products/search")
.send()
.responseBody
;
} }
exports.search = search; exports.search = search;
exports.VERSIONINFO = "Coupang Product Search Client (coupang.js) version 0.1.1"; exports.VERSIONINFO = "Coupang SERP API Client (coupang.js) version 1.0";
exports.AUTHOR = "gnh1201@catswords.re.kr"; exports.AUTHOR = "gnh1201@catswords.re.kr";
exports.global = global; exports.global = global;
exports.require = global.require; exports.require = global.require;

View File

@ -106,9 +106,9 @@ var HTTPObject = function(engine) {
"Msxml2.XMLHTTP", "Msxml2.XMLHTTP",
"Msxml2.XMLHTTP.7.0", "Msxml2.XMLHTTP.7.0",
"Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.6.0",
"Msxml2.XMLHTTP.5.O", "Msxml2.XMLHTTP.5.0",
"Msxml2.XMLHTTP.4.O", "Msxml2.XMLHTTP.4.0",
"Msxml2.XMLHTTP.3.O", "Msxml2.XMLHTTP.3.0",
"Msxml2.XMLHTTP.2.6", "Msxml2.XMLHTTP.2.6",
"Msxml2.ServerXMLHTTP", "Msxml2.ServerXMLHTTP",
"Msxml2.ServerXMLHTTP.6.0", "Msxml2.ServerXMLHTTP.6.0",
@ -1229,7 +1229,7 @@ exports.parseURL = parseURL;
exports.DEFAULT_USER_AGENT = DEFAULT_USER_AGENT; exports.DEFAULT_USER_AGENT = DEFAULT_USER_AGENT;
exports.defaultUserAgent = DEFAULT_USER_AGENT; // compatible exports.defaultUserAgent = DEFAULT_USER_AGENT; // compatible
exports.VERSIONINFO = "WelsonJS framework HTTP client (http.js) version 0.7.48"; exports.VERSIONINFO = "WelsonJS framework HTTP client (http.js) version 0.7.49";
exports.AUTHOR = "gnh1201@catswords.re.kr"; exports.AUTHOR = "gnh1201@catswords.re.kr";
exports.global = global; exports.global = global;
exports.require = global.require; exports.require = global.require;

View File

@ -38,6 +38,58 @@ function JsonRpc2(url) {
} }
} }
function extract(message, callback) {
var data;
if (typeof callback !== "function") {
throw new Error("Invalid callback");
}
try {
data = JSON.parse(message);
} catch (e) {
throw new Error("Invalid JSON: " + e.message);
}
if (!data || typeof data !== "object") {
throw new Error("Invalid request object");
}
if (data.jsonrpc !== "2.0") {
throw new Error("Invalid JSON-RPC version");
}
if (!data.method || typeof data.method !== "string") {
throw new Error("Missing or invalid method");
}
var params = data.params !== undefined ? data.params : null;
var id = data.id !== undefined ? data.id : null;
try {
var result = callback(data.method, params, id);
if (result !== false) {
return {
jsonrpc: "2.0",
result: result === undefined ? null : result,
id: id
};
}
return result;
} catch (e) {
return {
jsonrpc: "2.0",
error: {
code: -32603,
message: e && e.message ? e.message : "Internal error"
},
id: id
};
}
}
function wrap(method, params, id) { function wrap(method, params, id) {
return { return {
"jsonrpc": "2.0", "jsonrpc": "2.0",
@ -51,14 +103,15 @@ function create(url) {
return new JsonRpc2(url); return new JsonRpc2(url);
} }
var DEFAULT_JSONRPC2_URL = "https://azure-ashlan-40.tiiny.io/"; var DEFAULT_JSONRPC2_URL = "http://localhost:5555";
exports.extract = extract;
exports.wrap = wrap; exports.wrap = wrap;
exports.create = create; exports.create = create;
exports.DEFAULT_JSONRPC2_URL = DEFAULT_JSONRPC2_URL; exports.DEFAULT_JSONRPC2_URL = DEFAULT_JSONRPC2_URL;
exports.VERSIONINFO = "JSON-RPC 2.0 wrapper (jsonrpc2.js) version 0.1.5"; exports.VERSIONINFO = "JSON-RPC 2.0 wrapper (jsonrpc2.js) version 0.1.7";
exports.AUTHOR = "gnh1201@catswords.re.kr"; exports.AUTHOR = "gnh1201@catswords.re.kr";
exports.global = global; exports.global = global;
exports.require = global.require; exports.require = global.require;

68
lib/stdio-server.js Normal file
View File

@ -0,0 +1,68 @@
// stdio-server.js
// Copyright 2019-2026, Namhyeon Go <gnh1201@catswords.re.kr> and the WelsonJS contributors.
// SPDX-License-Identifier: GPL-3.0-or-later
// https://github.com/gnh1201/welsonjs
//
var STD = require("lib/std");
function StdioServer() {
// Set event-attachable object
STD.EventTarget.apply(this, arguments);
this.messages = [];
this.receive = function () {
return this.messages.shift();
};
this.send = function (message) {
if (!message) return;
if (typeof message === "object") {
try {
var _serialized = JSON.stringify(message);
message = _serialized;
} catch (e) { /* ignore */ }
}
WScript.StdOut.WriteLine(message);
WScript.StdErr.WriteLine(message);
};
this.listen = function () {
while (!WScript.StdIn.AtEndOfStream) {
this.messages.push(WScript.StdIn.ReadLine());
this.dispatchEvent(new STD.Event("message"));
}
};
}
StdioServer.prototype = new STD.EventTarget();
StdioServer.prototype.constructor = StdioServer;
/*
// For example
var server = new StdioServer();
server.addEventListener("message", function(e) {
// receive message
var message = e.target.receive();
console.log(message);
// send message
e.target.send("Hello world");
});
server.listen();
*/
exports.create = function () {
return new StdioServer();
};
exports.VERSIONINFO = "Stdio Server (stdio-server.js) version 0.1";
exports.AUTHOR = "gnh1201@catswords.re.kr";
exports.global = global;
exports.require = global.require;

126
mcploader.js Normal file
View File

@ -0,0 +1,126 @@
// mcploader.js
// Copyright 2019-2026, Namhyeon Go <gnh1201@catswords.re.kr> and the WelsonJS contributors.
// SPDX-License-Identifier: GPL-3.0-or-later
// https://github.com/gnh1201/welsonjs
//
var StdioServer = require("lib/stdio-server");
var JsonRpc2 = require("lib/jsonrpc2");
function main(args) {
var server = StdioServer.create();
server.addEventListener("message", function(e) {
var message = e.target.receive();
e.target.send(
JsonRpc2.extract(message, function (method, params, id) {
var isError = false;
if (method == "initialize") {
return {
"protocolVersion": "2025-11-25",
"capabilities": {
"extensions": {
"io.modelcontextprotocol/ui": {
"mimeTypes": ["text/html;profile=mcp-app"]
}
}
},
"serverInfo": {
"name": "WelsonJS MCP",
"version": "1.0.0"
},
"isError": isError
};
}
if (method === "notifications/initialized") {
// DO NOT return anything
return false;
}
if (method == "tools/list") {
return {
"tools": [
{
"name": "add_both_numbers",
"title": "add both_numbers (add A and B)",
"description": "add two numbers (add A and B)",
"inputSchema": {
"type": "object",
"properties": {
"a": {
"type": "number"
},
"b": {
"type": "number"
}
},
"required": ["a", "b"]
}
},
{
"name": "evaluate_js_es3",
"title": "Evaluate JavaScript ES3",
"description": "Evaluate JavaScript with ES3 syntax (use ES3 syntax strictly)",
"inputSchema": {
"type": "object",
"properties": {
"script": {
"type": "string"
}
},
"required": ["script"]
}
}
],
"isError": isError
};
}
if (method == "tools/call") {
var function_calling_name = params.name;
if (function_calling_name == "add_both_numbers") {
return {
"content": [
{
"type": "text",
"text": "Result is " + (parseFloat(params.arguments.a) + parseFloat(params.arguments.b))
}
],
"isError": isError
};
}
if (function_calling_name == "evaluate_js_es3") {
return {
"content": [
{
"type": "text",
"text": (function(script) {
try {
return String(eval(script));
} catch (e) {
return "Error";
isError = true;
}
})(params.arguments.script)
}
]
}
}
}
isError = true;
return {
"isError": isError
};
})
);
});
server.listen();
}
exports.main = main;