feat: initial commit - Jhonny Editor
- Adicionado estrutura completa do projeto - Configurado MCP server para Premiere Pro - Adicionado documentação e skills - Configurado Gitignore para o projeto
This commit is contained in:
Executable
+225
@@ -0,0 +1,225 @@
|
||||
import { once } from "node:events";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { WebSocket } from "ws";
|
||||
import {
|
||||
UxpBridgeError,
|
||||
UxpWebSocketBridge,
|
||||
} from "../../src/bridge/uxp-websocket-bridge.js";
|
||||
|
||||
const TOKEN = "test-token-at-least-16-characters";
|
||||
const bridges: UxpWebSocketBridge[] = [];
|
||||
|
||||
async function createBridge(options: {
|
||||
requestTimeoutMs?: number;
|
||||
handshakeTimeoutMs?: number;
|
||||
} = {}) {
|
||||
const bridge = new UxpWebSocketBridge({
|
||||
token: TOKEN,
|
||||
port: 0,
|
||||
...options,
|
||||
});
|
||||
bridges.push(bridge);
|
||||
await bridge.start();
|
||||
return bridge;
|
||||
}
|
||||
|
||||
function bridgeUrl(bridge: UxpWebSocketBridge, token = TOKEN): string {
|
||||
const address = bridge.address();
|
||||
return `ws://${address.host}:${address.port}${address.path}?token=${encodeURIComponent(token)}`;
|
||||
}
|
||||
|
||||
async function connectHost(
|
||||
bridge: UxpWebSocketBridge,
|
||||
commands: Record<string, { supported: boolean }> = {
|
||||
"state.get": { supported: true },
|
||||
"frame.export": { supported: true },
|
||||
},
|
||||
) {
|
||||
const client = new WebSocket(bridgeUrl(bridge));
|
||||
await once(client, "open");
|
||||
const connected = once(bridge, "connected");
|
||||
client.send(JSON.stringify({
|
||||
protocolVersion: 1,
|
||||
type: "hello",
|
||||
payload: {
|
||||
backend: "uxp",
|
||||
protocolVersion: 1,
|
||||
commands,
|
||||
},
|
||||
}));
|
||||
await connected;
|
||||
return client;
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(bridges.splice(0).map((bridge) => bridge.stop()));
|
||||
});
|
||||
|
||||
describe("UXP WebSocket bridge", () => {
|
||||
it("binds only to IPv4 loopback and requires the configured token", async () => {
|
||||
const bridge = await createBridge();
|
||||
expect(bridge.address()).toMatchObject({ host: "127.0.0.1", path: "/uxp" });
|
||||
|
||||
const unauthorized = new WebSocket(bridgeUrl(bridge, "wrong-token"));
|
||||
unauthorized.on("error", () => {});
|
||||
const [, response] = await once(unauthorized, "unexpected-response");
|
||||
expect(response.statusCode).toBe(401);
|
||||
response.resume();
|
||||
expect(bridge.getState()).toEqual({ status: "listening", connected: false });
|
||||
});
|
||||
|
||||
it("validates the versioned hello before reporting capabilities", async () => {
|
||||
const bridge = await createBridge();
|
||||
const client = await connectHost(bridge);
|
||||
expect(bridge.getState()).toMatchObject({
|
||||
status: "connected",
|
||||
connected: true,
|
||||
protocolVersion: 1,
|
||||
capabilities: {
|
||||
backend: "uxp",
|
||||
commands: { "state.get": { supported: true } },
|
||||
},
|
||||
});
|
||||
client.close();
|
||||
});
|
||||
|
||||
it("correlates concurrent command results by request id", async () => {
|
||||
const bridge = await createBridge();
|
||||
const client = await connectHost(bridge);
|
||||
const commands: any[] = [];
|
||||
client.on("message", (data) => {
|
||||
const message = JSON.parse(data.toString());
|
||||
commands.push(message);
|
||||
if (commands.length !== 2) return;
|
||||
for (const command of [...commands].reverse()) {
|
||||
client.send(JSON.stringify({
|
||||
protocolVersion: 1,
|
||||
type: "result",
|
||||
requestId: command.requestId,
|
||||
payload: { ok: true, result: { command: command.command } },
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
const [state, frame] = await Promise.all([
|
||||
bridge.request("state.get"),
|
||||
bridge.request("frame.export", { filename: "frame.png" }),
|
||||
]);
|
||||
expect(state).toEqual({ command: "state.get" });
|
||||
expect(frame).toEqual({ command: "frame.export" });
|
||||
client.close();
|
||||
});
|
||||
|
||||
it("rejects unsupported commands before sending them", async () => {
|
||||
const bridge = await createBridge();
|
||||
const client = await connectHost(bridge, { "state.get": { supported: true } });
|
||||
await expect(bridge.request("frame.export")).rejects.toMatchObject({
|
||||
code: "UXP_COMMAND_UNSUPPORTED",
|
||||
});
|
||||
client.close();
|
||||
});
|
||||
|
||||
it("times out requests and rejects in-flight work on disconnect", async () => {
|
||||
const bridge = await createBridge({ requestTimeoutMs: 30 });
|
||||
const client = await connectHost(bridge);
|
||||
await expect(bridge.request("state.get")).rejects.toMatchObject({
|
||||
code: "UXP_TIMEOUT",
|
||||
});
|
||||
|
||||
const pending = bridge.request("state.get");
|
||||
client.close();
|
||||
await expect(pending).rejects.toMatchObject({ code: "UXP_DISCONNECTED" });
|
||||
});
|
||||
|
||||
it("honors a per-request minimum timeout for bounded host waits", async () => {
|
||||
const bridge = await createBridge({ requestTimeoutMs: 20 });
|
||||
const client = await connectHost(bridge);
|
||||
client.once("message", (data) => {
|
||||
const command = JSON.parse(data.toString());
|
||||
setTimeout(() => {
|
||||
client.send(JSON.stringify({
|
||||
protocolVersion: 1,
|
||||
type: "result",
|
||||
requestId: command.requestId,
|
||||
payload: { ok: true, result: { waited: true } },
|
||||
}));
|
||||
}, 40);
|
||||
});
|
||||
|
||||
await expect(bridge.request("state.get", {}, { minimumTimeoutMs: 100 }))
|
||||
.resolves.toEqual({ waited: true });
|
||||
client.close();
|
||||
});
|
||||
|
||||
it("rejects invalid per-request minimum timeouts before dispatch", async () => {
|
||||
const bridge = await createBridge();
|
||||
const client = await connectHost(bridge);
|
||||
|
||||
await expect(bridge.request("state.get", {}, { minimumTimeoutMs: -1 }))
|
||||
.rejects.toThrow("non-negative integer");
|
||||
await expect(bridge.request("state.get", {}, { minimumTimeoutMs: 1.5 }))
|
||||
.rejects.toThrow("non-negative integer");
|
||||
client.close();
|
||||
});
|
||||
|
||||
it("rejects invalid configuration without opening a listener", () => {
|
||||
expect(() => new UxpWebSocketBridge({ token: "short" })).toThrow(
|
||||
"at least 16 characters",
|
||||
);
|
||||
expect(() => new UxpWebSocketBridge({
|
||||
token: TOKEN,
|
||||
port: 70_000,
|
||||
})).toThrow("between 0 and 65535");
|
||||
expect(new UxpBridgeError("CODE", "message")).toMatchObject({
|
||||
code: "CODE",
|
||||
message: "message",
|
||||
});
|
||||
expect(() => new UxpWebSocketBridge({ token: TOKEN, path: "uxp" }))
|
||||
.toThrow("must begin with '/'");
|
||||
});
|
||||
|
||||
it("forwards host events and command failures", async () => {
|
||||
const bridge = await createBridge();
|
||||
const client = await connectHost(bridge);
|
||||
const event = once(bridge, "event");
|
||||
client.send(JSON.stringify({ protocolVersion: 1, type: "event", payload: { kind: "projectChanged" } }));
|
||||
await expect(event).resolves.toEqual([{ kind: "projectChanged" }]);
|
||||
|
||||
client.once("message", (data) => {
|
||||
const command = JSON.parse(data.toString());
|
||||
client.send(JSON.stringify({
|
||||
protocolVersion: 1, type: "result", requestId: command.requestId,
|
||||
payload: { ok: false, error: { code: "HOST_BUSY", message: "Dialog open" } },
|
||||
}));
|
||||
});
|
||||
await expect(bridge.request("state.get")).rejects.toMatchObject({ code: "HOST_BUSY", message: "Dialog open" });
|
||||
client.close();
|
||||
});
|
||||
|
||||
it("rejects requests while stopped and rejects work when a host reconnects", async () => {
|
||||
const bridge = await createBridge();
|
||||
await expect(bridge.request("state.get")).rejects.toMatchObject({ code: "UXP_NOT_CONNECTED" });
|
||||
const first = await connectHost(bridge);
|
||||
const pending = bridge.request("state.get");
|
||||
const rejection = expect(pending).rejects.toMatchObject({ code: "UXP_RECONNECTED" });
|
||||
const second = await connectHost(bridge);
|
||||
await rejection;
|
||||
first.on("error", () => {});
|
||||
second.close();
|
||||
});
|
||||
|
||||
it("closes clients that send invalid JSON or an invalid handshake", async () => {
|
||||
const bridge = await createBridge();
|
||||
const invalidJson = new WebSocket(bridgeUrl(bridge));
|
||||
await once(invalidJson, "open");
|
||||
invalidJson.send("{");
|
||||
const [jsonCode] = await once(invalidJson, "close");
|
||||
expect(jsonCode).toBe(1007);
|
||||
|
||||
const invalidHello = new WebSocket(bridgeUrl(bridge));
|
||||
await once(invalidHello, "open");
|
||||
invalidHello.send(JSON.stringify({ protocolVersion: 99, type: "hello", payload: { backend: "uxp", protocolVersion: 99, commands: {} } }));
|
||||
const [helloCode] = await once(invalidHello, "close");
|
||||
expect(helloCode).toBe(1008);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user