mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-03-13 22:12:54 +03:00
auth: clarify invalid origin error toast guidance (#193)
* nix: fix flake module and runtime scripts * auth: clarify invalid origin toast
This commit is contained in:
334
flake.nix
334
flake.nix
@@ -7,14 +7,17 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils }:
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
let
|
||||||
|
forEachSystem = flake-utils.lib.eachDefaultSystem;
|
||||||
|
in
|
||||||
|
(forEachSystem (system:
|
||||||
let
|
let
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
|
||||||
# Build the application
|
# Build the application
|
||||||
gitea-mirror = pkgs.stdenv.mkDerivation {
|
gitea-mirror = pkgs.stdenv.mkDerivation {
|
||||||
pname = "gitea-mirror";
|
pname = "gitea-mirror";
|
||||||
version = "3.8.11";
|
version = "3.9.4";
|
||||||
|
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
|
||||||
@@ -53,7 +56,7 @@
|
|||||||
|
|
||||||
# Create entrypoint script that matches Docker behavior
|
# Create entrypoint script that matches Docker behavior
|
||||||
cat > $out/bin/gitea-mirror <<'EOF'
|
cat > $out/bin/gitea-mirror <<'EOF'
|
||||||
#!/usr/bin/env bash
|
#!${pkgs.bash}/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# === DEFAULT CONFIGURATION ===
|
# === DEFAULT CONFIGURATION ===
|
||||||
@@ -112,7 +115,7 @@ if [ -z "$ENCRYPTION_SECRET" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# === DATABASE INITIALIZATION ===
|
# === DATABASE INITIALIZATION ===
|
||||||
DB_PATH=$(echo "$DATABASE_URL" | sed 's|^file:||')
|
DB_PATH=$(echo "$DATABASE_URL" | ${pkgs.gnused}/bin/sed 's|^file:||')
|
||||||
if [ ! -f "$DB_PATH" ]; then
|
if [ ! -f "$DB_PATH" ]; then
|
||||||
echo "Database not found. It will be created and initialized via Drizzle migrations on first app startup..."
|
echo "Database not found. It will be created and initialized via Drizzle migrations on first app startup..."
|
||||||
touch "$DB_PATH"
|
touch "$DB_PATH"
|
||||||
@@ -123,25 +126,25 @@ fi
|
|||||||
# === STARTUP SCRIPTS ===
|
# === STARTUP SCRIPTS ===
|
||||||
# Initialize configuration from environment variables
|
# Initialize configuration from environment variables
|
||||||
echo "Checking for environment configuration..."
|
echo "Checking for environment configuration..."
|
||||||
if [ -f "dist/scripts/startup-env-config.js" ]; then
|
if [ -f "scripts/startup-env-config.ts" ]; then
|
||||||
echo "Loading configuration from environment variables..."
|
echo "Loading configuration from environment variables..."
|
||||||
${pkgs.bun}/bin/bun dist/scripts/startup-env-config.js && \
|
${pkgs.bun}/bin/bun scripts/startup-env-config.ts && \
|
||||||
echo "✅ Environment configuration loaded successfully" || \
|
echo "✅ Environment configuration loaded successfully" || \
|
||||||
echo "⚠️ Environment configuration loading completed with warnings"
|
echo "⚠️ Environment configuration loading completed with warnings"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run startup recovery
|
# Run startup recovery
|
||||||
echo "Running startup recovery..."
|
echo "Running startup recovery..."
|
||||||
if [ -f "dist/scripts/startup-recovery.js" ]; then
|
if [ -f "scripts/startup-recovery.ts" ]; then
|
||||||
${pkgs.bun}/bin/bun dist/scripts/startup-recovery.js --timeout=30000 && \
|
${pkgs.bun}/bin/bun scripts/startup-recovery.ts --timeout=30000 && \
|
||||||
echo "✅ Startup recovery completed successfully" || \
|
echo "✅ Startup recovery completed successfully" || \
|
||||||
echo "⚠️ Startup recovery completed with warnings"
|
echo "⚠️ Startup recovery completed with warnings"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run repository status repair
|
# Run repository status repair
|
||||||
echo "Running repository status repair..."
|
echo "Running repository status repair..."
|
||||||
if [ -f "dist/scripts/repair-mirrored-repos.js" ]; then
|
if [ -f "scripts/repair-mirrored-repos.ts" ]; then
|
||||||
${pkgs.bun}/bin/bun dist/scripts/repair-mirrored-repos.js --startup && \
|
${pkgs.bun}/bin/bun scripts/repair-mirrored-repos.ts --startup && \
|
||||||
echo "✅ Repository status repair completed successfully" || \
|
echo "✅ Repository status repair completed successfully" || \
|
||||||
echo "⚠️ Repository status repair completed with warnings"
|
echo "⚠️ Repository status repair completed with warnings"
|
||||||
fi
|
fi
|
||||||
@@ -170,7 +173,7 @@ EOF
|
|||||||
|
|
||||||
# Create database management helper
|
# Create database management helper
|
||||||
cat > $out/bin/gitea-mirror-db <<'EOF'
|
cat > $out/bin/gitea-mirror-db <<'EOF'
|
||||||
#!/usr/bin/env bash
|
#!${pkgs.bash}/bin/bash
|
||||||
export DATA_DIR=''${DATA_DIR:-"$HOME/.local/share/gitea-mirror"}
|
export DATA_DIR=''${DATA_DIR:-"$HOME/.local/share/gitea-mirror"}
|
||||||
mkdir -p "$DATA_DIR"
|
mkdir -p "$DATA_DIR"
|
||||||
cd $out/lib/gitea-mirror
|
cd $out/lib/gitea-mirror
|
||||||
@@ -217,176 +220,175 @@ EOF
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
# NixOS module
|
}
|
||||||
nixosModules.default = { config, lib, pkgs, ... }:
|
)) // {
|
||||||
with lib;
|
nixosModules.default = { config, lib, pkgs, ... }:
|
||||||
let
|
with lib;
|
||||||
cfg = config.services.gitea-mirror;
|
let
|
||||||
in {
|
cfg = config.services.gitea-mirror;
|
||||||
options.services.gitea-mirror = {
|
in {
|
||||||
enable = mkEnableOption "Gitea Mirror service";
|
options.services.gitea-mirror = {
|
||||||
|
enable = mkEnableOption "Gitea Mirror service";
|
||||||
|
|
||||||
package = mkOption {
|
package = mkOption {
|
||||||
type = types.package;
|
type = types.package;
|
||||||
default = self.packages.${system}.default;
|
default = self.packages.${pkgs.system}.default;
|
||||||
description = "The Gitea Mirror package to use";
|
description = "The Gitea Mirror package to use";
|
||||||
};
|
|
||||||
|
|
||||||
dataDir = mkOption {
|
|
||||||
type = types.path;
|
|
||||||
default = "/var/lib/gitea-mirror";
|
|
||||||
description = "Directory to store data and database";
|
|
||||||
};
|
|
||||||
|
|
||||||
user = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "gitea-mirror";
|
|
||||||
description = "User account under which Gitea Mirror runs";
|
|
||||||
};
|
|
||||||
|
|
||||||
group = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "gitea-mirror";
|
|
||||||
description = "Group under which Gitea Mirror runs";
|
|
||||||
};
|
|
||||||
|
|
||||||
host = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "0.0.0.0";
|
|
||||||
description = "Host to bind to";
|
|
||||||
};
|
|
||||||
|
|
||||||
port = mkOption {
|
|
||||||
type = types.port;
|
|
||||||
default = 4321;
|
|
||||||
description = "Port to listen on";
|
|
||||||
};
|
|
||||||
|
|
||||||
betterAuthUrl = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "http://localhost:4321";
|
|
||||||
description = "Better Auth URL (external URL of the service)";
|
|
||||||
};
|
|
||||||
|
|
||||||
betterAuthTrustedOrigins = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "http://localhost:4321";
|
|
||||||
description = "Comma-separated list of trusted origins for Better Auth";
|
|
||||||
};
|
|
||||||
|
|
||||||
mirrorIssueConcurrency = mkOption {
|
|
||||||
type = types.int;
|
|
||||||
default = 3;
|
|
||||||
description = "Number of concurrent issue mirror operations (set to 1 for perfect ordering)";
|
|
||||||
};
|
|
||||||
|
|
||||||
mirrorPullRequestConcurrency = mkOption {
|
|
||||||
type = types.int;
|
|
||||||
default = 5;
|
|
||||||
description = "Number of concurrent PR mirror operations (set to 1 for perfect ordering)";
|
|
||||||
};
|
|
||||||
|
|
||||||
environmentFile = mkOption {
|
|
||||||
type = types.nullOr types.path;
|
|
||||||
default = null;
|
|
||||||
description = ''
|
|
||||||
Path to file containing environment variables.
|
|
||||||
Only needed if you want to set BETTER_AUTH_SECRET or ENCRYPTION_SECRET manually.
|
|
||||||
Otherwise, secrets will be auto-generated and stored in the data directory.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
BETTER_AUTH_SECRET=your-32-character-secret-here
|
|
||||||
ENCRYPTION_SECRET=your-encryption-secret-here
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
openFirewall = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
description = "Open the firewall for the specified port";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
dataDir = mkOption {
|
||||||
users.users.${cfg.user} = {
|
type = types.path;
|
||||||
isSystemUser = true;
|
default = "/var/lib/gitea-mirror";
|
||||||
group = cfg.group;
|
description = "Directory to store data and database";
|
||||||
home = cfg.dataDir;
|
};
|
||||||
createHome = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
users.groups.${cfg.group} = {};
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "gitea-mirror";
|
||||||
|
description = "User account under which Gitea Mirror runs";
|
||||||
|
};
|
||||||
|
|
||||||
systemd.services.gitea-mirror = {
|
group = mkOption {
|
||||||
description = "Gitea Mirror - GitHub to Gitea mirroring service";
|
type = types.str;
|
||||||
after = [ "network.target" ];
|
default = "gitea-mirror";
|
||||||
wantedBy = [ "multi-user.target" ];
|
description = "Group under which Gitea Mirror runs";
|
||||||
|
};
|
||||||
|
|
||||||
environment = {
|
host = mkOption {
|
||||||
DATA_DIR = cfg.dataDir;
|
type = types.str;
|
||||||
DATABASE_URL = "file:${cfg.dataDir}/gitea-mirror.db";
|
default = "0.0.0.0";
|
||||||
HOST = cfg.host;
|
description = "Host to bind to";
|
||||||
PORT = toString cfg.port;
|
};
|
||||||
NODE_ENV = "production";
|
|
||||||
BETTER_AUTH_URL = cfg.betterAuthUrl;
|
|
||||||
BETTER_AUTH_TRUSTED_ORIGINS = cfg.betterAuthTrustedOrigins;
|
|
||||||
PUBLIC_BETTER_AUTH_URL = cfg.betterAuthUrl;
|
|
||||||
MIRROR_ISSUE_CONCURRENCY = toString cfg.mirrorIssueConcurrency;
|
|
||||||
MIRROR_PULL_REQUEST_CONCURRENCY = toString cfg.mirrorPullRequestConcurrency;
|
|
||||||
};
|
|
||||||
|
|
||||||
serviceConfig = {
|
port = mkOption {
|
||||||
Type = "simple";
|
type = types.port;
|
||||||
User = cfg.user;
|
default = 4321;
|
||||||
Group = cfg.group;
|
description = "Port to listen on";
|
||||||
ExecStart = "${cfg.package}/bin/gitea-mirror";
|
};
|
||||||
Restart = "always";
|
|
||||||
RestartSec = "10s";
|
|
||||||
|
|
||||||
# Security hardening
|
betterAuthUrl = mkOption {
|
||||||
NoNewPrivileges = true;
|
type = types.str;
|
||||||
PrivateTmp = true;
|
default = "http://localhost:4321";
|
||||||
ProtectSystem = "strict";
|
description = "Better Auth URL (external URL of the service)";
|
||||||
ProtectHome = true;
|
};
|
||||||
ReadWritePaths = [ cfg.dataDir ];
|
|
||||||
|
|
||||||
# Load environment file if specified (optional)
|
betterAuthTrustedOrigins = mkOption {
|
||||||
EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
|
type = types.str;
|
||||||
|
default = "http://localhost:4321";
|
||||||
|
description = "Comma-separated list of trusted origins for Better Auth";
|
||||||
|
};
|
||||||
|
|
||||||
# Graceful shutdown
|
mirrorIssueConcurrency = mkOption {
|
||||||
TimeoutStopSec = "30s";
|
type = types.int;
|
||||||
KillMode = "mixed";
|
default = 3;
|
||||||
KillSignal = "SIGTERM";
|
description = "Number of concurrent issue mirror operations (set to 1 for perfect ordering)";
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
# Health check timer (optional monitoring)
|
mirrorPullRequestConcurrency = mkOption {
|
||||||
systemd.timers.gitea-mirror-healthcheck = mkIf cfg.enable {
|
type = types.int;
|
||||||
description = "Gitea Mirror health check timer";
|
default = 5;
|
||||||
wantedBy = [ "timers.target" ];
|
description = "Number of concurrent PR mirror operations (set to 1 for perfect ordering)";
|
||||||
timerConfig = {
|
};
|
||||||
OnBootSec = "5min";
|
|
||||||
OnUnitActiveSec = "5min";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.gitea-mirror-healthcheck = mkIf cfg.enable {
|
environmentFile = mkOption {
|
||||||
description = "Gitea Mirror health check";
|
type = types.nullOr types.path;
|
||||||
after = [ "gitea-mirror.service" ];
|
default = null;
|
||||||
serviceConfig = {
|
description = ''
|
||||||
Type = "oneshot";
|
Path to file containing environment variables.
|
||||||
ExecStart = "${pkgs.curl}/bin/curl -f http://${cfg.host}:${toString cfg.port}/api/health || true";
|
Only needed if you want to set BETTER_AUTH_SECRET or ENCRYPTION_SECRET manually.
|
||||||
User = "nobody";
|
Otherwise, secrets will be auto-generated and stored in the data directory.
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall = mkIf cfg.openFirewall {
|
Example:
|
||||||
allowedTCPPorts = [ cfg.port ];
|
BETTER_AUTH_SECRET=your-32-character-secret-here
|
||||||
};
|
ENCRYPTION_SECRET=your-encryption-secret-here
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
openFirewall = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Open the firewall for the specified port";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
|
||||||
) // {
|
config = mkIf cfg.enable {
|
||||||
|
users.users.${cfg.user} = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = cfg.group;
|
||||||
|
home = cfg.dataDir;
|
||||||
|
createHome = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups.${cfg.group} = {};
|
||||||
|
|
||||||
|
systemd.services.gitea-mirror = {
|
||||||
|
description = "Gitea Mirror - GitHub to Gitea mirroring service";
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
|
environment = {
|
||||||
|
DATA_DIR = cfg.dataDir;
|
||||||
|
DATABASE_URL = "file:${cfg.dataDir}/gitea-mirror.db";
|
||||||
|
HOST = cfg.host;
|
||||||
|
PORT = toString cfg.port;
|
||||||
|
NODE_ENV = "production";
|
||||||
|
BETTER_AUTH_URL = cfg.betterAuthUrl;
|
||||||
|
BETTER_AUTH_TRUSTED_ORIGINS = cfg.betterAuthTrustedOrigins;
|
||||||
|
PUBLIC_BETTER_AUTH_URL = cfg.betterAuthUrl;
|
||||||
|
MIRROR_ISSUE_CONCURRENCY = toString cfg.mirrorIssueConcurrency;
|
||||||
|
MIRROR_PULL_REQUEST_CONCURRENCY = toString cfg.mirrorPullRequestConcurrency;
|
||||||
|
};
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
User = cfg.user;
|
||||||
|
Group = cfg.group;
|
||||||
|
ExecStart = "${cfg.package}/bin/gitea-mirror";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = "10s";
|
||||||
|
|
||||||
|
# Security hardening
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
ProtectHome = true;
|
||||||
|
ReadWritePaths = [ cfg.dataDir ];
|
||||||
|
|
||||||
|
# Graceful shutdown
|
||||||
|
TimeoutStopSec = "30s";
|
||||||
|
KillMode = "mixed";
|
||||||
|
KillSignal = "SIGTERM";
|
||||||
|
} // optionalAttrs (cfg.environmentFile != null) {
|
||||||
|
EnvironmentFile = cfg.environmentFile;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Health check timer (optional monitoring)
|
||||||
|
systemd.timers.gitea-mirror-healthcheck = {
|
||||||
|
description = "Gitea Mirror health check timer";
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnBootSec = "5min";
|
||||||
|
OnUnitActiveSec = "5min";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.gitea-mirror-healthcheck = {
|
||||||
|
description = "Gitea Mirror health check";
|
||||||
|
after = [ "gitea-mirror.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${pkgs.bash}/bin/bash -c '${pkgs.curl}/bin/curl -f http://127.0.0.1:${toString cfg.port}/api/health || true'";
|
||||||
|
User = "nobody";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall = mkIf cfg.openFirewall {
|
||||||
|
allowedTCPPorts = [ cfg.port ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
# Overlay for adding to nixpkgs
|
# Overlay for adding to nixpkgs
|
||||||
overlays.default = final: prev: {
|
overlays.default = final: prev: {
|
||||||
gitea-mirror = self.packages.${final.system}.default;
|
gitea-mirror = self.packages.${final.system}.default;
|
||||||
|
|||||||
@@ -169,4 +169,31 @@ describe("parseErrorMessage", () => {
|
|||||||
expect(result.description).toBeUndefined();
|
expect(result.description).toBeUndefined();
|
||||||
expect(result.isStructured).toBe(false);
|
expect(result.isStructured).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("adds trusted origins guidance for invalid origin errors", () => {
|
||||||
|
const errorMessage = "Invalid Origin: https://mirror.example.com";
|
||||||
|
|
||||||
|
const result = parseErrorMessage(errorMessage);
|
||||||
|
|
||||||
|
expect(result.title).toBe("Invalid Origin");
|
||||||
|
expect(result.description).toContain("BETTER_AUTH_TRUSTED_ORIGINS");
|
||||||
|
expect(result.description).toContain("https://mirror.example.com");
|
||||||
|
expect(result.isStructured).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("showErrorToast", () => {
|
||||||
|
test("shows invalid origin guidance in toast description", () => {
|
||||||
|
const calls: any[] = [];
|
||||||
|
const toast = {
|
||||||
|
error: (...args: any[]) => calls.push(args),
|
||||||
|
};
|
||||||
|
|
||||||
|
showErrorToast("Invalid Origin: http://10.10.20.45:4321", toast);
|
||||||
|
|
||||||
|
expect(calls).toHaveLength(1);
|
||||||
|
expect(calls[0][0]).toBe("Invalid Origin");
|
||||||
|
expect(calls[0][1].description).toContain("BETTER_AUTH_TRUSTED_ORIGINS");
|
||||||
|
expect(calls[0][1].description).toContain("http://10.10.20.45:4321");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -86,6 +86,30 @@ export interface ParsedErrorMessage {
|
|||||||
isStructured: boolean;
|
isStructured: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getInvalidOriginGuidance(title: string, description?: string): ParsedErrorMessage | null {
|
||||||
|
const fullMessage = `${title} ${description ?? ""}`.trim();
|
||||||
|
if (!/invalid origin/i.test(fullMessage)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlMatch = fullMessage.match(/https?:\/\/[^\s'")]+/i);
|
||||||
|
let originHint = "this URL";
|
||||||
|
|
||||||
|
if (urlMatch) {
|
||||||
|
try {
|
||||||
|
originHint = new URL(urlMatch[0]).origin;
|
||||||
|
} catch {
|
||||||
|
originHint = urlMatch[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: "Invalid Origin",
|
||||||
|
description: `Add ${originHint} to BETTER_AUTH_TRUSTED_ORIGINS and restart the app.`,
|
||||||
|
isStructured: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function parseErrorMessage(error: unknown): ParsedErrorMessage {
|
export function parseErrorMessage(error: unknown): ParsedErrorMessage {
|
||||||
// Handle Error objects
|
// Handle Error objects
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@@ -102,29 +126,32 @@ export function parseErrorMessage(error: unknown): ParsedErrorMessage {
|
|||||||
if (typeof parsed === "object" && parsed !== null) {
|
if (typeof parsed === "object" && parsed !== null) {
|
||||||
// Format 1: { error: "message", errorType: "type", troubleshooting: "info" }
|
// Format 1: { error: "message", errorType: "type", troubleshooting: "info" }
|
||||||
if (parsed.error) {
|
if (parsed.error) {
|
||||||
return {
|
const formatted = {
|
||||||
title: parsed.error,
|
title: parsed.error,
|
||||||
description: parsed.troubleshooting || parsed.errorType || undefined,
|
description: parsed.troubleshooting || parsed.errorType || undefined,
|
||||||
isStructured: true,
|
isStructured: true,
|
||||||
};
|
};
|
||||||
|
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format 2: { title: "title", description: "desc" }
|
// Format 2: { title: "title", description: "desc" }
|
||||||
if (parsed.title) {
|
if (parsed.title) {
|
||||||
return {
|
const formatted = {
|
||||||
title: parsed.title,
|
title: parsed.title,
|
||||||
description: parsed.description || undefined,
|
description: parsed.description || undefined,
|
||||||
isStructured: true,
|
isStructured: true,
|
||||||
};
|
};
|
||||||
|
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format 3: { message: "msg", details: "details" }
|
// Format 3: { message: "msg", details: "details" }
|
||||||
if (parsed.message) {
|
if (parsed.message) {
|
||||||
return {
|
const formatted = {
|
||||||
title: parsed.message,
|
title: parsed.message,
|
||||||
description: parsed.details || undefined,
|
description: parsed.details || undefined,
|
||||||
isStructured: true,
|
isStructured: true,
|
||||||
};
|
};
|
||||||
|
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -132,11 +159,12 @@ export function parseErrorMessage(error: unknown): ParsedErrorMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Plain string message
|
// Plain string message
|
||||||
return {
|
const formatted = {
|
||||||
title: error,
|
title: error,
|
||||||
description: undefined,
|
description: undefined,
|
||||||
isStructured: false,
|
isStructured: false,
|
||||||
};
|
};
|
||||||
|
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle objects directly
|
// Handle objects directly
|
||||||
@@ -144,36 +172,40 @@ export function parseErrorMessage(error: unknown): ParsedErrorMessage {
|
|||||||
const errorObj = error as any;
|
const errorObj = error as any;
|
||||||
|
|
||||||
if (errorObj.error) {
|
if (errorObj.error) {
|
||||||
return {
|
const formatted = {
|
||||||
title: errorObj.error,
|
title: errorObj.error,
|
||||||
description: errorObj.troubleshooting || errorObj.errorType || undefined,
|
description: errorObj.troubleshooting || errorObj.errorType || undefined,
|
||||||
isStructured: true,
|
isStructured: true,
|
||||||
};
|
};
|
||||||
|
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorObj.title) {
|
if (errorObj.title) {
|
||||||
return {
|
const formatted = {
|
||||||
title: errorObj.title,
|
title: errorObj.title,
|
||||||
description: errorObj.description || undefined,
|
description: errorObj.description || undefined,
|
||||||
isStructured: true,
|
isStructured: true,
|
||||||
};
|
};
|
||||||
|
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorObj.message) {
|
if (errorObj.message) {
|
||||||
return {
|
const formatted = {
|
||||||
title: errorObj.message,
|
title: errorObj.message,
|
||||||
description: errorObj.details || undefined,
|
description: errorObj.details || undefined,
|
||||||
isStructured: true,
|
isStructured: true,
|
||||||
};
|
};
|
||||||
|
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback for unknown types
|
// Fallback for unknown types
|
||||||
return {
|
const fallback = {
|
||||||
title: String(error),
|
title: String(error),
|
||||||
description: undefined,
|
description: undefined,
|
||||||
isStructured: false,
|
isStructured: false,
|
||||||
};
|
};
|
||||||
|
return getInvalidOriginGuidance(fallback.title, fallback.description) || fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enhanced toast helper that parses structured error messages
|
// Enhanced toast helper that parses structured error messages
|
||||||
|
|||||||
Reference in New Issue
Block a user