Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Implementacja aplikacji z wykorzystaniem poznanych technik

Witaj w końcowej fazie naszej przygody w Parku Jurajskim! W tym ćwiczeniu połączymy wszystkie poznane dotychczas techniki, aby stworzyć kompletną aplikację do monitorowania i zarządzania ekosystemem Parku Jurajskiego. To nasz "wielki finał" przed przejściem do strukturyzacji większych projektów.

Cel projektu

Stworzymy aplikację "JurassicTracker" - system do monitorowania, śledzenia i zarządzania dinozaurami w parku. Nasza aplikacja będzie wykorzystywać:

  1. Modularny kod (ES Modules)
  2. Asynchroniczne operacje (Promises, async/await)
  3. Obsługę błędów (try/catch, własne klasy błędów)
  4. Serwisy działające w tle (setInterval)
  5. Zarządzanie stanem aplikacji

Struktura projektu

Zanim przejdziemy do implementacji, zaplanujmy strukturę naszego projektu:

1jurassic-tracker/
2├── index.js               # Główny plik aplikacji
3├── modules/
4│   ├── api.js             # Moduł komunikacji z API
5│   ├── dinosaurs.js       # Moduł zarządzania dinozaurami
6│   ├── enclosures.js      # Moduł zarządzania wybiegami
7│   ├── security.js        # Moduł systemów bezpieczeństwa
8│   ├── monitoring.js      # Moduł monitorowania parku
9│   ├── utils.js           # Narzędzia pomocnicze
10│   └── errors/            # Katalog z klasami błędów
11│       ├── index.js       # Eksportuje wszystkie błędy
12│       ├── APIError.js    # Błędy związane z API
13│       ├── DinoError.js   # Błędy związane z dinozaurami
14│       └── SecurityError.js # Błędy związane z bezpieczeństwem
15└── data/
16    └── config.js          # Konfiguracja aplikacji

Implementacja modułów

Zacznijmy od zaimplementowania każdego z modułów. Pamiętaj, że w praktyce te pliki znalazłyby się w oddzielnych plikach zgodnie z powyższą strukturą.

1. Moduł błędów (errors/index.js)

Najpierw zdefiniujmy nasze niestandardowe klasy błędów:

1// errors/APIError.js
2export class APIError extends Error {
3  constructor(message, statusCode, endpoint) {
4    super(message);
5    this.name = 'APIError';
6    this.statusCode = statusCode;
7    this.endpoint = endpoint;
8    this.timestamp = new Date();
9  }
10
11  isServerError() {
12    return this.statusCode >= 500;
13  }
14
15  isClientError() {
16    return this.statusCode >= 400 && this.statusCode < 500;
17  }
18}
19
20// errors/DinoError.js
21export class DinoError extends Error {
22  constructor(message, dinoId, species) {
23    super(message);
24    this.name = 'DinoError';
25    this.dinoId = dinoId;
26    this.species = species;
27    this.timestamp = new Date();
28  }
29}
30
31export class DinoNotFoundError extends DinoError {
32  constructor(dinoId) {
33    super(`Dinozaur o ID ${dinoId} nie został znaleziony`, dinoId, null);
34    this.name = 'DinoNotFoundError';
35  }
36}
37
38export class DinoBehaviorError extends DinoError {
39  constructor(message, dinoId, species, behaviorType, dangerLevel) {
40    super(message, dinoId, species);
41    this.name = 'DinoBehaviorError';
42    this.behaviorType = behaviorType;
43    this.dangerLevel = dangerLevel; // 1-10
44  }
45
46  requiresIntervention() {
47    return this.dangerLevel >= 7;
48  }
49}
50
51// errors/SecurityError.js
52export class SecurityError extends Error {
53  constructor(message, sector, systemType) {
54    super(message);
55    this.name = 'SecurityError';
56    this.sector = sector;
57    this.systemType = systemType;
58    this.timestamp = new Date();
59  }
60}
61
62export class EnclosureBreachError extends SecurityError {
63  constructor(sector, dinoSpecies, count) {
64    super(`Naruszenie wybiegu w sektorze ${sector}: ${count} okazów ${dinoSpecies}`, sector, 'enclosure');
65    this.name = 'EnclosureBreachError';
66    this.dinoSpecies = dinoSpecies;
67    this.count = count;
68  }
69}
70
71// errors/index.js
72export * from './APIError.js';
73export * from './DinoError.js';
74export * from './SecurityError.js';

2. Moduł API (api.js)

Nasz moduł API będzie odpowiedzialny za komunikację z (hipotetycznym) serwerem Parku Jurajskiego:

1// modules/api.js
2import { APIError } from '../errors/index.js';
3
4// Symulacja bazy danych (w rzeczywistości byłoby to API)
5const API_DELAY = 500; // ms - symulacja opóźnienia sieciowego
6const API_ERROR_RATE = 0.1; // 10% szans na błąd API dla realizmu
7
8// Symulowane dane dinozaurów
9let dinosaursData = [
10  { id: 'trx1', name: 'Rex', species: 'Tyrannosaurus', enclosureId: 'enc1', health: 98, lastFed: new Date(Date.now() - 3600000), behavior: 'calm' },
11  { id: 'rap1', name: 'Blue', species: 'Velociraptor', enclosureId: 'enc2', health: 95, lastFed: new Date(Date.now() - 7200000), behavior: 'agitated' },
12  { id: 'rap2', name: 'Delta', species: 'Velociraptor', enclosureId: 'enc2', health: 92, lastFed: new Date(Date.now() - 7200000), behavior: 'hunting' },
13  { id: 'tri1', name: 'Sarah', species: 'Triceratops', enclosureId: 'enc3', health: 88, lastFed: new Date(Date.now() - 1800000), behavior: 'sleeping' },
14  { id: 'bro1', name: 'Bronty', species: 'Brontosaurus', enclosureId: 'enc4', health: 100, lastFed: new Date(Date.now() - 900000), behavior: 'grazing' },
15];
16
17// Symulowane dane wybiegów
18let enclosuresData = [
19  { id: 'enc1', name: 'T-Rex Paddock', sector: 'A1', powerStatus: 'active', securityLevel: 10, lastInspection: new Date(Date.now() - 86400000) },
20  { id: 'enc2', name: 'Raptor Containment', sector: 'B2', powerStatus: 'active', securityLevel: 9, lastInspection: new Date(Date.now() - 129600000) },
21  { id: 'enc3', name: 'Triceratops Territory', sector: 'C3', powerStatus: 'active', securityLevel: 6, lastInspection: new Date(Date.now() - 172800000) },
22  { id: 'enc4', name: 'Herbivore Valley', sector: 'D4', powerStatus: 'active', securityLevel: 4, lastInspection: new Date(Date.now() - 259200000) },
23];
24
25// Funkcja pomocnicza symulująca opóźnienie sieciowe
26const simulateNetworkDelay = () => {
27  return new Promise(resolve => setTimeout(resolve, API_DELAY * (0.5 + Math.random())));
28};
29
30// Funkcja pomocnicza symulująca błędy API
31const simulateApiError = (endpoint) => {
32  if (Math.random() < API_ERROR_RATE) {
33    const errorTypes = [
34      { statusCode: 500, message: 'Internal Server Error' },
35      { statusCode: 503, message: 'Service Unavailable' },
36      { statusCode: 429, message: 'Too Many Requests' },
37      { statusCode: 403, message: 'Forbidden - Insufficient permissions' }
38    ];
39    const errorType = errorTypes[Math.floor(Math.random() * errorTypes.length)];
40    throw new APIError(`API Error: ${errorType.message}`, errorType.statusCode, endpoint);
41  }
42};
43
44// API do pobierania danych o dinozaurach
45export async function getDinosaurs() {
46  const endpoint = '/api/dinosaurs';
47  try {
48    await simulateNetworkDelay();
49    simulateApiError(endpoint);
50    return [...dinosaursData]; // Zwracamy kopię, aby uniknąć mutacji
51  } catch (error) {
52    if (error instanceof APIError) {
53      throw error;
54    }
55    throw new APIError('Błąd podczas pobierania danych o dinozaurach', 500, endpoint);
56  }
57}
58
59export async function getDinosaurById(id) {
60  const endpoint = `/api/dinosaurs/${id}`;
61  try {
62    await simulateNetworkDelay();
63    simulateApiError(endpoint);
64    const dino = dinosaursData.find(d => d.id === id);
65    if (!dino) {
66      throw new APIError(`Dinozaur o ID ${id} nie został znaleziony`, 404, endpoint);
67    }
68    return { ...dino }; // Zwracamy kopię
69  } catch (error) {
70    if (error instanceof APIError) {
71      throw error;
72    }
73    throw new APIError(`Błąd podczas pobierania dinozaura ${id}`, 500, endpoint);
74  }
75}
76
77export async function updateDinosaur(id, updates) {
78  const endpoint = `/api/dinosaurs/${id}`;
79  try {
80    await simulateNetworkDelay();
81    simulateApiError(endpoint);
82    const dinoIndex = dinosaursData.findIndex(d => d.id === id);
83    if (dinoIndex === -1) {
84      throw new APIError(`Dinozaur o ID ${id} nie został znaleziony`, 404, endpoint);
85    }
86
87    // Aktualizacja danych
88    dinosaursData[dinoIndex] = {
89      ...dinosaursData[dinoIndex],
90      ...updates,
91      id // Upewniamy się, że ID się nie zmienia
92    };
93
94    return { ...dinosaursData[dinoIndex] };
95  } catch (error) {
96    if (error instanceof APIError) {
97      throw error;
98    }
99    throw new APIError(`Błąd podczas aktualizacji dinozaura ${id}`, 500, endpoint);
100  }
101}
102
103// API do pobierania danych o wybiegach
104export async function getEnclosures() {
105  const endpoint = '/api/enclosures';
106  try {
107    await simulateNetworkDelay();
108    simulateApiError(endpoint);
109    return [...enclosuresData];
110  } catch (error) {
111    if (error instanceof APIError) {
112      throw error;
113    }
114    throw new APIError('Błąd podczas pobierania danych o wybiegach', 500, endpoint);
115  }
116}
117
118export async function getEnclosureById(id) {
119  const endpoint = `/api/enclosures/${id}`;
120  try {
121    await simulateNetworkDelay();
122    simulateApiError(endpoint);
123    const enclosure = enclosuresData.find(e => e.id === id);
124    if (!enclosure) {
125      throw new APIError(`Wybieg o ID ${id} nie został znaleziony`, 404, endpoint);
126    }
127    return { ...enclosure };
128  } catch (error) {
129    if (error instanceof APIError) {
130      throw error;
131    }
132    throw new APIError(`Błąd podczas pobierania wybiegu ${id}`, 500, endpoint);
133  }
134}
135
136export async function updateEnclosure(id, updates) {
137  const endpoint = `/api/enclosures/${id}`;
138  try {
139    await simulateNetworkDelay();
140    simulateApiError(endpoint);
141    const enclosureIndex = enclosuresData.findIndex(e => e.id === id);
142    if (enclosureIndex === -1) {
143      throw new APIError(`Wybieg o ID ${id} nie został znaleziony`, 404, endpoint);
144    }
145
146    // Aktualizacja danych
147    enclosuresData[enclosureIndex] = {
148      ...enclosuresData[enclosureIndex],
149      ...updates,
150      id
151    };
152
153    return { ...enclosuresData[enclosureIndex] };
154  } catch (error) {
155    if (error instanceof APIError) {
156      throw error;
157    }
158    throw new APIError(`Błąd podczas aktualizacji wybiegu ${id}`, 500, endpoint);
159  }
160}
161
162// API do logowania zdarzeń
163export async function logEvent(eventType, details) {
164  const endpoint = '/api/events';
165  try {
166    await simulateNetworkDelay();
167    simulateApiError(endpoint);
168
169    // W rzeczywistości wysłalibyśmy zdarzenie do serwera
170    console.log(`[EVENT] ${eventType}:`, details);
171
172    return { success: true, timestamp: new Date() };
173  } catch (error) {
174    if (error instanceof APIError) {
175      throw error;
176    }
177    throw new APIError('Błąd podczas logowania zdarzenia', 500, endpoint);
178  }
179}

3. Moduł dinozaurów (dinosaurs.js)

Ten moduł będzie odpowiedzialny za zarządzanie danymi o dinozaurach i ich zachowaniami:

1// modules/dinosaurs.js
2import { getDinosaurs, getDinosaurById, updateDinosaur, logEvent } from './api.js';
3import { DinoNotFoundError, DinoBehaviorError } from '../errors/index.js';
4
5// Ocena ryzyka zachowania dinozaura
6function assessBehaviorRisk(dino) {
7  const dangerousSpecies = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
8  const riskBehaviors = {
9    'calm': 1,
10    'grazing': 1,
11    'sleeping': 2,
12    'exploring': 4,
13    'agitated': 6,
14    'hunting': 7,
15    'aggressive': 9
16  };
17
18  // Bazowy poziom ryzyka zależy od gatunku
19  let baseRisk = dangerousSpecies.includes(dino.species) ? 5 : 2;
20
21  // Modyfikacja ryzyka w zależności od zachowania
22  const behaviorRisk = riskBehaviors[dino.behavior] || 3;
23
24  // Modyfikacja ryzyka w zależności od czasu od ostatniego karmienia
25  const hoursSinceLastFed = (Date.now() - new Date(dino.lastFed).getTime()) / 3600000;
26  const hungerRisk = Math.min(5, Math.floor(hoursSinceLastFed / 3));
27
28  // Całkowity poziom ryzyka (1-10)
29  const totalRisk = Math.min(10, baseRisk + behaviorRisk + hungerRisk);
30
31  return {
32    risk: totalRisk,
33    factors: {
34      species: baseRisk,
35      behavior: behaviorRisk,
36      hunger: hungerRisk
37    }
38  };
39}
40
41// Pobieranie wszystkich dinozaurów
42export async function getAllDinosaurs() {
43  try {
44    const dinosaurs = await getDinosaurs();
45
46    // Wzbogacamy dane o ocenę ryzyka
47    return dinosaurs.map(dino => ({
48      ...dino,
49      riskAssessment: assessBehaviorRisk(dino)
50    }));
51  } catch (error) {
52    console.error('Błąd podczas pobierania listy dinozaurów:', error);
53    throw error;
54  }
55}
56
57// Pobieranie konkretnego dinozaura po ID
58export async function getDinosaur(id) {
59  try {
60    const dino = await getDinosaurById(id);
61    return {
62      ...dino,
63      riskAssessment: assessBehaviorRisk(dino)
64    };
65  } catch (error) {
66    if (error.statusCode === 404) {
67      throw new DinoNotFoundError(id);
68    }
69    console.error(`Błąd podczas pobierania dinozaura ${id}:`, error);
70    throw error;
71  }
72}
73
74// Karmienie dinozaura
75export async function feedDinosaur(id) {
76  try {
77    const dino = await getDinosaur(id);
78
79    // Aktualizacja stanu dinozaura
80    const updatedDino = await updateDinosaur(id, {
81      lastFed: new Date(),
82      health: Math.min(100, dino.health + 5),
83      behavior: 'calm' // Dinozaury są spokojniejsze po karmieniu
84    });
85
86    // Logowanie zdarzenia
87    await logEvent('FEEDING', {
88      dinoId: id,
89      species: dino.species,
90      name: dino.name,
91      previousFeedingTime: dino.lastFed
92    });
93
94    return {
95      ...updatedDino,
96      riskAssessment: assessBehaviorRisk(updatedDino)
97    };
98  } catch (error) {
99    console.error(`Błąd podczas karmienia dinozaura ${id}:`, error);
100    throw error;
101  }
102}
103
104// Monitorowanie zachowania dinozaura
105export async function monitorBehavior(id) {
106  try {
107    const dino = await getDinosaur(id);
108    const { risk, factors } = assessBehaviorRisk(dino);
109
110    // Sprawdzamy, czy zachowanie wymaga interwencji
111    if (risk >= 8) {
112      const error = new DinoBehaviorError(
113        `Niebezpieczne zachowanie dinozaura ${dino.name}`,
114        id,
115        dino.species,
116        dino.behavior,
117        risk
118      );
119
120      // Logowanie zdarzenia wysokiego ryzyka
121      await logEvent('HIGH_RISK_BEHAVIOR', {
122        dinoId: id,
123        species: dino.species,
124        name: dino.name,
125        behavior: dino.behavior,
126        risk,
127        factors
128      });
129
130      throw error;
131    }
132
133    // Dla średniego ryzyka tylko logujemy
134    if (risk >= 5) {
135      await logEvent('MEDIUM_RISK_BEHAVIOR', {
136        dinoId: id,
137        species: dino.species,
138        name: dino.name,
139        behavior: dino.behavior,
140        risk,
141        factors
142      });
143    }
144
145    return { dino, risk, factors };
146  } catch (error) {
147    if (error instanceof DinoBehaviorError) {
148      throw error; // Przekazujemy dalej nasz własny błąd
149    }
150    console.error(`Błąd podczas monitorowania dinozaura ${id}:`, error);
151    throw error;
152  }
153}
154
155// Symulacja zmiany zachowania (używana przez moduł monitorowania)
156export async function simulateBehaviorChange() {
157  try {
158    const allDinos = await getAllDinosaurs();
159    const randomIndex = Math.floor(Math.random() * allDinos.length);
160    const targetDino = allDinos[randomIndex];
161
162    // Możliwe zachowania zależne od gatunku i ostatniego karmienia
163    const possibleBehaviors = ['calm', 'grazing', 'sleeping', 'exploring', 'agitated', 'hunting', 'aggressive'];
164    const carnivores = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
165
166    let behaviorPool;
167    if (carnivores.includes(targetDino.species)) {
168      // Drapieżniki mają większe szanse na agresywne zachowania
169      behaviorPool = ['calm', 'sleeping', 'exploring', 'agitated', 'hunting', 'aggressive'];
170    } else {
171      // Roślinożercy są generalnie spokojniejsi
172      behaviorPool = ['calm', 'grazing', 'sleeping', 'exploring', 'agitated'];
173    }
174
175    // Głodne dinozaury są bardziej agresywne
176    const hoursSinceLastFed = (Date.now() - new Date(targetDino.lastFed).getTime()) / 3600000;
177    if (hoursSinceLastFed > 8) {
178      if (carnivores.includes(targetDino.species)) {
179        behaviorPool = ['agitated', 'hunting', 'aggressive'];
180      } else {
181        behaviorPool = ['agitated', 'exploring'];
182      }
183    }
184
185    // Losowy wybór nowego zachowania
186    const newBehavior = behaviorPool[Math.floor(Math.random() * behaviorPool.length)];
187
188    // Aktualizacja stanu dinozaura
189    const updatedDino = await updateDinosaur(targetDino.id, {
190      behavior: newBehavior
191    });
192
193    await logEvent('BEHAVIOR_CHANGE', {
194      dinoId: targetDino.id,
195      species: targetDino.species,
196      name: targetDino.name,
197      previousBehavior: targetDino.behavior,
198      newBehavior
199    });
200
201    return {
202      ...updatedDino,
203      riskAssessment: assessBehaviorRisk(updatedDino)
204    };
205  } catch (error) {
206    console.error('Błąd podczas symulacji zmiany zachowania:', error);
207    throw error;
208  }
209}

4. Moduł wybiegów (enclosures.js)

Ten moduł będzie zarządzał wybiegami i ich stanem bezpieczeństwa:

1// modules/enclosures.js
2import { getEnclosures, getEnclosureById, updateEnclosure, logEvent } from './api.js';
3import { SecurityError, EnclosureBreachError } from '../errors/index.js';
4import { getAllDinosaurs } from './dinosaurs.js';
5
6// Ocena stanu bezpieczeństwa wybiegu
7function assessSecurityStatus(enclosure, dinosaurs) {
8  // Sprawdzamy, ile dinozaurów jest w wybiegu
9  const enclosureDinosaurs = dinosaurs.filter(dino => dino.enclosureId === enclosure.id);
10
11  // Sprawdzamy, czy są drapieżniki
12  const carnivores = enclosureDinosaurs.filter(dino =>
13    ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'].includes(dino.species)
14  );
15
16  // Sprawdzamy łączne ryzyko
17  const totalRisk = enclosureDinosaurs.reduce((sum, dino) =>
18    sum + (dino.riskAssessment ? dino.riskAssessment.risk : 3), 0);
19
20  // Stan zasilania
21  const powerStatus = enclosure.powerStatus === 'active' ? 10 :
22                       enclosure.powerStatus === 'backup' ? 5 : 0;
23
24  // Czas od ostatniej inspekcji (w dniach)
25  const daysSinceInspection = (Date.now() - new Date(enclosure.lastInspection).getTime()) / (24 * 3600000);
26  const inspectionScore = daysSinceInspection < 1 ? 10 :
27                          daysSinceInspection < 3 ? 7 :
28                          daysSinceInspection < 7 ? 4 : 1;
29
30  // Obliczenie ogólnego wskaźnika bezpieczeństwa (0-100)
31  const securityScore = Math.max(0, Math.min(100,
32    enclosure.securityLevel * 10 - totalRisk + powerStatus + inspectionScore
33  ));
34
35  return {
36    id: enclosure.id,
37    name: enclosure.name,
38    sector: enclosure.sector,
39    dinosaurCount: enclosureDinosaurs.length,
40    carnivoreCount: carnivores.length,
41    securityLevel: enclosure.securityLevel,
42    powerStatus: enclosure.powerStatus,
43    securityScore,
44    status: securityScore >= 80 ? 'secure' :
45            securityScore >= 60 ? 'normal' :
46            securityScore >= 40 ? 'warning' : 'danger',
47    dinosaurs: enclosureDinosaurs.map(d => ({
48      id: d.id,
49      name: d.name,
50      species: d.species,
51      risk: d.riskAssessment ? d.riskAssessment.risk : 3
52    }))
53  };
54}
55
56// Pobieranie wszystkich wybiegów z oceną bezpieczeństwa
57export async function getAllEnclosures() {
58  try {
59    const [enclosures, dinosaurs] = await Promise.all([
60      getEnclosures(),
61      getAllDinosaurs()
62    ]);
63
64    // Wzbogacamy dane o ocenę bezpieczeństwa
65    return enclosures.map(enclosure =>
66      assessSecurityStatus(enclosure, dinosaurs)
67    );
68  } catch (error) {
69    console.error('Błąd podczas pobierania listy wybiegów:', error);
70    throw error;
71  }
72}
73
74// Pobieranie konkretnego wybiegu po ID
75export async function getEnclosure(id) {
76  try {
77    const [enclosure, dinosaurs] = await Promise.all([
78      getEnclosureById(id),
79      getAllDinosaurs()
80    ]);
81
82    return assessSecurityStatus(enclosure, dinosaurs);
83  } catch (error) {
84    console.error(`Błąd podczas pobierania wybiegu ${id}:`, error);
85    throw error;
86  }
87}
88
89// Przeprowadzenie inspekcji wybiegu
90export async function inspectEnclosure(id) {
91  try {
92    const enclosure = await getEnclosureById(id);
93
94    // Aktualizacja daty ostatniej inspekcji
95    const updatedEnclosure = await updateEnclosure(id, {
96      lastInspection: new Date()
97    });
98
99    // Logowanie zdarzenia
100    await logEvent('ENCLOSURE_INSPECTION', {
101      enclosureId: id,
102      name: enclosure.name,
103      sector: enclosure.sector,
104      previousInspection: enclosure.lastInspection
105    });
106
107    // Pobieramy dinozaury, aby zaktualizować ocenę bezpieczeństwa
108    const dinosaurs = await getAllDinosaurs();
109
110    return assessSecurityStatus(updatedEnclosure, dinosaurs);
111  } catch (error) {
112    console.error(`Błąd podczas inspekcji wybiegu ${id}:`, error);
113    throw error;
114  }
115}
116
117// Symulacja awarii systemu zasilania
118export async function simulatePowerFailure() {
119  try {
120    const enclosures = await getEnclosures();
121
122    // Wybieramy losowy wybieg
123    const randomIndex = Math.floor(Math.random() * enclosures.length);
124    const targetEnclosure = enclosures[randomIndex];
125
126    if (targetEnclosure.powerStatus !== 'active') {
127      // Wybieg już ma problem z zasilaniem
128      return;
129    }
130
131    // 70% szans na przejście na zasilanie awaryjne, 30% na całkowitą awarię
132    const newPowerStatus = Math.random() < 0.7 ? 'backup' : 'failure';
133
134    // Aktualizacja stanu wybiegu
135    const updatedEnclosure = await updateEnclosure(targetEnclosure.id, {
136      powerStatus: newPowerStatus
137    });
138
139    // Logowanie zdarzenia
140    await logEvent('POWER_FAILURE', {
141      enclosureId: targetEnclosure.id,
142      name: targetEnclosure.name,
143      sector: targetEnclosure.sector,
144      powerStatus: newPowerStatus
145    });
146
147    // Sprawdzenie, czy awaria może prowadzić do naruszenia wybiegu
148    if (newPowerStatus === 'failure') {
149      const dinosaurs = await getAllDinosaurs();
150      const enclosureDinos = dinosaurs.filter(dino => dino.enclosureId === targetEnclosure.id);
151
152      // Ocena ryzyka naruszenia (zależy od gatunków dinozaurów)
153      const dangerousSpecies = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
154      const hasDangerousDinos = enclosureDinos.some(dino =>
155        dangerousSpecies.includes(dino.species)
156      );
157
158      if (hasDangerousDinos && targetEnclosure.securityLevel >= 8) {
159        // Wysokie ryzyko naruszenia wybiegu
160        const breachingSpecies = enclosureDinos.find(dino =>
161          dangerousSpecies.includes(dino.species)
162        ).species;
163
164        const breachingCount = enclosureDinos.filter(dino =>
165          dino.species === breachingSpecies
166        ).length;
167
168        throw new EnclosureBreachError(
169          targetEnclosure.sector,
170          breachingSpecies,
171          breachingCount
172        );
173      }
174    }
175
176    const dinosaurs = await getAllDinosaurs();
177    return assessSecurityStatus(updatedEnclosure, dinosaurs);
178  } catch (error) {
179    if (error instanceof EnclosureBreachError) {
180      // Logujemy zdarzenie naruszenia wybiegu
181      await logEvent('ENCLOSURE_BREACH', {
182        sector: error.sector,
183        species: error.dinoSpecies,
184        count: error.count,
185        timestamp: error.timestamp
186      });
187      throw error; // Przekazujemy błąd dalej
188    }
189
190    console.error('Błąd podczas symulacji awarii zasilania:', error);
191    throw error;
192  }
193}
194
195// Naprawa systemu zasilania
196export async function repairPowerSystem(id) {
197  try {
198    const enclosure = await getEnclosureById(id);
199
200    if (enclosure.powerStatus === 'active') {
201      throw new SecurityError(
202        'System zasilania jest już aktywny',
203        enclosure.sector,
204        'power'
205      );
206    }
207
208    // Aktualizacja stanu zasilania
209    const updatedEnclosure = await updateEnclosure(id, {
210      powerStatus: 'active'
211    });
212
213    // Logowanie zdarzenia
214    await logEvent('POWER_RESTORED', {
215      enclosureId: id,
216      name: enclosure.name,
217      sector: enclosure.sector,
218      previousStatus: enclosure.powerStatus
219    });
220
221    // Pobieramy dinozaury, aby zaktualizować ocenę bezpieczeństwa
222    const dinosaurs = await getAllDinosaurs();
223
224    return assessSecurityStatus(updatedEnclosure, dinosaurs);
225  } catch (error) {
226    console.error(`Błąd podczas naprawy systemu zasilania wybiegu ${id}:`, error);
227    throw error;
228  }
229}

5. Moduł monitorowania (monitoring.js)

Ten moduł będzie odpowiedzialny za ciągłe monitorowanie parku w tle:

1// modules/monitoring.js
2import { getAllDinosaurs, monitorBehavior, simulateBehaviorChange } from './dinosaurs.js';
3import { getAllEnclosures, simulatePowerFailure } from './enclosures.js';
4import { logEvent } from './api.js';
5import { DinoBehaviorError, EnclosureBreachError } from '../errors/index.js';
6
7// Przechowujemy identyfikatory interwałów, aby móc je później zatrzymać
8const intervals = new Map();
9
10// Aktywne alarmy
11const activeAlerts = new Map();
12
13// Rejestratory dla alertów
14const alertListeners = [];
15
16// Status systemu monitorowania
17let monitoringStatus = 'idle'; // 'idle', 'running', 'error'
18
19// Dodawanie listenera dla alertów
20export function addAlertListener(callback) {
21  alertListeners.push(callback);
22  return function removeListener() {
23    const index = alertListeners.indexOf(callback);
24    if (index !== -1) {
25      alertListeners.splice(index, 1);
26      return true;
27    }
28    return false;
29  };
30}
31
32// Generowanie alertu
33function triggerAlert(alertType, data) {
34  const alertId = `${alertType}-${Date.now()}`;
35  const alert = {
36    id: alertId,
37    type: alertType,
38    timestamp: new Date(),
39    data,
40    resolved: false
41  };
42
43  // Dodajemy do aktywnych alertów
44  activeAlerts.set(alertId, alert);
45
46  // Powiadamiamy wszystkich słuchaczy
47  alertListeners.forEach(listener => {
48    try {
49      listener(alert);
50    } catch (error) {
51      console.error('Błąd w listenerze alertów:', error);
52    }
53  });
54
55  return alertId;
56}
57
58// Oznaczanie alertu jako rozwiązanego
59export function resolveAlert(alertId) {
60  if (!activeAlerts.has(alertId)) {
61    throw new Error(`Alert o ID ${alertId} nie istnieje`);
62  }
63
64  const alert = activeAlerts.get(alertId);
65  alert.resolved = true;
66  alert.resolutionTime = new Date();
67
68  // Powiadamiamy wszystkich słuchaczy
69  alertListeners.forEach(listener => {
70    try {
71      listener({ ...alert, status: 'resolved' });
72    } catch (error) {
73      console.error('Błąd w listenerze alertów:', error);
74    }
75  });
76
77  // Po 5 minutach usuwamy rozwiązany alert z pamięci
78  setTimeout(() => {
79    activeAlerts.delete(alertId);
80  }, 5 * 60 * 1000);
81
82  return alert;
83}
84
85// Pobieranie wszystkich aktywnych alertów
86export function getActiveAlerts() {
87  return Array.from(activeAlerts.values())
88    .filter(alert => !alert.resolved)
89    .sort((a, b) => b.timestamp - a.timestamp);
90}
91
92// Uruchomienie monitorowania zachowań dinozaurów
93function startDinoBehaviorMonitoring(interval = 60000) {
94  if (intervals.has('dinoBehavior')) {
95    clearInterval(intervals.get('dinoBehavior'));
96  }
97
98  // Funkcja monitorująca wszystkie dinozaury
99  async function monitorAllDinosaurs() {
100    try {
101      const allDinos = await getAllDinosaurs();
102
103      // Monitorujemy każdego dinozaura
104      const monitorPromises = allDinos.map(dino =>
105        monitorBehavior(dino.id).catch(error => {
106          if (error instanceof DinoBehaviorError && error.requiresIntervention()) {
107            // Generujemy alert dla niebezpiecznego zachowania
108            triggerAlert('DANGEROUS_BEHAVIOR', {
109              dinoId: error.dinoId,
110              species: error.species,
111              behavior: error.behaviorType,
112              dangerLevel: error.dangerLevel,
113              message: error.message
114            });
115          }
116          return { error, dino };
117        })
118      );
119
120      await Promise.all(monitorPromises);
121
122      // Symulujemy losową zmianę zachowania (dla jednego dinozaura)
123      if (Math.random() < 0.3) { // 30% szans na zmianę w każdym cyklu
124        await simulateBehaviorChange();
125      }
126
127    } catch (error) {
128      console.error('Błąd podczas monitorowania dinozaurów:', error);
129      monitoringStatus = 'error';
130
131      // Automatyczne ponowne uruchomienie po błędzie
132      setTimeout(() => {
133        if (monitoringStatus === 'error') {
134          monitoringStatus = 'running';
135        }
136      }, 5000);
137    }
138  }
139
140  // Pierwsze uruchomienie natychmiast
141  monitorAllDinosaurs();
142
143  // Następnie uruchamiamy cyklicznie
144  const intervalId = setInterval(monitorAllDinosaurs, interval);
145  intervals.set('dinoBehavior', intervalId);
146
147  return intervalId;
148}
149
150// Uruchomienie monitorowania zabezpieczeń wybiegów
151function startEnclosureMonitoring(interval = 90000) {
152  if (intervals.has('enclosureSecurity')) {
153    clearInterval(intervals.get('enclosureSecurity'));
154  }
155
156  // Funkcja monitorująca stan wybiegów
157  async function monitorAllEnclosures() {
158    try {
159      const allEnclosures = await getAllEnclosures();
160
161      // Sprawdzamy stan bezpieczeństwa każdego wybiegu
162      for (const enclosure of allEnclosures) {
163        if (enclosure.status === 'danger') {
164          // Generujemy alert dla niebezpiecznego stanu wybiegu
165          triggerAlert('ENCLOSURE_SECURITY', {
166            enclosureId: enclosure.id,
167            name: enclosure.name,
168            sector: enclosure.sector,
169            securityScore: enclosure.securityScore,
170            powerStatus: enclosure.powerStatus,
171            dinosaurCount: enclosure.dinosaurCount
172          });
173        }
174      }
175
176      // Symulujemy losowe awarie zasilania
177      if (Math.random() < 0.2) { // 20% szans na awarię w każdym cyklu
178        try {
179          await simulatePowerFailure();
180        } catch (error) {
181          if (error instanceof EnclosureBreachError) {
182            // Generujemy alert dla naruszenia wybiegu
183            triggerAlert('ENCLOSURE_BREACH', {
184              sector: error.sector,
185              species: error.dinoSpecies,
186              count: error.count,
187              timestamp: error.timestamp,
188              message: error.message
189            });
190          }
191          throw error;
192        }
193      }
194
195    } catch (error) {
196      if (!(error instanceof EnclosureBreachError)) {
197        console.error('Błąd podczas monitorowania wybiegów:', error);
198        monitoringStatus = 'error';
199
200        // Automatyczne ponowne uruchomienie po błędzie
201        setTimeout(() => {
202          if (monitoringStatus === 'error') {
203            monitoringStatus = 'running';
204          }
205        }, 5000);
206      }
207    }
208  }
209
210  // Pierwsze uruchomienie po 10 sekundach (aby nie nakładało się z monitorowaniem dinozaurów)
211  setTimeout(() => {
212    monitorAllEnclosures();
213  }, 10000);
214
215  // Następnie uruchamiamy cyklicznie
216  const intervalId = setInterval(monitorAllEnclosures, interval);
217  intervals.set('enclosureSecurity', intervalId);
218
219  return intervalId;
220}
221
222// Uruchomienie systemu monitorowania
223export function startMonitoring(options = {}) {
224  if (monitoringStatus === 'running') {
225    console.log('System monitorowania jest już uruchomiony.');
226    return;
227  }
228
229  monitoringStatus = 'running';
230
231  // Ustawienia domyślne
232  const settings = {
233    dinoBehaviorInterval: 60000, // 1 minuta
234    enclosureSecurityInterval: 90000, // 1.5 minuty
235    ...options
236  };
237
238  // Uruchamiamy monitorowanie dinozaurów
239  startDinoBehaviorMonitoring(settings.dinoBehaviorInterval);
240
241  // Uruchamiamy monitorowanie wybiegów
242  startEnclosureMonitoring(settings.enclosureSecurityInterval);
243
244  // Logujemy uruchomienie
245  logEvent('MONITORING_STARTED', {
246    timestamp: new Date(),
247    settings
248  }).catch(error => console.error('Błąd podczas logowania zdarzenia:', error));
249
250  return monitoringStatus;
251}
252
253// Zatrzymanie systemu monitorowania
254export function stopMonitoring() {
255  if (monitoringStatus !== 'running') {
256    console.log('System monitorowania nie jest uruchomiony.');
257    return;
258  }
259
260  // Zatrzymujemy wszystkie interwały
261  for (const [name, intervalId] of intervals.entries()) {
262    clearInterval(intervalId);
263    intervals.delete(name);
264  }
265
266  monitoringStatus = 'idle';
267
268  // Logujemy zatrzymanie
269  logEvent('MONITORING_STOPPED', {
270    timestamp: new Date()
271  }).catch(error => console.error('Błąd podczas logowania zdarzenia:', error));
272
273  return monitoringStatus;
274}
275
276// Zwracanie statusu systemu monitorowania
277export function getMonitoringStatus() {
278  return {
279    status: monitoringStatus,
280    activeMonitors: Array.from(intervals.keys()),
281    alertsCount: getActiveAlerts().length
282  };
283}

6. Moduł pomocniczy (utils.js)

Ten moduł będzie zawierał przydatne funkcje pomocnicze:

1// modules/utils.js
2
3// Formatowanie daty
4export function formatDate(date) {
5  if (!date) return 'N/A';
6
7  const d = new Date(date);
8  if (isNaN(d.getTime())) return 'Invalid Date';
9
10  return d.toLocaleString('pl-PL', {
11    year: 'numeric',
12    month: '2-digit',
13    day: '2-digit',
14    hour: '2-digit',
15    minute: '2-digit',
16    second: '2-digit'
17  });
18}
19
20// Formatowanie czasu, który upłynął
21export function formatTimeAgo(date) {
22  if (!date) return 'N/A';
23
24  const d = new Date(date);
25  if (isNaN(d.getTime())) return 'Invalid Date';
26
27  const now = new Date();
28  const diffMs = now - d;
29
30  if (diffMs < 0) return 'w przyszłości';
31
32  const diffSec = Math.floor(diffMs / 1000);
33  if (diffSec < 60) return `${diffSec} sek. temu`;
34
35  const diffMin = Math.floor(diffSec / 60);
36  if (diffMin < 60) return `${diffMin} min. temu`;
37
38  const diffHour = Math.floor(diffMin / 60);
39  if (diffHour < 24) return `${diffHour} godz. temu`;
40
41  const diffDay = Math.floor(diffHour / 24);
42  if (diffDay < 30) return `${diffDay} dni temu`;
43
44  const diffMonth = Math.floor(diffDay / 30);
45  if (diffMonth < 12) return `${diffMonth} mies. temu`;
46
47  const diffYear = Math.floor(diffMonth / 12);
48  return `${diffYear} lat temu`;
49}
50
51// Kolorowanie poziomu zagrożenia
52export function getDangerLevelColor(level) {
53  if (level <= 3) return 'green';
54  if (level <= 5) return 'yellow';
55  if (level <= 7) return 'orange';
56  return 'red';
57}
58
59// Formatowanie poziomu zagrożenia jako tekst
60export function formatDangerLevel(level) {
61  if (level <= 2) return 'Niski';
62  if (level <= 5) return 'Umiarkowany';
63  if (level <= 7) return 'Podwyższony';
64  if (level <= 9) return 'Wysoki';
65  return 'Krytyczny';
66}
67
68// Retry utility - ponowne próby wykonania funkcji w przypadku awarii
69export async function retry(fn, options = {}) {
70  const {
71    maxRetries = 3,
72    delay = 1000,
73    backoffFactor = 2,
74    retryCondition = error => true
75  } = options;
76
77  let lastError;
78
79  for (let attempt = 1; attempt <= maxRetries; attempt++) {
80    try {
81      return await fn();
82    } catch (error) {
83      // Sprawdzamy, czy ten błąd kwalifikuje się do ponownej próby
84      if (!retryCondition(error)) {
85        throw error;
86      }
87
88      lastError = error;
89      console.log(`Próba ${attempt}/${maxRetries} nie powiodła się. Powód: ${error.message}`);
90
91      // Jeśli to była ostatnia próba, nie czekamy
92      if (attempt === maxRetries) break;
93
94      // Czekamy z opóźnieniem eksponencjalnym
95      const waitTime = delay * Math.pow(backoffFactor, attempt - 1);
96      await new Promise(resolve => setTimeout(resolve, waitTime));
97    }
98  }
99
100  throw lastError;
101}
102
103// Memoizacja funkcji (buforowanie wyników)
104export function memoize(fn, { maxSize = 100, ttl = 60000 } = {}) {
105  const cache = new Map();
106  const timestamps = new Map();
107
108  return async function(...args) {
109    // Tworzymy klucz z argumentów
110    const key = JSON.stringify(args);
111
112    // Sprawdzamy, czy wynik jest w pamięci podręcznej i czy nie wygasł
113    const now = Date.now();
114    if (cache.has(key)) {
115      const timestamp = timestamps.get(key);
116      if (now - timestamp < ttl) {
117        return cache.get(key);
118      }
119    }
120
121    // Jeśli pamięć podręczna jest za duża, usuwamy najstarszy element
122    if (cache.size >= maxSize) {
123      let oldestKey = null;
124      let oldestTime = now;
125
126      for (const [key, time] of timestamps.entries()) {
127        if (time < oldestTime) {
128          oldestKey = key;
129          oldestTime = time;
130        }
131      }
132
133      if (oldestKey) {
134        cache.delete(oldestKey);
135        timestamps.delete(oldestKey);
136      }
137    }
138
139    // Pobieramy wynik, dodajemy do pamięci podręcznej i zwracamy
140    const result = await fn(...args);
141    cache.set(key, result);
142    timestamps.set(key, now);
143
144    return result;
145  };
146}

7. Konfiguracja (config.js)

Plik konfiguracyjny dla naszej aplikacji:

1// data/config.js
2
3export const appConfig = {
4  // Ogólne ustawienia aplikacji
5  appName: 'Jurassic Tracker',
6  version: '1.0.0',
7
8  // Ustawienia monitorowania
9  monitoring: {
10    dinoBehaviorInterval: 60000, // Interwał monitorowania zachowań dinozaurów (ms)
11    enclosureSecurityInterval: 90000, // Interwał monitorowania bezpieczeństwa wybiegów (ms)
12    autoStart: true // Automatyczne uruchamianie monitorowania przy starcie
13  },
14
15  // Ustawienia API
16  api: {
17    baseUrl: 'https://jurassic-park-api.example.com', // W rzeczywistości używalibyśmy prawdziwego URL
18    timeout: 10000, // Timeout dla żądań API (ms)
19    retryCount: 3 // Liczba prób ponownego wykonania nieudanego żądania
20  },
21
22  // Ocena ryzyka dinozaurów
23  dinorisk: {
24    dangerousSpecies: ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus', 'Allosaurus', 'Dilophosaurus'],
25    behaviorRiskMatrix: {
26      'calm': { base: 1, factor: 0.8 },
27      'grazing': { base: 1, factor: 0.9 },
28      'sleeping': { base: 2, factor: 0.7 },
29      'exploring': { base: 4, factor: 1.0 },
30      'agitated': { base: 6, factor: 1.2 },
31      'hunting': { base: 7, factor: 1.5 },
32      'aggressive': { base: 9, factor: 2.0 }
33    },
34    hungerRiskHours: 3, // Liczba godzin, po których głód zaczyna wpływać na ryzyko
35    hungerRiskFactor: 0.5 // Współczynnik wpływu głodu na ryzyko
36  },
37
38  // Poziomy bezpieczeństwa dla powiadomień
39  security: {
40    alertThresholds: {
41      low: 3,
42      medium: 5,
43      high: 7,
44      critical: 9
45    },
46    autoResolveAlerts: false // Czy automatycznie oznaczać alerty jako rozwiązane
47  }
48};
49
50export default appConfig;

8. Główny plik aplikacji (index.js)

Na koniec, zapiszmy główny plik naszej aplikacji, który łączy wszystkie moduły:

1// index.js
2import { appConfig } from './data/config.js';
3import * as DinosaurService from './modules/dinosaurs.js';
4import * as EnclosureService from './modules/enclosures.js';
5import * as MonitoringService from './modules/monitoring.js';
6import { formatDate, formatTimeAgo, formatDangerLevel, getDangerLevelColor } from './modules/utils.js';
7import { APIError, DinoError, SecurityError, EnclosureBreachError } from './modules/errors/index.js';
8
9console.log(`==========================================`);
10console.log(`Witaj w ${appConfig.appName} v${appConfig.version}!`);
11console.log(`Inicjalizacja systemów Parku Jurajskiego...`);
12console.log(`==========================================`);
13
14// Obsługa błędów na poziomie globalnym
15process.on('unhandledRejection', (reason, promise) => {
16  console.error('Nieobsłużone odrzucenie obietnicy:', reason);
17});
18
19process.on('uncaughtException', (error) => {
20  console.error('Nieobsłużony wyjątek:', error);
21
22  // Analiza typów błędów
23  if (error instanceof EnclosureBreachError) {
24    console.error(`KRYTYCZNY: Naruszenie wybiegu w sektorze ${error.sector}!`);
25    console.error(`${error.count} ${error.dinoSpecies} poza wybiegiem!`);
26    console.error('Uruchamianie protokołu awaryjnego...');
27  } else if (error instanceof SecurityError) {
28    console.error(`BEZPIECZEŃSTWO: Problem w sektorze ${error.sector}`);
29  } else if (error instanceof DinoError) {
30    console.error(`DINOZAUR: Problem z ${error.species} (ID: ${error.dinoId})`);
31  } else if (error instanceof APIError) {
32    console.error(`API: Błąd ${error.statusCode} podczas komunikacji z ${error.endpoint}`);
33  }
34});
35
36// Główna funkcja startowa
37async function initializeApp() {
38  try {
39    // Rejestrowanie listenera dla alertów
40    const removeAlertListener = MonitoringService.addAlertListener(alert => {
41      const { type, data, timestamp } = alert;
42
43      console.log(`[ALERT] ${formatDate(timestamp)} - ${type}`);
44
45      switch (type) {
46        case 'DANGEROUS_BEHAVIOR':
47          console.log(`Dinozaur ${data.species} (ID: ${data.dinoId}) wykazuje niebezpieczne zachowanie: ${data.behavior}`);
48          console.log(`Poziom zagrożenia: ${formatDangerLevel(data.dangerLevel)}`);
49          break;
50
51        case 'ENCLOSURE_SECURITY':
52          console.log(`Problem z bezpieczeństwem wybiegu ${data.name} (Sektor ${data.sector})`);
53          console.log(`Wskaźnik bezpieczeństwa: ${data.securityScore}/100`);
54          console.log(`Status zasilania: ${data.powerStatus}`);
55          break;
56
57        case 'ENCLOSURE_BREACH':
58          console.log(`!!! NARUSZENIE WYBIEGU !!!`);
59          console.log(`Sektor: ${data.sector}, Gatunek: ${data.species}, Liczba: ${data.count}`);
60          console.log('UWAGA: Wymagana natychmiastowa interwencja!');
61          break;
62
63        default:
64          console.log('Otrzymano alert:', data);
65      }
66    });
67
68    // Pobieranie danych początkowych
69    console.log('Pobieranie danych o dinozaurach...');
70    const dinosaurs = await DinosaurService.getAllDinosaurs();
71    console.log(`Załadowano dane ${dinosaurs.length} dinozaurów.`);
72
73    console.log('Pobieranie danych o wybiegach...');
74    const enclosures = await EnclosureService.getAllEnclosures();
75    console.log(`Załadowano dane ${enclosures.length} wybiegów.`);
76
77    // Uruchomienie monitorowania, jeśli jest włączone w konfiguracji
78    if (appConfig.monitoring.autoStart) {
79      console.log('Uruchamianie systemu monitorowania...');
80      MonitoringService.startMonitoring({
81        dinoBehaviorInterval: appConfig.monitoring.dinoBehaviorInterval,
82        enclosureSecurityInterval: appConfig.monitoring.enclosureSecurityInterval
83      });
84
85      const status = MonitoringService.getMonitoringStatus();
86      console.log(`Status monitorowania: ${status.status}`);
87      console.log(`Aktywne monitory: ${status.activeMonitors.join(', ')}`);
88    }
89
90    console.log('\nSystemy Parku Jurajskiego zostały zainicjalizowane pomyślnie.');
91    console.log('Rozpoczynam monitorowanie...');
92
93    // Przykładowe wywołania API, które mogą zostać użyte przez użytkownika
94    setTimeout(async () => {
95      try {
96        // Karmienie dinozaura
97        const fedDino = await DinosaurService.feedDinosaur('trx1');
98        console.log(`Nakarmiono ${fedDino.name} (${fedDino.species})`);
99
100        // Inspekcja wybiegu
101        const inspectedEnclosure = await EnclosureService.inspectEnclosure('enc2');
102        console.log(`Przeprowadzono inspekcję wybiegu: ${inspectedEnclosure.name} (Sektor ${inspectedEnclosure.sector})`);
103
104        // Naprawa systemu zasilania (jeśli jakiś wybieg ma problem)
105        const enclosureWithPowerIssue = enclosures.find(e => e.powerStatus !== 'active');
106        if (enclosureWithPowerIssue) {
107          const repairedEnclosure = await EnclosureService.repairPowerSystem(enclosureWithPowerIssue.id);
108          console.log(`Naprawiono system zasilania w: ${repairedEnclosure.name}`);
109        }
110
111      } catch (error) {
112        console.error('Błąd podczas wykonywania operacji demonstracyjnych:', error);
113      }
114    }, 5000);
115
116  } catch (error) {
117    console.error('Błąd podczas inicjalizacji aplikacji:', error);
118
119    if (error instanceof APIError) {
120      console.error(`Nie można połączyć się z API: ${error.statusCode} ${error.message}`);
121    } else {
122      console.error('Nieoczekiwany błąd:', error.message);
123    }
124
125    console.error('Próba ponownej inicjalizacji za 10 sekund...');
126    setTimeout(initializeApp, 10000);
127  }
128}
129
130// Uruchomienie aplikacji
131initializeApp();
132
133// Funkcja do zatrzymania aplikacji przed zamknięciem
134function cleanup() {
135  console.log('\nZamykanie aplikacji...');
136
137  // Zatrzymanie systemu monitorowania
138  MonitoringService.stopMonitoring();
139
140  console.log('Wszystkie systemy monitorowania zostały zatrzymane.');
141  console.log('Do widzenia!');
142  process.exit(0);
143}
144
145// Obsługa sygnałów zakończenia
146process.on('SIGINT', cleanup);
147process.on('SIGTERM', cleanup);

Uruchomienie aplikacji

W rzeczywistym środowisku moglibyśmy uruchomić tę aplikację za pomocą:

1node index.js

Po uruchomieniu, aplikacja:

  1. Zainicjalizuje wszystkie systemy
  2. Pobierze dane o dinozaurach i wybiegach
  3. Uruchomi monitorowanie w tle
  4. Rozpocznie reagowanie na zdarzenia

Podsumowanie zastosowanych technik

W naszej aplikacji JurassicTracker wykorzystaliśmy wszystkie najważniejsze techniki poznane w tym module:

  1. ES Modules - Cała aplikacja jest zorganizowana w moduły, które importują i eksportują funkcjonalności
  2. Asynchroniczność:
    • Używamy async/await dla przejrzystej obsługi operacji asynchronicznych
    • Wykorzystujemy Promise.all dla równoległego wykonywania operacji
    • Obsługujemy opóźnienia i interwały czasowe
  3. Obsługa błędów:
    • Stworzyliśmy hierarchię własnych klas błędów (APIError, DinoError, SecurityError)
    • Używamy try/catch bloków dla lokalnej obsługi błędów
    • Zaimplementowaliśmy globalną obsługę nieobsłużonych błędów
    • Stosujemy technikę retry dla operacji, które mogą być ponowione
  4. Monitorowanie w tle:
    • Używamy setInterval do cyklicznego wykonywania zadań monitorowania
    • Implementujemy prosty system powiadomień oparty na wzorcu obserwatora (observer pattern)
  5. Zarządzanie stanem aplikacji:
    • Przechowujemy stan w zmiennych modułowych
    • Używamy Map dla efektywnego przechowywania danych
    • Implementujemy system buforowania (memoization) dla optymalizacji

Ta aplikacja pokazuje, jak można połączyć wszystkie poznane techniki w funkcjonalny, modularny i odporny na błędy system.

W rzeczywistej aplikacji webowej moglibyśmy dodać warstwę UI (React, Vue, Angular) oraz prawdziwe API backendowe, ale podstawowa struktura i organizacja kodu pozostałyby podobne.

Wnioski

Budowa złożonej aplikacji JavaScript wymaga dobrego zrozumienia:

  1. Modularyzacji kodu dla łatwiejszego utrzymania
  2. Asynchronicznych wzorców dla wydajnych operacji
  3. Obsługi błędów dla odpornej aplikacji
  4. Monitorowania i logowania dla diagnostyki

Nasz system JurassicTracker, choć jest przykładem dydaktycznym, pokazuje, jak te wszystkie elementy współpracują ze sobą, tworząc solidną aplikację, która mogłaby - z odpowiednimi rozszerzeniami - zostać wykorzystana w prawdziwym Parku Jurajskim. Miejmy jednak nadzieję, że jej systemy bezpieczeństwa działałyby lepiej niż w filmie!

W następnym ćwiczeniu zagłębimy się w strukturyzację kodu w jeszcze większych projektach JavaScript, rozszerzając poznane techniki i wzorce.

Vai a CodeWorlds