GPALRestAPI is the local HTTP server that GPAL.OttoMagicClient talks to. It runs alongside a browser extension, listens on a local port, and translates REST calls into native-messaging commands the extension executes against the live browser. Understanding this server helps explain what's actually happening when your GPAL workflow calls .Execute().
GPALRestAPI.exe is a small console/Forms hybrid app that does two jobs at once: it hosts an HttpListener on a local port (default 3000, matching GPAL.OttoMagicRestApiBaseUrl), and it speaks Chrome's native messaging protocol over stdin/stdout to a companion browser extension. When GPAL.OttoMagicClient sends an endpoint like LeftClick or GoTo, this server receives the HTTP request, forwards it to the extension as a native message, and the extension performs the action in the real browser tab.
[STAThread]
static void Main(string[] args)
{
// only one instance runs at a time - kill any others first
Process current = Process.GetCurrentProcess();
var otherInstances = Process.GetProcessesByName(current.ProcessName)
.Where(p => p.Id != current.Id);
foreach (var process in otherInstances)
process.Kill();
if (args.Length == 1 && int.TryParse(args[0], out int port) && port > 1024 && port < 65536)
_listenPort = args[0];
StartRestServer();
ListenForNativeMessages();
}
StartRestServer binds an HttpListener to GPAL.OttoMagicRestApiBaseUrl (with the port substituted) plus every active local IPv4 address, so the API is reachable both from localhost and from the LAN. Each incoming request runs through HandleAPIRequest on a background thread.
private static void StartRestServer()
{
listener = new HttpListener();
listener.Prefixes.Add(GPAL.OttoMagicRestApiBaseUrl.Replace("3000", _listenPort));
foreach (var ip in GetActiveIPv4Addresses())
listener.Prefixes.Add($"http://{ip}:{_listenPort}/");
listener.Start();
new Thread(() =>
{
while (listener.IsListening)
{
HttpListenerContext context = listener.GetContext();
HandleAPIRequest(context);
}
}).Start();
}
If binding to a LAN IP fails with a permission error, the server copies netsh urlacl and firewall rule commands to the clipboard and shows a notification. Paste them into an elevated command prompt and restart the server.
HandleAPIRequest first checks for a small set of built-in routes - start, stop, status, help, controller, and a couple of OAuth helpers (access-token / get-access-token). Anything else falls through to the default case: GET requests are forwarded to the extension as-is, and POST requests forward the body too. This default case is what handles every ApiEndpoint that GPAL.OttoMagicClient calls.
switch (message)
{
case "status":
SendRestResponse(response,
null == listener ? HttpStatusCode.NotFound : HttpStatusCode.OK,
null == listener ? "Server is not running" : "Server is running");
break;
case "help":
SendRestResponse(response, HttpStatusCode.OK,
$"Commands: {string.Join(",", Enum.GetNames(typeof(GenerallyPositive.Enums.ApiEndpoint)))}");
break;
default:
if ("GET" == context.Request.HttpMethod)
SendNativeMessageToExtension(message);
else if ("POST" == context.Request.HttpMethod)
{
string jsonBody = new StreamReader(context.Request.InputStream).ReadToEnd();
SendNativeMessageToExtension(message, jsonBody);
}
else
SendRestResponse(response, HttpStatusCode.MethodNotAllowed, "Method not supported");
break;
}
When GPAL.OttoMagicClient calls .GoTo("https://example.com").Execute(), it issues an HTTP request whose path matches the endpoint (for example /goto). The path becomes 'message' here, with any leading slash stripped, and is forwarded straight to the extension.
Native messages use a 4-byte length prefix followed by a UTF-8 JSON payload, read from and written to stdin/stdout. ReadNativeMessage blocks waiting for the extension's responses, and SendNativeMessageToExtension packages an outgoing command (with optional JSON body) the same way. A heartbeat timer keeps the connection alive and shuts the server down if the extension goes quiet for too long.
private static void SendNativeMessageToExtension(string message, string jsonBody = null)
{
string payload = string.IsNullOrEmpty(jsonBody)
? $@"{{""message"":""{message}""}}"
: $@"{{""message"":""{message}"",""data"":{jsonBody}}}";
byte[] data = Encoding.UTF8.GetBytes(payload);
byte[] len = BitConverter.GetBytes(data.Length);
_stdout.Write(len, 0, 4);
_stdout.Write(data, 0, data.Length);
_stdout.Flush();
}
If no native message activity occurs for 30 seconds (no heartbeat from the extension), the server calls Environment.Exit(0) and terminates itself. This is by design - it prevents an orphaned server process from lingering after the browser extension is closed.
Hitting /controller serves a small HTML/JS workflow builder page. It lists every ApiEndpoint, lets you fill in parameters for each step, and runs the resulting sequence by calling the same endpoints over fetch() - useful for manually testing or demoing the API without writing any C# at all.
case "controller":
ServeControllerPage(response);
break;
// inside the served page:
// const endpoints = ["Back","CaptureVisibleTab","CheckNetworkIdle", ... ];
// async function executeStep(selectedEndpoint, params) {
// const apiEndpoint = endpointMap[selectedEndpoint];
// const url = `${window.location.origin}/${apiEndpoint}`;
// const options = (params && Object.keys(params).length)
// ? { method: "POST", headers: {"Content-Type":"application/json"}, body: JSON.stringify(params) }
// : { method: "GET" };
// const response = await fetch(url, options);
// return await response.json();
// }
Showing off some plain text in these paragraphs eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Here you can find different accents and emphasis sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
This is a link and how it could look like bestlinkinthebeautifulworld. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Here's just some classic bold text adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam notBoldSecondbestlinkinthebeautifulworld illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at adipisicing elit libero vel voluptas? Expedita, adipisicing facere dolores voluptatem ad ab rem assumenda soluta!
Other cuple of colors in case we want to emphasize several ways adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at voluptatem libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Repudiandae quas consequuntur illo numquam assumenda autem exercitationem distinctio perspiciatis in natus. Eius dicta similique ipsam ipsa minima, nemo quae enim tempore.
GPAL
.CallIfNotFound(GenericCallIfNotFound)
.WithPublishToConsole();
//System.Drawing.Rectangle windowSize = new System.Drawing.Rectangle(10, 10, 1500, 1024);
// NOTE: we have to set browser = before we execute any steps
// this is due to the 'GenericCallIfNotFound' which might throw an exception, and BankScraper will not have the browser set when it calls scraper.Close()
// until the complete fluent line gets executed (meaning every step, meaning browser is not set until everything else succeeds)
browser = GPAL.Browser
.WithBrowserType(Enums.BrowserType.Chrome)
.WithProfileDataDirectory(ChromeProfileLocation)
.WithUseAutomationEngine(AutomationEngine.Selenium)
.WithWindowSize(new System.Drawing.Rectangle(0,0,1920,1080))
.ToGPALObject();