All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Replace exported const mockConnections with getMockConnections() function that returns mock data only when NODE_ENV === "development". In production and test environments, returns an empty array as defense-in-depth alongside the existing ComingSoon page gate (SEC-WEB-4). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
282 lines
7.0 KiB
TypeScript
282 lines
7.0 KiB
TypeScript
/**
|
|
* Federation API Client
|
|
* Handles federation connection management API requests
|
|
*/
|
|
|
|
import { apiGet, apiPost, apiPatch } from "./client";
|
|
|
|
/**
|
|
* Federation connection status
|
|
*/
|
|
export enum FederationConnectionStatus {
|
|
PENDING = "PENDING",
|
|
ACTIVE = "ACTIVE",
|
|
DISCONNECTED = "DISCONNECTED",
|
|
REJECTED = "REJECTED",
|
|
}
|
|
|
|
/**
|
|
* Federation capabilities
|
|
*/
|
|
export interface FederationCapabilities {
|
|
supportsQuery: boolean;
|
|
supportsCommand: boolean;
|
|
supportsEvent: boolean;
|
|
supportsAgentSpawn: boolean;
|
|
protocolVersion: string;
|
|
}
|
|
|
|
/**
|
|
* Connection details
|
|
*/
|
|
export interface ConnectionDetails {
|
|
id: string;
|
|
workspaceId: string;
|
|
remoteInstanceId: string;
|
|
remoteUrl: string;
|
|
remotePublicKey: string;
|
|
remoteCapabilities: FederationCapabilities;
|
|
status: FederationConnectionStatus;
|
|
metadata: Record<string, unknown>;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
connectedAt: string | null;
|
|
disconnectedAt: string | null;
|
|
}
|
|
|
|
/**
|
|
* Public instance identity
|
|
*/
|
|
export interface PublicInstanceIdentity {
|
|
id: string;
|
|
instanceId: string;
|
|
name: string;
|
|
url: string;
|
|
publicKey: string;
|
|
capabilities: FederationCapabilities;
|
|
metadata: Record<string, unknown>;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
/**
|
|
* Initiate connection request
|
|
*/
|
|
export interface InitiateConnectionRequest {
|
|
remoteUrl: string;
|
|
}
|
|
|
|
/**
|
|
* Accept connection request
|
|
*/
|
|
export interface AcceptConnectionRequest {
|
|
metadata?: Record<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* Reject connection request
|
|
*/
|
|
export interface RejectConnectionRequest {
|
|
reason: string;
|
|
}
|
|
|
|
/**
|
|
* Disconnect connection request
|
|
*/
|
|
export interface DisconnectConnectionRequest {
|
|
reason?: string;
|
|
}
|
|
|
|
/**
|
|
* Fetch all connections
|
|
*/
|
|
export async function fetchConnections(
|
|
status?: FederationConnectionStatus
|
|
): Promise<ConnectionDetails[]> {
|
|
const params = new URLSearchParams();
|
|
|
|
if (status) {
|
|
params.append("status", status);
|
|
}
|
|
|
|
const queryString = params.toString();
|
|
const endpoint = queryString
|
|
? `/api/v1/federation/connections?${queryString}`
|
|
: "/api/v1/federation/connections";
|
|
|
|
return apiGet<ConnectionDetails[]>(endpoint);
|
|
}
|
|
|
|
/**
|
|
* Fetch a single connection
|
|
*/
|
|
export async function fetchConnection(connectionId: string): Promise<ConnectionDetails> {
|
|
return apiGet<ConnectionDetails>(`/api/v1/federation/connections/${connectionId}`);
|
|
}
|
|
|
|
/**
|
|
* Initiate a new connection
|
|
*/
|
|
export async function initiateConnection(
|
|
request: InitiateConnectionRequest
|
|
): Promise<ConnectionDetails> {
|
|
return apiPost<ConnectionDetails>("/api/v1/federation/connections/initiate", request);
|
|
}
|
|
|
|
/**
|
|
* Accept a pending connection
|
|
*/
|
|
export async function acceptConnection(
|
|
connectionId: string,
|
|
request?: AcceptConnectionRequest
|
|
): Promise<ConnectionDetails> {
|
|
return apiPost<ConnectionDetails>(
|
|
`/api/v1/federation/connections/${connectionId}/accept`,
|
|
request ?? {}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Reject a pending connection
|
|
*/
|
|
export async function rejectConnection(
|
|
connectionId: string,
|
|
request: RejectConnectionRequest
|
|
): Promise<ConnectionDetails> {
|
|
return apiPost<ConnectionDetails>(
|
|
`/api/v1/federation/connections/${connectionId}/reject`,
|
|
request
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Disconnect an active connection
|
|
*/
|
|
export async function disconnectConnection(
|
|
connectionId: string,
|
|
request?: DisconnectConnectionRequest
|
|
): Promise<ConnectionDetails> {
|
|
return apiPost<ConnectionDetails>(
|
|
`/api/v1/federation/connections/${connectionId}/disconnect`,
|
|
request ?? {}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get this instance's public identity
|
|
*/
|
|
export async function fetchInstanceIdentity(): Promise<PublicInstanceIdentity> {
|
|
return apiGet<PublicInstanceIdentity>("/api/v1/federation/instance");
|
|
}
|
|
|
|
/**
|
|
* Update instance configuration request
|
|
*/
|
|
export interface UpdateInstanceRequest {
|
|
name?: string;
|
|
capabilities?: FederationCapabilities;
|
|
metadata?: Record<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* Update this instance's configuration
|
|
* Admin-only operation
|
|
*/
|
|
export async function updateInstanceConfiguration(
|
|
updates: UpdateInstanceRequest
|
|
): Promise<PublicInstanceIdentity> {
|
|
return apiPatch<PublicInstanceIdentity>("/api/v1/federation/instance", updates);
|
|
}
|
|
|
|
/**
|
|
* Regenerate instance keypair
|
|
* Admin-only operation
|
|
*/
|
|
export async function regenerateInstanceKeys(): Promise<PublicInstanceIdentity> {
|
|
return apiPost<PublicInstanceIdentity>("/api/v1/federation/instance/regenerate-keys", {});
|
|
}
|
|
|
|
/**
|
|
* Get mock connections for development only.
|
|
* Returns an empty array in production as defense-in-depth.
|
|
* The federation pages are also gated behind a ComingSoon component
|
|
* in production (SEC-WEB-4), but this provides an additional layer.
|
|
*/
|
|
export function getMockConnections(): ConnectionDetails[] {
|
|
if (process.env.NODE_ENV !== "development") {
|
|
return [];
|
|
}
|
|
|
|
return [
|
|
{
|
|
id: "conn-1",
|
|
workspaceId: "workspace-1",
|
|
remoteInstanceId: "instance-work-001",
|
|
remoteUrl: "https://mosaic.work.example.com",
|
|
remotePublicKey: "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
|
|
remoteCapabilities: {
|
|
supportsQuery: true,
|
|
supportsCommand: true,
|
|
supportsEvent: true,
|
|
supportsAgentSpawn: true,
|
|
protocolVersion: "1.0",
|
|
},
|
|
status: FederationConnectionStatus.ACTIVE,
|
|
metadata: {
|
|
name: "Work Instance",
|
|
description: "Corporate Mosaic instance",
|
|
},
|
|
createdAt: new Date("2026-02-01").toISOString(),
|
|
updatedAt: new Date("2026-02-01").toISOString(),
|
|
connectedAt: new Date("2026-02-01").toISOString(),
|
|
disconnectedAt: null,
|
|
},
|
|
{
|
|
id: "conn-2",
|
|
workspaceId: "workspace-1",
|
|
remoteInstanceId: "instance-partner-001",
|
|
remoteUrl: "https://mosaic.partner.example.com",
|
|
remotePublicKey: "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
|
|
remoteCapabilities: {
|
|
supportsQuery: true,
|
|
supportsCommand: false,
|
|
supportsEvent: true,
|
|
supportsAgentSpawn: false,
|
|
protocolVersion: "1.0",
|
|
},
|
|
status: FederationConnectionStatus.PENDING,
|
|
metadata: {
|
|
name: "Partner Instance",
|
|
description: "Shared project collaboration",
|
|
},
|
|
createdAt: new Date("2026-02-02").toISOString(),
|
|
updatedAt: new Date("2026-02-02").toISOString(),
|
|
connectedAt: null,
|
|
disconnectedAt: null,
|
|
},
|
|
{
|
|
id: "conn-3",
|
|
workspaceId: "workspace-1",
|
|
remoteInstanceId: "instance-old-001",
|
|
remoteUrl: "https://mosaic.old.example.com",
|
|
remotePublicKey: "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
|
|
remoteCapabilities: {
|
|
supportsQuery: true,
|
|
supportsCommand: true,
|
|
supportsEvent: false,
|
|
supportsAgentSpawn: false,
|
|
protocolVersion: "1.0",
|
|
},
|
|
status: FederationConnectionStatus.DISCONNECTED,
|
|
metadata: {
|
|
name: "Previous Instance",
|
|
description: "No longer in use",
|
|
},
|
|
createdAt: new Date("2026-01-15").toISOString(),
|
|
updatedAt: new Date("2026-01-30").toISOString(),
|
|
connectedAt: new Date("2026-01-15").toISOString(),
|
|
disconnectedAt: new Date("2026-01-30").toISOString(),
|
|
},
|
|
];
|
|
}
|