organize repo
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Sample C Plugin — fully unloadable, no runtime baggage.
|
||||
*
|
||||
* Exports the standard Overlord plugin ABI:
|
||||
* PluginOnLoad, PluginOnEvent, PluginOnUnload, PluginSetCallback,
|
||||
* PluginGetRuntime (returns "c" — tells the host this DLL can be freed)
|
||||
*
|
||||
* Build (Windows):
|
||||
* cl /LD /O2 plugin.c /Fe:sample-c-windows-amd64.dll
|
||||
* — or with MinGW —
|
||||
* x86_64-w64-mingw32-gcc -shared -O2 -o sample-c-windows-amd64.dll plugin.c
|
||||
*
|
||||
* Build (Linux):
|
||||
* gcc -shared -fPIC -O2 -o sample-c-linux-amd64.so plugin.c
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Platform helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#ifdef _WIN32
|
||||
# define EXPORT __declspec(dllexport)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
BOOL WINAPI DllMain(HINSTANCE hDll, DWORD reason, LPVOID reserved) {
|
||||
(void)hDll; (void)reserved;
|
||||
switch (reason) {
|
||||
case DLL_PROCESS_ATTACH: break;
|
||||
case DLL_PROCESS_DETACH: break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
#else
|
||||
# define EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Host callback */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#ifdef _WIN32
|
||||
/* Windows: callback is void(__stdcall*)(ptr,len,ptr,len) */
|
||||
typedef void (__stdcall *host_callback_t)(
|
||||
const char *event, uintptr_t eventLen,
|
||||
const char *payload, uintptr_t payloadLen);
|
||||
#else
|
||||
/* Unix: callback is void(*)(ctx, event, eventLen, payload, payloadLen) */
|
||||
typedef void (*host_callback_t)(
|
||||
uintptr_t ctx,
|
||||
const char *event, int eventLen,
|
||||
const char *payload, int payloadLen);
|
||||
#endif
|
||||
|
||||
static host_callback_t g_callback = NULL;
|
||||
#ifndef _WIN32
|
||||
static uintptr_t g_callback_ctx = 0;
|
||||
#endif
|
||||
|
||||
static void send_event(const char *event, const char *payload) {
|
||||
if (!g_callback) return;
|
||||
int elen = event ? (int)strlen(event) : 0;
|
||||
int plen = payload ? (int)strlen(payload) : 0;
|
||||
#ifdef _WIN32
|
||||
g_callback(event, (uintptr_t)elen, payload, (uintptr_t)plen);
|
||||
#else
|
||||
g_callback(g_callback_ctx, event, elen, payload, plen);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Plugin state (trivial example) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static char g_client_id[256];
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Exported ABI */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
EXPORT const char *PluginGetRuntime(void) {
|
||||
return "c";
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
EXPORT void PluginSetCallback(uint64_t cb) {
|
||||
g_callback = (host_callback_t)(uintptr_t)cb;
|
||||
}
|
||||
|
||||
EXPORT int PluginOnLoad(const char *hostInfo, int hostInfoLen, uint64_t cb) {
|
||||
g_callback = (host_callback_t)(uintptr_t)cb;
|
||||
#else
|
||||
EXPORT int PluginOnLoad(const char *hostInfo, int hostInfoLen,
|
||||
uintptr_t cb, uintptr_t ctx) {
|
||||
g_callback = (host_callback_t)cb;
|
||||
g_callback_ctx = ctx;
|
||||
#endif
|
||||
/* Minimal JSON-ish parse to grab clientId — keeps the example dependency-free */
|
||||
g_client_id[0] = '\0';
|
||||
if (hostInfo && hostInfoLen > 0) {
|
||||
const char *key = "\"clientId\":\"";
|
||||
const char *p = strstr(hostInfo, key); /* safe: hostInfo is nul-terminated by the Go caller copying it */
|
||||
if (!p) {
|
||||
key = "\"clientId\": \"";
|
||||
p = strstr(hostInfo, key);
|
||||
}
|
||||
if (p) {
|
||||
p += strlen(key);
|
||||
const char *end = strchr(p, '"');
|
||||
if (end) {
|
||||
size_t n = (size_t)(end - p);
|
||||
if (n >= sizeof(g_client_id)) n = sizeof(g_client_id) - 1;
|
||||
memcpy(g_client_id, p, n);
|
||||
g_client_id[n] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "[sample-c] loaded, clientId=%s\n", g_client_id);
|
||||
send_event("ready", "{\"message\":\"sample-c plugin ready\"}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT int PluginOnEvent(const char *event, int eventLen,
|
||||
const char *payload, int payloadLen) {
|
||||
(void)payloadLen;
|
||||
|
||||
/* Simple echo for "ping" */
|
||||
if (eventLen == 4 && memcmp(event, "ping", 4) == 0) {
|
||||
send_event("pong", NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Echo back ui_message */
|
||||
if (eventLen == 10 && memcmp(event, "ui_message", 10) == 0) {
|
||||
fprintf(stderr, "[sample-c] got ui_message: %.*s\n", payloadLen, payload ? payload : "");
|
||||
/* Build a tiny JSON response — payload is already JSON from the host */
|
||||
char buf[512];
|
||||
snprintf(buf, sizeof(buf), "{\"message\":\"echo from C: %.*s\"}",
|
||||
payloadLen < 400 ? payloadLen : 400, payload ? payload : "");
|
||||
send_event("echo", buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr, "[sample-c] unhandled event: %.*s\n", eventLen, event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT void PluginOnUnload(void) {
|
||||
fprintf(stderr, "[sample-c] unloading\n");
|
||||
g_callback = NULL;
|
||||
#ifndef _WIN32
|
||||
g_callback_ctx = 0;
|
||||
#endif
|
||||
g_client_id[0] = '\0';
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", system-ui, sans-serif;
|
||||
background: #0f1115;
|
||||
color: #e8ecf1;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 820px;
|
||||
margin: 40px auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.sub {
|
||||
margin: 0 0 24px 0;
|
||||
color: #9aa4b2;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #161a22;
|
||||
border: 1px solid #222938;
|
||||
border-radius: 12px;
|
||||
padding: 18px;
|
||||
margin-bottom: 18px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: grid;
|
||||
grid-template-columns: 120px 1fr;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #9aa4b2;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.input {
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #2a3346;
|
||||
background: #0f131b;
|
||||
color: #e8ecf1;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #1d4ed8;
|
||||
}
|
||||
|
||||
.log {
|
||||
background: #0b0e14;
|
||||
border: 1px solid #1e2533;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Sample C Plugin</title>
|
||||
<link rel="stylesheet" href="/plugins/sample-c/assets/sample-c.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<header class="header">
|
||||
<h1>Sample C Plugin</h1>
|
||||
<p class="sub">Pure C — fully unloadable, zero runtime leak</p>
|
||||
</header>
|
||||
|
||||
<section class="card">
|
||||
<div class="row">
|
||||
<label class="label">Client ID</label>
|
||||
<input id="client-id" class="input" placeholder="clientId" />
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="label">Message</label>
|
||||
<input id="message" class="input" placeholder="hello from UI" />
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button id="send-btn" class="btn">Send event to client</button>
|
||||
<button id="ping-btn" class="btn">Ping</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Log</h2>
|
||||
<pre id="log" class="log"></pre>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/plugins/sample-c/assets/sample-c.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,43 @@
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const clientIdInput = document.getElementById("client-id");
|
||||
const messageInput = document.getElementById("message");
|
||||
const logEl = document.getElementById("log");
|
||||
const sendBtn = document.getElementById("send-btn");
|
||||
const pingBtn = document.getElementById("ping-btn");
|
||||
|
||||
const pluginId = "sample-c";
|
||||
clientIdInput.value = params.get("clientId") || "";
|
||||
messageInput.value = "hello from sample-c UI";
|
||||
|
||||
function log(line) {
|
||||
const ts = new Date().toISOString();
|
||||
logEl.textContent = `${ts} ${line}\n` + logEl.textContent;
|
||||
}
|
||||
|
||||
async function sendEvent(event, payload) {
|
||||
const clientId = clientIdInput.value.trim();
|
||||
if (!clientId) { log("Missing clientId"); return; }
|
||||
const res = await fetch(`/api/clients/${encodeURIComponent(clientId)}/plugins/${pluginId}/event`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ event, payload }),
|
||||
});
|
||||
if (!res.ok) { log(`Send failed: ${res.status} ${await res.text()}`); return; }
|
||||
log(`Sent event: ${event}`);
|
||||
}
|
||||
|
||||
sendBtn.addEventListener("click", () => {
|
||||
sendEvent("ui_message", { message: messageInput.value.trim() });
|
||||
});
|
||||
|
||||
pingBtn.addEventListener("click", () => {
|
||||
sendEvent("ping", {});
|
||||
});
|
||||
|
||||
// Listen for plugin events via SSE / polling (same pattern as Go sample)
|
||||
if (typeof EventSource !== "undefined") {
|
||||
const clientId = params.get("clientId");
|
||||
if (clientId) {
|
||||
log("Ready — C plugin (fully unloadable)");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user