Skip to content

Commit 6a3bb5b

Browse files
authored
feat: handle some cloud server error codes (#955)
This turns the server's `TOO_MANY_PROJECTS` error into the `SERVER_HAS_TOO_MANY_PROJECTS` error, and the `PROJECT_NOT_IN_ALLOWLIST` error into `PROJECT_NOT_IN_SERVER_ALLOWLIST` error. See <digidem/comapeo-cloud#33>, which adds these codes.
1 parent 5efcdfc commit 6a3bb5b

File tree

2 files changed

+110
-26
lines changed

2 files changed

+110
-26
lines changed

src/member-api.js

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,10 @@ export class MemberApi extends TypedEmitter {
277277
* peer. For example, the project must have a name.
278278
* - `NETWORK_ERROR`: there was an issue connecting to the server. Is the
279279
* device online? Is the server online?
280+
* - `SERVER_HAS_TOO_MANY_PROJECTS`: the server limits the number of projects
281+
* it can have, and it's at the limit.
282+
* - `PROJECT_NOT_IN_SERVER_ALLOWLIST`: the server only allows specific
283+
* projects to be added and ours wasn't one of them.
280284
* - `INVALID_SERVER_RESPONSE`: we connected to the server but it returned
281285
* an unexpected response. Is the server running a compatible version of
282286
* CoMapeo Cloud?
@@ -351,32 +355,7 @@ export class MemberApi extends TypedEmitter {
351355
)
352356
}
353357

354-
if (response.status !== 200 && response.status !== 201) {
355-
throw new ErrorWithCode(
356-
'INVALID_SERVER_RESPONSE',
357-
`Failed to add server peer due to HTTP status code ${response.status}`
358-
)
359-
}
360-
361-
try {
362-
const responseBody = await response.json()
363-
assert(
364-
responseBody &&
365-
typeof responseBody === 'object' &&
366-
'data' in responseBody &&
367-
responseBody.data &&
368-
typeof responseBody.data === 'object' &&
369-
'deviceId' in responseBody.data &&
370-
typeof responseBody.data.deviceId === 'string',
371-
'Response body is valid'
372-
)
373-
return { serverDeviceId: responseBody.data.deviceId }
374-
} catch (err) {
375-
throw new ErrorWithCode(
376-
'INVALID_SERVER_RESPONSE',
377-
"Failed to add server peer because we couldn't parse the response"
378-
)
379-
}
358+
return await parseAddServerResponse(response)
380359
}
381360

382361
/**
@@ -575,3 +554,66 @@ function isValidServerBaseUrl(
575554
function encodeBufferForServer(buffer) {
576555
return buffer ? b4a.toString(buffer, 'hex') : undefined
577556
}
557+
558+
/**
559+
* @param {Response} response
560+
* @returns {Promise<{ serverDeviceId: string }>}
561+
*/
562+
async function parseAddServerResponse(response) {
563+
if (response.status === 200) {
564+
try {
565+
const responseBody = await response.json()
566+
assert(
567+
responseBody &&
568+
typeof responseBody === 'object' &&
569+
'data' in responseBody &&
570+
responseBody.data &&
571+
typeof responseBody.data === 'object' &&
572+
'deviceId' in responseBody.data &&
573+
typeof responseBody.data.deviceId === 'string',
574+
'Response body is valid'
575+
)
576+
return { serverDeviceId: responseBody.data.deviceId }
577+
} catch (err) {
578+
throw new ErrorWithCode(
579+
'INVALID_SERVER_RESPONSE',
580+
"Failed to add server peer because we couldn't parse the response"
581+
)
582+
}
583+
}
584+
585+
let responseBody
586+
try {
587+
responseBody = await response.json()
588+
} catch (_) {
589+
responseBody = null
590+
}
591+
if (
592+
responseBody &&
593+
typeof responseBody === 'object' &&
594+
'error' in responseBody &&
595+
responseBody.error &&
596+
typeof responseBody.error === 'object' &&
597+
'code' in responseBody.error
598+
) {
599+
switch (responseBody.error.code) {
600+
case 'PROJECT_NOT_IN_ALLOWLIST':
601+
throw new ErrorWithCode(
602+
'PROJECT_NOT_IN_SERVER_ALLOWLIST',
603+
"The server only allows specific projects to be added, and this isn't one of them"
604+
)
605+
case 'TOO_MANY_PROJECTS':
606+
throw new ErrorWithCode(
607+
'SERVER_HAS_TOO_MANY_PROJECTS',
608+
"The server limits the number of projects it can have and it's at the limit"
609+
)
610+
default:
611+
break
612+
}
613+
}
614+
615+
throw new ErrorWithCode(
616+
'INVALID_SERVER_RESPONSE',
617+
`Failed to add server peer due to HTTP status code ${response.status}`
618+
)
619+
}

test-e2e/server.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { setTimeout as delay } from 'node:timers/promises'
99
import pDefer from 'p-defer'
1010
import { pEvent } from 'p-event'
1111
import RAM from 'random-access-memory'
12+
import { map } from 'iterpal'
1213
import { MEMBER_ROLE_ID } from '../src/roles.js'
1314
import comapeoServer from '@comapeo/cloud'
1415
import {
@@ -112,6 +113,46 @@ test("fails if we can't connect to the server", async (t) => {
112113
)
113114
})
114115

116+
test(
117+
"translates some of the server's error codes when adding one",
118+
{ concurrency: true },
119+
async (t) => {
120+
const manager = createManager('device0', t)
121+
const projectId = await manager.createProject({ name: 'foo' })
122+
const project = await manager.getProject(projectId)
123+
124+
const serverErrorToLocalError = new Map([
125+
['PROJECT_NOT_IN_ALLOWLIST', 'PROJECT_NOT_IN_SERVER_ALLOWLIST'],
126+
['TOO_MANY_PROJECTS', 'SERVER_HAS_TOO_MANY_PROJECTS'],
127+
['__TEST_UNRECOGNIZED_ERROR', 'INVALID_SERVER_RESPONSE'],
128+
])
129+
await Promise.all(
130+
map(serverErrorToLocalError, ([serverError, expectedCode]) =>
131+
t.test(`turns a ${serverError} into ${expectedCode}`, async (t) => {
132+
const fastify = createFastify()
133+
fastify.put('/projects', (_req, reply) => {
134+
reply.status(403).send({
135+
error: { code: serverError },
136+
})
137+
})
138+
const serverBaseUrl = await fastify.listen()
139+
t.after(() => fastify.close())
140+
141+
await assert.rejects(
142+
() =>
143+
project.$member.addServerPeer(serverBaseUrl, {
144+
dangerouslyAllowInsecureConnections: true,
145+
}),
146+
{
147+
code: expectedCode,
148+
}
149+
)
150+
})
151+
)
152+
)
153+
}
154+
)
155+
115156
test(
116157
"fails if server doesn't return a 200",
117158
{ concurrency: true },
@@ -160,6 +201,7 @@ test(
160201
'{bad_json',
161202
JSON.stringify({ data: {} }),
162203
JSON.stringify({ data: { deviceId: 123 } }),
204+
JSON.stringify({ error: { deviceId: '123' } }),
163205
JSON.stringify({ deviceId: 'not under "data"' }),
164206
].map((responseData) =>
165207
t.test(`when returning ${responseData}`, async (t) => {

0 commit comments

Comments
 (0)