mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
[PM-5840] add duck duck go forwarder (#7674)
This commit is contained in:
@@ -0,0 +1,120 @@
|
|||||||
|
/**
|
||||||
|
* include Request in test environment.
|
||||||
|
* @jest-environment ../../../../shared/test.environment.ts
|
||||||
|
*/
|
||||||
|
import { Forwarders } from "../options/constants";
|
||||||
|
|
||||||
|
import { DuckDuckGoForwarder } from "./duck-duck-go";
|
||||||
|
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||||
|
|
||||||
|
describe("DuckDuckGo Forwarder", () => {
|
||||||
|
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
|
||||||
|
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
||||||
|
const apiService = mockApiService(200, {});
|
||||||
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
|
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
async () =>
|
||||||
|
await forwarder.generate(null, {
|
||||||
|
token,
|
||||||
|
}),
|
||||||
|
).rejects.toEqual("forwaderInvalidToken");
|
||||||
|
|
||||||
|
expect(apiService.nativeFetch).not.toHaveBeenCalled();
|
||||||
|
expect(i18nService.t).toHaveBeenCalledWith(
|
||||||
|
"forwaderInvalidToken",
|
||||||
|
Forwarders.DuckDuckGo.name,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["jane.doe@duck.com", 201, "jane.doe"],
|
||||||
|
["john.doe@duck.com", 201, "john.doe"],
|
||||||
|
["jane.doe@duck.com", 200, "jane.doe"],
|
||||||
|
["john.doe@duck.com", 200, "john.doe"],
|
||||||
|
])(
|
||||||
|
"returns the generated email address (= %p) if the request is successful (status = %p)",
|
||||||
|
async (email, status, address) => {
|
||||||
|
const apiService = mockApiService(status, { address });
|
||||||
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
|
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
|
||||||
|
|
||||||
|
const result = await forwarder.generate(null, {
|
||||||
|
token: "token",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual(email);
|
||||||
|
expect(apiService.nativeFetch).toHaveBeenCalledWith(expect.any(Request));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it("throws an invalid token error if the request fails with a 401", async () => {
|
||||||
|
const apiService = mockApiService(401, {});
|
||||||
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
|
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
async () =>
|
||||||
|
await forwarder.generate(null, {
|
||||||
|
token: "token",
|
||||||
|
}),
|
||||||
|
).rejects.toEqual("forwaderInvalidToken");
|
||||||
|
|
||||||
|
expect(apiService.nativeFetch).toHaveBeenCalledWith(expect.any(Request));
|
||||||
|
// counting instances is terribly flaky over changes, but jest doesn't have a better way to do this
|
||||||
|
expect(i18nService.t).toHaveBeenCalledWith(
|
||||||
|
"forwaderInvalidToken",
|
||||||
|
Forwarders.DuckDuckGo.name,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws an unknown error if the request is successful but an address isn't present", async () => {
|
||||||
|
const apiService = mockApiService(200, {});
|
||||||
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
|
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
async () =>
|
||||||
|
await forwarder.generate(null, {
|
||||||
|
token: "token",
|
||||||
|
}),
|
||||||
|
).rejects.toEqual("forwarderUnknownError");
|
||||||
|
|
||||||
|
expect(apiService.nativeFetch).toHaveBeenCalledWith(expect.any(Request));
|
||||||
|
// counting instances is terribly flaky over changes, but jest doesn't have a better way to do this
|
||||||
|
expect(i18nService.t).toHaveBeenCalledWith(
|
||||||
|
"forwarderUnknownError",
|
||||||
|
Forwarders.DuckDuckGo.name,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([100, 202, 300, 418, 500, 600])(
|
||||||
|
"throws an unknown error if the request returns any other status code (= %i)",
|
||||||
|
async (statusCode) => {
|
||||||
|
const apiService = mockApiService(statusCode, {});
|
||||||
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
|
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
async () =>
|
||||||
|
await forwarder.generate(null, {
|
||||||
|
token: "token",
|
||||||
|
}),
|
||||||
|
).rejects.toEqual("forwarderUnknownError");
|
||||||
|
|
||||||
|
expect(apiService.nativeFetch).toHaveBeenCalledWith(expect.any(Request));
|
||||||
|
// counting instances is terribly flaky over changes, but jest doesn't have a better way to do this
|
||||||
|
expect(i18nService.t).toHaveBeenCalledWith(
|
||||||
|
"forwarderUnknownError",
|
||||||
|
Forwarders.DuckDuckGo.name,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { ApiService } from "../../../../abstractions/api.service";
|
||||||
|
import { I18nService } from "../../../../platform/abstractions/i18n.service";
|
||||||
|
import { Forwarders } from "../options/constants";
|
||||||
|
import { ApiOptions, Forwarder } from "../options/forwarder-options";
|
||||||
|
|
||||||
|
/** Generates a forwarding address for DuckDuckGo */
|
||||||
|
export class DuckDuckGoForwarder implements Forwarder {
|
||||||
|
/** Instantiates the forwarder
|
||||||
|
* @param apiService used for ajax requests to the forwarding service
|
||||||
|
* @param i18nService used to look up error strings
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private apiService: ApiService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/** {@link Forwarder.generate} */
|
||||||
|
async generate(_website: string | null, options: ApiOptions): Promise<string> {
|
||||||
|
if (!options.token || options.token === "") {
|
||||||
|
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.DuckDuckGo.name);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = "https://quack.duckduckgo.com/api/email/addresses";
|
||||||
|
const request = new Request(url, {
|
||||||
|
redirect: "manual",
|
||||||
|
cache: "no-store",
|
||||||
|
method: "POST",
|
||||||
|
headers: new Headers({
|
||||||
|
Authorization: "Bearer " + options.token,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.apiService.nativeFetch(request);
|
||||||
|
if (response.status === 200 || response.status === 201) {
|
||||||
|
const json = await response.json();
|
||||||
|
if (json.address) {
|
||||||
|
return `${json.address}@duck.com`;
|
||||||
|
} else {
|
||||||
|
const error = this.i18nService.t("forwarderUnknownError", Forwarders.DuckDuckGo.name);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} else if (response.status === 401) {
|
||||||
|
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.DuckDuckGo.name);
|
||||||
|
throw error;
|
||||||
|
} else {
|
||||||
|
const error = this.i18nService.t("forwarderUnknownError", Forwarders.DuckDuckGo.name);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user