Skip to content

Commit 2a8a346

Browse files
feat: add Service Worker for mock API endpoints (GitHub Pages support)
1 parent 913a6b2 commit 2a8a346

1 file changed

Lines changed: 176 additions & 0 deletions

File tree

public/sw.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/// <reference lib="webworker" />
2+
declare const self: ServiceWorkerGlobalScope;
3+
4+
import { DEFAULT_VESSELS, INITIAL_ALERTS, SCENARIOS, INITIAL_ARWEAVE_LOGS, INITIAL_GNOSIS_TX } from '../src/data';
5+
import type { Vessel, AIAlert } from '../src/types';
6+
7+
// In-memory mock database
8+
interface MockDB {
9+
vessels: Vessel[];
10+
alerts: AIAlert[];
11+
}
12+
13+
const mockDB: MockDB = {
14+
vessels: DEFAULT_VESSELS,
15+
alerts: INITIAL_ALERTS
16+
};
17+
18+
// Mock weather data
19+
const mockWeather = {
20+
location: 'Dana Point, CA',
21+
description: 'Clear skies, moderate breeze',
22+
tempF: 72,
23+
windMph: 8,
24+
source: 'mock'
25+
};
26+
27+
// Mock user
28+
const mockUser = {
29+
id: 'u1',
30+
name: 'Marina Operator',
31+
role: 'Marina Operator',
32+
authenticated: true
33+
};
34+
35+
// Generate mock compliance report
36+
function generateMockComplianceReport(vessel: Vessel, scenario?: any) {
37+
return `### 📋 COA_REG_HARBORCHAIN REPORT: ${vessel.name.toUpperCase()}
38+
**Security Protocol**: MOCK_SERVICE_WORKER (No real API key configured)
39+
**Vessel Slip Address**: ${vessel.slip} | **Official Hashed ID**: HC-${vessel.id}
40+
**Oracle Wave/Tide Check**: ${scenario?.name || 'Normal Operations'} Scenario (Normalised Validation)
41+
42+
#### ⚓ Current Telemetry Metrics Checked:
43+
- **Core Signal Tracking**: Active (Coordinates: ${vessel.coordinates})
44+
- **Bilge Sensor State**: ${vessel.telemetry.bilgeLevel}%
45+
- **Engine Temp Profile**: ${vessel.telemetry.engineTemp}°F
46+
- **Lithium Fuel Reserve**: ${vessel.telemetry.batteryLevel}%
47+
- **Owner Account Escrow**: ${vessel.owner}
48+
49+
#### ⚠️ Predictive Maintenance Hazard Checklist:
50+
1. ${mockDB.alerts.filter((a) => a.vesselId === vessel.id).length > 0 ? `🚨 **${mockDB.alerts.filter((a) => a.vesselId === vessel.id)[0].title}** — Investigation recommended.` : '✅ No critical mechanical failures detected.'}
51+
2. **Environmental Oracle Forecast**: Waves verified at ${scenario?.waveHeight || '2.1ft'} / Wind speed ${scenario?.windSpeed || '8kts'}.
52+
3. **Scheduled Inspection Target**: Standard ${vessel.category} inspection recommended within ${vessel.nextMaintWeeks} weeks.
53+
54+
#### 🔗 Requested Blockchain Action Ledger:
55+
- Action: Log current telemetry state to Arweave.
56+
- Recommended Payload: timestamp, bilge index, battery telemetry.
57+
- Gnosis Transaction Trigger: slip fee escrow distribution.
58+
59+
---
60+
*This is a mock report generated by Service Worker. No Claude AI connection.*`;
61+
}
62+
63+
// Handle fetch events
64+
self.addEventListener('fetch', (event: FetchEvent) => {
65+
const { request } = event;
66+
const url = new URL(request.url);
67+
68+
// Only intercept /api/* requests
69+
if (!url.pathname.startsWith('/api/')) {
70+
return;
71+
}
72+
73+
// Route to appropriate handler
74+
if (url.pathname === '/api/health' && request.method === 'GET') {
75+
event.respondWith(
76+
new Response(
77+
JSON.stringify({ status: 'ok', time: new Date().toISOString() }),
78+
{ headers: { 'Content-Type': 'application/json' } }
79+
)
80+
);
81+
} else if (url.pathname === '/api/vessels' && request.method === 'GET') {
82+
event.respondWith(
83+
new Response(JSON.stringify(mockDB.vessels), {
84+
headers: { 'Content-Type': 'application/json' }
85+
})
86+
);
87+
} else if (url.pathname === '/api/vessels/batch' && request.method === 'POST') {
88+
event.respondWith(
89+
request.json().then((body: any) => {
90+
if (Array.isArray(body.vessels)) {
91+
mockDB.vessels = body.vessels;
92+
return new Response(JSON.stringify({ saved: body.vessels.length }), {
93+
headers: { 'Content-Type': 'application/json' }
94+
});
95+
}
96+
return new Response(JSON.stringify({ error: 'Expected vessels array.' }), {
97+
status: 400,
98+
headers: { 'Content-Type': 'application/json' }\n });
99+
})
100+
);
101+
} else if (url.pathname === '/api/alerts' && request.method === 'GET') {
102+
event.respondWith(
103+
new Response(JSON.stringify(mockDB.alerts), {
104+
headers: { 'Content-Type': 'application/json' }
105+
})
106+
);
107+
} else if (url.pathname.match(/\/api\/alerts\/[^/]+\/ack$/) && request.method === 'POST') {
108+
const alertId = url.pathname.split('/')[3];
109+
const alert = mockDB.alerts.find((a) => a.id === alertId);
110+
if (alert) {
111+
alert.acknowledged = true;
112+
}
113+
event.respondWith(
114+
new Response(JSON.stringify({ updated: alertId }), {
115+
headers: { 'Content-Type': 'application/json' }
116+
})
117+
);
118+
} else if (url.pathname === '/api/weather' && request.method === 'GET') {
119+
// Simulate weather with slight randomness
120+
const weather = {
121+
...mockWeather,
122+
tempF: mockWeather.tempF + Math.floor(Math.random() * 10 - 5),
123+
windMph: mockWeather.windMph + Math.floor(Math.random() * 4 - 2)
124+
};
125+
event.respondWith(
126+
new Response(JSON.stringify(weather), {
127+
headers: { 'Content-Type': 'application/json' }
128+
})
129+
);
130+
} else if (url.pathname === '/api/auth/me' && request.method === 'GET') {
131+
event.respondWith(
132+
new Response(JSON.stringify(mockUser), {
133+
headers: { 'Content-Type': 'application/json' }
134+
})
135+
);
136+
} else if (url.pathname === '/api/auth/login' && request.method === 'POST') {
137+
event.respondWith(
138+
request.json().then((body: any) => {
139+
const role = body?.role ?? 'Marina Operator';
140+
return new Response(
141+
JSON.stringify({ id: 'u1', name: 'Marina Operator', role, authenticated: true }),
142+
{ headers: { 'Content-Type': 'application/json' } }
143+
);
144+
})
145+
);
146+
} else if (url.pathname === '/api/compliance-report' && request.method === 'POST') {
147+
event.respondWith(
148+
request.json().then((body: any) => {
149+
const vessel = mockDB.vessels.find((v) => v.id === body.vessel?.id);
150+
if (!vessel) {
151+
return new Response(JSON.stringify({ error: 'Vessel not found' }), {
152+
status: 404,
153+
headers: { 'Content-Type': 'application/json' }
154+
});
155+
}
156+
const report = generateMockComplianceReport(vessel, body.scenario);
157+
return new Response(JSON.stringify({ report, isMock: true }), {
158+
headers: { 'Content-Type': 'application/json' }
159+
});
160+
})
161+
);
162+
}
163+
});
164+
165+
// Activate and clean up old caches
166+
self.addEventListener('activate', (event: ExtendableEvent) => {
167+
event.waitUntil(
168+
caches.keys().then((cacheNames) => {
169+
return Promise.all(
170+
cacheNames.map((cacheName) => {
171+
return caches.delete(cacheName);
172+
})
173+
);
174+
})
175+
);
176+
});

0 commit comments

Comments
 (0)