apiClient

This commit is contained in:
2025-11-22 15:30:08 +01:00
parent 530218a4b8
commit d3c76bc303
11 changed files with 319 additions and 169 deletions
+17 -25
View File
@@ -1,14 +1,11 @@
import { useCallback, useState } from 'react';
interface ApiTokenRecord {
id?: string | number;
label?: string;
expires_at?: string;
created_at?: string;
last_used_at?: string;
capability_set_id?: string | number;
[key: string]: unknown;
}
import {
createApiToken,
deleteApiToken,
listApiTokens,
regenerateApiToken,
type ApiTokenRecord,
} from '../lib/apiClient';
interface ApiTokensResponse {
token_info?: ApiTokenRecord;
@@ -22,11 +19,6 @@ interface CreateTokenArgs {
}
interface UseApiTokensArgs {
api: {
get: <T = unknown>(url: string) => Promise<{ data: T }>;
post: <T = unknown>(url: string, payload?: unknown) => Promise<{ data: T }>;
delete: (url: string) => Promise<unknown>;
};
notifyApiError?: (error: unknown, message: string) => void;
setStatusMessage?: (message: string, variant?: string) => void;
token?: string | null;
@@ -46,7 +38,7 @@ interface UseApiTokensResult {
dismissSecret: () => void;
}
const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }: UseApiTokensArgs): UseApiTokensResult => {
const useApiTokens = ({ notifyApiError, setStatusMessage, token }: UseApiTokensArgs): UseApiTokensResult => {
const [tokens, setTokens] = useState<ApiTokenRecord[]>([]);
const [loading, setLoading] = useState(false);
const [creating, setCreating] = useState(false);
@@ -60,14 +52,14 @@ const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }: UseApiTo
}
setLoading(true);
try {
const { data } = await api.get<ApiTokenRecord[]>('/profile/api-tokens');
const data = await listApiTokens();
setTokens(Array.isArray(data) ? data : []);
} catch (error) {
notifyApiError?.(error, 'Failed to load API tokens.');
} finally {
setLoading(false);
}
}, [api, notifyApiError, token]);
}, [notifyApiError, token]);
const create = useCallback(
async ({ label, expires_at, capability_set_id }: CreateTokenArgs = {}) => {
@@ -76,7 +68,7 @@ const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }: UseApiTo
}
setCreating(true);
try {
const payload: Record<string, unknown> = { capability_set_id };
const payload: { capability_set_id: string | number; label?: string; expires_at?: string } = { capability_set_id };
if (label) {
payload.label = label;
}
@@ -84,7 +76,7 @@ const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }: UseApiTo
payload.expires_at = expires_at;
}
const { data } = await api.post<ApiTokensResponse>('/profile/api-tokens', payload);
const data = await createApiToken(payload) as ApiTokensResponse;
if (data?.token_info) {
setTokens((previous) => {
const filtered = previous.filter((entry) => entry.id !== data.token_info?.id);
@@ -107,7 +99,7 @@ const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }: UseApiTo
setCreating(false);
}
},
[api, creating, notifyApiError, refresh, setStatusMessage],
[creating, notifyApiError, refresh, setStatusMessage],
);
const revoke = useCallback(
@@ -117,7 +109,7 @@ const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }: UseApiTo
}
setDeletingId(tokenId);
try {
await api.delete(`/profile/api-tokens/${tokenId}`);
await deleteApiToken(tokenId);
await refresh();
setStatusMessage?.('API token revoked.', 'success');
return true;
@@ -128,7 +120,7 @@ const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }: UseApiTo
setDeletingId(null);
}
},
[api, notifyApiError, refresh, setStatusMessage],
[notifyApiError, refresh, setStatusMessage],
);
const regenerate = useCallback(
@@ -138,7 +130,7 @@ const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }: UseApiTo
}
setRegeneratingId(tokenId);
try {
const { data } = await api.post<ApiTokensResponse>(`/profile/api-tokens/${tokenId}/regenerate`);
const data = await regenerateApiToken(tokenId) as ApiTokensResponse;
if (data?.token_info) {
setTokens((previous) => {
let found = false;
@@ -171,7 +163,7 @@ const useApiTokens = ({ api, notifyApiError, setStatusMessage, token }: UseApiTo
setRegeneratingId(null);
}
},
[api, notifyApiError, refresh, setStatusMessage],
[notifyApiError, refresh, setStatusMessage],
);
const dismissSecret = useCallback(() => {
+11 -16
View File
@@ -1,5 +1,10 @@
import { useCallback, useEffect, useState } from 'react';
import { listCapabilitySets } from '../lib/apiClient';
import {
createCapabilitySet as createCapabilitySetRequest,
deleteCapabilitySet as deleteCapabilitySetRequest,
listCapabilitySets,
updateCapabilitySet as updateCapabilitySetRequest,
} from '../lib/apiClient';
type Identifier = string | number;
@@ -11,21 +16,13 @@ interface CapabilitySet {
[key: string]: unknown;
}
interface ApiClient {
get<T = CapabilitySet[]>(path: string): Promise<{ data: T }>;
post<T = CapabilitySet>(path: string, payload?: unknown): Promise<{ data: T }>;
patch<T = CapabilitySet>(path: string, payload?: unknown): Promise<{ data: T }>;
delete: (path: string) => Promise<void>;
}
interface UseCapabilitySetsOptions {
api: ApiClient;
notifyApiError?: (error: unknown, message: string) => void;
setStatusMessage?: (message: string, level?: string) => void;
token?: string | null;
}
const useCapabilitySets = ({ api, notifyApiError, setStatusMessage, token }: UseCapabilitySetsOptions) => {
const useCapabilitySets = ({ notifyApiError, setStatusMessage, token }: UseCapabilitySetsOptions) => {
const [capabilitySets, setCapabilitySets] = useState<CapabilitySet[]>([]);
const [capabilitySetsLoading, setCapabilitySetsLoading] = useState(false);
const [creatingCapabilitySet, setCreatingCapabilitySet] = useState(false);
@@ -92,7 +89,7 @@ const useCapabilitySets = ({ api, notifyApiError, setStatusMessage, token }: Use
payload.label = trimmedLabel;
}
const { data } = await api.post<CapabilitySet>('/capability-sets', payload);
const data = await createCapabilitySetRequest(payload);
if (data) {
applyCapabilitySets((previous) => {
const next = previous.filter((entry) => entry?.id !== data.id);
@@ -113,7 +110,6 @@ const useCapabilitySets = ({ api, notifyApiError, setStatusMessage, token }: Use
}
},
[
api,
applyCapabilitySets,
creatingCapabilitySet,
notifyApiError,
@@ -154,7 +150,7 @@ const useCapabilitySets = ({ api, notifyApiError, setStatusMessage, token }: Use
payload.capabilities = capabilities;
}
const { data } = await api.patch<CapabilitySet>(`/capability-sets/${capabilitySetId}`, payload);
const data = await updateCapabilitySetRequest(capabilitySetId, payload);
if (data) {
applyCapabilitySets((previous) => {
let found = false;
@@ -184,7 +180,6 @@ const useCapabilitySets = ({ api, notifyApiError, setStatusMessage, token }: Use
}
},
[
api,
applyCapabilitySets,
notifyApiError,
refreshCapabilitySets,
@@ -200,7 +195,7 @@ const useCapabilitySets = ({ api, notifyApiError, setStatusMessage, token }: Use
}
setDeletingCapabilitySetId(capabilitySetId);
try {
await api.delete(`/capability-sets/${capabilitySetId}`);
await deleteCapabilitySetRequest(capabilitySetId);
applyCapabilitySets((previous) => previous.filter((entry) => entry?.id !== capabilitySetId));
setStatusMessage?.('Capability set deleted.', 'success');
return true;
@@ -211,7 +206,7 @@ const useCapabilitySets = ({ api, notifyApiError, setStatusMessage, token }: Use
setDeletingCapabilitySetId(null);
}
},
[api, applyCapabilitySets, notifyApiError, setStatusMessage],
[applyCapabilitySets, notifyApiError, setStatusMessage],
);
return {
+14 -17
View File
@@ -6,12 +6,12 @@ import {
preparePublicKeyCreationOptions,
serializeRegistrationCredential,
} from '../utils/webauthn';
type ApiClient = {
get: (path: string) => Promise<{ data: unknown }>;
post: (path: string, body?: unknown) => Promise<{ data: unknown }>;
delete: (path: string) => Promise<{ data: unknown }>;
};
import {
deletePasskey,
finishPasskeyRegistration,
listPasskeys,
startPasskeyRegistration,
} from '../lib/apiClient';
type StatusMessageFn = (message: string, variant?: string) => void;
type NotifyApiErrorFn = (error: unknown, message: string) => void;
@@ -69,7 +69,6 @@ export type RevokePasskeyResult =
| { ok: false; reason: RevokePasskeyFailureReason; message?: string };
interface UsePasskeysArgs {
api: ApiClient;
notifyApiError: NotifyApiErrorFn;
setStatusMessage: StatusMessageFn;
token?: string | null;
@@ -89,7 +88,7 @@ interface UsePasskeysResult {
) => Promise<RevokePasskeyResult>;
}
const usePasskeys = ({ api, notifyApiError, setStatusMessage, token }: UsePasskeysArgs): UsePasskeysResult => {
const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArgs): UsePasskeysResult => {
const [passkeys, setPasskeys] = useState<PasskeyRecord[]>([]);
const [passkeysSupported, setPasskeysSupported] = useState<boolean | null>(null);
const [passkeysLoading, setPasskeysLoading] = useState(false);
@@ -102,8 +101,7 @@ const usePasskeys = ({ api, notifyApiError, setStatusMessage, token }: UsePasske
}
setPasskeysLoading(true);
try {
const { data } = await api.get('/profile/passkeys');
const passkeyData = Array.isArray(data) ? (data as PasskeyRecord[]) : [];
const passkeyData = await listPasskeys();
setPasskeys(passkeyData);
setPasskeysSupported(true);
} catch (error) {
@@ -117,7 +115,7 @@ const usePasskeys = ({ api, notifyApiError, setStatusMessage, token }: UsePasske
} finally {
setPasskeysLoading(false);
}
}, [api, notifyApiError, token]);
}, [notifyApiError, token]);
const registerPasskey = useCallback(
async ({ nickname }: { nickname?: string } = {}): Promise<RegisterPasskeyResult> => {
@@ -132,7 +130,7 @@ const usePasskeys = ({ api, notifyApiError, setStatusMessage, token }: UsePasske
setRegisteringPasskey(true);
try {
const { data } = await api.post('/auth/passkeys/register/start', {});
const data = await startPasskeyRegistration();
const challengeData = data as PasskeyChallengeResponse | undefined;
const challengeId = challengeData?.challengeId || challengeData?.challenge_id;
const publicKeyOptions =
@@ -164,7 +162,7 @@ const usePasskeys = ({ api, notifyApiError, setStatusMessage, token }: UsePasske
payload.nickname = trimmedNickname;
}
await api.post('/auth/passkeys/register/finish', payload);
await finishPasskeyRegistration(payload);
await refreshPasskeys();
setPasskeysSupported(true);
setStatusMessage('Passkey registered.', 'success');
@@ -188,7 +186,7 @@ const usePasskeys = ({ api, notifyApiError, setStatusMessage, token }: UsePasske
setRegisteringPasskey(false);
}
},
[api, notifyApiError, refreshPasskeys, registeringPasskey, setStatusMessage],
[notifyApiError, refreshPasskeys, registeringPasskey, setStatusMessage],
);
const revokePasskey = useCallback(
@@ -201,8 +199,7 @@ const usePasskeys = ({ api, notifyApiError, setStatusMessage, token }: UsePasske
}
setRevokingPasskeyId(passkeyId);
try {
const query = reason ? `?reason=${encodeURIComponent(reason)}` : '';
await api.delete(`/profile/passkeys/${passkeyId}${query}`);
await deletePasskey(passkeyId, { reason });
await refreshPasskeys();
setStatusMessage('Passkey revoked.', 'success');
return { ok: true };
@@ -214,7 +211,7 @@ const usePasskeys = ({ api, notifyApiError, setStatusMessage, token }: UsePasske
setRevokingPasskeyId(null);
}
},
[api, notifyApiError, refreshPasskeys, setStatusMessage],
[notifyApiError, refreshPasskeys, setStatusMessage],
);
return {