mot-js-sdk
Version:
A TypeScript SDK for the MOT History API.
199 lines • 7.29 kB
JavaScript
"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