todo-app/lib/desktop/browser_launcher.dart
Krzysztof kuhy Rudnicki 381afd1d77 Split the last three oversized lib/bin files
github_mirror_screen.dart  368 -> 232 + device_code_dialog.dart (109) and
                             a widget part
  wrapper_server.dart        336 -> 223 + wrapper_sync_routes.dart, the two
                             credential-provisioning routes
  bin/todo_desktop.dart      253 -> 199 + lib/desktop/browser_launcher.dart

The fixed port moved into browser_launcher.dart as kWrapperPort, with the
entry point aliasing it, so the "must not move" reason is stated once next
to the code that acts on it.

lib/ and bin/ are now entirely under the cap. The two remaining analyze
infos (bin/todo_desktop.dart cascade_invocations,
capture_screen_sync.dart unnecessary_unawaited) are pre-existing --
verified by analyzing e0eb8b9 in a worktree, which reports the same two.

Verified: 313 tests pass, coverage unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 16:29:35 +02:00

72 lines
2.7 KiB
Dart

/// Finding and launching the Chrome window the desktop app runs in.
///
/// Split out of `bin/todo_desktop.dart` for file size. The profile directory
/// is fixed on purpose: the GitHub token (localStorage) and the notes
/// (IndexedDB) are keyed by origin and live in that profile, so a moving
/// profile dir would silently log the user out and hide their notes.
library;
import 'dart:io';
import 'package:path/path.dart' as p;
/// Port must stay fixed: the browser keys `localStorage` (the GitHub token)
/// and IndexedDB (the notes) by origin, so a different port looks like a
/// different app with no token and no notes.
const kWrapperPort = 8730;
/// Browser profile directory for the desktop app.
///
/// Must stay stable: the GitHub token (localStorage) and the notes (IndexedDB)
/// live inside it, so a changing path silently logs the user out and hides
/// their notes. It doubles as the marker for "a window is still attached".
String profileDir(String home) =>
p.join(home, '.local', 'share', 'todo-desktop', 'profile');
/// Returns the first Chrome-family browser on this machine, or ''.
String findBrowser() {
// Ordered by preference, and deliberately broad: this machine runs Thorium
// behind /opt/google/chrome, and has a policy that uninstalls the `chromium`
// package, so assuming any single browser is wrong. TODO_BROWSER overrides.
final candidates = [
Platform.environment['TODO_BROWSER'] ?? '',
'/opt/google/chrome/chrome',
'/opt/thorium-browser/thorium-browser',
'/usr/bin/google-chrome-stable',
'/usr/bin/chromium',
'/usr/bin/brave',
];
return candidates.firstWhere(
(path) => path.isNotEmpty && File(path).existsSync(),
orElse: () => '',
);
}
/// Launches the app in a Chrome-family browser with a **stable** profile
/// directory, since the token and notes live in that profile.
///
/// Returns true when the browser ran long enough to have owned the session.
Future<bool> launchBrowser(String home) async {
final browser = findBrowser();
if (browser.isEmpty) {
stderr.writeln(
'No Chrome-family browser found; open '
'http://localhost:$kWrapperPort manually.',
);
return false;
}
final process = await Process.start(browser, [
'--app=http://localhost:$kWrapperPort',
'--user-data-dir=${profileDir(home)}',
// Sets WM_CLASS, which the .desktop entry matches on via StartupWMClass.
// Without it the window inherits the browser's class and the taskbar shows
// a browser icon instead of todo's.
'--class=todo',
'--no-first-run',
]);
final started = DateTime.now();
await process.exitCode;
return DateTime.now().difference(started) > const Duration(seconds: 5);
}