Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/beacon/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import NextAuth from 'next-auth';
import { authOptions } from '@/lib/auth';

const handler = NextAuth(authOptions);
const handler = (req: any, res: any) => {
return NextAuth(req, res, authOptions);
};
Comment thread
OlukaGibson marked this conversation as resolved.

export { handler as GET, handler as POST };
2 changes: 1 addition & 1 deletion src/beacon/components/device-model-3d-inner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export default function DeviceModel3DInner({ onModelLoaded, onModelFailed }: Rea
<ErrorBoundary fallback={null} onError={handleError}>
<Suspense fallback={null}>
<DeviceModel onLoad={handleLoad} onError={handleError} />
<Environment preset="city" />
<Environment files="/hdri/potsdamer_platz_1k.hdr" />
Comment thread
OlukaGibson marked this conversation as resolved.
</Suspense>
</ErrorBoundary>

Expand Down
2 changes: 1 addition & 1 deletion src/beacon/components/device-model-3d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export default function DeviceModel3D({ onModelLoaded, onModelFailed }: Readonly
{/* 3D Model */}
<Suspense fallback={null}>
<DeviceModel onLoad={handleLoad} onError={handleError} />
<Environment preset="city" />
<Environment files="/hdri/potsdamer_platz_1k.hdr" />
</Suspense>

{/* Interactive controls */}
Expand Down
31 changes: 29 additions & 2 deletions src/beacon/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,34 @@ import {
normalizeOAuthAccessToken,
fetchEnhancedUserProfile,
} from './oauth-session';
import { buildPlatformApiUrl } from './config';
import config, { buildPlatformApiUrl } from './config';

// Fallback defaults and environment-specific self-correction for NEXTAUTH_URL
const normalizeNextAuthUrl = () => {
const currentEnv = config.environment;
const rawUrl = process.env.NEXTAUTH_URL;

// Check if NEXTAUTH_URL is missing or incorrectly set to localhost in staging/production
const isLocalhostUrl = !rawUrl || rawUrl.includes('localhost') || rawUrl.includes('127.0.0.1');

if (currentEnv === 'staging') {
if (isLocalhostUrl) {
process.env.NEXTAUTH_URL = 'https://staging-beacon.airqo.net';
}
} else if (currentEnv === 'production') {
if (isLocalhostUrl) {
process.env.NEXTAUTH_URL = 'https://beacon.airqo.net';
}
} else {
// Development / Localhost
if (!rawUrl) {
process.env.NEXTAUTH_URL = 'http://localhost:3000';
}
}
};

normalizeNextAuthUrl();


const isProduction = process.env.NODE_ENV === 'production';

Expand Down Expand Up @@ -53,7 +80,7 @@ const cookieOptions = {
};

export const authOptions: NextAuthOptions = {
secret: process.env.NEXTAUTH_SECRET,
secret: process.env.NEXTAUTH_SECRET || process.env.AUTH_SECRET,
useSecureCookies: isProduction,
providers: [
CredentialsProvider({
Expand Down
129 changes: 81 additions & 48 deletions src/beacon/lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,48 @@ const isLocalhost = !isServer &&
const isDevelopment = process.env.NODE_ENV === 'development';
const isProduction = process.env.NODE_ENV === 'production';

/**
* Get the current environment context dynamically
*/
const getEnvironment = () => {
if (!isServer) {
const hostname = globalThis.location.hostname.toLowerCase();
if (
hostname === 'localhost' ||
hostname === '127.0.0.1' ||
hostname.startsWith('192.168.') ||
hostname.startsWith('10.') ||
hostname.startsWith('172.')
) {
Comment thread
OlukaGibson marked this conversation as resolved.
return 'development';
}
if (hostname.includes('stage') || hostname.includes('staging') || hostname.includes('beacon-stage')) {
return 'staging';
}
return 'production';
}

// Server-side detection
const nextAuthUrl = process.env.NEXTAUTH_URL || '';
if (nextAuthUrl.includes('stage') || nextAuthUrl.includes('staging') || nextAuthUrl.includes('beacon-stage')) {
return 'staging';
}
if (nextAuthUrl.includes('localhost') || nextAuthUrl.includes('127.0.0.1')) {
const serviceName = (process.env.SERVICE_NAME || '').toLowerCase();
const podHostName = (process.env.HOSTNAME || '').toLowerCase();
if (serviceName.includes('stage') || podHostName.includes('stage')) {
return 'staging';
}
return 'development';
}

if (process.env.NODE_ENV === 'development') {
return 'development';
}

return 'production';
};

/**
* Strip trailing slashes from URLs
*/
Expand Down Expand Up @@ -63,91 +105,82 @@ export const enforceHttpsForRemote = (url) => {

/**
* Get the appropriate API URL based on environment
* Priority:
* 1. NEXT_PUBLIC_LOCAL_API_URL (for local development with local backend)
* 2. NEXT_PUBLIC_AIRQO_STAGING_API_BASE_URL (for staging)
* 3. NEXT_PUBLIC_AIRQO_API_BASE_URL (for production)
* 4. AIRQO_STAGING_API_BASE_URL (server-side staging)
* 5. AIRQO_API_BASE_URL (server-side production)
*/
const getBeaconApiUrl = () => {
// For local development, use local API
if (isLocalhost || isDevelopment) {
const env = getEnvironment();

if (env === 'development') {
const localUrl = process.env.NEXT_PUBLIC_LOCAL_API_URL || process.env.BEACON_API_URL;
if (localUrl) return enforceHttpsForRemote(localUrl);
return 'http://localhost:8000';
}

// For client-side, prefer NEXT_PUBLIC_ variables
if (!isServer) {
const publicBeaconUrl = process.env.NEXT_PUBLIC_BEACON_API_URL;
const stagingUrl = process.env.NEXT_PUBLIC_AIRQO_STAGING_API_BASE_URL;
const prodUrl = process.env.NEXT_PUBLIC_AIRQO_API_BASE_URL;
if (publicBeaconUrl) return enforceHttpsForRemote(publicBeaconUrl);

if (env === 'staging') {
const stagingUrl = process.env.NEXT_PUBLIC_AIRQO_STAGING_API_BASE_URL ||
process.env.AIRQO_STAGING_API_BASE_URL;
if (stagingUrl) return enforceHttpsForRemote(stagingUrl);
if (prodUrl) return enforceHttpsForRemote(prodUrl);
return 'https://staging-platform.airqo.net';
}

// For server-side, can use non-public variables
const beaconUrl = process.env.BEACON_API_URL;
const serverStagingUrl = process.env.AIRQO_STAGING_API_BASE_URL;
const serverProdUrl = process.env.AIRQO_API_BASE_URL;
if (beaconUrl) return enforceHttpsForRemote(beaconUrl);
if (serverStagingUrl) return enforceHttpsForRemote(serverStagingUrl);
if (serverProdUrl) return enforceHttpsForRemote(serverProdUrl);

// Default fallback (should be avoided in production)
console.warn('No API URL configured, using default localhost:8000');
return 'http://localhost:8000';

// Production
const prodUrl = process.env.NEXT_PUBLIC_AIRQO_API_BASE_URL ||
process.env.AIRQO_API_BASE_URL;
if (prodUrl) return enforceHttpsForRemote(prodUrl);

return 'https://platform.airqo.net';
};

/**
* Get the AirQo Platform API URL (for auth, users, etc.)
*/
const getAirQoPlatformApiUrl = () => {
// Prefer staging for development
if (isDevelopment) {
const env = getEnvironment();

if (env === 'development' || env === 'staging') {
const stagingUrl = process.env.NEXT_PUBLIC_AIRQO_STAGING_API_BASE_URL ||
process.env.AIRQO_STAGING_API_BASE_URL;
if (stagingUrl) return stripTrailingSlash(stagingUrl);
if (stagingUrl) return enforceHttpsForRemote(stagingUrl);
return 'https://staging-platform.airqo.net';
}

// Production
const prodUrl = process.env.NEXT_PUBLIC_AIRQO_API_BASE_URL ||
process.env.AIRQO_API_BASE_URL;
if (prodUrl) return stripTrailingSlash(prodUrl);

// Fallback to staging
const stagingUrl = process.env.NEXT_PUBLIC_AIRQO_STAGING_API_BASE_URL ||
process.env.AIRQO_STAGING_API_BASE_URL;
if (stagingUrl) return stripTrailingSlash(stagingUrl);
if (prodUrl) return enforceHttpsForRemote(prodUrl);

return 'https://staging-platform.airqo.net';
return 'https://platform.airqo.net';
};

/**
* API Configuration
* API Configuration with dynamic properties
*/
export const config = {
// Environment flags
isServer,
isLocalhost,
isDevelopment,
isProduction,
get isServer() { return typeof window === 'undefined'; },
get isLocalhost() {
return typeof window !== 'undefined' &&
(window.location.hostname === 'localhost' ||
window.location.hostname === '127.0.0.1' ||
window.location.hostname.startsWith('192.168.'));
},
get isDevelopment() { return process.env.NODE_ENV === 'development'; },
get isProduction() { return process.env.NODE_ENV === 'production'; },
get environment() { return getEnvironment(); },

// API URLs
apiUrl: getBeaconApiUrl(),
beaconApiUrl: getBeaconApiUrl(),
airqoPlatformUrl: getAirQoPlatformApiUrl(),
get apiUrl() { return getBeaconApiUrl(); },
get beaconApiUrl() { return getBeaconApiUrl(); },
get airqoPlatformUrl() { return getAirQoPlatformApiUrl(); },

// API version
apiVersion: process.env.AIRQO_API_VERSION || 'v2',

// API prefixes
apiPrefix: '/api/v1',
beaconApiPrefix: isLocalhost ? '/api/v1' : '/api/v1/beacon',
get beaconApiPrefix() { return this.isLocalhost ? '/api/v1' : '/api/v1/beacon'; },

// Auth settings
requiresAuth: !isLocalhost && isProduction,
get requiresAuth() { return !this.isLocalhost && this.isProduction; },

// Timeouts
defaultTimeout: 30000,
Expand Down
Binary file added src/beacon/public/hdri/potsdamer_platz_1k.hdr
Binary file not shown.
Loading