UNPKG

mot-js-sdk

Version:

A TypeScript SDK for the MOT History API.

199 lines 7.29 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const axios_1 = __importDefault(require("axios")); const qs_1 = __importDefault(require("qs")); const events_1 = require("events"); class MotApiSdk extends events_1.EventEmitter { constructor(clientId, clientSecret, apiKey) { super(); this.clientId = clientId; this.clientSecret = clientSecret; this.apiKey = apiKey; this.token = null; this.tokenExpiry = 0; // Rate limiting properties this.dailyQuota = 500000; this.dailyQuotaReset = 0; this.burstLimit = 10; this.burstTokens = 10; this.rpsLimit = 15; this.requestTimestamps = []; this.axiosInstance = axios_1.default.create({ baseURL: MotApiSdk.BASE_URL, headers: { "X-API-Key": this.apiKey } }); this.setupInterceptors(); this.resetDailyQuota(); } setupInterceptors() { this.axiosInstance.interceptors.request.use(async (config) => { await this.waitForRateLimit(); config.headers["Authorization"] = `Bearer ${await this.getToken()}`; return config; }, error => Promise.reject(error)); this.axiosInstance.interceptors.response.use(response => response, error => this.handleApiError(error)); } async waitForRateLimit() { while (!this.checkRateLimits()) { await new Promise(resolve => setTimeout(resolve, 100)); } this.updateRateLimits(); } checkRateLimits() { const now = Date.now(); // Check daily quota if (this.dailyQuota <= 0 && now < this.dailyQuotaReset) { return false; } // Check burst limit if (this.burstTokens <= 0) { return false; } // Check RPS limit const oneSecondAgo = now - 1000; const requestsLastSecond = this.requestTimestamps.filter(t => t > oneSecondAgo).length; if (requestsLastSecond >= this.rpsLimit) { return false; } return true; } updateRateLimits() { const now = Date.now(); // Update daily quota if (now >= this.dailyQuotaReset) { this.resetDailyQuota(); } this.dailyQuota--; // Update burst tokens this.burstTokens = Math.min(this.burstTokens + 1, this.burstLimit); this.burstTokens--; // Update RPS tracking this.requestTimestamps.push(now); this.requestTimestamps = this.requestTimestamps.filter(t => t > now - 1000); } resetDailyQuota() { const now = Date.now(); this.dailyQuota = 500000; this.dailyQuotaReset = now + 24 * 60 * 60 * 1000; // Reset after 24 hours } async getToken() { if (this.token && this.tokenExpiry > Date.now()) { return this.token; } try { const params = qs_1.default.stringify({ grant_type: "client_credentials", client_id: this.clientId, client_secret: this.clientSecret, scope: MotApiSdk.SCOPE_URL }); const tokenResponse = await axios_1.default.post(MotApiSdk.TOKEN_URL, params, { headers: { "Content-Type": "application/x-www-form-urlencoded" } }); this.token = tokenResponse.data.access_token; this.tokenExpiry = Date.now() + tokenResponse.data.expires_in * 1000; this.emit("tokenRefreshed", { expiresAt: new Date(this.tokenExpiry) }); return this.token; } catch (error) { this.emit("tokenError", error); throw new Error("Failed to obtain access token"); } } handleApiError(error) { if (error.response) { const status = error.response.status; const message = MotApiSdk.ERROR_MESSAGES.get(status) || "An unknown error occurred"; this.emit("apiError", { status, message }); throw new Error(`${status}: ${message}`); } this.emit("networkError", error); throw error; } async makeRequest(endpoint, method = "GET", data) { try { const response = await this.axiosInstance.request({ url: endpoint, method, data: method === "PUT" ? qs_1.default.stringify(data) : data, headers: method === "PUT" ? { "Content-Type": "application/x-www-form-urlencoded" } : {} }); this.emit("requestSuccess", { endpoint, method }); return response.data; } catch (error) { this.emit("requestError", { endpoint, method, error }); throw error; } } async getVehicleByRegistration(registration) { return this.makeRequest(`/registration/${registration}`); } async getVehicleByVin(vin) { return this.makeRequest(`/vin/${vin}`); } async getBulkDownload() { return this.makeRequest("/bulk-download"); } async renewCredentials(credentials) { return this.makeRequest("/credentials", "PUT", credentials); } } MotApiSdk.BASE_URL = "https://history.mot.api.gov.uk/v1/trade/vehicles"; MotApiSdk.TOKEN_URL = "https://login.microsoftonline.com/a455b827-244f-4c97-b5b4-ce5d13b4d00c/oauth2/v2.0/token"; MotApiSdk.SCOPE_URL = "https://tapi.dvsa.gov.uk/.default"; MotApiSdk.ERROR_MESSAGES = new Map([ [400, "Bad Request - The format of the request is incorrect"], [401, "Unauthorized - Authentication credentials are missing or invalid"], [403, "Forbidden - The request is not allowed"], [404, "Not Found - The requested data is not found"], [ 405, "Method Not Allowed - The HTTP method is not supported for this endpoint" ], [406, "Not Acceptable - The requested media type is not supported"], [ 409, "Conflict - The request could not be completed due to a conflict with the current state of the target resource" ], [ 412, "Precondition Failed - Could not complete request because a constraint was not met" ], [ 415, "Unsupported Media Type - The media type of the request is not supported" ], [ 422, "Unprocessable Entity - The request was well-formed but contains semantic errors" ], [ 429, "Too Many Requests - The user has sent too many requests in a given amount of time" ], [500, "Internal Server Error - An unexpected error has occurred"], [ 502, "Bad Gateway - The server received an invalid response from an upstream server" ], [ 503, "Service Unavailable - The server is currently unable to handle the request" ], [ 504, "Gateway Timeout - The upstream server failed to send a request in the time allowed by the server" ] ]); exports.default = MotApiSdk; //# sourceMappingURL=mot-js-sdk.js.map