292 lines
8.8 KiB
TypeScript
292 lines
8.8 KiB
TypeScript
import { config } from './config.js';
|
|
|
|
export interface PlatformClientOptions {
|
|
/** Bearer token for the upstream request */
|
|
token?: string;
|
|
/** x-request-id to propagate */
|
|
requestId?: string;
|
|
/** x-product-id to forward */
|
|
productId?: string;
|
|
}
|
|
|
|
async function platformFetch<T>(
|
|
path: string,
|
|
init: RequestInit,
|
|
opts: PlatformClientOptions
|
|
): Promise<T> {
|
|
const url = `${config.PLATFORM_SERVICE_URL}${path}`;
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
|
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
|
...(opts.productId ? { 'x-product-id': opts.productId } : {}),
|
|
};
|
|
const res = await fetch(url, {
|
|
...init,
|
|
headers: { ...headers, ...(init.headers ?? {}) },
|
|
signal: AbortSignal.timeout(10_000),
|
|
});
|
|
if (!res.ok) {
|
|
const body = await res.text().catch(() => '');
|
|
throw new Error(`platform-service ${init.method ?? 'GET'} ${path} → ${res.status}: ${body}`);
|
|
}
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
// ── Telemetry ─────────────────────────────────────────────────────────────────
|
|
|
|
export interface TelemetryQueryResult {
|
|
events: unknown[];
|
|
total: number;
|
|
continuationToken?: string;
|
|
}
|
|
|
|
export async function telemetryQuery(
|
|
params: {
|
|
productId: string;
|
|
eventType?: string;
|
|
from?: string;
|
|
to?: string;
|
|
limit?: number;
|
|
continuationToken?: string;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<TelemetryQueryResult> {
|
|
const qs = new URLSearchParams();
|
|
qs.set('productId', params.productId);
|
|
if (params.eventType) qs.set('eventType', params.eventType);
|
|
if (params.from) qs.set('from', params.from);
|
|
if (params.to) qs.set('to', params.to);
|
|
qs.set(
|
|
'limit',
|
|
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
|
|
);
|
|
if (params.continuationToken) qs.set('continuationToken', params.continuationToken);
|
|
return platformFetch<TelemetryQueryResult>(`/api/telemetry/query?${qs}`, { method: 'GET' }, opts);
|
|
}
|
|
|
|
export interface TelemetryCluster {
|
|
/** Cluster doc ID: ${fingerprint}:${yyyyMM} */
|
|
id: string;
|
|
/** Partition key: ${productId}:${platform}:${module} */
|
|
pk: string;
|
|
fingerprint: string;
|
|
severity: 'warn' | 'error' | 'fatal';
|
|
totalCount: number;
|
|
lastSeenAt: string;
|
|
firstSeenAt: string;
|
|
platform: string;
|
|
module: string;
|
|
eventName: string;
|
|
sampleMessage?: string;
|
|
status: 'open' | 'resolved' | 'ignored';
|
|
resolvedBy?: string;
|
|
resolvedAt?: string;
|
|
}
|
|
|
|
export async function telemetryClusters(
|
|
params: { from?: string; to?: string; platform?: string; module?: string },
|
|
opts: PlatformClientOptions
|
|
): Promise<{ clusters: TelemetryCluster[]; total: number }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.from) qs.set('from', params.from);
|
|
if (params.to) qs.set('to', params.to);
|
|
if (params.platform) qs.set('platform', params.platform);
|
|
if (params.module) qs.set('module', params.module);
|
|
return platformFetch<{ clusters: TelemetryCluster[]; total: number }>(
|
|
`/api/telemetry/clusters?${qs}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function telemetryMetrics(opts: PlatformClientOptions): Promise<unknown> {
|
|
return platformFetch<unknown>('/api/telemetry/metrics', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export async function telemetryListPolicies(opts: PlatformClientOptions): Promise<unknown> {
|
|
return platformFetch<unknown>('/api/telemetry/policies', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export async function telemetryPreviewPolicy(
|
|
body: { targeting?: Record<string, unknown> },
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
'/api/telemetry/policies/preview',
|
|
{ method: 'POST', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function telemetryCreatePolicy(
|
|
body: Record<string, unknown>,
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
'/api/telemetry/policies',
|
|
{ method: 'POST', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function telemetryUpdatePolicy(
|
|
policyId: string,
|
|
body: Record<string, unknown>,
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
`/api/telemetry/policies/${encodeURIComponent(policyId)}`,
|
|
{ method: 'PUT', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function telemetryDeletePolicy(
|
|
policyId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<{ success: boolean }> {
|
|
return platformFetch<{ success: boolean }>(
|
|
`/api/telemetry/policies/${encodeURIComponent(policyId)}`,
|
|
{ method: 'DELETE' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function telemetryUpdateCluster(
|
|
clusterId: string,
|
|
pk: string,
|
|
status: 'open' | 'resolved' | 'ignored',
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
const qs = new URLSearchParams({ pk });
|
|
return platformFetch<unknown>(
|
|
`/api/telemetry/clusters/${encodeURIComponent(clusterId)}?${qs}`,
|
|
{ method: 'PATCH', body: JSON.stringify({ status }) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Diagnostics ───────────────────────────────────────────────────────────────
|
|
|
|
export interface DebugSession {
|
|
id: string;
|
|
productId: string;
|
|
status: string;
|
|
collectionLevel: string;
|
|
captureLogs: boolean;
|
|
captureNetwork: boolean;
|
|
captureScreenshots: boolean;
|
|
maxDurationMinutes: number;
|
|
logCount: number;
|
|
traceCount: number;
|
|
createdAt: string;
|
|
expiresAt: string;
|
|
targetUserId?: string;
|
|
targetAnonymousId?: string;
|
|
}
|
|
|
|
export async function diagnosticsListSessions(
|
|
params: {
|
|
productId?: string;
|
|
status?: string;
|
|
limit?: number;
|
|
offset?: number;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<{ sessions: DebugSession[]; total: number }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.productId) qs.set('productId', params.productId);
|
|
if (params.status) qs.set('status', params.status);
|
|
qs.set(
|
|
'limit',
|
|
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
|
|
);
|
|
if (params.offset) qs.set('offset', String(params.offset));
|
|
return platformFetch<{ sessions: DebugSession[]; total: number }>(
|
|
`/api/diagnostics/sessions?${qs}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function diagnosticsCreateSession(
|
|
body: {
|
|
productId: string;
|
|
targetUserId?: string;
|
|
targetAnonymousId?: string;
|
|
collectionLevel?: 'standard' | 'debug' | 'trace';
|
|
captureLogs?: boolean;
|
|
captureNetwork?: boolean;
|
|
maxDurationMinutes?: number;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<DebugSession> {
|
|
return platformFetch<DebugSession>(
|
|
'/api/diagnostics/sessions',
|
|
{ method: 'POST', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function diagnosticsGetSession(
|
|
sessionId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<DebugSession> {
|
|
return platformFetch<DebugSession>(
|
|
`/api/diagnostics/sessions/${encodeURIComponent(sessionId)}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function diagnosticsUpdateSession(
|
|
sessionId: string,
|
|
body: { status?: string; collectionLevel?: string; maxDurationMinutes?: number },
|
|
opts: PlatformClientOptions
|
|
): Promise<DebugSession> {
|
|
return platformFetch<DebugSession>(
|
|
`/api/diagnostics/sessions/${encodeURIComponent(sessionId)}`,
|
|
{ method: 'PATCH', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function diagnosticsGetLogs(
|
|
sessionId: string,
|
|
params: { level?: string; from?: string; to?: string; limit?: number },
|
|
opts: PlatformClientOptions
|
|
): Promise<{ logs: unknown[]; continuationToken?: string }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.level) qs.set('level', params.level);
|
|
if (params.from) qs.set('from', params.from);
|
|
if (params.to) qs.set('to', params.to);
|
|
qs.set(
|
|
'limit',
|
|
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
|
|
);
|
|
return platformFetch<{ logs: unknown[]; continuationToken?: string }>(
|
|
`/api/diagnostics/sessions/${encodeURIComponent(sessionId)}/logs?${qs}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function diagnosticsGetTraces(
|
|
sessionId: string,
|
|
params: { limit?: number; continuationToken?: string },
|
|
opts: PlatformClientOptions
|
|
): Promise<{ traces: unknown[]; continuationToken?: string }> {
|
|
const qs = new URLSearchParams();
|
|
qs.set(
|
|
'limit',
|
|
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
|
|
);
|
|
if (params.continuationToken) qs.set('continuationToken', params.continuationToken);
|
|
return platformFetch<{ traces: unknown[]; continuationToken?: string }>(
|
|
`/api/diagnostics/sessions/${encodeURIComponent(sessionId)}/traces?${qs}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|