UNPKG

sc-zabbix-api

Version:

TypeScript client for Zabbix JSON-RPC API with token authentication

387 lines (386 loc) 14 kB
import axios from "axios"; /** * Zabbix API Client using token authentication or user/password login * Provides methods to interact with Zabbix API endpoints. */ export class SCZabbixApi { client; token = null; sessionId = null; requestId = 1; config; initialized = null; constructor(config) { this.config = { timeout: 10000, retries: 3, retryDelay: 1000, loginTimeout: 15000, ...config, }; if (config.token) { this.token = config.token; } this.client = axios.create({ baseURL: config.url, timeout: this.config.timeout, headers: { "Content-Type": "application/json", }, }); // If username/password provided but no token, auto-login if (!config.token && config.username && config.password) { this.initialized = this.autoLogin(); } } /** * Auto-login using username/password when instance is created * @private */ async autoLogin() { if (this.config.username && this.config.password) { try { await this.UserLogin({ username: this.config.username, password: this.config.password, }); } catch (error) { const connectionError = this.createConnectionError(error, "AUTH_ERROR"); console.warn("Auto-login failed:", connectionError.message); throw connectionError; } } } /** * Create a standardized connection error * @private */ createConnectionError(error, type, retryAttempt) { let errorType = type || "UNKNOWN"; let message = "Unknown connection error"; if (error && typeof error === "object") { if (error.code === "ECONNABORTED" || error.message?.includes("timeout")) { errorType = "TIMEOUT"; message = `Connection timeout after ${this.config.timeout}ms`; } else if (error.code === "ECONNREFUSED") { errorType = "CONNECTION_REFUSED"; message = `Connection refused to ${this.config.url}`; } else if (error.code === "ENOTFOUND" || error.code === "EAI_AGAIN") { errorType = "NETWORK_ERROR"; message = `Network error: Cannot resolve ${this.config.url}`; } else if (error.response?.status === 401 || error.response?.status === 403) { errorType = "AUTH_ERROR"; message = "Authentication failed"; } else if (error.message) { message = error.message; } } const connectionError = new Error(message); connectionError.type = errorType; connectionError.code = error?.code; connectionError.originalError = error; connectionError.retryAttempt = retryAttempt; return connectionError; } /** * Sleep for specified milliseconds * @private */ sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Retry logic with exponential backoff * @private */ async withRetry(operation, operationName, useLoginTimeout = false) { const maxRetries = this.config.retries || 3; const baseDelay = this.config.retryDelay || 1000; const timeout = useLoginTimeout ? this.config.loginTimeout : this.config.timeout; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { // Adjust timeout for this specific request if needed const originalTimeout = this.client.defaults.timeout; if (timeout && timeout !== originalTimeout) { this.client.defaults.timeout = timeout; } const result = await operation(); // Restore original timeout if (timeout && timeout !== originalTimeout) { this.client.defaults.timeout = originalTimeout; } return result; } catch (error) { const connectionError = this.createConnectionError(error, undefined, attempt); const isLastAttempt = attempt === maxRetries; const shouldRetry = connectionError.type === "TIMEOUT" || connectionError.type === "NETWORK_ERROR" || connectionError.type === "CONNECTION_REFUSED"; if (!shouldRetry || isLastAttempt) { console.error(`${operationName} failed after ${attempt} attempt(s):`, connectionError.message); throw connectionError; } // Exponential backoff: baseDelay * 2^(attempt-1) const delay = baseDelay * Math.pow(2, attempt - 1); console.warn(`${operationName} failed (attempt ${attempt}/${maxRetries}), retrying in ${delay}ms...`, connectionError.message); await this.sleep(delay); } } throw new Error(`${operationName} failed after ${maxRetries} attempts`); } /** * Ensure the instance is initialized (auto-login completed if needed) * @private */ async ensureInitialized() { if (this.initialized) { await this.initialized; } } /** * Get the authentication token (either from token or session) * @private */ getAuthToken() { return this.token || this.sessionId; } /** * Login to Zabbix using username and password. * This method authenticates a user and returns information about the user including a session ID. * @param params - Login parameters */ async UserLogin(params) { return this.withRetry(async () => { const response = await this.client.post("/", { jsonrpc: "2.0", method: "user.login", params, id: this.requestId++, }); // Store the session ID for future requests if (response.data.result) { if (typeof response.data.result === "string") { // Zabbix returns sessionId directly as string this.sessionId = response.data.result; } else if (typeof response.data.result === "object" && "sessionid" in response.data.result) { // Some Zabbix versions might return as object this.sessionId = response.data.result.sessionid; } } return response.data; }, "Login", true); } /** * Checks if the user session is valid and returns user information. * This method can be used to verify if a session ID or API token is still valid and to retrieve user details. * @param params - Check authentication parameters */ async UserCheckAuthentication(params = {}) { await this.ensureInitialized(); let requestParams; // If no specific sessionid or token is provided in params, use current auth token if (!params.sessionid && !params.token) { const authToken = this.getAuthToken(); if (!authToken) { throw new Error("No authentication available. Please login first or provide a sessionid/token in params."); } // Determine if the current auth token is a session ID or API token // API tokens are typically longer and have a different format // Session IDs are usually shorter hexadecimal strings if (this.token) { // Using API token authentication requestParams = { token: authToken, extend: params.extend, }; } else { // Using session ID authentication requestParams = { sessionid: authToken, extend: params.extend, }; } } else { // Use the provided parameters requestParams = { ...params }; } const response = await this.client.post("/", { jsonrpc: "2.0", method: "user.checkauthentication", params: requestParams, id: this.requestId++, }); return response.data; } /** * Logout from Zabbix and invalidate the current session. */ async UserLogout() { await this.ensureInitialized(); const authToken = this.getAuthToken(); if (!authToken) { throw new Error("No authentication token available. Please login first."); } const response = await this.client.post("/", { jsonrpc: "2.0", method: "user.logout", params: [], auth: authToken, id: this.requestId++, }); // Clear the session ID this.sessionId = null; return response.data; } /** * This method allows to retrieve the version of the Zabbix API. */ async ApiinfoVersion() { return this.withRetry(async () => { const response = await this.client.post("/", { jsonrpc: "2.0", method: "apiinfo.version", params: [], id: this.requestId++, }); return response.data; }, "ApiinfoVersion"); } /** * Test connectivity to Zabbix server * This is a lightweight method to check if the server is reachable */ async testConnection() { const startTime = Date.now(); try { const response = await this.ApiinfoVersion(); const latency = Date.now() - startTime; return { success: true, version: response.result, latency, }; } catch (error) { const latency = Date.now() - startTime; const connectionError = error; return { success: false, error: connectionError.message || "Unknown error", latency, }; } } /** * The method allows to retrieve hosts according to the given parameters. * @param params - Parameters for host retrieval */ async HostGet(params = {}) { await this.ensureInitialized(); const authToken = this.getAuthToken(); if (!authToken) { throw new Error("No authentication token available. Please login first or provide a token in config."); } return this.withRetry(async () => { const response = await this.client.post("/", { jsonrpc: "2.0", method: "host.get", params, auth: authToken, id: this.requestId++, }); return response.data; }, "HostGet"); } /** * The method allows to retrieve items according to the given parameters. * @param params - Parameters for item retrieval */ async ItemGet(params = {}) { await this.ensureInitialized(); const authToken = this.getAuthToken(); if (!authToken) { throw new Error("No authentication token available. Please login first or provide a token in config."); } return this.withRetry(async () => { const response = await this.client.post("/", { jsonrpc: "2.0", method: "item.get", params, auth: authToken, id: this.requestId++, }); return response.data; }, "ItemGet"); } /** * This method allows to create new items. * @param params - Parameters for item creation */ async ItemCreate(params) { await this.ensureInitialized(); const authToken = this.getAuthToken(); if (!authToken) { throw new Error("No authentication token available. Please login first or provide a token in config."); } const response = await this.client.post("/", { jsonrpc: "2.0", method: "item.create", params, auth: authToken, id: this.requestId++, }); return response.data; } /** * This method allows to update existing items. * @param params - Parameters for item update */ async ItemUpdate(params) { await this.ensureInitialized(); const authToken = this.getAuthToken(); if (!authToken) { throw new Error("No authentication token available. Please login first or provide a token in config."); } const response = await this.client.post("/", { jsonrpc: "2.0", method: "item.update", params, auth: authToken, id: this.requestId++, }); return response.data; } /** * This method allows to delete items. * @param params - Parameters for item deletion (array of item IDs) */ async ItemDelete(params) { await this.ensureInitialized(); const authToken = this.getAuthToken(); if (!authToken) { throw new Error("No authentication token available. Please login first or provide a token in config."); } const response = await this.client.post("/", { jsonrpc: "2.0", method: "item.delete", params, auth: authToken, id: this.requestId++, }); return response.data; } }