SocketJack 2.0.0
dotnet add package SocketJack --version 2.0.0
NuGet\Install-Package SocketJack -Version 2.0.0
<PackageReference Include="SocketJack" Version="2.0.0" />
<PackageVersion Include="SocketJack" Version="2.0.0" />
<PackageReference Include="SocketJack" />
paket add SocketJack --version 2.0.0
#r "nuget: SocketJack, 2.0.0"
#:package SocketJack@2.0.0
#addin nuget:?package=SocketJack&version=2.0.0
#tool nuget:?package=SocketJack&version=2.0.0
SocketJack
![]()
A batteries-included .NET networking stack for object transport, browser-native HTTP/WebSocket apps, WPF control sharing, and local AI model orchestration.
SocketJack handles framing, segmentation, serialization, compression, routing, and protocol detection so the application code can stay boring: Send(myObject) on one side, register a typed callback on the other, and let the transport worry about the bytes.
One library. Many transports. A surprisingly capable local AI control plane.
Targets: SocketJack core: .NET Standard 2.1 | SocketJack.WPF: net10.0-windows7.0
What's New in v2.0.0?
- LmVsProxy Web Console - a full browser workspace for LM Studio-backed chat, sessions, model selection, file uploads, prompt intellisense, service routing, diagnostics, and per-client permissions.
- Remote Admin for WPF hosts - view and control the LmVsProxy GUI from the web console through SocketJack.WPF capture/input providers without moving the real Windows cursor.
- WebAuth and host-local administration - token/basic auth, registration approvals, administrator roles, host-IP admin detection, and CORS-friendly local LLM client APIs.
- Per-client capability gates - enable or disable agent access, internet search, VS Copilot tools, file transfer, SQL admin, FTP, terminal commands, image uploads, and downloads by owner key.
- Session and artifact persistence - SocketJack's embedded data server now backs chat sessions, auth records, usage costs, filesystem access lists, and uploaded/downloaded files.
- Architecture upgrades -
MutableTcpServeris now the shared front door for HTTP, WebSocket, SocketJack protocols, SQL admin, FTP configuration, LLM client endpoints, and the web UI.
Features
| Category | Highlights |
|---|---|
| Transport Core | Unified TcpClient / TcpServer, UdpClient / UdpServer, HTTP, and WebSocket APIs on top of System.Net.Sockets, with consistent lifecycle events and typed callbacks. |
| Protocol Multiplexing | MutableTcpServer auto-detects HTTP, SocketJack, WebSocket, RTMP, and custom protocols on one port, then routes each connection through the right handler. |
| HTTP App Hosting | Route mapping (Map, Map<T>, MapStream, MapUploadStream), file and directory serving, .htaccess controls, chunked responses, upload streams, static caching, ETag, Last-Modified, and 304 support. |
| Serialization | Default System.Text.Json, pluggable ISerializer, custom converters, and type whitelist/blacklist support for safer object transport. |
| Peer-to-Peer | Peer discovery, host/client role management, relay-assisted NAT traversal, and metadata-driven routing through the Identifier class. |
| Compression and Throughput | Pluggable compression (GZipStream, DeflateStream), large configurable buffers, async I/O, automatic segmentation, outbound chunking, and bandwidth throttling. |
| Security | TLS via SslStream, X.509 auth, .htaccess IP allow/deny, HTTP Basic auth, file-pattern restrictions, WebAuth records, bearer tokens, and local-host admin detection. |
| WPF Remoting | Capture any WPF FrameworkElement, stream it as JPEG frames, and forward remote mouse/keyboard input to the target window. |
| LmVsProxy | Local LM Studio bridge for Visual Studio/Copilot-compatible tooling, browser chat, prompt services, per-client permissions, WebAuth, remote admin, terminal approvals, FTP, SQL admin, and usage-cost tracking. |
Supported Transports
TCP
The core transport. TcpClient and TcpServer provide reliable, ordered, stream-oriented communication with automatic message segmentation for arbitrarily large payloads. TLS is supported via SslStream and X509Certificate.
UDP
UdpClient and UdpServer mirror the TCP API but use connectionless datagrams. The same NetworkOptions, serialization, compression, peer-to-peer, and callback systems work identically. Payloads are limited by MaxDatagramSize (default 65,507 bytes).
| TCP | UDP | |
|---|---|---|
| Connection | Stream-oriented, persistent | Connectionless datagrams |
| Reliability | Guaranteed delivery & ordering | No built-in delivery guarantee |
| Max payload | Unlimited (automatic segmentation) | Limited by MaxDatagramSize |
| TLS | Supported via SslStream |
Not supported |
HTTP
HttpServer and HttpClient layer a familiar HTTP API on top of the TCP transport. Route mapping (Map, Map<T>, MapStream, MapUploadStream), static file serving (MapDirectory), .htaccess security, typed callbacks, chunked transfer-encoding, RTMP ingest, HTTPS/TLS, and automatic redirects are all built in.
| Class | Description |
|---|---|
HttpServer |
Extends TcpServer. Parses HTTP requests, resolves routes, serves static files, and writes responses. |
HttpClient |
Extends TcpClient. Sends HTTP/HTTPS requests with redirect and chunked-transfer support. |
MutableTcpServer |
Extends HttpServer. Auto-detects protocol (HTTP, SocketJack, WebSocket, RTMP, or custom) per-connection on a single port. |
BroadcastServer |
Attaches to an HttpServer to relay live video from OBS (RTMP or HTTP upload) to browser and VLC viewers via FLV. |
HtAccessBuilder |
Fluent builder for .htaccess rules: IP allow/deny, HTTP Basic auth, file restrictions, custom headers. |
HttpContext |
Carries the HttpRequest, HttpResponse, status code, and content type for a single request cycle. |
HttpRequest |
Parsed request: Method, Path, Headers, Body, BodyBytes, QueryString, QueryParameters. |
HttpResponse |
Response: StatusCodeNumber, Headers, Body/BodyBytes, ContentType. |
WebSocket
WebSocketClient and WebSocketServer implement RFC 6455 while sharing the same serialization, compression, P2P, and callback systems. The server handles the HTTP upgrade handshake automatically and can generate JavaScript class constructors for browser clients.
| TCP | WebSocket | |
|---|---|---|
| Protocol | Raw TCP stream | WebSocket frames (RFC 6455) |
| Handshake | TCP three-way handshake | HTTP Upgrade + WebSocket handshake |
| Browser support | Not natively supported | Full browser WebSocket API compatibility |
| Client connect | Connect(host, port) |
Connect(host, port) or ConnectAsync(uri) |
WPF Live Control Sharing
Requires the
SocketJack.WPFNuGet package.
The SocketJack.WPF library lets you share any WPF FrameworkElement over a TcpClient or UdpClient connection. The sharer captures JPEG frames at a configurable frame rate and streams them to a remote peer. The viewer displays those frames in an Image control and automatically forwards mouse input back, so the remote user can interact with the shared element as if it were local.
LmVsProxy – Local AI Model Bridge
LmVsProxy bridges Visual Studio's GitHub Copilot tool-calling interface with locally-hosted language models via LM Studio. Instead of paying per API call to cloud providers, run open-source models (Qwen, Mistral, Phi, and others) on your own hardware and only pay for electricity.
How it works:
- Visual Studio sends GitHub-format tool requests to the proxy
- LmVsProxy translates schemas and method calls to OpenAI-compatible format
- Requests forward to LM Studio's
/v1/chat/completionsendpoint - Responses convert back to VS-compatible SSE format
- Multi-turn conversations seamlessly relay tool results back to the model
Features:
- Full protocol translation (GitHub format ↔ OpenAI format)
- Built-in browser-based chat web UI via
ChatServerproperty (automatically hosted) - Streaming and non-streaming request support
- Single-port multiplexing with SocketJack's
MutableTcpServer - Support for any LM Studio-compatible model
Getting Started:
- Install LM Studio and download your preferred model (Qwen, Mistral, Phi, etc.)
- Start LM Studio on its default port (e.g.,
localhost:1234) - In your application, create and start the proxy:
var proxy = new LmVsProxy("localhost", 1234, 11434);
proxy.Start();
// Enable the built-in web chat UI (optional)
if (!proxy.ChatServer.IsListening)
{
proxy.ChatServer.Listen();
}
Console.WriteLine("Proxy running at http://localhost:11434/v1/chat/completions");
Console.WriteLine("Chat web UI available at http://localhost:" + proxy.ChatServerPort);
- Configure Visual Studio to use the proxy as your Copilot endpoint at
http://localhost:11434/v1/chat/completions - Open the chat UI in your browser:
http://localhost:{chatServerPort}to interact with your local model directly - All tool calls from Visual Studio now execute against your local model — no cloud bills, just electricity
Use Cases
- Real-time multiplayer games — low-latency communication with dynamic peer discovery.
- Distributed chat — P2P messaging with metadata-driven room discovery.
- IoT device networks — efficient, secure communication across flexible topologies.
- Remote control & automation — event-driven command/control of remote systems.
- Custom protocols — build domain-specific protocols on top of any transport with full control over serialization and peer management.
- Local AI model serving — run enterprise-grade open-source LLMs (Qwen, Mistral, Phi) via LM Studio on your own hardware with zero cloud costs through LmVsProxy.
Getting Started
Install via NuGet:
Install-Package SocketJack
Examples
TCP — Server & Client
// Create and start a server
var server = new TcpServer(port: 12345);
server.Listen();
// Connect a client
var client = new TcpClient();
await client.Connect("127.0.0.1", 12345);
// Send any serializable object
client.Send(new CustomMessage("Hello!"));
// Handle it with a typed callback
server.RegisterCallback<CustomMessage>((args) =>
{
Console.WriteLine($"Received: {args.Object.Message}");
// Echo back to the sender
args.Connection.Send(new CustomMessage("10-4"));
});
TCP — Default Options
Must be set before creating any Client or Server instance.
NetworkOptions.DefaultOptions.UsePeerToPeer = true;
TCP — Attach Metadata (Server-Side)
// Inside a server callback or ClientConnected handler:
connection.SetMetaData("room", "Lobby1");
UDP — Server & Client
var server = new UdpServer(port: 12345);
server.Listen();
var client = new UdpClient();
await client.Connect("127.0.0.1", 12345);
// Same Send / RegisterCallback pattern as TCP
client.Send(new CustomMessage("Hello via UDP!"));
server.RegisterCallback<CustomMessage>((args) =>
{
Console.WriteLine($"Received: {args.Object.Message}");
});
UDP — Peer-to-Peer & Broadcasting
// Send to a specific peer (relayed through the server)
client.Send(remotePeer, new CustomMessage("P2P over UDP"));
// Broadcast to all peers
client.SendPeerBroadcast(new CustomMessage("Hello everyone!"));
// Server broadcasts to all connected clients
server.SendBroadcast(new CustomMessage("Server announcement"));
// Server sends to a specific client by Identifier
server.Send(clientIdentifier, new CustomMessage("Direct message"));
UDP — Options
var options = new NetworkOptions();
options.MaxDatagramSize = 1400; // Safe MTU (default 65,507)
options.ClientTimeoutSeconds = 60; // Default 30
options.UdpReceiveBufferSize = 131072; // Default 65,535
options.EnableBroadcast = true; // Default false
options.ClientTimeoutCheckIntervalMs = 10000; // Default 5,000
var server = new UdpServer(options, port: 12345);
var client = new UdpClient(options);
HTTP — Server
var server = new HttpServer(port: 8080);
server.Listen();
// Custom index page
server.IndexPageHtml = "<html><body><h1>Welcome!</h1></body></html>";
// Route mapping
server.Map("GET", "/hello", (connection, request, ct) =>
{
return "Hello, World!";
});
server.Map("POST", "/echo", (connection, request, ct) =>
{
return new EchoResponse(request.Body);
});
server.RemoveRoute("GET", "/hello");
// Fallback for unmatched routes
server.OnHttpRequest += (connection, ref context, ct) =>
{
context.Response.Body = "Custom response";
context.Response.ContentType = "text/plain";
context.StatusCode = "200 OK";
};
HTTP — Typed Routes
Automatically deserialize the request body to a typed parameter:
server.Map<LoginRequest>("POST", "/login", (connection, body, request, ct) =>
{
// body is already deserialized to LoginRequest
return new LoginResponse(body.Username);
});
HTTP — Static File Serving
Map a local directory to a URL prefix to serve static files with automatic MIME type detection:
// Serve files from C:\wwwroot at /static
server.MapDirectory("/static", @"C:\wwwroot");
// Enable auto-generated directory listings for directories without index.html
server.AllowDirectoryListing = true;
server.RemoveDirectoryMapping("/static");
HTTP — .htaccess Security
Use the HtAccessBuilder fluent API to configure per-directory access rules:
server.MapDirectory("/secure", @"C:\data", htaccess =>
{
htaccess
.DenyDirectoryListing()
.AllowFrom("192.168.1.0/24")
.DenyFiles("*.log", "*.bak")
.RequireBasicAuth("Admin Area", "admin:secret")
.AddHeader("X-Frame-Options", "DENY");
});
HTTP — Streaming Routes
Keep a connection open for server-sent events or long-lived responses:
// Chunked streaming response
server.MapStream("GET", "/events", async (connection, request, chunkedStream, ct) =>
{
for (int i = 0; i < 10; i++)
{
chunkedStream.WriteLine("event: " + i);
await Task.Delay(1000, ct);
}
});
// Upload streaming (e.g., continuous video ingest)
server.MapUploadStream("POST", "/upload", (connection, request, uploadStream, ct) =>
{
byte[] chunk;
while ((chunk = uploadStream.ReadAsync(ct).GetAwaiter().GetResult()) != null)
{
// Process each incoming data chunk
}
});
HTTP — RTMP Ingest
Accept RTMP publish connections (e.g., from OBS) directly on the HTTP server port:
server.MapRtmpPublish("live", async (connection, app, streamKey, uploadStream, ct) =>
{
byte[] chunk;
while ((chunk = await uploadStream.ReadAsync(ct)) != null)
{
// Process RTMP media chunks (prefixed with type byte: 8=audio, 9=video)
}
});
HTTP — Client
using var client = new HttpClient();
// GET
HttpResponse response = await client.GetAsync("http://localhost:8080/hello");
Console.WriteLine(response.Body);
// POST
byte[] body = Encoding.UTF8.GetBytes("{\"message\":\"hi\"}");
HttpResponse postResp = await client.PostAsync(
"http://localhost:8080/echo",
"application/json",
body);
// Full control
HttpResponse resp = await client.SendAsync(
"PUT",
"https://example.com/api/resource",
new Dictionary<string, string> { ["Authorization"] = "Bearer token" },
body);
// Streaming
using var fileStream = File.Create("download.bin");
await client.GetAsync("http://example.com/largefile", responseStream: fileStream);
HTTP — Client Options
var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(60); // Default 30
client.MaxRedirects = 10; // Default 5
client.DefaultHeaders["Accept"] = "application/json";
HTTP — Live Streaming with BroadcastServer
BroadcastServer turns any HttpServer into a live video relay. Point OBS (or any RTMP encoder) at the server and viewers can watch in a browser or VLC — no additional dependencies required.
using SocketJack.Net;
// Create the HTTP server
var server = new HttpServer(port: 8080);
// Attach BroadcastServer and register the default streaming routes:
// GET /stream — HTML player page (mpegts.js)
// GET /stream/data — raw FLV relay for the player / VLC
// PUT /Upload — OBS Custom Output (HTTP)
// POST /Upload — OBS Custom Output (HTTP)
// RTMP rtmp://host:port/live — OBS RTMP publish
var broadcast = new BroadcastServer(server);
broadcast.Register();
// Start listening
server.Listen();
// The stream key is auto-generated. In OBS set:
// Server: rtmp://localhost:8080/live
// Stream Key: <broadcast.StreamKey>
Console.WriteLine("Stream Key: " + broadcast.StreamKey);
// Viewers open http://localhost:8080/stream in a browser,
// or play http://localhost:8080/stream/data directly in VLC.
// Optional: poll stats once per second
while (true) {
var stats = broadcast.UpdateStats();
if (stats.Active) {
Console.WriteLine(
stats.BitrateKbps.ToString("N0") + " kbps | " +
stats.VideoFrames + " video | " +
stats.AudioFrames + " audio | " +
BroadcastServer.FormatBytes(stats.TotalBytes));
}
Thread.Sleep(1000);
}
WebSocket — Server & Client
var server = new WebSocketServer(port: 9000);
server.Listen();
// Optional TLS
server.SslCertificate = new X509Certificate2("cert.pfx", "password");
server.Options.UseSsl = true;
server.Listen();
// Connect a client
var client = new WebSocketClient();
await client.Connect("127.0.0.1", 9000);
// Or with a full URI
await client.ConnectAsync(new Uri("ws://127.0.0.1:9000/path"));
WebSocket — Send, Receive & Broadcast
client.Send(new CustomMessage("Hello via WebSocket!"));
server.RegisterCallback<CustomMessage>((args) =>
{
Console.WriteLine($"Received: {args.Object.Message}");
});
server.Send(clientConnection, new CustomMessage("Reply"));
server.SendBroadcast(new CustomMessage("Announcement"));
WebSocket — Peer-to-Peer
var options = new NetworkOptions();
options.UsePeerToPeer = true;
var client = new WebSocketClient(options);
await client.Connect("127.0.0.1", 9000);
client.Send(remotePeer, new CustomMessage("P2P over WebSocket"));
client.SendBroadcast(new CustomMessage("Hello everyone!"));
WebSocket — Events
// Server
server.ClientConnected += (e) => Console.WriteLine($"Client connected: {e.Connection.Identity.ID}");
server.ClientDisconnected += (e) => Console.WriteLine($"Client disconnected: {e.Connection.Identity.ID}");
server.OnReceive += (ref e) => Console.WriteLine($"Received: {e.Obj}");
// Client
client.OnConnected += (e) => Console.WriteLine("Connected!");
client.OnDisconnected += (e) => Console.WriteLine("Disconnected.");
client.PeerConnected += (sender, peer) => Console.WriteLine($"Peer joined: {peer.ID}");
client.PeerDisconnected += (sender, peer) => Console.WriteLine($"Peer left: {peer.ID}");
MutableTcpServer — Multi-Protocol on a Single Port
MutableTcpServer extends HttpServer and auto-detects the protocol for each incoming connection. HTTP, SocketJack, WebSocket, and RTMP connections can all share a single listening port. Custom protocols are supported via the IProtocolHandler interface.
var server = new MutableTcpServer(port: 9000);
// HTTP routes are configured through the Http property
server.Http.Map("GET", "/api/status", (connection, request, ct) =>
{
return "{ \"status\": \"ok\" }";
});
// Serve static files through the HTTP handler
server.Http.MapDirectory("/www", @"C:\wwwroot");
// Serve an individual file at a specific URL
server.Http.MapFile("/js/app.js", @"C:\Pages\app.js");
// SocketJack clients connect to the same port and are routed automatically
server.SocketJackClientConnected += (connection) =>
{
Console.WriteLine($"SocketJack client connected: {connection.ID}");
};
// WebSocket clients are detected via the HTTP Upgrade handshake
server.WebSocketClientConnected += (connection) =>
{
Console.WriteLine($"WebSocket client connected: {connection.ID}");
};
// Normal SocketJack callbacks work for both SocketJack and WebSocket clients
server.RegisterCallback<CustomMessage>((args) =>
{
Console.WriteLine($"Received: {args.Object.Message}");
});
server.Listen();
// SocketJack clients connect normally:
var client = new TcpClient();
await client.Connect("127.0.0.1", 9000);
client.Send(new CustomMessage("Hello!"));
// WebSocket clients connect to the same port:
var wsClient = new WebSocketClient();
await wsClient.ConnectAsync(new Uri("ws://127.0.0.1:9000"));
wsClient.Send(new CustomMessage("Hello from browser!"));
// HTTP clients hit the same port:
// curl http://localhost:9000/api/status
MutableTcpServer — Custom Protocol Handler
Implement IProtocolHandler to add support for any binary protocol:
public class MyProtocolHandler : IProtocolHandler
{
public string Name => "MyProtocol";
public bool CanHandle(byte[] data)
{
// Detect your protocol by inspecting the first bytes
return data.Length >= 4 && data[0] == 0xAB;
}
public void ProcessReceive(MutableTcpServer server, NetworkConnection connection, ref IReceivedEventArgs e)
{
// Handle incoming data for this protocol
}
public void OnDisconnected(MutableTcpServer server, NetworkConnection connection)
{
// Clean up when a connection using this protocol disconnects
}
}
server.RegisterProtocol(new MyProtocolHandler());
WPF — Sharing a Control
These examples require the
SocketJack.WPFNuGet package, notSocketJack.
using SocketJack.Net;
using SocketJack.Net.P2P;
using SocketJack.WPF;
// Share any FrameworkElement (Canvas, Grid, Border, Window, etc.)
IDisposable shareHandle = myCanvas.Share(client, peer, fps: 10);
// Stop sharing
shareHandle.Dispose();
WPF — Viewing a Shared Control
using System.Windows.Controls;
using SocketJack.Net;
using SocketJack.WPF;
var viewer = client.ViewShare(sharedImage, sharerPeer);
// Dispose when finished
viewer.Dispose();
WPF — Full Example
XAML (both instances):
<Image x:Name="SharedImage" Stretch="Uniform" />
Sharer (Instance A):
Identifier remotePeer = client.Peers.FirstNotMe();
IDisposable shareHandle = GameCanvas.Share(client, remotePeer, fps: 10);
Viewer (Instance B):
Identifier remotePeer = client.Peers.FirstNotMe();
var viewer = client.ViewShare(SharedImage, remotePeer);
Documentation
License
SocketJack is open source and licensed under the MIT License.
Contributing
Contributions, bug reports, and feature requests are welcome! See CONTRIBUTING.md for details.
SocketJack — Fast, flexible, and modern networking for .NET.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- Mono.Nat (>= 3.0.4)
- SSH.NET (>= 2025.1.0)
- System.Collections.Concurrent (>= 4.3.0)
- System.Text.Json (>= 9.0.7)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on SocketJack:
| Package | Downloads |
|---|---|
|
ProjectZ
An XNA UI Framework that runs on modern .NET 8.0 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 2.0.0 | 100 | 5/6/2026 | |
| 1.7.2 | 87 | 5/5/2026 | |
| 1.6.7 | 136 | 3/9/2026 | |
| 1.6.0 | 103 | 3/7/2026 | |
| 1.5.0 | 108 | 3/2/2026 | |
| 1.4.5 | 121 | 2/23/2026 | |
| 1.4.4 | 124 | 2/23/2026 | |
| 1.4.2 | 126 | 2/21/2026 | |
| 1.4.1 | 125 | 2/21/2026 | |
| 1.4.0 | 125 | 2/21/2026 | |
| 1.3.0 | 147 | 2/7/2026 | |
| 1.2.3 | 157 | 1/19/2026 | |
| 1.2.2 | 487 | 12/8/2025 | |
| 1.2.1 | 223 | 10/3/2025 |