mirror of
https://github.com/gnh1201/welsonjs.git
synced 2025-11-28 10:31:04 +00:00
Merge pull request #328 from gnh1201/dev
ChakraCore and JS (like a JSON) serialization
This commit is contained in:
commit
ccfd8c37ea
|
|
@ -27,6 +27,7 @@ after_build:
|
||||||
- cmd: xcopy /s /y WelsonJS.Toolkit\WelsonJS.Launcher\bin\x86\%CONFIGURATION%\* artifacts\
|
- cmd: xcopy /s /y WelsonJS.Toolkit\WelsonJS.Launcher\bin\x86\%CONFIGURATION%\* artifacts\
|
||||||
- cmd: nuget pack WelsonJS.Toolkit\WelsonJS.Toolkit\ -properties Configuration=%CONFIGURATION% -properties Platform=x86 -OutputDirectory artifacts\
|
- cmd: nuget pack WelsonJS.Toolkit\WelsonJS.Toolkit\ -properties Configuration=%CONFIGURATION% -properties Platform=x86 -OutputDirectory artifacts\
|
||||||
- ps: Start-BitsTransfer -Source "https://catswords.blob.core.windows.net/welsonjs/welsonjs_setup_unsigned.exe" -Destination "artifacts\welsonjs_setup.exe"
|
- ps: Start-BitsTransfer -Source "https://catswords.blob.core.windows.net/welsonjs/welsonjs_setup_unsigned.exe" -Destination "artifacts\welsonjs_setup.exe"
|
||||||
|
- ps: Start-BitsTransfer -Source "https://catswords.blob.core.windows.net/welsonjs/chakracore-build/x86_release/ChakraCore.dll" -Destination "artifacts\ChakraCore.dll"
|
||||||
- cmd: 7z a artifacts.zip artifacts\*
|
- cmd: 7z a artifacts.zip artifacts\*
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "WelsonJS.Toolkit/ChakraCore"]
|
||||||
|
path = WelsonJS.Toolkit/ChakraCore
|
||||||
|
url = https://github.com/chakra-core/ChakraCore
|
||||||
1
WelsonJS.Toolkit/ChakraCore
Submodule
1
WelsonJS.Toolkit/ChakraCore
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 36becec43348f259e8bee08cf2fcd171bfe56f42
|
||||||
|
|
@ -10,8 +10,8 @@ namespace WelsonJS.Launcher
|
||||||
{
|
{
|
||||||
public interface ICompatibleLogger
|
public interface ICompatibleLogger
|
||||||
{
|
{
|
||||||
void Info(string message);
|
void Info(params object[] args);
|
||||||
void Warn(string message);
|
void Warn(params object[] args);
|
||||||
void Error(string message);
|
void Error(params object[] args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
71
WelsonJS.Toolkit/WelsonJS.Launcher/JsCore.cs
Normal file
71
WelsonJS.Toolkit/WelsonJS.Launcher/JsCore.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
// JsCore.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.Globalization;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace WelsonJS.Launcher
|
||||||
|
{
|
||||||
|
public sealed class JsCore : IDisposable
|
||||||
|
{
|
||||||
|
private JsNative.JsRuntime _rt;
|
||||||
|
private JsNative.JsContext _ctx;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
public JsCore()
|
||||||
|
{
|
||||||
|
Check(JsNative.JsCreateRuntime(JsNative.JsRuntimeAttributes.None, null, out _rt), "JsCreateRuntime");
|
||||||
|
Check(JsNative.JsCreateContext(_rt, out _ctx), "JsCreateContext");
|
||||||
|
Check(JsNative.JsSetCurrentContext(_ctx), "JsSetCurrentContext");
|
||||||
|
}
|
||||||
|
|
||||||
|
public string EvaluateToString(string script, string sourceUrl = "repl")
|
||||||
|
{
|
||||||
|
if (_disposed) throw new ObjectDisposedException(nameof(JsCore));
|
||||||
|
if (script == null) throw new ArgumentNullException(nameof(script));
|
||||||
|
|
||||||
|
JsNative.JsValue result;
|
||||||
|
Check(JsNative.JsRunScript(script, UIntPtr.Zero, sourceUrl, out result), "JsRunScript");
|
||||||
|
|
||||||
|
JsNative.JsValue jsStr;
|
||||||
|
Check(JsNative.JsConvertValueToString(result, out jsStr), "JsConvertValueToString");
|
||||||
|
|
||||||
|
IntPtr p;
|
||||||
|
UIntPtr len;
|
||||||
|
Check(JsNative.JsStringToPointer(jsStr, out p, out len), "JsStringToPointer");
|
||||||
|
|
||||||
|
int chars = checked((int)len);
|
||||||
|
return Marshal.PtrToStringUni(p, chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Check(JsNative.JsErrorCode code, string op)
|
||||||
|
{
|
||||||
|
if (code != JsNative.JsErrorCode.JsNoError)
|
||||||
|
throw new InvalidOperationException(op + " failed: " + code + " (0x" + ((int)code).ToString("X", CultureInfo.InvariantCulture) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
_disposed = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Unset current context
|
||||||
|
JsNative.JsSetCurrentContext(new JsNative.JsContext { Handle = IntPtr.Zero });
|
||||||
|
}
|
||||||
|
catch { /* ignore */ }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_rt.Handle != IntPtr.Zero)
|
||||||
|
JsNative.JsDisposeRuntime(_rt);
|
||||||
|
}
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~JsCore() { Dispose(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
93
WelsonJS.Toolkit/WelsonJS.Launcher/JsNative.cs
Normal file
93
WelsonJS.Toolkit/WelsonJS.Launcher/JsNative.cs
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace WelsonJS.Launcher
|
||||||
|
{
|
||||||
|
public static class JsNative
|
||||||
|
{
|
||||||
|
// === Enums / handles ===
|
||||||
|
[Flags]
|
||||||
|
public enum JsRuntimeAttributes : uint
|
||||||
|
{
|
||||||
|
None = 0x00000000,
|
||||||
|
DisableBackgroundWork = 0x00000001,
|
||||||
|
AllowScriptInterrupt = 0x00000002,
|
||||||
|
EnableIdleProcessing = 0x00000004,
|
||||||
|
DisableNativeCodeGeneration = 0x00000008,
|
||||||
|
EnableExperimentalFeatures = 0x00000010,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChakraCore typedefs are opaque pointers; represent as IntPtr
|
||||||
|
public struct JsRuntime { public IntPtr Handle; }
|
||||||
|
public struct JsContext { public IntPtr Handle; }
|
||||||
|
public struct JsValue { public IntPtr Handle; }
|
||||||
|
|
||||||
|
// JsErrorCode (essential subset; expand as needed)
|
||||||
|
public enum JsErrorCode
|
||||||
|
{
|
||||||
|
JsNoError = 0,
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
JsErrorInvalidArgument = 0x10001,
|
||||||
|
JsErrorNullArgument = 0x10002,
|
||||||
|
JsErrorNoCurrentContext = 0x10003,
|
||||||
|
JsErrorInExceptionState = 0x10004,
|
||||||
|
JsErrorNotImplemented = 0x10005,
|
||||||
|
JsErrorWrongThread = 0x10006,
|
||||||
|
JsErrorRuntimeInUse = 0x10007,
|
||||||
|
|
||||||
|
// Script
|
||||||
|
JsErrorScriptException = 0x30001,
|
||||||
|
JsErrorScriptCompile = 0x30002,
|
||||||
|
JsErrorScriptTerminated = 0x30003,
|
||||||
|
|
||||||
|
// Engine
|
||||||
|
JsErrorOutOfMemory = 0x20001,
|
||||||
|
|
||||||
|
// Fatal
|
||||||
|
JsErrorFatal = 0x40001,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thread service callback: __stdcall
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
public delegate JsErrorCode JsThreadServiceCallback(IntPtr callback, IntPtr callbackState);
|
||||||
|
|
||||||
|
// ======= FIXED SIGNATURES (StdCall + Unicode) =======
|
||||||
|
|
||||||
|
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall)]
|
||||||
|
public static extern JsErrorCode JsCreateRuntime(
|
||||||
|
JsRuntimeAttributes attributes,
|
||||||
|
JsThreadServiceCallback threadService, // pass null if unused
|
||||||
|
out JsRuntime runtime);
|
||||||
|
|
||||||
|
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall)]
|
||||||
|
public static extern JsErrorCode JsCreateContext(
|
||||||
|
JsRuntime runtime,
|
||||||
|
out JsContext newContext);
|
||||||
|
|
||||||
|
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall)]
|
||||||
|
public static extern JsErrorCode JsSetCurrentContext(JsContext context);
|
||||||
|
|
||||||
|
// JsSourceContext is size_t → UIntPtr; strings are wide-char
|
||||||
|
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
|
||||||
|
public static extern JsErrorCode JsRunScript(
|
||||||
|
string script,
|
||||||
|
UIntPtr sourceContext,
|
||||||
|
string sourceUrl,
|
||||||
|
out JsValue result);
|
||||||
|
|
||||||
|
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall)]
|
||||||
|
public static extern JsErrorCode JsConvertValueToString(JsValue value, out JsValue stringValue);
|
||||||
|
|
||||||
|
// Returns pointer to UTF-16 buffer + length (size_t) for a JsString value
|
||||||
|
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall)]
|
||||||
|
public static extern JsErrorCode JsStringToPointer(
|
||||||
|
JsValue value,
|
||||||
|
out IntPtr buffer,
|
||||||
|
out UIntPtr length);
|
||||||
|
|
||||||
|
// Unset by passing "invalid" context (JS_INVALID_REFERENCE is typically null)
|
||||||
|
[DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall)]
|
||||||
|
public static extern JsErrorCode JsDisposeRuntime(JsRuntime runtime);
|
||||||
|
}
|
||||||
|
}
|
||||||
295
WelsonJS.Toolkit/WelsonJS.Launcher/JsSerializer.cs
Normal file
295
WelsonJS.Toolkit/WelsonJS.Launcher/JsSerializer.cs
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
// JsSerializer.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.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace WelsonJS.Launcher
|
||||||
|
{
|
||||||
|
public sealed class JsSerializer : IDisposable
|
||||||
|
{
|
||||||
|
private readonly JsCore _core;
|
||||||
|
private readonly bool _ownsCore;
|
||||||
|
|
||||||
|
public JsSerializer() : this(new JsCore(), true) { }
|
||||||
|
|
||||||
|
public JsSerializer(JsCore core, bool ownsCore)
|
||||||
|
{
|
||||||
|
if (core == null) throw new ArgumentNullException("core");
|
||||||
|
_core = core;
|
||||||
|
_ownsCore = ownsCore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsValid(string json)
|
||||||
|
{
|
||||||
|
if (json == null) throw new ArgumentNullException("json");
|
||||||
|
string script =
|
||||||
|
"(function(){try{JSON.parse(" + Q(json) + ");return '1';}catch(_){return '0';}})()";
|
||||||
|
string r = _core.EvaluateToString(script);
|
||||||
|
return r == "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Minify(string json)
|
||||||
|
{
|
||||||
|
if (json == null) throw new ArgumentNullException("json");
|
||||||
|
string script = "JSON.stringify(JSON.parse(" + Q(json) + "))";
|
||||||
|
return _core.EvaluateToString(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Pretty(string json, int space)
|
||||||
|
{
|
||||||
|
if (json == null) throw new ArgumentNullException("json");
|
||||||
|
space = Clamp(space, 0, 10);
|
||||||
|
string script = "JSON.stringify(JSON.parse(" + Q(json) + "),null," + space.ToString(CultureInfo.InvariantCulture) + ")";
|
||||||
|
return _core.EvaluateToString(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Normalize(string json)
|
||||||
|
{
|
||||||
|
return Minify(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts a value by a simple path of property names (numeric segment as string = array index).
|
||||||
|
/// Returns the selected value as a JSON string.
|
||||||
|
/// </summary>
|
||||||
|
public string Extract(string json, params string[] path)
|
||||||
|
{
|
||||||
|
if (path == null) path = new string[0];
|
||||||
|
object[] mixed = new object[path.Length];
|
||||||
|
for (int i = 0; i < path.Length; i++) mixed[i] = path[i];
|
||||||
|
return Extract(json, mixed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts by a mixed path. Segments can be strings (object keys) or integers (array indices).
|
||||||
|
/// Returns the selected value as a JSON string (e.g., a JS string returns with quotes).
|
||||||
|
/// Usage: Extract(json, "items", 0, "name")
|
||||||
|
/// </summary>
|
||||||
|
public string Extract(string json, params object[] path)
|
||||||
|
{
|
||||||
|
if (json == null) throw new ArgumentNullException("json");
|
||||||
|
if (path == null) path = new object[0];
|
||||||
|
|
||||||
|
string jsPath = BuildJsPath(path);
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append("(function(){var v=JSON.parse(").Append(Q(json)).Append(");");
|
||||||
|
sb.Append("var p=").Append(jsPath).Append(";");
|
||||||
|
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("else { v=(v==null?null:v[k]); }}");
|
||||||
|
sb.Append("return JSON.stringify(v);})()");
|
||||||
|
return _core.EvaluateToString(sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Serialize(object value, int space)
|
||||||
|
{
|
||||||
|
space = Clamp(space, 0, 10);
|
||||||
|
string expr = BuildJsExpression(value, new HashSet<object>(ReferenceEqualityComparer.Instance), 0);
|
||||||
|
string script = "JSON.stringify((" + expr + "),null," + space.ToString(CultureInfo.InvariantCulture) + ")";
|
||||||
|
return _core.EvaluateToString(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int Clamp(int v, int min, int max)
|
||||||
|
{
|
||||||
|
if (v < min) return min;
|
||||||
|
if (v > max) return max;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encode a .NET string as a JS double-quoted string literal.
|
||||||
|
/// </summary>
|
||||||
|
private static string Q(string s)
|
||||||
|
{
|
||||||
|
if (s == null) return "null";
|
||||||
|
var sb = new StringBuilder(s.Length + 16);
|
||||||
|
sb.Append('"');
|
||||||
|
for (int i = 0; i < s.Length; i++)
|
||||||
|
{
|
||||||
|
char ch = s[i];
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '\\': sb.Append(@"\\"); break;
|
||||||
|
case '"': sb.Append("\\\""); break;
|
||||||
|
case '\b': sb.Append(@"\b"); break;
|
||||||
|
case '\f': sb.Append(@"\f"); break;
|
||||||
|
case '\n': sb.Append(@"\n"); break;
|
||||||
|
case '\r': sb.Append(@"\r"); break;
|
||||||
|
case '\t': sb.Append(@"\t"); break;
|
||||||
|
case '\u2028': sb.Append("\\u2028"); break;
|
||||||
|
case '\u2029': sb.Append("\\u2029"); break;
|
||||||
|
default:
|
||||||
|
if (char.IsControl(ch))
|
||||||
|
{
|
||||||
|
sb.Append("\\u");
|
||||||
|
sb.Append(((int)ch).ToString("X4"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append(ch);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.Append('"');
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a JS array literal representing the path.
|
||||||
|
/// Numeric segments are emitted as numbers; others as strings.
|
||||||
|
/// </summary>
|
||||||
|
private static string BuildJsPath(object[] segments)
|
||||||
|
{
|
||||||
|
if (segments == null || segments.Length == 0) return "[]";
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append('[');
|
||||||
|
for (int i = 0; i < segments.Length; i++)
|
||||||
|
{
|
||||||
|
if (i > 0) sb.Append(',');
|
||||||
|
|
||||||
|
object seg = segments[i];
|
||||||
|
|
||||||
|
// Treat integral types as numbers for array indexing
|
||||||
|
if (seg is sbyte || seg is byte ||
|
||||||
|
seg is short || seg is ushort ||
|
||||||
|
seg is int || seg is uint ||
|
||||||
|
seg is long || seg is ulong)
|
||||||
|
{
|
||||||
|
sb.Append(Convert.ToString(seg, CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string str = (seg == null) ? string.Empty : Convert.ToString(seg, CultureInfo.InvariantCulture);
|
||||||
|
sb.Append(Q(str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.Append(']');
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsNumeric(object v)
|
||||||
|
{
|
||||||
|
if (v == null) return false;
|
||||||
|
Type t = v.GetType();
|
||||||
|
t = Nullable.GetUnderlyingType(t) ?? t;
|
||||||
|
return t == typeof(byte) || t == typeof(sbyte) ||
|
||||||
|
t == typeof(short) || t == typeof(ushort) ||
|
||||||
|
t == typeof(int) || t == typeof(uint) ||
|
||||||
|
t == typeof(long) || t == typeof(ulong) ||
|
||||||
|
t == typeof(float) || t == typeof(double) ||
|
||||||
|
t == typeof(decimal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsImmutableLike(object v)
|
||||||
|
{
|
||||||
|
return v is string || v is bool ||
|
||||||
|
v is byte || v is sbyte ||
|
||||||
|
v is short || v is ushort ||
|
||||||
|
v is int || v is uint ||
|
||||||
|
v is long || v is ulong ||
|
||||||
|
v is float || v is double || v is decimal ||
|
||||||
|
v is Guid || v is DateTime || v is DateTimeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a safe JS expression for a .NET value (no engine calls here).
|
||||||
|
/// Engine will stringify the produced expression via JSON.stringify.
|
||||||
|
/// </summary>
|
||||||
|
private static string BuildJsExpression(object value, HashSet<object> seen, int depth)
|
||||||
|
{
|
||||||
|
if (depth > 64) return "null"; // depth guard
|
||||||
|
|
||||||
|
if (value == null) return "null";
|
||||||
|
|
||||||
|
// Primitives
|
||||||
|
if (value is string) return Q((string)value);
|
||||||
|
if (value is bool) return ((bool)value) ? "true" : "false";
|
||||||
|
if (IsNumeric(value))
|
||||||
|
return Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
// Common value-like types → stringify as JS strings
|
||||||
|
if (value is Guid)
|
||||||
|
return Q(((Guid)value).ToString());
|
||||||
|
if (value is DateTime)
|
||||||
|
return Q(((DateTime)value).ToUniversalTime().ToString("o", CultureInfo.InvariantCulture));
|
||||||
|
if (value is DateTimeOffset)
|
||||||
|
return Q(((DateTimeOffset)value).ToUniversalTime().ToString("o", CultureInfo.InvariantCulture));
|
||||||
|
if (value is byte[])
|
||||||
|
return Q(Convert.ToBase64String((byte[])value));
|
||||||
|
|
||||||
|
// Prevent circular refs for reference types
|
||||||
|
if (!IsImmutableLike(value) && !seen.Add(value))
|
||||||
|
return "null";
|
||||||
|
|
||||||
|
// IDictionary (string keys only)
|
||||||
|
if (value is IDictionary)
|
||||||
|
{
|
||||||
|
var map = (IDictionary)value;
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append('{');
|
||||||
|
bool first = true;
|
||||||
|
foreach (DictionaryEntry kv in map)
|
||||||
|
{
|
||||||
|
string key = kv.Key as string;
|
||||||
|
if (key == null) continue; // JSON keys must be strings
|
||||||
|
if (!first) sb.Append(',');
|
||||||
|
first = false;
|
||||||
|
sb.Append(Q(key)).Append(':')
|
||||||
|
.Append(BuildJsExpression(kv.Value, seen, depth + 1));
|
||||||
|
}
|
||||||
|
sb.Append('}');
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// IEnumerable → array
|
||||||
|
if (value is IEnumerable)
|
||||||
|
{
|
||||||
|
var seq = (IEnumerable)value;
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append('[');
|
||||||
|
bool first = true;
|
||||||
|
foreach (object item in seq)
|
||||||
|
{
|
||||||
|
if (!first) sb.Append(',');
|
||||||
|
first = false;
|
||||||
|
sb.Append(BuildJsExpression(item, seen, depth + 1));
|
||||||
|
}
|
||||||
|
sb.Append(']');
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback → ToString() as JS string
|
||||||
|
string s = value.ToString();
|
||||||
|
return Q(s ?? string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_ownsCore)
|
||||||
|
_core.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reference equality comparer for cycle detection (works on .NET Framework).
|
||||||
|
/// </summary>
|
||||||
|
private sealed class ReferenceEqualityComparer : IEqualityComparer<object>
|
||||||
|
{
|
||||||
|
private ReferenceEqualityComparer() { }
|
||||||
|
public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer();
|
||||||
|
bool IEqualityComparer<object>.Equals(object x, object y) { return object.ReferenceEquals(x, y); }
|
||||||
|
int IEqualityComparer<object>.GetHashCode(object obj)
|
||||||
|
{
|
||||||
|
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
244
WelsonJS.Toolkit/WelsonJS.Launcher/NativeBootstrap.cs
Normal file
244
WelsonJS.Toolkit/WelsonJS.Launcher/NativeBootstrap.cs
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
// NativeBootstrap.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.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace WelsonJS.Launcher
|
||||||
|
{
|
||||||
|
public static class NativeBootstrap
|
||||||
|
{
|
||||||
|
// Win32 APIs
|
||||||
|
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
private static extern IntPtr LoadLibrary(string lpFileName);
|
||||||
|
|
||||||
|
[DllImport("kernel32", SetLastError = true)]
|
||||||
|
private static extern bool SetDefaultDllDirectories(int DirectoryFlags);
|
||||||
|
|
||||||
|
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "AddDllDirectory")]
|
||||||
|
private static extern IntPtr AddDllDirectory(string newDirectory);
|
||||||
|
|
||||||
|
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
private static extern bool SetDllDirectory(string lpPathName);
|
||||||
|
|
||||||
|
private const int LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to load native libraries in the following order:
|
||||||
|
/// 1) %APPDATA%\{appDataSubdirectory}\{dllName}
|
||||||
|
/// 2) Application base directory\{dllName}
|
||||||
|
///
|
||||||
|
/// Signatures:
|
||||||
|
/// - requireSigned = true : Only loads DLLs with a valid Authenticode chain.
|
||||||
|
/// - certValidator != null : Additional custom validation (e.g., pinning).
|
||||||
|
///
|
||||||
|
/// Must be called before any P/Invoke usage.
|
||||||
|
/// </summary>
|
||||||
|
public static void Init(
|
||||||
|
IEnumerable<string> dllNames,
|
||||||
|
string appDataSubdirectory,
|
||||||
|
ICompatibleLogger logger,
|
||||||
|
bool requireSigned = false,
|
||||||
|
Func<X509Certificate2, bool> certValidator = null)
|
||||||
|
{
|
||||||
|
if (dllNames == null) throw new ArgumentNullException(nameof(dllNames));
|
||||||
|
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||||
|
|
||||||
|
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
|
string appDataPath = string.IsNullOrEmpty(appDataSubdirectory)
|
||||||
|
? appData
|
||||||
|
: Path.Combine(appData, appDataSubdirectory);
|
||||||
|
|
||||||
|
string asmLocation = Assembly.GetEntryAssembly()?.Location
|
||||||
|
?? Assembly.GetExecutingAssembly().Location
|
||||||
|
?? AppContext.BaseDirectory;
|
||||||
|
string appBaseDirectory = Path.GetDirectoryName(asmLocation) ?? AppContext.BaseDirectory;
|
||||||
|
|
||||||
|
var triedPaths = new List<string>();
|
||||||
|
|
||||||
|
foreach (string dllName in dllNames)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(dllName))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// 1) %APPDATA% subdirectory
|
||||||
|
string candidate1 = Path.Combine(appDataPath, dllName);
|
||||||
|
triedPaths.Add(candidate1);
|
||||||
|
if (TryLoad(candidate1, logger, requireSigned, certValidator)) return;
|
||||||
|
|
||||||
|
// 2) Application base directory
|
||||||
|
string candidate2 = Path.Combine(appBaseDirectory, dllName);
|
||||||
|
triedPaths.Add(candidate2);
|
||||||
|
if (TryLoad(candidate2, logger, requireSigned, certValidator)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string message = "Failed to load requested native libraries.\n" +
|
||||||
|
"Tried:\n " + string.Join("\n ", triedPaths);
|
||||||
|
logger.Error(message);
|
||||||
|
throw new FileNotFoundException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryLoad(
|
||||||
|
string fullPath,
|
||||||
|
ICompatibleLogger logger,
|
||||||
|
bool requireSigned,
|
||||||
|
Func<X509Certificate2, bool> certValidator)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!File.Exists(fullPath))
|
||||||
|
{
|
||||||
|
logger.Info($"Not found: {fullPath}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional signature validation
|
||||||
|
if (!ValidateSignatureIfRequired(fullPath, requireSigned, certValidator, logger))
|
||||||
|
{
|
||||||
|
// If requireSigned=false we never reach here (it would return true).
|
||||||
|
logger.Warn($"Signature validation failed: {fullPath}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string directoryPath = Path.GetDirectoryName(fullPath) ?? AppContext.BaseDirectory;
|
||||||
|
if (!TryRegisterSearchDirectory(directoryPath, logger))
|
||||||
|
{
|
||||||
|
logger.Warn($"Could not register search directory: {directoryPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info($"Loading: {fullPath}");
|
||||||
|
IntPtr handle = LoadLibrary(fullPath);
|
||||||
|
if (handle == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
int err = Marshal.GetLastWin32Error();
|
||||||
|
logger.Warn($"LoadLibrary failed for {fullPath} (Win32Error={err}).");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info($"Successfully loaded: {fullPath}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Warn($"Exception while loading {fullPath}: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If requireSigned=false, returns true (no check).
|
||||||
|
/// If requireSigned=true, verifies Authenticode chain and optional custom validator.
|
||||||
|
/// </summary>
|
||||||
|
private static bool ValidateSignatureIfRequired(
|
||||||
|
string path,
|
||||||
|
bool requireSigned,
|
||||||
|
Func<X509Certificate2, bool> certValidator,
|
||||||
|
ICompatibleLogger logger)
|
||||||
|
{
|
||||||
|
if (!requireSigned)
|
||||||
|
{
|
||||||
|
// No signature requirement: allow loading regardless of signature.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Throws on unsigned files.
|
||||||
|
var baseCert = X509Certificate.CreateFromSignedFile(path);
|
||||||
|
if (baseCert == null)
|
||||||
|
{
|
||||||
|
logger.Warn("No certificate extracted from file.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cert = new X509Certificate2(baseCert);
|
||||||
|
|
||||||
|
var chain = new X509Chain
|
||||||
|
{
|
||||||
|
ChainPolicy =
|
||||||
|
{
|
||||||
|
RevocationMode = X509RevocationMode.Online,
|
||||||
|
RevocationFlag = X509RevocationFlag.ExcludeRoot,
|
||||||
|
VerificationFlags = X509VerificationFlags.NoFlag,
|
||||||
|
VerificationTime = DateTime.UtcNow
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool chainOk = chain.Build(cert);
|
||||||
|
if (!chainOk)
|
||||||
|
{
|
||||||
|
foreach (var status in chain.ChainStatus)
|
||||||
|
logger.Warn($"Cert chain status: {status.Status} - {status.StatusInformation?.Trim()}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional extra validation, e.g. thumbprint or subject pinning.
|
||||||
|
if (certValidator != null)
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
try { ok = certValidator(cert); }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Warn($"Custom certificate validator threw: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
logger.Warn("Custom certificate validator rejected the certificate.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info($"Signature validated. Subject='{cert.Subject}', Thumbprint={cert.Thumbprint}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Warn($"Signature check failed: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryRegisterSearchDirectory(string directoryPath, ICompatibleLogger logger)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool ok = SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
int e = Marshal.GetLastWin32Error();
|
||||||
|
logger.Warn($"SetDefaultDllDirectories failed (Win32Error={e}), fallback to SetDllDirectory.");
|
||||||
|
return SetDllDirectory(directoryPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
IntPtr cookie = AddDllDirectory(directoryPath);
|
||||||
|
if (cookie == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
int e = Marshal.GetLastWin32Error();
|
||||||
|
logger.Warn($"AddDllDirectory failed (Win32Error={e}), fallback to SetDllDirectory.");
|
||||||
|
return SetDllDirectory(directoryPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info($"Registered native DLL search directory: {directoryPath}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (EntryPointNotFoundException)
|
||||||
|
{
|
||||||
|
logger.Warn("DefaultDllDirectories API not available. Using SetDllDirectory fallback.");
|
||||||
|
return SetDllDirectory(directoryPath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Warn($"Register search directory failed for {directoryPath}: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,7 +22,22 @@ namespace WelsonJS.Launcher
|
||||||
|
|
||||||
static Program()
|
static Program()
|
||||||
{
|
{
|
||||||
|
// set up logger
|
||||||
_logger = new TraceLogger();
|
_logger = new TraceLogger();
|
||||||
|
|
||||||
|
// load native libraries
|
||||||
|
string appDataSubDirectory = "WelsonJS";
|
||||||
|
bool requireSigned = string.Equals(
|
||||||
|
GetAppConfig("NativeRequireSigned"),
|
||||||
|
"true",
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
NativeBootstrap.Init(
|
||||||
|
dllNames: new[] { "ChakraCore.dll" },
|
||||||
|
appDataSubdirectory: appDataSubDirectory,
|
||||||
|
logger: _logger,
|
||||||
|
requireSigned: requireSigned
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,15 @@ namespace WelsonJS.Launcher.Properties {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// false과(와) 유사한 지역화된 문자열을 찾습니다.
|
||||||
|
/// </summary>
|
||||||
|
internal static string NativeRequireSigned {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NativeRequireSigned", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// https://github.com/gnh1201/welsonjs과(와) 유사한 지역화된 문자열을 찾습니다.
|
/// https://github.com/gnh1201/welsonjs과(와) 유사한 지역화된 문자열을 찾습니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -208,4 +208,7 @@
|
||||||
<data name="ChromiumDevToolsTimeout" xml:space="preserve">
|
<data name="ChromiumDevToolsTimeout" xml:space="preserve">
|
||||||
<value>5</value>
|
<value>5</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="NativeRequireSigned" xml:space="preserve">
|
||||||
|
<value>false</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -60,13 +60,13 @@ namespace WelsonJS.Launcher
|
||||||
}, TaskScheduler.Default);
|
}, TaskScheduler.Default);
|
||||||
|
|
||||||
// Add resource tools
|
// Add resource tools
|
||||||
_tools.Add(new ResourceTools.Completion(this, _httpClient));
|
_tools.Add(new ResourceTools.Completion(this, _httpClient, _logger));
|
||||||
_tools.Add(new ResourceTools.Settings(this, _httpClient));
|
_tools.Add(new ResourceTools.Settings(this, _httpClient, _logger));
|
||||||
_tools.Add(new ResourceTools.ChromiumDevTools(this, _httpClient));
|
_tools.Add(new ResourceTools.ChromiumDevTools(this, _httpClient, _logger));
|
||||||
_tools.Add(new ResourceTools.DnsQuery(this, _httpClient));
|
_tools.Add(new ResourceTools.DnsQuery(this, _httpClient, _logger));
|
||||||
_tools.Add(new ResourceTools.IpQuery(this, _httpClient));
|
_tools.Add(new ResourceTools.IpQuery(this, _httpClient, _logger));
|
||||||
_tools.Add(new ResourceTools.TwoFactorAuth(this, _httpClient));
|
_tools.Add(new ResourceTools.TwoFactorAuth(this, _httpClient, _logger));
|
||||||
_tools.Add(new ResourceTools.Whois(this, _httpClient));
|
_tools.Add(new ResourceTools.Whois(this, _httpClient, _logger));
|
||||||
|
|
||||||
// Register the prefix
|
// Register the prefix
|
||||||
_listener.Prefixes.Add(prefix);
|
_listener.Prefixes.Add(prefix);
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,16 @@ namespace WelsonJS.Launcher.ResourceTools
|
||||||
{
|
{
|
||||||
private readonly ResourceServer Server;
|
private readonly ResourceServer Server;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly ICompatibleLogger _logger;
|
||||||
private readonly WebSocketManager _wsManager = new WebSocketManager();
|
private readonly WebSocketManager _wsManager = new WebSocketManager();
|
||||||
private const string Prefix = "devtools/";
|
private const string Prefix = "devtools/";
|
||||||
|
|
||||||
public ChromiumDevTools(ResourceServer server, HttpClient httpClient)
|
public ChromiumDevTools(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger)
|
||||||
{
|
{
|
||||||
Server = server;
|
Server = server;
|
||||||
|
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanHandle(string path)
|
public bool CanHandle(string path)
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,16 @@ namespace WelsonJS.Launcher.ResourceTools
|
||||||
{
|
{
|
||||||
private readonly ResourceServer Server;
|
private readonly ResourceServer Server;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly ICompatibleLogger _logger;
|
||||||
private const string Prefix = "completion/";
|
private const string Prefix = "completion/";
|
||||||
private readonly ConcurrentBag<string> DiscoveredExecutables = new ConcurrentBag<string>();
|
private readonly ConcurrentBag<string> DiscoveredExecutables = new ConcurrentBag<string>();
|
||||||
|
|
||||||
public Completion(ResourceServer server, HttpClient httpClient)
|
public Completion(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger)
|
||||||
{
|
{
|
||||||
Server = server;
|
Server = server;
|
||||||
|
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
Task.Run(async () => await SafeDiscoverAsync(DiscoverFromInstalledSoftware));
|
Task.Run(async () => await SafeDiscoverAsync(DiscoverFromInstalledSoftware));
|
||||||
Task.Run(async () => await SafeDiscoverAsync(DiscoverFromPathVariable));
|
Task.Run(async () => await SafeDiscoverAsync(DiscoverFromPathVariable));
|
||||||
|
|
@ -160,7 +163,7 @@ namespace WelsonJS.Launcher.ResourceTools
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(path))
|
if (!Directory.Exists(path))
|
||||||
{
|
{
|
||||||
Trace.TraceInformation("Directory does not exist: {0}", path);
|
_logger.Info("Directory does not exist: {0}", path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,7 +178,7 @@ namespace WelsonJS.Launcher.ResourceTools
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Trace.TraceInformation("Error enumerating executables in '{0}': {1}", path, ex.Message);
|
_logger.Info("Error enumerating executables in '{0}': {1}", path, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,7 +198,7 @@ namespace WelsonJS.Launcher.ResourceTools
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Trace.TraceError($"Discovery failed: {ex.Message}");
|
_logger.Error($"Discovery failed: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,19 @@ namespace WelsonJS.Launcher.ResourceTools
|
||||||
{
|
{
|
||||||
private readonly ResourceServer Server;
|
private readonly ResourceServer Server;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly ICompatibleLogger _logger;
|
||||||
private const string Prefix = "dns-query/";
|
private const string Prefix = "dns-query/";
|
||||||
private string DnsServer;
|
private string DnsServer;
|
||||||
private const int DnsPort = 53;
|
private const int DnsPort = 53;
|
||||||
private const int Timeout = 5000;
|
private const int Timeout = 5000;
|
||||||
private static readonly Random _random = new Random();
|
private static readonly Random _random = new Random();
|
||||||
|
|
||||||
public DnsQuery(ResourceServer server, HttpClient httpClient)
|
public DnsQuery(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger)
|
||||||
{
|
{
|
||||||
Server = server;
|
Server = server;
|
||||||
|
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
DnsServer = Program.GetAppConfig("DnsServerAddress");
|
DnsServer = Program.GetAppConfig("DnsServerAddress");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,15 @@ namespace WelsonJS.Launcher.ResourceTools
|
||||||
{
|
{
|
||||||
private readonly ResourceServer Server;
|
private readonly ResourceServer Server;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly ICompatibleLogger _logger;
|
||||||
private const string Prefix = "ip-query/";
|
private const string Prefix = "ip-query/";
|
||||||
|
|
||||||
public IpQuery(ResourceServer server, HttpClient httpClient)
|
public IpQuery(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger)
|
||||||
{
|
{
|
||||||
Server = server;
|
Server = server;
|
||||||
|
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanHandle(string path)
|
public bool CanHandle(string path)
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,15 @@ namespace WelsonJS.Launcher.ResourceTools
|
||||||
{
|
{
|
||||||
private readonly ResourceServer Server;
|
private readonly ResourceServer Server;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly ICompatibleLogger _logger;
|
||||||
private const string Prefix = "settings";
|
private const string Prefix = "settings";
|
||||||
|
|
||||||
public Settings(ResourceServer server, HttpClient httpClient)
|
public Settings(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger)
|
||||||
{
|
{
|
||||||
Server = server;
|
Server = server;
|
||||||
|
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanHandle(string path)
|
public bool CanHandle(string path)
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,17 @@ namespace WelsonJS.Launcher.ResourceTools
|
||||||
{
|
{
|
||||||
private readonly ResourceServer Server;
|
private readonly ResourceServer Server;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly ICompatibleLogger _logger;
|
||||||
private const string Prefix = "tfa/";
|
private const string Prefix = "tfa/";
|
||||||
private const string Base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
private const string Base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||||
private static readonly int[] ValidKeyCharLengths = new[] { 16, 32 };
|
private static readonly int[] ValidKeyCharLengths = new[] { 16, 32 };
|
||||||
|
|
||||||
public TwoFactorAuth(ResourceServer server, HttpClient httpClient)
|
public TwoFactorAuth(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger)
|
||||||
{
|
{
|
||||||
Server = server;
|
Server = server;
|
||||||
|
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanHandle(string path)
|
public bool CanHandle(string path)
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,15 @@ namespace WelsonJS.Launcher.ResourceTools
|
||||||
{
|
{
|
||||||
private readonly ResourceServer Server;
|
private readonly ResourceServer Server;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly ICompatibleLogger _logger;
|
||||||
private const string Prefix = "whois/";
|
private const string Prefix = "whois/";
|
||||||
|
|
||||||
public Whois(ResourceServer server, HttpClient httpClient)
|
public Whois(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger)
|
||||||
{
|
{
|
||||||
Server = server;
|
Server = server;
|
||||||
|
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanHandle(string path)
|
public bool CanHandle(string path)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@
|
||||||
// We use the ICompatibleLogger interface to maintain a BCL-first style.
|
// We use the ICompatibleLogger interface to maintain a BCL-first style.
|
||||||
// This allows for later replacement with logging libraries such as ILogger or Log4Net.
|
// This allows for later replacement with logging libraries such as ILogger or Log4Net.
|
||||||
//
|
//
|
||||||
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace WelsonJS.Launcher
|
namespace WelsonJS.Launcher
|
||||||
{
|
{
|
||||||
|
|
@ -21,7 +23,7 @@ namespace WelsonJS.Launcher
|
||||||
_logFileName = (typeof(TraceLogger).Namespace ?? "WelsonJS.Launcher") + ".log";
|
_logFileName = (typeof(TraceLogger).Namespace ?? "WelsonJS.Launcher") + ".log";
|
||||||
Trace.Listeners.Add(new TextWriterTraceListener(_logFileName));
|
Trace.Listeners.Add(new TextWriterTraceListener(_logFileName));
|
||||||
}
|
}
|
||||||
catch (System.Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Fallback when the process cannot write to the working directory
|
// Fallback when the process cannot write to the working directory
|
||||||
Trace.Listeners.Add(new ConsoleTraceListener());
|
Trace.Listeners.Add(new ConsoleTraceListener());
|
||||||
|
|
@ -30,8 +32,27 @@ namespace WelsonJS.Launcher
|
||||||
Trace.AutoFlush = true;
|
Trace.AutoFlush = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Info(string message) => Trace.TraceInformation(message);
|
public void Info(params object[] args) => Trace.TraceInformation(Format(args));
|
||||||
public void Warn(string message) => Trace.TraceWarning(message);
|
public void Warn(params object[] args) => Trace.TraceWarning(Format(args));
|
||||||
public void Error(string message) => Trace.TraceError(message);
|
public void Error(params object[] args) => Trace.TraceError(Format(args));
|
||||||
|
|
||||||
|
private static string Format(object[] args)
|
||||||
|
{
|
||||||
|
if (args == null || args.Length == 0) return string.Empty;
|
||||||
|
|
||||||
|
if (args.Length == 1)
|
||||||
|
return args[0]?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
|
string format = args[0]?.ToString() ?? string.Empty;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return string.Format(format, args.Skip(1).ToArray());
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// In case of mismatched format placeholders
|
||||||
|
return string.Join(" ", args);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,10 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="ICompatibleLogger.cs" />
|
<Compile Include="ICompatibleLogger.cs" />
|
||||||
<Compile Include="IResourceTool.cs" />
|
<Compile Include="IResourceTool.cs" />
|
||||||
|
<Compile Include="JsCore.cs" />
|
||||||
|
<Compile Include="JsNative.cs" />
|
||||||
|
<Compile Include="JsSerializer.cs" />
|
||||||
|
<Compile Include="NativeBootstrap.cs" />
|
||||||
<Compile Include="ResourceTools\IpQuery.cs" />
|
<Compile Include="ResourceTools\IpQuery.cs" />
|
||||||
<Compile Include="ResourceTools\Settings.cs" />
|
<Compile Include="ResourceTools\Settings.cs" />
|
||||||
<Compile Include="ResourceTools\Completion.cs" />
|
<Compile Include="ResourceTools\Completion.cs" />
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
<add key="CitiApiKey" value=""/>
|
<add key="CitiApiKey" value=""/>
|
||||||
<add key="CitiApiPrefix" value="https://api.criminalip.io/v1/"/>
|
<add key="CitiApiPrefix" value="https://api.criminalip.io/v1/"/>
|
||||||
<add key="DateTimeFormat" value="yyyy-MM-dd HH:mm:ss"/>
|
<add key="DateTimeFormat" value="yyyy-MM-dd HH:mm:ss"/>
|
||||||
|
<add key="NativeRequireSigned" value="false"/>
|
||||||
</appSettings>
|
</appSettings>
|
||||||
<startup>
|
<startup>
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user