| #!/usr/bin/env node | ||
| /* | ||
| * Guards package-lock.json against internal/private registry URLs. | ||
| * | ||
| * This is a public package, so every resolved dependency must come from the public | ||
| * npm registry. Running `npm install` on a machine configured with a corporate mirror | ||
| * (e.g. a Nexus proxy) can rewrite "resolved" URLs to that internal host, which then | ||
| * breaks CI and leaks internal infrastructure into a public lockfile. This check fails | ||
| * the commit if any such URL is present. | ||
| */ | ||
| "use strict"; | ||
| const fs = require("fs"); | ||
| const path = require("path"); | ||
| const ALLOWED_HOST = "registry.npmjs.org"; | ||
| const lockfilePath = path.join(__dirname, "..", "package-lock.json"); | ||
| function collectResolvedUrls(node, urls) { | ||
| if (Array.isArray(node)) { | ||
| node.forEach((child) => collectResolvedUrls(child, urls)); | ||
| } else if (node !== null && typeof node === "object") { | ||
| for (const [key, value] of Object.entries(node)) { | ||
| if (key === "resolved" && typeof value === "string") { | ||
| urls.push(value); | ||
| } else { | ||
| collectResolvedUrls(value, urls); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| const lockfile = JSON.parse(fs.readFileSync(lockfilePath, "utf8")); | ||
| const resolvedUrls = []; | ||
| collectResolvedUrls(lockfile, resolvedUrls); | ||
| const offenders = resolvedUrls.filter((url) => { | ||
| // This check is intentionally HTTP(S)-scoped: it targets registry tarball URLs, which is where an | ||
| // internal mirror host would appear. Non-HTTP resolved entries (e.g. git+ssh:, git+https:, file:, | ||
| // link:) are skipped, since they are not registry mirror URLs. | ||
| if (!/^https?:\/\//.test(url)) { return false; } | ||
| try { | ||
| return new URL(url).host !== ALLOWED_HOST; | ||
| } catch (error) { | ||
| return true; // unparseable URL is treated as an offender | ||
| } | ||
| }); | ||
| if (offenders.length > 0) { | ||
| const unique = Array.from(new Set(offenders)); | ||
| console.error(`ERROR: package-lock.json references non-public registry hosts (expected ${ALLOWED_HOST}):`); | ||
| unique.forEach((url) => console.error(` ${url}`)); | ||
| console.error("\nRegenerate the lockfile against the public registry, for example:"); | ||
| console.error(" npm install --registry=https://registry.npmjs.org"); | ||
| process.exit(1); | ||
| } | ||
| console.log(`package-lock.json OK: all resolved dependencies use ${ALLOWED_HOST}.`); |
| import {expect} from "chai"; | ||
| import "mocha"; | ||
| import * as sinon from "sinon"; | ||
| import {FetchHttpClient} from "../../src/client/HttpClient"; | ||
| import {ClientOptions} from "../../src/client/models"; | ||
| import {Errors} from "../../src"; | ||
| describe("FetchHttpClient", () => { | ||
| let sandbox: sinon.SinonSandbox; | ||
| const httpClient = new FetchHttpClient(); | ||
| // Minimal Fetch Response stand-in - only the members the client relies on. | ||
| const buildResponse = (status: number, body?: any): any => ({ | ||
| status, | ||
| text: async () => (body === undefined ? "" : JSON.stringify(body)), | ||
| }); | ||
| const stubFetch = () => sandbox.stub(httpClient, "client"); | ||
| beforeEach(() => { | ||
| sandbox = sinon.createSandbox(); | ||
| }); | ||
| afterEach(() => { | ||
| sandbox.restore(); | ||
| }); | ||
| describe("successful requests", () => { | ||
| it("returns parsed JSON body for 2xx responses", () => { | ||
| stubFetch().resolves(buildResponse(200, {To: "receiver@example.com"})); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "/server", {}, null, {}).then((result: any) => { | ||
| expect(result).to.eql({To: "receiver@example.com"}); | ||
| }); | ||
| }); | ||
| it("returns empty object when response body is empty", () => { | ||
| stubFetch().resolves(buildResponse(200)); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "/server", {}, null, {}).then((result: any) => { | ||
| expect(result).to.eql({}); | ||
| }); | ||
| }); | ||
| }); | ||
| describe("request composition", () => { | ||
| it("builds the URL from host, path and query parameters, skipping null/undefined", async () => { | ||
| const stub = stubFetch().resolves(buildResponse(200, {})); | ||
| await httpClient.httpRequest(ClientOptions.HttpMethod.GET, "/bounces", | ||
| {count: 10, offset: 0, tag: undefined, name: null, subject: "hi there"}, null, {}); | ||
| expect(stub.firstCall.args[0]).to.equal("https://api.postmarkapp.com/bounces?count=10&offset=0&subject=hi+there"); | ||
| }); | ||
| it("prefixes the path with a slash when missing", async () => { | ||
| const stub = stubFetch().resolves(buildResponse(200, {})); | ||
| await httpClient.httpRequest(ClientOptions.HttpMethod.GET, "server", {}, null, {}); | ||
| expect(stub.firstCall.args[0]).to.equal("https://api.postmarkapp.com/server"); | ||
| }); | ||
| it("serializes the body, forwards the method and headers, and sets an abort signal", async () => { | ||
| const stub = stubFetch().resolves(buildResponse(200, {})); | ||
| const headers = {"X-Postmark-Server-Token": "abc", "Content-Type": "application/json"}; | ||
| await httpClient.httpRequest(ClientOptions.HttpMethod.POST, "/email", {}, {To: "a@b.com"}, headers); | ||
| const init: any = stub.firstCall.args[1]; | ||
| expect(init.method).to.equal("POST"); | ||
| expect(init.body).to.equal(JSON.stringify({To: "a@b.com"})); | ||
| expect(init.headers).to.eql(headers); | ||
| expect(init.signal).to.be.instanceOf(AbortSignal); | ||
| }); | ||
| it("does not send a body for bodyless requests", async () => { | ||
| const stub = stubFetch().resolves(buildResponse(200, {})); | ||
| await httpClient.httpRequest(ClientOptions.HttpMethod.GET, "/server", {}, null, {}); | ||
| const init: any = stub.firstCall.args[1]; | ||
| expect(init.body).to.equal(undefined); | ||
| }); | ||
| it("expands array query params to repeated keys and serializes Dates as ISO strings", async () => { | ||
| const stub = stubFetch().resolves(buildResponse(200, {})); | ||
| const since = new Date("2026-01-02T03:04:05.000Z"); | ||
| await httpClient.httpRequest(ClientOptions.HttpMethod.GET, "/x", | ||
| {tags: ["a", "b"], since} as any, null, {}); | ||
| expect(stub.firstCall.args[0]) | ||
| .to.equal("https://api.postmarkapp.com/x?tags=a&tags=b&since=2026-01-02T03%3A04%3A05.000Z"); | ||
| }); | ||
| }); | ||
| describe("custom fetch option", () => { | ||
| it("uses a caller-supplied fetch implementation (e.g. for proxy support)", async () => { | ||
| const customFetch = sinon.stub().resolves(buildResponse(200, {ok: true})); | ||
| const clientWithFetch = new FetchHttpClient({fetch: customFetch} as any); | ||
| const result: any = await clientWithFetch.httpRequest(ClientOptions.HttpMethod.GET, "/server", {}, null, {}); | ||
| expect(customFetch.calledOnce).to.be.true; | ||
| expect(customFetch.firstCall.args[0]).to.equal("https://api.postmarkapp.com/server"); | ||
| expect(result).to.eql({ok: true}); | ||
| }); | ||
| }); | ||
| describe("network / timeout errors", () => { | ||
| it("default error", () => { | ||
| stubFetch().rejects(new Error("test")); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.PostmarkError); | ||
| expect(error.message).to.equal("test"); | ||
| expect(error.code).to.equal(0); | ||
| expect(error.statusCode).to.equal(0); | ||
| }); | ||
| }); | ||
| it("error with no message in it", () => { | ||
| const errorToThrow: any = { stack: 'Hello stack' }; | ||
| stubFetch().rejects(errorToThrow); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.an.instanceof(Errors.PostmarkError); | ||
| expect(error.name).to.equal("PostmarkError"); | ||
| expect(error.message).to.equal(JSON.stringify(errorToThrow)); | ||
| }); | ||
| }); | ||
| }); | ||
| describe("http status code errors", () => { | ||
| const stubStatus = (statusNumber: number, body: any = { Message: "Basic error", ErrorCode: 0 }) => | ||
| stubFetch().resolves(buildResponse(statusNumber, body)); | ||
| it("uses the response Message and ErrorCode for the built error", () => { | ||
| stubStatus(500, { Message: "Server exploded", ErrorCode: 100 }); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error.message).to.equal("Server exploded"); | ||
| expect(error.code).to.equal(100); | ||
| expect(error.statusCode).to.equal(500); | ||
| }); | ||
| }); | ||
| it("falls back to a generic message when the body has none", () => { | ||
| stubFetch().resolves(buildResponse(500)); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.InternalServerError); | ||
| expect(error.message).to.equal("Request returned status code 500"); | ||
| }); | ||
| }); | ||
| it("401", () => { | ||
| stubStatus(401); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.InvalidAPIKeyError); | ||
| }); | ||
| }); | ||
| it("404", () => { | ||
| stubStatus(404); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.PostmarkError); | ||
| }); | ||
| }); | ||
| it("422", () => { | ||
| stubStatus(422); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.ApiInputError); | ||
| }); | ||
| }); | ||
| it("422 - invalid email request", () => { | ||
| stubStatus(422, { Message: "Basic error", ErrorCode: 300 }); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.InvalidEmailRequestError); | ||
| }); | ||
| }); | ||
| it("422 - inactive recipients", () => { | ||
| stubStatus(422, { Message: "Basic error", ErrorCode: 406 }); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.InactiveRecipientsError); | ||
| }); | ||
| }); | ||
| it("429", () => { | ||
| stubStatus(429); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.RateLimitExceededError); | ||
| }); | ||
| }); | ||
| it("500", () => { | ||
| stubStatus(500); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.InternalServerError); | ||
| }); | ||
| }); | ||
| it("503", () => { | ||
| stubStatus(503); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.ServiceUnavailablerError); | ||
| }); | ||
| }); | ||
| it("unknown status", () => { | ||
| stubStatus(-1); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.UnknownError); | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
+18
-0
| # Changelog | ||
| ## 5.0.0 | ||
| Migrated the HTTP client from axios to the native Fetch API, making the SDK dependency-free. | ||
| This is a major release; the migration introduces five breaking changes, detailed below. | ||
| ### Breaking changes | ||
| 1. **Minimum Node version is now 18.0.0** (dropped Node 14 and 16). The native Fetch API requires Node 18+. | ||
| 2. **Proxy environment variables (`HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY`) are no longer honored automatically.** axios read them via `proxy-from-env`, but Node's built-in fetch (undici) does not. To route requests through a proxy, pass a custom fetch via the new `fetch` client option (e.g. bound to an undici `ProxyAgent` dispatcher) — see the README "Proxies / custom fetch" section. | ||
| 3. **An empty `2xx` response body now resolves to `{}`** instead of the empty string `""` that axios surfaced. | ||
| 4. **`client.httpClient.client` is no longer an axios instance** — it is now a `fetch` function. Code that reached into it to add axios interceptors, set `defaults`, or attach an axios adapter/proxy will no longer work; use the new `fetch` client option for transport customization instead. | ||
| 5. **Request `timeout` semantics changed.** With axios `timeout` was a response/inactivity timeout; it is now mapped to `AbortSignal.timeout()`, which is a total deadline for the entire request. Slow-but-progressing responses that previously completed may now abort once the total duration exceeds the configured `timeout`. | ||
| ### Other changes | ||
| * removed the `axios` dependency (resolves CVE exposure and `url.parse()` deprecation warnings, and restores compatibility with Edge/Workers runtimes) | ||
| * added the `fetch` client option, allowing a custom fetch implementation to be supplied for proxy support, testing, or other transport customization | ||
| ## 4.0.7 | ||
@@ -3,0 +21,0 @@ |
@@ -18,3 +18,3 @@ "use strict"; | ||
| this.clientVersion = CLIENT_VERSION; | ||
| this.httpClient = new HttpClient_1.AxiosHttpClient(configOptions); | ||
| this.httpClient = new HttpClient_1.FetchHttpClient(configOptions); | ||
| } | ||
@@ -21,0 +21,0 @@ BaseClient.prototype.setClientOptions = function (configOptions) { |
@@ -1,5 +0,8 @@ | ||
| import { AxiosInstance } from "axios"; | ||
| import { ClientOptions, HttpClient } from "./models"; | ||
| export declare class AxiosHttpClient extends HttpClient { | ||
| client: AxiosInstance; | ||
| /** | ||
| * Http client implementation based on the native Fetch API (available in Node 18+). | ||
| * This keeps the SDK dependency-free while preserving the previous error handling contract. | ||
| */ | ||
| export declare class FetchHttpClient extends HttpClient { | ||
| client: ClientOptions.FetchImplementation; | ||
| private errorHandler; | ||
@@ -9,4 +12,2 @@ constructor(configOptions?: ClientOptions.Configuration); | ||
| * Create http client instance with default settings. | ||
| * | ||
| * @return {AxiosInstance} | ||
| */ | ||
@@ -21,19 +22,65 @@ initHttpClient(configOptions?: ClientOptions.Configuration): void; | ||
| * @param body - Data sent with http request. | ||
| * @param requestHeaders - Headers sent with http request. | ||
| */ | ||
| httpRequest<T>(method: ClientOptions.HttpMethod, path: string, queryParameters: object, body: (null | object), requestHeaders: any): Promise<T>; | ||
| /** | ||
| * Process callback function for HTTP request. | ||
| * Build the full request URL from the base URL, path and query parameters. | ||
| * | ||
| * @param error - request error that needs to be transformed to proper Postmark error. | ||
| * @private | ||
| */ | ||
| private buildRequestURL; | ||
| /** | ||
| * Serialize query parameters into a querystring, ignoring undefined and null values. | ||
| * | ||
| * Every current *FilteringParameters query value is a string, number, boolean or enum, so | ||
| * primitive serialization is sufficient today. Arrays are still expanded to repeated keys and | ||
| * Date values to ISO strings (as axios used to do) so that adding such a filter param later | ||
| * cannot silently produce a malformed URL via String([1,2]) or String(new Date()). | ||
| * | ||
| * @private | ||
| */ | ||
| private serializeQueryParameters; | ||
| private stringifyQueryValue; | ||
| /** | ||
| * Read and parse the response body. Postmark responses are JSON, but empty bodies are tolerated. | ||
| * | ||
| * @private | ||
| */ | ||
| private parseResponseBody; | ||
| /** | ||
| * Build a Postmark error from a non-2xx response. | ||
| * | ||
| * @param data - parsed response body. | ||
| * @param status - http response status code. | ||
| * | ||
| * @return {PostmarkError} - formatted Postmark error | ||
| * @private | ||
| */ | ||
| private buildRequestError; | ||
| /** | ||
| * Transform a thrown error (network failure, timeout, abort) into a proper Postmark error. | ||
| * | ||
| * @param errorThrown - error thrown while performing the request. | ||
| * | ||
| * @return {PostmarkError} - formatted Postmark error | ||
| * @private | ||
| */ | ||
| private transformError; | ||
| /** | ||
| * Timeout in seconds is adjusted to Axios format. | ||
| * Build an AbortSignal that aborts the request once the configured timeout elapses. | ||
| * | ||
| * AbortSignal.timeout() is available at runtime in Node 18+, but is not present in the | ||
| * lib.dom typings shipped with the TypeScript version this project pins, so it is | ||
| * referenced through an explicit cast. | ||
| * | ||
| * @private | ||
| */ | ||
| private buildTimeoutSignal; | ||
| /** | ||
| * Timeout in seconds is adjusted to milliseconds. | ||
| * | ||
| * @private | ||
| */ | ||
| private getRequestTimeoutInMilliseconds; | ||
| private adjustValue; | ||
| } |
+190
-48
@@ -28,10 +28,49 @@ "use strict"; | ||
| }; | ||
| var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
| function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
| return new (P || (P = Promise))(function (resolve, reject) { | ||
| function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
| function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
| function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
| step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
| }); | ||
| }; | ||
| var __generator = (this && this.__generator) || function (thisArg, body) { | ||
| var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
| return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
| function verb(n) { return function (v) { return step([n, v]); }; } | ||
| function step(op) { | ||
| if (f) throw new TypeError("Generator is already executing."); | ||
| while (_) try { | ||
| if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
| if (y = 0, t) op = [op[0] & 2, t.value]; | ||
| switch (op[0]) { | ||
| case 0: case 1: t = op; break; | ||
| case 4: _.label++; return { value: op[1], done: false }; | ||
| case 5: _.label++; y = op[1]; op = [0]; continue; | ||
| case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
| default: | ||
| if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
| if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
| if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
| if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
| if (t[2]) _.ops.pop(); | ||
| _.trys.pop(); continue; | ||
| } | ||
| op = body.call(thisArg, _); | ||
| } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
| if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
| } | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.AxiosHttpClient = void 0; | ||
| var axios_1 = require("axios"); | ||
| exports.FetchHttpClient = void 0; | ||
| var models_1 = require("./models"); | ||
| var index_1 = require("./errors/index"); | ||
| var AxiosHttpClient = /** @class */ (function (_super) { | ||
| __extends(AxiosHttpClient, _super); | ||
| function AxiosHttpClient(configOptions) { | ||
| /** | ||
| * Http client implementation based on the native Fetch API (available in Node 18+). | ||
| * This keeps the SDK dependency-free while preserving the previous error handling contract. | ||
| */ | ||
| var FetchHttpClient = /** @class */ (function (_super) { | ||
| __extends(FetchHttpClient, _super); | ||
| function FetchHttpClient(configOptions) { | ||
| var _this = _super.call(this, configOptions) || this; | ||
@@ -43,19 +82,9 @@ _this.errorHandler = new index_1.ErrorHandler(); | ||
| * Create http client instance with default settings. | ||
| * | ||
| * @return {AxiosInstance} | ||
| */ | ||
| AxiosHttpClient.prototype.initHttpClient = function (configOptions) { | ||
| FetchHttpClient.prototype.initHttpClient = function (configOptions) { | ||
| var _a; | ||
| this.clientOptions = __assign(__assign({}, models_1.HttpClient.DefaultOptions), configOptions); | ||
| var httpClient = axios_1.default.create({ | ||
| baseURL: this.getBaseHttpRequestURL(), | ||
| timeout: this.getRequestTimeoutInMilliseconds(), | ||
| responseType: "json", | ||
| maxContentLength: Infinity, | ||
| maxBodyLength: Infinity, | ||
| validateStatus: function (status) { | ||
| return status >= 200 && status < 300; | ||
| }, | ||
| }); | ||
| httpClient.interceptors.response.use(function (response) { return (response.data); }); | ||
| this.client = httpClient; | ||
| // Use a caller-supplied fetch when provided (e.g. bound to a proxy dispatcher), | ||
| // otherwise wrap the global fetch. The reference is stored so it can also be stubbed in tests. | ||
| this.client = (_a = this.clientOptions.fetch) !== null && _a !== void 0 ? _a : (function (input, init) { return fetch(input, init); }); | ||
| }; | ||
@@ -69,51 +98,164 @@ /** | ||
| * @param body - Data sent with http request. | ||
| * @param requestHeaders - Headers sent with http request. | ||
| */ | ||
| AxiosHttpClient.prototype.httpRequest = function (method, path, queryParameters, body, requestHeaders) { | ||
| FetchHttpClient.prototype.httpRequest = function (method, path, queryParameters, body, requestHeaders) { | ||
| return __awaiter(this, void 0, void 0, function () { | ||
| var response, errorThrown_1, data; | ||
| return __generator(this, function (_a) { | ||
| switch (_a.label) { | ||
| case 0: | ||
| _a.trys.push([0, 2, , 3]); | ||
| return [4 /*yield*/, this.client(this.buildRequestURL(path, queryParameters), { | ||
| method: method, | ||
| headers: requestHeaders, | ||
| body: (body === null || body === undefined) ? undefined : JSON.stringify(body), | ||
| signal: this.buildTimeoutSignal(), | ||
| })]; | ||
| case 1: | ||
| response = _a.sent(); | ||
| return [3 /*break*/, 3]; | ||
| case 2: | ||
| errorThrown_1 = _a.sent(); | ||
| // Network errors, aborts and timeouts reject the fetch promise. | ||
| return [2 /*return*/, Promise.reject(this.transformError(errorThrown_1))]; | ||
| case 3: return [4 /*yield*/, this.parseResponseBody(response)]; | ||
| case 4: | ||
| data = _a.sent(); | ||
| // Unlike axios, fetch does not reject on non-2xx responses, so handle them manually. | ||
| if (response.status >= 200 && response.status < 300) { | ||
| return [2 /*return*/, data]; | ||
| } | ||
| return [2 /*return*/, Promise.reject(this.buildRequestError(data, response.status))]; | ||
| } | ||
| }); | ||
| }); | ||
| }; | ||
| /** | ||
| * Build the full request URL from the base URL, path and query parameters. | ||
| * | ||
| * @private | ||
| */ | ||
| FetchHttpClient.prototype.buildRequestURL = function (path, queryParameters) { | ||
| var baseURL = this.getBaseHttpRequestURL(); | ||
| var normalizedPath = path.startsWith("/") ? path : "/".concat(path); | ||
| var queryString = this.serializeQueryParameters(queryParameters); | ||
| return "".concat(baseURL).concat(normalizedPath).concat(queryString); | ||
| }; | ||
| /** | ||
| * Serialize query parameters into a querystring, ignoring undefined and null values. | ||
| * | ||
| * Every current *FilteringParameters query value is a string, number, boolean or enum, so | ||
| * primitive serialization is sufficient today. Arrays are still expanded to repeated keys and | ||
| * Date values to ISO strings (as axios used to do) so that adding such a filter param later | ||
| * cannot silently produce a malformed URL via String([1,2]) or String(new Date()). | ||
| * | ||
| * @private | ||
| */ | ||
| FetchHttpClient.prototype.serializeQueryParameters = function (queryParameters) { | ||
| var _this = this; | ||
| return this.client.request({ | ||
| method: method, | ||
| url: path, | ||
| data: body, | ||
| headers: requestHeaders, | ||
| params: queryParameters, | ||
| }).catch(function (errorThrown) { | ||
| return Promise.reject(_this.transformError(errorThrown)); | ||
| var searchParams = new URLSearchParams(); | ||
| Object.entries(queryParameters || {}).forEach(function (_a) { | ||
| var key = _a[0], value = _a[1]; | ||
| if (value === undefined || value === null) { | ||
| return; | ||
| } | ||
| if (Array.isArray(value)) { | ||
| value.forEach(function (item) { | ||
| if (item !== undefined && item !== null) { | ||
| searchParams.append(key, _this.stringifyQueryValue(item)); | ||
| } | ||
| }); | ||
| } | ||
| else { | ||
| searchParams.append(key, _this.stringifyQueryValue(value)); | ||
| } | ||
| }); | ||
| var queryString = searchParams.toString(); | ||
| return queryString === "" ? "" : "?".concat(queryString); | ||
| }; | ||
| FetchHttpClient.prototype.stringifyQueryValue = function (value) { | ||
| return (value instanceof Date) ? value.toISOString() : String(value); | ||
| }; | ||
| /** | ||
| * Process callback function for HTTP request. | ||
| * Read and parse the response body. Postmark responses are JSON, but empty bodies are tolerated. | ||
| * | ||
| * @param error - request error that needs to be transformed to proper Postmark error. | ||
| * @private | ||
| */ | ||
| FetchHttpClient.prototype.parseResponseBody = function (response) { | ||
| return __awaiter(this, void 0, void 0, function () { | ||
| var text; | ||
| return __generator(this, function (_a) { | ||
| switch (_a.label) { | ||
| case 0: return [4 /*yield*/, response.text()]; | ||
| case 1: | ||
| text = _a.sent(); | ||
| if (text === "") { | ||
| return [2 /*return*/, {}]; | ||
| } | ||
| try { | ||
| return [2 /*return*/, JSON.parse(text)]; | ||
| } | ||
| catch (_b) { | ||
| return [2 /*return*/, text]; | ||
| } | ||
| return [2 /*return*/]; | ||
| } | ||
| }); | ||
| }); | ||
| }; | ||
| /** | ||
| * Build a Postmark error from a non-2xx response. | ||
| * | ||
| * @param data - parsed response body. | ||
| * @param status - http response status code. | ||
| * | ||
| * @return {PostmarkError} - formatted Postmark error | ||
| * @private | ||
| */ | ||
| AxiosHttpClient.prototype.transformError = function (errorThrown) { | ||
| var response = errorThrown.response; | ||
| if (response !== undefined) { | ||
| var status_1 = this.adjustValue(0, response.status); | ||
| var errorCode = this.adjustValue(0, response.data.ErrorCode); | ||
| var message = this.adjustValue(errorThrown.message, response.data.Message); | ||
| return this.errorHandler.buildError(message, errorCode, status_1); | ||
| } | ||
| else if (errorThrown.message !== undefined) { | ||
| FetchHttpClient.prototype.buildRequestError = function (data, status) { | ||
| var errorCode = this.adjustValue(0, data == null ? undefined : data.ErrorCode); | ||
| var message = this.adjustValue("Request returned status code ".concat(status), data == null ? undefined : data.Message); | ||
| return this.errorHandler.buildError(message, errorCode, status); | ||
| }; | ||
| /** | ||
| * Transform a thrown error (network failure, timeout, abort) into a proper Postmark error. | ||
| * | ||
| * @param errorThrown - error thrown while performing the request. | ||
| * | ||
| * @return {PostmarkError} - formatted Postmark error | ||
| * @private | ||
| */ | ||
| FetchHttpClient.prototype.transformError = function (errorThrown) { | ||
| if (errorThrown !== null && errorThrown.message !== undefined) { | ||
| return this.errorHandler.buildError(errorThrown.message); | ||
| } | ||
| else { | ||
| return this.errorHandler.buildError(JSON.stringify(errorThrown, Object.getOwnPropertyNames(errorThrown))); | ||
| } | ||
| return this.errorHandler.buildError(JSON.stringify(errorThrown, Object.getOwnPropertyNames(errorThrown))); | ||
| }; | ||
| /** | ||
| * Timeout in seconds is adjusted to Axios format. | ||
| * Build an AbortSignal that aborts the request once the configured timeout elapses. | ||
| * | ||
| * AbortSignal.timeout() is available at runtime in Node 18+, but is not present in the | ||
| * lib.dom typings shipped with the TypeScript version this project pins, so it is | ||
| * referenced through an explicit cast. | ||
| * | ||
| * @private | ||
| */ | ||
| AxiosHttpClient.prototype.getRequestTimeoutInMilliseconds = function () { | ||
| FetchHttpClient.prototype.buildTimeoutSignal = function () { | ||
| var abortSignal = AbortSignal; | ||
| return abortSignal.timeout(this.getRequestTimeoutInMilliseconds()); | ||
| }; | ||
| /** | ||
| * Timeout in seconds is adjusted to milliseconds. | ||
| * | ||
| * @private | ||
| */ | ||
| FetchHttpClient.prototype.getRequestTimeoutInMilliseconds = function () { | ||
| return (this.clientOptions.timeout || 60) * 1000; | ||
| }; | ||
| AxiosHttpClient.prototype.adjustValue = function (defaultValue, data) { | ||
| FetchHttpClient.prototype.adjustValue = function (defaultValue, data) { | ||
| return (data === undefined) ? defaultValue : data; | ||
| }; | ||
| return AxiosHttpClient; | ||
| return FetchHttpClient; | ||
| }(models_1.HttpClient)); | ||
| exports.AxiosHttpClient = AxiosHttpClient; | ||
| exports.FetchHttpClient = FetchHttpClient; | ||
| //# sourceMappingURL=HttpClient.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"HttpClient.js","sourceRoot":"","sources":["../../src/client/HttpClient.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"HttpClient.js","sourceRoot":"","sources":["../../src/client/HttpClient.ts"],"names":[],"mappings":"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"} |
@@ -5,2 +5,2 @@ import * as Errors from "../../errors/Errors"; | ||
| */ | ||
| export type Callback<T> = (error: (Errors.PostmarkError | null), result: (T | null)) => void; | ||
| export declare type Callback<T> = (error: (Errors.PostmarkError | null), result: (T | null)) => void; |
| export declare namespace ClientOptions { | ||
| /** | ||
| * Custom fetch implementation signature. | ||
| * | ||
| * Node's built-in fetch (undici) does not read HTTP_PROXY/HTTPS_PROXY/NO_PROXY. Supplying a | ||
| * custom fetch (for example one bound to an undici ProxyAgent dispatcher) is the supported way | ||
| * to route requests through a corporate egress proxy or otherwise customise transport. | ||
| */ | ||
| type FetchImplementation = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>; | ||
| class Configuration { | ||
@@ -6,3 +14,4 @@ useHttps?: boolean; | ||
| timeout?: number; | ||
| constructor(useHttps?: boolean, requestHost?: string, timeout?: number); | ||
| fetch?: FetchImplementation; | ||
| constructor(useHttps?: boolean, requestHost?: string, timeout?: number, fetch?: FetchImplementation); | ||
| } | ||
@@ -9,0 +18,0 @@ enum HttpMethod { |
@@ -7,6 +7,10 @@ "use strict"; | ||
| var Configuration = /** @class */ (function () { | ||
| function Configuration(useHttps, requestHost, timeout) { | ||
| function Configuration(useHttps, requestHost, timeout, fetch) { | ||
| this.useHttps = useHttps; | ||
| this.requestHost = requestHost; | ||
| this.timeout = timeout; | ||
| // Only set when provided so the options object shape is unchanged for callers that don't use it. | ||
| if (fetch !== undefined) { | ||
| this.fetch = fetch; | ||
| } | ||
| } | ||
@@ -13,0 +17,0 @@ return Configuration; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"ClientOptions.js","sourceRoot":"","sources":["../../../../src/client/models/client/ClientOptions.ts"],"names":[],"mappings":";;;AAAA,IAAiB,aAAa,CA2B7B;AA3BD,WAAiB,aAAa;IAC1B;QAKI,uBAAY,QAAkB,EAAE,WAAoB,EAAE,OAAgB;YAClE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;YACzB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;YAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QAC3B,CAAC;QACL,oBAAC;IAAD,CAAC,AAVD,IAUC;IAVY,2BAAa,gBAUzB,CAAA;IAED,IAAY,UAQX;IARD,WAAY,UAAU;QAClB,yBAAW,CAAA;QACX,2BAAa,CAAA;QACb,+BAAiB,CAAA;QACjB,yBAAW,CAAA;QACX,iCAAmB,CAAA;QACnB,2BAAa,CAAA;QACb,6BAAe,CAAA;IACnB,CAAC,EARW,UAAU,GAAV,wBAAU,KAAV,wBAAU,QAQrB;IAED,IAAY,eAGX;IAHD,WAAY,eAAe;QACvB,2DAAwC,CAAA;QACxC,6DAA0C,CAAA;IAC9C,CAAC,EAHW,eAAe,GAAf,6BAAe,KAAf,6BAAe,QAG1B;AACL,CAAC,EA3BgB,aAAa,GAAb,qBAAa,KAAb,qBAAa,QA2B7B"} | ||
| {"version":3,"file":"ClientOptions.js","sourceRoot":"","sources":["../../../../src/client/models/client/ClientOptions.ts"],"names":[],"mappings":";;;AAAA,IAAiB,aAAa,CAwC7B;AAxCD,WAAiB,aAAa;IAW1B;QAMI,uBAAY,QAAkB,EAAE,WAAoB,EAAE,OAAgB,EAAE,KAA2B;YAC/F,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;YACzB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;YAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;YACvB,iGAAiG;YACjG,IAAI,KAAK,KAAK,SAAS,EAAE;gBAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;aAAE;QACpD,CAAC;QACL,oBAAC;IAAD,CAAC,AAbD,IAaC;IAbY,2BAAa,gBAazB,CAAA;IAED,IAAY,UAQX;IARD,WAAY,UAAU;QAClB,yBAAW,CAAA;QACX,2BAAa,CAAA;QACb,+BAAiB,CAAA;QACjB,yBAAW,CAAA;QACX,iCAAmB,CAAA;QACnB,2BAAa,CAAA;QACb,6BAAe,CAAA;IACnB,CAAC,EARW,UAAU,GAAV,wBAAU,KAAV,wBAAU,QAQrB;IAED,IAAY,eAGX;IAHD,WAAY,eAAe;QACvB,2DAAwC,CAAA;QACxC,6DAA0C,CAAA;IAC9C,CAAC,EAHW,eAAe,GAAf,6BAAe,KAAf,6BAAe,QAG1B;AACL,CAAC,EAxCgB,aAAa,GAAb,qBAAa,KAAb,qBAAa,QAwC7B"} |
@@ -23,3 +23,3 @@ export interface Suppression { | ||
| } | ||
| export type CreateSuppressionsRequest = SuppressionEntries; | ||
| export type DeleteSuppressionsRequest = CreateSuppressionsRequest; | ||
| export declare type CreateSuppressionsRequest = SuppressionEntries; | ||
| export declare type DeleteSuppressionsRequest = CreateSuppressionsRequest; |
+14
-9
@@ -12,3 +12,3 @@ { | ||
| ], | ||
| "version": "4.0.7", | ||
| "version": "5.0.0", | ||
| "author": "Postmark (under ActiveCampaign LLC)", | ||
@@ -40,2 +40,5 @@ "contributors": [ | ||
| "types": "./dist/index.d.ts", | ||
| "publishConfig": { | ||
| "registry": "https://registry.npmjs.org/" | ||
| }, | ||
| "directories": { | ||
@@ -45,9 +48,10 @@ "lib": "./dist/index.js" | ||
| "scripts": { | ||
| "compile": "rm -r -f ./dist && node_modules/.bin/tsc", | ||
| "test": "node_modules/.bin/mocha --timeout 30000 --retries 1 -r ts-node/register test/**/*test.ts", | ||
| "unittest": "node_modules/.bin/mocha --timeout 30000 --retries 1 -r ts-node/register test/unit/**/*test.ts", | ||
| "watchtests": "node_modules/.bin/mocha --timeout 30000 --retries 1 -r ts-node/register -R list -w --recursive -G test/**/*test.ts", | ||
| "check-lockfile": "node scripts/check-lockfile-registry.js", | ||
| "compile": "rm -r -f ./dist && tsc", | ||
| "test": "mocha --timeout 30000 --retries 1 -r ts-node/register test/**/*test.ts", | ||
| "unittest": "mocha --timeout 30000 --retries 1 -r ts-node/register test/unit/**/*test.ts", | ||
| "watchtests": "mocha --timeout 30000 --retries 1 -r ts-node/register -R list -w --recursive -G test/**/*test.ts", | ||
| "lint": "npx eslint src --ext ts --ignore-pattern 'src/*test*'; exit 0", | ||
| "lintfix": "npx eslint src --ext ts --fix --ignore-pattern 'src/test*.ts'; exit 0", | ||
| "compile-docs": "echo 'Generating docs...' && mkdir -p ./docs && rm -r ./docs && node_modules/.bin/typedoc --options typedoc.json && git add -A ./docs && echo 'Generated docs!'" | ||
| "compile-docs": "echo 'Generating docs...' && mkdir -p ./docs && rm -r ./docs && typedoc --options typedoc.json && git add -A ./docs && echo 'Generated docs!'" | ||
| }, | ||
@@ -57,3 +61,3 @@ "homepage": "http://ActiveCampaign.github.io/postmark.js", | ||
| "type": "git", | ||
| "url": "https://github.com/ActiveCampaign/postmark.js.git" | ||
| "url": "git+https://github.com/ActiveCampaign/postmark.js.git" | ||
| }, | ||
@@ -64,2 +68,3 @@ "bugs": { | ||
| "precommit": [ | ||
| "check-lockfile", | ||
| "compile", | ||
@@ -87,5 +92,5 @@ "lint", | ||
| }, | ||
| "dependencies": { | ||
| "axios": "^1.13.5" | ||
| "engines": { | ||
| "node": ">=18.0.0" | ||
| } | ||
| } |
+25
-2
@@ -18,5 +18,28 @@ <a href="https://postmarkapp.com"> | ||
| Minimum supported [Node](https://endoflife.date/nodejs) version `v14.0.0`. If you use older Node versions for which | ||
| [active and security support ended](https://endoflife.date/nodejs) , you will need to use older library versions (3.x.x). | ||
| Minimum supported [Node](https://endoflife.date/nodejs) version `v18.0.0`. The library uses the native Fetch API and has | ||
| no runtime dependencies. If you use older Node versions for which | ||
| [active and security support ended](https://endoflife.date/nodejs), you will need to use older library versions (3.x.x for Node < 14, 4.x.x for Node < 18). | ||
| > **Note:** The global Fetch API is stable from Node 21. On Node 18–20 it works but may log an | ||
| > `ExperimentalWarning: The Fetch API is an experimental feature`. This is emitted by Node itself, not by this library. | ||
| ## Proxies / custom fetch | ||
| Unlike axios, Node's built-in fetch (undici) does **not** read the `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` | ||
| environment variables. If you run behind a corporate egress proxy, supply your own fetch implementation via the | ||
| `fetch` client option — for example one bound to an undici [`ProxyAgent`](https://undici.nodejs.org/#/docs/api/ProxyAgent) dispatcher: | ||
| ```js | ||
| import { ServerClient } from "postmark"; | ||
| import { ProxyAgent } from "undici"; | ||
| const dispatcher = new ProxyAgent("http://proxy.internal:8080"); | ||
| const client = new ServerClient("server-token", { | ||
| fetch: (input, init) => fetch(input, { ...init, dispatcher }), | ||
| }); | ||
| ``` | ||
| The same option can be used to inject a mock/instrumented fetch for testing or other transport customization. | ||
| ## Usage | ||
@@ -23,0 +46,0 @@ |
@@ -43,2 +43,17 @@ import * as postmark from "../../src/index"; | ||
| // Postmark's suppression list is eventually consistent, so a record created via the API | ||
| // may not be immediately queryable. Poll until the expected state is reached (or time out), | ||
| // which keeps the assertions deterministic across fast and slow Node runtimes. | ||
| async function getSuppressionsUntil(filter: object, predicate: (result: Suppressions) => boolean): Promise<Suppressions> { | ||
| const maxAttempts = 15; | ||
| let suppressions: Suppressions = await client.getSuppressions('outbound', filter); | ||
| for (let attempt = 0; attempt < maxAttempts && !predicate(suppressions); attempt++) { | ||
| await new Promise((resolve) => setTimeout(resolve, 500)); | ||
| suppressions = await client.getSuppressions('outbound', filter); | ||
| } | ||
| return suppressions; | ||
| } | ||
| it("createSuppression", async () => { | ||
@@ -59,3 +74,3 @@ const emailAddress = `nothing+create@${suppression_email_domain}`; | ||
| const suppressions: Suppressions = await client.getSuppressions('outbound'); | ||
| const suppressions: Suppressions = await getSuppressionsUntil({}, (result) => result.Suppressions.length >= 1); | ||
| expect(suppressions.Suppressions.length).to.be.gte(1); | ||
@@ -70,7 +85,7 @@ }); | ||
| let suppressions = await client.getSuppressions('outbound', { | ||
| let suppressions = await getSuppressionsUntil({ | ||
| emailAddress: email, | ||
| origin: SuppressionOrigin.Customer, | ||
| suppressionReason: SuppressionReason.ManualSuppression | ||
| }); | ||
| }, (result) => result.Suppressions.length === 1); | ||
| expect(suppressions.Suppressions.length).to.eq(1); | ||
@@ -77,0 +92,0 @@ |
@@ -37,3 +37,3 @@ import * as postmark from "../../src/index"; | ||
| it("httpClient initialized", () => { | ||
| expect(client.httpClient.client.defaults).not.to.eql(180000); | ||
| expect(client.httpClient.client).to.be.a("function"); | ||
| }); | ||
@@ -40,0 +40,0 @@ }); |
@@ -33,3 +33,3 @@ import * as postmark from "../../src/index"; | ||
| it("httpClient initialized", () => { | ||
| expect(client.httpClient.client.defaults).not.to.eql(180000); | ||
| expect(client.httpClient.client).to.be.a("function"); | ||
| }); | ||
@@ -36,0 +36,0 @@ }); |
| import {expect} from "chai"; | ||
| import "mocha"; | ||
| import * as sinon from "sinon"; | ||
| import {AxiosHttpClient} from "../../src/client/HttpClient"; | ||
| import {ClientOptions} from "../../src/client/models"; | ||
| import {Errors} from "../../src"; | ||
| describe("AxiosHttpClient", () => { | ||
| let sandbox: sinon.SinonSandbox; | ||
| const httpClient = new AxiosHttpClient(); | ||
| beforeEach(() => { | ||
| sandbox = sinon.createSandbox(); | ||
| }); | ||
| afterEach(() => { | ||
| sandbox.restore(); | ||
| }); | ||
| it("default error", () => { | ||
| sandbox.stub(httpClient.client, "request").rejects(new Error("test")); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.PostmarkError); | ||
| expect(error.message).to.equal("test"); | ||
| expect(error.code).to.equal(0); | ||
| expect(error.statusCode).to.equal(0); | ||
| }); | ||
| }); | ||
| it("error with no message in it", () => { | ||
| const errorToThrow: any = { stack: 'Hello stack' }; | ||
| sandbox.stub(httpClient.client, "request").rejects(errorToThrow); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.an.instanceof(Errors.PostmarkError); | ||
| expect(error.name).to.equal("PostmarkError"); | ||
| expect(error.message).to.equal(JSON.stringify(errorToThrow)); | ||
| }); | ||
| }); | ||
| describe("http status code errors", () => { | ||
| const buildAxiosFormatError = (statusNumber: number) => ({ | ||
| response: { data: { Message: "Basic error", ErrorCode: 0 }, status: statusNumber } | ||
| }); | ||
| it("401", () => { | ||
| sandbox.stub(httpClient.client, "request").rejects(buildAxiosFormatError(401)); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.InvalidAPIKeyError); | ||
| }); | ||
| }); | ||
| it("404", () => { | ||
| sandbox.stub(httpClient.client, "request").rejects(buildAxiosFormatError(404)); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.PostmarkError); | ||
| }); | ||
| }); | ||
| it("422", () => { | ||
| sandbox.stub(httpClient.client, "request").rejects(buildAxiosFormatError(422)); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.ApiInputError); | ||
| }); | ||
| }); | ||
| it("422 - inactive recipients", () => { | ||
| const errorToThrow = buildAxiosFormatError(422) | ||
| errorToThrow.response.data.ErrorCode=300 | ||
| sandbox.stub(httpClient.client, "request").rejects(errorToThrow); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.InvalidEmailRequestError); | ||
| }); | ||
| }); | ||
| it("422 - invalid email", () => { | ||
| const errorToThrow = buildAxiosFormatError(422) | ||
| errorToThrow.response.data.ErrorCode=406 | ||
| sandbox.stub(httpClient.client, "request").rejects(errorToThrow); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.InactiveRecipientsError); | ||
| }); | ||
| }); | ||
| it("429", () => { | ||
| const errorToThrow = buildAxiosFormatError(429) | ||
| sandbox.stub(httpClient.client, "request").rejects(errorToThrow); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.RateLimitExceededError); | ||
| }); | ||
| }); | ||
| it("500", () => { | ||
| const errorToThrow = buildAxiosFormatError(500) | ||
| sandbox.stub(httpClient.client, "request").rejects(errorToThrow); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.InternalServerError); | ||
| }); | ||
| }); | ||
| it("503", () => { | ||
| const errorToThrow = buildAxiosFormatError(500) | ||
| sandbox.stub(httpClient.client, "request").rejects(errorToThrow); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.InternalServerError); | ||
| }); | ||
| }); | ||
| it("unknown status", () => { | ||
| const errorToThrow = buildAxiosFormatError(-1) | ||
| sandbox.stub(httpClient.client, "request").rejects(errorToThrow); | ||
| return httpClient.httpRequest(ClientOptions.HttpMethod.GET, "", {}, null, {}).then((result) => { | ||
| throw Error(`Should not be here with result: ${result}`); | ||
| }, (error) => { | ||
| expect(error).to.be.instanceOf(Errors.UnknownError); | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
Unstable ownership
Supply chain riskA new collaborator has begun publishing package versions. Package stability and security risk may be elevated.
Network access
Supply chain riskThis module accesses the network.
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
367890
6.63%0
-100%180
0.56%6384
5.7%69
50%1
Infinity%53
1.92%10
900%- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed