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 158c1a5 · 28.7.2026Wir verwenden Cookies und ähnliche Technologien, um diese Website zu betreiben und mit Ihrer Einwilligung für Analysen und (falls zutreffend) Werbung. Sie können alle akzeptieren, alle ablehnen oder Ihre Auswahl anpassen. Einzelheiten finden Sie in unserer Datenschutzerklärung und Cookie-Richtlinie.
Kategorien: Notwendig (Immer aktiv), Funktional, Analytik, MarketingHallo! Brauchen Sie Hilfe? Chatten Sie mit mir!