Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,6 @@ module.exports = {
"e2e/soak/support/probes.ts",
"e2e/soak/support/tracker.ts",
"e2e/tests/send-across-restart.spec.ts",
"electron/embeddedServerManager.ts",
"electron/gameList.ts",
"electron/helperStartup.ts",
"electron/presenceHelper.ts",
"electron/processWatcher.ts",
"src/components/AuthLoadingOverlay.tsx",
"src/components/mainApp.tsx",
"src/components/microphoneDebugOverlay.tsx",
"src/components/onboarding/OnboardingTour.tsx",
"src/components/serviceStatusBanner.tsx",
"src/components/sidebar.tsx",
"src/components/updateToastState.ts",
"src/components/videoDebugOverlay.tsx",
"src/lib/reports/callTimeline.ts",
"src/packages/addons/src/pluginPanels.ts",
"src/packages/common/src/auth/keycloak.ts",
Expand Down
7 changes: 3 additions & 4 deletions electron/embeddedServerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ function explainExit(
source: LogSource,
code: number | null,
): string {
const label =
source === "sfu" ? "SFU" : source === "worker" ? "Media worker" : "Server";
const label = { sfu: "SFU", worker: "Media worker" }[source as string] ?? "Server";
const lines = recentOutput.get(outputKey(owner, source)) ?? [];

// Prefer a line that names the actual failure over the last line, which is
Expand Down Expand Up @@ -347,7 +346,7 @@ function readBundledVersions(): { server?: string; sfu?: string; worker?: string
const runtimeRoot = packagedRuntimeRoot();
const packaged = runtimeRoot ? join(runtimeRoot, "versions.json") : null;
const dev = join(app.getAppPath(), "build", "embedded-server", "versions.json");
const path = packaged && existsSync(packaged) ? packaged : existsSync(dev) ? dev : null;
const path = [packaged, dev].find((p) => p && existsSync(p)) ?? null;
if (!path) return {};

try {
Expand All @@ -374,7 +373,7 @@ function getWorkerEntryPath(): string | null {
function getSfuBinaryPath(): string | null {
const ext = process.platform === "win32" ? ".exe" : "";
const name = `gryt_sfu${ext}`;
const ebOs = process.platform === "win32" ? "win" : process.platform === "darwin" ? "mac" : "linux";
const ebOs = { win32: "win", darwin: "mac" }[process.platform as string] ?? "linux";
const ebArch = process.arch === "arm64" ? "arm64" : "x64";
const runtimeRoot = packagedRuntimeRoot();
const packaged = runtimeRoot ? join(runtimeRoot, "sfu", name) : null;
Expand Down
2 changes: 1 addition & 1 deletion electron/gameList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function createGameIndex(games: readonly GameEntry[], platform: NodeJS.Pl
const byComm = new Map<string, string>();

// Proton runs Windows games on Linux under their Windows names.
const oses: GameOs[] = platform === "linux" ? ["linux", "win32"] : platform === "darwin" ? ["darwin"] : ["win32"];
const oses: GameOs[] = ({ linux: ["linux", "win32"], darwin: ["darwin"] } as Record<string, GameOs[]>)[platform] ?? ["win32"];

for (const game of games) {
for (const id of game.ids) if (!byApp.has(id)) byApp.set(id, game.name);
Expand Down
3 changes: 2 additions & 1 deletion electron/helperStartup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ export const runQuietly = (command: string, args: string[]): Promise<number> =>
new Promise((resolve) => {
execFile(command, args, { timeout: 10_000, windowsHide: true }, (err) => {
const code = (err as { code?: unknown } | null)?.code;
resolve(err ? (typeof code === "number" ? code : 1) : 0);
if (!err) resolve(0);
else resolve(typeof code === "number" ? code : 1);
});
});

Expand Down
5 changes: 4 additions & 1 deletion electron/presenceHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ export function createPresenceSource(options: PresenceSourceOptions): PresenceSo
link = null;
await this.stop();
},
via: () => (link ? "helper" : host ? "app" : null),
via: () => {
if (link) return "helper";
return host ? "app" : null;
},
};
}
9 changes: 6 additions & 3 deletions electron/processWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,12 @@ export async function listRunningPrograms(): Promise<string[]> {
seen.set(key, basename(raw.replace(/\\/g, "/")));
}

return [...seen.values()].sort((a, b) =>
a.toLowerCase() < b.toLowerCase() ? -1 : a.toLowerCase() > b.toLowerCase() ? 1 : 0,
);
return [...seen.values()].sort((a, b) => {
const x = a.toLowerCase();
const y = b.toLowerCase();
if (x === y) return 0;
return x < y ? -1 : 1;
});
}

/* ── The watcher ─────────────────────────────────────────────────────── */
Expand Down
5 changes: 4 additions & 1 deletion scripts/check-embedded-starting.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ const statusLines = (() => {
assert.notEqual(start, -1, `${SIDEBAR} no longer reads rawStatus. Move this check with it.`);
const ring = sidebar.indexOf("const ringState", start);
assert.notEqual(ring, -1, `${SIDEBAR} no longer declares ringState after rawStatus. Move this check with it.`);
return sidebar.slice(start, sidebar.indexOf(";", ring) + 1).replace(/: ServerRingState/g, "");
const fn = sidebar.indexOf("function ringStateOf(");
assert.notEqual(fn, -1, `${SIDEBAR} no longer has ringStateOf(). Move this check with it.`);
const ringStateOf = sidebar.slice(fn, sidebar.indexOf("\n}\n", fn) + 2).replace(/\(s: \{[^}]*\}\): ServerRingState/, "(s)");
return `${ringStateOf}\n${sidebar.slice(start, sidebar.indexOf(";", ring) + 1)}`;
})();

/* The hover card's connection words, each with the condition it is drawn under. A new one
Expand Down
15 changes: 7 additions & 8 deletions src/components/AuthLoadingOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ const SPLASH_COPY = {
slow: "This is taking longer than it should. Gryt will carry on without an account in a moment.",
} as const;

function splashMessage(reach: AccountsReach | null, slow: boolean) {
if (reach === "unreachable") return "accounts-down";
if (reach === "offline") return "offline";
return slow ? "slow" : "normal";
}

export function AuthLoadingOverlay({
open,
fadeDurationMs = 450,
Expand Down Expand Up @@ -62,14 +68,7 @@ export function AuthLoadingOverlay({
};
}, [open]);

const message =
reach === "unreachable"
? "accounts-down"
: reach === "offline"
? "offline"
: slow
? "slow"
: "normal";
const message = splashMessage(reach, slow);

useEffect(() => {
if (!open) {
Expand Down
17 changes: 7 additions & 10 deletions src/components/mainApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ export function MainApp() {

/* What a bug report calls "where you were". Recorded here rather than in the
report form, which would always answer "the report form". */
useRememberPlace(
showDiscovery ? "discovery" : Object.keys(servers).length > 0 ? "server" : "empty",
);
const hasServers = Object.keys(servers).length > 0;
const emptyPlace = hasServers ? "server" : "empty";
useRememberPlace(showDiscovery ? "discovery" : emptyPlace);

/* The page to reopen on the next launch. Not the one above, which is only what
a bug report says and is never read back. */
Expand All @@ -87,13 +87,10 @@ export function MainApp() {
<DmFeeds />
<NewMessageDialog />

{dmSpaceOpen && Object.keys(servers).length > 0 ? (
<ServerView dmSpace />
) : showDiscovery ? (
<Discovery />
) : Object.keys(servers).length > 0 ? (
<ServerView />
) : (
{dmSpaceOpen && hasServers && <ServerView dmSpace />}
{!(dmSpaceOpen && hasServers) && showDiscovery && <Discovery />}
{!dmSpaceOpen && !showDiscovery && hasServers && <ServerView />}
{!showDiscovery && !hasServers && (
/* Was a line of text with a finger emoji pointing at the sidebar. That is
the tour's job; somebody who dismissed it still ends up here. */
<div className="flex grow items-center justify-center">
Expand Down
10 changes: 7 additions & 3 deletions src/components/microphoneDebugOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ const StateDot = ({ on }: { on: boolean }) => (
<PiCircleFill size={8} color={on ? "var(--gryt-success-11)" : "var(--gryt-danger-11)"} />
);

function volumeColour(volume: number): string {
if (volume > 80) return "var(--gryt-danger-9)";
return volume > 50 ? "var(--gryt-warning-9)" : "var(--gryt-success-9)";
}

export function MicrophoneDebugOverlay({ isVisible }: MicrophoneDebugOverlayProps) {
const {
microphoneBuffer,
Expand Down Expand Up @@ -143,7 +148,7 @@ export function MicrophoneDebugOverlay({ isVisible }: MicrophoneDebugOverlayProp
<div style={{ marginLeft: "8px", fontSize: "11px" }}>
<div>ID: {micID ? micID.slice(0, 8) + "..." : "None"}</div>
<div>Name: {currentDevice?.label || "Unknown"}</div>
<div>Available: {devicesLoading ? "Loading..." : devicesError ? "Error" : `${devices.length} devices`}</div>
<div>Available: {devicesLoading && "Loading..."}{!devicesLoading && devicesError && "Error"}{!devicesLoading && !devicesError && `${devices.length} devices`}</div>
{devicesError && (
<div style={{ color: "var(--gryt-danger-11)", fontSize: "10px" }}>
{devicesError}
Expand Down Expand Up @@ -201,8 +206,7 @@ export function MicrophoneDebugOverlay({ isVisible }: MicrophoneDebugOverlayProp
<div style={{
width: `${Math.min(processedVolume, 100)}%`,
height: "100%",
backgroundColor: processedVolume > 80 ? "var(--gryt-danger-9)" :
processedVolume > 50 ? "var(--gryt-warning-9)" : "var(--gryt-success-9)",
backgroundColor: volumeColour(processedVolume),
transition: "width 0.1s ease-out"
}} />
</div>
Expand Down
6 changes: 2 additions & 4 deletions src/components/onboarding/OnboardingTour.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,7 @@ export function OnboardingTour({ onFinish }: { onFinish: () => void }) {
const rightOfTarget = cut ? cut.left + cut.width + OFFSET : 0;
const fitsOnTheRight = rightOfTarget + CARD_WIDTH + 16 <= window.innerWidth;

const card = !cut
? null
: step.side === "right"
const card = cut && (step.side === "right"
? {
top: Math.min(
Math.max(cut.top + cut.height / 2 - 90, 16),
Expand All @@ -342,7 +340,7 @@ export function OnboardingTour({ onFinish }: { onFinish: () => void }) {
Math.max(cut.left + cut.width / 2 - CARD_WIDTH / 2, 16),
window.innerWidth - CARD_WIDTH - 16
)
};
});

// **Portaled to body, and it has to be.** The app renders inside
// `.radix-themes`, a stacking context, so no z-index would beat a dialog.
Expand Down
21 changes: 9 additions & 12 deletions src/components/serviceStatusBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ const TONE: Record<AnnouncementType, string> = {
const OFFLINE =
"You're offline, so Gryt couldn't check your account. It'll check again once you're back online.";

/* Not reachable is a warning whatever else is going on. Offline is just a fact. */
const UNANNOUNCED = {
unreachable: { message: GENERIC, Glyph: PiWarningFill },
offline: { message: OFFLINE, Glyph: PiInfoFill },
};

/**
* Signed in, or launched while accounts couldn't be checked. A guest on a normal
* day has nothing that breaks when the account services go down.
Expand Down Expand Up @@ -144,21 +150,12 @@ export function ServiceStatusBanner() {

if (!banner) return null;

const message =
const { message, Glyph } =
banner.kind === "announced"
? banner.announcement.message
: banner.kind === "offline"
? OFFLINE
: GENERIC;
? { message: banner.announcement.message, Glyph: ICON[banner.announcement.type] }
: UNANNOUNCED[banner.kind];
const tone =
banner.kind === "announced" ? TONE[banner.announcement.type] : "warning";
/* Not reachable is a warning whatever else is going on. Offline is just a fact. */
const Glyph =
banner.kind === "announced"
? ICON[banner.announcement.type]
: banner.kind === "offline"
? PiInfoFill
: PiWarningFill;

if (message === dismissed) return null;

Expand Down
40 changes: 21 additions & 19 deletions src/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ async function copyServerAddress(host: string): Promise<void> {
}
}

function ringStateOf(s: { isStarting: boolean; isSettling: boolean; isReconnecting: boolean; isConnecting: boolean }): ServerRingState {
if (s.isStarting) return "starting";
if (s.isSettling) return "settling";
if (s.isReconnecting) return "reconnecting";
return s.isConnecting ? "connecting" : "none";
}

function iconOpacity(s: { bright: boolean; isUnavailable: boolean; awaitingApproval: boolean; isReconnecting: boolean }) {
if (s.bright) return 1;
if (s.isUnavailable) return 0.45;
if (s.awaitingApproval) return 0.4;
return s.isReconnecting ? undefined : 0.5;
}

export function Sidebar({ setShowAddServer }: SidebarProps) {
const { isSignedIn, login, logout } = useAccount();
const { nickname, avatarDataUrl, setShowSettings, openSettings } = useSettings();
Expand Down Expand Up @@ -439,15 +453,7 @@ function ServerItem({
*/
const isSettling = !rawStatus && !settleExpired && !isStarting;

const ringState: ServerRingState = isStarting
? "starting"
: isSettling
? "settling"
: isReconnecting
? "reconnecting"
: isConnecting
? "connecting"
: "none";
const ringState = ringStateOf({ isStarting, isSettling, isReconnecting, isConnecting });
/* Waiting on a moderator (GRYT-289). Not a connection state: the server is
reachable and answering, it just has not let this person in yet. */
const awaitingApproval = Boolean(servers[host]?.approvalRequestedAt);
Expand Down Expand Up @@ -490,16 +496,12 @@ function ServerItem({
style={{
// A server you just created keeps its colour. Greying it
// would say something is wrong; the ring says not ready.
opacity:
currentlyViewingServer?.host === host || isStarting
? 1
: isUnavailable
? 0.45
: awaitingApproval
? 0.4
: isReconnecting
? undefined
: 0.5,
opacity: iconOpacity({
bright: currentlyViewingServer?.host === host || isStarting,
isUnavailable,
awaitingApproval,
isReconnecting,
}),
filter:
!isStarting &&
(isUnavailable || isReconnecting || awaitingApproval)
Expand Down
6 changes: 5 additions & 1 deletion src/components/updateToastState.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type { UpdateStatus } from "../lib/electron";

function downloadPhase(autoDownload: boolean | undefined) {
return autoDownload ? "downloading" : "waiting";
}

/** One id for every release, so a newer one redraws the toast in place rather than stacking a second. */
export const UPDATE_TOAST_ID = "update";

Expand Down Expand Up @@ -38,7 +42,7 @@ export function nextShown(current: Shown | null, status: UpdateStatus): Shown |
version: status.version,
/* Announced with `autoDownload` means the bytes are already moving —
the main process says so once electron-updater starts fetching. */
phase: status.storeManaged ? "store" : status.autoDownload ? "downloading" : "waiting",
phase: status.storeManaged ? "store" : downloadPhase(status.autoDownload),
/* Only when the release changed: a press during a download installs that same one. */
newerAfterPress:
Boolean(status.installWhenReady) &&
Expand Down
13 changes: 9 additions & 4 deletions src/components/videoDebugOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,16 @@ function useVideoDebugDiagnostics(enabled: boolean): VideoDiagnostics {
return diagnostics;
}

function flag(value: boolean | null | undefined, yes: string, no: string): string | null {
if (value == null) return null;
return value ? yes : no;
}

function SenderSection({ s }: { s: SenderDiagnostics }) {
const stateParts = [
s.readyState ?? "—",
s.enabled == null ? null : s.enabled ? "enabled" : "disabled",
s.muted == null ? null : s.muted ? "muted" : "unmuted",
flag(s.enabled, "enabled", "disabled"),
flag(s.muted, "muted", "unmuted"),
].filter(Boolean);

return (
Expand All @@ -211,7 +216,7 @@ function SenderSection({ s }: { s: SenderDiagnostics }) {
{s.frameRate != null ? ` @ ${fmt(s.frameRate, 1)} fps` : ""}
</div>
<div>
Encoding: {s.encodingActive == null ? "—" : s.encodingActive ? "active" : "inactive"}
Encoding: {flag(s.encodingActive, "active", "inactive") ?? "—"}
{" / "}max {s.maxBitrateKbps != null ? `${fmtInt(s.maxBitrateKbps)} kbps` : "—"}
</div>
</div>
Expand Down Expand Up @@ -376,7 +381,7 @@ export function VideoDebugOverlay({ isVisible }: VideoDebugOverlayProps) {
icon={<PiCameraFill size={16} />}
status={{
active: isConnected && hasVideo,
label: isConnected ? (hasVideo ? "Streaming" : "No video") : "Disconnected",
label: isConnected ? flag(hasVideo, "Streaming", "No video") ?? "" : "Disconnected",
}}
initialPosition={{ x: window.innerWidth - 680, y: 10 }}
>
Expand Down
Loading