1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 14:23:32 +00:00

[PM-9613] port forwarders to integrations (#10075)

* introduced forwarder integrations
* simply contexts
* report error and message when both are present in an RPC response
This commit is contained in:
✨ Audrey ✨
2024-07-30 08:40:52 -04:00
committed by GitHub
parent 8f437dc773
commit 8c78959aaf
70 changed files with 2392 additions and 2415 deletions

View File

@@ -10,59 +10,96 @@ export class RestClient {
private api: ApiService,
private i18n: I18nService,
) {}
/** uses the fetch API to request a JSON payload. */
async fetchJson<Parameters extends IntegrationRequest, Response>(
rpc: JsonRpc<Parameters, Response>,
// FIXME: once legacy password generator is removed, replace forwarder-specific error
// messages with RPC-generalized ones.
async fetchJson<Parameters extends IntegrationRequest, Result>(
rpc: JsonRpc<Parameters, Result>,
params: Parameters,
): Promise<Response> {
): Promise<Result> {
// run the request
const request = rpc.toRequest(params);
const response = await this.api.nativeFetch(request);
// FIXME: once legacy password generator is removed, replace forwarder-specific error
// messages with RPC-generalized ones.
let error: string = undefined;
let cause: string = undefined;
let result: Result = undefined;
let errorKey: string = undefined;
let errorMessage: string = undefined;
const commonError = await this.detectCommonErrors(response);
if (commonError) {
[errorKey, errorMessage] = commonError;
} else if (rpc.hasJsonPayload(response)) {
[result, errorMessage] = rpc.processJson(await response.json());
}
if (result) {
return result;
}
// handle failures
errorKey ??= errorMessage ? "forwarderError" : "forwarderUnknownError";
const error = this.i18n.t(errorKey, rpc.requestor.name, errorMessage);
throw error;
}
private async detectCommonErrors(response: Response): Promise<[string, string] | undefined> {
if (response.status === 401 || response.status === 403) {
cause = await this.tryGetErrorMessage(response);
error = cause ? "forwarderInvalidTokenWithMessage" : "forwarderInvalidToken";
} else if (response.status >= 500) {
cause = await this.tryGetErrorMessage(response);
cause = cause ?? response.statusText;
error = "forwarderError";
const message = await this.tryGetErrorMessage(response);
const key = message ? "forwaderInvalidTokenWithMessage" : "forwaderInvalidToken";
return [key, message];
} else if (response.status === 429 || response.status >= 500) {
const message = await this.tryGetErrorMessage(response);
const key = message ? "forwarderError" : "forwarderUnknownError";
return [key, message];
}
let ok: Response = undefined;
if (!error && rpc.hasJsonPayload(response)) {
[ok, cause] = rpc.processJson(await response.json());
}
// success
if (ok) {
return ok;
}
// failure
if (!error) {
error = cause ? "forwarderError" : "forwarderUnknownError";
}
throw this.i18n.t(error, rpc.requestor.name, cause);
}
private async tryGetErrorMessage(response: Response) {
const body = (await response.text()) ?? "";
if (!body.startsWith("{")) {
// nullish continues processing; false returns undefined
const error =
this.tryFindErrorAsJson(body) ?? this.tryFindErrorAsText(body) ?? response.statusText;
return error || undefined;
}
private tryFindErrorAsJson(body: string) {
// tryParse JSON object or string
const parsable = body.startsWith("{") || body.startsWith(`'`) || body.startsWith(`"`);
if (!parsable) {
// fail-and-continue because it's not JSON
return undefined;
}
let parsed = undefined;
try {
parsed = JSON.parse(body);
} catch {
// fail-and-exit in case `body` is malformed JSON
return false;
}
// could be a string
if (parsed && typeof parsed === "string") {
return parsed;
}
// could be { error?: T, message?: U }
const error = parsed.error?.toString() ?? null;
const message = parsed.message?.toString() ?? null;
// `false` signals no message found
const result = error && message ? `${error}: ${message}` : (error ?? message ?? false);
return result;
}
private tryFindErrorAsText(body: string) {
if (!body.length || body.includes("<")) {
return undefined;
}
const json = JSON.parse(body);
if ("error" in json) {
return json.error;
} else if ("message" in json) {
return json.message;
}
return undefined;
return body;
}
}