[client/ui] Unify profile-switching logic in ProfileSwitcher service

Both the tray and the React Profiles page previously had separate
switching logic: the tray applied a status-aware reconnect policy
(Down for error states, Up only when previously Connected/Connecting),
while the React page always called Switch + Up unconditionally with no
Down for LoginFailed/NeedsLogin/SessionExpired.

Introduce a single ProfileSwitcher service that encapsulates the full
reconnect policy. SwitchActive queries the current daemon status, calls
Switch, and launches Down/Up in a background goroutine so the caller
returns immediately after the Switch RPC completes. Both the tray and
the React Profiles page now delegate to this service.

Export the daemon status string constants (StatusConnected, etc.) from
the services package so tray.go no longer duplicates them as private
constants.
This commit is contained in:
Zoltan Papp
2026-05-13 15:46:00 +02:00
parent c0cd88a3d0
commit 803144e569
23 changed files with 2463 additions and 1521 deletions

View File

@@ -1,3 +1,4 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT

View File

@@ -0,0 +1,192 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "@wailsio/runtime";
/**
* NotificationAction represents an action button for a notification.
*/
export class NotificationAction {
/**
* Creates a new NotificationAction instance.
* @param {Partial<NotificationAction>} [$$source = {}] - The source object to create the NotificationAction.
*/
constructor($$source = {}) {
if (/** @type {any} */(false)) {
/**
* @member
* @type {string | undefined}
*/
this["id"] = undefined;
}
if (/** @type {any} */(false)) {
/**
* @member
* @type {string | undefined}
*/
this["title"] = undefined;
}
if (/** @type {any} */(false)) {
/**
* (macOS-specific)
* @member
* @type {boolean | undefined}
*/
this["destructive"] = undefined;
}
Object.assign(this, $$source);
}
/**
* Creates a new NotificationAction instance from a string or object.
* @param {any} [$$source = {}]
* @returns {NotificationAction}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new NotificationAction(/** @type {Partial<NotificationAction>} */($$parsedSource));
}
}
/**
* NotificationCategory groups actions for notifications.
*/
export class NotificationCategory {
/**
* Creates a new NotificationCategory instance.
* @param {Partial<NotificationCategory>} [$$source = {}] - The source object to create the NotificationCategory.
*/
constructor($$source = {}) {
if (/** @type {any} */(false)) {
/**
* @member
* @type {string | undefined}
*/
this["id"] = undefined;
}
if (/** @type {any} */(false)) {
/**
* @member
* @type {NotificationAction[] | undefined}
*/
this["actions"] = undefined;
}
if (/** @type {any} */(false)) {
/**
* @member
* @type {boolean | undefined}
*/
this["hasReplyField"] = undefined;
}
if (/** @type {any} */(false)) {
/**
* @member
* @type {string | undefined}
*/
this["replyPlaceholder"] = undefined;
}
if (/** @type {any} */(false)) {
/**
* @member
* @type {string | undefined}
*/
this["replyButtonTitle"] = undefined;
}
Object.assign(this, $$source);
}
/**
* Creates a new NotificationCategory instance from a string or object.
* @param {any} [$$source = {}]
* @returns {NotificationCategory}
*/
static createFrom($$source = {}) {
const $$createField1_0 = $$createType1;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("actions" in $$parsedSource) {
$$parsedSource["actions"] = $$createField1_0($$parsedSource["actions"]);
}
return new NotificationCategory(/** @type {Partial<NotificationCategory>} */($$parsedSource));
}
}
/**
* NotificationOptions contains configuration for a notification
*/
export class NotificationOptions {
/**
* Creates a new NotificationOptions instance.
* @param {Partial<NotificationOptions>} [$$source = {}] - The source object to create the NotificationOptions.
*/
constructor($$source = {}) {
if (!("id" in $$source)) {
/**
* @member
* @type {string}
*/
this["id"] = "";
}
if (!("title" in $$source)) {
/**
* @member
* @type {string}
*/
this["title"] = "";
}
if (/** @type {any} */(false)) {
/**
* (macOS and Linux only)
* @member
* @type {string | undefined}
*/
this["subtitle"] = undefined;
}
if (/** @type {any} */(false)) {
/**
* @member
* @type {string | undefined}
*/
this["body"] = undefined;
}
if (/** @type {any} */(false)) {
/**
* @member
* @type {string | undefined}
*/
this["categoryId"] = undefined;
}
if (/** @type {any} */(false)) {
/**
* @member
* @type {{ [_ in string]?: any } | undefined}
*/
this["data"] = undefined;
}
Object.assign(this, $$source);
}
/**
* Creates a new NotificationOptions instance from a string or object.
* @param {any} [$$source = {}]
* @returns {NotificationOptions}
*/
static createFrom($$source = {}) {
const $$createField5_0 = $$createType2;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("data" in $$parsedSource) {
$$parsedSource["data"] = $$createField5_0($$parsedSource["data"]);
}
return new NotificationOptions(/** @type {Partial<NotificationOptions>} */($$parsedSource));
}
}
// Private type creation functions
const $$createType0 = NotificationAction.createFrom;
const $$createType1 = $Create.Array($$createType0);
const $$createType2 = $Create.Map($Create.Any, $Create.Any);

View File

@@ -1,107 +0,0 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "@wailsio/runtime";
/**
* NotificationAction represents an action button for a notification.
*/
export class NotificationAction {
"id"?: string;
"title"?: string;
/**
* (macOS-specific)
*/
"destructive"?: boolean;
/** Creates a new NotificationAction instance. */
constructor($$source: Partial<NotificationAction> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new NotificationAction instance from a string or object.
*/
static createFrom($$source: any = {}): NotificationAction {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new NotificationAction($$parsedSource as Partial<NotificationAction>);
}
}
/**
* NotificationCategory groups actions for notifications.
*/
export class NotificationCategory {
"id"?: string;
"actions"?: NotificationAction[];
"hasReplyField"?: boolean;
"replyPlaceholder"?: string;
"replyButtonTitle"?: string;
/** Creates a new NotificationCategory instance. */
constructor($$source: Partial<NotificationCategory> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new NotificationCategory instance from a string or object.
*/
static createFrom($$source: any = {}): NotificationCategory {
const $$createField1_0 = $$createType1;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("actions" in $$parsedSource) {
$$parsedSource["actions"] = $$createField1_0($$parsedSource["actions"]);
}
return new NotificationCategory($$parsedSource as Partial<NotificationCategory>);
}
}
/**
* NotificationOptions contains configuration for a notification
*/
export class NotificationOptions {
"id": string;
"title": string;
/**
* (macOS and Linux only)
*/
"subtitle"?: string;
"body"?: string;
"categoryId"?: string;
"data"?: { [_ in string]?: any };
/** Creates a new NotificationOptions instance. */
constructor($$source: Partial<NotificationOptions> = {}) {
if (!("id" in $$source)) {
this["id"] = "";
}
if (!("title" in $$source)) {
this["title"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new NotificationOptions instance from a string or object.
*/
static createFrom($$source: any = {}): NotificationOptions {
const $$createField5_0 = $$createType2;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("data" in $$parsedSource) {
$$parsedSource["data"] = $$createField5_0($$parsedSource["data"]);
}
return new NotificationOptions($$parsedSource as Partial<NotificationOptions>);
}
}
// Private type creation functions
const $$createType0 = NotificationAction.createFrom;
const $$createType1 = $Create.Array($$createType0);
const $$createType2 = $Create.Map($Create.Any, $Create.Any);

View File

@@ -0,0 +1,101 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
/**
* Service represents the notifications service
* @module
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as $models from "./models.js";
/**
* @returns {$CancellablePromise<boolean>}
*/
export function CheckNotificationAuthorization() {
return $Call.ByID(2216952893);
}
/**
* @param {$models.NotificationCategory} category
* @returns {$CancellablePromise<void>}
*/
export function RegisterNotificationCategory(category) {
return $Call.ByID(2917562919, category);
}
/**
* @returns {$CancellablePromise<void>}
*/
export function RemoveAllDeliveredNotifications() {
return $Call.ByID(3956282340);
}
/**
* @returns {$CancellablePromise<void>}
*/
export function RemoveAllPendingNotifications() {
return $Call.ByID(108821341);
}
/**
* @param {string} identifier
* @returns {$CancellablePromise<void>}
*/
export function RemoveDeliveredNotification(identifier) {
return $Call.ByID(975691940, identifier);
}
/**
* @param {string} identifier
* @returns {$CancellablePromise<void>}
*/
export function RemoveNotification(identifier) {
return $Call.ByID(3966653866, identifier);
}
/**
* @param {string} categoryID
* @returns {$CancellablePromise<void>}
*/
export function RemoveNotificationCategory(categoryID) {
return $Call.ByID(2032615554, categoryID);
}
/**
* @param {string} identifier
* @returns {$CancellablePromise<void>}
*/
export function RemovePendingNotification(identifier) {
return $Call.ByID(3729049703, identifier);
}
/**
* Public methods that delegate to the implementation.
* @returns {$CancellablePromise<boolean>}
*/
export function RequestNotificationAuthorization() {
return $Call.ByID(3933442950);
}
/**
* @param {$models.NotificationOptions} options
* @returns {$CancellablePromise<void>}
*/
export function SendNotification(options) {
return $Call.ByID(3968228732, options);
}
/**
* @param {$models.NotificationOptions} options
* @returns {$CancellablePromise<void>}
*/
export function SendNotificationWithActions(options) {
return $Call.ByID(1886542847, options);
}

View File

@@ -1,62 +0,0 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
/**
* Service represents the notifications service
* @module
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as $models from "./models.js";
export function CheckNotificationAuthorization(): $CancellablePromise<boolean> {
return $Call.ByID(2216952893);
}
export function RegisterNotificationCategory(category: $models.NotificationCategory): $CancellablePromise<void> {
return $Call.ByID(2917562919, category);
}
export function RemoveAllDeliveredNotifications(): $CancellablePromise<void> {
return $Call.ByID(3956282340);
}
export function RemoveAllPendingNotifications(): $CancellablePromise<void> {
return $Call.ByID(108821341);
}
export function RemoveDeliveredNotification(identifier: string): $CancellablePromise<void> {
return $Call.ByID(975691940, identifier);
}
export function RemoveNotification(identifier: string): $CancellablePromise<void> {
return $Call.ByID(3966653866, identifier);
}
export function RemoveNotificationCategory(categoryID: string): $CancellablePromise<void> {
return $Call.ByID(2032615554, categoryID);
}
export function RemovePendingNotification(identifier: string): $CancellablePromise<void> {
return $Call.ByID(3729049703, identifier);
}
/**
* Public methods that delegate to the implementation.
*/
export function RequestNotificationAuthorization(): $CancellablePromise<boolean> {
return $Call.ByID(3933442950);
}
export function SendNotification(options: $models.NotificationOptions): $CancellablePromise<void> {
return $Call.ByID(3968228732, options);
}
export function SendNotificationWithActions(options: $models.NotificationOptions): $CancellablePromise<void> {
return $Call.ByID(1886542847, options);
}