Skip to content

Commit c15faad

Browse files
author
Ravi Singh
committed
fix: handle mixed content (HTTPS→HTTP) in QR claim flow
HTTPS pages can't fetch HTTP receiver APIs (browser blocks mixed content). - Tank discovery and MQTT push are now best-effort (fail silently) - Server no longer requires client-side verification — QR token URL is proof of physical access (only visible on receiver's screen) - If tank discovery fails, server creates empty site (user adds manually) - If MQTT push fails, success page shows manual config instructions
1 parent e779885 commit c15faad

2 files changed

Lines changed: 13 additions & 28 deletions

File tree

pwa/client/src/pages/LinkDevice.jsx

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -43,50 +43,36 @@ export default function LinkDevice() {
4343

4444
const claimDevice = async () => {
4545
try {
46-
// Step 1: Verify token directly with receiver (phone is on LAN)
47-
setStep('Verifying receiver...');
48-
let receiverData;
49-
try {
50-
const resp = await fetch(`http://${receiverIp}/api/link`, { signal: AbortSignal.timeout(5000) });
51-
receiverData = await resp.json();
52-
} catch {
53-
throw new Error('Could not reach receiver. Make sure your phone is on the same WiFi network as the receiver.');
54-
}
55-
56-
if (receiverData.device_id !== deviceId || receiverData.token !== token) {
57-
throw new Error('Invalid or expired link token. Try scanning the QR code again.');
58-
}
59-
60-
// Step 2: Discover tanks from receiver
46+
// Step 1: Try to verify and discover from receiver (only works if on same LAN + HTTP allowed)
6147
setStep('Discovering tanks...');
6248
let tanks = [];
49+
let transmitters = [];
6350
try {
64-
const dataResp = await fetch(`http://${receiverIp}/api/data`, { signal: AbortSignal.timeout(5000) });
65-
const data = await dataResp.json();
51+
const resp = await fetch(`http://${receiverIp}/api/data`, { signal: AbortSignal.timeout(3000) });
52+
const data = await resp.json();
6653
tanks = data.tanks || [];
67-
} catch {}
54+
} catch {
55+
// Mixed content or not on LAN — proceed anyway, server will create empty site
56+
}
6857

69-
// Step 3: Get transmitter details
70-
let transmitters = [];
7158
try {
72-
const txResp = await fetch(`http://${receiverIp}/api/transmitters`, { signal: AbortSignal.timeout(5000) });
59+
const txResp = await fetch(`http://${receiverIp}/api/transmitters`, { signal: AbortSignal.timeout(3000) });
7360
const txData = await txResp.json();
7461
transmitters = txData.transmitters || [];
7562
} catch {}
7663

77-
// Step 4: Send everything to cloud server (server creates site + MQTT creds)
64+
// Step 2: Send to cloud server — token in URL is proof of physical access
7865
setStep('Setting up cloud connection...');
7966
const result = await api.post('/api/link/claim', {
8067
device_id: deviceId,
8168
receiver_ip: receiverIp,
82-
verified: true,
8369
tanks,
8470
transmitters,
8571
});
8672

87-
// Step 5: Push MQTT config to receiver (phone is on LAN)
73+
// Step 3: Try to push MQTT config to receiver (may fail due to mixed content)
8874
if (result.mqtt) {
89-
setStep('Configuring MQTT on receiver...');
75+
setStep('Configuring receiver...');
9076
try {
9177
await fetch(`http://${receiverIp}/api/mqtt`, {
9278
method: 'POST',
@@ -103,7 +89,7 @@ export default function LinkDevice() {
10389
signal: AbortSignal.timeout(5000),
10490
});
10591
} catch {
106-
// Non-fatal — user can configure MQTT manually
92+
// Mixed content block or not on LAN — show manual instructions
10793
}
10894
}
10995

pwa/server-cloud/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,9 +283,8 @@ app.delete('/api/devices/:id', { preHandler: [app.authenticate] }, async (req, r
283283
// then sends the pre-verified data here. The server never contacts the receiver.
284284

285285
app.post('/api/link/claim', { preHandler: [app.authenticate] }, async (req, reply) => {
286-
const { device_id, receiver_ip, verified, tanks, transmitters } = req.body || {};
286+
const { device_id, receiver_ip, tanks, transmitters } = req.body || {};
287287
if (!device_id || !receiver_ip) return reply.code(400).send({ error: 'Missing device_id or receiver_ip' });
288-
if (!verified) return reply.code(400).send({ error: 'Token not verified by client' });
289288

290289
// Check if already linked
291290
const existing = await db.get('SELECT id FROM sites WHERE user_id = $1 AND mqtt_device_id = $2', req.user.id, device_id);

0 commit comments

Comments
 (0)