chore: establish local platform baseline
This commit is contained in:
@@ -0,0 +1,467 @@
|
||||
import type { components, paths } from "./schema";
|
||||
|
||||
type LivenessResponse = paths["/health/live"]["get"]["responses"]["200"]["content"]["application/json"];
|
||||
type ReadinessResponse = paths["/health/ready"]["get"]["responses"]["200"]["content"]["application/json"];
|
||||
type VersionResponse = paths["/api/v1/version"]["get"]["responses"]["200"]["content"]["application/json"];
|
||||
type CurrentUserResponse = paths["/api/v1/me"]["get"]["responses"]["200"]["content"]["application/json"];
|
||||
export type CurrentUser = components["schemas"]["CurrentUser"];
|
||||
export type Household = components["schemas"]["Household"];
|
||||
export type HouseholdMember = components["schemas"]["HouseholdMember"];
|
||||
export type MemberInvitation = components["schemas"]["MemberInvitation"];
|
||||
export type MemberInvitationPreview = components["schemas"]["MemberInvitationPreview"];
|
||||
export type Subject = components["schemas"]["Subject"];
|
||||
export type SubjectFact = components["schemas"]["SubjectFact"];
|
||||
export type Consent = components["schemas"]["Consent"];
|
||||
export type ConversationReadiness = components["schemas"]["ConversationReadiness"];
|
||||
export type ManagedDevice = components["schemas"]["ManagedDevice"];
|
||||
export type DevicePresence = components["schemas"]["DevicePresence"];
|
||||
export type DeviceTwin = components["schemas"]["DeviceTwin"];
|
||||
export type DeviceCommand = components["schemas"]["DeviceCommand"];
|
||||
export type FirmwareSigningKey = components["schemas"]["FirmwareSigningKey"];
|
||||
export type FirmwareRelease = components["schemas"]["FirmwareRelease"];
|
||||
export type FirmwareReleaseImpact = components["schemas"]["FirmwareReleaseImpact"];
|
||||
export type FirmwareRollout = components["schemas"]["FirmwareRollout"];
|
||||
export type FirmwareAssignment = components["schemas"]["FirmwareAssignment"];
|
||||
export type FirmwareAssignmentEvent = components["schemas"]["FirmwareAssignmentEvent"];
|
||||
export type ConversationSummary = components["schemas"]["ConversationSummary"];
|
||||
export type ConversationDetail = components["schemas"]["ConversationDetail"];
|
||||
export type ConversationUsage = components["schemas"]["ConversationUsage"];
|
||||
export type ConversationTurn = components["schemas"]["ConversationTurn"];
|
||||
export type ConversationContentSummary = components["schemas"]["ConversationContentSummary"];
|
||||
export type ConversationMedia = components["schemas"]["ConversationMedia"];
|
||||
export type MediaSegment = components["schemas"]["MediaSegment"];
|
||||
export type RecordingAccessGrant = components["schemas"]["RecordingAccessGrant"];
|
||||
export type MediaAccess = components["schemas"]["MediaAccess"];
|
||||
export type Story = components["schemas"]["Story"];
|
||||
export type CuratedMemory = components["schemas"]["CuratedMemory"];
|
||||
export type PrivacyRequest = components["schemas"]["PrivacyRequest"];
|
||||
export type ExportTask = components["schemas"]["ExportTask"];
|
||||
export type SharingGrant = components["schemas"]["SharingGrant"];
|
||||
export type OpsConfigVersion = components["schemas"]["OpsConfigVersion"];
|
||||
export type OpsConfigEvaluation = components["schemas"]["OpsConfigEvaluation"];
|
||||
export type OpsResourceReference = components["schemas"]["OpsResourceReference"];
|
||||
export type OpsDeviceGroup = components["schemas"]["OpsDeviceGroup"];
|
||||
export type OpsConfigRollout = components["schemas"]["OpsConfigRollout"];
|
||||
export type OpsConfigTransitionRequest = components["schemas"]["OpsConfigTransitionRequest"];
|
||||
export type BillingPriceBook = components["schemas"]["BillingPriceBook"];
|
||||
export type QuotaPolicy = components["schemas"]["QuotaPolicy"];
|
||||
export type CostSummary = components["schemas"]["CostSummary"];
|
||||
export type AlertRoutingPolicy = components["schemas"]["AlertRoutingPolicy"];
|
||||
export type AlertDeliveryTest = components["schemas"]["AlertDeliveryTest"];
|
||||
export type AlertIncident = components["schemas"]["AlertIncident"];
|
||||
export type AlertSilence = components["schemas"]["AlertSilence"];
|
||||
export type OpsDiagnosticRecord = components["schemas"]["OpsDiagnosticRecord"];
|
||||
export type OpsExternalDependency = components["schemas"]["OpsExternalDependency"];
|
||||
export type OpsDiagnosticAction = components["schemas"]["OpsDiagnosticAction"];
|
||||
export type OpsDiagnosticJob = components["schemas"]["OpsDiagnosticJob"];
|
||||
export type OpsDiagnosticBundle = components["schemas"]["OpsDiagnosticBundle"];
|
||||
export type OpsAcceptanceSnapshot = components["schemas"]["OpsAcceptanceSnapshot"];
|
||||
export type OpsAcceptanceSnapshotRequest = components["schemas"]["OpsAcceptanceSnapshotRequest"];
|
||||
export type OpsPilotDailySnapshot = components["schemas"]["OpsPilotDailySnapshot"];
|
||||
export type OpsPilotDailySnapshotRequest = components["schemas"]["OpsPilotDailySnapshotRequest"];
|
||||
export type OpsPilotWeeklySnapshot = components["schemas"]["OpsPilotWeeklySnapshot"];
|
||||
export type OpsPilotWeeklySnapshotRequest = components["schemas"]["OpsPilotWeeklySnapshotRequest"];
|
||||
export type IntegrationIssue = components["schemas"]["IntegrationIssue"];
|
||||
export type IntegrationIssueSummary = components["schemas"]["IntegrationIssueSummary"];
|
||||
export type OpsSensitiveAccessRequest = components["schemas"]["OpsSensitiveAccessRequest"];
|
||||
export type OpsSensitiveConversation = components["schemas"]["OpsSensitiveConversation"];
|
||||
export type OpsSensitiveRecordingAccess = components["schemas"]["OpsSensitiveRecordingAccess"];
|
||||
export type CreateSubjectRequest = components["schemas"]["CreateSubjectRequest"];
|
||||
export type UpdateSubjectRequest = components["schemas"]["UpdateSubjectRequest"];
|
||||
export type CreateSubjectFactRequest = components["schemas"]["CreateSubjectFactRequest"];
|
||||
export type GrantConsentRequest = components["schemas"]["GrantConsentRequest"];
|
||||
export type PrepareFirmwareReleaseRequest = components["schemas"]["PrepareFirmwareReleaseRequest"];
|
||||
export type DeviceClaimRequest = components["schemas"]["DeviceClaimRequest"];
|
||||
export type UpdateDeviceTwinRequest = components["schemas"]["UpdateDeviceTwinRequest"];
|
||||
export type CreateMemberInvitationRequest = components["schemas"]["CreateMemberInvitationRequest"];
|
||||
|
||||
export type ConversationQuery = {
|
||||
status?: components["schemas"]["ConversationStatus"];
|
||||
engine_mode?: "s2s_pure" | "s2s_hybrid" | "cascade";
|
||||
from?: string;
|
||||
to?: string;
|
||||
limit?: number;
|
||||
cursor?: string;
|
||||
};
|
||||
|
||||
type Envelope<T> = { request_id: string; data: T };
|
||||
const requestTimeoutMS = 15_000;
|
||||
export const AUTHENTICATION_REQUIRED_EVENT = "platform:authentication-required";
|
||||
|
||||
export class APIRequestError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly status: number,
|
||||
public readonly code?: string
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
async function getJSON<T>(path: string, signal?: AbortSignal): Promise<T> {
|
||||
const response = await fetchWithTimeout(path, {
|
||||
headers: { Accept: "application/json" }
|
||||
}, signal);
|
||||
if (!response.ok) {
|
||||
throw new Error(`请求失败(HTTP ${response.status})`);
|
||||
}
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
|
||||
export const platformApi = {
|
||||
liveness: (signal?: AbortSignal) => getJSON<LivenessResponse>("/health/live", signal),
|
||||
readiness: (signal?: AbortSignal) => getJSON<ReadinessResponse>("/health/ready", signal),
|
||||
version: (signal?: AbortSignal) => getJSON<VersionResponse>("/api/v1/version", signal),
|
||||
currentUser: (accessToken: string, signal?: AbortSignal) =>
|
||||
getJSONWithToken<CurrentUserResponse>("/api/v1/me", accessToken, signal),
|
||||
opsConfigVersions: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: OpsConfigVersion[] }>>("GET", "/api/v1/ops/config-versions", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
createOpsConfigVersion: (tenantID: string, input: components["schemas"]["CreateOpsConfigVersionRequest"], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsConfigVersion>>("POST", "/api/v1/ops/config-versions", accessToken, undefined, input,
|
||||
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-config-${crypto.randomUUID()}` }),
|
||||
submitOpsConfigVersion: (tenantID: string, item: OpsConfigVersion, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsConfigVersion>>("PATCH", `/api/v1/ops/config-versions/${item.id}/submit`, accessToken, undefined, undefined,
|
||||
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
|
||||
publishOpsConfigVersion: (tenantID: string, item: OpsConfigVersion, input: OpsConfigTransitionRequest, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsConfigVersion>>("PATCH", `/api/v1/ops/config-versions/${item.id}/publish`, accessToken, undefined, input,
|
||||
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
|
||||
rollbackOpsConfigVersion: (tenantID: string, item: OpsConfigVersion, input: OpsConfigTransitionRequest, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsConfigVersion>>("POST", `/api/v1/ops/config-versions/${item.id}/rollback`, accessToken, undefined, input,
|
||||
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-config-rollback-${crypto.randomUUID()}` }),
|
||||
opsConfigEvaluations: (tenantID: string, configID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: OpsConfigEvaluation[] }>>("GET", `/api/v1/ops/config-versions/${configID}/evaluations`, accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
createOpsConfigEvaluation: (tenantID: string, configID: string, input: components["schemas"]["CreateOpsConfigEvaluationRequest"], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsConfigEvaluation>>("POST", `/api/v1/ops/config-versions/${configID}/evaluations`, accessToken, undefined, input,
|
||||
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-config-evaluation-${crypto.randomUUID()}` }),
|
||||
opsResourceReferences: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: OpsResourceReference[] }>>("GET", "/api/v1/ops/resource-references", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
createOpsResourceReference: (tenantID: string, input: components["schemas"]["CreateOpsResourceReferenceRequest"], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsResourceReference>>("POST", "/api/v1/ops/resource-references", accessToken, undefined, input,
|
||||
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-resource-reference-${crypto.randomUUID()}` }),
|
||||
revokeOpsResourceReference: (tenantID: string, item: OpsResourceReference, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsResourceReference>>("PATCH", `/api/v1/ops/resource-references/${item.id}/revoke`, accessToken, undefined, { reason },
|
||||
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
|
||||
opsDeviceGroups: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: OpsDeviceGroup[] }>>("GET", "/api/v1/ops/device-groups", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
createOpsDeviceGroup: (tenantID: string, input: components["schemas"]["CreateOpsDeviceGroupRequest"], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsDeviceGroup>>("POST", "/api/v1/ops/device-groups", accessToken, undefined, input,
|
||||
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-device-group-${crypto.randomUUID()}` }),
|
||||
opsConfigRollouts: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: OpsConfigRollout[] }>>("GET", "/api/v1/ops/config-rollouts", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
pauseOpsConfigRollout: (tenantID: string, item: OpsConfigRollout, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsConfigRollout>>("PATCH", `/api/v1/ops/config-rollouts/${item.id}/pause`, accessToken, undefined, { reason },
|
||||
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
|
||||
resumeOpsConfigRollout: (tenantID: string, item: OpsConfigRollout, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsConfigRollout>>("PATCH", `/api/v1/ops/config-rollouts/${item.id}/resume`, accessToken, undefined, { reason },
|
||||
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
|
||||
opsPriceBooks: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: BillingPriceBook[] }>>("GET", "/api/v1/ops/price-books", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
createOpsPriceBook: (tenantID: string, input: components["schemas"]["CreateBillingPriceBookRequest"], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<BillingPriceBook>>("POST", "/api/v1/ops/price-books", accessToken, undefined, input,
|
||||
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `billing-price-${crypto.randomUUID()}` }),
|
||||
publishOpsPriceBook: (tenantID: string, item: BillingPriceBook, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<BillingPriceBook>>("PATCH", `/api/v1/ops/price-books/${item.id}/publish`, accessToken, undefined, { reason },
|
||||
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
|
||||
opsQuotaPolicies: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: QuotaPolicy[] }>>("GET", "/api/v1/ops/quota-policies", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
createOpsQuotaPolicy: (tenantID: string, input: components["schemas"]["CreateQuotaPolicyRequest"], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<QuotaPolicy>>("POST", "/api/v1/ops/quota-policies", accessToken, undefined, input,
|
||||
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `usage-quota-${crypto.randomUUID()}` }),
|
||||
opsCostSummary: (tenantID: string, from: string, to: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<CostSummary>>("GET", `/api/v1/ops/cost-summary?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`, accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
opsAlertRoutingPolicies: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: AlertRoutingPolicy[] }>>("GET", "/api/v1/ops/alert-routing-policies", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
createOpsAlertRoutingPolicy: (tenantID: string, input: components["schemas"]["CreateAlertRoutingPolicyRequest"], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<AlertRoutingPolicy>>("POST", "/api/v1/ops/alert-routing-policies", accessToken, undefined, input,
|
||||
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `alert-route-${crypto.randomUUID()}` }),
|
||||
opsAlertDeliveryTests: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: AlertDeliveryTest[] }>>("GET", "/api/v1/ops/alert-delivery-tests", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
createOpsAlertDeliveryTest: (tenantID: string, routingPolicyID: string, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<AlertDeliveryTest>>("POST", `/api/v1/ops/alert-routing-policies/${routingPolicyID}/delivery-tests`, accessToken, undefined, { reason },
|
||||
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `alert-delivery-test-${crypto.randomUUID()}` }),
|
||||
opsAlerts: (tenantID: string, status: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: AlertIncident[] }>>("GET", `/api/v1/ops/alerts${status ? `?status=${encodeURIComponent(status)}` : ""}`, accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
opsAlert: (tenantID: string, alertID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<AlertIncident>>("GET", `/api/v1/ops/alerts/${alertID}`, accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
transitionOpsAlert: (tenantID: string, item: AlertIncident, action: "acknowledge" | "mitigate" | "resolve", note: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<AlertIncident>>("POST", `/api/v1/ops/alerts/${item.id}/${action}`, accessToken, undefined, { note },
|
||||
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
|
||||
opsAlertSilences: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: AlertSilence[] }>>("GET", "/api/v1/ops/alert-silences", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
createOpsAlertSilence: (tenantID: string, input: components["schemas"]["CreateAlertSilenceRequest"], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<AlertSilence>>("POST", "/api/v1/ops/alert-silences", accessToken, undefined, input,
|
||||
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `alert-silence-${crypto.randomUUID()}` }),
|
||||
revokeOpsAlertSilence: (tenantID: string, item: AlertSilence, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<AlertSilence>>("POST", `/api/v1/ops/alert-silences/${item.id}/revoke`, accessToken, undefined, { reason },
|
||||
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
|
||||
opsIntegrationIssues: (tenantID: string, status: components["schemas"]["IntegrationIssueStatus"] | "", accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: IntegrationIssue[]; summary: IntegrationIssueSummary }>>("GET", `/api/v1/ops/integration-issues${status ? `?status=${encodeURIComponent(status)}` : ""}`, accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
createOpsIntegrationIssue: (tenantID: string, input: components["schemas"]["CreateIntegrationIssueRequest"], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<IntegrationIssue>>("POST", "/api/v1/ops/integration-issues", accessToken, undefined, input,
|
||||
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-integration-issue-${crypto.randomUUID()}` }),
|
||||
updateOpsIntegrationIssue: (tenantID: string, item: IntegrationIssue, input: components["schemas"]["UpdateIntegrationIssueRequest"], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<IntegrationIssue>>("PATCH", `/api/v1/ops/integration-issues/${item.id}`, accessToken, undefined, input,
|
||||
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.version}"` }),
|
||||
searchOpsDiagnostics: (tenantID: string, field: "device_id" | "conversation_id" | "room_id" | "provider_task_ref" | "request_id", value: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<{ items: OpsDiagnosticRecord[] }>>("GET", `/api/v1/ops/diagnostics/search?field=${encodeURIComponent(field)}&value=${encodeURIComponent(value)}`, accessToken, undefined, undefined, { "X-Tenant-Id": tenantID }),
|
||||
opsExternalDependencies: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: OpsExternalDependency[] }>>("GET", "/api/v1/ops/diagnostics/external-dependencies", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
|
||||
stopOpsDiagnosticConversation: (tenantID: string, conversationID: string, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsDiagnosticAction>>("POST", `/api/v1/ops/diagnostics/conversations/${conversationID}/stop`, accessToken, undefined, { reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-diagnostic-stop-${crypto.randomUUID()}` }),
|
||||
reconcileOpsDiagnosticConversation: (tenantID: string, conversationID: string, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsDiagnosticAction>>("POST", `/api/v1/ops/diagnostics/conversations/${conversationID}/reconcile`, accessToken, undefined, { reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-diagnostic-reconcile-${crypto.randomUUID()}` }),
|
||||
refreshOpsDiagnosticDeviceConfig: (tenantID: string, deviceID: string, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsDiagnosticAction>>("POST", `/api/v1/ops/diagnostics/devices/${deviceID}/config-refresh`, accessToken, undefined, { reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-diagnostic-refresh-${crypto.randomUUID()}` }),
|
||||
revokeOpsDiagnosticDevice: (tenantID: string, deviceID: string, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsDiagnosticAction>>("POST", `/api/v1/ops/diagnostics/devices/${deviceID}/revoke`, accessToken, undefined, { reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-diagnostic-revoke-${crypto.randomUUID()}` }),
|
||||
opsDiagnosticJobs: (tenantID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<{ items: OpsDiagnosticJob[] }>>("GET", "/api/v1/ops/diagnostics/jobs", accessToken, undefined, undefined, { "X-Tenant-Id": tenantID }),
|
||||
retryOpsDiagnosticJob: (tenantID: string, jobID: string, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsDiagnosticAction>>("POST", `/api/v1/ops/diagnostics/jobs/${jobID}/retry`, accessToken, undefined, { reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-diagnostic-job-retry-${crypto.randomUUID()}` }),
|
||||
exportOpsDiagnosticBundle: (tenantID: string, field: "device_id" | "conversation_id" | "room_id" | "provider_task_ref" | "request_id", value: string, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsDiagnosticBundle>>("POST", "/api/v1/ops/diagnostics/bundles", accessToken, undefined, { field, value, reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-diagnostic-bundle-${crypto.randomUUID()}` }),
|
||||
exportOpsAcceptanceSnapshot: (tenantID: string, input: OpsAcceptanceSnapshotRequest, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsAcceptanceSnapshot>>("POST", "/api/v1/ops/diagnostics/acceptance-snapshots", accessToken, undefined, input, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-acceptance-snapshot-${crypto.randomUUID()}` }),
|
||||
exportOpsPilotDailySnapshot: (tenantID: string, input: OpsPilotDailySnapshotRequest, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsPilotDailySnapshot>>("POST", "/api/v1/ops/diagnostics/pilot-daily-snapshots", accessToken, undefined, input, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-pilot-daily-${crypto.randomUUID()}` }),
|
||||
exportOpsPilotWeeklySnapshot: (tenantID: string, input: OpsPilotWeeklySnapshotRequest, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsPilotWeeklySnapshot>>("POST", "/api/v1/ops/diagnostics/pilot-weekly-snapshots", accessToken, undefined, input, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-pilot-weekly-${crypto.randomUUID()}` }),
|
||||
opsSensitiveAccessRequests: (tenantID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<{ items: OpsSensitiveAccessRequest[] }>>("GET", "/api/v1/ops/sensitive-access-requests", accessToken, undefined, undefined, { "X-Tenant-Id": tenantID }),
|
||||
createOpsSensitiveAccessRequest: (tenantID: string, conversationID: string, capabilities: ("TRANSCRIPT_READ" | "RECORDING_PLAY")[], reason: string, expiresAt: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsSensitiveAccessRequest>>("POST", "/api/v1/ops/sensitive-access-requests", accessToken, undefined, { conversation_id: conversationID, capabilities, reason, expires_at: expiresAt }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-sensitive-access-${crypto.randomUUID()}` }),
|
||||
reviewOpsSensitiveAccessRequest: (tenantID: string, requestID: string, decision: "APPROVE" | "REJECT", reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsSensitiveAccessRequest>>("POST", `/api/v1/ops/sensitive-access-requests/${requestID}/review`, accessToken, undefined, { decision, reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-sensitive-review-${crypto.randomUUID()}` }),
|
||||
opsSensitiveConversation: (tenantID: string, conversationID: string, grantID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsSensitiveConversation>>("GET", `/api/v1/ops/sensitive-conversations/${conversationID}`, accessToken, undefined, undefined, { "X-Tenant-Id": tenantID, "X-Ops-Access-Grant": grantID }),
|
||||
createOpsSensitiveRecordingAccess: (tenantID: string, segmentID: string, grantID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<OpsSensitiveRecordingAccess>>("POST", `/api/v1/ops/sensitive-media/${segmentID}/play`, accessToken, undefined, { grant_id: grantID }, { "X-Tenant-Id": tenantID }),
|
||||
households: (accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: Household[] }>>("GET", "/api/v1/households", accessToken, signal),
|
||||
createHousehold: (tenantID: string, name: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<Household>>("POST", "/api/v1/households", accessToken, undefined, { name }, { "X-Tenant-Id": tenantID }),
|
||||
subjects: (householdID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: Subject[] }>>("GET", `/api/v1/households/${householdID}/subjects`, accessToken, signal),
|
||||
facts: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: SubjectFact[] }>>("GET", `/api/v1/subjects/${subjectID}/facts`, accessToken, signal),
|
||||
consents: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: Consent[] }>>("GET", `/api/v1/subjects/${subjectID}/consents`, accessToken, signal),
|
||||
conversationReadiness: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<ConversationReadiness>>("GET", `/api/v1/subjects/${subjectID}/conversation-readiness`, accessToken, signal),
|
||||
conversations: (subjectID: string, query: ConversationQuery, accessToken: string, signal?: AbortSignal) => {
|
||||
const parameters = new URLSearchParams({ limit: String(query.limit ?? 20) });
|
||||
if (query.status) parameters.set("status", query.status);
|
||||
if (query.engine_mode) parameters.set("engine_mode", query.engine_mode);
|
||||
if (query.from) parameters.set("from", query.from);
|
||||
if (query.to) parameters.set("to", query.to);
|
||||
if (query.cursor) parameters.set("cursor", query.cursor);
|
||||
return authenticatedJSON<Envelope<{ items: ConversationSummary[]; next_cursor?: string }>>(
|
||||
"GET",
|
||||
`/api/v1/subjects/${subjectID}/conversations?${parameters.toString()}`,
|
||||
accessToken,
|
||||
signal
|
||||
);
|
||||
},
|
||||
conversation: (conversationID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<ConversationDetail>>("GET", `/api/v1/conversations/${conversationID}`, accessToken, signal),
|
||||
conversationUsage: (conversationID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<ConversationUsage>>("GET", `/api/v1/conversations/${conversationID}/usage`, accessToken, signal),
|
||||
conversationTurns: (conversationID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: ConversationTurn[]; next_cursor?: string }>>("GET", `/api/v1/conversations/${conversationID}/turns?limit=100`, accessToken, signal),
|
||||
conversationSummaries: (conversationID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: ConversationContentSummary[] }>>("GET", `/api/v1/conversations/${conversationID}/summaries`, accessToken, signal),
|
||||
conversationMedia: (conversationID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<ConversationMedia>>("GET", `/api/v1/conversations/${conversationID}/media`, accessToken, signal),
|
||||
recordingAccessGrants: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: RecordingAccessGrant[] }>>("GET", `/api/v1/subjects/${subjectID}/recording-access-grants`, accessToken, signal),
|
||||
grantRecordingAccess: (subjectID: string, input: { grantee_user_id: string; permission: "PLAY" | "DOWNLOAD"; purpose: string; expires_at: string }, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<RecordingAccessGrant>>("POST", `/api/v1/subjects/${subjectID}/recording-access-grants`, accessToken, undefined, input),
|
||||
revokeRecordingAccess: (grantID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<RecordingAccessGrant>>("POST", `/api/v1/recording-access-grants/${grantID}/revoke`, accessToken),
|
||||
createMediaAccess: (segmentID: string, action: "PLAY" | "DOWNLOAD", purpose: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<MediaAccess>>("POST", `/api/v1/media-segments/${segmentID}/access`, accessToken, undefined, { action, purpose }),
|
||||
sharingGrants: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: SharingGrant[] }>>("GET", `/api/v1/sharing-grants?subject_id=${encodeURIComponent(subjectID)}`, accessToken, signal),
|
||||
createSharingGrant: (input: components["schemas"]["CreateSharingGrantRequest"], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<SharingGrant>>("POST", "/api/v1/sharing-grants", accessToken, undefined, input,
|
||||
{ "Idempotency-Key": `sharing-grant-${crypto.randomUUID()}` }),
|
||||
revokeSharingGrant: (grantID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<SharingGrant>>("DELETE", `/api/v1/sharing-grants/${grantID}`, accessToken),
|
||||
stories: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: Story[] }>>("GET", `/api/v1/subjects/${subjectID}/stories`, accessToken, signal),
|
||||
story: (storyID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<Story>>("GET", `/api/v1/stories/${storyID}`, accessToken, signal),
|
||||
reviseStory: (story: Story, content: string, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<Story>>("PATCH", `/api/v1/stories/${story.id}`, accessToken, undefined,
|
||||
{ content, reason }, { "If-Match": `"v${story.version}"` }),
|
||||
confirmStory: (story: Story, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<{ story: Story; memory: CuratedMemory }>>("POST", `/api/v1/stories/${story.id}/confirm`, accessToken, undefined,
|
||||
{ reason }, { "If-Match": `"v${story.version}"` }),
|
||||
rejectStory: (story: Story, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<Story>>("POST", `/api/v1/stories/${story.id}/reject`, accessToken, undefined,
|
||||
{ reason }, { "If-Match": `"v${story.version}"` }),
|
||||
memories: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: CuratedMemory[] }>>("GET", `/api/v1/subjects/${subjectID}/memories`, accessToken, signal),
|
||||
updateMemory: (memory: CuratedMemory, input: { content?: string; status?: "ACTIVE" | "DISABLED"; reason: string }, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<CuratedMemory>>("PATCH", `/api/v1/memories/${memory.id}`, accessToken, undefined,
|
||||
input, { "If-Match": `"v${memory.version}"` }),
|
||||
deleteMemory: (memory: CuratedMemory, reason: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<CuratedMemory>>("DELETE", `/api/v1/memories/${memory.id}`, accessToken, undefined,
|
||||
{ reason }, { "If-Match": `"v${memory.version}"` }),
|
||||
privacyRequests: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: PrivacyRequest[] }>>("GET", `/api/v1/subjects/${subjectID}/privacy-requests`, accessToken, signal),
|
||||
privacyRequest: (requestID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<PrivacyRequest>>("GET", `/api/v1/privacy-requests/${requestID}`, accessToken, signal),
|
||||
createPrivacyAccessRequest: (subjectID: string, categories: string[], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<PrivacyRequest>>("POST", `/api/v1/subjects/${subjectID}/privacy-requests`, accessToken, undefined,
|
||||
{ request_type: "ACCESS", scope: { categories } }, { "Idempotency-Key": `privacy-access-${crypto.randomUUID()}` }),
|
||||
createPrivacyDeleteRequest: (subjectID: string, reason: string, confirmation: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<PrivacyRequest>>("POST", `/api/v1/subjects/${subjectID}/privacy-requests`, accessToken, undefined,
|
||||
{ request_type: "DELETE", scope: {}, reason, confirmation }, { "Idempotency-Key": `privacy-delete-${crypto.randomUUID()}` }),
|
||||
exportTasks: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: ExportTask[] }>>("GET", `/api/v1/subjects/${subjectID}/exports`, accessToken, signal),
|
||||
exportTask: (exportID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<ExportTask>>("GET", `/api/v1/exports/${exportID}`, accessToken, signal),
|
||||
createExportTask: (subjectID: string, categories: string[], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<ExportTask>>("POST", `/api/v1/subjects/${subjectID}/exports`, accessToken, undefined,
|
||||
{ scope: { categories } }, { "Idempotency-Key": `subject-export-${crypto.randomUUID()}` }),
|
||||
devices: (accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: ManagedDevice[] }>>("GET", "/api/v1/devices", accessToken, signal),
|
||||
devicePresence: (deviceID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<DevicePresence>>("GET", `/api/v1/devices/${deviceID}/presence`, accessToken, signal),
|
||||
deviceTwin: (deviceID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<DeviceTwin>>("GET", `/api/v1/devices/${deviceID}/twin`, accessToken, signal),
|
||||
members: (householdID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: HouseholdMember[] }>>("GET", `/api/v1/households/${householdID}/members`, accessToken, signal),
|
||||
invitations: (householdID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: MemberInvitation[] }>>("GET", `/api/v1/households/${householdID}/invitations`, accessToken, signal),
|
||||
createInvitation: (householdID: string, input: CreateMemberInvitationRequest, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<MemberInvitation>>("POST", `/api/v1/households/${householdID}/invitations`, accessToken, undefined, input),
|
||||
revokeInvitation: (householdID: string, invitationID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<MemberInvitation>>("POST", `/api/v1/households/${householdID}/invitations/${invitationID}/revoke`, accessToken),
|
||||
previewInvitation: (token: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<MemberInvitationPreview>>("POST", "/api/v1/member-invitations/preview", accessToken, undefined, { token }),
|
||||
acceptInvitation: (token: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<HouseholdMember>>("POST", "/api/v1/member-invitations/accept", accessToken, undefined, { token }),
|
||||
updateMemberRole: (householdID: string, member: HouseholdMember, role: "admin" | "editor" | "viewer", accessToken: string) =>
|
||||
authenticatedJSON<Envelope<HouseholdMember>>("PATCH", `/api/v1/households/${householdID}/members/${member.id}`, accessToken, undefined,
|
||||
{ role }, { "If-Match": `"v${member.version}"` }),
|
||||
removeMember: (householdID: string, member: HouseholdMember, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<HouseholdMember>>("DELETE", `/api/v1/households/${householdID}/members/${member.id}`, accessToken, undefined,
|
||||
undefined, { "If-Match": `"v${member.version}"` }),
|
||||
transferOwnership: (household: Household, memberID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<Household>>("POST", `/api/v1/households/${household.id}/ownership`, accessToken, undefined,
|
||||
{ member_id: memberID }, { "If-Match": `"v${household.version}"` }),
|
||||
createSubject: (householdID: string, input: CreateSubjectRequest, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<Subject>>("POST", `/api/v1/households/${householdID}/subjects`, accessToken, undefined, input),
|
||||
updateSubject: (subject: Subject, input: UpdateSubjectRequest, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<Subject>>("PATCH", `/api/v1/subjects/${subject.id}`, accessToken, undefined, input,
|
||||
{ "If-Match": `"v${subject.version}"` }),
|
||||
createFact: (subjectID: string, input: CreateSubjectFactRequest, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<SubjectFact>>("POST", `/api/v1/subjects/${subjectID}/facts`, accessToken, undefined, input),
|
||||
confirmFact: (subjectID: string, fact: SubjectFact, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<SubjectFact>>("PATCH", `/api/v1/subjects/${subjectID}/facts/${fact.id}`, accessToken, undefined,
|
||||
{ confirmation_status: "confirmed" }, { "If-Match": `"v${fact.version}"` }),
|
||||
grantConsent: (subjectID: string, input: GrantConsentRequest, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<Consent>>("POST", `/api/v1/subjects/${subjectID}/consents`, accessToken, undefined, input),
|
||||
withdrawConsent: (subjectID: string, consentID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<Consent>>("POST", `/api/v1/subjects/${subjectID}/consents/${consentID}/withdraw`, accessToken),
|
||||
claimDevice: (input: DeviceClaimRequest, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<components["schemas"]["DeviceClaimResult"]>>("POST", "/api/v1/device-claims", accessToken, undefined, input),
|
||||
updateDeviceTwin: (deviceID: string, revision: number, input: UpdateDeviceTwinRequest, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<DeviceTwin>>("PATCH", `/api/v1/devices/${deviceID}/twin`, accessToken, undefined, input,
|
||||
{ "If-Match": `"v${revision}"` })
|
||||
,createDeviceCommand: (deviceID: string, type: "apply_config" | "reboot" | "diagnostics" | "firmware_update", payload: Record<string, unknown>, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<DeviceCommand>>("POST", `/api/v1/devices/${deviceID}/commands`, accessToken, undefined,
|
||||
{ type, payload, expires_in_sec: 300 }, { "Idempotency-Key": `device-command-${crypto.randomUUID()}` })
|
||||
,firmwareReleases: (householdID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: FirmwareRelease[] }>>("GET", `/api/v1/households/${householdID}/firmware-releases`, accessToken, signal)
|
||||
,firmwareReleaseImpact: (releaseID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<FirmwareReleaseImpact>>("GET", `/api/v1/firmware-releases/${releaseID}/impact`, accessToken, signal)
|
||||
,registerFirmwareSigningKey: (householdID: string, input: { key_id: string; public_key_base64: string }, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<{ key_id: string; status: string }>>("POST", `/api/v1/households/${householdID}/firmware-signing-keys`, accessToken, undefined, input)
|
||||
,firmwareSigningKeys: (householdID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: FirmwareSigningKey[] }>>("GET", `/api/v1/households/${householdID}/firmware-signing-keys`, accessToken, signal)
|
||||
,revokeFirmwareSigningKey: (householdID: string, keyID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<FirmwareSigningKey>>("POST", `/api/v1/households/${householdID}/firmware-signing-keys/revoke`, accessToken, undefined, { key_id: keyID })
|
||||
,prepareFirmwareRelease: (householdID: string, input: components["schemas"]["PrepareFirmwareReleaseRequest"], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<FirmwareRelease>>("POST", `/api/v1/households/${householdID}/firmware-releases`, accessToken, undefined, input)
|
||||
,finalizeFirmwareRelease: (releaseID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<FirmwareRelease>>("POST", `/api/v1/firmware-releases/${releaseID}/finalize`, accessToken)
|
||||
,renewFirmwareUpload: (releaseID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<FirmwareRelease>>("POST", `/api/v1/firmware-releases/${releaseID}/upload-session`, accessToken)
|
||||
,pauseFirmwareRelease: (releaseID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<FirmwareRelease>>("POST", `/api/v1/firmware-releases/${releaseID}/pause`, accessToken)
|
||||
,resumeFirmwareRelease: (releaseID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<FirmwareRelease>>("POST", `/api/v1/firmware-releases/${releaseID}/resume`, accessToken)
|
||||
,retireFirmwareRelease: (releaseID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<FirmwareRelease>>("POST", `/api/v1/firmware-releases/${releaseID}/retire`, accessToken)
|
||||
,firmwareRollouts: (householdID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<{ items: FirmwareRollout[] }>>("GET", `/api/v1/households/${householdID}/firmware-rollouts`, accessToken, signal)
|
||||
,firmwareRollout: (rolloutID: string, accessToken: string, signal?: AbortSignal) =>
|
||||
authenticatedJSON<Envelope<FirmwareRollout>>("GET", `/api/v1/firmware-rollouts/${rolloutID}`, accessToken, signal)
|
||||
,startFirmwareRollout: (householdID: string, input: components["schemas"]["StartFirmwareRolloutRequest"], accessToken: string) =>
|
||||
authenticatedJSON<Envelope<FirmwareRollout>>("POST", `/api/v1/households/${householdID}/firmware-rollouts`, accessToken, undefined, input)
|
||||
,pauseFirmwareRollout: (rolloutID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<FirmwareRollout>>("POST", `/api/v1/firmware-rollouts/${rolloutID}/pause`, accessToken)
|
||||
,rollbackFirmwareRollout: (rolloutID: string, rollbackReleaseID: string, accessToken: string) =>
|
||||
authenticatedJSON<Envelope<FirmwareRollout>>("POST", `/api/v1/firmware-rollouts/${rolloutID}/rollback`, accessToken, undefined,
|
||||
{ rollback_release_id: rollbackReleaseID })
|
||||
};
|
||||
|
||||
async function getJSONWithToken<T>(path: string, accessToken: string, signal?: AbortSignal): Promise<T> {
|
||||
return authenticatedJSON<T>("GET", path, accessToken, signal);
|
||||
}
|
||||
|
||||
async function authenticatedJSON<T>(
|
||||
method: string,
|
||||
path: string,
|
||||
accessToken: string,
|
||||
signal?: AbortSignal,
|
||||
body?: unknown,
|
||||
extraHeaders: Record<string, string> = {}
|
||||
): Promise<T> {
|
||||
const response = await fetchWithTimeout(path, {
|
||||
method,
|
||||
body: body === undefined ? undefined : JSON.stringify(body),
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
...(body === undefined ? {} : { "Content-Type": "application/json; charset=utf-8" }),
|
||||
...extraHeaders
|
||||
}
|
||||
}, signal);
|
||||
if (!response.ok) {
|
||||
let code: string | undefined;
|
||||
let message = response.status === 401 ? "登录状态已经失效,请重新登录" : `请求失败(HTTP ${response.status})`;
|
||||
try {
|
||||
const body = (await response.json()) as { error?: { code?: string; message?: string } };
|
||||
code = body.error?.code;
|
||||
if (body.error?.message) message = body.error.message;
|
||||
} catch {
|
||||
// Keep the stable HTTP fallback when a proxy returned a non-JSON response.
|
||||
}
|
||||
if (response.status === 401) window.dispatchEvent(new Event(AUTHENTICATION_REQUIRED_EVENT));
|
||||
throw new APIRequestError(message, response.status, code);
|
||||
}
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
|
||||
async function fetchWithTimeout(input: RequestInfo | URL, init: RequestInit, callerSignal?: AbortSignal): Promise<Response> {
|
||||
const controller = new AbortController();
|
||||
const onCallerAbort = () => controller.abort(callerSignal?.reason);
|
||||
if (callerSignal?.aborted) onCallerAbort();
|
||||
else callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
||||
const timeout = window.setTimeout(() => controller.abort(new DOMException("请求超时", "TimeoutError")), requestTimeoutMS);
|
||||
try {
|
||||
return await fetch(input, { ...init, signal: controller.signal });
|
||||
} catch (error) {
|
||||
if (!callerSignal?.aborted && controller.signal.aborted) {
|
||||
throw new APIRequestError("请求超时,请检查网络后重试", 0, "REQUEST_TIMEOUT");
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
window.clearTimeout(timeout);
|
||||
callerSignal?.removeEventListener("abort", onCallerAbort);
|
||||
}
|
||||
}
|
||||
Vendored
+11007
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user