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
33import 'dotenv/config' ;
4- import express from 'express' ;
4+ import express , { Request , Response , NextFunction } from 'express' ;
55import path from 'path' ;
66import { TpaServer , TpaSession , ViewType } from '@augmentos/sdk' ;
77import axios from 'axios' ;
@@ -14,7 +14,7 @@ const packageJson = JSON.parse(
1414) ;
1515const APP_VERSION = packageJson . version ;
1616const 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 ' ;
1818const AUGMENTOS_API_KEY = process . env . AUGMENTOS_API_KEY ;
1919const 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