mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-03-21 02:13:39 +00:00
Force simulate mode when OSM OAuth secrets are missing
Preview/PR builds don't have access to GitHub Secrets, so the OAuth client IDs are empty. Previously this caused a runtime crash from keys.dart throwing on empty values. Now we detect missing secrets and force simulate mode, which already fully supports fake auth and uploads. Also fixes a latent bug where forceLogin() would crash with LateInitializationError in simulate mode since _helper is never initialized when OAuth setup is skipped. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,20 @@
|
||||
// OpenStreetMap OAuth client IDs for this app.
|
||||
// These must be provided via --dart-define at build time.
|
||||
|
||||
/// Whether OSM OAuth secrets were provided at build time.
|
||||
/// When false, the app should force simulate mode.
|
||||
bool get kHasOsmSecrets {
|
||||
const prod = String.fromEnvironment('OSM_PROD_CLIENTID');
|
||||
const sandbox = String.fromEnvironment('OSM_SANDBOX_CLIENTID');
|
||||
return prod.isNotEmpty && sandbox.isNotEmpty;
|
||||
}
|
||||
|
||||
String get kOsmProdClientId {
|
||||
const fromBuild = String.fromEnvironment('OSM_PROD_CLIENTID');
|
||||
if (fromBuild.isNotEmpty) return fromBuild;
|
||||
|
||||
throw Exception('OSM_PROD_CLIENTID not configured. Use --dart-define=OSM_PROD_CLIENTID=your_id');
|
||||
return fromBuild;
|
||||
}
|
||||
|
||||
String get kOsmSandboxClientId {
|
||||
const fromBuild = String.fromEnvironment('OSM_SANDBOX_CLIENTID');
|
||||
if (fromBuild.isNotEmpty) return fromBuild;
|
||||
|
||||
throw Exception('OSM_SANDBOX_CLIENTID not configured. Use --dart-define=OSM_SANDBOX_CLIENTID=your_id');
|
||||
return fromBuild;
|
||||
}
|
||||
@@ -23,6 +23,8 @@ class UploadModeSection extends StatelessWidget {
|
||||
subtitle: Text(locService.t('uploadMode.subtitle')),
|
||||
trailing: DropdownButton<UploadMode>(
|
||||
value: appState.uploadMode,
|
||||
// This entire section is gated behind kEnableDevelopmentModes
|
||||
// in osm_account_screen.dart, so all modes are always available here.
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: UploadMode.production,
|
||||
|
||||
@@ -36,6 +36,7 @@ class AuthService {
|
||||
|
||||
void setUploadMode(UploadMode mode) {
|
||||
_mode = mode;
|
||||
if (mode == UploadMode.simulate || !kHasOsmSecrets) return;
|
||||
final isSandbox = (mode == UploadMode.sandbox);
|
||||
final authBase = isSandbox
|
||||
? 'https://master.apis.dev.openstreetmap.org'
|
||||
@@ -150,7 +151,9 @@ class AuthService {
|
||||
|
||||
// Force a fresh login by clearing stored tokens
|
||||
Future<String?> forceLogin() async {
|
||||
await _helper.removeAllTokens();
|
||||
if (_mode != UploadMode.simulate) {
|
||||
await _helper.removeAllTokens();
|
||||
}
|
||||
_displayName = null;
|
||||
return await login();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:collection/collection.dart';
|
||||
|
||||
import '../models/tile_provider.dart';
|
||||
import '../dev_config.dart';
|
||||
import '../keys.dart';
|
||||
|
||||
// Enum for upload mode (Production, OSM Sandbox, Simulate)
|
||||
enum UploadMode { production, sandbox, simulate }
|
||||
@@ -41,7 +42,8 @@ class SettingsState extends ChangeNotifier {
|
||||
bool _offlineMode = false;
|
||||
bool _pauseQueueProcessing = false;
|
||||
int _maxNodes = kDefaultMaxNodes;
|
||||
UploadMode _uploadMode = kEnableDevelopmentModes ? UploadMode.simulate : UploadMode.production;
|
||||
// Default must account for missing secrets (preview builds) even before init() runs
|
||||
UploadMode _uploadMode = (kEnableDevelopmentModes || !kHasOsmSecrets) ? UploadMode.simulate : UploadMode.production;
|
||||
FollowMeMode _followMeMode = FollowMeMode.follow;
|
||||
bool _proximityAlertsEnabled = false;
|
||||
int _proximityAlertDistance = kProximityAlertDefaultDistance;
|
||||
@@ -150,8 +152,16 @@ class SettingsState extends ChangeNotifier {
|
||||
await prefs.setInt(_uploadModePrefsKey, _uploadMode.index);
|
||||
}
|
||||
|
||||
// In production builds, force production mode if development modes are disabled
|
||||
if (!kEnableDevelopmentModes && _uploadMode != UploadMode.production) {
|
||||
// Override persisted upload mode when the current build configuration
|
||||
// doesn't support it. This handles two cases:
|
||||
// 1. Preview/PR builds without OAuth secrets — force simulate to avoid crashes
|
||||
// 2. Production builds — force production (prefs may have sandbox/simulate
|
||||
// from a previous dev build on the same device)
|
||||
if (!kHasOsmSecrets && _uploadMode != UploadMode.simulate) {
|
||||
debugPrint('SettingsState: No OSM secrets available, forcing simulate mode');
|
||||
_uploadMode = UploadMode.simulate;
|
||||
await prefs.setInt(_uploadModePrefsKey, _uploadMode.index);
|
||||
} else if (kHasOsmSecrets && !kEnableDevelopmentModes && _uploadMode != UploadMode.production) {
|
||||
debugPrint('SettingsState: Development modes disabled, forcing production mode');
|
||||
_uploadMode = UploadMode.production;
|
||||
await prefs.setInt(_uploadModePrefsKey, _uploadMode.index);
|
||||
@@ -258,11 +268,10 @@ class SettingsState extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> setUploadMode(UploadMode mode) async {
|
||||
// In production builds, only allow production mode
|
||||
if (!kEnableDevelopmentModes && mode != UploadMode.production) {
|
||||
debugPrint('SettingsState: Development modes disabled, forcing production mode');
|
||||
mode = UploadMode.production;
|
||||
}
|
||||
// The upload mode dropdown is only visible when kEnableDevelopmentModes is
|
||||
// true (gated in osm_account_screen.dart), so no secrets/dev-mode guards
|
||||
// are needed here. The init() method handles forcing the correct mode on
|
||||
// startup for production builds and builds without OAuth secrets.
|
||||
|
||||
_uploadMode = mode;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
73
test/state/settings_state_test.dart
Normal file
73
test/state/settings_state_test.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:deflockapp/state/settings_state.dart';
|
||||
import 'package:deflockapp/keys.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
});
|
||||
|
||||
group('kHasOsmSecrets (no --dart-define)', () {
|
||||
test('is false when built without secrets', () {
|
||||
expect(kHasOsmSecrets, isFalse);
|
||||
});
|
||||
|
||||
test('client ID getters return empty strings instead of throwing', () {
|
||||
expect(kOsmProdClientId, isEmpty);
|
||||
expect(kOsmSandboxClientId, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('SettingsState without secrets', () {
|
||||
test('defaults to simulate mode', () {
|
||||
final state = SettingsState();
|
||||
expect(state.uploadMode, UploadMode.simulate);
|
||||
});
|
||||
|
||||
test('init() forces simulate even if prefs has production stored', () async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
'upload_mode': UploadMode.production.index,
|
||||
});
|
||||
|
||||
final state = SettingsState();
|
||||
await state.init();
|
||||
|
||||
expect(state.uploadMode, UploadMode.simulate);
|
||||
|
||||
// Verify it persisted the override
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
expect(prefs.getInt('upload_mode'), UploadMode.simulate.index);
|
||||
});
|
||||
|
||||
test('init() forces simulate even if prefs has sandbox stored', () async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
'upload_mode': UploadMode.sandbox.index,
|
||||
});
|
||||
|
||||
final state = SettingsState();
|
||||
await state.init();
|
||||
|
||||
expect(state.uploadMode, UploadMode.simulate);
|
||||
});
|
||||
|
||||
test('init() keeps simulate if already simulate', () async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
'upload_mode': UploadMode.simulate.index,
|
||||
});
|
||||
|
||||
final state = SettingsState();
|
||||
await state.init();
|
||||
|
||||
expect(state.uploadMode, UploadMode.simulate);
|
||||
});
|
||||
|
||||
test('setUploadMode() allows simulate', () async {
|
||||
final state = SettingsState();
|
||||
await state.setUploadMode(UploadMode.simulate);
|
||||
|
||||
expect(state.uploadMode, UploadMode.simulate);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user