mirror of
https://github.com/linsa-io/linsa.git
synced 2026-04-20 15:31:30 +02:00
Update flow.toml to start web and desktop dev servers with port management and cleanup logic; enhance desktop preload and main process to resolve preload path, set web URLs, and expose shell functions; improve README with load fallback info and environment variables; add external logs configuration to worker package.
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
"wrangler": "^4.53.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@1focus/logs": "file:/Users/nikiv/lang/ts/lib/1focus/packages/logs",
|
||||
"drizzle-orm": "^0.45.0",
|
||||
"drizzle-zod": "^0.8.3",
|
||||
"hono": "^4.10.4",
|
||||
|
||||
@@ -116,6 +116,23 @@ Authentication:
|
||||
- `PATCH /api/v1/admin/browser-sessions/:sessionId`
|
||||
- `DELETE /api/v1/admin/browser-sessions/:sessionId`
|
||||
|
||||
### External Logs
|
||||
|
||||
To forward logs into 1focus Logs for the `linsa` server, set these secrets/vars in the worker:
|
||||
|
||||
- `FOCUS_LOGS_API_KEY` (required)
|
||||
- `FOCUS_LOGS_SERVER` (optional, default: `linsa`)
|
||||
- `FOCUS_LOGS_ENDPOINT` (optional, default: `https://1focus.app/api/logs`)
|
||||
|
||||
Then send a log:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8787/api/v1/admin/logs" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $ADMIN_API_KEY" \
|
||||
-d '{"message":"Hello from linsa","level":"info"}'
|
||||
```
|
||||
|
||||
### Example (create chat thread)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Hono, type Context, type MiddlewareHandler } from "hono"
|
||||
import { cors } from "hono/cors"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { createLogsClient, type LogPayload, type LogsWriteResult } from "@1focus/logs"
|
||||
import {
|
||||
browser_session_tabs,
|
||||
browser_sessions,
|
||||
@@ -17,6 +18,9 @@ type Env = {
|
||||
ADMIN_API_KEY?: string
|
||||
DATABASE_URL?: string
|
||||
HYPERDRIVE?: Hyperdrive
|
||||
FOCUS_LOGS_API_KEY?: string
|
||||
FOCUS_LOGS_ENDPOINT?: string
|
||||
FOCUS_LOGS_SERVER?: string
|
||||
}
|
||||
|
||||
// Create a new Hono app
|
||||
@@ -62,6 +66,40 @@ const parseBody = async (c: Context<AppEnv>) => {
|
||||
return (await c.req.json().catch(() => ({}))) as Record<string, unknown>
|
||||
}
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
Boolean(value) && typeof value === "object" && !Array.isArray(value)
|
||||
|
||||
const getLogsClient = (env: Env) => {
|
||||
const apiKey = env.FOCUS_LOGS_API_KEY?.trim()
|
||||
if (!apiKey) return null
|
||||
const endpoint = env.FOCUS_LOGS_ENDPOINT?.trim() || undefined
|
||||
const server = env.FOCUS_LOGS_SERVER?.trim() || "linsa"
|
||||
return createLogsClient({
|
||||
apiKey,
|
||||
server,
|
||||
endpoint,
|
||||
defaultSource: "linsa-worker",
|
||||
timeoutMs: 3000,
|
||||
})
|
||||
}
|
||||
|
||||
const logTo1Focus = async (
|
||||
c: Context<AppEnv>,
|
||||
payload: LogPayload,
|
||||
awaitResult = false,
|
||||
): Promise<LogsWriteResult | null> => {
|
||||
const client = getLogsClient(c.env)
|
||||
if (!client) return null
|
||||
|
||||
const promise = client.log(payload)
|
||||
if (!awaitResult && c.executionCtx) {
|
||||
c.executionCtx.waitUntil(promise)
|
||||
return null
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
const parseInteger = (value: unknown) => {
|
||||
const numberValue =
|
||||
typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN
|
||||
@@ -127,9 +165,55 @@ app.get("/", (c) => {
|
||||
// Example API endpoint
|
||||
app.get("/api/v1/hello", (c) => {
|
||||
const name = c.req.query("name") || "World"
|
||||
void logTo1Focus(c, {
|
||||
message: "hello endpoint called",
|
||||
level: "info",
|
||||
meta: { name },
|
||||
})
|
||||
return c.json({ message: `Hello, ${name}!` })
|
||||
})
|
||||
|
||||
// Manual log write (admin-only)
|
||||
app.post("/api/v1/admin/logs", async (c) => {
|
||||
const body = await parseBody(c)
|
||||
const message = typeof body.message === "string" ? body.message.trim() : ""
|
||||
if (!message) {
|
||||
return c.json({ error: "message is required" }, 400)
|
||||
}
|
||||
|
||||
const metaInput = isRecord(body.meta) ? body.meta : {}
|
||||
const meta = {
|
||||
...metaInput,
|
||||
path: c.req.path,
|
||||
method: c.req.method,
|
||||
}
|
||||
|
||||
const payload: LogPayload = {
|
||||
message,
|
||||
level: typeof body.level === "string" ? (body.level as LogPayload["level"]) : "info",
|
||||
source: typeof body.source === "string" ? body.source : undefined,
|
||||
timestamp:
|
||||
typeof body.timestamp === "number" || typeof body.timestamp === "string"
|
||||
? body.timestamp
|
||||
: undefined,
|
||||
meta,
|
||||
attributes: isRecord(body.attributes) ? body.attributes : undefined,
|
||||
resource: isRecord(body.resource) ? body.resource : undefined,
|
||||
scope: isRecord(body.scope) ? body.scope : undefined,
|
||||
traceId: typeof body.traceId === "string" ? body.traceId : undefined,
|
||||
spanId: typeof body.spanId === "string" ? body.spanId : undefined,
|
||||
parentSpanId: typeof body.parentSpanId === "string" ? body.parentSpanId : undefined,
|
||||
traceFlags: typeof body.traceFlags === "number" ? body.traceFlags : undefined,
|
||||
}
|
||||
|
||||
const result = await logTo1Focus(c, payload, true)
|
||||
if (!result) {
|
||||
return c.json({ error: "Logging not configured" }, 503)
|
||||
}
|
||||
|
||||
return c.json({ result }, result.ok ? 200 : 502)
|
||||
})
|
||||
|
||||
// Canvas endpoints
|
||||
app.post("/api/v1/admin/canvas", async (c) => {
|
||||
const body = await parseBody(c)
|
||||
|
||||
Reference in New Issue
Block a user