Skip to content

Commit c852342

Browse files
committed
Cloudflare nearly ready
Vscode errors persist but deploy attempt due for CloudFlare tunnel
1 parent 8040f93 commit c852342

File tree

3 files changed

+70
-93
lines changed

3 files changed

+70
-93
lines changed

package.json

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,25 @@
11
{
2-
"name": "@everywoah/air-quality",
3-
"version": "1.2.0",
2+
"name": "air-quality-app",
3+
"version": "1.2.1",
44
"type": "module",
55
"augmentos": {
6-
"versionDisplay": "Air Quality v1.2"
6+
"versionDisplay": "Air Quality v1.2.1",
7+
"websocket": true
78
},
8-
"repository": {
9-
"type": "git",
10-
"url": "https://github.com/paulgailey/air-quality-app"
11-
},
12-
"main": "dist/index.js",
139
"scripts": {
1410
"dev": "bun --hot src/index.ts",
1511
"start": "bun src/index.ts",
1612
"docker:dev": "docker compose -f docker/docker-compose.dev.yml -p dev up",
17-
"docker:dev:detach": "./scripts/docker-dev.sh -d",
18-
"docker:stop": "docker compose -f docker/docker-compose.dev.yml -p dev down",
19-
"postinstall": "npx npm-force-resolutions"
13+
"test:connect": "bun test-websocket.ts"
2014
},
2115
"dependencies": {
22-
"@augmentos/sdk": "1.1.4",
23-
"@types/express": "^5.0.1",
24-
"@types/node": "^22.13.14",
25-
"axios": "^1.6.0",
16+
"@augmentos/sdk": "^1.2.0",
2617
"dotenv": "^16.5.0",
2718
"express": "^4.21.2",
28-
"jsonwebtoken": "9.0.2",
29-
"npm-force-resolutions": "^0.0.10",
30-
"path": "^0.12.7"
19+
"axios": "^1.6.0"
3120
},
3221
"devDependencies": {
22+
"@types/node": "^22",
3323
"typescript": "^5.0.0"
34-
},
35-
"resolutions": {
36-
"jsonwebtoken": "9.0.2"
37-
},
38-
"overrides": {
39-
"jsonwebtoken": "9.0.2"
4024
}
4125
}

readme.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
# air-quality-app-sat
2-
Satisfactory developer release to production
1+
# air-quality-app-prod
2+
Cloudflare production release

src/index.ts

Lines changed: 60 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// Version: 1.2.1
2-
// Description: Air Quality Augmentos App - Fixed Location Handling
1+
// Version: 1.2.4
2+
// Description: Air Quality Augmentos App - Cloudflare Optimized
33
import 'dotenv/config';
4-
import express from 'express';
4+
import express, { Request, Response, NextFunction } from 'express';
55
import path from 'path';
66
import { TpaServer, TpaSession, ViewType } from '@augmentos/sdk';
77
import axios from 'axios';
@@ -14,7 +14,7 @@ const packageJson = JSON.parse(
1414
);
1515
const APP_VERSION = packageJson.version;
1616
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
17-
const PACKAGE_NAME = process.env.PACKAGE_NAME || 'com.everywoah.airquality';
17+
const PACKAGE_NAME = process.env.PACKAGE_NAME || 'air-quality-app';
1818
const AUGMENTOS_API_KEY = process.env.AUGMENTOS_API_KEY;
1919
const AQI_TOKEN = process.env.AQI_TOKEN;
2020

@@ -42,14 +42,12 @@ interface AQIStationData {
4242
};
4343
}
4444

45-
class AirQualityApp extends TpaServer {
46-
private activeSessions = new Map<string, {
47-
userId: string;
48-
started: Date;
49-
locationObtained: boolean;
50-
lastLocation?: { lat: number; lon: number };
51-
}>();
45+
interface SessionExtension {
46+
locationObtained: boolean;
47+
lastLocation?: { lat: number; lon: number };
48+
}
5249

50+
class AirQualityApp extends TpaServer {
5351
private readonly VOICE_COMMANDS = [
5452
"air quality",
5553
"what's the air like",
@@ -61,43 +59,56 @@ class AirQualityApp extends TpaServer {
6159
"air pollution here"
6260
];
6361

62+
private sessionExtensions = new Map<string, SessionExtension>();
63+
private expressApp: express.Express;
64+
6465
constructor() {
6566
super({
6667
packageName: PACKAGE_NAME,
6768
apiKey: AUGMENTOS_API_KEY,
6869
port: PORT,
6970
publicDir: path.join(__dirname, '../public'),
71+
augmentOSWebsocketUrl: process.env.AUGMENTOS_WEBSOCKET_URL || 'wss://prod.augmentos.cloud/tpa-ws',
72+
websocket: {
73+
reconnect: true,
74+
timeout: 15000
75+
},
76+
trustProxy: true
7077
});
78+
79+
this.expressApp = express();
7180
this.setupRoutes();
7281
}
7382

7483
private setupRoutes(): void {
75-
const app = this.getExpressApp();
76-
77-
// Middleware
78-
app.use((req, res, next) => {
84+
// Enhanced Cloudflare middleware
85+
this.expressApp.use((req: Request, res: Response, next: NextFunction) => {
86+
req.headers['cf-connecting-ip'] = req.headers['cf-connecting-ip'] || req.ip;
87+
req.headers['x-device-latitude'] = req.headers['x-device-latitude'] || '';
88+
req.headers['x-device-longitude'] = req.headers['x-device-longitude'] || '';
7989
res.set('X-Request-ID', crypto.randomUUID());
8090
next();
8191
});
82-
app.use(express.json());
92+
this.expressApp.use(express.json());
8393

8494
// Routes
85-
app.get('/', (req, res) => {
95+
this.expressApp.get('/', (req: Request, res: Response) => {
8696
res.json({
8797
status: "running",
8898
version: APP_VERSION,
8999
endpoints: ['/health', '/tpa_config.json']
90100
});
91101
});
92102

93-
app.get('/health', (req, res) => {
103+
this.expressApp.get('/health', (req: Request, res: Response) => {
94104
res.json({
95105
status: "healthy",
96-
sessions: this.activeSessions.size
106+
sessions: this.sessionExtensions.size,
107+
clientIp: req.headers['cf-connecting-ip'] || req.ip
97108
});
98109
});
99110

100-
app.get('/tpa_config.json', (req, res) => {
111+
this.expressApp.get('/tpa_config.json', (req: Request, res: Response) => {
101112
res.json({
102113
voiceCommands: this.VOICE_COMMANDS.map(phrase => ({
103114
phrase,
@@ -108,10 +119,10 @@ class AirQualityApp extends TpaServer {
108119
});
109120
});
110121

111-
app.post('/webhook', async (req, res) => {
122+
this.expressApp.post('/webhook', async (req: Request, res: Response) => {
112123
if (req.body?.type === 'session_request') {
113124
try {
114-
await this.initTpaSession({
125+
await this.initSession({
115126
sessionId: req.body.sessionId,
116127
userId: req.body.userId,
117128
packageName: PACKAGE_NAME
@@ -128,90 +139,71 @@ class AirQualityApp extends TpaServer {
128139
}
129140

130141
protected async onSession(session: TpaSession, sessionId: string, userId: string): Promise<void> {
131-
this.activeSessions.set(sessionId, {
132-
userId,
133-
started: new Date(),
142+
await super.onSession(session, sessionId, userId);
143+
this.sessionExtensions.set(sessionId, {
134144
locationObtained: false
135145
});
136146

137147
console.log(`New session ${sessionId} started for user ${userId}`);
138148

139-
// 🔍 PRIORITY: SDK location callback
140149
session.events.onLocation(async (coords) => {
141150
console.log(`📍 Received coordinates from SDK: ${coords.lat}, ${coords.lon}`);
142-
const sessionData = this.activeSessions.get(sessionId);
143-
if (sessionData) {
144-
sessionData.locationObtained = true;
145-
sessionData.lastLocation = { lat: coords.lat, lon: coords.lon };
151+
const ext = this.sessionExtensions.get(sessionId);
152+
if (ext) {
153+
ext.locationObtained = true;
154+
ext.lastLocation = { lat: coords.lat, lon: coords.lon };
146155
}
147156
await this.showAirQuality(session, coords.lat, coords.lon, false);
148157
});
149158

150-
// 🎤 Voice command trigger
151159
session.onTranscriptionForLanguage('en-US', async (transcript) => {
152160
const text = transcript.text.toLowerCase();
153161
console.log(`🎤 Heard: "${text}" for session ${sessionId}`);
154162

155163
if (this.VOICE_COMMANDS.some(cmd => text.includes(cmd.toLowerCase()))) {
156-
const sessionData = this.activeSessions.get(sessionId);
157-
if (sessionData?.lastLocation) {
158-
// Use last known location if available
159-
await this.showAirQuality(
160-
session,
161-
sessionData.lastLocation.lat,
162-
sessionData.lastLocation.lon,
163-
false
164-
);
164+
const ext = this.sessionExtensions.get(sessionId);
165+
if (ext?.lastLocation) {
166+
await this.showAirQuality(session, ext.lastLocation.lat, ext.lastLocation.lon, false);
165167
} else {
166-
// Try to get fresh location
167168
await this.handleAirQualityRequest(session, sessionId);
168169
}
169170
}
170171
});
171172

172-
// Initial location attempt
173173
setTimeout(() => {
174174
this.handleAirQualityRequest(session, sessionId).catch(console.error);
175175
}, 1000);
176176
}
177177

178178
private async handleAirQualityRequest(session: TpaSession, sessionId: string): Promise<void> {
179-
const sessionData = this.activeSessions.get(sessionId);
180-
if (!sessionData) return;
179+
const ext = this.sessionExtensions.get(sessionId);
180+
if (!ext) return;
181181

182-
// 1. First try session.location if available
183182
if (session.location?.latitude && session.location?.longitude) {
184183
console.log(`📍 Using session.location: ${session.location.latitude}, ${session.location.longitude}`);
185-
sessionData.locationObtained = true;
186-
sessionData.lastLocation = {
184+
ext.locationObtained = true;
185+
ext.lastLocation = {
187186
lat: session.location.latitude,
188187
lon: session.location.longitude
189188
};
190-
await this.showAirQuality(
191-
session,
192-
session.location.latitude,
193-
session.location.longitude,
194-
false
195-
);
189+
await this.showAirQuality(session, session.location.latitude, session.location.longitude, false);
196190
return;
197191
}
198192

199-
// 2. Try client IP geolocation (respecting Fly.io headers)
200193
try {
201194
const clientIp = this.getClientIp(session);
202195
if (clientIp) {
203196
const ipLocation = await this.getIpLocation(clientIp);
204197
console.log(`📍 Using client IP location: ${ipLocation.lat}, ${ipLocation.lon}`);
205-
sessionData.locationObtained = true;
206-
sessionData.lastLocation = ipLocation;
198+
ext.locationObtained = true;
199+
ext.lastLocation = ipLocation;
207200
await this.showAirQuality(session, ipLocation.lat, ipLocation.lon, false);
208201
return;
209202
}
210203
} catch (error) {
211204
console.error('Client IP geolocation failed:', error);
212205
}
213206

214-
// 3. Final fallback with warning
215207
console.log('⚠️ Using default London location');
216208
await this.showAirQuality(session, 51.5074, -0.1278, true);
217209
}
@@ -244,15 +236,12 @@ class AirQualityApp extends TpaServer {
244236

245237
private getClientIp(session: TpaSession): string | null {
246238
if (!session.request?.headers) return null;
247-
248239
const headers = session.request.headers;
249240

250-
// Fly.io headers take priority
251-
if (headers['fly-client-ip']) {
252-
return headers['fly-client-ip'] as string;
241+
if (headers['cf-connecting-ip']) {
242+
return headers['cf-connecting-ip'] as string;
253243
}
254244

255-
// Standard headers
256245
const xForwardedFor = headers['x-forwarded-for'];
257246
if (xForwardedFor) {
258247
return (Array.isArray(xForwardedFor) ? xForwardedFor[0] : xForwardedFor).split(',')[0].trim();
@@ -263,7 +252,6 @@ class AirQualityApp extends TpaServer {
263252

264253
private async getIpLocation(ip: string): Promise<{ lat: number; lon: number }> {
265254
try {
266-
// First try ipapi.co
267255
const response = await axios.get(`https://ipapi.co/${ip}/json/`, { timeout: 3000 });
268256
if (response.data.latitude && response.data.longitude) {
269257
return {
@@ -272,7 +260,6 @@ class AirQualityApp extends TpaServer {
272260
};
273261
}
274262

275-
// Fallback to ip-api.com if ipapi fails
276263
const fallbackResponse = await axios.get(`http://ip-api.com/json/${ip}`, { timeout: 3000 });
277264
if (fallbackResponse.data.lat && fallbackResponse.data.lon) {
278265
return {
@@ -311,8 +298,14 @@ class AirQualityApp extends TpaServer {
311298
throw error;
312299
}
313300
}
301+
302+
public start(): void {
303+
this.expressApp.listen(PORT, () => {
304+
console.log(`✅ Air Quality v${APP_VERSION} running on port ${PORT}`);
305+
});
306+
}
314307
}
315308

316-
new AirQualityApp().getExpressApp().listen(PORT, () => {
317-
console.log(`✅ Air Quality v${APP_VERSION} running on port ${PORT}`);
318-
});
309+
// Start the server
310+
const airQualityApp = new AirQualityApp();
311+
airQualityApp.start();

0 commit comments

Comments
 (0)