sc-zabbix-api
Version:
TypeScript client for Zabbix JSON-RPC API with token authentication
387 lines (386 loc) • 14 kB
JavaScript
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;
}
}