Comprehensive guide to best practices for using the LISA REST API effectively and securely.
// Server-side: Use environment variables
const integratorUID = process.env.LISA_INTEGRATOR_UID;
const accessToken = process.env.LISA_ACCESS_TOKEN;
// Client-side: Use secure storage
const accessToken = await secureStorage.getItem('lisa_access_token');
const refreshToken = await secureStorage.getItem('lisa_refresh_token');
// Never store tokens in localStorage for production
// localStorage.setItem('token', token); // DON'T DO THIS
class TokenManager {
constructor() {
this.accessToken = null;
this.refreshToken = null;
this.tokenExpiry = null;
}
isTokenExpired() {
if (!this.tokenExpiry) return true;
return Date.now() >= this.tokenExpiry;
}
async ensureValidToken() {
if (!this.accessToken || this.isTokenExpired()) {
if (this.refreshToken && !this.isRefreshTokenExpired()) {
await this.refreshAccessToken();
} else {
await this.generateNewTokens();
}
}
}
async refreshAccessToken() {
try {
const response = await fetch('/api/v1/tokens/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken: this.refreshToken })
});
const data = await response.json();
if (data.success) {
this.accessToken = data.data.accessToken;
this.refreshToken = data.data.refreshToken;
this.tokenExpiry = data.data.expiresAt;
}
} catch (error) {
console.error('Token refresh failed:', error);
throw error;
}
}
}
// Production: Always use HTTPS
const baseURL = 'https://api.biocv.info/api/v1';
// Development: Can use HTTP for localhost
const baseURL = process.env.NODE_ENV === 'production'
? 'https://api.biocv.info/api/v1'
: 'http://localhost:3000/api/v1';
function validateAnimalData(animalData) {
const errors = [];
if (!animalData.ID) errors.push('ID is required');
if (!animalData.MAC) errors.push('MAC is required');
if (!['male', 'female'].includes(animalData.Sex)) {
errors.push('Sex must be male or female');
}
if (animalData.Battery < 0 || animalData.Battery > 100) {
errors.push('Battery must be between 0 and 100');
}
return errors;
}
async function getAllAnimals(userId) {
let page = 1;
let allAnimals = [];
let hasMore = true;
while (hasMore) {
try {
const response = await client.getAnimals(userId, page, 100);
allAnimals.push(...response.data);
hasMore = page < response.pagination.totalPages;
page++;
// Add small delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
console.error(`Error fetching page ${page}:`, error);
break;
}
}
return allAnimals;
}
// Good: Reasonable page sizes
const animals = await client.getAnimals(userId, 1, 50);
// Avoid: Very large page sizes
const animals = await client.getAnimals(userId, 1, 1000); // May timeout
// Avoid: Very small page sizes
const animals = await client.getAnimals(userId, 1, 1); // Inefficient
class CachedAPIClient {
constructor(apiClient, cacheTTL = 300000) { // 5 minutes
this.apiClient = apiClient;
this.cache = new Map();
this.cacheTTL = cacheTTL;
}
getCacheKey(endpoint, params = {}) {
return `${endpoint}_${JSON.stringify(params)}`;
}
async getCachedData(endpoint, params, fetchFunction) {
const cacheKey = this.getCacheKey(endpoint, params);
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
return cached.data;
}
const data = await fetchFunction();
this.cache.set(cacheKey, {
data,
timestamp: Date.now()
});
return data;
}
async getAnimals(userId, page = 1, limit = 10) {
return this.getCachedData(
`/users/${userId}/animals`,
{ page, limit },
() => this.apiClient.getAnimals(userId, page, limit)
);
}
}
async function makeRequestWithRetry(url, options = {}, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
}
return response;
} catch (error) {
if (attempt === maxRetries) throw error;
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
function checkRateLimit(response) {
const remaining = response.headers.get('X-RateLimit-Remaining');
const reset = response.headers.get('X-RateLimit-Reset');
if (remaining && parseInt(remaining) < 10) {
console.warn(`Rate limit warning: ${remaining} requests remaining`);
}
return {
remaining: parseInt(remaining) || null,
reset: parseInt(reset) || null
};
}
class RobustAPIClient {
async makeRequest(endpoint, options = {}) {
try {
const response = await fetch(endpoint, options);
const data = await response.json();
if (!data.success) {
// Handle different error types
if (response.status === 401) {
await this.handleTokenExpiry();
return this.makeRequest(endpoint, options); // Retry
}
if (response.status === 429) {
await this.handleRateLimit();
return this.makeRequest(endpoint, options); // Retry
}
throw new APIError(data.error, response.status, data.details);
}
return data;
} catch (error) {
if (error instanceof APIError) throw error;
// Handle network errors
if (error.name === 'TypeError' && error.message.includes('fetch')) {
throw new NetworkError('Network request failed', error);
}
throw new APIError('Unknown error occurred', 500, null, error);
}
}
async handleTokenExpiry() {
try {
await this.refreshToken();
} catch (error) {
await this.generateNewTokens();
}
}
async handleRateLimit() {
const delay = Math.random() * 2000 + 1000; // 1-3 seconds
await new Promise(resolve => setTimeout(resolve, delay));
}
}
class APIError extends Error {
constructor(message, status, details, originalError = null) {
super(message);
this.name = 'APIError';
this.status = status;
this.details = details;
this.originalError = originalError;
}
}
class MonitoredAPIClient {
constructor(apiClient, logger) {
this.apiClient = apiClient;
this.logger = logger;
}
async makeRequest(endpoint, options = {}) {
const startTime = Date.now();
try {
const result = await this.apiClient.makeRequest(endpoint, options);
this.logger.info('API request successful', {
endpoint,
duration: Date.now() - startTime,
status: 'success'
});
return result;
} catch (error) {
this.logger.error('API request failed', {
endpoint,
duration: Date.now() - startTime,
error: error.message,
status: 'error'
});
throw error;
}
}
}
class DataProcessor {
static processAnimals(animals) {
return animals.map(animal => ({
id: animal.ID,
mac: animal.MAC,
group: animal.Group,
sex: animal.Sex,
temperature: animal.CurrentTemperature,
battery: animal.Battery,
suspicious: animal.Suspicious,
lastUpdate: new Date(animal.t)
}));
}
static filterActiveAnimals(animals) {
return animals.filter(animal => animal.Active);
}
static groupByProperty(animals, property) {
return animals.reduce((groups, animal) => {
const key = animal[property];
if (!groups[key]) groups[key] = [];
groups[key].push(animal);
return groups;
}, {});
}
static calculateStatistics(animals) {
const activeAnimals = this.filterActiveAnimals(animals);
return {
total: animals.length,
active: activeAnimals.length,
averageTemperature: activeAnimals.reduce((sum, a) => sum + a.CurrentTemperature, 0) / activeAnimals.length,
lowBattery: activeAnimals.filter(a => a.Battery < 20).length,
suspicious: activeAnimals.filter(a => a.Suspicious).length
};
}
}
class BatchProcessor {
constructor(apiClient, batchSize = 10) {
this.apiClient = apiClient;
this.batchSize = batchSize;
}
async processInBatches(items, processor) {
const results = [];
for (let i = 0; i < items.length; i += this.batchSize) {
const batch = items.slice(i, i + this.batchSize);
try {
const batchResults = await Promise.all(
batch.map(item => processor(item))
);
results.push(...batchResults);
// Add delay between batches
if (i + this.batchSize < items.length) {
await new Promise(resolve => setTimeout(resolve, 100));
}
} catch (error) {
console.error(`Batch ${i / this.batchSize + 1} failed:`, error);
// Continue with next batch
}
}
return results;
}
}
class InputValidator {
static validateMAC(mac) {
const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
return macRegex.test(mac);
}
static validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
static validateUserID(userId) {
return typeof userId === 'string' && userId.length > 0;
}
static sanitizeInput(input) {
if (typeof input === 'string') {
return input.trim().replace(/[<>]/g, '');
}
return input;
}
}
// Use environment variables for sensitive data
const config = {
integratorUID: process.env.LISA_INTEGRATOR_UID,
baseURL: process.env.LISA_API_URL || 'https://api.biocv.info/api/v1',
timeout: parseInt(process.env.LISA_API_TIMEOUT) || 30000,
retryAttempts: parseInt(process.env.LISA_API_RETRIES) || 3
};
// Validate configuration on startup
function validateConfig() {
const required = ['integratorUID'];
const missing = required.filter(key => !config[key]);
if (missing.length > 0) {
throw new Error(`Missing required configuration: ${missing.join(', ')}`);
}
}
// Mock API responses for testing
const mockAPIResponse = {
success: true,
data: [
{
ID: 'test-animal-1',
MAC: 'AA:BB:CC:DD:EE:FF',
Group: 'TestGroup',
Sex: 'female',
Active: true,
CurrentTemperature: 38.5,
Battery: 85
}
],
pagination: {
page: 1,
limit: 10,
total: 1,
totalPages: 1
},
timestamp: '2024-12-01T08:47:16.838Z'
};
// Test API client
describe('LISAPIClient', () => {
let client;
beforeEach(() => {
client = new LISAPIClient('test-integrator');
// Mock fetch
global.fetch = jest.fn();
});
test('should get animals successfully', async () => {
fetch.mockResolvedValueOnce({
json: () => Promise.resolve(mockAPIResponse)
});
const result = await client.getAnimals('user123');
expect(result.success).toBe(true);
expect(result.data).toHaveLength(1);
expect(fetch).toHaveBeenCalledWith(
expect.stringContaining('/users/user123/animals'),
expect.objectContaining({
headers: expect.objectContaining({
'Authorization': expect.stringContaining('Bearer')
})
})
);
});
});
// Test with real API (use test environment)
describe('LISA API Integration', () => {
let client;
beforeAll(async () => {
client = new LISAPIClient(process.env.TEST_INTEGRATOR_UID);
await client.generateTokens();
});
test('should authenticate successfully', async () => {
const response = await client.makeRequest('/tokens/validate');
expect(response.success).toBe(true);
});
test('should handle rate limiting gracefully', async () => {
const promises = Array(10).fill().map(() =>
client.makeRequest('/health')
);
const results = await Promise.allSettled(promises);
const successful = results.filter(r => r.status === 'fulfilled');
expect(successful.length).toBeGreaterThan(0);
});
});
class HealthMonitor {
constructor(apiClient) {
this.apiClient = apiClient;
this.lastHealthCheck = null;
this.healthStatus = 'unknown';
}
async checkHealth() {
try {
const startTime = Date.now();
const response = await this.apiClient.makeRequest('/health');
const duration = Date.now() - startTime;
this.healthStatus = 'healthy';
this.lastHealthCheck = new Date();
return {
status: 'healthy',
duration,
timestamp: this.lastHealthCheck
};
} catch (error) {
this.healthStatus = 'unhealthy';
this.lastHealthCheck = new Date();
return {
status: 'unhealthy',
error: error.message,
timestamp: this.lastHealthCheck
};
}
}
async startMonitoring(intervalMs = 60000) {
setInterval(async () => {
const health = await this.checkHealth();
if (health.status === 'unhealthy') {
console.error('API health check failed:', health);
// Send alert to monitoring service
}
}, intervalMs);
}
}
class PerformanceMonitor {
constructor() {
this.metrics = {
requestCount: 0,
totalDuration: 0,
errorCount: 0,
lastRequest: null
};
}
recordRequest(duration, success) {
this.metrics.requestCount++;
this.metrics.totalDuration += duration;
this.metrics.lastRequest = new Date();
if (!success) {
this.metrics.errorCount++;
}
}
getAverageResponseTime() {
return this.metrics.requestCount > 0
? this.metrics.totalDuration / this.metrics.requestCount
: 0;
}
getErrorRate() {
return this.metrics.requestCount > 0
? this.metrics.errorCount / this.metrics.requestCount
: 0;
}
}
/**
* LISA API Client for livestock monitoring and management
*
* @class LISAPIClient
* @param {string} integratorUID - Your integrator UID from BioCV
* @param {string} baseURL - API base URL (default: production)
*
* @example
* const client = new LISAPIClient('your-integrator-uid');
* const animals = await client.getAnimals('user123');
*/
class LISAPIClient {
/**
* Get all animals for a specific user
*
* @param {string} userId - The user ID
* @param {number} page - Page number (default: 1)
* @param {number} limit - Items per page (default: 10, max: 100)
* @returns {Promise<Object>} API response with animals data
*
* @example
* const animals = await client.getAnimals('user123', 1, 20);
* console.log(`Found ${animals.data.length} animals`);
*/
async getAnimals(userId, page = 1, limit = 10) {
// Implementation...
}
}
Next: Troubleshooting - Complete troubleshooting guide
Diese Seite wird aus dem offiziellen LiSA-API-Wiki erzeugt. Sollte etwas falsch sein oder fehlen, kontaktieren Sie uns — wir korrigieren es an der Quelle.
Wiki-Stand e01e79e · 19.8.2026