Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  node-host.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import { spawn, spawnSync, type ChildProcess } from "node:child_process";
import { randomUUID } from "node:crypto";
import { setTimeout as sleep } from "node:timers/promises";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import {
  DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND,
  DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND,
} from "./config.js";
import {
  GOOGLE_MEET_SYSTEM_PROFILER_COMMAND,
  outputMentionsBlackHole2ch,
} from "./transports/chrome.js";

type NodeBridgeSession = {
  id: string;
  input?: ChildProcess;
  output?: ChildProcess;
  chunks: Buffer[];
  waiters: Array<() => void>;
  closed: boolean;
  createdAt: string;
  lastInputAt?: string;
  lastOutputAt?: string;
  lastInputBytes: number;
  lastOutputBytes: number;
};

const sessions = new Map<string, NodeBridgeSession>();

function asRecord(value: unknown): Record<string, unknown> {
  return value && typeof value === "object" && !Array.isArray(value)
    ? (value as Record<string, unknown>)
    : {};
}

function readString(value: unknown): string | undefined {
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
}

function readStringArray(value: unknown): string[] | undefined {
  if (!Array.isArray(value)) {
    return undefined;
  }
  const result = value.filter(
    (entry): entry is string => typeof entry === "string" && entry.length > 0,
  );
  return result.length > 0 ? result : undefined;
}

function readNumber(value: unknown, fallback: number): number {
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
}

function runCommandWithTimeout(argv: string[], timeoutMs: number) {
  const [command, ...args] = argv;
  if (!command) {
    throw new Error("command must not be empty");
  }
  const result = spawnSync(command, args, {
    encoding: "utf8",
    timeout: timeoutMs,
  });
  return {
    code: typeof result.status === "number" ? result.status : result.error ? 1 : 0,
    stdout: result.stdout ?? "",
    stderr: result.stderr ?? (result.error ? formatErrorMessage(result.error) : ""),
  };
}

function assertBlackHoleAvailable(timeoutMs: number) {
  if (process.platform !== "darwin") {
    throw new Error("Chrome Meet transport with blackhole-2ch audio is currently macOS-only");
  }
  const result = runCommandWithTimeout(
    [GOOGLE_MEET_SYSTEM_PROFILER_COMMAND, "SPAudioDataType"],
    timeoutMs,
  );
  const output = `${result.stdout}\n${result.stderr}`;
  if (result.code !== 0 || !outputMentionsBlackHole2ch(output)) {
    throw new Error("BlackHole 2ch audio device not found on the node.");
  }
}

function splitCommand(argv: string[]): { command: string; args: string[] } {
  const [command, ...args] = argv;
  if (!command) {
    throw new Error("audio command must not be empty");
  }
  return { command, args };
}

function wake(session: NodeBridgeSession) {
  const waiters = session.waiters.splice(0);
  for (const waiter of waiters) {
    waiter();
  }
}

function stopSession(session: NodeBridgeSession) {
  if (session.closed) {
    return;
  }
  session.closed = true;
  session.input?.kill("SIGTERM");
  session.output?.kill("SIGTERM");
  wake(session);
}

function startCommandPair(params: {
  inputCommand: string[];
  outputCommand: string[];
}): NodeBridgeSession {
  const input = splitCommand(params.inputCommand);
  const output = splitCommand(params.outputCommand);
  const session: NodeBridgeSession = {
    id: `meet_node_${randomUUID()}`,
    chunks: [],
    waiters: [],
    closed: false,
    createdAt: new Date().toISOString(),
    lastInputBytes: 0,
    lastOutputBytes: 0,
  };
  const outputProcess = spawn(output.command, output.args, {
    stdio: ["pipe", "ignore", "pipe"],
  });
  const inputProcess = spawn(input.command, input.args, {
    stdio: ["ignore", "pipe", "pipe"],
  });
  session.input = inputProcess;
  session.output = outputProcess;
  inputProcess.stdout?.on("data", (chunk) => {
    const audio = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
    session.lastInputAt = new Date().toISOString();
    session.lastInputBytes += audio.byteLength;
    session.chunks.push(audio);
    if (session.chunks.length > 200) {
      session.chunks.splice(0, session.chunks.length - 200);
    }
    wake(session);
  });
  inputProcess.on("exit", () => stopSession(session));
  outputProcess.on("exit", () => stopSession(session));
  inputProcess.on("error", () => stopSession(session));
  outputProcess.on("error", () => stopSession(session));
  sessions.set(session.id, session);
  return session;
}

async function pullAudio(params: Record<string, unknown>) {
  const bridgeId = readString(params.bridgeId);
  if (!bridgeId) {
    throw new Error("bridgeId required");
  }
  const session = sessions.get(bridgeId);
  if (!session) {
    throw new Error(`unknown bridgeId: ${bridgeId}`);
  }
  const timeoutMs = Math.min(readNumber(params.timeoutMs, 250), 2_000);
  if (session.chunks.length === 0 && !session.closed) {
    await Promise.race([
      sleep(timeoutMs),
      new Promise<void>((resolve) => {
        session.waiters.push(resolve);
      }),
    ]);
  }
  const chunk = session.chunks.shift();
  return {
    bridgeId,
    closed: session.closed,
    base64: chunk ? chunk.toString("base64") : undefined,
  };
}

function pushAudio(params: Record<string, unknown>) {
  const bridgeId = readString(params.bridgeId);
  const base64 = readString(params.base64);
  if (!bridgeId || !base64) {
    throw new Error("bridgeId and base64 required");
  }
  const session = sessions.get(bridgeId);
  if (!session || session.closed) {
    throw new Error(`bridge is not open: ${bridgeId}`);
  }
  const audio = Buffer.from(base64, "base64");
  session.lastOutputAt = new Date().toISOString();
  session.lastOutputBytes += audio.byteLength;
  session.output?.stdin?.write(audio);
  return { bridgeId, ok: true };
}

function startChrome(params: Record<string, unknown>) {
  const url = readString(params.url);
  if (!url) {
    throw new Error("url required");
  }
  const timeoutMs = readNumber(params.joinTimeoutMs, 30_000);
  assertBlackHoleAvailable(Math.min(timeoutMs, 10_000));

  const healthCommand = readStringArray(params.audioBridgeHealthCommand);
  if (healthCommand) {
    const health = runCommandWithTimeout(healthCommand, timeoutMs);
    if (health.code !== 0) {
      throw new Error(
        `Chrome audio bridge health check failed: ${health.stderr || health.stdout || health.code}`,
      );
    }
  }

  let bridgeId: string | undefined;
  let audioBridge: { type: "external-command" | "node-command-pair" } | undefined;
  const bridgeCommand = readStringArray(params.audioBridgeCommand);
  if (bridgeCommand) {
    const bridge = runCommandWithTimeout(bridgeCommand, timeoutMs);
    if (bridge.code !== 0) {
      throw new Error(
        `failed to start Chrome audio bridge: ${bridge.stderr || bridge.stdout || bridge.code}`,
      );
    }
    audioBridge = { type: "external-command" };
  } else if (params.mode === "realtime") {
    const session = startCommandPair({
      inputCommand: readStringArray(params.audioInputCommand) ?? [
        ...DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND,
      ],
      outputCommand: readStringArray(params.audioOutputCommand) ?? [
        ...DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND,
      ],
    });
    bridgeId = session.id;
    audioBridge = { type: "node-command-pair" };
  }

  if (params.launch !== false) {
    const argv = ["open", "-a", "Google Chrome"];
    const browserProfile = readString(params.browserProfile);
    if (browserProfile) {
      argv.push("--args", `--profile-directory=${browserProfile}`);
    }
    argv.push(url);
    const result = runCommandWithTimeout(argv, timeoutMs);
    if (result.code !== 0) {
      if (bridgeId) {
        const session = sessions.get(bridgeId);
        if (session) {
          stopSession(session);
        }
      }
      throw new Error(
        `failed to launch Chrome for Meet: ${result.stderr || result.stdout || result.code}`,
      );
    }
  }

  return {
    launched: params.launch !== false,
    bridgeId,
    audioBridge,
    browser:
      params.launch !== false
        ? {
            status: "chrome-opened",
            browserUrl: url,
            notes: [
              "Browser page control is handled by OpenClaw browser automation when using chrome-node.",
            ],
          }
        : undefined,
  };
}

function bridgeStatus(params: Record<string, unknown>) {
  const bridgeId = readString(params.bridgeId);
  const session = bridgeId ? sessions.get(bridgeId) : undefined;
  return {
    bridge: session
      ? {
          bridgeId,
          closed: session.closed,
          createdAt: session.createdAt,
          lastInputAt: session.lastInputAt,
          lastOutputAt: session.lastOutputAt,
          lastInputBytes: session.lastInputBytes,
          lastOutputBytes: session.lastOutputBytes,
        }
      : bridgeId
        ? { bridgeId, closed: true }
        : undefined,
  };
}

function stopChrome(params: Record<string, unknown>) {
  const bridgeId = readString(params.bridgeId);
  if (!bridgeId) {
    return { ok: true, stopped: false };
  }
  const session = sessions.get(bridgeId);
  if (!session) {
    return { ok: true, stopped: false };
  }
  stopSession(session);
  sessions.delete(bridgeId);
  return { ok: true, stopped: true };
}

export async function handleGoogleMeetNodeHostCommand(paramsJSON?: string | null): Promise<string> {
  const raw = paramsJSON ? JSON.parse(paramsJSON) : {};
  const params = asRecord(raw);
  const action = readString(params.action);
  let result: unknown;
  switch (action) {
    case "setup":
      assertBlackHoleAvailable(10_000);
      result = { ok: true };
      break;
    case "start":
      result = startChrome(params);
      break;
    case "status":
      result = bridgeStatus(params);
      break;
    case "pullAudio":
      result = await pullAudio(params);
      break;
    case "pushAudio":
      result = pushAudio(params);
      break;
    case "stop":
      result = stopChrome(params);
      break;
    default:
      throw new Error("unsupported googlemeet.chrome action");
  }
  return JSON.stringify(result);
}

¤ Dauer der Verarbeitung: 0.2 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge