mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-05-20 07:26:58 -04:00
refactor: upgrade tauri 2.0
This commit is contained in:
73 files changed
+7232
-5026
No files matched your search
+38
-21
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:18.04
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ARG USERNAME=vscode
|
||||
ARG USER_UID=1000
|
||||
@@ -7,8 +7,9 @@ ARG USER_GID=$USER_UID
|
||||
ENV RUSTUP_HOME=/usr/local/rustup \
|
||||
CARGO_HOME=/usr/local/cargo \
|
||||
PATH=/usr/local/cargo/bin:$PATH \
|
||||
RUST_VERSION=1.75.0
|
||||
RUST_VERSION=1.80.0
|
||||
|
||||
# Install common dependencies
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
@@ -18,34 +19,50 @@ RUN set -eux; \
|
||||
gnupg \
|
||||
git \
|
||||
less \
|
||||
software-properties-common \
|
||||
# Tauri dependencies
|
||||
libwebkit2gtk-4.0-dev build-essential wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev; \
|
||||
# Install openconnect
|
||||
add-apt-repository ppa:yuezk/globalprotect-openconnect; \
|
||||
apt-get update; \
|
||||
apt-get install -y openconnect libopenconnect-dev; \
|
||||
# Create a non-root user
|
||||
software-properties-common
|
||||
|
||||
# Create a non-root user
|
||||
RUN set -eux; \
|
||||
groupadd --gid $USER_GID $USERNAME; \
|
||||
useradd --uid $USER_UID --gid $USER_GID -m $USERNAME; \
|
||||
echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME; \
|
||||
chmod 0440 /etc/sudoers.d/$USERNAME; \
|
||||
# Install Node.js
|
||||
mkdir -p /etc/apt/keyrings; \
|
||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg; \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list; \
|
||||
apt-get update; \
|
||||
apt-get install -y nodejs; \
|
||||
corepack enable; \
|
||||
# Install diff-so-fancy
|
||||
npm install -g diff-so-fancy; \
|
||||
# Install Rust
|
||||
chmod 0440 /etc/sudoers.d/$USERNAME
|
||||
|
||||
# Install Rust
|
||||
RUN set -eux; \
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain $RUST_VERSION; \
|
||||
chown -R $USERNAME:$USERNAME $RUSTUP_HOME $CARGO_HOME; \
|
||||
rustup --version; \
|
||||
cargo --version; \
|
||||
rustc --version
|
||||
|
||||
# Install Node.js
|
||||
RUN set -eux; \
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -; \
|
||||
apt-get install -y nodejs; \
|
||||
corepack enable; \
|
||||
# Install diff-so-fancy
|
||||
npm install -g diff-so-fancy
|
||||
|
||||
# Install openconnect
|
||||
RUN set -eux; \
|
||||
add-apt-repository ppa:yuezk/globalprotect-openconnect; \
|
||||
apt-get update; \
|
||||
apt-get install -y openconnect libopenconnect-dev
|
||||
|
||||
# Tauri dependencies
|
||||
RUN set -eux; \
|
||||
apt-get install -y \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
build-essential \
|
||||
curl \
|
||||
wget \
|
||||
file \
|
||||
libxdo-dev \
|
||||
libssl-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev
|
||||
|
||||
USER $USERNAME
|
||||
|
||||
# Install Oh My Zsh
|
||||
|
||||
@@ -24,7 +24,8 @@ jobs:
|
||||
- name: Set up matrix
|
||||
id: set-matrix
|
||||
run: |
|
||||
if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
|
||||
# Set the matrix to include arm64 if the ref is a tag or is the dev branch
|
||||
if [[ "${{ github.ref }}" == "refs/tags/"* || "${{ github.ref }}" == "refs/heads/dev" ]]; then
|
||||
echo 'matrix=[{"runner": "ubuntu-latest", "arch": "amd64"}, {"runner": "arm64", "arch": "arm64"}]' >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo 'matrix=[{"runner": "ubuntu-latest", "arch": "amd64"}]' >> $GITHUB_OUTPUT
|
||||
@@ -34,13 +35,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [setup-matrix]
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
version: 9
|
||||
- name: Prepare workspace
|
||||
run: rm -rf source && mkdir source
|
||||
- name: Checkout GlobalProtect-openconnect
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
repository: yuezk/GlobalProtect-openconnect
|
||||
@@ -55,7 +56,7 @@ jobs:
|
||||
fi
|
||||
make tarball
|
||||
- name: Upload tarball
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifact-source
|
||||
if-no-files-found: error
|
||||
@@ -79,7 +80,7 @@ jobs:
|
||||
rm -rf build-gp-${{ matrix.package }}
|
||||
mkdir -p build-gp-${{ matrix.package }}
|
||||
- name: Download tarball
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: artifact-source
|
||||
path: build-gp-${{ matrix.package }}
|
||||
@@ -87,18 +88,18 @@ jobs:
|
||||
run: echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin
|
||||
- name: Build ${{ matrix.package }} package in Docker
|
||||
run: |
|
||||
docker run --rm \
|
||||
docker run --pull=always --rm \
|
||||
-v $(pwd)/build-gp-${{ matrix.package }}:/${{ matrix.package }} \
|
||||
yuezk/gpdev:${{ matrix.package }}-builder
|
||||
yuezk/gpdev:${{ matrix.package }}-builder-tauri2
|
||||
- name: Install ${{ matrix.package }} package in Docker
|
||||
run: |
|
||||
docker run --rm \
|
||||
docker run --pull=always --rm \
|
||||
-e GPGUI_INSTALLED=0 \
|
||||
-v $(pwd)/build-gp-${{ matrix.package }}:/${{ matrix.package }} \
|
||||
yuezk/gpdev:${{ matrix.package }}-builder \
|
||||
yuezk/gpdev:${{ matrix.package }}-builder-tauri2 \
|
||||
bash install.sh
|
||||
- name: Upload ${{ matrix.package }} package
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifact-gp-${{ matrix.package }}-${{ matrix.os.arch }}
|
||||
if-no-files-found: error
|
||||
@@ -114,20 +115,20 @@ jobs:
|
||||
runs-on: ${{ matrix.os.runner }}
|
||||
name: build-gpgui (${{ matrix.os.arch }})
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
version: 9
|
||||
- name: Prepare workspace
|
||||
run: rm -rf gpgui-source && mkdir gpgui-source
|
||||
- name: Checkout GlobalProtect-openconnect
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
repository: yuezk/GlobalProtect-openconnect
|
||||
ref: ${{ github.ref }}
|
||||
path: gpgui-source/gp
|
||||
- name: Checkout gpgui@${{ github.ref_name }}
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
repository: yuezk/gpgui
|
||||
@@ -141,15 +142,15 @@ jobs:
|
||||
run: echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin
|
||||
- name: Build gpgui in Docker
|
||||
run: |
|
||||
docker run --rm -v $(pwd)/gpgui-source:/gpgui yuezk/gpdev:gpgui-builder
|
||||
docker run --pull=always --rm -v $(pwd)/gpgui-source:/gpgui yuezk/gpdev:gpgui-builder-tauri2
|
||||
- name: Install gpgui in Docker
|
||||
run: |
|
||||
cd gpgui-source
|
||||
tar -xJf *.bin.tar.xz
|
||||
docker run --rm -v $(pwd):/gpgui yuezk/gpdev:gpgui-builder \
|
||||
docker run --pull=always --rm -v $(pwd):/gpgui yuezk/gpdev:gpgui-builder-tauri2 \
|
||||
bash -c "cd /gpgui/gpgui_*/ && ./gpgui --version"
|
||||
- name: Upload gpgui
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifact-gpgui-${{ matrix.os.arch }}
|
||||
if-no-files-found: error
|
||||
@@ -170,7 +171,7 @@ jobs:
|
||||
run: rm -rf gh-release && mkdir gh-release
|
||||
|
||||
- name: Checkout GlobalProtect-openconnect
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
repository: yuezk/GlobalProtect-openconnect
|
||||
@@ -178,7 +179,7 @@ jobs:
|
||||
path: gh-release/gp
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: gh-release/gp/.build/artifacts
|
||||
|
||||
|
||||
@@ -47,9 +47,9 @@ jobs:
|
||||
if: ${{ inputs.ppa }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
version: 9
|
||||
- name: Prepare workspace
|
||||
run: rm -rf publish-ppa && mkdir publish-ppa
|
||||
- name: Download ${{ inputs.tag }} source code
|
||||
@@ -71,12 +71,20 @@ jobs:
|
||||
|
||||
# Prepare the debian directory with custom files
|
||||
mkdir -p .build/debian
|
||||
sed 's/@RUST@/rust-all(>=1.70)/g' packaging/deb/control.in > .build/debian/control
|
||||
sed 's/@OFFLINE@/1/g' packaging/deb/rules.in > .build/debian/rules
|
||||
cp packaging/deb/postrm .build/debian/postrm
|
||||
|
||||
cp -v packaging/deb/control.in .build/debian/control
|
||||
cp -v packaging/deb/rules.in .build/debian/rules
|
||||
cp -v packaging/deb/compat .build/debian/compat
|
||||
cp -v packaging/deb/postrm .build/debian/postrm
|
||||
|
||||
sed -i "s/@RUST@/cargo-1.80/g" .build/debian/control
|
||||
|
||||
sed -i "s/@OFFLINE@/1/g" .build/debian/rules
|
||||
sed -i "s/@BUILD_GUI@/1/g" .build/deb/debian/rules
|
||||
sed -i "s/@RUST_VERSION@/1.80/g" .build/deb/debian/rules
|
||||
|
||||
- name: Publish to PPA
|
||||
uses: yuezk/publish-ppa-package@v2
|
||||
uses: yuezk/publish-ppa-package@gp
|
||||
with:
|
||||
repository: "yuezk/globalprotect-openconnect"
|
||||
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
|
||||
@@ -85,5 +93,7 @@ jobs:
|
||||
debian_dir: publish-ppa/globalprotect-openconnect-*/.build/debian
|
||||
deb_email: "[email protected]"
|
||||
deb_fullname: "Kevin Yue"
|
||||
extra_ppa: "yuezk/globalprotect-openconnect liushuyu-011/rust-bpo-1.75"
|
||||
extra_ppa: "yuezk/globalprotect-openconnect liushuyu-011/rust-updates-1.80"
|
||||
# Ubuntu 18.04 and 20.04 are excluded because tauri2 no longer supports them
|
||||
excluded_series: "bionic focal"
|
||||
revision: ${{ inputs.revision }}
|
||||
@@ -109,20 +109,20 @@ jobs:
|
||||
run: echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin
|
||||
- name: Build ${{ matrix.package }} package in Docker
|
||||
run: |
|
||||
docker run --rm \
|
||||
docker run --pull=always --rm \
|
||||
-v $(pwd)/build-${{ matrix.package }}:/${{ matrix.package }} \
|
||||
-e INCLUDE_GUI=1 \
|
||||
yuezk/gpdev:${{ matrix.package }}-builder-tauri2
|
||||
|
||||
- name: Install ${{ matrix.package }} package in Docker
|
||||
run: |
|
||||
docker run --rm \
|
||||
docker run --pull=always --rm \
|
||||
-v $(pwd)/build-${{ matrix.package }}:/${{ matrix.package }} \
|
||||
yuezk/gpdev:${{ matrix.package }}-builder-tauri2 \
|
||||
bash install.sh
|
||||
|
||||
- name: Upload ${{ matrix.package }} package
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifact-${{ matrix.os }}-${{ matrix.package }}
|
||||
if-no-files-found: error
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
- name: Prepare workspace
|
||||
run: rm -rf gh-release && mkdir gh-release
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: gh-release
|
||||
- name: Update release
|
||||
|
||||
@@ -8,3 +8,6 @@
|
||||
.cargo
|
||||
.build
|
||||
SNAPSHOT
|
||||
|
||||
# Tauri generated files
|
||||
gen
|
||||
Generated
+2009
-1430
File diff suppressed because it is too large.
Load diff
+13
-16
@@ -4,8 +4,8 @@ resolver = "2"
|
||||
members = ["crates/*", "apps/gpclient", "apps/gpservice", "apps/gpauth", "apps/gpgui-helper/src-tauri"]
|
||||
|
||||
[workspace.package]
|
||||
rust-version = "1.70"
|
||||
version = "2.3.9"
|
||||
rust-version = "1.80"
|
||||
version = "2.4.0"
|
||||
authors = ["Kevin Yue <[email protected]>"]
|
||||
homepage = "https://github.com/yuezk/GlobalProtect-openconnect"
|
||||
edition = "2021"
|
||||
@@ -13,22 +13,22 @@ license = "GPL-3.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0"
|
||||
base64 = "0.21"
|
||||
clap = { version = "4.4.2", features = ["derive"] }
|
||||
base64 = "0.22"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
ctrlc = "3.4"
|
||||
directories = "5.0"
|
||||
dns-lookup = "2.0.4"
|
||||
env_logger = "0.10"
|
||||
env_logger = "0.11"
|
||||
is_executable = "1.0"
|
||||
log = "0.4"
|
||||
regex = "1"
|
||||
reqwest = { version = "0.11", features = ["native-tls-vendored", "json"] }
|
||||
reqwest = { version = "0.12", features = ["native-tls", "json"] }
|
||||
openssl = "0.10"
|
||||
pem = "3"
|
||||
roxmltree = "0.18"
|
||||
roxmltree = "0.20"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sysinfo = "0.29"
|
||||
sysinfo = "0.33"
|
||||
tempfile = "3.8"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-util = "0.7"
|
||||
@@ -37,22 +37,19 @@ urlencoding = "2.1.3"
|
||||
axum = "0.7"
|
||||
futures = "0.3"
|
||||
futures-util = "0.3"
|
||||
tokio-tungstenite = "0.20.1"
|
||||
uzers = "0.11"
|
||||
uzers = "0.12"
|
||||
whoami = "1"
|
||||
thiserror = "1"
|
||||
thiserror = "2"
|
||||
redact-engine = "0.1"
|
||||
compile-time = "0.2"
|
||||
serde_urlencoded = "0.7"
|
||||
md5="0.7"
|
||||
sha256="1"
|
||||
which="6"
|
||||
which="7"
|
||||
|
||||
# Tauri dependencies
|
||||
tauri = { version = "1.5" }
|
||||
specta = "=2.0.0-rc.1"
|
||||
specta-macros = "=2.0.0-rc.1"
|
||||
rspc = { version = "1.0.0-rc.5", features = ["tauri"] }
|
||||
tauri = { version = "2" }
|
||||
specta = "=2.0.0-rc.20"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 'z' # Optimize for size
|
||||
|
||||
@@ -4,6 +4,7 @@ OFFLINE ?= 0
|
||||
BUILD_FE ?= 1
|
||||
INCLUDE_GUI ?= 0
|
||||
CARGO ?= cargo
|
||||
RUST_VERSION = 1.80
|
||||
|
||||
VERSION = $(shell $(CARGO) metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
|
||||
REVISION ?= 1
|
||||
@@ -13,6 +14,9 @@ PKG = $(PKG_NAME)-$(VERSION)
|
||||
SERIES ?= $(shell lsb_release -cs)
|
||||
PUBLISH ?= 0
|
||||
|
||||
# Indicates whether to build the GUI components
|
||||
BUILD_GUI ?= 1
|
||||
|
||||
export DEBEMAIL = [email protected]
|
||||
export DEBFULLNAME = Kevin Yue
|
||||
export SNAPSHOT = $(shell test -f SNAPSHOT && echo "true" || echo "false")
|
||||
@@ -42,7 +46,7 @@ clean-tarball:
|
||||
|
||||
# Create a tarball, include the cargo dependencies if OFFLINE is set to 1
|
||||
tarball: clean-tarball
|
||||
if [ $(BUILD_FE) -eq 1 ]; then \
|
||||
if [ $(BUILD_GUI) -eq 1 ] && [ $(BUILD_FE) -eq 1 ]; then \
|
||||
echo "Building frontend..."; \
|
||||
cd apps/gpgui-helper && pnpm install && pnpm build; \
|
||||
fi
|
||||
@@ -80,13 +84,13 @@ build: download-gui build-fe build-rs
|
||||
# Install and build the frontend
|
||||
# If OFFLINE is set to 1, skip it
|
||||
build-fe:
|
||||
if [ $(OFFLINE) -eq 1 ] || [ $(BUILD_FE) -eq 0 ]; then \
|
||||
echo "Skipping frontend build (OFFLINE=1 or BUILD_FE=0)"; \
|
||||
if [ $(BUILD_GUI) -eq 0 ] || [ $(OFFLINE) -eq 1 ] || [ $(BUILD_FE) -eq 0 ]; then \
|
||||
echo "Skipping frontend build (BUILD_GUI=0 or OFFLINE=1 or BUILD_FE=0)"; \
|
||||
else \
|
||||
cd apps/gpgui-helper && pnpm install && pnpm build; \
|
||||
fi
|
||||
|
||||
if [ ! -d apps/gpgui-helper/dist ]; then \
|
||||
if [ $(BUILD_GUI) -eq 1 ] && [ ! -d apps/gpgui-helper/dist ]; then \
|
||||
echo "Error: frontend build failed"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@@ -96,8 +100,13 @@ build-rs:
|
||||
tar -xJf vendor.tar.xz; \
|
||||
fi
|
||||
|
||||
$(CARGO) build $(CARGO_BUILD_ARGS) -p gpclient -p gpservice -p gpauth
|
||||
$(CARGO) build $(CARGO_BUILD_ARGS) -p gpgui-helper --features "tauri/custom-protocol"
|
||||
# Only build the GUI components if BUILD_GUI is set to 1
|
||||
if [ $(BUILD_GUI) -eq 1 ]; then \
|
||||
$(CARGO) build $(CARGO_BUILD_ARGS) -p gpclient -p gpservice -p gpauth; \
|
||||
$(CARGO) build $(CARGO_BUILD_ARGS) -p gpgui-helper --features "tauri/custom-protocol"; \
|
||||
else \
|
||||
$(CARGO) build $(CARGO_BUILD_ARGS) -p gpclient -p gpservice -p gpauth --no-default-features; \
|
||||
fi
|
||||
|
||||
clean:
|
||||
$(CARGO) clean
|
||||
@@ -111,7 +120,11 @@ install:
|
||||
install -Dm755 target/release/gpclient $(DESTDIR)/usr/bin/gpclient
|
||||
install -Dm755 target/release/gpauth $(DESTDIR)/usr/bin/gpauth
|
||||
install -Dm755 target/release/gpservice $(DESTDIR)/usr/bin/gpservice
|
||||
install -Dm755 target/release/gpgui-helper $(DESTDIR)/usr/bin/gpgui-helper
|
||||
|
||||
# Install the GUI components if BUILD_GUI is set to 1
|
||||
if [ $(BUILD_GUI) -eq 1 ]; then \
|
||||
install -Dm755 target/release/gpgui-helper $(DESTDIR)/usr/bin/gpgui-helper; \
|
||||
fi
|
||||
|
||||
if [ -f .build/gpgui/gpgui_*/gpgui ]; then \
|
||||
install -Dm755 .build/gpgui/gpgui_*/gpgui $(DESTDIR)/usr/bin/gpgui; \
|
||||
@@ -154,13 +167,25 @@ init-debian: clean-debian tarball
|
||||
cp -f packaging/deb/control.in .build/deb/$(PKG)/debian/control
|
||||
cp -f packaging/deb/rules.in .build/deb/$(PKG)/debian/rules
|
||||
cp -f packaging/deb/postrm .build/deb/$(PKG)/debian/postrm
|
||||
cp -f packaging/deb/compat .build/deb/$(PKG)/debian/compat
|
||||
|
||||
sed -i "s/@OFFLINE@/$(OFFLINE)/g" .build/deb/$(PKG)/debian/rules
|
||||
sed -i "s/@BUILD_GUI@/$(BUILD_GUI)/g" .build/deb/$(PKG)/debian/rules
|
||||
sed -i "s/@RUST_VERSION@/$(RUST_VERSION)/g" .build/deb/$(PKG)/debian/rules
|
||||
|
||||
# Remove the GUI dependencies if BUILD_GUI is set to 0
|
||||
if [ $(BUILD_GUI) -eq 0 ]; then \
|
||||
sed -i "/libxml2/d" .build/deb/$(PKG)/debian/control; \
|
||||
sed -i "/libsecret-1-0/d" .build/deb/$(PKG)/debian/control; \
|
||||
sed -i "/libayatana-appindicator3-1/d" .build/deb/$(PKG)/debian/control; \
|
||||
sed -i "/gnome-keyring/d" .build/deb/$(PKG)/debian/control; \
|
||||
sed -i "/libwebkit2gtk-4.1-dev/d" .build/deb/$(PKG)/debian/control; \
|
||||
fi
|
||||
|
||||
rm -f .build/deb/$(PKG)/debian/changelog
|
||||
|
||||
deb: init-debian
|
||||
# Remove the rust build depdency from the control file
|
||||
# Remove the rust build dependency from the control file
|
||||
sed -i "s/@RUST@//g" .build/deb/$(PKG)/debian/control
|
||||
|
||||
cd .build/deb/$(PKG) && dch --create --distribution unstable --package $(PKG_NAME) --newversion $(VERSION)-$(REVISION) "Bugfix and improvements."
|
||||
@@ -174,7 +199,7 @@ check-ppa:
|
||||
|
||||
# Usage: make ppa SERIES=focal OFFLINE=1 PUBLISH=1
|
||||
ppa: check-ppa init-debian
|
||||
sed -i "s/@RUST@/rust-all(>=1.70)/g" .build/deb/$(PKG)/debian/control
|
||||
sed -i "s/@RUST@/cargo-1.80/g" .build/deb/$(PKG)/debian/control
|
||||
|
||||
$(eval SERIES_VER = $(shell distro-info --series $(SERIES) -r | cut -d' ' -f1))
|
||||
@echo "Building for $(SERIES) $(SERIES_VER)"
|
||||
|
||||
@@ -70,12 +70,10 @@ The GUI version is also available after you installed it. You can launch it from
|
||||
|
||||
### Debian/Ubuntu based distributions
|
||||
|
||||
#### Install from PPA (Ubuntu 18.04 and later, except 24.04)
|
||||
#### Install from PPA (Ubuntu > 18.04)
|
||||
|
||||
```
|
||||
sudo apt-get install gir1.2-gtk-3.0 gir1.2-webkit2-4.0
|
||||
sudo add-apt-repository ppa:yuezk/globalprotect-openconnect
|
||||
sudo apt-get update
|
||||
sudo apt-get install globalprotect-openconnect
|
||||
```
|
||||
|
||||
@@ -83,18 +81,9 @@ sudo apt-get install globalprotect-openconnect
|
||||
>
|
||||
> For Linux Mint, you might need to import the GPG key with: `sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7937C393082992E5D6E4A60453FC26B43838D761` if you encountered an error `gpg: keyserver receive failed: General error`.
|
||||
|
||||
#### **Ubuntu 24.04 and later**
|
||||
|
||||
The `libwebkit2gtk-4.0-37` package was [removed](https://bugs.launchpad.net/ubuntu/+source/webkit2gtk/+bug/2061914) from its repo. You can use the [`deb-install.sh`](./scripts/deb-install.sh) script to install the package:
|
||||
|
||||
```bash
|
||||
curl -o- https://raw.githubusercontent.com/yuezk/GlobalProtect-openconnect/main/scripts/deb-install.sh \
|
||||
| bash -s -- 2.3.9
|
||||
```
|
||||
|
||||
#### **Ubuntu 18.04**
|
||||
|
||||
The latest package is not available in the PPA either, but you still needs to add the `ppa:yuezk/globalprotect-openconnect` repo beforehand to use the required `openconnect` package. Then you can follow the [Install from deb package](#install-from-deb-package) section to install the latest package.
|
||||
The latest package is not available in the PPA, but you still needs to add the `ppa:yuezk/globalprotect-openconnect` repo beforehand to use the required `openconnect` package. Then you can follow the [Install from deb package](#install-from-deb-package) section to install the latest package.
|
||||
|
||||
#### Install from deb package
|
||||
|
||||
@@ -172,8 +161,8 @@ You can also build the client from source, steps are as follows:
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Install Rust 1.75 or later](https://www.rust-lang.org/tools/install)
|
||||
- Install Tauri dependencies: https://tauri.app/v1/guides/getting-started/prerequisites/#setting-up-linux
|
||||
- [Install Rust 1.80 or later](https://www.rust-lang.org/tools/install)
|
||||
- Install Tauri dependencies: https://tauri.app/start/prerequisites/
|
||||
- Install `perl` and `jq`
|
||||
- Install `openconnect >= 8.20` and `libopenconnect-dev` (or `openconnect-devel` on RPM-based distributions)
|
||||
- Install `pkexec`, `gnome-keyring` (or `pam_kwallet` on KDE)
|
||||
@@ -210,6 +199,7 @@ The CLI version is always free, while the GUI version is paid. There are two tri
|
||||
- crate [gpapi](./crates/gpapi): [MIT](./crates/gpapi/LICENSE)
|
||||
- crate [openconnect](./crates/openconnect): [GPL-3.0](./crates/openconnect/LICENSE)
|
||||
- crate [common](./crates/common): [GPL-3.0](./crates/common/LICENSE)
|
||||
- crate [auth](./crates/auth): [GPL-3.0](./crates/auth/LICENSE)
|
||||
- app [gpservice](./apps/gpservice): [GPL-3.0](./apps/gpservice/LICENSE)
|
||||
- app [gpclient](./apps/gpclient): [GPL-3.0](./apps/gpclient/LICENSE)
|
||||
- app [gpauth](./apps/gpauth): [GPL-3.0](./apps/gpauth/LICENSE)
|
||||
|
||||
+14
-11
@@ -1,29 +1,32 @@
|
||||
[package]
|
||||
name = "gpauth"
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.5", features = [] }
|
||||
tauri-build = { version = "2", features = [], optional = true }
|
||||
|
||||
[dependencies]
|
||||
gpapi = { path = "../../crates/gpapi", features = [
|
||||
"tauri",
|
||||
"clap",
|
||||
"browser-auth",
|
||||
] }
|
||||
gpapi = { path = "../../crates/gpapi", features = ["clap"] }
|
||||
|
||||
auth = { path = "../../crates/auth", features = ["browser-auth"] }
|
||||
|
||||
# Shared dependencies
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
env_logger.workspace = true
|
||||
log.workspace = true
|
||||
regex.workspace = true
|
||||
serde_json.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-util.workspace = true
|
||||
tempfile.workspace = true
|
||||
html-escape = "0.2.13"
|
||||
webkit2gtk = "0.18.2"
|
||||
tauri = { workspace = true, features = ["http-all"] }
|
||||
compile-time.workspace = true
|
||||
|
||||
# webview auth dependencies
|
||||
tauri = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
default = ["webview-auth"]
|
||||
webview-auth = ["auth/webview-auth", "dep:tauri", "dep:tauri-build"]
|
||||
@@ -1,3 +1,4 @@
|
||||
fn main() {
|
||||
#[cfg(feature = "webview-auth")]
|
||||
tauri_build::build()
|
||||
}
|
||||
@@ -1,523 +0,0 @@
|
||||
use std::{
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use anyhow::bail;
|
||||
use gpapi::{
|
||||
auth::SamlAuthData,
|
||||
error::AuthDataParseError,
|
||||
gp_params::GpParams,
|
||||
portal::{prelogin, Prelogin},
|
||||
utils::{redact::redact_uri, window::WindowExt},
|
||||
};
|
||||
use log::{info, warn};
|
||||
use regex::Regex;
|
||||
use tauri::{AppHandle, Window, WindowEvent, WindowUrl};
|
||||
use tokio::sync::{mpsc, oneshot, RwLock};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use webkit2gtk::{
|
||||
gio::Cancellable,
|
||||
glib::{GString, TimeSpan},
|
||||
LoadEvent, SettingsExt, TLSErrorsPolicy, URIResponse, URIResponseExt, WebContextExt, WebResource, WebResourceExt,
|
||||
WebView, WebViewExt, WebsiteDataManagerExtManual, WebsiteDataTypes,
|
||||
};
|
||||
|
||||
enum AuthDataError {
|
||||
/// Failed to load page due to TLS error
|
||||
TlsError,
|
||||
/// 1. Found auth data in headers/body but it's invalid
|
||||
/// 2. Loaded an empty page, failed to load page. etc.
|
||||
Invalid,
|
||||
/// No auth data found in headers/body
|
||||
NotFound,
|
||||
}
|
||||
|
||||
type AuthResult = Result<SamlAuthData, AuthDataError>;
|
||||
|
||||
pub(crate) struct AuthWindow<'a> {
|
||||
app_handle: AppHandle,
|
||||
server: &'a str,
|
||||
saml_request: &'a str,
|
||||
user_agent: &'a str,
|
||||
gp_params: Option<GpParams>,
|
||||
clean: bool,
|
||||
}
|
||||
|
||||
impl<'a> AuthWindow<'a> {
|
||||
pub fn new(app_handle: AppHandle) -> Self {
|
||||
Self {
|
||||
app_handle,
|
||||
server: "",
|
||||
saml_request: "",
|
||||
user_agent: "",
|
||||
gp_params: None,
|
||||
clean: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn server(mut self, server: &'a str) -> Self {
|
||||
self.server = server;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn saml_request(mut self, saml_request: &'a str) -> Self {
|
||||
self.saml_request = saml_request;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn user_agent(mut self, user_agent: &'a str) -> Self {
|
||||
self.user_agent = user_agent;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn gp_params(mut self, gp_params: GpParams) -> Self {
|
||||
self.gp_params.replace(gp_params);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn clean(mut self, clean: bool) -> Self {
|
||||
self.clean = clean;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn open(&self) -> anyhow::Result<SamlAuthData> {
|
||||
info!("Open auth window, user_agent: {}", self.user_agent);
|
||||
|
||||
let window = Window::builder(&self.app_handle, "auth_window", WindowUrl::default())
|
||||
.title("GlobalProtect Login")
|
||||
// .user_agent(self.user_agent)
|
||||
.focused(true)
|
||||
.visible(false)
|
||||
.center()
|
||||
.build()?;
|
||||
|
||||
let window = Arc::new(window);
|
||||
|
||||
let cancel_token = CancellationToken::new();
|
||||
let cancel_token_clone = cancel_token.clone();
|
||||
|
||||
window.on_window_event(move |event| {
|
||||
if let WindowEvent::CloseRequested { .. } = event {
|
||||
cancel_token_clone.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
let window_clone = Arc::clone(&window);
|
||||
let timeout_secs = 15;
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_secs(timeout_secs)).await;
|
||||
let visible = window_clone.is_visible().unwrap_or(false);
|
||||
if !visible {
|
||||
info!("Try to raise auth window after {} seconds", timeout_secs);
|
||||
raise_window(&window_clone);
|
||||
}
|
||||
});
|
||||
|
||||
tokio::select! {
|
||||
_ = cancel_token.cancelled() => {
|
||||
bail!("Auth cancelled");
|
||||
}
|
||||
saml_result = self.auth_loop(&window) => {
|
||||
window.close()?;
|
||||
saml_result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn auth_loop(&self, window: &Arc<Window>) -> anyhow::Result<SamlAuthData> {
|
||||
let saml_request = self.saml_request.to_string();
|
||||
let (auth_result_tx, mut auth_result_rx) = mpsc::unbounded_channel::<AuthResult>();
|
||||
let raise_window_cancel_token: Arc<RwLock<Option<CancellationToken>>> = Default::default();
|
||||
let gp_params = self.gp_params.as_ref().unwrap();
|
||||
let tls_err_policy = if gp_params.ignore_tls_errors() {
|
||||
TLSErrorsPolicy::Ignore
|
||||
} else {
|
||||
TLSErrorsPolicy::Fail
|
||||
};
|
||||
|
||||
if self.clean {
|
||||
clear_webview_cookies(window).await?;
|
||||
}
|
||||
|
||||
let raise_window_cancel_token_clone = Arc::clone(&raise_window_cancel_token);
|
||||
window.with_webview(move |wv| {
|
||||
let wv = wv.inner();
|
||||
|
||||
if let Some(context) = wv.context() {
|
||||
context.set_tls_errors_policy(tls_err_policy);
|
||||
}
|
||||
|
||||
if let Some(settings) = wv.settings() {
|
||||
let ua = settings.user_agent().unwrap_or("".into());
|
||||
info!("Auth window user agent: {}", ua);
|
||||
}
|
||||
|
||||
// Load the initial SAML request
|
||||
load_saml_request(&wv, &saml_request);
|
||||
|
||||
let auth_result_tx_clone = auth_result_tx.clone();
|
||||
wv.connect_load_changed(move |wv, event| {
|
||||
if event == LoadEvent::Started {
|
||||
let Ok(mut cancel_token) = raise_window_cancel_token_clone.try_write() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Cancel the raise window task
|
||||
if let Some(cancel_token) = cancel_token.take() {
|
||||
cancel_token.cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if event != LoadEvent::Finished {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(main_resource) = wv.main_resource() {
|
||||
let uri = main_resource.uri().unwrap_or("".into());
|
||||
|
||||
if uri.is_empty() {
|
||||
warn!("Loaded an empty uri");
|
||||
send_auth_result(&auth_result_tx_clone, Err(AuthDataError::Invalid));
|
||||
return;
|
||||
}
|
||||
|
||||
info!("Loaded uri: {}", redact_uri(&uri));
|
||||
if uri.starts_with("globalprotectcallback:") {
|
||||
return;
|
||||
}
|
||||
|
||||
read_auth_data(&main_resource, auth_result_tx_clone.clone());
|
||||
}
|
||||
});
|
||||
|
||||
let auth_result_tx_clone = auth_result_tx.clone();
|
||||
wv.connect_load_failed_with_tls_errors(move |_wv, uri, cert, err| {
|
||||
let redacted_uri = redact_uri(uri);
|
||||
warn!(
|
||||
"Failed to load uri: {} with error: {}, cert: {}",
|
||||
redacted_uri, err, cert
|
||||
);
|
||||
|
||||
send_auth_result(&auth_result_tx_clone, Err(AuthDataError::TlsError));
|
||||
true
|
||||
});
|
||||
|
||||
wv.connect_load_failed(move |_wv, _event, uri, err| {
|
||||
let redacted_uri = redact_uri(uri);
|
||||
if !uri.starts_with("globalprotectcallback:") {
|
||||
warn!("Failed to load uri: {} with error: {}", redacted_uri, err);
|
||||
}
|
||||
// NOTE: Don't send error here, since load_changed event will be triggered after this
|
||||
// send_auth_result(&auth_result_tx, Err(AuthDataError::Invalid));
|
||||
// true to stop other handlers from being invoked for the event. false to propagate the event further.
|
||||
true
|
||||
});
|
||||
})?;
|
||||
|
||||
let portal = self.server.to_string();
|
||||
|
||||
loop {
|
||||
if let Some(auth_result) = auth_result_rx.recv().await {
|
||||
match auth_result {
|
||||
Ok(auth_data) => return Ok(auth_data),
|
||||
Err(AuthDataError::TlsError) => bail!("TLS error: certificate verify failed"),
|
||||
Err(AuthDataError::NotFound) => {
|
||||
info!("No auth data found, it may not be the /SAML20/SP/ACS endpoint");
|
||||
|
||||
// The user may need to interact with the auth window, raise it in 3 seconds
|
||||
if !window.is_visible().unwrap_or(false) {
|
||||
let window = Arc::clone(window);
|
||||
let cancel_token = CancellationToken::new();
|
||||
|
||||
raise_window_cancel_token.write().await.replace(cancel_token.clone());
|
||||
|
||||
tokio::spawn(async move {
|
||||
let delay_secs = 1;
|
||||
|
||||
info!("Raise window in {} second(s)", delay_secs);
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(Duration::from_secs(delay_secs)) => {
|
||||
raise_window(&window);
|
||||
}
|
||||
_ = cancel_token.cancelled() => {
|
||||
info!("Raise window cancelled");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(AuthDataError::Invalid) => {
|
||||
info!("Got invalid auth data, retrying...");
|
||||
|
||||
window.with_webview(|wv| {
|
||||
let wv = wv.inner();
|
||||
wv.run_javascript(r#"
|
||||
var loading = document.createElement("div");
|
||||
loading.innerHTML = '<div style="position: absolute; width: 100%; text-align: center; font-size: 20px; font-weight: bold; top: 50%; left: 50%; transform: translate(-50%, -50%);">Got invalid token, retrying...</div>';
|
||||
loading.style = "position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.85); z-index: 99999;";
|
||||
document.body.appendChild(loading);
|
||||
"#,
|
||||
Cancellable::NONE,
|
||||
|_| info!("Injected loading element successfully"),
|
||||
);
|
||||
})?;
|
||||
|
||||
let saml_request = portal_prelogin(&portal, gp_params).await?;
|
||||
window.with_webview(move |wv| {
|
||||
let wv = wv.inner();
|
||||
load_saml_request(&wv, &saml_request);
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn raise_window(window: &Arc<Window>) {
|
||||
let visible = window.is_visible().unwrap_or(false);
|
||||
if !visible {
|
||||
if let Err(err) = window.raise() {
|
||||
warn!("Failed to raise window: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn portal_prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<String> {
|
||||
match prelogin(portal, gp_params).await? {
|
||||
Prelogin::Saml(prelogin) => Ok(prelogin.saml_request().to_string()),
|
||||
Prelogin::Standard(_) => bail!("Received non-SAML prelogin response"),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_auth_result(auth_result_tx: &mpsc::UnboundedSender<AuthResult>, auth_result: AuthResult) {
|
||||
if let Err(err) = auth_result_tx.send(auth_result) {
|
||||
warn!("Failed to send auth event: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn load_saml_request(wv: &Rc<WebView>, saml_request: &str) {
|
||||
if saml_request.starts_with("http") {
|
||||
info!("Load the SAML request as URI...");
|
||||
wv.load_uri(saml_request);
|
||||
} else {
|
||||
info!("Load the SAML request as HTML...");
|
||||
wv.load_html(saml_request, None);
|
||||
}
|
||||
}
|
||||
|
||||
fn read_auth_data_from_headers(response: &URIResponse) -> AuthResult {
|
||||
response.http_headers().map_or_else(
|
||||
|| {
|
||||
info!("No headers found in response");
|
||||
Err(AuthDataError::NotFound)
|
||||
},
|
||||
|mut headers| match headers.get("saml-auth-status") {
|
||||
Some(status) if status == "1" => {
|
||||
let username = headers.get("saml-username").map(GString::into);
|
||||
let prelogin_cookie = headers.get("prelogin-cookie").map(GString::into);
|
||||
let portal_userauthcookie = headers.get("portal-userauthcookie").map(GString::into);
|
||||
|
||||
if SamlAuthData::check(&username, &prelogin_cookie, &portal_userauthcookie) {
|
||||
return Ok(SamlAuthData::new(
|
||||
username.unwrap(),
|
||||
prelogin_cookie,
|
||||
portal_userauthcookie,
|
||||
));
|
||||
}
|
||||
|
||||
info!("Found invalid auth data in headers");
|
||||
Err(AuthDataError::Invalid)
|
||||
}
|
||||
Some(status) => {
|
||||
info!("Found invalid SAML status: {} in headers", status);
|
||||
Err(AuthDataError::Invalid)
|
||||
}
|
||||
None => {
|
||||
info!("No saml-auth-status header found");
|
||||
Err(AuthDataError::NotFound)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn read_auth_data_from_body<F>(main_resource: &WebResource, callback: F)
|
||||
where
|
||||
F: FnOnce(Result<SamlAuthData, AuthDataParseError>) + Send + 'static,
|
||||
{
|
||||
main_resource.data(Cancellable::NONE, |data| match data {
|
||||
Ok(data) => {
|
||||
let html = String::from_utf8_lossy(&data);
|
||||
callback(read_auth_data_from_html(&html));
|
||||
}
|
||||
Err(err) => {
|
||||
info!("Failed to read response body: {}", err);
|
||||
callback(Err(AuthDataParseError::Invalid))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn read_auth_data_from_html(html: &str) -> Result<SamlAuthData, AuthDataParseError> {
|
||||
if html.contains("Temporarily Unavailable") {
|
||||
info!("Found 'Temporarily Unavailable' in HTML, auth failed");
|
||||
return Err(AuthDataParseError::Invalid);
|
||||
}
|
||||
|
||||
SamlAuthData::from_html(html).or_else(|err| {
|
||||
if let Some(gpcallback) = extract_gpcallback(html) {
|
||||
info!("Found gpcallback from html...");
|
||||
SamlAuthData::from_gpcallback(&gpcallback)
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_gpcallback(html: &str) -> Option<String> {
|
||||
let re = Regex::new(r#"globalprotectcallback:[^"]+"#).unwrap();
|
||||
re.captures(html)
|
||||
.and_then(|captures| captures.get(0))
|
||||
.map(|m| html_escape::decode_html_entities(m.as_str()).to_string())
|
||||
}
|
||||
|
||||
fn read_auth_data(main_resource: &WebResource, auth_result_tx: mpsc::UnboundedSender<AuthResult>) {
|
||||
let Some(response) = main_resource.response() else {
|
||||
info!("No response found in main resource");
|
||||
send_auth_result(&auth_result_tx, Err(AuthDataError::Invalid));
|
||||
return;
|
||||
};
|
||||
|
||||
info!("Trying to read auth data from response headers...");
|
||||
|
||||
match read_auth_data_from_headers(&response) {
|
||||
Ok(auth_data) => {
|
||||
info!("Got auth data from headers");
|
||||
send_auth_result(&auth_result_tx, Ok(auth_data));
|
||||
}
|
||||
Err(AuthDataError::Invalid) => {
|
||||
info!("Found invalid auth data in headers, trying to read from body...");
|
||||
read_auth_data_from_body(main_resource, move |auth_result| {
|
||||
// Since we have already found invalid auth data in headers, which means this could be the `/SAML20/SP/ACS` endpoint
|
||||
// any error result from body should be considered as invalid, and trigger a retry
|
||||
let auth_result = auth_result.map_err(|err| {
|
||||
info!("Failed to read auth data from body: {}", err);
|
||||
AuthDataError::Invalid
|
||||
});
|
||||
send_auth_result(&auth_result_tx, auth_result);
|
||||
});
|
||||
}
|
||||
Err(AuthDataError::NotFound) => {
|
||||
info!("No auth data found in headers, trying to read from body...");
|
||||
|
||||
let is_acs_endpoint = main_resource.uri().map_or(false, |uri| uri.contains("/SAML20/SP/ACS"));
|
||||
|
||||
read_auth_data_from_body(main_resource, move |auth_result| {
|
||||
// If the endpoint is `/SAML20/SP/ACS` and no auth data found in body, it should be considered as invalid
|
||||
let auth_result = auth_result.map_err(|err| {
|
||||
info!("Failed to read auth data from body: {}", err);
|
||||
|
||||
if !is_acs_endpoint && matches!(err, AuthDataParseError::NotFound) {
|
||||
AuthDataError::NotFound
|
||||
} else {
|
||||
AuthDataError::Invalid
|
||||
}
|
||||
});
|
||||
|
||||
send_auth_result(&auth_result_tx, auth_result)
|
||||
});
|
||||
}
|
||||
Err(AuthDataError::TlsError) => {
|
||||
// NOTE: This is unreachable
|
||||
info!("TLS error found in headers, trying to read from body...");
|
||||
send_auth_result(&auth_result_tx, Err(AuthDataError::TlsError));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn clear_webview_cookies(window: &Window) -> anyhow::Result<()> {
|
||||
let (tx, rx) = oneshot::channel::<Result<(), String>>();
|
||||
|
||||
window.with_webview(|wv| {
|
||||
let send_result = move |result: Result<(), String>| {
|
||||
if let Err(err) = tx.send(result) {
|
||||
info!("Failed to send result: {:?}", err);
|
||||
}
|
||||
};
|
||||
|
||||
let wv = wv.inner();
|
||||
let context = match wv.context() {
|
||||
Some(context) => context,
|
||||
None => {
|
||||
send_result(Err("No webview context found".into()));
|
||||
return;
|
||||
}
|
||||
};
|
||||
let data_manager = match context.website_data_manager() {
|
||||
Some(manager) => manager,
|
||||
None => {
|
||||
send_result(Err("No data manager found".into()));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let now = Instant::now();
|
||||
data_manager.clear(
|
||||
WebsiteDataTypes::COOKIES,
|
||||
TimeSpan(0),
|
||||
Cancellable::NONE,
|
||||
move |result| match result {
|
||||
Err(err) => {
|
||||
send_result(Err(err.to_string()));
|
||||
}
|
||||
Ok(_) => {
|
||||
info!("Cookies cleared in {} ms", now.elapsed().as_millis());
|
||||
send_result(Ok(()));
|
||||
}
|
||||
},
|
||||
);
|
||||
})?;
|
||||
|
||||
rx.await?.map_err(|err| anyhow::anyhow!(err))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn extract_gpcallback_some() {
|
||||
let html = r#"
|
||||
<meta http-equiv="refresh" content="0; URL=globalprotectcallback:PGh0bWw+PCEtLSA8c">
|
||||
<meta http-equiv="refresh" content="0; URL=globalprotectcallback:PGh0bWw+PCEtLSA8c">
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
extract_gpcallback(html).as_deref(),
|
||||
Some("globalprotectcallback:PGh0bWw+PCEtLSA8c")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_gpcallback_cas() {
|
||||
let html = r#"
|
||||
<meta http-equiv="refresh" content="0; URL=globalprotectcallback:cas-as=1&[email protected]&token=very_long_string">
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
extract_gpcallback(html).as_deref(),
|
||||
Some("globalprotectcallback:cas-as=1&[email protected]&token=very_long_string")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_gpcallback_none() {
|
||||
let html = r#"
|
||||
<meta http-equiv="refresh" content="0; URL=PGh0bWw+PCEtLSA8c">
|
||||
"#;
|
||||
|
||||
assert_eq!(extract_gpcallback(html), None);
|
||||
}
|
||||
}
|
||||
+76
-150
@@ -1,21 +1,17 @@
|
||||
use std::{env::temp_dir, fs, os::unix::fs::PermissionsExt};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use auth::{auth_prelogin, Authenticator, BrowserAuthenticator};
|
||||
use clap::Parser;
|
||||
use gpapi::{
|
||||
auth::{SamlAuthData, SamlAuthResult},
|
||||
clap::args::Os,
|
||||
clap::{args::Os, handle_error, Args},
|
||||
gp_params::{ClientOs, GpParams},
|
||||
process::browser_authenticator::BrowserAuthenticator,
|
||||
utils::{normalize_server, openssl},
|
||||
GP_USER_AGENT,
|
||||
};
|
||||
use log::{info, LevelFilter};
|
||||
use serde_json::json;
|
||||
use tauri::{App, AppHandle, RunEvent};
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::{io::AsyncReadExt, net::TcpListener};
|
||||
|
||||
use crate::auth_window::{portal_prelogin, AuthWindow};
|
||||
|
||||
const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::date_str!(), ")");
|
||||
|
||||
@@ -37,7 +33,7 @@ const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::dat
|
||||
See 'gpauth -h' for more information.
|
||||
"
|
||||
)]
|
||||
struct Cli {
|
||||
pub(crate) struct Cli {
|
||||
#[arg(help = "The portal server to authenticate")]
|
||||
server: String,
|
||||
|
||||
@@ -56,18 +52,13 @@ struct Cli {
|
||||
#[arg(long)]
|
||||
os_version: Option<String>,
|
||||
|
||||
#[arg(long, help = "The HiDPI mode, useful for high-resolution screens")]
|
||||
hidpi: bool,
|
||||
|
||||
#[arg(long, help = "Get around the OpenSSL `unsafe legacy renegotiation` error")]
|
||||
fix_openssl: bool,
|
||||
|
||||
#[arg(long, help = "Ignore TLS errors")]
|
||||
ignore_tls_errors: bool,
|
||||
|
||||
#[arg(long, help = "Clean the cache of the embedded browser")]
|
||||
clean: bool,
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
#[arg(long, help = "Use the default browser for authentication")]
|
||||
default_browser: bool,
|
||||
|
||||
@@ -76,76 +67,30 @@ struct Cli {
|
||||
help = "The browser to use for authentication, e.g., `default`, `firefox`, `chrome`, `chromium`, or the path to the browser executable"
|
||||
)]
|
||||
browser: Option<String>,
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
#[arg(long, help = "The HiDPI mode, useful for high-resolution screens")]
|
||||
hidpi: bool,
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
#[arg(long, help = "Clean the cache of the embedded browser")]
|
||||
pub clean: bool,
|
||||
}
|
||||
|
||||
impl Args for Cli {
|
||||
fn fix_openssl(&self) -> bool {
|
||||
self.fix_openssl
|
||||
}
|
||||
|
||||
fn ignore_tls_errors(&self) -> bool {
|
||||
self.ignore_tls_errors
|
||||
}
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
async fn run(&mut self) -> anyhow::Result<()> {
|
||||
if self.ignore_tls_errors {
|
||||
info!("TLS errors will be ignored");
|
||||
}
|
||||
|
||||
let mut openssl_conf = self.prepare_env()?;
|
||||
|
||||
self.server = normalize_server(&self.server)?;
|
||||
let gp_params = self.build_gp_params();
|
||||
|
||||
// Get the initial SAML request
|
||||
let saml_request = match self.saml_request {
|
||||
Some(ref saml_request) => saml_request.clone(),
|
||||
None => portal_prelogin(&self.server, &gp_params).await?,
|
||||
};
|
||||
|
||||
let browser_auth = if let Some(browser) = &self.browser {
|
||||
Some(BrowserAuthenticator::new_with_browser(&saml_request, browser))
|
||||
} else if self.default_browser {
|
||||
Some(BrowserAuthenticator::new(&saml_request))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(browser_auth) = browser_auth {
|
||||
browser_auth.authenticate()?;
|
||||
|
||||
info!("Please continue the authentication process in the default browser");
|
||||
|
||||
let auth_result = match wait_auth_data().await {
|
||||
Ok(auth_data) => SamlAuthResult::Success(auth_data),
|
||||
Err(err) => SamlAuthResult::Failure(format!("{}", err)),
|
||||
};
|
||||
|
||||
info!("Authentication completed");
|
||||
|
||||
println!("{}", json!(auth_result));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.saml_request.replace(saml_request);
|
||||
|
||||
let app = create_app(self.clone())?;
|
||||
|
||||
app.run(move |_app_handle, event| {
|
||||
if let RunEvent::Exit = event {
|
||||
if let Some(file) = openssl_conf.take() {
|
||||
if let Err(err) = file.close() {
|
||||
info!("Error closing OpenSSL config file: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_env(&self) -> anyhow::Result<Option<NamedTempFile>> {
|
||||
std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "1");
|
||||
|
||||
if self.hidpi {
|
||||
info!("Setting GDK_SCALE=2 and GDK_DPI_SCALE=0.5");
|
||||
|
||||
std::env::set_var("GDK_SCALE", "2");
|
||||
std::env::set_var("GDK_DPI_SCALE", "0.5");
|
||||
}
|
||||
#[cfg(feature = "webview-auth")]
|
||||
gpapi::utils::env_utils::patch_gui_runtime_env(self.hidpi);
|
||||
|
||||
if self.fix_openssl {
|
||||
info!("Fixing OpenSSL environment");
|
||||
@@ -157,6 +102,49 @@ impl Cli {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn run(&self) -> anyhow::Result<()> {
|
||||
if self.ignore_tls_errors {
|
||||
info!("TLS errors will be ignored");
|
||||
}
|
||||
|
||||
let openssl_conf = self.prepare_env()?;
|
||||
|
||||
let server = normalize_server(&self.server)?;
|
||||
let server: &'static str = Box::leak(server.into_boxed_str());
|
||||
let gp_params: &'static GpParams = Box::leak(Box::new(self.build_gp_params()));
|
||||
|
||||
let auth_request = match self.saml_request.as_deref() {
|
||||
Some(auth_request) => Cow::Borrowed(auth_request),
|
||||
None => Cow::Owned(auth_prelogin(server, gp_params).await?),
|
||||
};
|
||||
|
||||
let auth_request: &'static str = Box::leak(auth_request.into_owned().into_boxed_str());
|
||||
let authenticator = Authenticator::new(&server, gp_params).with_auth_request(&auth_request);
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
let browser = self
|
||||
.browser
|
||||
.as_deref()
|
||||
.or_else(|| self.default_browser.then_some("default"));
|
||||
|
||||
#[cfg(not(feature = "webview-auth"))]
|
||||
let browser = self.browser.as_deref().or(Some("default"));
|
||||
|
||||
if browser.is_some() {
|
||||
let auth_result = authenticator.browser_authenticate(browser).await;
|
||||
print_auth_result(auth_result);
|
||||
|
||||
// explicitly drop openssl_conf to avoid the unused variable warning
|
||||
drop(openssl_conf);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
crate::webview_auth::authenticate(&self, authenticator, openssl_conf)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_gp_params(&self) -> GpParams {
|
||||
let gp_params = GpParams::builder()
|
||||
.user_agent(&self.user_agent)
|
||||
@@ -168,37 +156,6 @@ impl Cli {
|
||||
|
||||
gp_params
|
||||
}
|
||||
|
||||
async fn saml_auth(&self, app_handle: AppHandle) -> anyhow::Result<SamlAuthData> {
|
||||
let auth_window = AuthWindow::new(app_handle)
|
||||
.server(&self.server)
|
||||
.user_agent(&self.user_agent)
|
||||
.gp_params(self.build_gp_params())
|
||||
.saml_request(self.saml_request.as_ref().unwrap())
|
||||
.clean(self.clean);
|
||||
|
||||
auth_window.open().await
|
||||
}
|
||||
}
|
||||
|
||||
fn create_app(cli: Cli) -> anyhow::Result<App> {
|
||||
let app = tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
let app_handle = app.handle();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let auth_result = match cli.saml_auth(app_handle.clone()).await {
|
||||
Ok(auth_data) => SamlAuthResult::Success(auth_data),
|
||||
Err(err) => SamlAuthResult::Failure(format!("{}", err)),
|
||||
};
|
||||
|
||||
println!("{}", json!(auth_result));
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
.build(tauri::generate_context!())?;
|
||||
|
||||
Ok(app)
|
||||
}
|
||||
|
||||
fn init_logger() {
|
||||
@@ -206,53 +163,22 @@ fn init_logger() {
|
||||
}
|
||||
|
||||
pub async fn run() {
|
||||
let mut cli = Cli::parse();
|
||||
let cli = Cli::parse();
|
||||
|
||||
init_logger();
|
||||
info!("gpauth started: {}", VERSION);
|
||||
|
||||
if let Err(err) = cli.run().await {
|
||||
eprintln!("\nError: {}", err);
|
||||
|
||||
if err.to_string().contains("unsafe legacy renegotiation") && !cli.fix_openssl {
|
||||
eprintln!("\nRe-run it with the `--fix-openssl` option to work around this issue, e.g.:\n");
|
||||
// Print the command
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
eprintln!("{} --fix-openssl {}\n", args[0], args[1..].join(" "));
|
||||
}
|
||||
|
||||
handle_error(err, &cli);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_auth_data() -> anyhow::Result<SamlAuthData> {
|
||||
// Start a local server to receive the browser authentication data
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let port = listener.local_addr()?.port();
|
||||
let port_file = temp_dir().join("gpcallback.port");
|
||||
pub fn print_auth_result(auth_result: anyhow::Result<SamlAuthData>) {
|
||||
let auth_result = match auth_result {
|
||||
Ok(auth_data) => SamlAuthResult::Success(auth_data),
|
||||
Err(err) => SamlAuthResult::Failure(format!("{}", err)),
|
||||
};
|
||||
|
||||
// Write the port to a file
|
||||
fs::write(&port_file, port.to_string())?;
|
||||
fs::set_permissions(&port_file, fs::Permissions::from_mode(0o600))?;
|
||||
|
||||
// Remove the previous log file
|
||||
let callback_log = temp_dir().join("gpcallback.log");
|
||||
let _ = fs::remove_file(&callback_log);
|
||||
|
||||
info!("Listening authentication data on port {}", port);
|
||||
info!(
|
||||
"If it hangs, please check the logs at `{}` for more information",
|
||||
callback_log.display()
|
||||
);
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
|
||||
info!("Received the browser authentication data from the socket");
|
||||
let mut data = String::new();
|
||||
socket.read_to_string(&mut data).await?;
|
||||
|
||||
// Remove the port file
|
||||
fs::remove_file(&port_file)?;
|
||||
|
||||
let auth_data = SamlAuthData::from_gpcallback(&data)?;
|
||||
Ok(auth_data)
|
||||
println!("{}", json!(auth_result));
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
mod auth_window;
|
||||
mod cli;
|
||||
#[cfg(feature = "webview-auth")]
|
||||
mod webview_auth;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use auth::{Authenticator, WebviewAuthenticator};
|
||||
use log::info;
|
||||
use tauri::RunEvent;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
use crate::cli::{print_auth_result, Cli};
|
||||
|
||||
pub fn authenticate(
|
||||
cli: &Cli,
|
||||
authenticator: Authenticator<'static>,
|
||||
mut openssl_conf: Option<NamedTempFile>,
|
||||
) -> anyhow::Result<()> {
|
||||
let authenticator = authenticator.with_clean(cli.clean);
|
||||
|
||||
tauri::Builder::default()
|
||||
.setup(move |app| {
|
||||
let app_handle = app.handle().clone();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let auth_result = authenticator.webview_authenticate(&app_handle).await;
|
||||
print_auth_result(auth_result);
|
||||
|
||||
// Ensure the app exits after the authentication process
|
||||
app_handle.exit(0);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.build(tauri::generate_context!())?
|
||||
.run(move |_app_handle, event| {
|
||||
if let RunEvent::Exit = event {
|
||||
if let Some(file) = openssl_conf.take() {
|
||||
if let Err(err) = file.close() {
|
||||
info!("Error closing OpenSSL config file: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,47 +1,16 @@
|
||||
{
|
||||
"$schema": "https://cdn.jsdelivr.net/gh/tauri-apps/tauri@tauri-v1.5.0/tooling/cli/schema.json",
|
||||
"$schema": "https://cdn.jsdelivr.net/gh/tauri-apps/tauri@tauri-v2.1.1/crates/tauri-cli/config.schema.json",
|
||||
"build": {
|
||||
"distDir": [
|
||||
"index.html"
|
||||
],
|
||||
"devPath": [
|
||||
"index.html"
|
||||
],
|
||||
"frontendDist": ["index.html"],
|
||||
"beforeDevCommand": "",
|
||||
"beforeBuildCommand": "",
|
||||
"withGlobalTauri": false
|
||||
"beforeBuildCommand": ""
|
||||
},
|
||||
"package": {
|
||||
"productName": "gpauth",
|
||||
"version": "0.0.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": false,
|
||||
"http": {
|
||||
"all": true,
|
||||
"request": true,
|
||||
"scope": [
|
||||
"http://*",
|
||||
"https://*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "deb",
|
||||
"identifier": "com.yuezk.gpauth",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/[email protected]",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
},
|
||||
"identifier": "com.yuezk.gpauth",
|
||||
"productName": "gpauth",
|
||||
"app": {
|
||||
"withGlobalTauri": false,
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"windows": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
[package]
|
||||
name = "gpclient"
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
@@ -9,10 +10,11 @@ license.workspace = true
|
||||
common = { path = "../../crates/common" }
|
||||
gpapi = { path = "../../crates/gpapi", features = ["clap"] }
|
||||
openconnect = { path = "../../crates/openconnect" }
|
||||
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
env_logger.workspace = true
|
||||
inquire = "0.6.2"
|
||||
inquire = "0.7"
|
||||
log.workspace = true
|
||||
tokio.workspace = true
|
||||
sysinfo.workspace = true
|
||||
@@ -22,3 +24,7 @@ tempfile.workspace = true
|
||||
reqwest.workspace = true
|
||||
directories = "5.0"
|
||||
compile-time.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["webview-auth"]
|
||||
webview-auth = ["gpapi/webview-auth"]
|
||||
+19
-20
@@ -1,7 +1,10 @@
|
||||
use std::{env::temp_dir, fs::File};
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use gpapi::utils::openssl;
|
||||
use gpapi::{
|
||||
clap::{handle_error, Args},
|
||||
utils::openssl,
|
||||
};
|
||||
use log::{info, LevelFilter};
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
@@ -50,12 +53,25 @@ struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: CliCommand,
|
||||
|
||||
#[arg(long, help = "Uses extended compatibility mode for OpenSSL operations to support a broader range of systems and formats.")]
|
||||
#[arg(
|
||||
long,
|
||||
help = "Uses extended compatibility mode for OpenSSL operations to support a broader range of systems and formats."
|
||||
)]
|
||||
fix_openssl: bool,
|
||||
#[arg(long, help = "Ignore the TLS errors")]
|
||||
ignore_tls_errors: bool,
|
||||
}
|
||||
|
||||
impl Args for Cli {
|
||||
fn fix_openssl(&self) -> bool {
|
||||
self.fix_openssl
|
||||
}
|
||||
|
||||
fn ignore_tls_errors(&self) -> bool {
|
||||
self.ignore_tls_errors
|
||||
}
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
fn fix_openssl(&self) -> anyhow::Result<Option<NamedTempFile>> {
|
||||
if self.fix_openssl {
|
||||
@@ -113,24 +129,7 @@ pub(crate) async fn run() {
|
||||
info!("gpclient started: {}", VERSION);
|
||||
|
||||
if let Err(err) = cli.run().await {
|
||||
eprintln!("\nError: {}", err);
|
||||
|
||||
let err = err.to_string();
|
||||
|
||||
if err.contains("unsafe legacy renegotiation") && !cli.fix_openssl {
|
||||
eprintln!("\nRe-run it with the `--fix-openssl` option to work around this issue, e.g.:\n");
|
||||
// Print the command
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
eprintln!("{} --fix-openssl {}\n", args[0], args[1..].join(" "));
|
||||
}
|
||||
|
||||
if err.contains("certificate verify failed") && !cli.ignore_tls_errors {
|
||||
eprintln!("\nRe-run it with the `--ignore-tls-errors` option to ignore the certificate error, e.g.:\n");
|
||||
// Print the command
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
eprintln!("{} --ignore-tls-errors {}\n", args[0], args[1..].join(" "));
|
||||
}
|
||||
|
||||
handle_error(err, &cli);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -93,12 +93,15 @@ pub(crate) struct ConnectArgs {
|
||||
#[arg(long, help = "Disable DTLS and ESP")]
|
||||
no_dtls: bool,
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
#[arg(long, help = "The HiDPI mode, useful for high-resolution screens")]
|
||||
hidpi: bool,
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
#[arg(long, help = "Do not reuse the remembered authentication cookie")]
|
||||
clean: bool,
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
#[arg(long, help = "Use the default browser to authenticate")]
|
||||
default_browser: bool,
|
||||
|
||||
@@ -151,6 +154,7 @@ impl<'a> ConnectHandler<'a> {
|
||||
}
|
||||
|
||||
pub(crate) async fn handle(&self) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "webview-auth")]
|
||||
if self.args.default_browser && self.args.browser.is_some() {
|
||||
bail!("Cannot use `--default-browser` and `--browser` options at the same time");
|
||||
}
|
||||
@@ -343,28 +347,34 @@ impl<'a> ConnectHandler<'a> {
|
||||
|
||||
match prelogin {
|
||||
Prelogin::Saml(prelogin) => {
|
||||
let use_default_browser = prelogin.support_default_browser() && self.args.default_browser;
|
||||
let browser = if prelogin.support_default_browser() {
|
||||
self.args.browser.as_deref()
|
||||
} else if !cfg!(feature = "webview-auth") {
|
||||
bail!("The server does not support authentication via the default browser and the gpclient is not built with the `webview-auth` feature");
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let cred = SamlAuthLauncher::new(&self.args.server)
|
||||
let os_version = self.args.os_version();
|
||||
let auth_launcher = SamlAuthLauncher::new(&self.args.server)
|
||||
.gateway(is_gateway)
|
||||
.saml_request(prelogin.saml_request())
|
||||
.user_agent(&self.args.user_agent)
|
||||
.os(self.args.os.as_str())
|
||||
.os_version(Some(&self.args.os_version()))
|
||||
.hidpi(self.args.hidpi)
|
||||
.os_version(Some(&os_version))
|
||||
.fix_openssl(self.shared_args.fix_openssl)
|
||||
.ignore_tls_errors(self.shared_args.ignore_tls_errors)
|
||||
.clean(self.args.clean)
|
||||
.default_browser(use_default_browser)
|
||||
.browser(browser)
|
||||
.launch()
|
||||
.await?;
|
||||
.browser(browser);
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
let use_default_browser = prelogin.support_default_browser() && self.args.default_browser;
|
||||
#[cfg(feature = "webview-auth")]
|
||||
let auth_launcher = auth_launcher
|
||||
.hidpi(self.args.hidpi)
|
||||
.clean(self.args.clean)
|
||||
.default_browser(use_default_browser);
|
||||
|
||||
let cred = auth_launcher.launch().await?;
|
||||
Ok(cred)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::GP_CLIENT_LOCK_FILE;
|
||||
use log::{info, warn};
|
||||
use std::fs;
|
||||
use sysinfo::{Pid, ProcessExt, Signal, System, SystemExt};
|
||||
use sysinfo::{Pid, Signal, System};
|
||||
|
||||
pub(crate) struct DisconnectHandler;
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ use clap::Args;
|
||||
use directories::ProjectDirs;
|
||||
use gpapi::{
|
||||
process::service_launcher::ServiceLauncher,
|
||||
utils::{endpoint::http_endpoint, env_file, shutdown_signal},
|
||||
utils::{endpoint::http_endpoint, env_utils, shutdown_signal},
|
||||
GP_CALLBACK_PORT_FILENAME,
|
||||
};
|
||||
use log::info;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
@@ -62,7 +63,7 @@ impl<'a> LaunchGuiHandler<'a> {
|
||||
extra_envs.insert("GP_LOG_FILE".into(), log_file_path.clone());
|
||||
|
||||
// Persist the environment variables to a file
|
||||
let env_file = env_file::persist_env_vars(Some(extra_envs))?;
|
||||
let env_file = env_utils::persist_env_vars(Some(extra_envs))?;
|
||||
let env_file = env_file.into_temp_path();
|
||||
let env_file_path = env_file.to_string_lossy().to_string();
|
||||
|
||||
@@ -80,42 +81,17 @@ impl<'a> LaunchGuiHandler<'a> {
|
||||
}
|
||||
|
||||
async fn feed_auth_data(auth_data: &str) -> anyhow::Result<()> {
|
||||
let (res_gui, res_cli) = tokio::join!(feed_auth_data_gui(auth_data), feed_auth_data_cli(auth_data));
|
||||
if let Err(err) = res_gui {
|
||||
info!("Failed to feed auth data to the GUI: {}", err);
|
||||
}
|
||||
|
||||
if let Err(err) = res_cli {
|
||||
if let Err(err) = feed_auth_data_cli(auth_data).await {
|
||||
info!("Failed to feed auth data to the CLI: {}", err);
|
||||
}
|
||||
|
||||
// Cleanup the temporary file
|
||||
let html_file = temp_dir().join("gpauth.html");
|
||||
if let Err(err) = std::fs::remove_file(&html_file) {
|
||||
info!("Failed to remove {}: {}", html_file.display(), err);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn feed_auth_data_gui(auth_data: &str) -> anyhow::Result<()> {
|
||||
info!("Feeding auth data to the GUI");
|
||||
let service_endpoint = http_endpoint().await?;
|
||||
|
||||
reqwest::Client::default()
|
||||
.post(format!("{}/auth-data", service_endpoint))
|
||||
.body(auth_data.to_string())
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn feed_auth_data_cli(auth_data: &str) -> anyhow::Result<()> {
|
||||
info!("Feeding auth data to the CLI");
|
||||
|
||||
let port_file = temp_dir().join("gpcallback.port");
|
||||
let port_file = temp_dir().join(GP_CALLBACK_PORT_FILENAME);
|
||||
let port = tokio::fs::read_to_string(port_file).await?;
|
||||
let mut stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{}", port.trim())).await?;
|
||||
|
||||
|
||||
Vendored
File renamed without changes.
Vendored
File renamed without changes.
+193
@@ -0,0 +1,193 @@
|
||||
var zg=Object.defineProperty;var wg=(l,i,c)=>i in l?zg(l,i,{enumerable:!0,configurable:!0,writable:!0,value:c}):l[i]=c;var Ni=(l,i,c)=>wg(l,typeof i!="symbol"?i+"":i,c);function Rg(l,i){for(var c=0;c<i.length;c++){const o=i[c];if(typeof o!="string"&&!Array.isArray(o)){for(const s in o)if(s!=="default"&&!(s in l)){const d=Object.getOwnPropertyDescriptor(o,s);d&&Object.defineProperty(l,s,d.get?d:{enumerable:!0,get:()=>o[s]})}}}return Object.freeze(Object.defineProperty(l,Symbol.toStringTag,{value:"Module"}))}(function(){const i=document.createElement("link").relList;if(i&&i.supports&&i.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))o(s);new MutationObserver(s=>{for(const d of s)if(d.type==="childList")for(const m of d.addedNodes)m.tagName==="LINK"&&m.rel==="modulepreload"&&o(m)}).observe(document,{childList:!0,subtree:!0});function c(s){const d={};return s.integrity&&(d.integrity=s.integrity),s.referrerPolicy&&(d.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?d.credentials="include":s.crossOrigin==="anonymous"?d.credentials="omit":d.credentials="same-origin",d}function o(s){if(s.ep)return;s.ep=!0;const d=c(s);fetch(s.href,d)}})();function Mg(l){return l&&l.__esModule&&Object.prototype.hasOwnProperty.call(l,"default")?l.default:l}var Jo={exports:{}},Ui={};/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var wh;function Dg(){if(wh)return Ui;wh=1;var l=Symbol.for("react.transitional.element"),i=Symbol.for("react.fragment");function c(o,s,d){var m=null;if(d!==void 0&&(m=""+d),s.key!==void 0&&(m=""+s.key),"key"in s){d={};for(var b in s)b!=="key"&&(d[b]=s[b])}else d=s;return s=d.ref,{$$typeof:l,type:o,key:m,ref:s!==void 0?s:null,props:d}}return Ui.Fragment=i,Ui.jsx=c,Ui.jsxs=c,Ui}var Rh;function Bg(){return Rh||(Rh=1,Jo.exports=Dg()),Jo.exports}var rt=Bg(),Fo={exports:{}},Hi={},Po={exports:{}},Io={};/**
|
||||
* @license React
|
||||
* scheduler.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var Mh;function Ng(){return Mh||(Mh=1,function(l){function i(G,it){var tt=G.length;G.push(it);t:for(;0<tt;){var ot=tt-1>>>1,S=G[ot];if(0<s(S,it))G[ot]=it,G[tt]=S,tt=ot;else break t}}function c(G){return G.length===0?null:G[0]}function o(G){if(G.length===0)return null;var it=G[0],tt=G.pop();if(tt!==it){G[0]=tt;t:for(var ot=0,S=G.length,q=S>>>1;ot<q;){var at=2*(ot+1)-1,lt=G[at],J=at+1,bt=G[J];if(0>s(lt,tt))J<S&&0>s(bt,lt)?(G[ot]=bt,G[J]=tt,ot=J):(G[ot]=lt,G[at]=tt,ot=at);else if(J<S&&0>s(bt,tt))G[ot]=bt,G[J]=tt,ot=J;else break t}}return it}function s(G,it){var tt=G.sortIndex-it.sortIndex;return tt!==0?tt:G.id-it.id}if(l.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var d=performance;l.unstable_now=function(){return d.now()}}else{var m=Date,b=m.now();l.unstable_now=function(){return m.now()-b}}var v=[],p=[],C=1,_=null,w=3,j=!1,B=!1,U=!1,X=typeof setTimeout=="function"?setTimeout:null,Q=typeof clearTimeout=="function"?clearTimeout:null,F=typeof setImmediate<"u"?setImmediate:null;function V(G){for(var it=c(p);it!==null;){if(it.callback===null)o(p);else if(it.startTime<=G)o(p),it.sortIndex=it.expirationTime,i(v,it);else break;it=c(p)}}function L(G){if(U=!1,V(G),!B)if(c(v)!==null)B=!0,St();else{var it=c(p);it!==null&&_t(L,it.startTime-G)}}var O=!1,$=-1,W=5,gt=-1;function K(){return!(l.unstable_now()-gt<W)}function y(){if(O){var G=l.unstable_now();gt=G;var it=!0;try{t:{B=!1,U&&(U=!1,Q($),$=-1),j=!0;var tt=w;try{e:{for(V(G),_=c(v);_!==null&&!(_.expirationTime>G&&K());){var ot=_.callback;if(typeof ot=="function"){_.callback=null,w=_.priorityLevel;var S=ot(_.expirationTime<=G);if(G=l.unstable_now(),typeof S=="function"){_.callback=S,V(G),it=!0;break e}_===c(v)&&o(v),V(G)}else o(v);_=c(v)}if(_!==null)it=!0;else{var q=c(p);q!==null&&_t(L,q.startTime-G),it=!1}}break t}finally{_=null,w=tt,j=!1}it=void 0}}finally{it?Y():O=!1}}}var Y;if(typeof F=="function")Y=function(){F(y)};else if(typeof MessageChannel<"u"){var et=new MessageChannel,ct=et.port2;et.port1.onmessage=y,Y=function(){ct.postMessage(null)}}else Y=function(){X(y,0)};function St(){O||(O=!0,Y())}function _t(G,it){$=X(function(){G(l.unstable_now())},it)}l.unstable_IdlePriority=5,l.unstable_ImmediatePriority=1,l.unstable_LowPriority=4,l.unstable_NormalPriority=3,l.unstable_Profiling=null,l.unstable_UserBlockingPriority=2,l.unstable_cancelCallback=function(G){G.callback=null},l.unstable_continueExecution=function(){B||j||(B=!0,St())},l.unstable_forceFrameRate=function(G){0>G||125<G?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):W=0<G?Math.floor(1e3/G):5},l.unstable_getCurrentPriorityLevel=function(){return w},l.unstable_getFirstCallbackNode=function(){return c(v)},l.unstable_next=function(G){switch(w){case 1:case 2:case 3:var it=3;break;default:it=w}var tt=w;w=it;try{return G()}finally{w=tt}},l.unstable_pauseExecution=function(){},l.unstable_requestPaint=function(){},l.unstable_runWithPriority=function(G,it){switch(G){case 1:case 2:case 3:case 4:case 5:break;default:G=3}var tt=w;w=G;try{return it()}finally{w=tt}},l.unstable_scheduleCallback=function(G,it,tt){var ot=l.unstable_now();switch(typeof tt=="object"&&tt!==null?(tt=tt.delay,tt=typeof tt=="number"&&0<tt?ot+tt:ot):tt=ot,G){case 1:var S=-1;break;case 2:S=250;break;case 5:S=1073741823;break;case 4:S=1e4;break;default:S=5e3}return S=tt+S,G={id:C++,callback:it,priorityLevel:G,startTime:tt,expirationTime:S,sortIndex:-1},tt>ot?(G.sortIndex=tt,i(p,G),c(v)===null&&G===c(p)&&(U?(Q($),$=-1):U=!0,_t(L,tt-ot))):(G.sortIndex=S,i(v,G),B||j||(B=!0,St())),G},l.unstable_shouldYield=K,l.unstable_wrapCallback=function(G){var it=w;return function(){var tt=w;w=it;try{return G.apply(this,arguments)}finally{w=tt}}}}(Io)),Io}var Dh;function Ug(){return Dh||(Dh=1,Po.exports=Ng()),Po.exports}var ts={exports:{}},ht={};/**
|
||||
* @license React
|
||||
* react.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var Bh;function Hg(){if(Bh)return ht;Bh=1;var l=Symbol.for("react.transitional.element"),i=Symbol.for("react.portal"),c=Symbol.for("react.fragment"),o=Symbol.for("react.strict_mode"),s=Symbol.for("react.profiler"),d=Symbol.for("react.consumer"),m=Symbol.for("react.context"),b=Symbol.for("react.forward_ref"),v=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),C=Symbol.for("react.lazy"),_=Symbol.iterator;function w(S){return S===null||typeof S!="object"?null:(S=_&&S[_]||S["@@iterator"],typeof S=="function"?S:null)}var j={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},B=Object.assign,U={};function X(S,q,at){this.props=S,this.context=q,this.refs=U,this.updater=at||j}X.prototype.isReactComponent={},X.prototype.setState=function(S,q){if(typeof S!="object"&&typeof S!="function"&&S!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,S,q,"setState")},X.prototype.forceUpdate=function(S){this.updater.enqueueForceUpdate(this,S,"forceUpdate")};function Q(){}Q.prototype=X.prototype;function F(S,q,at){this.props=S,this.context=q,this.refs=U,this.updater=at||j}var V=F.prototype=new Q;V.constructor=F,B(V,X.prototype),V.isPureReactComponent=!0;var L=Array.isArray,O={H:null,A:null,T:null,S:null},$=Object.prototype.hasOwnProperty;function W(S,q,at,lt,J,bt){return at=bt.ref,{$$typeof:l,type:S,key:q,ref:at!==void 0?at:null,props:bt}}function gt(S,q){return W(S.type,q,void 0,void 0,void 0,S.props)}function K(S){return typeof S=="object"&&S!==null&&S.$$typeof===l}function y(S){var q={"=":"=0",":":"=2"};return"$"+S.replace(/[=:]/g,function(at){return q[at]})}var Y=/\/+/g;function et(S,q){return typeof S=="object"&&S!==null&&S.key!=null?y(""+S.key):q.toString(36)}function ct(){}function St(S){switch(S.status){case"fulfilled":return S.value;case"rejected":throw S.reason;default:switch(typeof S.status=="string"?S.then(ct,ct):(S.status="pending",S.then(function(q){S.status==="pending"&&(S.status="fulfilled",S.value=q)},function(q){S.status==="pending"&&(S.status="rejected",S.reason=q)})),S.status){case"fulfilled":return S.value;case"rejected":throw S.reason}}throw S}function _t(S,q,at,lt,J){var bt=typeof S;(bt==="undefined"||bt==="boolean")&&(S=null);var ft=!1;if(S===null)ft=!0;else switch(bt){case"bigint":case"string":case"number":ft=!0;break;case"object":switch(S.$$typeof){case l:case i:ft=!0;break;case C:return ft=S._init,_t(ft(S._payload),q,at,lt,J)}}if(ft)return J=J(S),ft=lt===""?"."+et(S,0):lt,L(J)?(at="",ft!=null&&(at=ft.replace(Y,"$&/")+"/"),_t(J,q,at,"",function(Yt){return Yt})):J!=null&&(K(J)&&(J=gt(J,at+(J.key==null||S&&S.key===J.key?"":(""+J.key).replace(Y,"$&/")+"/")+ft)),q.push(J)),1;ft=0;var ie=lt===""?".":lt+":";if(L(S))for(var At=0;At<S.length;At++)lt=S[At],bt=ie+et(lt,At),ft+=_t(lt,q,at,bt,J);else if(At=w(S),typeof At=="function")for(S=At.call(S),At=0;!(lt=S.next()).done;)lt=lt.value,bt=ie+et(lt,At++),ft+=_t(lt,q,at,bt,J);else if(bt==="object"){if(typeof S.then=="function")return _t(St(S),q,at,lt,J);throw q=String(S),Error("Objects are not valid as a React child (found: "+(q==="[object Object]"?"object with keys {"+Object.keys(S).join(", ")+"}":q)+"). If you meant to render a collection of children, use an array instead.")}return ft}function G(S,q,at){if(S==null)return S;var lt=[],J=0;return _t(S,lt,"","",function(bt){return q.call(at,bt,J++)}),lt}function it(S){if(S._status===-1){var q=S._result;q=q(),q.then(function(at){(S._status===0||S._status===-1)&&(S._status=1,S._result=at)},function(at){(S._status===0||S._status===-1)&&(S._status=2,S._result=at)}),S._status===-1&&(S._status=0,S._result=q)}if(S._status===1)return S._result.default;throw S._result}var tt=typeof reportError=="function"?reportError:function(S){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var q=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof S=="object"&&S!==null&&typeof S.message=="string"?String(S.message):String(S),error:S});if(!window.dispatchEvent(q))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",S);return}console.error(S)};function ot(){}return ht.Children={map:G,forEach:function(S,q,at){G(S,function(){q.apply(this,arguments)},at)},count:function(S){var q=0;return G(S,function(){q++}),q},toArray:function(S){return G(S,function(q){return q})||[]},only:function(S){if(!K(S))throw Error("React.Children.only expected to receive a single React element child.");return S}},ht.Component=X,ht.Fragment=c,ht.Profiler=s,ht.PureComponent=F,ht.StrictMode=o,ht.Suspense=v,ht.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=O,ht.act=function(){throw Error("act(...) is not supported in production builds of React.")},ht.cache=function(S){return function(){return S.apply(null,arguments)}},ht.cloneElement=function(S,q,at){if(S==null)throw Error("The argumeLine truncated
|
||||
* @license React
|
||||
* react-dom.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var Uh;function jg(){if(Uh)return de;Uh=1;var l=_s();function i(v){var p="https://react.dev/errors/"+v;if(1<arguments.length){p+="?args[]="+encodeURIComponent(arguments[1]);for(var C=2;C<arguments.length;C++)p+="&args[]="+encodeURIComponent(arguments[C])}return"Minified React error #"+v+"; visit "+p+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function c(){}var o={d:{f:c,r:function(){throw Error(i(522))},D:c,C:c,L:c,m:c,X:c,S:c,M:c},p:0,findDOMNode:null},s=Symbol.for("react.portal");function d(v,p,C){var _=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:s,key:_==null?null:""+_,children:v,containerInfo:p,implementation:C}}var m=l.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function b(v,p){if(v==="font")return"";if(typeof p=="string")return p==="use-credentials"?p:""}return de.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=o,de.createPortal=function(v,p){var C=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!p||p.nodeType!==1&&p.nodeType!==9&&p.nodeType!==11)throw Error(i(299));return d(v,p,null,C)},de.flushSync=function(v){var p=m.T,C=o.p;try{if(m.T=null,o.p=2,v)return v()}finally{m.T=p,o.p=C,o.d.f()}},de.preconnect=function(v,p){typeof v=="string"&&(p?(p=p.crossOrigin,p=typeof p=="string"?p==="use-credentials"?p:"":void 0):p=null,o.d.C(v,p))},de.prefetchDNS=function(v){typeof v=="string"&&o.d.D(v)},de.preinit=function(v,p){if(typeof v=="string"&&p&&typeof p.as=="string"){var C=p.as,_=b(C,p.crossOrigin),w=typeof p.integrity=="string"?p.integrity:void 0,j=typeof p.fetchPriority=="string"?p.fetchPriority:void 0;C==="style"?o.d.S(v,typeof p.precedence=="string"?p.precedence:void 0,{crossOrigin:_,integrity:w,fetchPriority:j}):C==="script"&&o.d.X(v,{crossOrigin:_,integrity:w,fetchPriority:j,nonce:typeof p.nonce=="string"?p.nonce:void 0})}},de.preinitModule=function(v,p){if(typeof v=="string")if(typeof p=="object"&&p!==null){if(p.as==null||p.as==="script"){var C=b(p.as,p.crossOrigin);o.d.M(v,{crossOrigin:C,integrity:typeof p.integrity=="string"?p.integrity:void 0,nonce:typeof p.nonce=="string"?p.nonce:void 0})}}else p==null&&o.d.M(v)},de.preload=function(v,p){if(typeof v=="string"&&typeof p=="object"&&p!==null&&typeof p.as=="string"){var C=p.as,_=b(C,p.crossOrigin);o.d.L(v,C,{crossOrigin:_,integrity:typeof p.integrity=="string"?p.integrity:void 0,nonce:typeof p.nonce=="string"?p.nonce:void 0,type:typeof p.type=="string"?p.type:void 0,fetchPriority:typeof p.fetchPriority=="string"?p.fetchPriority:void 0,referrerPolicy:typeof p.referrerPolicy=="string"?p.referrerPolicy:void 0,imageSrcSet:typeof p.imageSrcSet=="string"?p.imageSrcSet:void 0,imageSizes:typeof p.imageSizes=="string"?p.imageSizes:void 0,media:typeof p.media=="string"?p.media:void 0})}},de.preloadModule=function(v,p){if(typeof v=="string")if(p){var C=b(p.as,p.crossOrigin);o.d.m(v,{as:typeof p.as=="string"&&p.as!=="script"?p.as:void 0,crossOrigin:C,integrity:typeof p.integrity=="string"?p.integrity:void 0})}else o.d.m(v)},de.requestFormReset=function(v){o.d.r(v)},de.unstable_batchedUpdates=function(v,p){return v(p)},de.useFormState=function(v,p,C){return m.H.useFormState(v,p,C)},de.useFormStatus=function(){return m.H.useHostTransitionStatus()},de.version="19.0.0",de}var Hh;function qg(){if(Hh)return es.exports;Hh=1;function l(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(l)}catch(i){console.error(i)}}return l(),es.exports=jg(),es.exports}/**
|
||||
* @license React
|
||||
* react-dom-client.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var jh;function Lg(){if(jh)return Hi;jh=1;var l=Ug(),i=_s(),c=qg();function o(t){var e="https://react.dev/errors/"+t;if(1<arguments.length){e+="?args[]="+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)e+="&args[]="+encodeURIComponent(arguments[n])}return"Minified React error #"+t+"; visit "+e+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function s(t){return!(!t||t.nodeType!==1&&t.nodeType!==9&&t.nodeType!==11)}var d=Symbol.for("react.element"),m=Symbol.for("react.transitional.element"),b=Symbol.for("react.portal"),v=Symbol.for("react.fragment"),p=Symbol.for("react.strict_mode"),C=Symbol.for("react.profiler"),_=Symbol.for("react.provider"),w=Symbol.for("react.consumer"),j=Symbol.for("react.context"),B=Symbol.for("react.forward_ref"),U=Symbol.for("react.suspense"),X=Symbol.for("react.suspense_list"),Q=Symbol.for("react.memo"),F=Symbol.for("react.lazy"),V=Symbol.for("react.offscreen"),L=Symbol.for("react.memo_cache_sentinel"),O=Symbol.iterator;function $(t){return t===null||typeof t!="object"?null:(t=O&&t[O]||t["@@iterator"],typeof t=="function"?t:null)}var W=Symbol.for("react.client.reference");function gt(t){if(t==null)return null;if(typeof t=="function")return t.$$typeof===W?null:t.displayName||t.name||null;if(typeof t=="string")return t;switch(t){case v:return"Fragment";case b:return"Portal";case C:return"Profiler";case p:return"StrictMode";case U:return"Suspense";case X:return"SuspenseList"}if(typeof t=="object")switch(t.$$typeof){case j:return(t.displayName||"Context")+".Provider";case w:return(t._context.displayName||"Context")+".Consumer";case B:var e=t.render;return t=t.displayName,t||(t=e.displayName||e.name||"",t=t!==""?"ForwardRef("+t+")":"ForwardRef"),t;case Q:return e=t.displayName||null,e!==null?e:gt(t.type)||"Memo";case F:e=t._payload,t=t._init;try{return gt(t(e))}catch{}}return null}var K=i.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,y=Object.assign,Y,et;function ct(t){if(Y===void 0)try{throw Error()}catch(n){var e=n.stack.trim().match(/\n( *(at )?)/);Y=e&&e[1]||"",et=-1<n.stack.indexOf(`
|
||||
at`)?" (<anonymous>)":-1<n.stack.indexOf("@")?"@unknown:0:0":""}return`
|
||||
`+Y+t+et}var St=!1;function _t(t,e){if(!t||St)return"";St=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var a={DetermineComponentFrameRoot:function(){try{if(e){var H=function(){throw Error()};if(Object.defineProperty(H.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(H,[])}catch(M){var z=M}Reflect.construct(t,[],H)}else{try{H.call()}catch(M){z=M}t.call(H.prototype)}}else{try{throw Error()}catch(M){z=M}(H=t())&&typeof H.catch=="function"&&H.catch(function(){})}}catch(M){if(M&&z&&typeof M.stack=="string")return[M.stack,z.stack]}return[null,null]}};a.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var u=Object.getOwnPropertyDescriptor(a.DetermineComponentFrameRoot,"name");u&&u.configurable&&Object.defineProperty(a.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var r=a.DetermineComponentFrameRoot(),f=r[0],h=r[1];if(f&&h){var g=f.split(`
|
||||
`),A=h.split(`
|
||||
`);for(u=a=0;a<g.length&&!g[a].includes("DetermineComponentFrameRoot");)a++;for(;u<A.length&&!A[u].includes("DetermineComponentFrameRoot");)u++;if(a===g.length||u===A.length)for(a=g.length-1,u=A.length-1;1<=a&&0<=u&&g[a]!==A[u];)u--;for(;1<=a&&0<=u;a--,u--)if(g[a]!==A[u]){if(a!==1||u!==1)do if(a--,u--,0>u||g[a]!==A[u]){var D=`
|
||||
`+g[a].replace(" at new "," at ");return t.displayName&&D.includes("<anonymous>")&&(D=D.replace("<anonymous>",t.displayName)),D}while(1<=a&&0<=u);break}}}finally{St=!1,Error.prepareStackTrace=n}return(n=t?t.displayName||t.name:"")?ct(n):""}function G(t){switch(t.tag){case 26:case 27:case 5:return ct(t.type);case 16:return ct("Lazy");case 13:return ct("Suspense");case 19:return ct("SuspenseList");case 0:case 15:return t=_t(t.type,!1),t;case 11:return t=_t(t.type.render,!1),t;case 1:return t=_t(t.type,!0),t;default:return""}}function it(t){try{var e="";do e+=G(t),t=t.return;while(t);return e}catch(n){return`
|
||||
Error generating stack: `+n.message+`
|
||||
`+n.stack}}function tt(t){var e=t,n=t;if(t.alternate)for(;e.return;)e=e.return;else{t=e;do e=t,e.flags&4098&&(n=e.return),t=e.return;while(t)}return e.tag===3?n:null}function ot(t){if(t.tag===13){var e=t.memoizedState;if(e===null&&(t=t.alternate,t!==null&&(e=t.memoizedState)),e!==null)return e.dehydrated}return null}function S(t){if(tt(t)!==t)throw Error(o(188))}function q(t){var e=t.alternate;if(!e){if(e=tt(t),e===null)throw Error(o(188));return e!==t?null:t}for(var n=t,a=e;;){var u=n.return;if(u===null)break;var r=u.alternate;if(r===null){if(a=u.return,a!==null){n=a;continue}break}if(u.child===r.child){for(r=u.child;r;){if(r===n)return S(u),t;if(r===a)return S(u),e;r=r.sibling}throw Error(o(188))}if(n.return!==a.return)n=u,a=r;else{for(var f=!1,h=u.child;h;){if(h===n){f=!0,n=u,a=r;break}if(h===a){f=!0,a=u,n=r;break}h=h.sibling}if(!f){for(h=r.child;h;){if(h===n){f=!0,n=r,a=u;break}if(h===a){f=!0,a=r,n=u;break}h=h.sibling}if(!f)throw Error(o(189))}}if(n.alternate!==a)throw Error(o(190))}if(n.tag!==3)throw Error(o(188));return n.stateNode.current===n?t:e}function at(t){var e=t.tag;if(e===5||e===26||e===27||e===6)return t;for(t=t.child;t!==null;){if(e=at(t),e!==null)return e;t=t.sibling}return null}var lt=Array.isArray,J=c.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,bt={pending:!1,data:null,method:null,action:null},ft=[],ie=-1;function At(t){return{current:t}}function Yt(t){0>ie||(t.current=ft[ie],ft[ie]=null,ie--)}function Nt(t,e){ie++,ft[ie]=t.current,t.current=e}var De=At(null),oa=At(null),tn=At(null),Un=At(null);function Ha(t,e){switch(Nt(tn,e),Nt(oa,t),Nt(De,null),t=e.nodeType,t){case 9:case 11:e=(e=e.documentElement)&&(e=e.namespaceURI)?lh(e):0;break;default:if(t=t===8?e.parentNode:e,e=t.tagName,t=t.namespaceURI)t=lh(t),e=ih(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}Yt(De),Nt(De,e)}function Hn(){Yt(De),Yt(oa),Yt(tn)}function jn(t){t.memoizedState!==null&&Nt(Un,t);var e=De.current,n=ih(e,t.type);e!==n&&(Nt(oa,t),Nt(De,n))}function en(t){oa.current===t&&(Yt(De),Yt(oa)),Un.current===t&&(Yt(Un),wi._currentValue=bt)}var Hl=Object.prototype.hasOwnProperty,ja=l.unstable_scheduleCallback,jl=l.unstable_cancelCallback,dt=l.unstable_shouldYield,ry=l.unstable_requestPaint,nn=l.unstable_now,cy=l.unstable_getCurrentPriorityLevel,Qs=l.unstable_ImmediatePriority,Zs=l.unstable_UserBlockingPriority,tu=l.unstable_NormalPriority,oy=l.unstable_LowPriority,Ks=l.unstable_IdlePriority,sy=l.log,fy=l.unstable_setDisableYieldValue,ql=null,Te=null;function dy(t){if(Te&&typeof Te.onCommitFiberRoot=="function")try{Te.onCommitFiberRoot(ql,t,void 0,(t.current.flags&128)===128)}catch{}}function qn(t){if(typeof sy=="function"&&fy(t),Te&&typeof Te.setStrictMode=="function")try{Te.setStrictMode(ql,t)}catch{}}var Ae=Math.clz32?Math.clz32:yy,hy=Math.log,my=Math.LN2;function yy(t){return t>>>=0,t===0?32:31-(hy(t)/my|0)|0}var eu=128,nu=4194304;function sa(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t&4194176;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function au(t,e){var n=t.pendingLanes;if(n===0)return 0;var a=0,u=t.suspendedLanes,r=t.pingedLanes,f=t.warmLanes;t=t.finishedLanes!==0;var h=n&134217727;return h!==0?(n=h&~u,n!==0?a=sa(n):(r&=h,r!==0?a=sa(r):t||(f=h&~f,f!==0&&(a=sa(f))))):(h=n&~u,h!==0?a=sa(h):r!==0?a=sa(r):t||(f=n&~f,f!==0&&(a=sa(f)))),a===0?0:e!==0&&e!==a&&!(e&u)&&(u=a&-a,f=e&-e,u>=f||u===32&&(f&4194176)!==0)?e:a}function Ll(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function py(t,e){switch(t){case 1:case 2:case 4:case 8:return e+250;case 16:case 32:case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Ws(){var t=eu;return eu<<=1,!(eu&4194176)&&(eu=128),t}function Js(){var t=nu;return nu<<=1,!(nu&62914560)&&(nu=4194304),t}function $r(t){for(var e=[],n=0;31>n;n++)e.push(t);return e}function Gl(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function gy(t,e,n,a,u,r){var f=t.pendingLanes;t.pendingLanes=n,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=n,t.entangledLanes&=n,t.errorRecoveryDisabledLanes&=n,t.shellSuspendCounter=0;var h=t.entanglements,g=t.expirationTimes,A=t.hiddenUpdates;for(n=f&~n;0<n;){var D=31-Ae(n),H=1<<D;
Line truncated
|
||||
`).replace(Fp,"")}function ah(t,e){return e=nh(e),nh(t)===e}function Fu(){}function Rt(t,e,n,a,u,r){switch(n){case"children":typeof a=="string"?e==="body"||e==="textarea"&&a===""||Xa(t,a):(typeof a=="number"||typeof a=="bigint")&&e!=="body"&&Xa(t,""+a);break;case"className":iu(t,"class",a);break;case"tabIndex":iu(t,"tabindex",a);break;case"dir":case"role":case"viewBox":case"width":case"height":iu(t,n,a);break;case"style":hf(t,a,r);break;case"data":if(e!=="object"){iu(t,"data",a);break}case"src":case"href":if(a===""&&(e!=="a"||n!=="href")){t.removeAttribute(n);break}if(a==null||typeof a=="function"||typeof a=="symbol"||typeof a=="boolean"){t.removeAttribute(n);break}a=cu(""+a),t.setAttribute(n,a);break;case"action":case"formAction":if(typeof a=="function"){t.setAttribute(n,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof r=="function"&&(n==="formAction"?(e!=="input"&&Rt(t,e,"name",u.name,u,null),Rt(t,e,"formEncType",u.formEncType,u,null),Rt(t,e,"formMethod",u.formMethod,u,null),Rt(t,e,"formTarget",u.formTarget,u,null)):(Rt(t,e,"encType",u.encType,u,null),Rt(t,e,"method",u.method,u,null),Rt(t,e,"target",u.target,u,null)));if(a==null||typeof a=="symbol"||typeof a=="boolean"){t.removeAttribute(n);break}a=cu(""+a),t.setAttribute(n,a);break;case"onClick":a!=null&&(t.onclick=Fu);break;case"onScroll":a!=null&&vt("scroll",t);break;case"onScrollEnd":a!=null&&vt("scrollend",t);break;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(o(61));if(n=a.__html,n!=null){if(u.children!=null)throw Error(o(60));t.innerHTML=n}}break;case"multiple":t.multiple=a&&typeof a!="function"&&typeof a!="symbol";break;case"muted":t.muted=a&&typeof a!="function"&&typeof a!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(a==null||typeof a=="function"||typeof a=="boolean"||typeof a=="symbol"){t.removeAttribute("xlink:href");break}n=cu(""+a),t.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",n);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":a!=null&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(n,""+a):t.removeAttribute(n);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":a&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(n,""):t.removeAttribute(n);break;case"capture":case"download":a===!0?t.setAttribute(n,""):a!==!1&&a!=null&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(n,a):t.removeAttribute(n);break;case"cols":case"rows":case"size":case"span":a!=null&&typeof a!="function"&&typeof a!="symbol"&&!isNaN(a)&&1<=a?t.setAttribute(n,a):t.removeAttribute(n);break;case"rowSpan":case"start":a==null||typeof a=="function"||typeof a=="symbol"||isNaN(a)?t.removeAttribute(n):t.setAttribute(n,a);break;case"popover":vt("beforetoggle",t),vt("toggle",t),lu(t,"popover",a);break;case"xlinkActuate":yn(t,"http://www.w3.org/1999/xlink","xlink:actuate",a);break;case"xlinkArcrole":yn(t,"http://www.w3.org/1999/xlink","xlink:arcrole",a);break;case"xlinkRole":yn(t,"http://www.w3.org/1999/xlink","xlink:role",a);break;case"xlinkShow":yn(t,"http://www.w3.org/1999/xlink","xlink:show",a);break;case"xlinkTitle":yn(t,"http://www.w3.org/1999/xlink","xlink:title",a);break;case"xlinkType":yn(t,"http://www.w3.org/1999/xlink","xlink:type",a);break;case"xmlBase":yn(t,"http://www.w3.org/XML/1998/namespace","xml:base",a);break;case"xmlLang":yn(t,"http://www.w3.org/XML/1998/namespace","xml:lang",a);break;case"xmlSpace":yn(t,"http://www.w3.org/XML/1998/namespace","xml:space",a);break;case"is":lu(t,"is",a);break;case"innerText":case"textContent":break;default:(!(2<n.length)||n[0]!=="o"&&n[0]!=="O"||n[1]!=="n"&&n[1]!=="N")&&(n=Oy.get(n)||n,lu(t,n,a))}}function No(t,e,n,a,u,r){switch(n){case"style":hf(t,a,r);break;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(o(61));if(n=a.__html,n!=null){if(u.children!=null)throw Error(o(60));t.innerHTML=n}}break;case"children":typeof a=="string"?Xa(t,a):(typeof a=="number"||typeof a=="bigint")&&Xa(t,""+a);break;case"onScroll":a!=null&&vt("scroll",t);break;case"onScrollEnd":a!=null&&vt("scrollend",t);break;case"onClick":a!=null&&(t.onclick=Fu);break;case"suppressContentEditableWarning":case"suppressHydrationWar
Line truncated
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var Xh;function v1(){if(Xh)return zt;Xh=1;var l=typeof Symbol=="function"&&Symbol.for,i=l?Symbol.for("react.element"):60103,c=l?Symbol.for("react.portal"):60106,o=l?Symbol.for("react.fragment"):60107,s=l?Symbol.for("react.strict_mode"):60108,d=l?Symbol.for("react.profiler"):60114,m=l?Symbol.for("react.provider"):60109,b=l?Symbol.for("react.context"):60110,v=l?Symbol.for("react.async_mode"):60111,p=l?Symbol.for("react.concurrent_mode"):60111,C=l?Symbol.for("react.forward_ref"):60112,_=l?Symbol.for("react.suspense"):60113,w=l?Symbol.for("react.suspense_list"):60120,j=l?Symbol.for("react.memo"):60115,B=l?Symbol.for("react.lazy"):60116,U=l?Symbol.for("react.block"):60121,X=l?Symbol.for("react.fundamental"):60117,Q=l?Symbol.for("react.responder"):60118,F=l?Symbol.for("react.scope"):60119;function V(O){if(typeof O=="object"&&O!==null){var $=O.$$typeof;switch($){case i:switch(O=O.type,O){case v:case p:case o:case d:case s:case _:return O;default:switch(O=O&&O.$$typeof,O){case b:case C:case B:case j:case m:return O;default:return $}}case c:return $}}}function L(O){return V(O)===p}return zt.AsyncMode=v,zt.ConcurrentMode=p,zt.ContextConsumer=b,zt.ContextProvider=m,zt.Element=i,zt.ForwardRef=C,zt.Fragment=o,zt.Lazy=B,zt.Memo=j,zt.Portal=c,zt.Profiler=d,zt.StrictMode=s,zt.Suspense=_,zt.isAsyncMode=function(O){return L(O)||V(O)===v},zt.isConcurrentMode=L,zt.isContextConsumer=function(O){return V(O)===b},zt.isContextProvider=function(O){return V(O)===m},zt.isElement=function(O){return typeof O=="object"&&O!==null&&O.$$typeof===i},zt.isForwardRef=function(O){return V(O)===C},zt.isFragment=function(O){return V(O)===o},zt.isLazy=function(O){return V(O)===B},zt.isMemo=function(O){return V(O)===j},zt.isPortal=function(O){return V(O)===c},zt.isProfiler=function(O){return V(O)===d},zt.isStrictMode=function(O){return V(O)===s},zt.isSuspense=function(O){return V(O)===_},zt.isValidElementType=function(O){return typeof O=="string"||typeof O=="function"||O===o||O===p||O===d||O===s||O===_||O===w||typeof O=="object"&&O!==null&&(O.$$typeof===B||O.$$typeof===j||O.$$typeof===m||O.$$typeof===b||O.$$typeof===C||O.$$typeof===X||O.$$typeof===Q||O.$$typeof===F||O.$$typeof===U)},zt.typeOf=V,zt}var $h;function S1(){return $h||($h=1,ns.exports=v1()),ns.exports}var as,kh;function T1(){if(kh)return as;kh=1;var l=S1(),i={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},c={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},o={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},s={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},d={};d[l.ForwardRef]=o,d[l.Memo]=s;function m(B){return l.isMemo(B)?s:d[B.$$typeof]||i}var b=Object.defineProperty,v=Object.getOwnPropertyNames,p=Object.getOwnPropertySymbols,C=Object.getOwnPropertyDescriptor,_=Object.getPrototypeOf,w=Object.prototype;function j(B,U,X){if(typeof U!="string"){if(w){var Q=_(U);Q&&Q!==w&&j(B,Q,X)}var F=v(U);p&&(F=F.concat(p(U)));for(var V=m(B),L=m(U),O=0;O<F.length;++O){var $=F[O];if(!c[$]&&!(X&&X[$])&&!(L&&L[$])&&!(V&&V[$])){var W=C(U,$);try{b(B,$,W)}catch{}}}}return B}return as=j,as}T1();var A1=!0;function Cm(l,i,c){var o="";return c.split(" ").forEach(function(s){l[s]!==void 0?i.push(l[s]+";"):s&&(o+=s+" ")}),o}var Rs=function(i,c,o){var s=i.key+"-"+c.name;(o===!1||A1===!1)&&i.registered[s]===void 0&&(i.registered[s]=c.styles)},Ms=function(i,c,o){Rs(i,c,o);var s=i.key+"-"+c.name;if(i.inserted[c.name]===void 0){var d=c;do i.insert(c===d?"."+s:"",d,i.sheet,!0),d=d.next;while(d!==void 0)}};function E1(l){for(var i=0,c,o=0,s=l.length;s>=4;++o,s-=4)c=l.charCodeAt(o)&255|(l.charCodeAt(++o)&255)<<8|(l.charCodeAt(++o)&255)<<16|(l.charCodeAt(++o)&255)<<24,c=(c&65535)*1540483477+((c>>>16)*59797<<16),c^=c>>>24,i=(c&65535)*1540483477+((c>>>16)*59797<<16)^(i&65535)*1540483477+((i>>>16)*59797<<16);switch(s){case 3:i^=(l.charCodeAt(o+2)&255)<<16;case 2:i^=(l.charCodeAt(o+1)&255)<<8;case 1:i^=l.charCodeAt(o)&255,i=(i&65535)*1540483477+((i>>>16)*59797<<16)}return i^=i>>>13,i=(i&65535)*1540483477+((i>>>16)*59797<<16),((i^i>>>15)>>>0).toString(36)}var x1={animationIterationCount:1,aspectRatio:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,scale:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1},_1=/[A-Z]|^ms/g,O1=/_EMO_([^_]+?)_([^]*?)_EMO_/g,zm=function(i){return i.charCodeAt(1)===45},Qh=functioLine truncated
|
||||
* @mui/styled-engine v6.3.0
|
||||
*
|
||||
* @license MIT
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/function Dm(l,i){return hs(l,i)}function k1(l,i){Array.isArray(l.__emotion_styles)&&(l.__emotion_styles=i(l.__emotion_styles))}const Ph=[];function Ih(l){return Ph[0]=l,Wi(Ph)}function dn(l){if(typeof l!="object"||l===null)return!1;const i=Object.getPrototypeOf(l);return(i===null||i===Object.prototype||Object.getPrototypeOf(i)===null)&&!(Symbol.toStringTag in l)&&!(Symbol.iterator in l)}function Bm(l){if(k.isValidElement(l)||!dn(l))return l;const i={};return Object.keys(l).forEach(c=>{i[c]=Bm(l[c])}),i}function Me(l,i,c={clone:!0}){const o=c.clone?{...l}:l;return dn(l)&&dn(i)&&Object.keys(i).forEach(s=>{k.isValidElement(i[s])?o[s]=i[s]:dn(i[s])&&Object.prototype.hasOwnProperty.call(l,s)&&dn(l[s])?o[s]=Me(l[s],i[s],c):c.clone?o[s]=dn(i[s])?Bm(i[s]):i[s]:o[s]=i[s]}),o}const Q1=l=>{const i=Object.keys(l).map(c=>({key:c,val:l[c]}))||[];return i.sort((c,o)=>c.val-o.val),i.reduce((c,o)=>({...c,[o.key]:o.val}),{})};function Z1(l){const{values:i={xs:0,sm:600,md:900,lg:1200,xl:1536},unit:c="px",step:o=5,...s}=l,d=Q1(i),m=Object.keys(d);function b(w){return`@media (min-width:${typeof i[w]=="number"?i[w]:w}${c})`}function v(w){return`@media (max-width:${(typeof i[w]=="number"?i[w]:w)-o/100}${c})`}function p(w,j){const B=m.indexOf(j);return`@media (min-width:${typeof i[w]=="number"?i[w]:w}${c}) and (max-width:${(B!==-1&&typeof i[m[B]]=="number"?i[m[B]]:j)-o/100}${c})`}function C(w){return m.indexOf(w)+1<m.length?p(w,m[m.indexOf(w)+1]):b(w)}function _(w){const j=m.indexOf(w);return j===0?b(m[1]):j===m.length-1?v(m[j]):p(w,m[m.indexOf(w)+1]).replace("@media","@media not all and")}return{keys:m,values:d,up:b,down:v,between:p,only:C,not:_,unit:c,...s}}function K1(l,i){if(!l.containerQueries)return i;const c=Object.keys(i).filter(o=>o.startsWith("@container")).sort((o,s)=>{var m,b;const d=/min-width:\s*([0-9.]+)/;return+(((m=o.match(d))==null?void 0:m[1])||0)-+(((b=s.match(d))==null?void 0:b[1])||0)});return c.length?c.reduce((o,s)=>{const d=i[s];return delete o[s],o[s]=d,o},{...i}):i}function W1(l,i){return i==="@"||i.startsWith("@")&&(l.some(c=>i.startsWith(`@${c}`))||!!i.match(/^@\d/))}function J1(l,i){const c=i.match(/^@([^/]+)?\/?(.+)?$/);if(!c)return null;const[,o,s]=c,d=Number.isNaN(+o)?o||0:+o;return l.containerQueries(s).up(d)}function F1(l){const i=(d,m)=>d.replace("@media",m?`@container ${m}`:"@container");function c(d,m){d.up=(...b)=>i(l.breakpoints.up(...b),m),d.down=(...b)=>i(l.breakpoints.down(...b),m),d.between=(...b)=>i(l.breakpoints.between(...b),m),d.only=(...b)=>i(l.breakpoints.only(...b),m),d.not=(...b)=>{const v=i(l.breakpoints.not(...b),m);return v.includes("not all and")?v.replace("not all and ","").replace("min-width:","width<").replace("max-width:","width>").replace("and","or"):v}}const o={},s=d=>(c(o,d),o);return c(s),{...l,containerQueries:s}}const P1={borderRadius:4};function Yi(l,i){return i?Me(l,i,{clone:!1}):l}const Br={xs:0,sm:600,md:900,lg:1200,xl:1536},tm={keys:["xs","sm","md","lg","xl"],up:l=>`@media (min-width:${Br[l]}px)`},I1={containerQueries:l=>({up:i=>{let c=typeof i=="number"?i:Br[i]||i;return typeof c=="number"&&(c=`${c}px`),l?`@container ${l} (min-width:${c})`:`@container (min-width:${c})`}})};function Nn(l,i,c){const o=l.theme||{};if(Array.isArray(i)){const d=o.breakpoints||tm;return i.reduce((m,b,v)=>(m[d.up(d.keys[v])]=c(i[v]),m),{})}if(typeof i=="object"){const d=o.breakpoints||tm;return Object.keys(i).reduce((m,b)=>{if(W1(d.keys,b)){const v=J1(o.containerQueries?o:I1,b);v&&(m[v]=c(i[b],b))}else if(Object.keys(d.values||Br).includes(b)){const v=d.up(b);m[v]=c(i[b],b)}else{const v=b;m[v]=i[v]}return m},{})}return c(i)}function tb(l={}){var c;return((c=l.keys)==null?void 0:c.reduce((o,s)=>{const d=l.up(s);return o[d]={},o},{}))||{}}function eb(l,i){return l.reduce((c,o)=>{const s=c[o];return(!s||Object.keys(s).length===0)&&delete c[o],c},i)}function jt(l){if(typeof l!="string")throw new Error(Ba(7));return l.charAt(0).toUpperCase()+l.slice(1)}function Nr(l,i,c=!0){if(!i||typeof i!="string")return null;if(l&&l.vars&&c){const o=`vars.${i}`.split(".").reduce((s,d)=>s&&s[d]?s[d]:null,l);if(o!=null)return o}return i.split(".").reduce((o,s)=>o&&o[s]!=null?o[s]:null,l)}function _r(l,i,c,o=c){let s;return typeof l=="function"?s=l(c):Array.isArray(l)?s=l[c]||o:s=Nr(l,c)||o,i&&(s=i(s,o,l)),s}function Kt(l){const{prop:i,cssProperty:c=l.prop,themeKey:o,transform:s}=l,d=m=>{if(m[i]==null)return null;const b=m[i],v=m.theme,p=Nr(v,o)||{};return Nn(m,b,_=>{let w=_r(p,s,_);return _===w&&typeof _=="string"&&(w=_r(p,s,`${i}${_==="default"?"":jt(_)}`,_)),c===!1?w:{[c]:w}})};return d.propTypes={},d.filterProps=[i],d}function nb(l){const i={};return c=>(i[c]===void 0&&(i[c]=l(c)),i[c])}const ab={m:"margin",p:"padding"},lb={t:"Top",r:"Right",b:"Bottom",l:"Left",x:["Left","Right"],y:["Top","Bottom"]},em={marginX:"mx",marginY:"my",paddingX:"px",paddingY:"py"},ib=nb(l=>{if(l.length>2)if(em[l])l=em[l];else return[l];const[i,c]=l.split(""),o=ab[i],s=lb[c]||"";return Array.isArray(s)?s.map(d=>o+d):[o+s]Line truncated
|
||||
|
||||
const theme = ${JSON.stringify(i,null,2)};
|
||||
|
||||
theme.breakpoints = createBreakpoints(theme.breakpoints || {});
|
||||
theme.transitions = createTransitions(theme.transitions || {});
|
||||
|
||||
export default theme;`}function ys(l={},...i){const{breakpoints:c,mixins:o={},spacing:s,palette:d={},transitions:m={},typography:b={},shape:v,...p}=l;if(l.vars)throw new Error(Ba(20));const C=Ys(d),_=qs(l);let w=Me(_,{mixins:Bv(_.breakpoints,o),palette:C,shadows:Lv.slice(),typography:Uv(C,b),transitions:Xv(m),zIndex:{...$v}});return w=Me(w,p),w=i.reduce((j,B)=>Me(j,B),w),w.unstable_sxConfig={...Pi,...p==null?void 0:p.unstable_sxConfig},w.unstable_sx=function(B){return Na({sx:B,theme:this})},w.toRuntimeSource=Qm,w}function Qv(l){let i;return l<1?i=5.11916*l**2:i=4.5*Math.log(l+1)+2,Math.round(i*10)/1e3}const Zv=[...Array(25)].map((l,i)=>{if(i===0)return"none";const c=Qv(i);return`linear-gradient(rgba(255 255 255 / ${c}), rgba(255 255 255 / ${c}))`});function Zm(l){return{inputPlaceholder:l==="dark"?.5:.42,inputUnderline:l==="dark"?.7:.42,switchTrackDisabled:l==="dark"?.2:.12,switchTrack:l==="dark"?.3:.38}}function Km(l){return l==="dark"?Zv:[]}function Kv(l){const{palette:i={mode:"light"},opacity:c,overlays:o,...s}=l,d=Ys(i);return{palette:d,opacity:{...Zm(d.mode),...c},overlays:o||Km(d.mode),...s}}function Wv(l){var i;return!!l[0].match(/(cssVarPrefix|colorSchemeSelector|rootSelector|typography|mixins|breakpoints|direction|transitions)/)||!!l[0].match(/sxConfig$/)||l[0]==="palette"&&!!((i=l[1])!=null&&i.match(/(mode|contrastThreshold|tonalOffset)/))}const Jv=l=>[...[...Array(25)].map((i,c)=>`--${l?`${l}-`:""}overlays-${c}`),`--${l?`${l}-`:""}palette-AppBar-darkBg`,`--${l?`${l}-`:""}palette-AppBar-darkColor`],Fv=l=>(i,c)=>{const o=l.rootSelector||":root",s=l.colorSchemeSelector;let d=s;if(s==="class"&&(d=".%s"),s==="data"&&(d="[data-%s]"),s!=null&&s.startsWith("data-")&&!s.includes("%s")&&(d=`[${s}="%s"]`),l.defaultColorScheme===i){if(i==="dark"){const m={};return Jv(l.cssVarPrefix).forEach(b=>{m[b]=c[b],delete c[b]}),d==="media"?{[o]:c,"@media (prefers-color-scheme: dark)":{[o]:m}}:d?{[d.replace("%s",i)]:m,[`${o}, ${d.replace("%s",i)}`]:c}:{[o]:{...c,...m}}}if(d&&d!=="media")return`${o}, ${d.replace("%s",String(i))}`}else if(i){if(d==="media")return{[`@media (prefers-color-scheme: ${String(i)})`]:{[o]:c}};if(d)return d.replace("%s",String(i))}return o};function Pv(l,i){i.forEach(c=>{l[c]||(l[c]={})})}function R(l,i,c){!l[i]&&c&&(l[i]=c)}function Gi(l){return typeof l!="string"||!l.startsWith("hsl")?l:Vm(l)}function Mn(l,i){`${i}Channel`in l||(l[`${i}Channel`]=Li(Gi(l[i])))}function Iv(l){return typeof l=="number"?`${l}px`:typeof l=="string"||typeof l=="function"||Array.isArray(l)?l:"8px"}const on=l=>{try{return l()}catch{}},tS=(l="mui")=>Sv(l);function rs(l,i,c,o){if(!i)return;i=i===!0?{}:i;const s=o==="dark"?"dark":"light";if(!c){l[o]=Kv({...i,palette:{mode:s,...i==null?void 0:i.palette}});return}const{palette:d,...m}=ys({...c,palette:{mode:s,...i==null?void 0:i.palette}});return l[o]={...i,palette:d,opacity:{...Zm(s),...i==null?void 0:i.opacity},overlays:(i==null?void 0:i.overlays)||Km(s)},m}function eS(l={},...i){const{colorSchemes:c={light:!0},defaultColorScheme:o,disableCssColorScheme:s=!1,cssVarPrefix:d="mui",shouldSkipGeneratingVar:m=Wv,colorSchemeSelector:b=c.light&&c.dark?"media":void 0,rootSelector:v=":root",...p}=l,C=Object.keys(c)[0],_=o||(c.light&&C!=="light"?"light":C),w=tS(d),{[_]:j,light:B,dark:U,...X}=c,Q={...X};let F=j;if((_==="dark"&&!("dark"in c)||_==="light"&&!("light"in c))&&(F=!0),!F)throw new Error(Ba(21,_));const V=rs(Q,F,p,_);B&&!Q.light&&rs(Q,B,void 0,"light"),U&&!Q.dark&&rs(Q,U,void 0,"dark");let L={defaultColorScheme:_,...V,cssVarPrefix:d,colorSchemeSelector:b,rootSelector:v,getCssVar:w,colorSchemes:Q,font:{...Dv(V.typography),...V.font},spacing:Iv(p.spacing)};Object.keys(L.colorSchemes).forEach(K=>{const y=L.colorSchemes[K].palette,Y=et=>{const ct=et.split("-"),St=ct[1],_t=ct[2];return w(et,y[St][_t])};if(y.mode==="light"&&(R(y.common,"background","#fff"),R(y.common,"onBackground","#000")),y.mode==="dark"&&(R(y.common,"background","#000"),R(y.common,"onBackground","#fff")),Pv(y,["Alert","AppBar","Avatar","Button","Chip","FilledInput","LinearProgress","Skeleton","Slider","SnackbarContent","SpeedDialAction","StepConnector","StepContent","Switch","TableCell","Tooltip"]),y.mode==="light"){R(y.Alert,"errorColor",Dt(y.error.light,.6)),R(y.Alert,"infoColor",Dt(y.info.light,.6)),R(y.Alert,"successColor",Dt(y.success.light,.6)),R(y.Alert,"warningColor",Dt(y.warning.light,.6)),R(y.Alert,"errorFilledBg",Y("palette-error-main")),R(y.Alert,"infoFilledBg",Y("palette-info-main")),R(y.Alert,"successFilledBg",Y("palette-success-main")),R(y.Alert,"warningFilledBg",Y("palette-warning-main")),R(y.Alert,"errorFilledColor",on(()=>y.getContrastText(y.error.main))),R(y.Alert,"infoFilledColor",on(()=>y.getContrastText(y.info.main))),R(y.Alert,"successFilledColor",on(()=>y.getContrastText(y.success.main))),R(y.Alert,"warningFilledColor",on(()=>y.getContrastText(y.warning.main))),R(y.Alert,"errorStandardBg",Bt(y.error.light,.9)),R(y.Alert,"infoStandardBg",Bt(y.info.light,.9)),R(y.Alert,"successStandardB
Line truncated
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.3;
|
||||
}
|
||||
`,bS=Dl`
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
`,vS=Dl`
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
`,SS=Ie("span",{name:"MuiTouchRipple",slot:"Root"})({overflow:"hidden",pointerEvents:"none",position:"absolute",zIndex:0,top:0,right:0,bottom:0,left:0,borderRadius:"inherit"}),TS=Ie(yS,{name:"MuiTouchRipple",slot:"Ripple"})`
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
|
||||
&.${ke.rippleVisible} {
|
||||
opacity: 0.3;
|
||||
transform: scale(1);
|
||||
animation-name: ${gS};
|
||||
animation-duration: ${gs}ms;
|
||||
animation-timing-function: ${({theme:l})=>l.transitions.easing.easeInOut};
|
||||
}
|
||||
|
||||
&.${ke.ripplePulsate} {
|
||||
animation-duration: ${({theme:l})=>l.transitions.duration.shorter}ms;
|
||||
}
|
||||
|
||||
& .${ke.child} {
|
||||
opacity: 1;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
& .${ke.childLeaving} {
|
||||
opacity: 0;
|
||||
animation-name: ${bS};
|
||||
animation-duration: ${gs}ms;
|
||||
animation-timing-function: ${({theme:l})=>l.transitions.easing.easeInOut};
|
||||
}
|
||||
|
||||
& .${ke.childPulsate} {
|
||||
position: absolute;
|
||||
/* @noflip */
|
||||
left: 0px;
|
||||
top: 0;
|
||||
animation-name: ${vS};
|
||||
animation-duration: 2500ms;
|
||||
animation-timing-function: ${({theme:l})=>l.transitions.easing.easeInOut};
|
||||
animation-iteration-count: infinite;
|
||||
animation-delay: 200ms;
|
||||
}
|
||||
`,AS=k.forwardRef(function(i,c){const o=Ul({props:i,name:"MuiTouchRipple"}),{center:s=!1,classes:d={},className:m,...b}=o,[v,p]=k.useState([]),C=k.useRef(0),_=k.useRef(null);k.useEffect(()=>{_.current&&(_.current(),_.current=null)},[v]);const w=k.useRef(!1),j=hv(),B=k.useRef(null),U=k.useRef(null),X=k.useCallback(L=>{const{pulsate:O,rippleX:$,rippleY:W,rippleSize:gt,cb:K}=L;p(y=>[...y,rt.jsx(TS,{classes:{ripple:pe(d.ripple,ke.ripple),rippleVisible:pe(d.rippleVisible,ke.rippleVisible),ripplePulsate:pe(d.ripplePulsate,ke.ripplePulsate),child:pe(d.child,ke.child),childLeaving:pe(d.childLeaving,ke.childLeaving),childPulsate:pe(d.childPulsate,ke.childPulsate)},timeout:gs,pulsate:O,rippleX:$,rippleY:W,rippleSize:gt},C.current)]),C.current+=1,_.current=K},[d]),Q=k.useCallback((L={},O={},$=()=>{})=>{const{pulsate:W=!1,center:gt=s||O.pulsate,fakeElement:K=!1}=O;if((L==null?void 0:L.type)==="mousedown"&&w.current){w.current=!1;return}(L==null?void 0:L.type)==="touchstart"&&(w.current=!0);const y=K?null:U.current,Y=y?y.getBoundingClientRect():{width:0,height:0,left:0,top:0};let et,ct,St;if(gt||L===void 0||L.clientX===0&&L.clientY===0||!L.clientX&&!L.touches)et=Math.round(Y.width/2),ct=Math.round(Y.height/2);else{const{clientX:_t,clientY:G}=L.touches&&L.touches.length>0?L.touches[0]:L;et=Math.round(_t-Y.left),ct=Math.round(G-Y.top)}if(gt)St=Math.sqrt((2*Y.width**2+Y.height**2)/3),St%2===0&&(St+=1);else{const _t=Math.max(Math.abs((y?y.clientWidth:0)-et),et)*2+2,G=Math.max(Math.abs((y?y.clientHeight:0)-ct),ct)*2+2;St=Math.sqrt(_t**2+G**2)}L!=null&&L.touches?B.current===null&&(B.current=()=>{X({pulsate:W,rippleX:et,rippleY:ct,rippleSize:St,cb:$})},j.start(pS,()=>{B.current&&(B.current(),B.current=null)})):X({pulsate:W,rippleX:et,rippleY:ct,rippleSize:St,cb:$})},[s,X,j]),F=k.useCallback(()=>{Q({},{pulsate:!0})},[Q]),V=k.useCallback((L,O)=>{if(j.clear(),(L==null?void 0:L.type)==="touchend"&&B.current){B.current(),B.current=null,j.start(0,()=>{V(L,O)});return}B.current=null,p($=>$.length>0?$.slice(1):$),_.current=O},[j]);return k.useImperativeHandle(c,()=>({pulsate:F,start:Q,stop:V}),[F,Q,V]),rt.jsx(SS,{className:pe(ke.root,d.root,m),ref:U,...b,children:rt.jsx(Xs,{component:null,exit:!0,children:v})})});function ES(l){return Ii("MuiButtonBase",l)}const xS=Bl("MuiButtonBase",["root","disabled","focusVisible"]),_S=l=>{const{disabled:i,focusVisible:c,focusVisibleClassName:o,classes:s}=l,m=Xr({root:["root",i&&"disabled",c&&"focusVisible"]},ES,s);return c&&o&&(m.root+=` ${o}`),m},OS=Ie("button",{name:"MuiButtonBase",slot:"Root",overridesResolver:(l,i)=>i.root})({display:"inline-flex",alignItems:"center",justifyContent:"center",position:"relative",boxSizing:"border-box",WebkitTapHighlightColor:"transparent",backgroundColor:"transparent",outline:0,border:0,margin:0,borderRadius:0,padding:0,cursor:"pointer",userSelect:"none",verticalAlign:"middle",MozAppearance:"none",WebkitAppearance:"none",textDecoration:"none",color:"inherit","&::-moz-focus-inner":{borderStyle:"none"},[`&.${xS.disabled}`]:{pointerEvents:"none",cursor:"default"},"@media print":{colorAdjust:"exact"}}),CS=k.forwardRef(function(i,c){const o=Ul({props:i,name:"MuiButtonBase"}),{action:s,centerRipple:d=!1,children:m,className:b,component:v="button",disabled:p=!1,disableRipple:C=!1,disableTouchRipple:_=!1,focusRipple:w=!1,focusVisibleClassName:j,LinkComponent:B="a",onBlur:U,onClick:X,onContextMenu:Q,onDragLeave:F,onFocus:V,onFocusVisible:L,onKeyDown:O,onKeyUp:$,onMouseDown:W,onMouseLeave:gt,onMouseUp:K,onTouchEnd:y,onTouchMove:Y,onTouchStart:et,tabIndex:ct=0,TouchRippleProps:St,touchRippleRef:_t,type:G,...it}=o,tt=k.useRef(null),ot=hS(),S=am(ot.ref,_t),[q,at]=k.useState(!1);p&&q&&at(!1),k.useImperativeHandle(s,()=>({focusVisible:()=>{at(!0),tt.current.focus()}}),[]);const lt=ot.shouldMount&&!C&&!p;k.useEffect(()=>{q&&w&&!C&&ot.pulsate()},[C,w,q,ot]);const J=Dn(ot,"start",W,_),bt=Dn(ot,"stop",Q,_),ft=Dn(ot,"stop",F,_),ie=Dn(ot,"stop",K,_),At=Dn(ot,"stop",dt=>{q&&dt.preventDefault(),gt&>(dt)},_),Yt=Dn(ot,"start",et,_),Nt=Dn(ot,"stop",y,_),De=Dn(ot,"stop",Y,_),oa=Dn(ot,"stop",dt=>{im(dt.target)||at(!1),U&&U(dt)},!1),tn=vr(dt=>{tt.current||(tt.current=dt.currentTarget),im(dt.target)&&(at(!0),L&&L(dt)),V&&V(dt)}),Un=()=>{const dt=tt.current;return v&&v!=="button"&&!(dt.tagName==="A"&&dt.href)},Ha=vr(dt=>{w&&!dt.repeat&&q&&dt.key===" "&&ot.stop(dt,()=>{ot.start(dt)}),dt.target===dt.currentTarget&&Un()&&dt.key===" "&&dt.preventDefault(),O&&O(dt),dt.target===dt.currentTarget&&Un()&&dt.key==="Enter"&&!p&&(dt.preventDefault(),X&&X(dt))}),Hn=vr(dt=>{w&&dt.key===" "&&q&&!dt.defaultPrevented&&ot.stop(dt,()=>{ot.pulsate(dt)}),$&&$(dt),X&&dt.target===dt.currentTarget&&Un()&&dt.key===" "&&!dt.defaultPrevented&&X(dt)});let jn=v;jn==="button"&&(it.href||it.to)&&(jn=B);const en={};jn==="button"?(en.type=G===void 0?"button":G,en.disabled=p):(!it.href&&!it.to&&(en.role="button"),p&&(en["aria-disabled"]=p));const Hl=am(c,tt),ja={...o,centerRipple:d,component:v,disabled:p,disableRipple:C,di
Line truncated
|
||||
0% {
|
||||
left: -35%;
|
||||
right: 100%;
|
||||
}
|
||||
|
||||
60% {
|
||||
left: 100%;
|
||||
right: -90%;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 100%;
|
||||
right: -90%;
|
||||
}
|
||||
`,FS=typeof Ss!="string"?Dr`
|
||||
animation: ${Ss} 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
|
||||
`:null,Ts=Dl`
|
||||
0% {
|
||||
left: -200%;
|
||||
right: 100%;
|
||||
}
|
||||
|
||||
60% {
|
||||
left: 107%;
|
||||
right: -8%;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 107%;
|
||||
right: -8%;
|
||||
}
|
||||
`,PS=typeof Ts!="string"?Dr`
|
||||
animation: ${Ts} 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) 1.15s infinite;
|
||||
`:null,As=Dl`
|
||||
0% {
|
||||
opacity: 1;
|
||||
background-position: 0 -23px;
|
||||
}
|
||||
|
||||
60% {
|
||||
opacity: 0;
|
||||
background-position: 0 -23px;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
background-position: -200px -23px;
|
||||
}
|
||||
`,IS=typeof As!="string"?Dr`
|
||||
animation: ${As} 3s infinite linear;
|
||||
`:null,t2=l=>{const{classes:i,variant:c,color:o}=l,s={root:["root",`color${jt(o)}`,c],dashed:["dashed",`dashedColor${jt(o)}`],bar1:["bar",`barColor${jt(o)}`,(c==="indeterminate"||c==="query")&&"bar1Indeterminate",c==="determinate"&&"bar1Determinate",c==="buffer"&&"bar1Buffer"],bar2:["bar",c!=="buffer"&&`barColor${jt(o)}`,c==="buffer"&&`color${jt(o)}`,(c==="indeterminate"||c==="query")&&"bar2Indeterminate",c==="buffer"&&"bar2Buffer"]};return Xr(s,JS,i)},ks=(l,i)=>l.vars?l.vars.palette.LinearProgress[`${i}Bg`]:l.palette.mode==="light"?Vr(l.palette[i].main,.62):Yr(l.palette[i].main,.5),e2=Ie("span",{name:"MuiLinearProgress",slot:"Root",overridesResolver:(l,i)=>{const{ownerState:c}=l;return[i.root,i[`color${jt(c.color)}`],i[c.variant]]}})(Nl(({theme:l})=>({position:"relative",overflow:"hidden",display:"block",height:4,zIndex:0,"@media print":{colorAdjust:"exact"},variants:[...Object.entries(l.palette).filter(Ua()).map(([i])=>({props:{color:i},style:{backgroundColor:ks(l,i)}})),{props:({ownerState:i})=>i.color==="inherit"&&i.variant!=="buffer",style:{"&::before":{content:'""',position:"absolute",left:0,top:0,right:0,bottom:0,backgroundColor:"currentColor",opacity:.3}}},{props:{variant:"buffer"},style:{backgroundColor:"transparent"}},{props:{variant:"query"},style:{transform:"rotate(180deg)"}}]}))),n2=Ie("span",{name:"MuiLinearProgress",slot:"Dashed",overridesResolver:(l,i)=>{const{ownerState:c}=l;return[i.dashed,i[`dashedColor${jt(c.color)}`]]}})(Nl(({theme:l})=>({position:"absolute",marginTop:0,height:"100%",width:"100%",backgroundSize:"10px 10px",backgroundPosition:"0 -23px",variants:[{props:{color:"inherit"},style:{opacity:.3,backgroundImage:"radial-gradient(currentColor 0%, currentColor 16%, transparent 42%)"}},...Object.entries(l.palette).filter(Ua()).map(([i])=>{const c=ks(l,i);return{props:{color:i},style:{backgroundImage:`radial-gradient(${c} 0%, ${c} 16%, transparent 42%)`}}})]})),IS||{animation:`${As} 3s infinite linear`}),a2=Ie("span",{name:"MuiLinearProgress",slot:"Bar1",overridesResolver:(l,i)=>{const{ownerState:c}=l;return[i.bar,i[`barColor${jt(c.color)}`],(c.variant==="indeterminate"||c.variant==="query")&&i.bar1Indeterminate,c.variant==="determinate"&&i.bar1Determinate,c.variant==="buffer"&&i.bar1Buffer]}})(Nl(({theme:l})=>({width:"100%",position:"absolute",left:0,bottom:0,top:0,transition:"transform 0.2s linear",transformOrigin:"left",variants:[{props:{color:"inherit"},style:{backgroundColor:"currentColor"}},...Object.entries(l.palette).filter(Ua()).map(([i])=>({props:{color:i},style:{backgroundColor:(l.vars||l).palette[i].main}})),{props:{variant:"determinate"},style:{transition:`transform .${vs}s linear`}},{props:{variant:"buffer"},style:{zIndex:1,transition:`transform .${vs}s linear`}},{props:({ownerState:i})=>i.variant==="indeterminate"||i.variant==="query",style:{width:"auto"}},{props:({ownerState:i})=>i.variant==="indeterminate"||i.variant==="query",style:FS||{animation:`${Ss} 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite`}}]}))),l2=Ie("span",{name:"MuiLinearProgress",slot:"Bar2",overridesResolver:(l,i)=>{const{ownerState:c}=l;return[i.bar,i[`barColor${jt(c.color)}`],(c.variant==="indeterminate"||c.variant==="query")&&i.bar2Indeterminate,c.variant==="buffer"&&i.bar2Buffer]}})(Nl(({theme:l})=>({width:"100%",position:"absolute",left:0,bottom:0,top:0,transition:"transform 0.2s linear",transformOrigin:"left",variants:[...Object.entries(l.palette).filter(Ua()).map(([i])=>({props:{color:i},style:{"--LinearProgressBar2-barColor":(l.vars||l).palette[i].main}})),{props:({ownerState:i})=>i.variant!=="buffer"&&i.color!=="inherit",style:{backgroundColor:"var(--LinearProgressBar2-barColor, currentColor)"}},{props:({ownerState:i})=>i.variant!=="buffer"&&i.color==="inherit",style:{backgroundColor:"currentColor"}},{props:{color:"inherit"},style:{opacity:.3}},...Object.entries(l.palette).filter(Ua()).map(([i])=>({props:{color:i,variant:"buffer"},style:{backgroundColor:ks(l,i),transition:`transform .${vs}s linear`}})),{props:({ownerState:i})=>i.variant==="indeterminate"||i.variant==="query",style:{width:"auto"}},{props:({ownerState:i})=>i.variant==="indeterminate"||i.variant==="query",style:PS||{animation:`${Ts} 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) 1.15s infinite`}}]}))),i2=k.forwardRef(function(i,c){const o=Ul({props:i,name:"MuiLinearProgress"}),{className:s,color:d="primary",value:m,valueBuffer:b,variant:v="indeterminate",...p}=o,C={...o,color:d,variant:v},_=t2(C),w=yv(),j={},B={bar1:{},bar2:{}};if((v==="determinate"||v==="buffer")&&m!==void 0){j["aria-valuenow"]=Math.round(m),j["aria-valuemin"]=0,j["aria-valuemax"]=100;let U=m-100;w&&(U=-U),B.bar1.transform=`translateX(${U}%)`}if(v==="buffer"&&b!==void 0){let U=(b||0)-100;w&&(U=-U),B.bar2.transform=`translateX(${U}%)`}return rt.jsxs(e2,{className:pe(_.root,s),ownerState:C,role:"progressbar",...j,ref:c,...p,children:[v==="buffer"?rt.jsx(n2,{className:_.dashed,ownerState:C}):null,rt.jsx(a2,{className:_.bar1,ownerState:C,style:B.bar1}),v==
Line truncated
|
||||
-188
@@ -1,188 +0,0 @@
|
||||
function dh(e,t){for(var n=0;n<t.length;n++){const r=t[n];if(typeof r!="string"&&!Array.isArray(r)){for(const o in r)if(o!=="default"&&!(o in e)){const i=Object.getOwnPropertyDescriptor(r,o);i&&Object.defineProperty(e,o,i.get?i:{enumerable:!0,get:()=>r[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const i of o)if(i.type==="childList")for(const l of i.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&r(l)}).observe(document,{childList:!0,subtree:!0});function n(o){const i={};return o.integrity&&(i.integrity=o.integrity),o.referrerPolicy&&(i.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?i.credentials="include":o.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(o){if(o.ep)return;o.ep=!0;const i=n(o);fetch(o.href,i)}})();function ph(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function sn(e){if(e.__esModule)return e;var t=e.default;if(typeof t=="function"){var n=function r(){return this instanceof r?Reflect.construct(t,arguments,this.constructor):t.apply(this,arguments)};n.prototype=t.prototype}else n={};return Object.defineProperty(n,"__esModule",{value:!0}),Object.keys(e).forEach(function(r){var o=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(n,r,o.get?o:{enumerable:!0,get:function(){return e[r]}})}),n}var jf={exports:{}},Bi={},Ff={exports:{}},D={};/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var yo=Symbol.for("react.element"),mh=Symbol.for("react.portal"),hh=Symbol.for("react.fragment"),yh=Symbol.for("react.strict_mode"),gh=Symbol.for("react.profiler"),vh=Symbol.for("react.provider"),wh=Symbol.for("react.context"),xh=Symbol.for("react.forward_ref"),Sh=Symbol.for("react.suspense"),kh=Symbol.for("react.memo"),_h=Symbol.for("react.lazy"),Uu=Symbol.iterator;function Ch(e){return e===null||typeof e!="object"?null:(e=Uu&&e[Uu]||e["@@iterator"],typeof e=="function"?e:null)}var Wf={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Bf=Object.assign,Uf={};function fr(e,t,n){this.props=e,this.context=t,this.refs=Uf,this.updater=n||Wf}fr.prototype.isReactComponent={};fr.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};fr.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function Vf(){}Vf.prototype=fr.prototype;function _s(e,t,n){this.props=e,this.context=t,this.refs=Uf,this.updater=n||Wf}var Cs=_s.prototype=new Vf;Cs.constructor=_s;Bf(Cs,fr.prototype);Cs.isPureReactComponent=!0;var Vu=Array.isArray,Hf=Object.prototype.hasOwnProperty,Es={current:null},Kf={key:!0,ref:!0,__self:!0,__source:!0};function Qf(e,t,n){var r,o={},i=null,l=null;if(t!=null)for(r in t.ref!==void 0&&(l=t.ref),t.key!==void 0&&(i=""+t.key),t)Hf.call(t,r)&&!Kf.hasOwnProperty(r)&&(o[r]=t[r]);var a=arguments.length-2;if(a===1)o.children=n;else if(1<a){for(var s=Array(a),u=0;u<a;u++)s[u]=arguments[u+2];o.children=s}if(e&&e.defaultProps)for(r in a=e.defaultProps,a)o[r]===void 0&&(o[r]=a[r]);return{$$typeof:yo,type:e,key:i,ref:l,props:o,_owner:Es.current}}function Eh(e,t){return{$$typeof:yo,type:e.type,key:t,ref:e.ref,props:e.props,_owner:e._owner}}function Ps(e){return typeof e=="object"&&e!==null&&e.$$typeof===yo}function Ph(e){var t={"=":"=0",":":"=2"};return"$"+e.replace(/[=:]/g,function(n){return t[n]})}var Hu=/\/+/g;function Wl(e,t){return typeof e=="object"&&e!==null&&e.key!=null?Ph(""+e.key):t.toString(36)}function qo(e,t,n,r,o){var i=typeof e;(i==="undefined"||i==="boolean")&&(e=null);var l=!1;if(e===null)l=!0;else switch(i){case"string":case"number":l=!0;break;case"object":switch(e.$$typeof){case yo:case mh:l=!0}}if(l)return l=e,o=o(l),e=r===""?"."+Wl(l,0):r,Vu(o)?(n="",e!=null&&(n=e.replace(Hu,"$&/")+"/"),qo(o,t,n,"",function(u){return u})):o!=null&&(Ps(o)&&(o=Eh(o,n+(!o.key||l&&l.key===o.key?"":(""+o.key).replace(Hu,"$&/")+"/")+e)),t.push(o)),1;if(l=0,r=r===""?".":r+":",Vu(e))for(var a=0;a<e.length;a++){i=e[a];var s=r+Wl(i,a);l+=qo(i,t,n,s,o)}else if(s=Ch(e),typeof s=="function")for(e=s.call(e),a=0;!(i=e.next()).done;)i=i.value,s=r+Wl(i,a++),l+=qo(i,t,n,s,o);else if(i==="object")throw t=String(e),Error("Objects are not valid as a React child (found: "+(t==="[object Object]"?"object with keys {"+Object.keys(e).join(", ")+"}":t)+"). If you meant to render a collection of children, use an array instead.");return l}function Ro(e,t,n){if(e==null)return e;var r=[],o=0;return qo(e,r,"","",function(i){return t.call(n,i,o++)}),r}function $h(e){if(e._status===-1){var t=e._result;t=t(),t.then(function(n){(e._status===0||e._status===-1)&&(e._status=1,e._result=n)},function(n){(e._status===0||e._status===-1)&&(e._status=2,e._result=n)}),e._status===-1&&(e._status=0,e._result=t)}if(e._status===1)return e._result.default;throw e._result}var be={current:null},Zo={transition:null},Th={ReactCurrentDispatcher:be,ReactCurrentBatchConfig:Zo,ReactCurrentOwner:Es};function Gf(){throw Error("act(...) is not supported in production builds of React.")}D.Children={map:Ro,forEach:function(e,t,n){Ro(e,function(){t.apply(this,arguments)},n)},count:function(e){var t=0;return Ro(e,function(){t++}),t},toArray:function(e){return Ro(e,function(t){return t})||[]},only:function(e){if(!Ps(e))throw Error("React.Children.only expected to receive a single React element child.");return e}};D.Component=fr;D.Fragment=hh;D.Profiler=gh;D.PureComponent=_s;D.StrictMode=yh;D.Suspense=Sh;D.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=Th;D.act=Gf;D.cloneElement=function(e,t,n){if(e==null)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+e+".");var r=Bf({},e.props),o=e.key,i=e.ref,l=e._owner;if(t!=null){if(t.ref!==void 0&&(i=t.ref,l=Es.current),t.key!==void 0&&(o=""+t.key),e.type&&e.type.defaultProps)var a=e.type.defaultProps;for(s in t)Hf.call(t,s)&&!Kf.hasOwnProperty(s)&&(r[s]=t[s]===void 0&&a!==void 0?a[s]:t[s])}var s=arguments.length-2;if(s===1)r.children=n;else if(1<s){a=Array(s);for(var u=0;u<s;u++)a[u]=arguments[u+2];r.children=a}return{$$typeof:yo,type:e.type,key:o,ref:i,props:r,_owner:l}};D.createContext=function(e){return e={$$typeof:wh,_currentValue:e,_currentValue2:e,_threadCount:0,ProviderLine truncated
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var Rh=C,Oh=Symbol.for("react.element"),Mh=Symbol.for("react.fragment"),bh=Object.prototype.hasOwnProperty,zh=Rh.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,Nh={key:!0,ref:!0,__self:!0,__source:!0};function Yf(e,t,n){var r,o={},i=null,l=null;n!==void 0&&(i=""+n),t.key!==void 0&&(i=""+t.key),t.ref!==void 0&&(l=t.ref);for(r in t)bh.call(t,r)&&!Nh.hasOwnProperty(r)&&(o[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps,t)o[r]===void 0&&(o[r]=t[r]);return{$$typeof:Oh,type:e,key:i,ref:l,props:o,_owner:zh.current}}Bi.Fragment=Mh;Bi.jsx=Yf;Bi.jsxs=Yf;jf.exports=Bi;var b=jf.exports,Xf={exports:{}},Ge={},qf={exports:{}},Zf={};/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/(function(e){function t(E,z){var N=E.length;E.push(z);e:for(;0<N;){var Z=N-1>>>1,ue=E[Z];if(0<o(ue,z))E[Z]=z,E[N]=ue,N=Z;else break e}}function n(E){return E.length===0?null:E[0]}function r(E){if(E.length===0)return null;var z=E[0],N=E.pop();if(N!==z){E[0]=N;e:for(var Z=0,ue=E.length,Tn=ue>>>1;Z<Tn;){var Te=2*(Z+1)-1,Bt=E[Te],ct=Te+1,Rn=E[ct];if(0>o(Bt,N))ct<ue&&0>o(Rn,Bt)?(E[Z]=Rn,E[ct]=N,Z=ct):(E[Z]=Bt,E[Te]=N,Z=Te);else if(ct<ue&&0>o(Rn,N))E[Z]=Rn,E[ct]=N,Z=ct;else break e}}return z}function o(E,z){var N=E.sortIndex-z.sortIndex;return N!==0?N:E.id-z.id}if(typeof performance=="object"&&typeof performance.now=="function"){var i=performance;e.unstable_now=function(){return i.now()}}else{var l=Date,a=l.now();e.unstable_now=function(){return l.now()-a}}var s=[],u=[],p=1,m=null,d=3,v=!1,g=!1,y=!1,$=typeof setTimeout=="function"?setTimeout:null,f=typeof clearTimeout=="function"?clearTimeout:null,c=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function h(E){for(var z=n(u);z!==null;){if(z.callback===null)r(u);else if(z.startTime<=E)r(u),z.sortIndex=z.expirationTime,t(s,z);else break;z=n(u)}}function w(E){if(y=!1,h(E),!g)if(n(s)!==null)g=!0,$e(x);else{var z=n(u);z!==null&&Tt(w,z.startTime-E)}}function x(E,z){g=!1,y&&(y=!1,f(P),P=-1),v=!0;var N=d;try{for(h(z),m=n(s);m!==null&&(!(m.expirationTime>z)||E&&!W());){var Z=m.callback;if(typeof Z=="function"){m.callback=null,d=m.priorityLevel;var ue=Z(m.expirationTime<=z);z=e.unstable_now(),typeof ue=="function"?m.callback=ue:m===n(s)&&r(s),h(z)}else r(s);m=n(s)}if(m!==null)var Tn=!0;else{var Te=n(u);Te!==null&&Tt(w,Te.startTime-z),Tn=!1}return Tn}finally{m=null,d=N,v=!1}}var k=!1,S=null,P=-1,I=5,O=-1;function W(){return!(e.unstable_now()-O<I)}function H(){if(S!==null){var E=e.unstable_now();O=E;var z=!0;try{z=S(!0,E)}finally{z?se():(k=!1,S=null)}}else k=!1}var se;if(typeof c=="function")se=function(){c(H)};else if(typeof MessageChannel<"u"){var Se=new MessageChannel,ut=Se.port2;Se.port1.onmessage=H,se=function(){ut.postMessage(null)}}else se=function(){$(H,0)};function $e(E){S=E,k||(k=!0,se())}function Tt(E,z){P=$(function(){E(e.unstable_now())},z)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(E){E.callback=null},e.unstable_continueExecution=function(){g||v||(g=!0,$e(x))},e.unstable_forceFrameRate=function(E){0>E||125<E?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):I=0<E?Math.floor(1e3/E):5},e.unstable_getCurrentPriorityLevel=function(){return d},e.unstable_getFirstCallbackNode=function(){return n(s)},e.unstable_next=function(E){switch(d){case 1:case 2:case 3:var z=3;break;default:z=d}var N=d;d=z;try{return E()}finally{d=N}},e.unstable_pauseExecution=function(){},e.unstable_requestPaint=function(){},e.unstable_runWithPriority=function(E,z){switch(E){case 1:case 2:case 3:case 4:case 5:break;default:E=3}var N=d;d=E;try{return z()}finally{d=N}},e.unstable_scheduleCallback=function(E,z,N){var Z=e.unstable_now();switch(typeof N=="object"&&N!==null?(N=N.delay,N=typeof N=="number"&&0<N?Z+N:Z):N=Z,E){case 1:var ue=-1;break;case 2:ue=250;break;case 5:ue=1073741823;break;case 4:ue=1e4;break;default:ue=5e3}return ue=N+ue,E={id:p++,callback:z,priorityLevel:E,startTime:N,expirationTime:ue,sortIndex:-1},N>Z?(E.sortIndex=N,t(u,E),n(s)===null&&E===n(u)&&(y?(f(P),P=-1):y=!0,Tt(w,N-Z))):(E.sortIndex=ue,t(s,E),g||v||(g=!0,$e(x))),E},e.unstable_shouldYield=W,e.unstable_wrapCallback=function(E){var z=d;return function(){var N=d;d=z;try{return E.apply(this,arguments)}finally{d=N}}}})(Zf);qf.exports=Zf;var Lh=qf.exports;/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var Ih=C,Qe=Lh;function _(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var Jf=new Set,Hr={};function En(e,t){rr(e,t),rr(e+"Capture",t)}function rr(e,t){for(Hr[e]=t,e=0;e<t.length;e++)Jf.add(t[e])}var It=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),va=Object.prototype.hasOwnProperty,Dh=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Qu={},Gu={};function Ah(e){return va.call(Gu,e)?!0:va.call(Qu,e)?!1:Dh.test(e)?Gu[e]=!0:(Qu[e]=!0,!1)}function jh(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function Fh(e,t,n,r){if(t===null||typeof t>"u"||jh(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function ze(e,t,n,r,o,i,l){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=o,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=l}var xe={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){xe[e]=new ze(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];xe[t]=new ze(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){xe[e]=new ze(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){xe[e]=new ze(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){xe[e]=new ze(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){xe[e]=new ze(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){xe[e]=new ze(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){xe[e]=new ze(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){xe[e]=new ze(e,5,!1,e.toLowerCase(),null,!1,!1)});var $s=/[\-:]([a-z])/g;function Ts(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace($s,Ts);xe[t]=new ze(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace($s,Ts);xe[t]=new ze(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace($s,Ts);xe[t]=new ze(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){xe[e]=new ze(e,1,!1,e.toLowerCase(),null,!1,!1)});xe.xlinkHref=new ze("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){xe[e]=new ze(e,1,!1,e.toLowerCase(),null,!0,!0)});function Rs(e,t,n,r){var o=xe.hasOwnProperty(t)?xe[t]:null;(o!==null?o.type!==0:r||!(2<t.length)||t[0]!=="o"&&t[0]!=="
Line truncated
|
||||
`+Bl+e}var Ul=!1;function Vl(e,t){if(!e||Ul)return"";Ul=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(t)if(t=function(){throw Error()},Object.defineProperty(t.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(t,[])}catch(u){var r=u}Reflect.construct(e,[],t)}else{try{t.call()}catch(u){r=u}e.call(t.prototype)}else{try{throw Error()}catch(u){r=u}e()}}catch(u){if(u&&r&&typeof u.stack=="string"){for(var o=u.stack.split(`
|
||||
`),i=r.stack.split(`
|
||||
`),l=o.length-1,a=i.length-1;1<=l&&0<=a&&o[l]!==i[a];)a--;for(;1<=l&&0<=a;l--,a--)if(o[l]!==i[a]){if(l!==1||a!==1)do if(l--,a--,0>a||o[l]!==i[a]){var s=`
|
||||
`+o[l].replace(" at new "," at ");return e.displayName&&s.includes("<anonymous>")&&(s=s.replace("<anonymous>",e.displayName)),s}while(1<=l&&0<=a);break}}}finally{Ul=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?Mr(e):""}function Wh(e){switch(e.tag){case 5:return Mr(e.type);case 16:return Mr("Lazy");case 13:return Mr("Suspense");case 19:return Mr("SuspenseList");case 0:case 2:case 15:return e=Vl(e.type,!1),e;case 11:return e=Vl(e.type.render,!1),e;case 1:return e=Vl(e.type,!0),e;default:return""}}function ka(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case An:return"Fragment";case Dn:return"Portal";case wa:return"Profiler";case Os:return"StrictMode";case xa:return"Suspense";case Sa:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case td:return(e.displayName||"Context")+".Consumer";case ed:return(e._context.displayName||"Context")+".Provider";case Ms:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case bs:return t=e.displayName||null,t!==null?t:ka(e.type)||"Memo";case Vt:t=e._payload,e=e._init;try{return ka(e(t))}catch{}}return null}function Bh(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return ka(t);case 8:return t===Os?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function ln(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function rd(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Uh(e){var t=rd(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var o=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return o.call(this)},set:function(l){r=""+l,i.call(this,l)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(l){r=""+l},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Mo(e){e._valueTracker||(e._valueTracker=Uh(e))}function od(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=rd(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function mi(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function _a(e,t){var n=t.checked;return te({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Xu(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=ln(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function id(e,t){t=t.checked,t!=null&&Rs(e,"checked",t,!1)}function Ca(e,t){id(e,t);var n=ln(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Ea(e,t.type,n):t.hasOwnProperty("defaultValue")&&Ea(e,t.type,ln(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function qu(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Ea(e,t,n){(t!=="number"||mi(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var br=Array.isArray;function Yn(e,t,n,r){if(e=e.options,t){t={};for(var o=0;o<n.length;o++)t["$"+n[o]]=!0;for(n=0;n<e.length;n++)o=t.hasOwnProperty("$"+e[n].value),e[n].selected!==o&&(e[n].selected=o),o&&r&&(e[n].defaultSelected=!0)}else{for(n=""+ln(n),t=null,o=0;o<e.length;o++){if(e[o].value===n){e[o].selected=!0,r&&(e[o].defaultSelected=!0);return}t!==null||e[o].disabled||(t=e[o])}t!==null&&(t.selected=!0)}}function Pa(e,t){if(t.dan
Line truncated
|
||||
`).replace(J0,"")}function jo(e,t,n){if(t=xc(t),xc(e)!==t&&n)throw Error(_(425))}function ki(){}var ja=null,Fa=null;function Wa(e,t){return e==="textarea"||e==="noscript"||typeof t.children=="string"||typeof t.children=="number"||typeof t.dangerouslySetInnerHTML=="object"&&t.dangerouslySetInnerHTML!==null&&t.dangerouslySetInnerHTML.__html!=null}var Ba=typeof setTimeout=="function"?setTimeout:void 0,ey=typeof clearTimeout=="function"?clearTimeout:void 0,Sc=typeof Promise=="function"?Promise:void 0,ty=typeof queueMicrotask=="function"?queueMicrotask:typeof Sc<"u"?function(e){return Sc.resolve(null).then(e).catch(ny)}:Ba;function ny(e){setTimeout(function(){throw e})}function ra(e,t){var n=t,r=0;do{var o=n.nextSibling;if(e.removeChild(n),o&&o.nodeType===8)if(n=o.data,n==="/$"){if(r===0){e.removeChild(o),Xr(t);return}r--}else n!=="$"&&n!=="$?"&&n!=="$!"||r++;n=o}while(n);Xr(t)}function en(e){for(;e!=null;e=e.nextSibling){var t=e.nodeType;if(t===1||t===3)break;if(t===8){if(t=e.data,t==="$"||t==="$!"||t==="$?")break;if(t==="/$")return null}}return e}function kc(e){e=e.previousSibling;for(var t=0;e;){if(e.nodeType===8){var n=e.data;if(n==="$"||n==="$!"||n==="$?"){if(t===0)return e;t--}else n==="/$"&&t++}e=e.previousSibling}return null}var pr=Math.random().toString(36).slice(2),_t="__reactFiber$"+pr,to="__reactProps$"+pr,Dt="__reactContainer$"+pr,Ua="__reactEvents$"+pr,ry="__reactListeners$"+pr,oy="__reactHandles$"+pr;function mn(e){var t=e[_t];if(t)return t;for(var n=e.parentNode;n;){if(t=n[Dt]||n[_t]){if(n=t.alternate,t.child!==null||n!==null&&n.child!==null)for(e=kc(e);e!==null;){if(n=e[_t])return n;e=kc(e)}return t}e=n,n=e.parentNode}return null}function wo(e){return e=e[_t]||e[Dt],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function Bn(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(_(33))}function Qi(e){return e[to]||null}var Va=[],Un=-1;function cn(e){return{current:e}}function Y(e){0>Un||(e.current=Va[Un],Va[Un]=null,Un--)}function K(e,t){Un++,Va[Un]=e.current,e.current=t}var an={},Pe=cn(an),Ie=cn(!1),xn=an;function or(e,t){var n=e.type.contextTypes;if(!n)return an;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var o={},i;for(i in n)o[i]=t[i];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=o),o}function De(e){return e=e.childContextTypes,e!=null}function _i(){Y(Ie),Y(Pe)}function _c(e,t,n){if(Pe.current!==an)throw Error(_(168));K(Pe,t),K(Ie,n)}function Qd(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var o in r)if(!(o in t))throw Error(_(108,Bh(e)||"Unknown",o));return te({},n,r)}function Ci(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||an,xn=Pe.current,K(Pe,e),K(Ie,Ie.current),!0}function Cc(e,t,n){var r=e.stateNode;if(!r)throw Error(_(169));n?(e=Qd(e,t,xn),r.__reactInternalMemoizedMergedChildContext=e,Y(Ie),Y(Pe),K(Pe,e)):Y(Ie),K(Ie,n)}var Mt=null,Gi=!1,oa=!1;function Gd(e){Mt===null?Mt=[e]:Mt.push(e)}function iy(e){Gi=!0,Gd(e)}function fn(){if(!oa&&Mt!==null){oa=!0;var e=0,t=B;try{var n=Mt;for(B=1;e<n.length;e++){var r=n[e];do r=r(!0);while(r!==null)}Mt=null,Gi=!1}catch(o){throw Mt!==null&&(Mt=Mt.slice(e+1)),wd(Ns,fn),o}finally{B=t,oa=!1}}return null}var Vn=[],Hn=0,Ei=null,Pi=0,Je=[],et=0,Sn=null,bt=1,zt="";function dn(e,t){Vn[Hn++]=Pi,Vn[Hn++]=Ei,Ei=e,Pi=t}function Yd(e,t,n){Je[et++]=bt,Je[et++]=zt,Je[et++]=Sn,Sn=e;var r=bt;e=zt;var o=32-ht(r)-1;r&=~(1<<o),n+=1;var i=32-ht(t)+o;if(30<i){var l=o-o%5;i=(r&(1<<l)-1).toString(32),r>>=l,o-=l,bt=1<<32-ht(t)+o|n<<o|r,zt=i+e}else bt=1<<i|n<<o|r,zt=e}function Us(e){e.return!==null&&(dn(e,1),Yd(e,1,0))}function Vs(e){for(;e===Ei;)Ei=Vn[--Hn],Vn[Hn]=null,Pi=Vn[--Hn],Vn[Hn]=null;for(;e===Sn;)Sn=Je[--et],Je[et]=null,zt=Je[--et],Je[et]=null,bt=Je[--et],Je[et]=null}var He=null,Ve=null,q=!1,mt=null;function Xd(e,t){var n=nt(5,null,null,0);n.elementType="DELETED",n.stateNode=t,n.return=e,t=e.deletions,t===null?(e.deletions=[n],e.flags|=16):t.push(n)}function Ec(e,t){switch(e.tag){case 5:var n=e.type;return t=t.nodeType!==1||n.toLowerCase()!==t.nodeName.toLowerCase()?null:t,t!==null?(e.stateNode=t,He=e,Ve=en(t.firstChild),!0):!1;case 6:return t=e.pendingProps===""||t.nodeType!==3?null:t,t!==null?(e.stateNode=t,He=e,Ve=null,!0):!1;case 13:return t=t.nodeType!==8?null:t,t!==null?(n=Sn!==null?{id:bt,overflow:zt}:null,e.memoizedState={dehydrated:t,treeContext:n,retryLane:1073741824},n=nt(18,null,null,0),n.stateNode=t,n.return=e,e.child=n,He=e,Ve=null,!0):!1;default:return!1}}function Ha(e){return(e.mode&1)!==0&&(e.flags&128)===0}function Ka(e){if(q){var t=Ve;if(t){var n=t;if(!Ec(e,t)){if(Ha(e))throw Error(_(418));t=en(n.nextSibling);var r=He;t&&Ec(e,t)?Xd(r,n):(e.flags=e.flags&-4097|2,q=!1,He=e)}}else{if(Ha(e))throw Error(_(418));e.flags=e.flags&-4097|2,q=!1,He=e}}}function Pc(e){for(e=e.return;e!==null&&e.
Line truncated
|
||||
Error generating stack: `+i.message+`
|
||||
`+i.stack}return{value:e,source:t,stack:o,digest:null}}function ua(e,t,n){return{value:e,source:null,stack:n??null,digest:t??null}}function Xa(e,t){try{console.error(t.value)}catch(n){setTimeout(function(){throw n})}}var my=typeof WeakMap=="function"?WeakMap:Map;function _p(e,t,n){n=Lt(-1,n),n.tag=3,n.payload={element:null};var r=t.value;return n.callback=function(){Ni||(Ni=!0,ls=r),Xa(e,t)},n}function Cp(e,t,n){n=Lt(-1,n),n.tag=3;var r=e.type.getDerivedStateFromError;if(typeof r=="function"){var o=t.value;n.payload=function(){return r(o)},n.callback=function(){Xa(e,t)}}var i=e.stateNode;return i!==null&&typeof i.componentDidCatch=="function"&&(n.callback=function(){Xa(e,t),typeof r!="function"&&(nn===null?nn=new Set([this]):nn.add(this));var l=t.stack;this.componentDidCatch(t.value,{componentStack:l!==null?l:""})}),n}function Nc(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new my;var o=new Set;r.set(t,o)}else o=r.get(t),o===void 0&&(o=new Set,r.set(t,o));o.has(n)||(o.add(n),e=Ty.bind(null,e,t,n),t.then(e,e))}function Lc(e){do{var t;if((t=e.tag===13)&&(t=e.memoizedState,t=t!==null?t.dehydrated!==null:!0),t)return e;e=e.return}while(e!==null);return null}function Ic(e,t,n,r,o){return e.mode&1?(e.flags|=65536,e.lanes=o,e):(e===t?e.flags|=65536:(e.flags|=128,n.flags|=131072,n.flags&=-52805,n.tag===1&&(n.alternate===null?n.tag=17:(t=Lt(-1,1),t.tag=2,tn(n,t,1))),n.lanes|=1),e)}var hy=Wt.ReactCurrentOwner,Le=!1;function Re(e,t,n,r){t.child=e===null?Jd(t,null,n,r):lr(t,e.child,n,r)}function Dc(e,t,n,r,o){n=n.render;var i=t.ref;return Jn(t,o),r=tu(e,t,n,r,i,o),n=nu(),e!==null&&!Le?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~o,jt(e,t,o)):(q&&n&&Us(t),t.flags|=1,Re(e,t,r,o),t.child)}function Ac(e,t,n,r,o){if(e===null){var i=n.type;return typeof i=="function"&&!du(i)&&i.defaultProps===void 0&&n.compare===null&&n.defaultProps===void 0?(t.tag=15,t.type=i,Ep(e,t,i,r,o)):(e=si(n.type,null,r,t,t.mode,o),e.ref=t.ref,e.return=t,t.child=e)}if(i=e.child,!(e.lanes&o)){var l=i.memoizedProps;if(n=n.compare,n=n!==null?n:Zr,n(l,r)&&e.ref===t.ref)return jt(e,t,o)}return t.flags|=1,e=on(i,r),e.ref=t.ref,e.return=t,t.child=e}function Ep(e,t,n,r,o){if(e!==null){var i=e.memoizedProps;if(Zr(i,r)&&e.ref===t.ref)if(Le=!1,t.pendingProps=r=i,(e.lanes&o)!==0)e.flags&131072&&(Le=!0);else return t.lanes=e.lanes,jt(e,t,o)}return qa(e,t,n,r,o)}function Pp(e,t,n){var r=t.pendingProps,o=r.children,i=e!==null?e.memoizedState:null;if(r.mode==="hidden")if(!(t.mode&1))t.memoizedState={baseLanes:0,cachePool:null,transitions:null},K(Gn,Be),Be|=n;else{if(!(n&1073741824))return e=i!==null?i.baseLanes|n:n,t.lanes=t.childLanes=1073741824,t.memoizedState={baseLanes:e,cachePool:null,transitions:null},t.updateQueue=null,K(Gn,Be),Be|=e,null;t.memoizedState={baseLanes:0,cachePool:null,transitions:null},r=i!==null?i.baseLanes:n,K(Gn,Be),Be|=r}else i!==null?(r=i.baseLanes|n,t.memoizedState=null):r=n,K(Gn,Be),Be|=r;return Re(e,t,o,n),t.child}function $p(e,t){var n=t.ref;(e===null&&n!==null||e!==null&&e.ref!==n)&&(t.flags|=512,t.flags|=2097152)}function qa(e,t,n,r,o){var i=De(n)?xn:Pe.current;return i=or(t,i),Jn(t,o),n=tu(e,t,n,r,i,o),r=nu(),e!==null&&!Le?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~o,jt(e,t,o)):(q&&r&&Us(t),t.flags|=1,Re(e,t,n,o),t.child)}function jc(e,t,n,r,o){if(De(n)){var i=!0;Ci(t)}else i=!1;if(Jn(t,o),t.stateNode===null)ii(e,t),kp(t,n,r),Ya(t,n,r,o),r=!0;else if(e===null){var l=t.stateNode,a=t.memoizedProps;l.props=a;var s=l.context,u=n.contextType;typeof u=="object"&&u!==null?u=ot(u):(u=De(n)?xn:Pe.current,u=or(t,u));var p=n.getDerivedStateFromProps,m=typeof p=="function"||typeof l.getSnapshotBeforeUpdate=="function";m||typeof l.UNSAFE_componentWillReceiveProps!="function"&&typeof l.componentWillReceiveProps!="function"||(a!==r||s!==u)&&zc(t,l,r,u),Ht=!1;var d=t.memoizedState;l.state=d,Ri(t,r,l,o),s=t.memoizedState,a!==r||d!==s||Ie.current||Ht?(typeof p=="function"&&(Ga(t,n,p,r),s=t.memoizedState),(a=Ht||bc(t,n,a,r,d,s,u))?(m||typeof l.UNSAFE_componentWillMount!="function"&&typeof l.componentWillMount!="function"||(typeof l.componentWillMount=="function"&&l.componentWillMount(),typeof l.UNSAFE_componentWillMount=="function"&&l.UNSAFE_componentWillMount()),typeof l.componentDidMount=="function"&&(t.flags|=4194308)):(typeof l.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=r,t.memoizedState=s),l.props=r,l.state=s,l.context=u,r=a):(typeof l.componentDidMount=="function"&&(t.flags|=4194308),r=!1)}else{l=t.stateNode,tp(e,t),a=t.memoizedProps,u=t.type===t.elementType?a:dt(t.type,a),l.props=u,m=t.pendingProps,d=l.context,s=n.contextType,typeof s=="object"&&s!==null?s=ot(s):(s=De(n)?xn:Pe.current,s=or(t,s));var v=n.getDerivedStateFromProps;(p=typeof v=="function"||typeof l.getSnapshotBeforeUpdate=="function")||typeof l.UNSAFE_componentWillReceiveProps!="function"&&typeof l.componentWillReceiveProps!="function"||(a!==m||d!==s)&&zc(t,l,r,s),Ht=!1,d=t.memoizedState,l.state=d,Ri(t,r,l,o);var g=t.memoizedState;a
Line truncated
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var ye=typeof Symbol=="function"&&Symbol.for,Su=ye?Symbol.for("react.element"):60103,ku=ye?Symbol.for("react.portal"):60106,al=ye?Symbol.for("react.fragment"):60107,sl=ye?Symbol.for("react.strict_mode"):60108,ul=ye?Symbol.for("react.profiler"):60114,cl=ye?Symbol.for("react.provider"):60109,fl=ye?Symbol.for("react.context"):60110,_u=ye?Symbol.for("react.async_mode"):60111,dl=ye?Symbol.for("react.concurrent_mode"):60111,pl=ye?Symbol.for("react.forward_ref"):60112,ml=ye?Symbol.for("react.suspense"):60113,$g=ye?Symbol.for("react.suspense_list"):60120,hl=ye?Symbol.for("react.memo"):60115,yl=ye?Symbol.for("react.lazy"):60116,Tg=ye?Symbol.for("react.block"):60121,Rg=ye?Symbol.for("react.fundamental"):60117,Og=ye?Symbol.for("react.responder"):60118,Mg=ye?Symbol.for("react.scope"):60119;function Xe(e){if(typeof e=="object"&&e!==null){var t=e.$$typeof;switch(t){case Su:switch(e=e.type,e){case _u:case dl:case al:case ul:case sl:case ml:return e;default:switch(e=e&&e.$$typeof,e){case fl:case pl:case yl:case hl:case cl:return e;default:return t}}case ku:return t}}}function sm(e){return Xe(e)===dl}U.AsyncMode=_u;U.ConcurrentMode=dl;U.ContextConsumer=fl;U.ContextProvider=cl;U.Element=Su;U.ForwardRef=pl;U.Fragment=al;U.Lazy=yl;U.Memo=hl;U.Portal=ku;U.Profiler=ul;U.StrictMode=sl;U.Suspense=ml;U.isAsyncMode=function(e){return sm(e)||Xe(e)===_u};U.isConcurrentMode=sm;U.isContextConsumer=function(e){return Xe(e)===fl};U.isContextProvider=function(e){return Xe(e)===cl};U.isElement=function(e){return typeof e=="object"&&e!==null&&e.$$typeof===Su};U.isForwardRef=function(e){return Xe(e)===pl};U.isFragment=function(e){return Xe(e)===al};U.isLazy=function(e){return Xe(e)===yl};U.isMemo=function(e){return Xe(e)===hl};U.isPortal=function(e){return Xe(e)===ku};U.isProfiler=function(e){return Xe(e)===ul};U.isStrictMode=function(e){return Xe(e)===sl};U.isSuspense=function(e){return Xe(e)===ml};U.isValidElementType=function(e){return typeof e=="string"||typeof e=="function"||e===al||e===dl||e===ul||e===sl||e===ml||e===$g||typeof e=="object"&&e!==null&&(e.$$typeof===yl||e.$$typeof===hl||e.$$typeof===cl||e.$$typeof===fl||e.$$typeof===pl||e.$$typeof===Rg||e.$$typeof===Og||e.$$typeof===Mg||e.$$typeof===Tg)};U.typeOf=Xe;am.exports=U;var bg=am.exports,um=bg,zg={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},Ng={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},cm={};cm[um.ForwardRef]=zg;cm[um.Memo]=Ng;var Lg=!0;function Ig(e,t,n){var r="";return n.split(" ").forEach(function(o){e[o]!==void 0?t.push(e[o]+";"):r+=o+" "}),r}var fm=function(t,n,r){var o=t.key+"-"+n.name;(r===!1||Lg===!1)&&t.registered[o]===void 0&&(t.registered[o]=n.styles)},dm=function(t,n,r){fm(t,n,r);var o=t.key+"-"+n.name;if(t.inserted[n.name]===void 0){var i=n;do t.insert(n===i?"."+o:"",i,t.sheet,!0),i=i.next;while(i!==void 0)}};function Dg(e){for(var t=0,n,r=0,o=e.length;o>=4;++r,o-=4)n=e.charCodeAt(r)&255|(e.charCodeAt(++r)&255)<<8|(e.charCodeAt(++r)&255)<<16|(e.charCodeAt(++r)&255)<<24,n=(n&65535)*1540483477+((n>>>16)*59797<<16),n^=n>>>24,t=(n&65535)*1540483477+((n>>>16)*59797<<16)^(t&65535)*1540483477+((t>>>16)*59797<<16);switch(o){case 3:t^=(e.charCodeAt(r+2)&255)<<16;case 2:t^=(e.charCodeAt(r+1)&255)<<8;case 1:t^=e.charCodeAt(r)&255,t=(t&65535)*1540483477+((t>>>16)*59797<<16)}return t^=t>>>13,t=(t&65535)*1540483477+((t>>>16)*59797<<16),((t^t>>>15)>>>0).toString(36)}var Ag={animationIterationCount:1,aspectRatio:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1},jg=!1,Fg=/[A-Z]|^ms/g,Wg=/_EMO_([^_]+?)_([^]*?)_EMO_/g,pm=function(t){return t.charCodeAt(1)===45},nf=function(t){return t!=null&&typeof t!="boolean"},pa=Zp(function(e){return pm(e)?e:e.replace(Fg,"-$&").toLowerCase()}),rf=function(t,n){switch(t){case"animation":case"animationName":if(typeof n=="string")return n.replace(Wg,function(r,o,i){return kt={name:o,styles:i,next:kt},o})}return Ag[t]!==1&&!pm(t)&&typeof n=="number"&&n!==0?n+"px":n},Bg="Component selectors can only be used in conjunction with @emotion/babel-plugin, the swc Emotion plugin, or another Emotion-aware compiler transform.";function po(e,t,n){if(n==null)return"";var r=n;if(r.__emotion_styles!==void 0)return r;switch(typeof n){case"boolean":return"";case"object":{var o=n;if(o.anim===1)return kt={name:o.name,styles:o.styles,next:kt},o.name;var i=n;if(i.styles!==void 0){var l=i.next;if(l!==void 0)for(;l!==void 0;)kt={name:l.name,styles:l.styles,nextLine truncated
|
||||
* @mui/styled-engine v5.16.6
|
||||
*
|
||||
* @license MIT
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/function vm(e,t){return ds(e,t)}const n1=(e,t)=>{Array.isArray(e.__emotion_styles)&&(e.__emotion_styles=t(e.__emotion_styles))},r1=Object.freeze(Object.defineProperty({__proto__:null,GlobalStyles:gm,StyledEngineProvider:e1,ThemeContext:gl,css:ko,default:vm,internal_processStyles:n1,keyframes:$n},Symbol.toStringTag,{value:"Module"}));function Gt(e){if(typeof e!="object"||e===null)return!1;const t=Object.getPrototypeOf(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)}function wm(e){if(!Gt(e))return e;const t={};return Object.keys(e).forEach(n=>{t[n]=wm(e[n])}),t}function $t(e,t,n={clone:!0}){const r=n.clone?R({},e):e;return Gt(e)&&Gt(t)&&Object.keys(t).forEach(o=>{Gt(t[o])&&Object.prototype.hasOwnProperty.call(e,o)&&Gt(e[o])?r[o]=$t(e[o],t[o],n):n.clone?r[o]=Gt(t[o])?wm(t[o]):t[o]:r[o]=t[o]}),r}const o1=Object.freeze(Object.defineProperty({__proto__:null,default:$t,isPlainObject:Gt},Symbol.toStringTag,{value:"Module"})),i1=["values","unit","step"],l1=e=>{const t=Object.keys(e).map(n=>({key:n,val:e[n]}))||[];return t.sort((n,r)=>n.val-r.val),t.reduce((n,r)=>R({},n,{[r.key]:r.val}),{})};function xm(e){const{values:t={xs:0,sm:600,md:900,lg:1200,xl:1536},unit:n="px",step:r=5}=e,o=Fe(e,i1),i=l1(t),l=Object.keys(i);function a(d){return`@media (min-width:${typeof t[d]=="number"?t[d]:d}${n})`}function s(d){return`@media (max-width:${(typeof t[d]=="number"?t[d]:d)-r/100}${n})`}function u(d,v){const g=l.indexOf(v);return`@media (min-width:${typeof t[d]=="number"?t[d]:d}${n}) and (max-width:${(g!==-1&&typeof t[l[g]]=="number"?t[l[g]]:v)-r/100}${n})`}function p(d){return l.indexOf(d)+1<l.length?u(d,l[l.indexOf(d)+1]):a(d)}function m(d){const v=l.indexOf(d);return v===0?a(l[1]):v===l.length-1?s(l[v]):u(d,l[l.indexOf(d)+1]).replace("@media","@media not all and")}return R({keys:l,values:i,up:a,down:s,between:u,only:p,not:m,unit:n},o)}const a1={borderRadius:4},s1=a1;function Vr(e,t){return t?$t(e,t,{clone:!1}):e}const Eu={xs:0,sm:600,md:900,lg:1200,xl:1536},uf={keys:["xs","sm","md","lg","xl"],up:e=>`@media (min-width:${Eu[e]}px)`};function Ft(e,t,n){const r=e.theme||{};if(Array.isArray(t)){const i=r.breakpoints||uf;return t.reduce((l,a,s)=>(l[i.up(i.keys[s])]=n(t[s]),l),{})}if(typeof t=="object"){const i=r.breakpoints||uf;return Object.keys(t).reduce((l,a)=>{if(Object.keys(i.values||Eu).indexOf(a)!==-1){const s=i.up(a);l[s]=n(t[a],a)}else{const s=a;l[s]=t[s]}return l},{})}return n(t)}function u1(e={}){var t;return((t=e.keys)==null?void 0:t.reduce((r,o)=>{const i=e.up(o);return r[i]={},r},{}))||{}}function c1(e,t){return e.reduce((n,r)=>{const o=n[r];return(!o||Object.keys(o).length===0)&&delete n[r],n},t)}function G(e){if(typeof e!="string")throw new Error(uo(7));return e.charAt(0).toUpperCase()+e.slice(1)}const f1=Object.freeze(Object.defineProperty({__proto__:null,default:G},Symbol.toStringTag,{value:"Module"}));function vl(e,t,n=!0){if(!t||typeof t!="string")return null;if(e&&e.vars&&n){const r=`vars.${t}`.split(".").reduce((o,i)=>o&&o[i]?o[i]:null,e);if(r!=null)return r}return t.split(".").reduce((r,o)=>r&&r[o]!=null?r[o]:null,e)}function ji(e,t,n,r=n){let o;return typeof e=="function"?o=e(n):Array.isArray(e)?o=e[n]||r:o=vl(e,n)||r,t&&(o=t(o,r,e)),o}function le(e){const{prop:t,cssProperty:n=e.prop,themeKey:r,transform:o}=e,i=l=>{if(l[t]==null)return null;const a=l[t],s=l.theme,u=vl(s,r)||{};return Ft(l,a,m=>{let d=ji(u,o,m);return m===d&&typeof m=="string"&&(d=ji(u,o,`${t}${m==="default"?"":G(m)}`,m)),n===!1?d:{[n]:d}})};return i.propTypes={},i.filterProps=[t],i}function d1(e){const t={};return n=>(t[n]===void 0&&(t[n]=e(n)),t[n])}const p1={m:"margin",p:"padding"},m1={t:"Top",r:"Right",b:"Bottom",l:"Left",x:["Left","Right"],y:["Top","Bottom"]},cf={marginX:"mx",marginY:"my",paddingX:"px",paddingY:"py"},h1=d1(e=>{if(e.length>2)if(cf[e])e=cf[e];else return[e];const[t,n]=e.split(""),r=p1[t],o=m1[n]||"";return Array.isArray(o)?o.map(i=>r+i):[r+o]}),Pu=["m","mt","mr","mb","ml","mx","my","margin","marginTop","marginRight","marginBottom","marginLeft","marginX","marginY","marginInline","marginInlineStart","marginInlineEnd","marginBlock","marginBlockStart","marginBlockEnd"],$u=["p","pt","pr","pb","pl","px","py","padding","paddingTop","paddingRight","paddingBottom","paddingLeft","paddingX","paddingY","paddingInline","paddingInlineStart","paddingInlineEnd","paddingBlock","paddingBlockStart","paddingBlockEnd"];[...Pu,...$u];function _o(e,t,n,r){var o;const i=(o=vl(e,t,!1))!=null?o:n;return typeof i=="number"?l=>typeof l=="string"?l:i*l:Array.isArray(i)?l=>typeof l=="string"?l:i[l]:typeof i=="function"?i:()=>{}}function Sm(e){return _o(e,"spacing",8)}function Co(e,t){if(typeof t=="string"||t==null)return t;const n=Math.abs(t),r=e(n);return t>=0?r:typeof r=="number"?-r:`-${r}`}function y1(e,t){return n=>e.reduce((r,o)=>(r[o]=Co(t,n),r),{})}function g1(e,t,n,r){if(t.indexOf(n)===-1)return null;const o=h1(n),i=y1(o,r),l=e[n];return Ft(e,l,i)}function km(e,Line truncated
|
||||
* @license React
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var Mu=Symbol.for("react.element"),bu=Symbol.for("react.portal"),El=Symbol.for("react.fragment"),Pl=Symbol.for("react.strict_mode"),$l=Symbol.for("react.profiler"),Tl=Symbol.for("react.provider"),Rl=Symbol.for("react.context"),dv=Symbol.for("react.server_context"),Ol=Symbol.for("react.forward_ref"),Ml=Symbol.for("react.suspense"),bl=Symbol.for("react.suspense_list"),zl=Symbol.for("react.memo"),Nl=Symbol.for("react.lazy"),pv=Symbol.for("react.offscreen"),Om;Om=Symbol.for("react.module.reference");function st(e){if(typeof e=="object"&&e!==null){var t=e.$$typeof;switch(t){case Mu:switch(e=e.type,e){case El:case $l:case Pl:case Ml:case bl:return e;default:switch(e=e&&e.$$typeof,e){case dv:case Rl:case Ol:case Nl:case zl:case Tl:return e;default:return t}}case bu:return t}}}V.ContextConsumer=Rl;V.ContextProvider=Tl;V.Element=Mu;V.ForwardRef=Ol;V.Fragment=El;V.Lazy=Nl;V.Memo=zl;V.Portal=bu;V.Profiler=$l;V.StrictMode=Pl;V.Suspense=Ml;V.SuspenseList=bl;V.isAsyncMode=function(){return!1};V.isConcurrentMode=function(){return!1};V.isContextConsumer=function(e){return st(e)===Rl};V.isContextProvider=function(e){return st(e)===Tl};V.isElement=function(e){return typeof e=="object"&&e!==null&&e.$$typeof===Mu};V.isForwardRef=function(e){return st(e)===Ol};V.isFragment=function(e){return st(e)===El};V.isLazy=function(e){return st(e)===Nl};V.isMemo=function(e){return st(e)===zl};V.isPortal=function(e){return st(e)===bu};V.isProfiler=function(e){return st(e)===$l};V.isStrictMode=function(e){return st(e)===Pl};V.isSuspense=function(e){return st(e)===Ml};V.isSuspenseList=function(e){return st(e)===bl};V.isValidElementType=function(e){return typeof e=="string"||typeof e=="function"||e===El||e===$l||e===Pl||e===Ml||e===bl||e===pv||typeof e=="object"&&e!==null&&(e.$$typeof===Nl||e.$$typeof===zl||e.$$typeof===Tl||e.$$typeof===Rl||e.$$typeof===Ol||e.$$typeof===Om||e.getModuleId!==void 0)};V.typeOf=st;Rm.exports=V;var df=Rm.exports;const mv=/^\s*function(?:\s|\s*\/\*.*\*\/\s*)+([^(\s/]*)\s*/;function Mm(e){const t=`${e}`.match(mv);return t&&t[1]||""}function bm(e,t=""){return e.displayName||e.name||Mm(e)||t}function pf(e,t,n){const r=bm(t);return e.displayName||(r!==""?`${n}(${r})`:n)}function hv(e){if(e!=null){if(typeof e=="string")return e;if(typeof e=="function")return bm(e,"Component");if(typeof e=="object")switch(e.$$typeof){case df.ForwardRef:return pf(e,e.render,"ForwardRef");case df.Memo:return pf(e,e.type,"memo");default:return}}}const yv=Object.freeze(Object.defineProperty({__proto__:null,default:hv,getFunctionName:Mm},Symbol.toStringTag,{value:"Module"}));function Fi(e,t){const n=R({},t);return Object.keys(e).forEach(r=>{if(r.toString().match(/^(components|slots)$/))n[r]=R({},e[r],n[r]);else if(r.toString().match(/^(componentsProps|slotProps)$/)){const o=e[r]||{},i=t[r];n[r]={},!i||!Object.keys(i)?n[r]=o:!o||!Object.keys(o)?n[r]=i:(n[r]=R({},i),Object.keys(o).forEach(l=>{n[r][l]=Fi(o[l],i[l])}))}else n[r]===void 0&&(n[r]=e[r])}),n}const gv=typeof window<"u"?C.useLayoutEffect:C.useEffect,vv=gv;function wv(e,t=Number.MIN_SAFE_INTEGER,n=Number.MAX_SAFE_INTEGER){return Math.max(t,Math.min(e,n))}const xv=Object.freeze(Object.defineProperty({__proto__:null,default:wv},Symbol.toStringTag,{value:"Module"}));function Sv(e,t){typeof e=="function"?e(t):e&&(e.current=t)}function Qo(e){const t=C.useRef(e);return vv(()=>{t.current=e}),C.useRef((...n)=>(0,t.current)(...n)).current}function mf(...e){return C.useMemo(()=>e.every(t=>t==null)?null:t=>{e.forEach(n=>{Sv(n,t)})},e)}const hf={};function kv(e,t){const n=C.useRef(hf);return n.current===hf&&(n.current=e(t)),n}const _v=[];function Cv(e){C.useEffect(e,_v)}class Ll{constructor(){this.currentId=null,this.clear=()=>{this.currentId!==null&&(clearTimeout(this.currentId),this.currentId=null)},this.disposeEffect=()=>this.clear}static create(){return new Ll}start(t,n){this.clear(),this.currentId=setTimeout(()=>{this.currentId=null,n()},t)}}function Ev(){const e=kv(Ll.create).current;return Cv(e.disposeEffect),e}let Il=!0,ms=!1;const Pv=new Ll,$v={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function Tv(e){const{type:t,tagName:n}=e;return!!(n==="INPUT"&&$v[t]&&!e.readOnly||n==="TEXTAREA"&&!e.readOnly||e.isContentEditable)}function Rv(e){e.metaKey||e.altKey||e.ctrlKey||(Il=!0)}function ma(){Il=!1}function Ov(){this.visibilityState==="hidden"&&ms&&(Il=!0)}function Mv(e){e.addEventListener("keydown",Rv,!0),e.addEventListener("mousedown",ma,!0),e.addEventListener("pointerdown",ma,!0),e.addEventListener("touchstart",ma,!0),e.addEventListener("visibilitychange",Ov,!0)}function bv(e){const{target:t}=e;try{return t.matches(":focus-visible")}catch{}return Il||Tv(t)}function zv(){const e=C.useCallback(o=>{o!=null&&Mv(o.ownerDocument)},[]),t=C.useRef(!1);function n(){return t.current?(ms=!0,Pv.start(100,()=>{ms=!1}),t.current=!1,!0):!1}function r(o){return bv(o)?(t.current=!0,!0):!1}return{isFocusVisibleRef:t,onFocusLine truncated
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.3;
|
||||
}
|
||||
`)),sx=$n($f||($f=Al`
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
`)),ux=$n(Tf||(Tf=Al`
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
`)),cx=vt("span",{name:"MuiTouchRipple",slot:"Root"})({overflow:"hidden",pointerEvents:"none",position:"absolute",zIndex:0,top:0,right:0,bottom:0,left:0,borderRadius:"inherit"}),fx=vt(rx,{name:"MuiTouchRipple",slot:"Ripple"})(Rf||(Rf=Al`
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
|
||||
&.${0} {
|
||||
opacity: 0.3;
|
||||
transform: scale(1);
|
||||
animation-name: ${0};
|
||||
animation-duration: ${0}ms;
|
||||
animation-timing-function: ${0};
|
||||
}
|
||||
|
||||
&.${0} {
|
||||
animation-duration: ${0}ms;
|
||||
}
|
||||
|
||||
& .${0} {
|
||||
opacity: 1;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
& .${0} {
|
||||
opacity: 0;
|
||||
animation-name: ${0};
|
||||
animation-duration: ${0}ms;
|
||||
animation-timing-function: ${0};
|
||||
}
|
||||
|
||||
& .${0} {
|
||||
position: absolute;
|
||||
/* @noflip */
|
||||
left: 0px;
|
||||
top: 0;
|
||||
animation-name: ${0};
|
||||
animation-duration: 2500ms;
|
||||
animation-timing-function: ${0};
|
||||
animation-iteration-count: infinite;
|
||||
animation-delay: 200ms;
|
||||
}
|
||||
`),Ze.rippleVisible,ax,gs,({theme:e})=>e.transitions.easing.easeInOut,Ze.ripplePulsate,({theme:e})=>e.transitions.duration.shorter,Ze.child,Ze.childLeaving,sx,gs,({theme:e})=>e.transitions.easing.easeInOut,Ze.childPulsate,ux,({theme:e})=>e.transitions.easing.easeInOut),dx=C.forwardRef(function(t,n){const r=vr({props:t,name:"MuiTouchRipple"}),{center:o=!1,classes:i={},className:l}=r,a=Fe(r,ix),[s,u]=C.useState([]),p=C.useRef(0),m=C.useRef(null);C.useEffect(()=>{m.current&&(m.current(),m.current=null)},[s]);const d=C.useRef(!1),v=Ev(),g=C.useRef(null),y=C.useRef(null),$=C.useCallback(w=>{const{pulsate:x,rippleX:k,rippleY:S,rippleSize:P,cb:I}=w;u(O=>[...O,b.jsx(fx,{classes:{ripple:Oe(i.ripple,Ze.ripple),rippleVisible:Oe(i.rippleVisible,Ze.rippleVisible),ripplePulsate:Oe(i.ripplePulsate,Ze.ripplePulsate),child:Oe(i.child,Ze.child),childLeaving:Oe(i.childLeaving,Ze.childLeaving),childPulsate:Oe(i.childPulsate,Ze.childPulsate)},timeout:gs,pulsate:x,rippleX:k,rippleY:S,rippleSize:P},p.current)]),p.current+=1,m.current=I},[i]),f=C.useCallback((w={},x={},k=()=>{})=>{const{pulsate:S=!1,center:P=o||x.pulsate,fakeElement:I=!1}=x;if((w==null?void 0:w.type)==="mousedown"&&d.current){d.current=!1;return}(w==null?void 0:w.type)==="touchstart"&&(d.current=!0);const O=I?null:y.current,W=O?O.getBoundingClientRect():{width:0,height:0,left:0,top:0};let H,se,Se;if(P||w===void 0||w.clientX===0&&w.clientY===0||!w.clientX&&!w.touches)H=Math.round(W.width/2),se=Math.round(W.height/2);else{const{clientX:ut,clientY:$e}=w.touches&&w.touches.length>0?w.touches[0]:w;H=Math.round(ut-W.left),se=Math.round($e-W.top)}if(P)Se=Math.sqrt((2*W.width**2+W.height**2)/3),Se%2===0&&(Se+=1);else{const ut=Math.max(Math.abs((O?O.clientWidth:0)-H),H)*2+2,$e=Math.max(Math.abs((O?O.clientHeight:0)-se),se)*2+2;Se=Math.sqrt(ut**2+$e**2)}w!=null&&w.touches?g.current===null&&(g.current=()=>{$({pulsate:S,rippleX:H,rippleY:se,rippleSize:Se,cb:k})},v.start(lx,()=>{g.current&&(g.current(),g.current=null)})):$({pulsate:S,rippleX:H,rippleY:se,rippleSize:Se,cb:k})},[o,$,v]),c=C.useCallback(()=>{f({},{pulsate:!0})},[f]),h=C.useCallback((w,x)=>{if(v.clear(),(w==null?void 0:w.type)==="touchend"&&g.current){g.current(),g.current=null,v.start(0,()=>{h(w,x)});return}g.current=null,u(k=>k.length>0?k.slice(1):k),m.current=x},[v]);return C.useImperativeHandle(n,()=>({pulsate:c,start:f,stop:h}),[c,f,h]),b.jsx(cx,R({className:Oe(Ze.root,i.root,l),ref:y},a,{children:b.jsx(nx,{component:null,exit:!0,children:s})}))}),px=dx;function mx(e){return Po("MuiButtonBase",e)}const hx=hr("MuiButtonBase",["root","disabled","focusVisible"]),yx=hx,gx=["action","centerRipple","children","className","component","disabled","disableRipple","disableTouchRipple","focusRipple","focusVisibleClassName","LinkComponent","onBlur","onClick","onContextMenu","onDragLeave","onFocus","onFocusVisible","onKeyDown","onKeyUp","onMouseDown","onMouseLeave","onMouseUp","onTouchEnd","onTouchMove","onTouchStart","tabIndex","TouchRippleProps","touchRippleRef","type"],vx=e=>{const{disabled:t,focusVisible:n,focusVisibleClassName:r,classes:o}=e,l=Dl({root:["root",t&&"disabled",n&&"focusVisible"]},mx,o);return n&&r&&(l.root+=` ${r}`),l},wx=vt("button",{name:"MuiButtonBase",slot:"Root",overridesResolver:(e,t)=>t.root})({display:"inline-flex",alignItems:"center",justifyContent:"center",position:"relative",boxSizing:"border-box",WebkitTapHighlightColor:"transparent",backgroundColor:"transparent",outline:0,border:0,margin:0,borderRadius:0,padding:0,cursor:"pointer",userSelect:"none",verticalAlign:"middle",MozAppearance:"none",WebkitAppearance:"none",textDecoration:"none",color:"inherit","&::-moz-focus-inner":{borderStyle:"none"},[`&.${yx.disabled}`]:{pointerEvents:"none",cursor:"default"},"@media print":{colorAdjust:"exact"}}),xx=C.forwardRef(function(t,n){const r=vr({props:t,name:"MuiButtonBase"}),{action:o,centerRipple:i=!1,children:l,className:a,component:s="button",disabled:u=!1,disableRipple:p=!1,disableTouchRipple:m=!1,focusRipple:d=!1,LinkComponent:v="a",onBlur:g,onClick:y,onContextMenu:$,onDragLeave:f,onFocus:c,onFocusVisible:h,onKeyDown:w,onKeyUp:x,onMouseDown:k,onMouseLeave:S,onMouseUp:P,onTouchEnd:I,onTouchMove:O,onTouchStart:W,tabIndex:H=0,TouchRippleProps:se,touchRippleRef:Se,type:ut}=r,$e=Fe(r,gx),Tt=C.useRef(null),E=C.useRef(null),z=mf(E,Se),{isFocusVisibleRef:N,onFocus:Z,onBlur:ue,ref:Tn}=zv(),[Te,Bt]=C.useState(!1);u&&Te&&Bt(!1),C.useImperativeHandle(o,()=>({focusVisible:()=>{Bt(!0),Tt.current.focus()}}),[]);const[ct,Rn]=C.useState(!1);C.useEffect(()=>{Rn(!0)},[]);const Xm=ct&&!p&&!u;C.useEffect(()=>{Te&&d&&!p&&ct&&E.current.pulsate()},[p,d,Te,ct]);function Rt(L,Wu,fh=m){return Qo(Bu=>(Wu&&Wu(Bu),!fh&&E.current&&E.current[L](Bu),!0))}const qm=Rt("start",k),Zm=Rt("stop",$),Jm=Rt("stop",f),eh=Rt("stop",P),th=Rt("stop",L=>{Te&&L.preventDefault(),S&&S(L)}),nh=Rt("start",W),rh=Rt("stop",I),oh=Rt("stop",O),ih=Rt("stop",L=>{ue(L),N.current===!1&&Bt(!1),g&&g(L)},!1),lh=Qo(L=>{Tt.current||(Tt.current=L.currentTarget)
Line truncated
|
||||
0% {
|
||||
left: -35%;
|
||||
right: 100%;
|
||||
}
|
||||
|
||||
60% {
|
||||
left: 100%;
|
||||
right: -90%;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 100%;
|
||||
right: -90%;
|
||||
}
|
||||
`)),eS=$n(bf||(bf=wr`
|
||||
0% {
|
||||
left: -200%;
|
||||
right: 100%;
|
||||
}
|
||||
|
||||
60% {
|
||||
left: 107%;
|
||||
right: -8%;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 107%;
|
||||
right: -8%;
|
||||
}
|
||||
`)),tS=$n(zf||(zf=wr`
|
||||
0% {
|
||||
opacity: 1;
|
||||
background-position: 0 -23px;
|
||||
}
|
||||
|
||||
60% {
|
||||
opacity: 0;
|
||||
background-position: 0 -23px;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
background-position: -200px -23px;
|
||||
}
|
||||
`)),nS=e=>{const{classes:t,variant:n,color:r}=e,o={root:["root",`color${G(r)}`,n],dashed:["dashed",`dashedColor${G(r)}`],bar1:["bar",`barColor${G(r)}`,(n==="indeterminate"||n==="query")&&"bar1Indeterminate",n==="determinate"&&"bar1Determinate",n==="buffer"&&"bar1Buffer"],bar2:["bar",n!=="buffer"&&`barColor${G(r)}`,n==="buffer"&&`color${G(r)}`,(n==="indeterminate"||n==="query")&&"bar2Indeterminate",n==="buffer"&&"bar2Buffer"]};return Dl(o,qx,t)},ju=(e,t)=>t==="inherit"?"currentColor":e.vars?e.vars.palette.LinearProgress[`${t}Bg`]:e.palette.mode==="light"?Dm(e.palette[t].main,.62):Im(e.palette[t].main,.5),rS=vt("span",{name:"MuiLinearProgress",slot:"Root",overridesResolver:(e,t)=>{const{ownerState:n}=e;return[t.root,t[`color${G(n.color)}`],t[n.variant]]}})(({ownerState:e,theme:t})=>R({position:"relative",overflow:"hidden",display:"block",height:4,zIndex:0,"@media print":{colorAdjust:"exact"},backgroundColor:ju(t,e.color)},e.color==="inherit"&&e.variant!=="buffer"&&{backgroundColor:"none","&::before":{content:'""',position:"absolute",left:0,top:0,right:0,bottom:0,backgroundColor:"currentColor",opacity:.3}},e.variant==="buffer"&&{backgroundColor:"transparent"},e.variant==="query"&&{transform:"rotate(180deg)"})),oS=vt("span",{name:"MuiLinearProgress",slot:"Dashed",overridesResolver:(e,t)=>{const{ownerState:n}=e;return[t.dashed,t[`dashedColor${G(n.color)}`]]}})(({ownerState:e,theme:t})=>{const n=ju(t,e.color);return R({position:"absolute",marginTop:0,height:"100%",width:"100%"},e.color==="inherit"&&{opacity:.3},{backgroundImage:`radial-gradient(${n} 0%, ${n} 16%, transparent 42%)`,backgroundSize:"10px 10px",backgroundPosition:"0 -23px"})},ko(Nf||(Nf=wr`
|
||||
animation: ${0} 3s infinite linear;
|
||||
`),tS)),iS=vt("span",{name:"MuiLinearProgress",slot:"Bar1",overridesResolver:(e,t)=>{const{ownerState:n}=e;return[t.bar,t[`barColor${G(n.color)}`],(n.variant==="indeterminate"||n.variant==="query")&&t.bar1Indeterminate,n.variant==="determinate"&&t.bar1Determinate,n.variant==="buffer"&&t.bar1Buffer]}})(({ownerState:e,theme:t})=>R({width:"100%",position:"absolute",left:0,bottom:0,top:0,transition:"transform 0.2s linear",transformOrigin:"left",backgroundColor:e.color==="inherit"?"currentColor":(t.vars||t).palette[e.color].main},e.variant==="determinate"&&{transition:`transform .${vs}s linear`},e.variant==="buffer"&&{zIndex:1,transition:`transform .${vs}s linear`}),({ownerState:e})=>(e.variant==="indeterminate"||e.variant==="query")&&ko(Lf||(Lf=wr`
|
||||
width: auto;
|
||||
animation: ${0} 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
|
||||
`),Jx)),lS=vt("span",{name:"MuiLinearProgress",slot:"Bar2",overridesResolver:(e,t)=>{const{ownerState:n}=e;return[t.bar,t[`barColor${G(n.color)}`],(n.variant==="indeterminate"||n.variant==="query")&&t.bar2Indeterminate,n.variant==="buffer"&&t.bar2Buffer]}})(({ownerState:e,theme:t})=>R({width:"100%",position:"absolute",left:0,bottom:0,top:0,transition:"transform 0.2s linear",transformOrigin:"left"},e.variant!=="buffer"&&{backgroundColor:e.color==="inherit"?"currentColor":(t.vars||t).palette[e.color].main},e.color==="inherit"&&{opacity:.3},e.variant==="buffer"&&{backgroundColor:ju(t,e.color),transition:`transform .${vs}s linear`}),({ownerState:e})=>(e.variant==="indeterminate"||e.variant==="query")&&ko(If||(If=wr`
|
||||
width: auto;
|
||||
animation: ${0} 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) 1.15s infinite;
|
||||
`),eS)),aS=C.forwardRef(function(t,n){const r=vr({props:t,name:"MuiLinearProgress"}),{className:o,color:i="primary",value:l,valueBuffer:a,variant:s="indeterminate"}=r,u=Fe(r,Zx),p=R({},r,{color:i,variant:s}),m=nS(p),d=Lv(),v={},g={bar1:{},bar2:{}};if((s==="determinate"||s==="buffer")&&l!==void 0){v["aria-valuenow"]=Math.round(l),v["aria-valuemin"]=0,v["aria-valuemax"]=100;let y=l-100;d&&(y=-y),g.bar1.transform=`translateX(${y}%)`}if(s==="buffer"&&a!==void 0){let y=(a||0)-100;d&&(y=-y),g.bar2.transform=`translateX(${y}%)`}return b.jsxs(rS,R({className:Oe(m.root,o),ownerState:p,role:"progressbar"},v,{ref:n},u,{children:[s==="buffer"?b.jsx(oS,{className:m.dashed,ownerState:p}):null,b.jsx(iS,{className:m.bar1,ownerState:p,style:g.bar1}),s==="determinate"?null:b.jsx(lS,{className:m.bar2,ownerState:p,style:g.bar2})]}))}),sS=aS;function uS(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function ws(e,t=!1){const n=uS(),r=`_${n}`;return Object.defineProperty(window,r,{value:o=>(t&&Reflect.deleteProperty(window,r),e==null?void 0:e(o)),writable:!1,configurable:!0}),n}async function cS(e,t={}){return new Promise((n,r)=>{const o=ws(l=>{n(l),Reflect.deleteProperty(window,`_${i}`)},!0),i=ws(l=>{r(l),Reflect.deleteProperty(window,`_${o}`)},!0);window.__TAURI_IPC__({cmd:e,callback:o,error:i,...t})})}async function M(e){return cS("tauri",e)}async function Gm(e,t){return M({__tauriModule:"Event",message:{cmd:"unlisten",event:e,eventId:t}})}async function fS(e,t,n){await M({__tauriModule:"Event",message:{cmd:"emit",event:e,windowLabel:t,payload:n}})}async function Ym(e,t,n){return M({__tauriModule:"Event",message:{cmd:"listen",event:e,windowLabel:t,handler:ws(n)}}).then(r=>async()=>Gm(e,r))}async function dS(e,t,n){return Ym(e,t,r=>{n(r),Gm(e,r.id).catch(()=>{})})}var We;(function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_CREATED="tauri://window-created",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_FILE_DROP="tauri://file-drop",e.WINDOW_FILE_DROP_HOVER="tauri://file-drop-hover",e.WINDOW_FILE_DROP_CANCELLED="tauri://file-drop-cancelled",e.MENU="tauri://menu",e.CHECK_UPDATE="tauri://update",e.UPDATE_AVAILABLE="tauri://update-available",e.INSTALL_UPDATE="tauri://update-install",e.STATUS_UPDATE="tauri://update-status",e.DOWNLOAD_PROGRESS="tauri://update-download-progress"})(We||(We={}));class pS{constructor(t,n){this.type="Logical",this.width=t,this.height=n}}class xs{constructor(t,n){this.type="Physical",this.width=t,this.height=n}toLogical(t){return new pS(this.width/t,this.height/t)}}class mS{constructor(t,n){this.type="Logical",this.x=t,this.y=n}}class Ss{constructor(t,n){this.type="Physical",this.x=t,this.y=n}toLogical(t){return new mS(this.x/t,this.y/t)}}var ks;(function(e){e[e.Critical=1]="Critical",e[e.Informational=2]="Informational"})(ks||(ks={}));function Df(){return window.__TAURI_METADATA__.__windows.map(e=>new mo(e.label,{skip:!0}))}const Af=["tauri://created","tauri://error"];class hS{constructor(t){this.label=t,this.listeners=Object.create(null)}async listen(t,n){return this._handleTauriEvent(t,n)?Promise.resolve(()=>{const r=this.listeners[t];r.splice(r.indexOf(n),1)}):Ym(t,this.label,n)}async once(t,n){return this._handleTauriEvent(t,n)?Promise.resolve(()=>{const r=this.listeners[t];r.splice(r.indexOf(n),1)}):dS(t,this.label,n)}async emit(t,n){if(Af.includes(t)){for(const r of this.listeners[t]||[])r({event:t,id:-1,windowLabel:this.label,payload:n});return Promise.resolve()}return fS(t,this.label,n)}_handleTauriEvent(t,n){return Af.includes(t)?(t in this.listeners?this.listeners[t].push(n):this.listeners[t]=[n],!0):!1}}class yS extends hS{async scaleFactor(){return M({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"scaleFactor"}}}})}async innerPosition(){return M({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"innerPosition"}}}}).then(({x:t,y:n})=>new Ss(t,n))}async outerPosition(){return M({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"outerPosition"}}}}).then(({x:t,y:n})=>new Ss(t,n))}async innerSize(){return M({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"innerSize"}}}}).then(({width:t,height:n})=>new xs(t,n))}async outerSize(){return M({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"outerSize"}}}}).then(({width:t,height:n})=>new xs(t,n))}async isFullscreen(){return M({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isFullscreen"}}}})}async isMinimized(){return M({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isMinimized"}}}})}async isMaximized(){return M({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cm
Line truncated
|
||||
Note that this is not an issue if running this frontend on a browser instead of a Tauri window.`),ho=new mo("main",{skip:!0}));function vS(e){return new Ss(e.x,e.y)}function wS(e){return new xs(e.width,e.height)}const xS="/assets/icon-674efcbe.svg";function SS(){const[e,t]=C.useState(null);return C.useEffect(()=>{const n=ho.listen("app://update-progress",r=>{t(r.payload)});return()=>{n.then(r=>r())}},[]),e}function kS(){const[e,t]=C.useState(!1);C.useEffect(()=>{const r=ho.listen("app://update-error",()=>{t(!0)});return()=>{r.then(o=>o())}},[]);const n=()=>{t(!1),ho.emit("app://update")};return b.jsxs(b.Fragment,{children:[b.jsx(Xx,{}),b.jsx(Nt,{sx:{position:"absolute",inset:0},display:"flex",alignItems:"center",px:2,"data-tauri-drag-region":!0,children:b.jsxs(Nt,{display:"flex",alignItems:"center",flex:"1","data-tauri-drag-region":!0,children:[b.jsx(Nt,{component:"img",src:xS,alt:"logo",sx:{width:"4rem",height:"4rem"},"data-tauri-drag-region":!0}),b.jsx(Nt,{flex:1,ml:2,children:e?b.jsx(CS,{onRetry:n}):b.jsx(_S,{})})]})})]})}function _S(){const e=SS();return b.jsxs(b.Fragment,{children:[b.jsx(Au,{variant:"h1",fontSize:"1rem","data-tauri-drag-region":!0,children:"Updating the GUI components..."}),b.jsx(Nt,{mt:1,children:b.jsx(ES,{value:e})})]})}function CS({onRetry:e}){return b.jsxs(b.Fragment,{children:[b.jsx(Au,{variant:"h1",fontSize:"1rem","data-tauri-drag-region":!0,children:"Failed to update the GUI components."}),b.jsx(Nt,{mt:1,"data-tauri-drag-region":!0,children:b.jsx(Kx,{variant:"contained",color:"primary",size:"small",onClick:e,sx:{textTransform:"none"},children:"Retry"})})]})}function ES(e){const{value:t}=e;return b.jsxs(Nt,{sx:{display:"flex",alignItems:"center"},children:[b.jsx(Nt,{flex:"1",children:b.jsx(sS,{variant:t===null?"indeterminate":"determinate",value:t??0,sx:{py:1.2,".MuiLinearProgress-bar":{transition:"none"}}})}),t!==null&&b.jsx(Nt,{sx:{minWidth:35,textAlign:"right",ml:1},children:b.jsx(Au,{variant:"body2",color:"text.secondary",children:`${Math.round(t)}%`})})]})}const PS=qp(document.getElementById("root"));PS.render(b.jsx(kS,{}));
|
||||
Vendored
+2
-3
@@ -5,8 +5,8 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>GlobalProtect</title>
|
||||
<script type="module" crossorigin src="/assets/main-c159dd55.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-11e7064a.css">
|
||||
<script type="module" crossorigin src="/assets/main-CQPVXkdn.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-B3YRsHQ2.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
@@ -16,6 +16,5 @@
|
||||
document.documentElement.style.fontSize = 16 / ratio + "px";
|
||||
</script>
|
||||
<div id="root" data-tauri-drag-region></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -9,29 +9,29 @@
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.13.0",
|
||||
"@emotion/styled": "^11.13.0",
|
||||
"@mui/icons-material": "^5.16.7",
|
||||
"@mui/material": "^5.16.7",
|
||||
"@tauri-apps/api": "^1.6.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^6.3.0",
|
||||
"@mui/material": "^6.3.0",
|
||||
"@tauri-apps/api": "^2.1.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.6.0",
|
||||
"@types/node": "^20.14.15",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"eslint": "^8.57.0",
|
||||
"@tauri-apps/cli": "^2.1.0",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/react": "^19.0.2",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
||||
"@typescript-eslint/parser": "^8.18.2",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-react": "^7.35.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"prettier": "3.1.0",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^4.5.3"
|
||||
"eslint-plugin-react": "^7.37.3",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"prettier": "3.4.2",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.5"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.7"
|
||||
"packageManager": "pnpm@9.15.1"
|
||||
}
|
||||
Generated
+2790
-2265
File diff suppressed because it is too large.
Load diff
@@ -1,16 +1,18 @@
|
||||
[package]
|
||||
name = "gpgui-helper"
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.5", features = [] }
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
gpapi = { path = "../../../crates/gpapi", features = ["tauri"] }
|
||||
tauri = { workspace = true, features = ["window-start-dragging"] }
|
||||
tauri.workspace = true
|
||||
|
||||
tokio.workspace = true
|
||||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:window:allow-start-dragging",
|
||||
"core:event:allow-listen",
|
||||
"core:event:allow-emit",
|
||||
"core:event:allow-unlisten"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpapi::utils::window::WindowExt;
|
||||
use log::info;
|
||||
use tauri::Manager;
|
||||
use tauri::{Listener, Manager};
|
||||
|
||||
use crate::updater::{GuiUpdater, Installer, ProgressNotifier};
|
||||
|
||||
@@ -25,15 +24,15 @@ impl App {
|
||||
|
||||
tauri::Builder::default()
|
||||
.setup(move |app| {
|
||||
let win = app.get_window("main").expect("no main window");
|
||||
win.hide_menu();
|
||||
let win = app.get_webview_window("main").expect("no main window");
|
||||
let _ = win.hide_menu();
|
||||
|
||||
let notifier = ProgressNotifier::new(win.clone());
|
||||
let installer = Installer::new(api_key);
|
||||
let updater = Arc::new(GuiUpdater::new(gui_version, notifier, installer));
|
||||
|
||||
let win_clone = win.clone();
|
||||
app.listen_global("app://update-done", move |_event| {
|
||||
app.listen_any("app://update-done", move |_event| {
|
||||
info!("Update done");
|
||||
let _ = win_clone.close();
|
||||
});
|
||||
@@ -41,12 +40,15 @@ impl App {
|
||||
// Listen for the update event
|
||||
win.listen("app://update", move |_event| {
|
||||
let updater = Arc::clone(&updater);
|
||||
if updater.is_in_progress() {
|
||||
info!("Update already in progress");
|
||||
updater.notify_progress();
|
||||
return;
|
||||
}
|
||||
|
||||
tokio::spawn(async move { updater.update().await });
|
||||
});
|
||||
|
||||
// Update the GUI on startup
|
||||
win.trigger("app://update", None);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())?;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use clap::Parser;
|
||||
use gpapi::utils::base64;
|
||||
use gpapi::utils::{base64, env_utils};
|
||||
use log::{info, LevelFilter};
|
||||
|
||||
use crate::app::App;
|
||||
@@ -22,6 +22,8 @@ impl Cli {
|
||||
let api_key = self.read_api_key()?;
|
||||
let app = App::new(api_key, &self.gui_version);
|
||||
|
||||
env_utils::patch_gui_runtime_env(false);
|
||||
|
||||
app.run()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use gpapi::{
|
||||
service::request::UpdateGuiRequest,
|
||||
utils::{checksum::verify_checksum, crypto::Crypto, endpoint::http_endpoint},
|
||||
};
|
||||
use log::{info, warn};
|
||||
use tauri::{Manager, Window};
|
||||
use tauri::{Emitter, WebviewWindow};
|
||||
|
||||
use crate::downloader::{ChecksumFetcher, FileDownloader};
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
const SNAPSHOT: &str = match option_env!("SNAPSHOT") {
|
||||
Some(val) => val,
|
||||
None => "false"
|
||||
Some(val) => val,
|
||||
None => "false",
|
||||
};
|
||||
|
||||
pub struct ProgressNotifier {
|
||||
win: Window,
|
||||
win: WebviewWindow,
|
||||
}
|
||||
|
||||
impl ProgressNotifier {
|
||||
pub fn new(win: Window) -> Self {
|
||||
pub fn new(win: WebviewWindow) -> Self {
|
||||
Self { win }
|
||||
}
|
||||
|
||||
fn notify(&self, progress: Option<f64>) {
|
||||
let _ = self.win.emit_all("app://update-progress", progress);
|
||||
let _ = self.win.emit("app://update-progress", progress);
|
||||
}
|
||||
|
||||
fn notify_error(&self) {
|
||||
let _ = self.win.emit_all("app://update-error", ());
|
||||
let _ = self.win.emit("app://update-error", ());
|
||||
}
|
||||
|
||||
fn notify_done(&self) {
|
||||
let _ = self.win.emit_and_trigger("app://update-done", ());
|
||||
let _ = self.win.emit("app://update-done", ());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@ pub struct GuiUpdater {
|
||||
version: String,
|
||||
notifier: Arc<ProgressNotifier>,
|
||||
installer: Installer,
|
||||
in_progress: RwLock<bool>,
|
||||
progress: Arc<RwLock<Option<f64>>>,
|
||||
}
|
||||
|
||||
impl GuiUpdater {
|
||||
@@ -80,6 +82,8 @@ impl GuiUpdater {
|
||||
version,
|
||||
notifier: Arc::new(notifier),
|
||||
installer,
|
||||
in_progress: Default::default(),
|
||||
progress: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,15 +116,23 @@ impl GuiUpdater {
|
||||
let cf = ChecksumFetcher::new(&checksum_url);
|
||||
let notifier = Arc::clone(&self.notifier);
|
||||
|
||||
dl.on_progress(move |progress| notifier.notify(progress));
|
||||
let progress_ref = Arc::clone(&self.progress);
|
||||
dl.on_progress(move |progress| {
|
||||
// Save progress to shared state so that it can be notified to the UI when needed
|
||||
if let Ok(mut guard) = progress_ref.try_write() {
|
||||
*guard = progress;
|
||||
}
|
||||
notifier.notify(progress);
|
||||
});
|
||||
|
||||
self.set_in_progress(true);
|
||||
let res = tokio::try_join!(dl.download(), cf.fetch());
|
||||
|
||||
let (file, checksum) = match res {
|
||||
Ok((file, checksum)) => (file, checksum),
|
||||
Err(err) => {
|
||||
warn!("Download error: {}", err);
|
||||
self.notifier.notify_error();
|
||||
self.notify_error();
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -130,7 +142,7 @@ impl GuiUpdater {
|
||||
|
||||
if let Err(err) = verify_checksum(&file_path, &checksum) {
|
||||
warn!("Checksum error: {}", err);
|
||||
self.notifier.notify_error();
|
||||
self.notify_error();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -138,10 +150,48 @@ impl GuiUpdater {
|
||||
|
||||
if let Err(err) = self.installer.install(&file_path, &checksum).await {
|
||||
warn!("Install error: {}", err);
|
||||
self.notifier.notify_error();
|
||||
self.notify_error();
|
||||
} else {
|
||||
info!("Install success");
|
||||
self.notifier.notify_done();
|
||||
self.notify_done();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_in_progress(&self) -> bool {
|
||||
if let Ok(guard) = self.in_progress.try_read() {
|
||||
*guard
|
||||
} else {
|
||||
info!("Failed to acquire in_progress lock");
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn set_in_progress(&self, in_progress: bool) {
|
||||
if let Ok(mut guard) = self.in_progress.try_write() {
|
||||
*guard = in_progress;
|
||||
} else {
|
||||
info!("Failed to acquire in_progress lock");
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_error(&self) {
|
||||
self.set_in_progress(false);
|
||||
self.notifier.notify_error();
|
||||
}
|
||||
|
||||
fn notify_done(&self) {
|
||||
self.set_in_progress(false);
|
||||
self.notifier.notify_done();
|
||||
}
|
||||
|
||||
pub fn notify_progress(&self) {
|
||||
let progress = if let Ok(guard) = self.progress.try_read() {
|
||||
*guard
|
||||
} else {
|
||||
info!("Failed to acquire progress lock");
|
||||
None
|
||||
};
|
||||
|
||||
self.notifier.notify(progress);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,15 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
"beforeBuildCommand": "pnpm build",
|
||||
"devPath": "http://localhost:1421",
|
||||
"distDir": "../dist",
|
||||
"withGlobalTauri": false
|
||||
"devUrl": "http://localhost:1421",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"package": {
|
||||
"productName": "gpgui-helper"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": false,
|
||||
"window": {
|
||||
"all": false,
|
||||
"startDragging": true
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": false,
|
||||
"targets": "deb",
|
||||
"identifier": "com.yuezk.gpgui-helper",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/[email protected]",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
},
|
||||
"identifier": "com.yuezk.gpgui-helper",
|
||||
"productName": "gpgui-helper",
|
||||
"app": {
|
||||
"withGlobalTauri": false,
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
@@ -48,5 +28,16 @@
|
||||
"decorations": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"bundle": {
|
||||
"active": false,
|
||||
"targets": "deb",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/[email protected]",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Box, Button, CssBaseline, LinearProgress, Typography } from "@mui/material";
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import logo from "../../assets/icon.svg";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
const appWindow = getCurrentWindow();
|
||||
|
||||
function useUpdateProgress() {
|
||||
const [progress, setProgress] = useState<number | null>(null);
|
||||
|
||||
@@ -25,6 +27,8 @@ export default function App() {
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
appWindow.emit("app://update");
|
||||
|
||||
const unlisten = appWindow.listen("app://update-error", () => {
|
||||
setError(true);
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ use clap::Parser;
|
||||
use gpapi::{
|
||||
process::gui_launcher::GuiLauncher,
|
||||
service::{request::WsRequest, vpn_state::VpnState},
|
||||
utils::{crypto::generate_key, env_file, lock_file::LockFile, redact::Redaction, shutdown_signal},
|
||||
utils::{crypto::generate_key, env_utils, lock_file::LockFile, redact::Redaction, shutdown_signal},
|
||||
GP_SERVICE_LOCK_FILE,
|
||||
};
|
||||
use log::{info, warn, LevelFilter};
|
||||
@@ -63,7 +63,7 @@ impl Cli {
|
||||
if no_gui {
|
||||
info!("GUI is disabled");
|
||||
} else {
|
||||
let envs = self.env_file.as_ref().map(env_file::load_env_vars).transpose()?;
|
||||
let envs = self.env_file.as_ref().map(env_utils::load_env_vars).transpose()?;
|
||||
|
||||
let minimized = self.minimized;
|
||||
|
||||
|
||||
@@ -39,10 +39,6 @@ pub(crate) async fn active_gui(State(ctx): State<Arc<WsServerContext>>) -> impl
|
||||
ctx.send_event(WsEvent::ActiveGui).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn auth_data(State(ctx): State<Arc<WsServerContext>>, body: String) -> impl IntoResponse {
|
||||
ctx.send_event(WsEvent::AuthData(body)).await;
|
||||
}
|
||||
|
||||
pub async fn update_gui(State(ctx): State<Arc<WsServerContext>>, body: Bytes) -> Result<(), StatusCode> {
|
||||
let payload = match ctx.decrypt::<UpdateGuiRequest>(body.to_vec()) {
|
||||
Ok(payload) => payload,
|
||||
|
||||
@@ -11,7 +11,6 @@ pub(crate) fn routes(ctx: Arc<WsServerContext>) -> Router {
|
||||
Router::new()
|
||||
.route("/health", get(handlers::health))
|
||||
.route("/active-gui", post(handlers::active_gui))
|
||||
.route("/auth-data", post(handlers::auth_data))
|
||||
.route("/update-gui", post(handlers::update_gui))
|
||||
.route("/ws", get(handlers::ws_handler))
|
||||
.with_state(ctx)
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## 2.4.0 - 2024-12-26
|
||||
|
||||
- Upgrade to Tauri 2.0
|
||||
- Support Ubuntu 22.04 and later
|
||||
|
||||
## 2.3.9 - 2024-11-02
|
||||
|
||||
- Enhance the OpenSSL compatibility mode (fix [#437](https://github.com/yuezk/GlobalProtect-openconnect/issues/437))
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
[package]
|
||||
name = "auth"
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
homepage.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
gpapi = { path = "../gpapi" }
|
||||
|
||||
# Shared dependencies
|
||||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
tokio.workspace = true
|
||||
|
||||
# Browser auth dependencies
|
||||
webbrowser = { version = "1", optional = true }
|
||||
open = { version = "5", optional = true }
|
||||
which = { workspace = true, optional = true }
|
||||
tiny_http = { version = "0.12", optional = true }
|
||||
uuid = { version = "1", optional = true, features = ["v4"] }
|
||||
|
||||
# Webview auth dependencies
|
||||
tauri = { workspace = true, optional = true }
|
||||
regex = { workspace = true, optional = true }
|
||||
tokio-util = { workspace = true, optional = true }
|
||||
html-escape = { version = "0.2.13", optional = true }
|
||||
|
||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||
webkit2gtk = { version = "2", optional = true }
|
||||
|
||||
[features]
|
||||
browser-auth = [
|
||||
"dep:webbrowser",
|
||||
"dep:open",
|
||||
"dep:which",
|
||||
"dep:tiny_http",
|
||||
"dep:uuid",
|
||||
]
|
||||
webview-auth = [
|
||||
"dep:tauri",
|
||||
"dep:regex",
|
||||
"dep:tokio-util",
|
||||
"dep:html-escape",
|
||||
"dep:webkit2gtk",
|
||||
"gpapi/tauri",
|
||||
]
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,60 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::bail;
|
||||
use gpapi::{
|
||||
gp_params::GpParams,
|
||||
portal::{prelogin, Prelogin},
|
||||
};
|
||||
|
||||
pub struct Authenticator<'a> {
|
||||
server: &'a str,
|
||||
auth_request: Option<&'a str>,
|
||||
pub(crate) gp_params: &'a GpParams,
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
pub(crate) clean: bool,
|
||||
#[cfg(feature = "webview-auth")]
|
||||
pub(crate) is_retrying: tokio::sync::RwLock<bool>,
|
||||
}
|
||||
|
||||
impl<'a> Authenticator<'a> {
|
||||
pub fn new(server: &'a str, gp_params: &'a GpParams) -> Self {
|
||||
Self {
|
||||
server,
|
||||
gp_params,
|
||||
auth_request: None,
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
clean: false,
|
||||
#[cfg(feature = "webview-auth")]
|
||||
is_retrying: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_auth_request(mut self, auth_request: &'a str) -> Self {
|
||||
if !auth_request.is_empty() {
|
||||
self.auth_request = Some(auth_request);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) async fn initial_auth_request(&self) -> anyhow::Result<Cow<'a, str>> {
|
||||
if let Some(auth_request) = self.auth_request {
|
||||
return Ok(Cow::Borrowed(auth_request));
|
||||
}
|
||||
|
||||
let auth_request = self.portal_prelogin().await?;
|
||||
Ok(Cow::Owned(auth_request))
|
||||
}
|
||||
|
||||
pub(crate) async fn portal_prelogin(&self) -> anyhow::Result<String> {
|
||||
auth_prelogin(self.server, self.gp_params).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn auth_prelogin(server: &str, gp_params: &GpParams) -> anyhow::Result<String> {
|
||||
match prelogin(server, gp_params).await? {
|
||||
Prelogin::Saml(prelogin) => Ok(prelogin.saml_request().to_string()),
|
||||
Prelogin::Standard(_) => bail!("Received non-SAML prelogin response"),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mod auth_server;
|
||||
mod browser_auth_ext;
|
||||
mod browser_auth_impl;
|
||||
|
||||
pub use browser_auth_ext::BrowserAuthenticator;
|
||||
@@ -0,0 +1,57 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use log::info;
|
||||
use tiny_http::{Header, Response, Server};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(super) struct AuthServer {
|
||||
server: Server,
|
||||
auth_id: String,
|
||||
}
|
||||
|
||||
impl AuthServer {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let server = Server::http("127.0.0.1:0").map_err(|err| anyhow::anyhow!(err))?;
|
||||
let auth_id = Uuid::new_v4().to_string();
|
||||
|
||||
Ok(Self { server, auth_id })
|
||||
}
|
||||
|
||||
pub fn auth_url(&self) -> String {
|
||||
format!("http://{}/{}", self.server.server_addr(), self.auth_id)
|
||||
}
|
||||
|
||||
pub fn serve_request(&self, auth_request: &str) {
|
||||
info!("auth server started at: {}", self.auth_url());
|
||||
|
||||
for req in self.server.incoming_requests() {
|
||||
info!("received request, method: {}, url: {}", req.method(), req.url());
|
||||
|
||||
if req.url() != format!("/{}", self.auth_id) {
|
||||
let forbidden = Response::from_string("forbidden").with_status_code(403);
|
||||
let _ = req.respond(forbidden);
|
||||
} else {
|
||||
let auth_response = build_auth_response(auth_request);
|
||||
if let Err(err) = req.respond(auth_response) {
|
||||
info!("failed to respond to request: {}", err);
|
||||
} else {
|
||||
info!("stop the auth server");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_auth_response(auth_request: &str) -> Response<Cursor<Vec<u8>>> {
|
||||
if auth_request.starts_with("http") {
|
||||
let header = format!("location: {}", auth_request);
|
||||
let header: Header = header.parse().unwrap();
|
||||
Response::from_string("redirect")
|
||||
.with_status_code(302)
|
||||
.with_header(header)
|
||||
} else {
|
||||
let content_type: Header = "content-type: text/html".parse().unwrap();
|
||||
Response::from_string(auth_request).with_header(content_type)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use std::future::Future;
|
||||
|
||||
use gpapi::auth::SamlAuthData;
|
||||
|
||||
use crate::{browser_auth::browser_auth_impl::BrowserAuthenticatorImpl, Authenticator};
|
||||
|
||||
pub trait BrowserAuthenticator {
|
||||
fn browser_authenticate(&self, browser: Option<&str>) -> impl Future<Output = anyhow::Result<SamlAuthData>> + Send;
|
||||
}
|
||||
|
||||
impl BrowserAuthenticator for Authenticator<'_> {
|
||||
async fn browser_authenticate(&self, browser: Option<&str>) -> anyhow::Result<SamlAuthData> {
|
||||
let auth_request = self.initial_auth_request().await?;
|
||||
let browser_auth = if let Some(browser) = browser {
|
||||
BrowserAuthenticatorImpl::new_with_browser(&auth_request, browser)
|
||||
} else {
|
||||
BrowserAuthenticatorImpl::new(&auth_request)
|
||||
};
|
||||
|
||||
browser_auth.authenticate().await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
use std::{env::temp_dir, fs, os::unix::fs::PermissionsExt};
|
||||
|
||||
use gpapi::{auth::SamlAuthData, GP_CALLBACK_PORT_FILENAME};
|
||||
use log::info;
|
||||
use tokio::{io::AsyncReadExt, net::TcpListener};
|
||||
|
||||
use super::auth_server::AuthServer;
|
||||
|
||||
pub(super) struct BrowserAuthenticatorImpl<'a> {
|
||||
auth_request: &'a str,
|
||||
browser: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl BrowserAuthenticatorImpl<'_> {
|
||||
pub fn new(auth_request: &str) -> BrowserAuthenticatorImpl {
|
||||
BrowserAuthenticatorImpl {
|
||||
auth_request,
|
||||
browser: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_browser<'a>(auth_request: &'a str, browser: &'a str) -> BrowserAuthenticatorImpl<'a> {
|
||||
let browser = browser.trim();
|
||||
BrowserAuthenticatorImpl {
|
||||
auth_request,
|
||||
browser: if browser.is_empty() || browser == "default" {
|
||||
None
|
||||
} else {
|
||||
Some(browser)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn authenticate(&self) -> anyhow::Result<SamlAuthData> {
|
||||
let auth_server = AuthServer::new()?;
|
||||
let auth_url = auth_server.auth_url();
|
||||
|
||||
let auth_request = self.auth_request.to_string();
|
||||
tokio::spawn(async move {
|
||||
auth_server.serve_request(&auth_request);
|
||||
});
|
||||
|
||||
if let Some(browser) = self.browser {
|
||||
let app = find_browser_path(browser);
|
||||
|
||||
info!("Launching browser: {}", app);
|
||||
open::with_detached(auth_url, app)?;
|
||||
} else {
|
||||
info!("Launching the default browser...");
|
||||
webbrowser::open(&auth_url)?;
|
||||
}
|
||||
|
||||
info!("Please continue the authentication process in the default browser");
|
||||
wait_auth_data().await
|
||||
}
|
||||
}
|
||||
|
||||
fn find_browser_path(browser: &str) -> String {
|
||||
if browser == "chrome" {
|
||||
which::which("google-chrome-stable")
|
||||
.or_else(|_| which::which("google-chrome"))
|
||||
.or_else(|_| which::which("chromium"))
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|_| browser.to_string())
|
||||
} else {
|
||||
browser.into()
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_auth_data() -> anyhow::Result<SamlAuthData> {
|
||||
// Start a local server to receive the browser authentication data
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let port = listener.local_addr()?.port();
|
||||
let port_file = temp_dir().join(GP_CALLBACK_PORT_FILENAME);
|
||||
|
||||
// Write the port to a file
|
||||
fs::write(&port_file, port.to_string())?;
|
||||
fs::set_permissions(&port_file, fs::Permissions::from_mode(0o600))?;
|
||||
|
||||
// Remove the previous log file
|
||||
let callback_log = temp_dir().join("gpcallback.log");
|
||||
let _ = fs::remove_file(&callback_log);
|
||||
|
||||
info!("Listening authentication data on port {}", port);
|
||||
info!(
|
||||
"If it hangs, please check the logs at `{}` for more information",
|
||||
callback_log.display()
|
||||
);
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
|
||||
info!("Received the browser authentication data from the socket");
|
||||
let mut data = String::new();
|
||||
socket.read_to_string(&mut data).await?;
|
||||
|
||||
// Remove the port file
|
||||
fs::remove_file(&port_file)?;
|
||||
|
||||
let auth_data = SamlAuthData::from_gpcallback(&data)?;
|
||||
Ok(auth_data)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
mod authenticator;
|
||||
pub use authenticator::auth_prelogin;
|
||||
pub use authenticator::Authenticator;
|
||||
|
||||
#[cfg(feature = "browser-auth")]
|
||||
mod browser_auth;
|
||||
#[cfg(feature = "browser-auth")]
|
||||
pub use browser_auth::BrowserAuthenticator;
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
mod webview_auth;
|
||||
#[cfg(feature = "webview-auth")]
|
||||
pub use webview_auth::WebviewAuthenticator;
|
||||
@@ -0,0 +1,9 @@
|
||||
mod auth_messenger;
|
||||
mod auth_response;
|
||||
mod auth_settings;
|
||||
mod webview_auth_ext;
|
||||
|
||||
#[cfg_attr(not(target_os = "macos"), path = "webview_auth/unix.rs")]
|
||||
mod platform_impl;
|
||||
|
||||
pub use webview_auth_ext::WebviewAuthenticator;
|
||||
@@ -0,0 +1,108 @@
|
||||
use anyhow::bail;
|
||||
use gpapi::auth::SamlAuthData;
|
||||
use log::{error, info};
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub enum AuthError {
|
||||
/// Failed to load page due to TLS error
|
||||
TlsError,
|
||||
/// 1. Found auth data in headers/body but it's invalid
|
||||
/// 2. Loaded an empty page, failed to load page. etc.
|
||||
Invalid,
|
||||
/// No auth data found in headers/body
|
||||
NotFound,
|
||||
}
|
||||
|
||||
pub type AuthResult = anyhow::Result<SamlAuthData, AuthError>;
|
||||
|
||||
pub enum AuthEvent {
|
||||
Data(SamlAuthData),
|
||||
Error(AuthError),
|
||||
RaiseWindow,
|
||||
Close,
|
||||
}
|
||||
|
||||
pub struct AuthMessenger {
|
||||
tx: mpsc::UnboundedSender<AuthEvent>,
|
||||
rx: RwLock<mpsc::UnboundedReceiver<AuthEvent>>,
|
||||
raise_window_cancel_token: RwLock<Option<CancellationToken>>,
|
||||
}
|
||||
|
||||
impl AuthMessenger {
|
||||
pub fn new() -> Self {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
Self {
|
||||
tx,
|
||||
rx: RwLock::new(rx),
|
||||
raise_window_cancel_token: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn subscribe(&self) -> anyhow::Result<AuthEvent> {
|
||||
let mut rx = self.rx.write().await;
|
||||
if let Some(event) = rx.recv().await {
|
||||
return Ok(event);
|
||||
}
|
||||
bail!("Failed to receive auth event");
|
||||
}
|
||||
|
||||
pub fn send_auth_event(&self, event: AuthEvent) {
|
||||
if let Err(event) = self.tx.send(event) {
|
||||
error!("Failed to send auth event: {}", event);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_auth_result(&self, result: AuthResult) {
|
||||
match result {
|
||||
Ok(data) => self.send_auth_data(data),
|
||||
Err(err) => self.send_auth_error(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_auth_error(&self, err: AuthError) {
|
||||
self.send_auth_event(AuthEvent::Error(err));
|
||||
}
|
||||
|
||||
pub fn send_auth_data(&self, data: SamlAuthData) {
|
||||
self.send_auth_event(AuthEvent::Data(data));
|
||||
}
|
||||
|
||||
pub fn schedule_raise_window(&self, delay: u64) {
|
||||
let cancel_token = CancellationToken::new();
|
||||
let cancel_token_clone = cancel_token.clone();
|
||||
|
||||
if let Ok(mut guard) = self.raise_window_cancel_token.try_write() {
|
||||
// Cancel the previous raise window task if it exists
|
||||
if let Some(token) = guard.take() {
|
||||
token.cancel();
|
||||
}
|
||||
*guard = Some(cancel_token_clone);
|
||||
}
|
||||
|
||||
let tx = self.tx.clone();
|
||||
tokio::spawn(async move {
|
||||
info!("Displaying the window in {} second(s)...", delay);
|
||||
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(tokio::time::Duration::from_secs(delay)) => {
|
||||
if let Err(err) = tx.send(AuthEvent::RaiseWindow) {
|
||||
error!("Failed to send raise window event: {}", err);
|
||||
}
|
||||
}
|
||||
_ = cancel_token.cancelled() => {
|
||||
info!("Cancelled raise window task");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn cancel_raise_window(&self) {
|
||||
if let Ok(mut cancel_token) = self.raise_window_cancel_token.try_write() {
|
||||
if let Some(token) = cancel_token.take() {
|
||||
token.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpapi::{
|
||||
auth::{AuthDataParseResult, SamlAuthData},
|
||||
error::AuthDataParseError,
|
||||
};
|
||||
use log::{info, warn};
|
||||
use regex::Regex;
|
||||
|
||||
use crate::webview_auth::auth_messenger::{AuthError, AuthMessenger};
|
||||
|
||||
/// Trait for handling authentication response
|
||||
pub trait AuthResponse {
|
||||
fn get_header(&self, key: &str) -> Option<String>;
|
||||
fn get_body<F>(&self, cb: F)
|
||||
where
|
||||
F: FnOnce(anyhow::Result<Vec<u8>>) + 'static;
|
||||
|
||||
fn url(&self) -> Option<String>;
|
||||
|
||||
fn is_acs_endpoint(&self) -> bool {
|
||||
self.url().map_or(false, |url| url.ends_with("/SAML20/SP/ACS"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_auth_data(auth_response: &impl AuthResponse, auth_messenger: &Arc<AuthMessenger>) {
|
||||
let auth_messenger = Arc::clone(auth_messenger);
|
||||
|
||||
match read_from_headers(auth_response) {
|
||||
Ok(auth_data) => {
|
||||
info!("Found auth data in headers");
|
||||
auth_messenger.send_auth_data(auth_data);
|
||||
}
|
||||
Err(header_err) => {
|
||||
info!("Failed to read auth data from headers: {}", header_err);
|
||||
|
||||
let is_acs_endpoint = auth_response.is_acs_endpoint();
|
||||
read_from_body(auth_response, move |auth_result| {
|
||||
// If the endpoint is `/SAML20/SP/ACS` and no auth data found in body, it should be considered as invalid
|
||||
let auth_result = auth_result.map_err(move |e| {
|
||||
info!("Failed to read auth data from body: {}", e);
|
||||
if is_acs_endpoint || e.is_invalid() || header_err.is_invalid() {
|
||||
AuthError::Invalid
|
||||
} else {
|
||||
AuthError::NotFound
|
||||
}
|
||||
});
|
||||
|
||||
auth_messenger.send_auth_result(auth_result);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_from_headers(auth_response: &impl AuthResponse) -> AuthDataParseResult {
|
||||
let Some(status) = auth_response.get_header("saml-auth-status") else {
|
||||
info!("No SAML auth status found in headers");
|
||||
return Err(AuthDataParseError::NotFound);
|
||||
};
|
||||
|
||||
if status != "1" {
|
||||
info!("Found invalid auth status: {}", status);
|
||||
return Err(AuthDataParseError::Invalid);
|
||||
}
|
||||
|
||||
let username = auth_response.get_header("saml-username");
|
||||
let prelogin_cookie = auth_response.get_header("prelogin-cookie");
|
||||
let portal_userauthcookie = auth_response.get_header("portal-userauthcookie");
|
||||
|
||||
SamlAuthData::new(username, prelogin_cookie, portal_userauthcookie).map_err(|e| {
|
||||
warn!("Found invalid auth data: {}", e);
|
||||
AuthDataParseError::Invalid
|
||||
})
|
||||
}
|
||||
|
||||
fn read_from_body<F>(auth_response: &impl AuthResponse, cb: F)
|
||||
where
|
||||
F: FnOnce(AuthDataParseResult) + 'static,
|
||||
{
|
||||
auth_response.get_body(|body| match body {
|
||||
Ok(body) => {
|
||||
let html = String::from_utf8_lossy(&body);
|
||||
cb(read_from_html(&html))
|
||||
}
|
||||
Err(err) => {
|
||||
info!("Failed to read body: {}", err);
|
||||
cb(Err(AuthDataParseError::Invalid))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn read_from_html(html: &str) -> AuthDataParseResult {
|
||||
if html.contains("Temporarily Unavailable") {
|
||||
info!("Found 'Temporarily Unavailable' in HTML, auth failed");
|
||||
return Err(AuthDataParseError::Invalid);
|
||||
}
|
||||
|
||||
SamlAuthData::from_html(html).or_else(|err| {
|
||||
if let Some(gpcallback) = extract_gpcallback(html) {
|
||||
info!("Found gpcallback from html...");
|
||||
SamlAuthData::from_gpcallback(&gpcallback)
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_gpcallback(html: &str) -> Option<String> {
|
||||
let re = Regex::new(r#"globalprotectcallback:[^"]+"#).unwrap();
|
||||
re.captures(html)
|
||||
.and_then(|captures| captures.get(0))
|
||||
.map(|m| html_escape::decode_html_entities(m.as_str()).to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn extract_gpcallback_some() {
|
||||
let html = r#"
|
||||
<meta http-equiv="refresh" content="0; URL=globalprotectcallback:PGh0bWw+PCEtLSA8c">
|
||||
<meta http-equiv="refresh" content="0; URL=globalprotectcallback:PGh0bWw+PCEtLSA8c">
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
extract_gpcallback(html).as_deref(),
|
||||
Some("globalprotectcallback:PGh0bWw+PCEtLSA8c")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_gpcallback_cas() {
|
||||
let html = r#"
|
||||
<meta http-equiv="refresh" content="0; URL=globalprotectcallback:cas-as=1&[email protected]&token=very_long_string">
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
extract_gpcallback(html).as_deref(),
|
||||
Some("globalprotectcallback:cas-as=1&[email protected]&token=very_long_string")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_gpcallback_none() {
|
||||
let html = r#"
|
||||
<meta http-equiv="refresh" content="0; URL=PGh0bWw+PCEtLSA8c">
|
||||
"#;
|
||||
|
||||
assert_eq!(extract_gpcallback(html), None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::auth_messenger::AuthMessenger;
|
||||
|
||||
pub struct AuthRequest<'a>(&'a str);
|
||||
|
||||
impl<'a> AuthRequest<'a> {
|
||||
pub fn new(auth_request: &'a str) -> Self {
|
||||
Self(auth_request)
|
||||
}
|
||||
|
||||
pub fn is_url(&self) -> bool {
|
||||
self.0.starts_with("http")
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AuthSettings<'a> {
|
||||
pub auth_request: AuthRequest<'a>,
|
||||
pub auth_messenger: Arc<AuthMessenger>,
|
||||
pub ignore_tls_errors: bool,
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::bail;
|
||||
use gpapi::utils::redact::redact_uri;
|
||||
use log::{info, warn};
|
||||
use webkit2gtk::{
|
||||
gio::Cancellable,
|
||||
glib::{GString, TimeSpan},
|
||||
LoadEvent, TLSErrorsPolicy, URIResponseExt, WebResource, WebResourceExt, WebView, WebViewExt, WebsiteDataManagerExt,
|
||||
WebsiteDataManagerExtManual, WebsiteDataTypes,
|
||||
};
|
||||
|
||||
use crate::webview_auth::{
|
||||
auth_messenger::AuthError,
|
||||
auth_response::read_auth_data,
|
||||
auth_settings::{AuthRequest, AuthSettings},
|
||||
};
|
||||
|
||||
use super::auth_response::AuthResponse;
|
||||
|
||||
impl AuthResponse for WebResource {
|
||||
fn get_header(&self, key: &str) -> Option<String> {
|
||||
self
|
||||
.response()
|
||||
.and_then(|response| response.http_headers())
|
||||
.and_then(|headers| headers.one(key))
|
||||
.map(GString::into)
|
||||
}
|
||||
|
||||
fn get_body<F>(&self, cb: F)
|
||||
where
|
||||
F: FnOnce(anyhow::Result<Vec<u8>>) + 'static,
|
||||
{
|
||||
let cancellable = Cancellable::NONE;
|
||||
self.data(cancellable, |data| cb(data.map_err(|e| anyhow::anyhow!(e))));
|
||||
}
|
||||
|
||||
fn url(&self) -> Option<String> {
|
||||
self.uri().map(GString::into)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_data<F>(wv: &WebView, cb: F)
|
||||
where
|
||||
F: FnOnce(anyhow::Result<()>) + Send + 'static,
|
||||
{
|
||||
let Some(data_manager) = wv.website_data_manager() else {
|
||||
cb(Err(anyhow::anyhow!("Failed to get website data manager")));
|
||||
return;
|
||||
};
|
||||
|
||||
data_manager.clear(
|
||||
WebsiteDataTypes::COOKIES,
|
||||
TimeSpan(0),
|
||||
Cancellable::NONE,
|
||||
move |result| {
|
||||
cb(result.map_err(|e| anyhow::anyhow!(e)));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn setup_webview(wv: &WebView, auth_settings: AuthSettings) -> anyhow::Result<()> {
|
||||
let AuthSettings {
|
||||
auth_request,
|
||||
auth_messenger,
|
||||
ignore_tls_errors,
|
||||
} = auth_settings;
|
||||
let auth_messenger_clone = Arc::clone(&auth_messenger);
|
||||
|
||||
let Some(data_manager) = wv.website_data_manager() else {
|
||||
bail!("Failed to get website data manager");
|
||||
};
|
||||
|
||||
if ignore_tls_errors {
|
||||
data_manager.set_tls_errors_policy(TLSErrorsPolicy::Ignore);
|
||||
}
|
||||
|
||||
wv.connect_load_changed(move |wv, event| {
|
||||
if event == LoadEvent::Started {
|
||||
auth_messenger_clone.cancel_raise_window();
|
||||
return;
|
||||
}
|
||||
|
||||
if event != LoadEvent::Finished {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(main_resource) = wv.main_resource() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let uri = main_resource.uri().unwrap_or("".into());
|
||||
if uri.is_empty() {
|
||||
warn!("Loaded an empty URI");
|
||||
auth_messenger_clone.send_auth_error(AuthError::Invalid);
|
||||
return;
|
||||
}
|
||||
|
||||
read_auth_data(&main_resource, &auth_messenger_clone);
|
||||
});
|
||||
|
||||
wv.connect_load_failed_with_tls_errors(move |_wv, uri, cert, err| {
|
||||
let redacted_uri = redact_uri(uri);
|
||||
warn!(
|
||||
"Failed to load uri: {} with error: {}, cert: {}",
|
||||
redacted_uri, err, cert
|
||||
);
|
||||
|
||||
auth_messenger.send_auth_error(AuthError::TlsError);
|
||||
true
|
||||
});
|
||||
|
||||
wv.connect_load_failed(move |_wv, _event, uri, err| {
|
||||
let redacted_uri = redact_uri(uri);
|
||||
if !uri.starts_with("globalprotectcallback:") {
|
||||
warn!("Failed to load uri: {} with error: {}", redacted_uri, err);
|
||||
}
|
||||
// NOTE: Don't send error here, since load_changed event will be triggered after this
|
||||
// true to stop other handlers from being invoked for the event. false to propagate the event further.
|
||||
true
|
||||
});
|
||||
|
||||
load_auth_request(wv, &auth_request);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_auth_request(wv: &WebView, auth_request: &AuthRequest) {
|
||||
if auth_request.is_url() {
|
||||
info!("Loading auth request as URI...");
|
||||
wv.load_uri(auth_request.as_str());
|
||||
} else {
|
||||
info!("Loading auth request as HTML...");
|
||||
wv.load_html(auth_request.as_str(), None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use anyhow::bail;
|
||||
use gpapi::{auth::SamlAuthData, error::PortalError, utils::window::WindowExt};
|
||||
use log::{info, warn};
|
||||
use tauri::{AppHandle, WebviewUrl, WebviewWindow, WindowEvent};
|
||||
use tokio::{sync::oneshot, time};
|
||||
|
||||
use crate::{
|
||||
webview_auth::{
|
||||
auth_messenger::{AuthError, AuthEvent, AuthMessenger},
|
||||
auth_settings::{AuthRequest, AuthSettings},
|
||||
platform_impl,
|
||||
},
|
||||
Authenticator,
|
||||
};
|
||||
|
||||
pub trait WebviewAuthenticator {
|
||||
fn with_clean(self, clean: bool) -> Self;
|
||||
fn webview_authenticate(&self, app_handle: &AppHandle) -> impl Future<Output = anyhow::Result<SamlAuthData>> + Send;
|
||||
}
|
||||
|
||||
impl WebviewAuthenticator for Authenticator<'_> {
|
||||
fn with_clean(mut self, clean: bool) -> Self {
|
||||
self.clean = clean;
|
||||
self
|
||||
}
|
||||
|
||||
async fn webview_authenticate(&self, app_handle: &AppHandle) -> anyhow::Result<SamlAuthData> {
|
||||
let auth_window = WebviewWindow::builder(app_handle, "auth_window", WebviewUrl::default())
|
||||
.title("GlobalProtect Login")
|
||||
.focused(true)
|
||||
.visible(false)
|
||||
.center()
|
||||
.build()?;
|
||||
|
||||
self.auth_loop(&auth_window).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Authenticator<'_> {
|
||||
async fn auth_loop(&self, auth_window: &WebviewWindow) -> anyhow::Result<SamlAuthData> {
|
||||
if self.clean {
|
||||
self.clear_webview_data(&auth_window).await?;
|
||||
}
|
||||
|
||||
let auth_messenger = self.setup_auth_window(&auth_window).await?;
|
||||
|
||||
loop {
|
||||
match auth_messenger.subscribe().await? {
|
||||
AuthEvent::Close => bail!("Authentication cancelled"),
|
||||
AuthEvent::RaiseWindow => self.raise_window(auth_window),
|
||||
AuthEvent::Error(AuthError::TlsError) => bail!(PortalError::TlsError),
|
||||
AuthEvent::Error(AuthError::NotFound) => self.handle_not_found(auth_window, &auth_messenger),
|
||||
AuthEvent::Error(AuthError::Invalid) => self.retry_auth(auth_window).await,
|
||||
AuthEvent::Data(auth_data) => {
|
||||
auth_window.close()?;
|
||||
return Ok(auth_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn clear_webview_data(&self, auth_window: &WebviewWindow) -> anyhow::Result<()> {
|
||||
info!("Clearing webview data...");
|
||||
|
||||
let (tx, rx) = oneshot::channel::<anyhow::Result<()>>();
|
||||
let now = Instant::now();
|
||||
auth_window.with_webview(|webview| {
|
||||
platform_impl::clear_data(&webview.inner(), |result| {
|
||||
if let Err(result) = tx.send(result) {
|
||||
warn!("Failed to send clear data result: {:?}", result);
|
||||
}
|
||||
})
|
||||
})?;
|
||||
|
||||
rx.await??;
|
||||
info!("Webview data cleared in {:?}", now.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn setup_auth_window(&self, auth_window: &WebviewWindow) -> anyhow::Result<Arc<AuthMessenger>> {
|
||||
info!("Setting up auth window...");
|
||||
|
||||
let auth_messenger = Arc::new(AuthMessenger::new());
|
||||
let auth_request = self.initial_auth_request().await?.into_owned();
|
||||
let ignore_tls_errors = self.gp_params.ignore_tls_errors();
|
||||
|
||||
// Handle window close event
|
||||
let auth_messenger_clone = Arc::clone(&auth_messenger);
|
||||
auth_window.on_window_event(move |event| {
|
||||
if let WindowEvent::CloseRequested { .. } = event {
|
||||
auth_messenger_clone.send_auth_event(AuthEvent::Close);
|
||||
}
|
||||
});
|
||||
|
||||
// Show the window after 10 seconds, so that the user can see the window if the auth process is stuck
|
||||
let auth_messenger_clone = Arc::clone(&auth_messenger);
|
||||
tokio::spawn(async move {
|
||||
time::sleep(Duration::from_secs(10)).await;
|
||||
auth_messenger_clone.send_auth_event(AuthEvent::RaiseWindow);
|
||||
});
|
||||
|
||||
// setup webview
|
||||
let auth_messenger_clone = Arc::clone(&auth_messenger);
|
||||
let (tx, rx) = oneshot::channel::<anyhow::Result<()>>();
|
||||
|
||||
auth_window.with_webview(move |webview| {
|
||||
let auth_settings = AuthSettings {
|
||||
auth_request: AuthRequest::new(&auth_request),
|
||||
auth_messenger: auth_messenger_clone,
|
||||
ignore_tls_errors,
|
||||
};
|
||||
|
||||
let result = platform_impl::setup_webview(&webview.inner(), auth_settings);
|
||||
if let Err(result) = tx.send(result) {
|
||||
warn!("Failed to send setup auth window result: {:?}", result);
|
||||
}
|
||||
})?;
|
||||
|
||||
rx.await??;
|
||||
info!("Auth window setup completed");
|
||||
|
||||
Ok(auth_messenger)
|
||||
}
|
||||
|
||||
fn handle_not_found(&self, auth_window: &WebviewWindow, auth_messenger: &Arc<AuthMessenger>) {
|
||||
info!("No auth data found, it may not be the /SAML20/SP/ACS endpoint");
|
||||
|
||||
let visible = auth_window.is_visible().unwrap_or(false);
|
||||
if visible {
|
||||
return;
|
||||
}
|
||||
|
||||
auth_messenger.schedule_raise_window(1);
|
||||
}
|
||||
|
||||
async fn retry_auth(&self, auth_window: &WebviewWindow) {
|
||||
let mut is_retrying = self.is_retrying.write().await;
|
||||
if *is_retrying {
|
||||
info!("Already retrying authentication, skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
*is_retrying = true;
|
||||
drop(is_retrying);
|
||||
|
||||
if let Err(err) = self.retry_auth_impl(auth_window).await {
|
||||
warn!("Failed to retry authentication: {}", err);
|
||||
}
|
||||
|
||||
*self.is_retrying.write().await = false;
|
||||
}
|
||||
|
||||
async fn retry_auth_impl(&self, auth_window: &WebviewWindow) -> anyhow::Result<()> {
|
||||
info!("Retrying authentication...");
|
||||
|
||||
auth_window.eval( r#"
|
||||
var loading = document.createElement("div");
|
||||
loading.innerHTML = '<div style="position: absolute; width: 100%; text-align: center; font-size: 20px; font-weight: bold; top: 50%; left: 50%; transform: translate(-50%, -50%);">Got invalid token, retrying...</div>';
|
||||
loading.style = "position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.85); z-index: 99999;";
|
||||
document.body.appendChild(loading);
|
||||
"#)?;
|
||||
|
||||
let auth_request = self.portal_prelogin().await?;
|
||||
let (tx, rx) = oneshot::channel::<()>();
|
||||
auth_window.with_webview(move |webview| {
|
||||
let auth_request = AuthRequest::new(&auth_request);
|
||||
platform_impl::load_auth_request(&webview.inner(), &auth_request);
|
||||
|
||||
tx.send(()).expect("Failed to send message to the channel")
|
||||
})?;
|
||||
|
||||
rx.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn raise_window(&self, auth_window: &WebviewWindow) {
|
||||
let visible = auth_window.is_visible().unwrap_or(false);
|
||||
if visible {
|
||||
return;
|
||||
}
|
||||
|
||||
info!("Raising auth window...");
|
||||
if let Err(err) = auth_window.raise() {
|
||||
warn!("Failed to raise window: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
[package]
|
||||
name = "gpapi"
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license = "MIT"
|
||||
@@ -14,8 +15,7 @@ openssl.workspace = true
|
||||
pem.workspace = true
|
||||
roxmltree.workspace = true
|
||||
serde.workspace = true
|
||||
specta.workspace = true
|
||||
specta-macros.workspace = true
|
||||
specta = { workspace = true, features = ["derive"] }
|
||||
urlencoding.workspace = true
|
||||
tokio.workspace = true
|
||||
serde_json.workspace = true
|
||||
@@ -30,14 +30,11 @@ uzers.workspace = true
|
||||
serde_urlencoded.workspace = true
|
||||
md5.workspace = true
|
||||
sha256.workspace = true
|
||||
which.workspace = true
|
||||
|
||||
tauri = { workspace = true, optional = true }
|
||||
clap = { workspace = true, optional = true }
|
||||
open = { version = "5", optional = true }
|
||||
webbrowser = { version = "1", optional = true }
|
||||
|
||||
[features]
|
||||
tauri = ["dep:tauri"]
|
||||
clap = ["dep:clap"]
|
||||
browser-auth = ["dep:open", "dep:webbrowser"]
|
||||
webview-auth = []
|
||||
+39
-39
@@ -1,11 +1,14 @@
|
||||
use std::borrow::{Borrow, Cow};
|
||||
|
||||
use anyhow::bail;
|
||||
use log::{info, warn};
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{error::AuthDataParseError, utils::base64::decode_to_string};
|
||||
|
||||
pub type AuthDataParseResult = anyhow::Result<SamlAuthData, AuthDataParseError>;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SamlAuthData {
|
||||
@@ -33,33 +36,51 @@ impl SamlAuthResult {
|
||||
}
|
||||
|
||||
impl SamlAuthData {
|
||||
pub fn new(username: String, prelogin_cookie: Option<String>, portal_userauthcookie: Option<String>) -> Self {
|
||||
Self {
|
||||
username,
|
||||
prelogin_cookie,
|
||||
portal_userauthcookie,
|
||||
token: None,
|
||||
pub fn new(
|
||||
username: Option<String>,
|
||||
prelogin_cookie: Option<String>,
|
||||
portal_userauthcookie: Option<String>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let username = username.unwrap_or_default();
|
||||
if username.is_empty() {
|
||||
bail!("Invalid username: <empty>");
|
||||
}
|
||||
|
||||
let prelogin_cookie = prelogin_cookie.unwrap_or_default();
|
||||
let portal_userauthcookie = portal_userauthcookie.unwrap_or_default();
|
||||
|
||||
if prelogin_cookie.len() <= 5 && portal_userauthcookie.len() <= 5 {
|
||||
bail!(
|
||||
"Invalid prelogin-cookie: {}, portal-userauthcookie: {}",
|
||||
prelogin_cookie,
|
||||
portal_userauthcookie
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
username,
|
||||
prelogin_cookie: Some(prelogin_cookie),
|
||||
portal_userauthcookie: Some(portal_userauthcookie),
|
||||
token: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_html(html: &str) -> anyhow::Result<SamlAuthData, AuthDataParseError> {
|
||||
pub fn from_html(html: &str) -> AuthDataParseResult {
|
||||
match parse_xml_tag(html, "saml-auth-status") {
|
||||
Some(saml_status) if saml_status == "1" => {
|
||||
Some(status) if status == "1" => {
|
||||
let username = parse_xml_tag(html, "saml-username");
|
||||
let prelogin_cookie = parse_xml_tag(html, "prelogin-cookie");
|
||||
let portal_userauthcookie = parse_xml_tag(html, "portal-userauthcookie");
|
||||
|
||||
if SamlAuthData::check(&username, &prelogin_cookie, &portal_userauthcookie) {
|
||||
Ok(SamlAuthData::new(
|
||||
username.unwrap(),
|
||||
prelogin_cookie,
|
||||
portal_userauthcookie,
|
||||
))
|
||||
} else {
|
||||
Err(AuthDataParseError::Invalid)
|
||||
}
|
||||
SamlAuthData::new(username, prelogin_cookie, portal_userauthcookie).map_err(|e| {
|
||||
warn!("Failed to parse auth data: {}", e);
|
||||
AuthDataParseError::Invalid
|
||||
})
|
||||
}
|
||||
Some(status) => {
|
||||
warn!("Found invalid auth status: {}", status);
|
||||
Err(AuthDataParseError::Invalid)
|
||||
}
|
||||
Some(_) => Err(AuthDataParseError::Invalid),
|
||||
None => Err(AuthDataParseError::NotFound),
|
||||
}
|
||||
}
|
||||
@@ -105,27 +126,6 @@ impl SamlAuthData {
|
||||
pub fn token(&self) -> Option<&str> {
|
||||
self.token.as_deref()
|
||||
}
|
||||
|
||||
pub fn check(
|
||||
username: &Option<String>,
|
||||
prelogin_cookie: &Option<String>,
|
||||
portal_userauthcookie: &Option<String>,
|
||||
) -> bool {
|
||||
let username_valid = username.as_ref().is_some_and(|username| !username.is_empty());
|
||||
let prelogin_cookie_valid = prelogin_cookie.as_ref().is_some_and(|val| val.len() > 5);
|
||||
let portal_userauthcookie_valid = portal_userauthcookie.as_ref().is_some_and(|val| val.len() > 5);
|
||||
|
||||
let is_valid = username_valid && (prelogin_cookie_valid || portal_userauthcookie_valid);
|
||||
|
||||
if !is_valid {
|
||||
warn!(
|
||||
"Invalid SAML auth data: username: {:?}, prelogin-cookie: {:?}, portal-userauthcookie: {:?}",
|
||||
username, prelogin_cookie, portal_userauthcookie
|
||||
);
|
||||
}
|
||||
|
||||
is_valid
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_xml_tag(html: &str, tag: &str) -> Option<String> {
|
||||
|
||||
@@ -1 +1,28 @@
|
||||
use crate::error::PortalError;
|
||||
|
||||
pub mod args;
|
||||
|
||||
pub trait Args {
|
||||
fn fix_openssl(&self) -> bool;
|
||||
fn ignore_tls_errors(&self) -> bool;
|
||||
}
|
||||
|
||||
pub fn handle_error(err: anyhow::Error, args: &impl Args) {
|
||||
eprintln!("\nError: {}", err);
|
||||
|
||||
let Some(err) = err.downcast_ref::<PortalError>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if err.is_legacy_openssl_error() && !args.fix_openssl() {
|
||||
eprintln!("\nRe-run it with the `--fix-openssl` option to work around this issue, e.g.:\n");
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
eprintln!("{} --fix-openssl {}\n", args[0], args[1..].join(" "));
|
||||
}
|
||||
|
||||
if err.is_tls_error() && !args.ignore_tls_errors() {
|
||||
eprintln!("\nRe-run it with the `--ignore-tls-errors` option to ignore the certificate error, e.g.:\n");
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
eprintln!("{} --ignore-tls-errors {}\n", args[0], args[1..].join(" "));
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,19 @@ pub enum PortalError {
|
||||
#[error("Portal config error: {0}")]
|
||||
ConfigError(String),
|
||||
#[error("Network error: {0}")]
|
||||
NetworkError(String),
|
||||
NetworkError(#[from] reqwest::Error),
|
||||
#[error("TLS error")]
|
||||
TlsError,
|
||||
}
|
||||
|
||||
impl PortalError {
|
||||
pub fn is_legacy_openssl_error(&self) -> bool {
|
||||
format!("{:?}", self).contains("unsafe legacy renegotiation")
|
||||
}
|
||||
|
||||
pub fn is_tls_error(&self) -> bool {
|
||||
matches!(self, PortalError::TlsError) || format!("{:?}", self).contains("certificate verify failed")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -17,3 +29,9 @@ pub enum AuthDataParseError {
|
||||
#[error("Invalid auth data")]
|
||||
Invalid,
|
||||
}
|
||||
|
||||
impl AuthDataParseError {
|
||||
pub fn is_invalid(&self) -> bool {
|
||||
matches!(self, AuthDataParseError::Invalid)
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ pub async fn gateway_login(gateway: &str, cred: &Credential, gp_params: &GpParam
|
||||
.form(¶ms)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e.to_string())))?;
|
||||
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e)))?;
|
||||
|
||||
let res = parse_gp_response(res).await.map_err(|err| {
|
||||
warn!("{err}");
|
||||
|
||||
@@ -16,6 +16,7 @@ pub const GP_API_KEY: &[u8; 32] = &[0; 32];
|
||||
|
||||
pub const GP_USER_AGENT: &str = "PAN GlobalProtect";
|
||||
pub const GP_SERVICE_LOCK_FILE: &str = "/var/run/gpservice.lock";
|
||||
pub const GP_CALLBACK_PORT_FILENAME: &str = "gpcallback.port";
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const GP_CLIENT_BINARY: &str = "/usr/bin/gpclient";
|
||||
|
||||
@@ -116,7 +116,7 @@ pub async fn retrieve_config(portal: &str, cred: &Credential, gp_params: &GpPara
|
||||
.form(¶ms)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e.to_string())))?;
|
||||
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e)))?;
|
||||
|
||||
let res_xml = parse_gp_response(res).await.or_else(|err| {
|
||||
if err.status == StatusCode::NOT_FOUND {
|
||||
|
||||
@@ -116,14 +116,12 @@ pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prel
|
||||
|
||||
let client = Client::try_from(gp_params)?;
|
||||
|
||||
info!("Perform prelogin, user_agent: {}", gp_params.user_agent());
|
||||
|
||||
let res = client
|
||||
.post(&prelogin_url)
|
||||
.form(¶ms)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e.to_string())))?;
|
||||
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e)))?;
|
||||
|
||||
let res_xml = parse_gp_response(res).await.or_else(|err| {
|
||||
if err.status == StatusCode::NOT_FOUND {
|
||||
|
||||
@@ -14,10 +14,13 @@ pub struct SamlAuthLauncher<'a> {
|
||||
user_agent: Option<&'a str>,
|
||||
os: Option<&'a str>,
|
||||
os_version: Option<&'a str>,
|
||||
hidpi: bool,
|
||||
fix_openssl: bool,
|
||||
ignore_tls_errors: bool,
|
||||
#[cfg(feature = "webview-auth")]
|
||||
hidpi: bool,
|
||||
#[cfg(feature = "webview-auth")]
|
||||
clean: bool,
|
||||
#[cfg(feature = "webview-auth")]
|
||||
default_browser: bool,
|
||||
browser: Option<&'a str>,
|
||||
}
|
||||
@@ -31,10 +34,13 @@ impl<'a> SamlAuthLauncher<'a> {
|
||||
user_agent: None,
|
||||
os: None,
|
||||
os_version: None,
|
||||
hidpi: false,
|
||||
fix_openssl: false,
|
||||
ignore_tls_errors: false,
|
||||
#[cfg(feature = "webview-auth")]
|
||||
hidpi: false,
|
||||
#[cfg(feature = "webview-auth")]
|
||||
clean: false,
|
||||
#[cfg(feature = "webview-auth")]
|
||||
default_browser: false,
|
||||
browser: None,
|
||||
}
|
||||
@@ -65,11 +71,6 @@ impl<'a> SamlAuthLauncher<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn hidpi(mut self, hidpi: bool) -> Self {
|
||||
self.hidpi = hidpi;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fix_openssl(mut self, fix_openssl: bool) -> Self {
|
||||
self.fix_openssl = fix_openssl;
|
||||
self
|
||||
@@ -80,11 +81,19 @@ impl<'a> SamlAuthLauncher<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
pub fn hidpi(mut self, hidpi: bool) -> Self {
|
||||
self.hidpi = hidpi;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
pub fn clean(mut self, clean: bool) -> Self {
|
||||
self.clean = clean;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "webview-auth")]
|
||||
pub fn default_browser(mut self, default_browser: bool) -> Self {
|
||||
self.default_browser = default_browser;
|
||||
self
|
||||
@@ -120,10 +129,6 @@ impl<'a> SamlAuthLauncher<'a> {
|
||||
auth_cmd.arg("--os-version").arg(os_version);
|
||||
}
|
||||
|
||||
if self.hidpi {
|
||||
auth_cmd.arg("--hidpi");
|
||||
}
|
||||
|
||||
if self.fix_openssl {
|
||||
auth_cmd.arg("--fix-openssl");
|
||||
}
|
||||
@@ -132,12 +137,19 @@ impl<'a> SamlAuthLauncher<'a> {
|
||||
auth_cmd.arg("--ignore-tls-errors");
|
||||
}
|
||||
|
||||
if self.clean {
|
||||
auth_cmd.arg("--clean");
|
||||
}
|
||||
#[cfg(feature = "webview-auth")]
|
||||
{
|
||||
if self.hidpi {
|
||||
auth_cmd.arg("--hidpi");
|
||||
}
|
||||
|
||||
if self.default_browser {
|
||||
auth_cmd.arg("--default-browser");
|
||||
if self.clean {
|
||||
auth_cmd.arg("--clean");
|
||||
}
|
||||
|
||||
if self.default_browser {
|
||||
auth_cmd.arg("--default-browser");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(browser) = self.browser {
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
use std::{borrow::Cow, env::temp_dir, fs, io::Write, os::unix::fs::PermissionsExt};
|
||||
|
||||
use anyhow::bail;
|
||||
use log::{info, warn};
|
||||
|
||||
pub struct BrowserAuthenticator<'a> {
|
||||
auth_request: &'a str,
|
||||
browser: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl BrowserAuthenticator<'_> {
|
||||
pub fn new(auth_request: &str) -> BrowserAuthenticator {
|
||||
BrowserAuthenticator {
|
||||
auth_request,
|
||||
browser: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_browser<'a>(auth_request: &'a str, browser: &'a str) -> BrowserAuthenticator<'a> {
|
||||
let browser = browser.trim();
|
||||
BrowserAuthenticator {
|
||||
auth_request,
|
||||
browser: if browser.is_empty() || browser == "default" {
|
||||
None
|
||||
} else {
|
||||
Some(browser)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn authenticate(&self) -> anyhow::Result<()> {
|
||||
let path = if self.auth_request.starts_with("http") {
|
||||
Cow::Borrowed(self.auth_request)
|
||||
} else {
|
||||
let html_file = temp_dir().join("gpauth.html");
|
||||
|
||||
// Remove the file and error if permission denied
|
||||
if let Err(err) = fs::remove_file(&html_file) {
|
||||
if err.kind() != std::io::ErrorKind::NotFound {
|
||||
warn!("Failed to remove the temporary file: {}", err);
|
||||
bail!("Please remove the file manually: {:?}", html_file);
|
||||
}
|
||||
}
|
||||
|
||||
let mut file = fs::File::create(&html_file)?;
|
||||
|
||||
file.set_permissions(fs::Permissions::from_mode(0o600))?;
|
||||
file.write_all(self.auth_request.as_bytes())?;
|
||||
|
||||
Cow::Owned(html_file.to_string_lossy().to_string())
|
||||
};
|
||||
|
||||
if let Some(browser) = self.browser {
|
||||
let app = find_browser_path(browser);
|
||||
|
||||
info!("Launching browser: {}", app);
|
||||
open::with_detached(path.as_ref(), app)?;
|
||||
} else {
|
||||
info!("Launching the default browser...");
|
||||
webbrowser::open(path.as_ref())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn find_browser_path(browser: &str) -> String {
|
||||
if browser == "chrome" {
|
||||
which::which("google-chrome-stable")
|
||||
.or_else(|_| which::which("google-chrome"))
|
||||
.or_else(|_| which::which("chromium"))
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|_| browser.to_string())
|
||||
} else {
|
||||
browser.into()
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@ pub(crate) mod command_traits;
|
||||
pub(crate) mod gui_helper_launcher;
|
||||
|
||||
pub mod auth_launcher;
|
||||
#[cfg(feature = "browser-auth")]
|
||||
pub mod browser_authenticator;
|
||||
pub mod gui_launcher;
|
||||
pub mod hip_launcher;
|
||||
pub mod service_launcher;
|
||||
|
||||
@@ -7,6 +7,4 @@ use super::vpn_state::VpnState;
|
||||
pub enum WsEvent {
|
||||
VpnState(VpnState),
|
||||
ActiveGui,
|
||||
/// External authentication data
|
||||
AuthData(String),
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use std::env;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use log::info;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
pub fn persist_env_vars(extra: Option<HashMap<String, String>>) -> anyhow::Result<NamedTempFile> {
|
||||
@@ -35,3 +36,20 @@ pub fn load_env_vars<T: AsRef<Path>>(env_file: T) -> anyhow::Result<HashMap<Stri
|
||||
|
||||
Ok(env_vars)
|
||||
}
|
||||
|
||||
pub fn patch_gui_runtime_env(hidpi: bool) {
|
||||
// This is to avoid blank screen on some systems
|
||||
std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "1");
|
||||
|
||||
// Workaround for https://github.com/tauri-apps/tao/issues/929
|
||||
let desktop = env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().to_lowercase();
|
||||
if desktop.contains("gnome") {
|
||||
env::set_var("GDK_BACKEND", "x11");
|
||||
}
|
||||
|
||||
if hidpi {
|
||||
info!("Setting GDK_SCALE=2 and GDK_DPI_SCALE=0.5");
|
||||
std::env::set_var("GDK_SCALE", "2");
|
||||
std::env::set_var("GDK_DPI_SCALE", "0.5");
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ pub mod base64;
|
||||
pub mod checksum;
|
||||
pub mod crypto;
|
||||
pub mod endpoint;
|
||||
pub mod env_file;
|
||||
pub mod env_utils;
|
||||
pub mod lock_file;
|
||||
pub mod openssl;
|
||||
pub mod redact;
|
||||
|
||||
@@ -2,25 +2,20 @@ use std::{process::ExitStatus, time::Duration};
|
||||
|
||||
use anyhow::bail;
|
||||
use log::info;
|
||||
use tauri::Window;
|
||||
use tauri::WebviewWindow;
|
||||
use tokio::process::Command;
|
||||
|
||||
pub trait WindowExt {
|
||||
fn raise(&self) -> anyhow::Result<()>;
|
||||
fn hide_menu(&self);
|
||||
}
|
||||
|
||||
impl WindowExt for Window {
|
||||
impl WindowExt for WebviewWindow {
|
||||
fn raise(&self) -> anyhow::Result<()> {
|
||||
raise_window(self)
|
||||
}
|
||||
|
||||
fn hide_menu(&self) {
|
||||
hide_menu(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raise_window(win: &Window) -> anyhow::Result<()> {
|
||||
pub fn raise_window(win: &WebviewWindow) -> anyhow::Result<()> {
|
||||
let is_wayland = std::env::var("XDG_SESSION_TYPE").unwrap_or_default() == "wayland";
|
||||
|
||||
if is_wayland {
|
||||
@@ -40,7 +35,7 @@ pub fn raise_window(win: &Window) -> anyhow::Result<()> {
|
||||
|
||||
// Calling window.show() on Windows will cause the menu to be shown.
|
||||
// We need to hide it again.
|
||||
hide_menu(win);
|
||||
win.hide_menu()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -76,22 +71,3 @@ async fn wmctrl_try_raise_window(title: &str) -> anyhow::Result<ExitStatus> {
|
||||
|
||||
Ok(exit_status)
|
||||
}
|
||||
|
||||
fn hide_menu(win: &Window) {
|
||||
let menu_handle = win.menu_handle();
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let menu_visible = menu_handle.is_visible().unwrap_or(false);
|
||||
|
||||
if !menu_visible {
|
||||
break;
|
||||
}
|
||||
|
||||
if menu_visible {
|
||||
let _ = menu_handle.hide();
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
10
|
||||
@@ -7,17 +7,24 @@ Build-Depends: debhelper (>= 9),
|
||||
pkg-config,
|
||||
jq (>= 1),
|
||||
make (>= 4),
|
||||
libssl-dev,
|
||||
libxml2,
|
||||
libsecret-1-0,
|
||||
libayatana-appindicator3-1,
|
||||
gnome-keyring,
|
||||
libwebkit2gtk-4.0-dev,
|
||||
libwebkit2gtk-4.1-dev,
|
||||
libopenconnect-dev (>= 8.20),@RUST@
|
||||
Homepage: https://github.com/yuezk/GlobalProtect-openconnect
|
||||
|
||||
Package: globalprotect-openconnect
|
||||
Architecture: any
|
||||
Multi-Arch: foreign
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}, openconnect (>=8.20), libxml2, libsecret-1-0, libayatana-appindicator3-1, gnome-keyring
|
||||
Depends: ${misc:Depends},
|
||||
${shlibs:Depends},
|
||||
openconnect (>=8.20),
|
||||
libxml2,
|
||||
libsecret-1-0,
|
||||
libayatana-appindicator3-1,
|
||||
gnome-keyring,
|
||||
Description: A GUI for GlobalProtect VPN
|
||||
A GUI for GlobalProtect VPN, based on OpenConnect, supports the SSO authentication method.
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
export OFFLINE = @OFFLINE@
|
||||
export BUILD_FE = 0
|
||||
export BUILD_GUI = @BUILD_GUI@
|
||||
|
||||
export PATH := /usr/lib/rust-@RUST_VERSION@/bin:$(PATH)
|
||||
|
||||
%:
|
||||
which cargo
|
||||
which rustc
|
||||
dh $@ --no-parallel
|
||||
@@ -8,8 +8,8 @@ pkgdesc="A GUI for GlobalProtect VPN, based on OpenConnect, supports the SSO aut
|
||||
arch=('x86_64' 'aarch64')
|
||||
url="https://github.com/yuezk/GlobalProtect-openconnect"
|
||||
license=('GPL3')
|
||||
makedepends=('make' 'pkg-config' 'rust' 'cargo' 'jq' 'webkit2gtk' 'curl' 'wget' 'file' 'openssl' 'appmenu-gtk-module' 'gtk3' 'libappindicator-gtk3' 'librsvg' 'libvips' 'libayatana-appindicator' 'openconnect' 'libsecret')
|
||||
depends=('openconnect>=8.20' webkit2gtk libappindicator-gtk3 libayatana-appindicator libsecret libxml2)
|
||||
makedepends=('make' 'pkg-config' 'rust' 'cargo' 'jq' 'webkit2gtk-4.1' 'curl' 'wget' 'file' 'openssl' 'appmenu-gtk-module' 'libappindicator-gtk3' 'librsvg' 'openconnect' 'libsecret')
|
||||
depends=('openconnect>=8.20' webkit2gtk-4.1 libappindicator-gtk3 libsecret libxml2)
|
||||
optdepends=('wmctrl: for window management')
|
||||
|
||||
provides=('globalprotect-openconnect' 'gpclient' 'gpservice' 'gpauth' 'gpgui')
|
||||
|
||||
@@ -19,11 +19,11 @@ BuildRequires: wget
|
||||
BuildRequires: file
|
||||
BuildRequires: perl
|
||||
|
||||
BuildRequires: (webkit2gtk4.0-devel or webkit2gtk3-soup2-devel)
|
||||
BuildRequires: (webkit2gtk4.1-devel or webkit2gtk3-soup2-devel)
|
||||
BuildRequires: (libappindicator-gtk3-devel or libappindicator3-1)
|
||||
BuildRequires: (librsvg2-devel or librsvg-devel)
|
||||
|
||||
Requires: openconnect >= 8.20, (libayatana-appindicator or libappindicator-gtk3)
|
||||
Requires: openconnect >= 8.20, (libappindicator-gtk3 or libayatana-appindicator)
|
||||
Conflicts: globalprotect-openconnect-snapshot
|
||||
|
||||
%global debug_package %{nil}
|
||||
|
||||
Reference in new issue
Block a user