mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 19:07:02 +01:00
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
94 lines
3.8 KiB
Dart
94 lines
3.8 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 ''.
|
|
///
|
|
/// [override] defaults to `$TODO_BROWSER`; [exists] to a file check.
|
|
String findBrowser({String? override, bool Function(String path)? exists}) {
|
|
// 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 = [
|
|
override ?? 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 && (exists ?? (p) => File(p).existsSync())(path),
|
|
orElse: () => '',
|
|
);
|
|
}
|
|
|
|
/// Command-line arguments for the app window.
|
|
List<String> browserArgs(String home) => [
|
|
'--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',
|
|
// Keep Chrome off the GNOME keyring. While the keyring is locked, Chrome
|
|
// blocks every non-incognito page load on its first cookie access, and an
|
|
// "Unlock Keyring" prompt waits on another workspace: the window stays a
|
|
// blank grey. Nothing in this profile needs OS-level encryption (the
|
|
// GitHub token is in localStorage, not the password store).
|
|
'--password-store=basic',
|
|
// The distro wrapper appends ~/.config/chromium-flags.conf to every launch,
|
|
// which here carries `--load-extension` for Claude in Chrome. Logged out in
|
|
// this profile, it opened a claude.ai login tab in a second, normal browser
|
|
// window on every start. This flag also drops command-line extensions.
|
|
'--disable-extensions',
|
|
];
|
|
|
|
/// 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.
|
|
/// [browser] and [start] exist for tests; they default to [findBrowser] and
|
|
/// [Process.start].
|
|
Future<bool> launchBrowser(
|
|
String home, {
|
|
String? browser,
|
|
Future<Process> Function(String executable, List<String> args)? start,
|
|
}) async {
|
|
browser ??= findBrowser();
|
|
if (browser.isEmpty) {
|
|
stderr.writeln(
|
|
'No Chrome-family browser found; open '
|
|
'http://localhost:$kWrapperPort manually.',
|
|
);
|
|
return false;
|
|
}
|
|
final process = await (start ?? Process.start)(browser, browserArgs(home));
|
|
|
|
final started = DateTime.now();
|
|
await process.exitCode;
|
|
return DateTime.now().difference(started) > const Duration(seconds: 5);
|
|
}
|