fix(desktop): accept instance hosts without an https prefix

This commit is contained in:
tianma-if committed 2026-09-17 21:32:59 +08:00
1 parent d81ee60c58
commit de7e006e57
9 files changed
+58 -10

No files matched your search

+6 -3
View File
@@ -1,7 +1,7 @@
import { useState, type FormEvent } from "react";
import { LockKeyhole } from "lucide-react";
import { useTranslation } from "react-i18next";
import { resolveInstanceUrlInput } from "@edgeever/shared";
import { normalizeInstanceUrl } from "@edgeever/shared";
import { Button } from "@/components/ui/button";
import { GitHubRepositoryLink } from "@/components/GitHubRepositoryLink";
import { Input } from "@/components/ui/input";
@@ -39,7 +39,7 @@ export const LoginScreen = ({ error, instanceUrl: initialInstanceUrl, isSubmitti
}
onSubmit({
...(initialInstanceUrl !== undefined ? { instanceUrl: resolveInstanceUrlInput(instanceUrl) } : {}),
...(initialInstanceUrl !== undefined ? { instanceUrl: normalizeInstanceUrl(instanceUrl) } : {}),
username: username.trim(),
password,
});
@@ -83,11 +83,14 @@ export const LoginScreen = ({ error, instanceUrl: initialInstanceUrl, isSubmitti
<label className="block">
<span className="mb-2 block text-sm font-semibold text-slate-700">{t("login.desktopInstanceUrl")}</span>
<Input
autoCapitalize="none"
autoComplete="url"
autoCorrect="off"
className="h-11 rounded-lg bg-slate-50/50 px-3.5 focus-visible:bg-card focus-visible:ring-2 focus-visible:ring-emerald-500/20"
inputMode="url"
placeholder={t("login.instanceUrlPlaceholder")}
required
type="url"
spellCheck={false}
value={instanceUrl}
onChange={(event) => setInstanceUrl(event.target.value)}
/>
+20 -1
View File
@@ -74,7 +74,7 @@ const {
describe("desktop instance setup", () => {
test("can retry with a valid URL after invalid input", async () => {
await expect(saveDesktopApiBaseUrl("not-an-instance")).rejects.toThrow();
await expect(saveDesktopApiBaseUrl("ftp://example.com")).rejects.toThrow();
const saving = saveDesktopApiBaseUrl(" https://notes.example.com/ ");
await Promise.resolve();
@@ -100,6 +100,25 @@ describe("desktop instance setup", () => {
await expect(saving).resolves.toBe("https://demo.edgeever.org");
});
test("prefixes https when the instance host has no protocol", async () => {
calls.length = 0;
const saving = saveDesktopApiBaseUrl(" example.workers.dev/ ");
await Promise.resolve();
expect(calls).toEqual([["bridge:start", "https://example.workers.dev"]]);
completeSave();
await expect(saving).resolves.toBe("https://example.workers.dev");
expect(storage.get(DESKTOP_API_BASE_URL_STORAGE_KEY)).toBe("https://example.workers.dev");
});
test("keeps an explicit http instance URL", async () => {
calls.length = 0;
const saving = saveDesktopApiBaseUrl("http://127.0.0.1:8787/");
await Promise.resolve();
expect(calls).toEqual([["bridge:start", "http://127.0.0.1:8787"]]);
completeSave();
await expect(saving).resolves.toBe("http://127.0.0.1:8787");
});
test("clears the cached session when the login form changes instances", async () => {
calls.length = 0;
window.edgeeverDesktop.apiBaseUrl = "https://notes.example.com";
+2 -2
View File
@@ -4,7 +4,7 @@ import {
type EdgeEverClientRequestContext,
} from "@edgeever/client";
import type { AuthSession } from "@edgeever/shared";
import { resolveInstanceUrlInput } from "@edgeever/shared";
import { normalizeInstanceUrl } from "@edgeever/shared";
import { createClientUuid } from "./client-id";
export { ApiRequestError };
@@ -121,7 +121,7 @@ export class DesktopInstanceUrlError extends Error {
}
export const saveDesktopApiBaseUrl = async (value: string) => {
const normalized = resolveInstanceUrlInput(value).replace(/\/$/, "");
const normalized = normalizeInstanceUrl(value);
let parsed: URL;
try {
parsed = new URL(normalized);
+1 -1
View File
@@ -262,7 +262,7 @@ export const enUS = {
desktopInstanceUrl: "Instance URL",
desktopInstanceContinue: "Continue",
desktopChangeInstance: "Change instance URL",
desktopInstanceUrlInvalid: "Enter a valid instance URL beginning with http:// or https://.",
desktopInstanceUrlInvalid: "Enter a valid instance URL.",
instanceUrlPlaceholder: "https://demo.edgeever.org",
},
nav: {
+1 -1
View File
@@ -262,7 +262,7 @@ export const ja = {
desktopInstanceUrl: "インスタンス URL",
desktopInstanceContinue: "続ける",
desktopChangeInstance: "インスタンス URL を変更",
desktopInstanceUrlInvalid: "http:// または https:// で始まる有効なインスタンス URL を入力してください。",
desktopInstanceUrlInvalid: "有効なインスタンス URL を入力してください。",
instanceUrlPlaceholder: "https://demo.edgeever.org",
},
nav: {
+1 -1
View File
@@ -262,7 +262,7 @@ export const zhCN = {
desktopInstanceUrl: "实例地址",
desktopInstanceContinue: "继续",
desktopChangeInstance: "更换实例地址",
desktopInstanceUrlInvalid: "请输入以 http:// 或 https:// 开头的有效实例地址。",
desktopInstanceUrlInvalid: "请输入有效的实例地址。",
instanceUrlPlaceholder: "https://demo.edgeever.org",
},
nav: {
+11 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { PUBLIC_DEMO_INSTANCE_URL, resolveInstanceUrlInput } from "./public-demo.ts";
import { PUBLIC_DEMO_INSTANCE_URL, normalizeInstanceUrl, resolveInstanceUrlInput } from "./public-demo.ts";
describe("public demo instance URL", () => {
test("maps the App Review 'demo' alias to the public instance", () => {
@@ -16,4 +16,14 @@ describe("public demo instance URL", () => {
expect(resolveInstanceUrlInput("demo.example.com")).toBe("demo.example.com");
expect(resolveInstanceUrlInput("")).toBe("");
});
test("prefixes https when a host has no protocol", () => {
expect(normalizeInstanceUrl("example.workers.dev")).toBe("https://example.workers.dev");
expect(normalizeInstanceUrl(" example.workers.dev/ ")).toBe("https://example.workers.dev");
expect(normalizeInstanceUrl("https://notes.example.com/")).toBe("https://notes.example.com");
expect(normalizeInstanceUrl("http://127.0.0.1:8787")).toBe("http://127.0.0.1:8787");
expect(normalizeInstanceUrl("ftp://example.com")).toBe("ftp://example.com");
expect(normalizeInstanceUrl("demo")).toBe(PUBLIC_DEMO_INSTANCE_URL);
expect(normalizeInstanceUrl("")).toBe("");
});
});
+10
View File
@@ -16,3 +16,13 @@ export const resolveInstanceUrlInput = (value: string) => {
}
return publicDemoAliasHost(trimmed) === "demo" ? PUBLIC_DEMO_INSTANCE_URL : trimmed;
};
/** Resolve aliases, default missing schemes to https://, and drop trailing slashes. */
export const normalizeInstanceUrl = (value: string) => {
const resolved = resolveInstanceUrlInput(value);
if (!resolved) {
return resolved;
}
const withProtocol = /:\/\//.test(resolved) ? resolved : `https://${resolved}`;
return withProtocol.replace(/\/+$/, "");
};
+6
View File
@@ -11,4 +11,10 @@ describe("login screen chrome", () => {
expect(loginScreen).toContain("backdrop-blur-md");
expect(loginScreen).not.toContain("slate-900-rgb");
});
test("accepts instance hosts without a protocol", () => {
expect(loginScreen).toContain("normalizeInstanceUrl");
expect(loginScreen).toContain('inputMode="url"');
expect(loginScreen).not.toContain('type="url"');
});
});