Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/keyring-eth-mpc/src/cloud.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,14 @@ describe('cloud helpers', () => {
encryptedKeyShare,
backupId: 'backup-1',
});

expect(fetchSpy).toHaveBeenCalledWith(
'https://cloud.example/load-key-share-backup',
expect.objectContaining({
method: 'GET',
headers: { Authorization: 'Bearer token-1' },
}),
);
});

it('throws when loading a key share backup fails', async () => {
Expand Down
64 changes: 49 additions & 15 deletions packages/keyring-eth-mpc/src/cloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,64 @@ export type LoadKeyShareBackupResult = {
};

/**
* Fetch JSON from the MPC backend, throwing on non-OK responses.
* Parse a fetch response as JSON, throwing on non-OK status.
*
* @param response - The fetch response.
* @param errorPrefix - Prefix for the thrown error message.
* @returns The parsed JSON body, or `undefined` when the response is empty.
*/
async function parseJsonResponse<Result>(
response: Response,
errorPrefix: string,
): Promise<Result> {
if (!response.ok) {
throw new Error(`${errorPrefix}: ${response.statusText}`);
}

const text = await response.text();
if (text.length === 0) {
return undefined as Result;
}
return JSON.parse(text) as Result;
}

/**
* GET JSON from the MPC backend, throwing on non-OK responses.
*
* @param url - The request URL.
* @param token - Profile token sent as a Bearer header.
* @param errorPrefix - Prefix for the thrown error message.
* @returns The parsed JSON body, or `undefined` when the response is empty.
*/
async function getJson<Result>(
url: string,
token: string,
errorPrefix: string,
): Promise<Result> {
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
});
return parseJsonResponse(response, errorPrefix);
}

/**
* POST JSON to the MPC backend, throwing on non-OK responses.
*
* @param url - The request URL.
* @param token - Profile token sent as a Bearer header.
* @param body - The JSON request body.
* @param errorPrefix - Prefix for the thrown error message.
* @returns The parsed JSON body, or `undefined` when the response is empty.
*/
async function postJson<Response>(
async function postJson<Result>(
url: string,
token: string,
body: Record<string, unknown>,
errorPrefix: string,
): Promise<Response> {
): Promise<Result> {
const response = await fetch(url, {
method: 'POST',
headers: {
Expand All @@ -29,16 +73,7 @@ async function postJson<Response>(
},
body: JSON.stringify(body),
});

if (!response.ok) {
throw new Error(`${errorPrefix}: ${response.statusText}`);
}

const text = await response.text();
if (text.length === 0) {
return undefined as Response;
}
return JSON.parse(text) as Response;
return parseJsonResponse(response, errorPrefix);
}

/**
Expand Down Expand Up @@ -225,13 +260,12 @@ export async function loadKeyShareBackup(opts: {
baseURL: string;
token: string;
}): Promise<LoadKeyShareBackupResult> {
const data = await postJson<{
const data = await getJson<{
encryptedKeyShare: string;
backupId: string;
}>(
`${opts.baseURL}/load-key-share-backup`,
opts.token,
{},
'Failed to load key share backup',
);
return {
Expand Down