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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .github/workflows/web-docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: Web client CI

on:
pull_request:
paths:
- 'flutter/web/**'
- 'flutter/lib/**'
- 'flutter/assets/**'
- 'flutter/pubspec.*'
- 'Dockerfile.webclient'
- '.github/workflows/web-docker.yml'
- '.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
push:
tags: ['*']
Comment thread
coderabbitai[bot] marked this conversation as resolved.
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/rustdesk-webclient

jobs:
test:
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
persist-credentials: false

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install protoc
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler

- name: Install and test
working-directory: flutter/web/js
run: |
yarn install
python3 ./gen_js_from_hbb.py > src/gen_js_from_hbb.ts
python3 ./ts_proto.py
yarn test
Comment thread
coderabbitai[bot] marked this conversation as resolved.

build:
needs: test
runs-on: ubuntu-22.04
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
persist-credentials: false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
if: startsWith(github.ref, 'refs/tags/')
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.webclient
push: ${{ startsWith(github.ref, 'refs/tags/') }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,9 @@ rustdesk-data/
# web client build artifacts
flutter/web/ogvjs*/
flutter/web/web_deps.tar.gz
flutter/web/js/coverage/
# core dumps
core
flutter/core
# playwright testing artifacts
.playwright-mcp/
20 changes: 18 additions & 2 deletions Dockerfile.web
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ ARG NODE_MAJOR=22

RUN apt-get update && apt-get install -y --no-install-recommends \
wget curl git unzip xz-utils ca-certificates python3 gnupg \
&& curl -fsSL https://dl.google.com/linux/linux_signing_key.pub \
| gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg \
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" \
> /etc/apt/sources.list.d/google-chrome.list \
&& apt-get update && apt-get install -y --no-install-recommends google-chrome-stable \
&& rm -rf /var/lib/apt/lists/*

RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
Expand All @@ -22,20 +27,31 @@ ENV PATH="/opt/flutter/bin:${PATH}"

RUN npm install -g yarn typescript protoc

RUN apt-get update && apt-get install -y --no-install-recommends build-essential pkg-config libclang-dev \
&& rm -rf /var/lib/apt/lists/*
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
ENV PATH="/root/.cargo/bin:${PATH}"
RUN cargo install flutter_rust_bridge_codegen --version 1.80.1 --features uuid --locked

COPY .github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff /tmp/flutter.diff
RUN cd /opt/flutter && git apply /tmp/flutter.diff && rm /tmp/flutter.diff

ARG UID=1000
ARG GID=1000
RUN (groupadd -g ${GID} builder 2>/dev/null || groupadd builder) \
&& (useradd -m -u ${UID} -g builder builder 2>/dev/null || useradd -m -g builder builder) \
&& chown -R builder:builder /opt/flutter
&& chown -R builder:builder /opt/flutter \
&& cp -r /root/.cargo /home/builder/.cargo \
&& cp -r /root/.rustup /home/builder/.rustup \
&& chown -R builder:builder /home/builder/.cargo /home/builder/.rustup

USER builder
ENV PATH="/home/builder/.cargo/bin:${PATH}"
ENV RUSTUP_HOME="/home/builder/.rustup"

RUN flutter config --no-analytics && flutter precache --web

RUN git config --global --add safe.directory /opt/flutter \
&& git config --global --add safe.directory /app

WORKDIR /app/flutter
WORKDIR /app
68 changes: 68 additions & 0 deletions Dockerfile.webclient
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
FROM --platform=linux/amd64 ubuntu:22.04 AS build

Check warning on line 1 in Dockerfile.webclient

View workflow job for this annotation

GitHub Actions / build

FROM --platform flag should not use a constant value

FromPlatformFlagConstDisallowed: FROM --platform flag should not use constant value "linux/amd64" More info: https://docs.docker.com/go/dockerfile/rule/from-platform-flag-const-disallowed/

ARG FLUTTER_VERSION=3.24.5
ARG NODE_MAJOR=22

RUN apt-get update && apt-get install -y --no-install-recommends \
wget curl git unzip xz-utils ca-certificates python3 gnupg \
&& rm -rf /var/lib/apt/lists/*

RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
| gpg --dearmor -o /usr/share/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" \
> /etc/apt/sources.list.d/nodesource.list \
&& apt-get update && apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/*

RUN wget -q https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz \
&& tar xf flutter_linux_${FLUTTER_VERSION}-stable.tar.xz -C /opt \
&& rm flutter_linux_${FLUTTER_VERSION}-stable.tar.xz

ENV PATH="/opt/flutter/bin:${PATH}"

RUN npm install -g yarn typescript protoc

COPY .github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff /tmp/flutter.diff
RUN cd /opt/flutter && git apply /tmp/flutter.diff && rm /tmp/flutter.diff

RUN flutter config --no-analytics && flutter precache --web

RUN git config --global --add safe.directory /opt/flutter \
&& git config --global --add safe.directory /build

WORKDIR /build
COPY . .

WORKDIR /build/flutter/web/js
RUN yarn install && yarn build

WORKDIR /build/flutter
ARG WEB_DEPS_SHA256=b66011c4fc066b90c46ba0c78884fe5d1a7e5a7fad3dce401300ad893de63818
RUN wget -q -O /tmp/web_deps.tar.gz \
https://github.com/rustdesk/doc.rustdesk.com/releases/download/console/web_deps.tar.gz \
&& echo "${WEB_DEPS_SHA256} /tmp/web_deps.tar.gz" | sha256sum -c - \
&& flutter pub get && flutter build web --release \
&& tar xzf /tmp/web_deps.tar.gz -C build/web
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# --- Production stage ---
FROM nginx:alpine

COPY --from=build /build/flutter/build/web /usr/share/nginx/html

COPY <<'EOF' /docker-entrypoint.d/90-rustdesk-config.sh
#!/bin/sh
set -e
CONFIG=/usr/share/nginx/html/config.json
if [ -n "$RUSTDESK_HOST" ]; then
cat > "$CONFIG" <<CONF
{
"host": "${RUSTDESK_HOST}",
"relay": "${RUSTDESK_RELAY:-$RUSTDESK_HOST}",
"key": "${RUSTDESK_KEY:-}",
"api": "${RUSTDESK_API:-}"
}
CONF
echo "config.json written: host=$RUSTDESK_HOST relay=${RUSTDESK_RELAY:-$RUSTDESK_HOST}"
fi
EOF
RUN chmod +x /docker-entrypoint.d/90-rustdesk-config.sh
1 change: 1 addition & 0 deletions flutter/web/js/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
legacy-peer-deps=true
8 changes: 6 additions & 2 deletions flutter/web/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
"scripts": {
"dev": "vite",
"build": "python3 ./gen_js_from_hbb.py > src/gen_js_from_hbb.ts && python3 ./ts_proto.py && tsc && vite build",
"preview": "vite preview"
"preview": "vite preview",
"test": "vitest run"
},
"devDependencies": {
"@vitest/coverage-v8": "^4.1.11",
"jsdom": "^30.0.1",
"typescript": "^4.4.4",
"vite": "2.8"
"vite": "2.8",
"vitest": "^4.1.11"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
},
"dependencies": {
"@types/node": "^18.19.130",
Expand Down
57 changes: 57 additions & 0 deletions flutter/web/js/src/codec.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* @vitest-environment jsdom
*/
import { describe, it, expect, vi, beforeEach } from "vitest";

vi.mock("wasm-feature-detect", () => ({
simd: vi.fn(),
}));

import { simd } from "wasm-feature-detect";
import { loadVp9 } from "./codec";

describe("loadVp9", () => {
let mockDecoder: any;
let mockLoadClass: any;

beforeEach(() => {
mockDecoder = {
init: vi.fn((cb: Function) => cb(mockDecoder)),
};
mockLoadClass = vi.fn((className: string, callback: Function, opts: any) => {
callback(vi.fn().mockResolvedValue(mockDecoder));
});
(window as any).OGVLoader = { loadClass: mockLoadClass };
(window as any).videoCodecClass = undefined;
});

it("loads SIMD codec when SIMD is supported", async () => {
vi.mocked(simd).mockResolvedValue(true);
const callback = vi.fn();
await loadVp9(callback);
expect(mockLoadClass).toHaveBeenCalledWith(
"OGVDecoderVideoVP9SIMDW",
expect.any(Function),
{ worker: true, threading: true }
);
expect(callback).toHaveBeenCalledWith(mockDecoder);
});

it("loads non-SIMD codec when SIMD is not supported", async () => {
vi.mocked(simd).mockResolvedValue(false);
const callback = vi.fn();
await loadVp9(callback);
expect(mockLoadClass).toHaveBeenCalledWith(
"OGVDecoderVideoVP9W",
expect.any(Function),
{ worker: true, threading: true }
);
expect(callback).toHaveBeenCalledWith(mockDecoder);
});

it("sets window.videoCodecClass", async () => {
vi.mocked(simd).mockResolvedValue(false);
await loadVp9(vi.fn());
expect((window as any).videoCodecClass).toBeDefined();
});
});
Loading
Loading