From 4b88d64b2de343e6cea99767f957255665cbf4d1 Mon Sep 17 00:00:00 2001 From: Hampus Date: Thu, 8 Oct 2026 03:03:05 +0200 Subject: [PATCH] fix(app): show the official badge and a visible account menu (#3265) --- .../AccountProfilePicker.module.css | 13 ++++++------ .../InstanceDirectoryStorage.test.ts | 16 +++++++++++++++ .../src/OfficialInstance.ts | 20 +++++++++++++++---- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/fluxer_app/src/features/auth/components/accounts/account_switcher_modal/AccountProfilePicker.module.css b/fluxer_app/src/features/auth/components/accounts/account_switcher_modal/AccountProfilePicker.module.css index d41da6978..44b33ce57 100644 --- a/fluxer_app/src/features/auth/components/accounts/account_switcher_modal/AccountProfilePicker.module.css +++ b/fluxer_app/src/features/auth/components/accounts/account_switcher_modal/AccountProfilePicker.module.css @@ -231,16 +231,17 @@ justify-content: center; box-sizing: border-box; padding: 0; - border: 0; + border: 0.0625rem solid color-mix(in srgb, var(--account-profile-name-color) 45%, transparent); border-radius: 50%; - background: color-mix(in srgb, var(--account-profile-name-color) 14%, transparent); - color: color-mix(in srgb, var(--account-profile-name-color) 78%, transparent); + background: var(--background-tertiary); + color: var(--text-primary); + box-shadow: 0 0.125rem 0.5rem rgb(0 0 0 / 0.28); cursor: pointer; outline: none; pointer-events: auto; transition: background-color 40ms ease, - color 40ms ease; + border-color 40ms ease; } .moreButton:disabled { @@ -250,8 +251,8 @@ :global(:where(html.window-focused, html.unfocused-fully-interactive):not(.window-focus-activation-guard)) .moreButton:not(:disabled):hover { - background: color-mix(in srgb, var(--account-profile-name-color) 26%, transparent); - color: var(--account-profile-name-color); + border-color: color-mix(in srgb, var(--account-profile-name-color) 70%, transparent); + background: color-mix(in srgb, var(--text-primary) 14%, var(--background-tertiary)); } .nameText { diff --git a/fluxer_app/src/features/auth/flow/instance_selector/InstanceDirectoryStorage.test.ts b/fluxer_app/src/features/auth/flow/instance_selector/InstanceDirectoryStorage.test.ts index 7671fcb73..e90a80b95 100644 --- a/fluxer_app/src/features/auth/flow/instance_selector/InstanceDirectoryStorage.test.ts +++ b/fluxer_app/src/features/auth/flow/instance_selector/InstanceDirectoryStorage.test.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import {resolveInstanceLabel} from '@app/features/auth/flow/instance_selector/InstanceDirectoryStorage'; +import {isOfficialInstanceHost} from '@fluxer/instance_bootstrap/src/OfficialInstance'; import {describe, expect, test} from 'vitest'; describe('resolveInstanceLabel', () => { @@ -23,3 +24,18 @@ describe('resolveInstanceLabel', () => { expect(resolveInstanceLabel(null, 'fluxer.app')).toBe('Fluxer'); }); }); + +describe('isOfficialInstanceHost', () => { + test('an official API endpoint with a path is official', () => { + expect(isOfficialInstanceHost('https://web.canary.fluxer.app/api')).toBe(true); + expect(isOfficialInstanceHost('https://canary.fluxer.com/api/')).toBe(true); + expect(isOfficialInstanceHost('https://web.fluxer.app')).toBe(true); + }); + + test('other hosts are not official whatever the path', () => { + expect(isOfficialInstanceHost('http://localhost:48090/api')).toBe(false); + expect(isOfficialInstanceHost('https://fluxer.app.example.com/api')).toBe(false); + expect(isOfficialInstanceHost('https://example.com/web.fluxer.app')).toBe(false); + expect(isOfficialInstanceHost('https://web.fluxer.app/api?x=1')).toBe(false); + }); +}); diff --git a/packages/instance_bootstrap/src/OfficialInstance.ts b/packages/instance_bootstrap/src/OfficialInstance.ts index 93f0902b1..fb66a3ddf 100644 --- a/packages/instance_bootstrap/src/OfficialInstance.ts +++ b/packages/instance_bootstrap/src/OfficialInstance.ts @@ -1,6 +1,10 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {normalizeHTTPNetworkOrigin} from '@fluxer/instance_bootstrap/src/NetworkOrigin'; +import { + type CanonicalNetworkEndpointRule, + HTTP_NETWORK_PROTOCOLS, + normalizeCanonicalNetworkEndpoint, +} from '@fluxer/instance_bootstrap/src/NetworkOrigin'; export const OFFICIAL_INSTANCE_NAME = 'Fluxer'; @@ -70,11 +74,19 @@ export const OFFICIAL_INSTANCE_HOSTS: ReadonlyArray = Object.freeze([ 'canary.fluxer.com', ]); +const OFFICIAL_HOST_LOOKUP_RULE: CanonicalNetworkEndpointRule = Object.freeze({ + protocols: HTTP_NETWORK_PROTOCOLS, + allowPath: true, + allowRelative: false, +}); + export function isOfficialInstanceHost(value: string): boolean { - const origin = normalizeHTTPNetworkOrigin(value); - if (origin == null) { + const endpoint = normalizeCanonicalNetworkEndpoint(value, OFFICIAL_HOST_LOOKUP_RULE); + if (endpoint == null) { return false; } - const host = origin.slice(origin.indexOf('//') + 2).toLowerCase(); + const authority = endpoint.slice(endpoint.indexOf('//') + 2); + const pathStart = authority.indexOf('/'); + const host = (pathStart === -1 ? authority : authority.slice(0, pathStart)).toLowerCase(); return OFFICIAL_INSTANCE_HOSTS.some((officialHost) => officialHost === host); }