GPALRestAPI.exe is a small console/Forms hybrid app that does two jobs at once: it hosts an HttpListener on a local port (3000 at startup, incrementing until it finds a free one, which is what browser.RestApiBaseUrl then points at), and it speaks Chrome's native messaging protocol over stdin/stdout to a companion browser extension. When browser.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)
{
try
{
if (args
_listenPort = args[0];
// one instance runs per browser, each recorded with its process id and port in ports-in-use
AppDomain
RegisterForToasts();
StartRestServer();
if (listener != null)
RegisterInPortsLog();
ShowFirstRunNotification();
var stdinSafe = new SafeFileHandle(GetStdHandle(STD_INPUT_HANDLE), ownsHandle: false);
var stdoutSafe = new SafeFileHandle(GetStdHandle(STD_OUTPUT_HANDLE), ownsHandle: false);
_stdin = new FileStream(stdinSafe, FileAccess
_stdout = new FileStream(stdoutSafe, FileAccess
// StartRestServer ran before stdout existed, so the port goes to the extension now
SendPortToExtension();
// returns when the browser closes stdin: the browser exiting or the extension reloading
ListenForNativeMessages();
Environment
}
catch (Exception ex)
{
File
{ex
");
}
}
StartRestServer starts at port 3000 (or the port given on the command line) and walks up until an HttpListener can bind it, on localhost plus every active local IPv4 address, so the API is reachable both from localhost and from the LAN. If the LAN addresses are refused with access denied, it binds localhost alone. Once it is listening it announces the port, over UDP to the GPAL that launched the browser and over native messaging to the extension. Each incoming request is handed to HandleAPIRequest on a thread-pool thread, so the listener can take the next one while a request waits on the extension.
private static void StartRestServer()
{
var ips = GetActiveIPv4Addresses();
int port = int
while (port < 65535)
{
_listenPort = port
bool skipIps = false;
int ipRetry = 2;
while (0 < ipRetry--)
try
{
listener = new HttpListener();
listener
if (false == skipIps)
foreach (var ip in ips)
listener
listener
SendPortHandshake(port); // UDP, to the GPAL that launched the browser
SendPortToExtension();
new Thread(() =>
{
while (listener
{
HttpListenerContext context = listener
ThreadPool
}
})
return;
}
catch (HttpListenerException ex) when (ex
{
// access denied on the LAN addresses: try again on localhost alone
StopServer();
skipIps = true;
}
catch (HttpListenerException)
{
// port in use: try the next one
try { listener?
listener = null;
ipRetry = 0;
port++;
}
}
}
If binding to a LAN IP fails with a permission error, the server quietly falls back to localhost only. To open it to the LAN, call /openlan: it copies the netsh urlacl and firewall rule commands for its port 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: status, process-id, help, mcp, controller, hosts, openlan, upload, the OAuth helpers access-token and get-access-token, and the ANTKEY, XAIKEY and OPENAIKEY key lookups. Anything else falls through to the default case, which relays it to the extension: GET requests go as-is, POST requests carry the body too, and the request waits for the extension's reply. This default case is what handles every ApiEndpoint that browser.OttoMagicClient calls.
switch (message)
{
case "status":
// a request only arrives while the listener is up, so any answer means it is running
SendRestResponse(response, HttpStatusCode
break;
case "help":
SendRestResponse(response, HttpStatusCode
break;
default:
if ("GET" == context
RelayToExtension(context, message, null);
else if ("POST" == context
{
string jsonBody = new StreamReader(context
RelayToExtension(context, message, jsonBody);
}
else
SendRestResponse(response, HttpStatusCode
break;
}
When browser.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 every slash removed, and is forwarded straight to the extension. RelayToExtension waits up to two minutes for the extension's reply and answers the request with it; if the browser closed first the request gets a 503, and if no reply comes in time, a 504.
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. That same blocking read is what tells the server when to stop: the loop runs until the pipe closes, and closing the pipe is what the browser does on its way out.
private static void SendNativeMessageToExtension(string message, string jsonBody = null)
{
string payload = string
? $@"{{""message"":""{message}""}}"
: $@"{{""message"":""{message}"",""data"":{jsonBody}}}";
byte[] data = Encoding
byte[] len = BitConverter
_stdout
_stdout
_stdout
}
The server lives exactly as long as the browser does. Closing the browser or reloading the extension breaks stdin, and the server exits with it rather than lingering as an orphan. The port goes too: a workflow still holding that base url has nothing to talk to, and the next server takes whichever port is free and broadcasts the new one.
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 EP = {
// 'goto': {cat:'Navigation', method:'POST', label:'GoTo', params:[{n:'url', req:true}]},
// 'back': {cat:'Navigation', method:'GET', label:'Back', params:[]},
//
// };
// async function execStep(s) {
// const info = EP[s
// const url = apiBase() + '/' + s
// const hasP = Object
// const method = (hasP && info
// const opts = method === 'POST'
// ? { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON
// : { method: 'GET' };
// const res = await fetch(url, opts);
// const txt = await res
// try { return 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();