From 875dae58d7eca63a107adda92e97f839027625bf Mon Sep 17 00:00:00 2001 From: lycaon Date: Tue, 14 Jul 2026 02:26:55 -0600 Subject: [PATCH] feat: ship signed Android thin client --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/workflows/release.yml | 132 ++++- README.md | 47 +- SECURITY.md | 2 +- apps/desktop/package.json | 2 +- apps/desktop/src/target-manager.ts | 2 +- apps/mobile/README.md | 23 + apps/mobile/android/.gitignore | 101 ++++ apps/mobile/android/app/.gitignore | 2 + apps/mobile/android/app/build.gradle | 88 +++ .../mobile/android/app/capacitor.build.gradle | 19 + apps/mobile/android/app/proguard-rules.pro | 21 + .../t4code/AppIdentityTest.java | 20 + .../android/app/src/main/AndroidManifest.xml | 42 ++ .../lycaonsolutions/t4code/MainActivity.java | 13 + .../t4code/T4SecureStoragePlugin.java | 168 ++++++ .../main/res/drawable-land-hdpi/splash.png | Bin 0 -> 13196 bytes .../main/res/drawable-land-mdpi/splash.png | Bin 0 -> 8044 bytes .../main/res/drawable-land-xhdpi/splash.png | Bin 0 -> 21795 bytes .../main/res/drawable-land-xxhdpi/splash.png | Bin 0 -> 32086 bytes .../main/res/drawable-land-xxxhdpi/splash.png | Bin 0 -> 44598 bytes .../main/res/drawable-port-hdpi/splash.png | Bin 0 -> 13816 bytes .../main/res/drawable-port-mdpi/splash.png | Bin 0 -> 8337 bytes .../main/res/drawable-port-xhdpi/splash.png | Bin 0 -> 23170 bytes .../main/res/drawable-port-xxhdpi/splash.png | Bin 0 -> 33572 bytes .../main/res/drawable-port-xxxhdpi/splash.png | Bin 0 -> 45827 bytes .../drawable-v24/ic_launcher_foreground.xml | 34 ++ .../res/drawable/ic_launcher_background.xml | 170 ++++++ .../app/src/main/res/drawable/splash.png | Bin 0 -> 8044 bytes .../app/src/main/res/layout/activity_main.xml | 12 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 1866 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 1844 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 1866 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1395 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 1471 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 1395 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3088 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 2389 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 3088 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 4746 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 3254 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 4746 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 6112 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 2224 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 6112 bytes .../app/src/main/res/values/colors.xml | 6 + .../res/values/ic_launcher_background.xml | 4 + .../app/src/main/res/values/strings.xml | 7 + .../app/src/main/res/values/styles.xml | 22 + .../app/src/main/res/xml/file_paths.xml | 5 + apps/mobile/android/build.gradle | 29 + apps/mobile/android/capacitor.settings.gradle | 3 + apps/mobile/android/gradle.properties | 22 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43764 bytes .../gradle/wrapper/gradle-wrapper.properties | 8 + apps/mobile/android/gradlew | 251 ++++++++ apps/mobile/android/gradlew.bat | 94 +++ apps/mobile/android/settings.gradle | 5 + apps/mobile/android/variables.gradle | 16 + .../mobile/assets/android-icon-foreground.svg | 12 + apps/mobile/capacitor.config.json | 17 + apps/mobile/package.json | 20 + apps/mobile/scripts/prepare-web.mjs | 56 ++ apps/mobile/scripts/prepare-web.test.mjs | 64 ++ apps/mobile/scripts/run-gradle.mjs | 39 ++ apps/site/package.json | 2 +- apps/site/src/docs/content.ts | 46 +- apps/site/src/landing/Landing.tsx | 63 +- apps/site/src/release.ts | 29 +- apps/site/src/site.css | 58 ++ apps/site/test/docs-nav.test.ts | 2 + apps/site/test/release.test.ts | 22 +- apps/web/package.json | 2 +- apps/web/src/application.tsx | 18 + .../src/components/MobileConnectionAction.tsx | 82 +++ .../src/components/MobileConnectionScreen.tsx | 108 ++++ apps/web/src/components/Titlebar.tsx | 2 + apps/web/src/main.tsx | 29 +- apps/web/src/platform/browser-shell-port.ts | 21 +- apps/web/src/platform/native-mobile.ts | 230 ++++++++ apps/web/test/browser-platform.test.ts | 22 + apps/web/test/native-mobile.test.tsx | 123 ++++ compat/omp-app-matrix.json | 2 +- docs/TAILNET_REMOTE.md | 46 +- e2e/site-mobile-docs.spec.ts | 25 + package.json | 2 +- packages/client/package.json | 2 +- packages/client/src/omp-client-frames.ts | 2 +- packages/fixture-server/package.json | 2 +- packages/protocol/package.json | 2 +- packages/remote/package.json | 2 +- packages/service-manager/package.json | 2 +- packages/ui/package.json | 2 +- pnpm-lock.yaml | 551 +++++++++++++++--- scripts/check-release-consistency.mjs | 14 +- scripts/check-release-consistency.test.mjs | 19 +- scripts/inspect-macos-dmg.test.mjs | 2 +- scripts/tailnet-gateway.mjs | 33 +- scripts/tailnet-gateway.test.mjs | 76 ++- scripts/tailnet-service.mjs | 17 + scripts/tailnet-service.test.mjs | 16 + scripts/wait-for-release-assets.test.mjs | 21 +- 104 files changed, 3056 insertions(+), 231 deletions(-) create mode 100644 apps/mobile/README.md create mode 100644 apps/mobile/android/.gitignore create mode 100644 apps/mobile/android/app/.gitignore create mode 100644 apps/mobile/android/app/build.gradle create mode 100644 apps/mobile/android/app/capacitor.build.gradle create mode 100644 apps/mobile/android/app/proguard-rules.pro create mode 100644 apps/mobile/android/app/src/androidTest/java/com/lycaonsolutions/t4code/AppIdentityTest.java create mode 100644 apps/mobile/android/app/src/main/AndroidManifest.xml create mode 100644 apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/MainActivity.java create mode 100644 apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4SecureStoragePlugin.java create mode 100644 apps/mobile/android/app/src/main/res/drawable-land-hdpi/splash.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-land-mdpi/splash.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-land-xxxhdpi/splash.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-port-hdpi/splash.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-port-mdpi/splash.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-port-xhdpi/splash.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-port-xxhdpi/splash.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 apps/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 apps/mobile/android/app/src/main/res/drawable/splash.png create mode 100644 apps/mobile/android/app/src/main/res/layout/activity_main.xml create mode 100644 apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/values/colors.xml create mode 100644 apps/mobile/android/app/src/main/res/values/ic_launcher_background.xml create mode 100644 apps/mobile/android/app/src/main/res/values/strings.xml create mode 100644 apps/mobile/android/app/src/main/res/values/styles.xml create mode 100644 apps/mobile/android/app/src/main/res/xml/file_paths.xml create mode 100644 apps/mobile/android/build.gradle create mode 100644 apps/mobile/android/capacitor.settings.gradle create mode 100644 apps/mobile/android/gradle.properties create mode 100644 apps/mobile/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 apps/mobile/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 apps/mobile/android/gradlew create mode 100644 apps/mobile/android/gradlew.bat create mode 100644 apps/mobile/android/settings.gradle create mode 100644 apps/mobile/android/variables.gradle create mode 100644 apps/mobile/assets/android-icon-foreground.svg create mode 100644 apps/mobile/capacitor.config.json create mode 100644 apps/mobile/package.json create mode 100644 apps/mobile/scripts/prepare-web.mjs create mode 100644 apps/mobile/scripts/prepare-web.test.mjs create mode 100644 apps/mobile/scripts/run-gradle.mjs create mode 100644 apps/web/src/application.tsx create mode 100644 apps/web/src/components/MobileConnectionAction.tsx create mode 100644 apps/web/src/components/MobileConnectionScreen.tsx create mode 100644 apps/web/src/platform/native-mobile.ts create mode 100644 apps/web/test/native-mobile.test.tsx diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 0fa84271..0ef0b2e4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -10,7 +10,7 @@ body: id: version attributes: label: T4 Code version - placeholder: "0.1.8" + placeholder: "0.1.9" validations: required: true - type: dropdown diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2947b354..18519151 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Release desktop builds +name: Release app builds on: push: @@ -159,6 +159,115 @@ jobs: if-no-files-found: error retention-days: 7 + build-android: + needs: verify + runs-on: ubuntu-24.04 + timeout-minutes: 35 + steps: + - name: Check out verified release source + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: ${{ needs.verify.outputs.source_sha }} + persist-credentials: false + + - name: Install pnpm + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 + with: + version: 11.10.0 + + - name: Install Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: 24.13.1 + cache: pnpm + + - name: Install Java + uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 + with: + distribution: temurin + java-version: "21" + cache: gradle + + - name: Install Android SDK + uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3 + + - name: Install pinned Android platform and build tools + run: sdkmanager --install "platforms;android-36" "build-tools;36.0.0" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Restore and validate Android release signing key + shell: bash + env: + T4_ANDROID_KEYSTORE_BASE64: ${{ secrets.T4_ANDROID_KEYSTORE_BASE64 }} + T4_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.T4_ANDROID_KEYSTORE_PASSWORD }} + T4_ANDROID_KEY_ALIAS: ${{ secrets.T4_ANDROID_KEY_ALIAS }} + T4_ANDROID_KEY_PASSWORD: ${{ secrets.T4_ANDROID_KEY_PASSWORD }} + run: | + set -euo pipefail + for name in \ + T4_ANDROID_KEYSTORE_BASE64 \ + T4_ANDROID_KEYSTORE_PASSWORD \ + T4_ANDROID_KEY_ALIAS \ + T4_ANDROID_KEY_PASSWORD + do + if [[ -z "${!name:-}" ]]; then + echo "required GitHub Actions secret ${name} is not configured" >&2 + exit 1 + fi + done + keystore_path="$RUNNER_TEMP/t4-code-release.jks" + printf '%s' "$T4_ANDROID_KEYSTORE_BASE64" | base64 --decode > "$keystore_path" + chmod 600 "$keystore_path" + keytool -list \ + -keystore "$keystore_path" \ + -storepass "$T4_ANDROID_KEYSTORE_PASSWORD" \ + -alias "$T4_ANDROID_KEY_ALIAS" >/dev/null + + - name: Build signed Android APK + env: + T4_ANDROID_KEYSTORE_PATH: ${{ runner.temp }}/t4-code-release.jks + T4_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.T4_ANDROID_KEYSTORE_PASSWORD }} + T4_ANDROID_KEY_ALIAS: ${{ secrets.T4_ANDROID_KEY_ALIAS }} + T4_ANDROID_KEY_PASSWORD: ${{ secrets.T4_ANDROID_KEY_PASSWORD }} + run: | + pnpm build:web + pnpm --filter @t4-code/mobile build:android:release + + - name: Inspect signed Android APK + shell: bash + env: + VERSION: ${{ needs.verify.outputs.version }} + run: | + set -euo pipefail + apk="apps/mobile/android/app/build/outputs/apk/release/app-release.apk" + test -f "$apk" + build_tools="${ANDROID_SDK_ROOT:-$ANDROID_HOME}/build-tools/36.0.0" + export PATH="$build_tools:$PATH" + apksigner verify --verbose "$apk" + aapt dump badging "$apk" | grep -F "package: name='com.lycaonsolutions.t4code'" + aapt dump badging "$apk" | grep -F "versionName='${VERSION}'" + + - name: Stage Android artifact + shell: bash + env: + VERSION: ${{ needs.verify.outputs.version }} + run: | + mkdir -p artifacts + cp \ + apps/mobile/android/app/build/outputs/apk/release/app-release.apk \ + "artifacts/T4-Code-${VERSION}-android.apk" + (cd artifacts && sha256sum T4-Code-* > SHA256SUMS-android.txt) + + - name: Upload Android artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: android-release + path: artifacts/* + if-no-files-found: error + retention-days: 7 + build-macos: needs: verify runs-on: macos-15 @@ -208,7 +317,7 @@ jobs: retention-days: 7 publish: - needs: [verify, build-linux, build-macos] + needs: [verify, build-android, build-linux, build-macos] runs-on: ubuntu-24.04 timeout-minutes: 10 permissions: @@ -239,7 +348,7 @@ jobs: - name: Assemble checksums shell: bash - run: cat artifacts/SHA256SUMS-linux.txt artifacts/SHA256SUMS-macos.txt | sort -k2 > artifacts/SHA256SUMS.txt + run: cat artifacts/SHA256SUMS-android.txt artifacts/SHA256SUMS-linux.txt artifacts/SHA256SUMS-macos.txt | sort -k2 > artifacts/SHA256SUMS.txt - name: Publish GitHub release uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 @@ -248,21 +357,23 @@ jobs: generate_release_notes: true fail_on_unmatched_files: true body: | - ## Tailnet web app + ## Android app + + This release includes a signed Android APK for direct installation. It is a thin client: the T4 Code interface is bundled in the APK, while OMP sessions and runtime state remain on the host. On first run, enter the host's HTTPS Tailscale address and complete the normal T4 pairing flow. - The source-hosted Tailnet client is now installable from its titlebar on Android and iPhone. It opens in a standalone window with the T4 Code icon; after installation, the same titlebar action reloads the live client. This remains an online thin client and does not cache host configuration, transcripts, or session state for offline use. + Android device credentials are encrypted with an app-specific Android Keystore key. The app does not contain an OMP appserver and does not require a public route; the phone and host should use the same tailnet. - ## Session controls + ## Browser and iOS - Queue and Steer no longer attach a volatile active-turn revision that OMP can reject as stale. Normal prompts keep their revision guard, and prompt leases remain supported when negotiated. Fast mode copy now accurately describes a request for provider priority processing without implying a change in reasoning effort. + The installable Tailnet web app remains available. iOS TestFlight is coming soon; there is no iOS binary in this release. - ## Development guardrails + ## Connection security - The arbitrary physical-line ceiling has been removed. File boundaries are judged by cohesion and reviewability rather than a hard line count. + The Tailnet gateway accepts only the configured HTTPS web origin and the exact native app origins. It rejects wildcard, opaque, and untrusted origins. Keep the route on Tailscale Serve and do not enable Funnel. ## Install notes - Linux packages are built for x86_64. macOS packages are built for Apple Silicon. + The Android APK is signed and supports Android 7.0 or later. Linux packages are built for x86_64. macOS packages are built for Apple Silicon. ## Runtime compatibility @@ -276,6 +387,7 @@ jobs: Verify downloads with `SHA256SUMS.txt`. files: | + artifacts/T4-Code-*.apk artifacts/T4-Code-*.deb artifacts/T4-Code-*.AppImage artifacts/T4-Code-*.dmg diff --git a/README.md b/README.md index e0087f62..f5dcdbf4 100644 --- a/README.md +++ b/README.md @@ -4,38 +4,47 @@ T4 Code is a free, open-source (MIT) desktop app for [Oh My Pi](https://github.c ![T4 Code main window](docs/assets/t4-code-main.png) -[**Download v0.1.8**](https://github.com/LycaonLLC/t4-code/releases/tag/v0.1.8) · [**Docs**](https://t4code.net/docs) · [**Get the source**](#build-from-source) +[**Download v0.1.9**](https://github.com/LycaonLLC/t4-code/releases/tag/v0.1.9) · [**Docs**](https://t4code.net/docs) · [**Get the source**](#build-from-source) ## Requirements -T4 Code needs an OMP build with desktop appserver support. For v0.1.8, use the public integration build below. +T4 Code needs an OMP build with desktop appserver support. For v0.1.9, use the public integration build below. -T4 Code v0.1.8 was verified with OMP 16.5.0 built from [`d4a0b934`](https://github.com/lyc-aon/oh-my-pi/commit/d4a0b9344e1796c0e56041cfeea3431a8a728e61), tagged [`t4code-16.5.0-appserver-3`](https://github.com/lyc-aon/oh-my-pi/tree/t4code-16.5.0-appserver-3). That public integration is based on the official upstream [`v16.5.0`](https://github.com/can1357/oh-my-pi/tree/v16.5.0) tag at [`3047c27c`](https://github.com/can1357/oh-my-pi/commit/3047c27c332c5629c8e063283d349384c10c9a56). It adds bounded large-session replay, complete desktop runtime events, catalog-backed session management, ordered remote delivery, cross-client control-state convergence, terminal streaming-state settlement, and restart-safe session teardown. The official upstream v16.5.0 tag has no `appserver` command, so it cannot host T4 Code. The verified runtime is a normal build from the public `lyc-aon/oh-my-pi` source; T4 Code does not depend on private home-directory files, an auth broker, or a custom Codex CLI fork. T4 Code vendors `@oh-my-pi/app-wire` 0.5.2 from integration commit [`5d4315ee`](https://github.com/lyc-aon/oh-my-pi/commit/5d4315eea317260fec030e2b4726f10fed0cd5f6), source tree `713688e8099d4553a0a30b1bf415a7cffb5963f4`. +T4 Code v0.1.9 was verified with OMP 16.5.0 built from [`d4a0b934`](https://github.com/lyc-aon/oh-my-pi/commit/d4a0b9344e1796c0e56041cfeea3431a8a728e61), tagged [`t4code-16.5.0-appserver-3`](https://github.com/lyc-aon/oh-my-pi/tree/t4code-16.5.0-appserver-3). That public integration is based on the official upstream [`v16.5.0`](https://github.com/can1357/oh-my-pi/tree/v16.5.0) tag at [`3047c27c`](https://github.com/can1357/oh-my-pi/commit/3047c27c332c5629c8e063283d349384c10c9a56). It adds bounded large-session replay, complete desktop runtime events, catalog-backed session management, ordered remote delivery, cross-client control-state convergence, terminal streaming-state settlement, and restart-safe session teardown. The official upstream v16.5.0 tag has no `appserver` command, so it cannot host T4 Code. The verified runtime is a normal build from the public `lyc-aon/oh-my-pi` source; T4 Code does not depend on private home-directory files, an auth broker, or a custom Codex CLI fork. T4 Code vendors `@oh-my-pi/app-wire` 0.5.2 from integration commit [`5d4315ee`](https://github.com/lyc-aon/oh-my-pi/commit/5d4315eea317260fec030e2b4726f10fed0cd5f6), source tree `713688e8099d4553a0a30b1bf415a7cffb5963f4`. | Platform | Arch | Package | | --- | --- | --- | +| Android | arm64, armv7, x86_64 | `.apk` (**signed**) | | Linux | x86_64 | `.deb`, AppImage | | macOS | Apple Silicon (arm64) | `.dmg`, `.zip` (**unsigned, see below**) | -No Windows build and no Intel Mac build in v0.1.8. +No Windows build and no Intel Mac build in v0.1.9. The iOS TestFlight build is coming soon. -## What changed in v0.1.8 +## What changed in v0.1.9 -- The Tailnet browser client can now be installed to an Android or iPhone home screen. The titlebar download button uses the browser's native install prompt when available and otherwise shows the Add to Home Screen path. -- Installed web apps open in a standalone window with the T4 Code icon. The same titlebar slot becomes a Reload T4 Code button, so a phone user does not need browser chrome to refresh the client. -- Queue and Steer now work during an active turn without sending a stale session revision. Normal prompts keep their revision guard, and negotiated prompt leases remain supported. -- Fast mode now explains what the host actually requests: provider priority processing when supported. It does not imply a lower thinking effort. -- The installed client remains online-only. It fetches the current gateway UI on launch and does not cache host configuration, transcripts, or session state for offline use. -- The arbitrary physical-line ceiling is gone. File boundaries are now judged by cohesion and reviewability instead of a hard line count. -- The manifest, phone icons, gateway content type, install states, active-turn commands, and 320 × 568 titlebar layout are covered by unit and built-app browser tests. The verified OMP 16.5.0 and app-wire 0.5.2 runtime contract is unchanged from v0.1.7. +- Android now has a signed, installable APK. It is a thin client: the UI ships inside the app, while sessions and runtime state stay on your T4 Code host. +- First run accepts an HTTPS Tailscale hostname, checks the secure WebSocket route, and then uses the normal T4 pairing flow. +- Android device credentials are encrypted with an app-specific Android Keystore key. Only the non-secret Tailnet hostname is kept in web storage. +- The Tailnet gateway accepts the exact Capacitor Android and iOS origins while continuing to reject wildcard, opaque, and untrusted origins. +- The connection screen and titlebar controls are sized for small phone displays. A user can clear the saved host and pair again without reinstalling. +- The browser-installable PWA remains available, and the iOS download slot now reports that TestFlight is coming soon without advertising a build that is not published. ## Install +### Android + +1. On the Android phone, sign in to Tailscale with an account that can reach the T4 Code host. +2. Download [`T4-Code-0.1.9-android.apk`](https://github.com/LycaonLLC/t4-code/releases/download/v0.1.9/T4-Code-0.1.9-android.apk). +3. If Android asks, allow your browser or file manager to install unknown apps, then install the APK. +4. Open T4 Code and enter the host's HTTPS Tailscale address, including its port. + +The APK does not contain an appserver or expose one to the public internet. It connects to the separately running Tailnet gateway on your OMP host. + ### Linux (Debian/Ubuntu) ```sh -wget https://github.com/LycaonLLC/t4-code/releases/download/v0.1.8/T4-Code-0.1.8-linux-amd64.deb -sudo apt install ./T4-Code-0.1.8-linux-amd64.deb +wget https://github.com/LycaonLLC/t4-code/releases/download/v0.1.9/T4-Code-0.1.9-linux-amd64.deb +sudo apt install ./T4-Code-0.1.9-linux-amd64.deb ``` Use `apt install` rather than `dpkg -i` so system dependencies resolve automatically. @@ -43,17 +52,17 @@ Use `apt install` rather than `dpkg -i` so system dependencies resolve automatic ### Linux (AppImage) ```sh -wget https://github.com/LycaonLLC/t4-code/releases/download/v0.1.8/T4-Code-0.1.8-linux-x86_64.AppImage -chmod +x T4-Code-0.1.8-linux-x86_64.AppImage -./T4-Code-0.1.8-linux-x86_64.AppImage +wget https://github.com/LycaonLLC/t4-code/releases/download/v0.1.9/T4-Code-0.1.9-linux-x86_64.AppImage +chmod +x T4-Code-0.1.9-linux-x86_64.AppImage +./T4-Code-0.1.9-linux-x86_64.AppImage ``` ### macOS (Apple Silicon) > [!WARNING] -> **The macOS v0.1.8 build is unsigned and unnotarized.** Apple has not signed or notarized it, so Gatekeeper can report a "damaged" app or an unidentified developer. Only continue if you trust the release from this repository. You can always build from source instead. +> **The macOS v0.1.9 build is unsigned and unnotarized.** Apple has not signed or notarized it, so Gatekeeper can report a "damaged" app or an unidentified developer. Only continue if you trust the release from this repository. You can always build from source instead. -1. Download [`T4-Code-0.1.8-mac-arm64.dmg`](https://github.com/LycaonLLC/t4-code/releases/download/v0.1.8/T4-Code-0.1.8-mac-arm64.dmg) (or [`T4-Code-0.1.8-mac-arm64.zip`](https://github.com/LycaonLLC/t4-code/releases/download/v0.1.8/T4-Code-0.1.8-mac-arm64.zip)). +1. Download [`T4-Code-0.1.9-mac-arm64.dmg`](https://github.com/LycaonLLC/t4-code/releases/download/v0.1.9/T4-Code-0.1.9-mac-arm64.dmg) (or [`T4-Code-0.1.9-mac-arm64.zip`](https://github.com/LycaonLLC/t4-code/releases/download/v0.1.9/T4-Code-0.1.9-mac-arm64.zip)). 2. Drag `T4 Code.app` into `/Applications`. 3. If Gatekeeper blocks the app and you choose to proceed, remove the quarantine attributes from the copied app bundle: diff --git a/SECURITY.md b/SECURITY.md index 6c1077d2..791cad49 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -26,4 +26,4 @@ We read every report and will reply to tell you what happens next. This is a sma - T4 Code is a desktop client. The OMP runtime is a separate project; runtime vulnerabilities belong at . - Pairing credentials are encrypted with the OS keychain via Electron `safeStorage`. Reports about credential handling, the pairing flow, or the `t4-code://` deep-link handler are especially welcome. -- The macOS v0.1.8 build is unsigned and unnotarized; that is a known, disclosed limitation, not a vulnerability report. Removing `com.apple.quarantine` changes Gatekeeper handling but does not sign, notarize, or verify the app. +- The macOS v0.1.9 build is unsigned and unnotarized; that is a known, disclosed limitation, not a vulnerability report. Removing `com.apple.quarantine` changes Gatekeeper handling but does not sign, notarize, or verify the app. diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 7f17f31a..4e1cb75b 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/desktop", - "version": "0.1.8", + "version": "0.1.9", "private": true, "type": "module", "main": "dist-electron/main.cjs", diff --git a/apps/desktop/src/target-manager.ts b/apps/desktop/src/target-manager.ts index 12e09be5..372afea6 100644 --- a/apps/desktop/src/target-manager.ts +++ b/apps/desktop/src/target-manager.ts @@ -324,7 +324,7 @@ export class DesktopTargetManager { cursorStore: this.cursorStoreFactory(targetId), capabilities: requestedCapabilities, requestedFeatures: ADDITIVE_FEATURES, - client: { name: "T4 Code", version: "0.1.8", build: "desktop", platform: process.platform }, + client: { name: "T4 Code", version: "0.1.9", build: "desktop", platform: process.platform }, reconnect: { attemptCap: 12, baseMs: 250, maxMs: 10_000 }, }; const client = createOmpClient(clientOptions); diff --git a/apps/mobile/README.md b/apps/mobile/README.md new file mode 100644 index 00000000..c42a05f7 --- /dev/null +++ b/apps/mobile/README.md @@ -0,0 +1,23 @@ +# T4 Code Mobile + +Android thin-client shell for the bundled T4 Code web application. The APK contains the UI assets and connects over secure WebSocket to a separately running T4 Tailnet gateway; it never embeds or starts an OMP appserver. + +## Build + +```bash +pnpm build:web +pnpm --filter @t4-code/mobile build:android:debug +``` + +The installable debug APK is written to `android/app/build/outputs/apk/debug/app-debug.apk`. + +A release build can be signed without committing credentials by setting all four variables before `pnpm --filter @t4-code/mobile build:android:release`: + +```text +T4_ANDROID_KEYSTORE_PATH +T4_ANDROID_KEYSTORE_PASSWORD +T4_ANDROID_KEY_ALIAS +T4_ANDROID_KEY_PASSWORD +``` + +Without those variables Gradle still creates an unsigned release APK for CI inspection. The mobile shell deliberately has no Capacitor `server.url`; `cap sync` copies `apps/web/dist` into the native application. diff --git a/apps/mobile/android/.gitignore b/apps/mobile/android/.gitignore new file mode 100644 index 00000000..48354a3d --- /dev/null +++ b/apps/mobile/android/.gitignore @@ -0,0 +1,101 @@ +# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore + +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# Android Profiling +*.hprof + +# Cordova plugins for Capacitor +capacitor-cordova-android-plugins + +# Copied web assets +app/src/main/assets/public + +# Generated Config files +app/src/main/assets/capacitor.config.json +app/src/main/assets/capacitor.plugins.json +app/src/main/res/xml/config.xml diff --git a/apps/mobile/android/app/.gitignore b/apps/mobile/android/app/.gitignore new file mode 100644 index 00000000..043df802 --- /dev/null +++ b/apps/mobile/android/app/.gitignore @@ -0,0 +1,2 @@ +/build/* +!/build/.npmkeep diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle new file mode 100644 index 00000000..62f1c0f9 --- /dev/null +++ b/apps/mobile/android/app/build.gradle @@ -0,0 +1,88 @@ +import groovy.json.JsonSlurper + +apply plugin: 'com.android.application' + +def mobilePackage = new JsonSlurper().parse(rootProject.file('../package.json')) +def mobileVersionName = System.getenv('T4_ANDROID_VERSION_NAME') ?: mobilePackage.version +def versionParts = mobileVersionName.tokenize('.').collect { part -> + def match = part =~ /^(\d+)/ + match.find() ? match.group(1).toInteger() : 0 +} +while (versionParts.size() < 3) versionParts.add(0) +def defaultVersionCode = versionParts[0] * 1000000 + versionParts[1] * 10000 + versionParts[2] +def mobileVersionCode = (System.getenv('T4_ANDROID_VERSION_CODE') ?: defaultVersionCode.toString()).toInteger() + +def releaseKeystorePath = System.getenv('T4_ANDROID_KEYSTORE_PATH') +def releaseKeystorePassword = System.getenv('T4_ANDROID_KEYSTORE_PASSWORD') +def releaseKeyAlias = System.getenv('T4_ANDROID_KEY_ALIAS') +def releaseKeyPassword = System.getenv('T4_ANDROID_KEY_PASSWORD') +def hasReleaseSigning = [ + releaseKeystorePath, + releaseKeystorePassword, + releaseKeyAlias, + releaseKeyPassword, +].every { value -> value != null && !value.isBlank() } + +android { + namespace = "com.lycaonsolutions.t4code" + compileSdk = rootProject.ext.compileSdkVersion + defaultConfig { + applicationId "com.lycaonsolutions.t4code" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode mobileVersionCode + versionName mobileVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + aaptOptions { + // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. + // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 + ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' + } + } + signingConfigs { + if (hasReleaseSigning) { + release { + storeFile file(releaseKeystorePath) + storePassword releaseKeystorePassword + keyAlias releaseKeyAlias + keyPassword releaseKeyPassword + } + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + if (hasReleaseSigning) signingConfig signingConfigs.release + } + } +} + +repositories { + flatDir{ + dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" + implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" + implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion" + implementation project(':capacitor-android') + testImplementation "junit:junit:$junitVersion" + androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" + androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" + implementation project(':capacitor-cordova-android-plugins') +} + +apply from: 'capacitor.build.gradle' + +try { + def servicesJSON = file('google-services.json') + if (servicesJSON.text) { + apply plugin: 'com.google.gms.google-services' + } +} catch(Exception e) { + logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work") +} diff --git a/apps/mobile/android/app/capacitor.build.gradle b/apps/mobile/android/app/capacitor.build.gradle new file mode 100644 index 00000000..bbfb44fa --- /dev/null +++ b/apps/mobile/android/app/capacitor.build.gradle @@ -0,0 +1,19 @@ +// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN + +android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 + } +} + +apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" +dependencies { + + +} + + +if (hasProperty('postBuildExtras')) { + postBuildExtras() +} diff --git a/apps/mobile/android/app/proguard-rules.pro b/apps/mobile/android/app/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/apps/mobile/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/apps/mobile/android/app/src/androidTest/java/com/lycaonsolutions/t4code/AppIdentityTest.java b/apps/mobile/android/app/src/androidTest/java/com/lycaonsolutions/t4code/AppIdentityTest.java new file mode 100644 index 00000000..ef13627a --- /dev/null +++ b/apps/mobile/android/app/src/androidTest/java/com/lycaonsolutions/t4code/AppIdentityTest.java @@ -0,0 +1,20 @@ +package com.lycaonsolutions.t4code; + +import static org.junit.Assert.assertEquals; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public final class AppIdentityTest { + @Test + public void applicationIdMatchesReleaseIdentity() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.lycaonsolutions.t4code", context.getPackageName()); + } +} diff --git a/apps/mobile/android/app/src/main/AndroidManifest.xml b/apps/mobile/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c96bbd17 --- /dev/null +++ b/apps/mobile/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/MainActivity.java b/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/MainActivity.java new file mode 100644 index 00000000..d89da696 --- /dev/null +++ b/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/MainActivity.java @@ -0,0 +1,13 @@ +package com.lycaonsolutions.t4code; + +import android.os.Bundle; + +import com.getcapacitor.BridgeActivity; + +public class MainActivity extends BridgeActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + registerPlugin(T4SecureStoragePlugin.class); + super.onCreate(savedInstanceState); + } +} diff --git a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4SecureStoragePlugin.java b/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4SecureStoragePlugin.java new file mode 100644 index 00000000..8b0d9111 --- /dev/null +++ b/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4SecureStoragePlugin.java @@ -0,0 +1,168 @@ +package com.lycaonsolutions.t4code; + +import android.content.Context; +import android.content.SharedPreferences; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; +import android.util.Base64; + +import com.getcapacitor.JSObject; +import com.getcapacitor.Plugin; +import com.getcapacitor.PluginCall; +import com.getcapacitor.PluginMethod; +import com.getcapacitor.annotation.CapacitorPlugin; + +import org.json.JSONObject; + +import java.nio.charset.StandardCharsets; +import java.security.KeyStore; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; + +@CapacitorPlugin(name = "T4SecureStorage") +public final class T4SecureStoragePlugin extends Plugin { + private static final String KEYSTORE_PROVIDER = "AndroidKeyStore"; + private static final String KEY_ALIAS = "t4_code_device_credentials_v1"; + private static final String CIPHER_TRANSFORMATION = "AES/GCM/NoPadding"; + private static final String PREFERENCES_NAME = "t4_code_secure_storage"; + private static final String PREFERENCE_IV = "credentials_iv"; + private static final String PREFERENCE_PAYLOAD = "credentials_payload"; + private static final int GCM_TAG_BITS = 128; + private static final int MAX_DEVICE_ID_LENGTH = 256; + private static final int MAX_DEVICE_TOKEN_LENGTH = 512; + + @PluginMethod + public void getCredentials(PluginCall call) { + synchronized (this) { + try { + JSObject result = new JSObject(); + JSObject credentials = readCredentials(); + result.put("credentials", credentials == null ? JSONObject.NULL : credentials); + call.resolve(result); + } catch (Exception error) { + clearStoredStateBestEffort(); + call.reject("Stored credentials could not be decrypted."); + } + } + } + + @PluginMethod + public void setCredentials(PluginCall call) { + String deviceId = call.getString("deviceId"); + String deviceToken = call.getString("deviceToken"); + if (!isBoundedText(deviceId, MAX_DEVICE_ID_LENGTH) || !isBoundedText(deviceToken, MAX_DEVICE_TOKEN_LENGTH)) { + call.reject("Invalid device credentials."); + return; + } + + synchronized (this) { + try { + JSObject credentials = new JSObject(); + credentials.put("deviceId", deviceId); + credentials.put("deviceToken", deviceToken); + storeCredentials(credentials.toString()); + call.resolve(); + } catch (Exception error) { + call.reject("Device credentials could not be stored."); + } + } + } + + @PluginMethod + public void clearCredentials(PluginCall call) { + synchronized (this) { + try { + clearStoredState(); + call.resolve(); + } catch (Exception error) { + call.reject("Device credentials could not be cleared."); + } + } + } + + private boolean isBoundedText(String value, int maxLength) { + if (value == null || value.isEmpty() || value.length() > maxLength) return false; + for (int index = 0; index < value.length(); index += 1) { + char character = value.charAt(index); + if (character <= 0x1f || character == 0x7f) return false; + } + return true; + } + + private SharedPreferences preferences() { + return getContext().getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); + } + + private SecretKey getOrCreateKey() throws Exception { + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER); + keyStore.load(null); + if (keyStore.containsAlias(KEY_ALIAS)) { + return (SecretKey) keyStore.getKey(KEY_ALIAS, null); + } + + KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER); + KeyGenParameterSpec specification = new KeyGenParameterSpec.Builder( + KEY_ALIAS, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT + ) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setKeySize(256) + .build(); + generator.init(specification); + return generator.generateKey(); + } + + private void storeCredentials(String plaintext) throws Exception { + Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION); + cipher.init(Cipher.ENCRYPT_MODE, getOrCreateKey()); + byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); + + boolean committed = preferences().edit() + .putString(PREFERENCE_IV, Base64.encodeToString(cipher.getIV(), Base64.NO_WRAP)) + .putString(PREFERENCE_PAYLOAD, Base64.encodeToString(ciphertext, Base64.NO_WRAP)) + .commit(); + if (!committed) throw new IllegalStateException("secure preferences commit failed"); + } + + private JSObject readCredentials() throws Exception { + String encodedIv = preferences().getString(PREFERENCE_IV, null); + String encodedPayload = preferences().getString(PREFERENCE_PAYLOAD, null); + if (encodedIv == null && encodedPayload == null) return null; + if (encodedIv == null || encodedPayload == null) throw new IllegalStateException("incomplete secure credential state"); + + byte[] iv = Base64.decode(encodedIv, Base64.NO_WRAP); + byte[] ciphertext = Base64.decode(encodedPayload, Base64.NO_WRAP); + Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION); + cipher.init(Cipher.DECRYPT_MODE, getOrCreateKey(), new GCMParameterSpec(GCM_TAG_BITS, iv)); + String plaintext = new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8); + JSObject credentials = JSObject.fromJSONObject(new JSONObject(plaintext)); + + String deviceId = credentials.getString("deviceId"); + String deviceToken = credentials.getString("deviceToken"); + if (!isBoundedText(deviceId, MAX_DEVICE_ID_LENGTH) || !isBoundedText(deviceToken, MAX_DEVICE_TOKEN_LENGTH)) { + throw new IllegalStateException("invalid secure credential payload"); + } + return credentials; + } + + private void clearStoredState() throws Exception { + if (!preferences().edit().clear().commit()) { + throw new IllegalStateException("secure preferences clear failed"); + } + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER); + keyStore.load(null); + if (keyStore.containsAlias(KEY_ALIAS)) keyStore.deleteEntry(KEY_ALIAS); + } + + private void clearStoredStateBestEffort() { + try { + clearStoredState(); + } catch (Exception ignored) { + // The caller still receives a generic failure; never expose credential details. + } + } +} diff --git a/apps/mobile/android/app/src/main/res/drawable-land-hdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-hdpi/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..598442327aae21579914d9469676d83de13a95c7 GIT binary patch literal 13196 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/drawable-land-mdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-mdpi/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..5fe4dd61e62be154a806413c4bb329a3aee0b597 GIT binary patch literal 8044 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..5c0136521cffda9d49ec3dabe0168394e5debf1d GIT binary patch literal 21795 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..8343f58676730653f27ab8cddb8b09c48161659b GIT binary patch literal 32086 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/drawable-port-hdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-port-hdpi/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..9fe5ea4a0c273034d367d12bc9d601f031e68ebc GIT binary patch literal 13816 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

ty3s){AwcMNPHyR%X=e#_vzgkY;pmg%! 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∋^ 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

)N15oiLrh9d^}}7Oc-0E0_29q&I7n}gi=vY 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..468d279b013749fb3db18e0a77417273e3f45c49 GIT binary patch literal 45827 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 S=@z^-mkq96C^&cL&;J3sr|*~m literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/apps/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..c7bd21db --- /dev/null +++ b/apps/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/apps/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml b/apps/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..d5fccc53 --- /dev/null +++ b/apps/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/mobile/android/app/src/main/res/drawable/splash.png b/apps/mobile/android/app/src/main/res/drawable/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..5fe4dd61e62be154a806413c4bb329a3aee0b597 GIT binary patch literal 8044 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/layout/activity_main.xml b/apps/mobile/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..b5ad1387 --- /dev/null +++ b/apps/mobile/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..036d09bc --- /dev/null +++ b/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..036d09bc --- /dev/null +++ b/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..04e562975ba474e66248dec377c130f6d23edd35 GIT binary patch literal 1866 zcmZ`&YgAKL7QTW>AS4hVkQhizkcWVQ3kg9mNCHVnfbdK(g4%=>G$N0N@YI<>m>IC5 zqJVZlDAI>53&cWg3RcU~f`ACxQN*P}7?47ML<;CgK@bG|ufT*Y_E|<&UaCkgkbaXU}#ZswMN~KaNmG0fUHz6SbaDq80 zDG3mQ1i%J(f`Wno78qE9ARqy334+ccL+d%Oi(#UaCCIU;c(X0)?_l-%E}6Z!Pwc^VX;^n8yg_u;Nalon$xUEi5dyZQC|FIyyEs z=H}*xLZQHi%*@P~Os2lRJ_3Q@?d?52KCaX0=yZBWNXY#B{K&`%g+lT2@_PRK`NYJ; z@bGXjn?1jRn3y84lq>ZbdFDSFOB`r#lv4THrTr-mE+3u?ECoo(=! z-KC3u`Ny_$U2uf!o*&HmX{1CSON>=1l~+y^fF{?WUw8%$4en0`Xmy4p8IA`!Rq^T)eBgy_si4y<~*RQ26v?S z+f!_@HcOld zX%b=*f&WuO;>i>Qqqy_m6S|{Jzfb4iB9ywx{AK?MJ+h+%@iV!oaWf(nMs!jS`dsT{n%7{`}^-e5Ty}``6xZg+5&~kvo#z8AvXk;KL9ebKUAd`{W-vGi@Fv7w*Oy2 zqsmOn)hIG^{(#vT40QZhzl$V!N_+rj%X>^>cDklOp-zJe3JQEwSsA%XMNXPew)$Yb zflUGzeO<)kN@TZ{aw6ih5@g9)ksv9C;#*Z+t4OEGF(*r#&vks6KK}+h#!#>Ro!5IA?RXv@tPrv(#V5s0t?JT_Oljda^^^+qN3trdjvb= zMkOZio9+|!5hMNOLS>KO4RLNst*pC!C%e_iFAd^U{}9c5^9IJ)#obqX3^+H549RG- zPiSoly`Y7g^(E=z?2HB4^G$J8`f07Uy!~7iwT^o$ea6Q%*kaa|U=?M{EaRm~_lW}g zA4MkNL*nm#eU8Ly;v`%X#MY$#dKfON5Iup*9!~e($E5O!)04l%LjNtIVp4lmaU6LZ z{8yYEp2(@>UQ$fj7IF(^+x?J5JOSSxia$&FiHHv$Ya%vdn$Mp!OTA^W1@9Oxk>Ba- z^>B4{bqDv?y@#u23#o{q(`Qb)^-*IVKm2y3a;WtPwSPcZUiyrU3;mIb?T?B2$Bg?r zvgjxh@91!W2i?HMCnrfPkS`o+rJUn0^`t6JiHD!1owM#*9`!HMRV*&F5XEF2H}h!j zLUgs#ZZ(avu5uS=k$IIQrXAL6VL@-0o10slzdS@8v9$LPH~QxdemloVFHT6k-})qX zX1M-G`DAl%)cN7&o~E?U^X;;(!>e`I=0~%~zCYno#%dqy$b0&VysIE{O#C(XXnN+; zn+02Yk&7REq|;prIBLE1Nlp#3?C=9K)7X-|M{L9H-2B^J%bz1y$wkvelmGeR?jE20 zUB_1I0#y(Gwjq!e78c5u#j+Rd4ExPPVOc6ULx}06#u`(!#%DKjO^t!$j_B_Mo0B{H z7?9aZt>$V#224s8wh^8o6{FF#-f_YL;x{(ukXM}s9dJ{^nEO5Q)t?%#cD{;EI z)6*-+-r$_F-O~%FKiC=huYu$#8Svh1rqUNcvr&Y GrT+s2NzADL literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..ba8eafc39833c6b4dbba455abb85eb2687f4cde9 GIT binary patch literal 1844 zcmaJ?2{@E#9RKK`OBqQsYHNv1%gi^(U{UdnjFwu8DT%>|iZL@EaB7^YvN~KVx#lot42?NxvG1_Tv-`B~^S%G$_y7Oj|M9%v_i;CPuAMUzHxq)O zIaD{q3-CPk)}8{W+?{Q?*T{R&`?$oHUldv>>LvVf>=c5DLC zzzPH~2hh|QRXcZ&Sp_8ZD8@lRfvWaYKV#u)|M7Fi1QJJ`WdaBSyI!;HS-+5s0&HAx63^Fb!K| z+e&1*=$lTTHIs&gn+Oq(`o?se^=2&Wq;Kr3Z|aMM9p)QPoi(EaW^Kb+8R+P;7dw0) zGJUbc_-|I%S%lxg`_~h8)|!Xi!Eb9M>;m_bXSnAjU|iJ8(;M*rUz(JCsfa~;+uBcV zu0tlTM@H%h0YUU5Qu%N6gx0_Ag#~F9RO;>KS?Rnwqg)S&YJ*0u%4>dHap&;Mi(XST zGloSJy%*^QZx1bav*7K|b05bCiAQQyOmm9lge?28Y;{w>QEr<#5TsM7-WpGsmqP%kg;G5zS|TkSZLN9Hy1j`2GC?UcG=z>a?1R}1 zz#vQ5(#8TNTac_b!nXD>*`8$c9SqyUa1{L|c$+5$L@-0bL*u?4py3eR4+6|SXF$Uv z7#x%y5%u*LAN)oFh{393yqTeBES=4OVq;?o;gLHz!SpBwfyv%}y4L{*z8R;f$eNA2 zn&;ZFNueJv3M};oU8}wh>&|Ju*lEv#c)6Cu@ zgso3x`{N!xG<qd(aydwdAkflE1Ue z_E5P}h@YD!eRsEZNkdSuZ8`S=PazRBXC#kIaUh(>E|lWz!mcahM{e~UKIa~kEvW2B z$6vBfOkJ;c=g9fzr);-S7g?PcMpiXamKo`C-4G+pkql%)tg$L~u4Z-C=AQJ(rd?s{ z^DzaUryLkN&QZN=BZjhaZfauJUA0MgwUynT*MZ3)2l(X{B9i1ZN_ACYlhJcx*YiBw z$|m>OTkci4R~0lA9Wx{|-6Kzm+xV58oqso?@izXcZyICR zur5({qVUg_QfXM-(OXp?Q;}si9zVdunqJSj+;q#2+H&(`ugj*Ak`9Gr)l$b(F@5u% hgc?o0F+f&Tt*;VEN42l*2fsZCqEbAOBIm&5zX55Xn34bh literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..04e562975ba474e66248dec377c130f6d23edd35 GIT binary patch literal 1866 zcmZ`&YgAKL7QTW>AS4hVkQhizkcWVQ3kg9mNCHVnfbdK(g4%=>G$N0N@YI<>m>IC5 zqJVZlDAI>53&cWg3RcU~f`ACxQN*P}7?47ML<;CgK@bG|ufT*Y_E|<&UaCkgkbaXU}#ZswMN~KaNmG0fUHz6SbaDq80 zDG3mQ1i%J(f`Wno78qE9ARqy334+ccL+d%Oi(#UaCCIU;c(X0)?_l-%E}6Z!Pwc^VX;^n8yg_u;Nalon$xUEi5dyZQC|FIyyEs z=H}*xLZQHi%*@P~Os2lRJ_3Q@?d?52KCaX0=yZBWNXY#B{K&`%g+lT2@_PRK`NYJ; z@bGXjn?1jRn3y84lq>ZbdFDSFOB`r#lv4THrTr-mE+3u?ECoo(=! z-KC3u`Ny_$U2uf!o*&HmX{1CSON>=1l~+y^fF{?WUw8%$4en0`Xmy4p8IA`!Rq^T)eBgy_si4y<~*RQ26v?S z+f!_@HcOld zX%b=*f&WuO;>i>Qqqy_m6S|{Jzfb4iB9ywx{AK?MJ+h+%@iV!oaWf(nMs!jS`dsT{n%7{`}^-e5Ty}``6xZg+5&~kvo#z8AvXk;KL9ebKUAd`{W-vGi@Fv7w*Oy2 zqsmOn)hIG^{(#vT40QZhzl$V!N_+rj%X>^>cDklOp-zJe3JQEwSsA%XMNXPew)$Yb zflUGzeO<)kN@TZ{aw6ih5@g9)ksv9C;#*Z+t4OEGF(*r#&vks6KK}+h#!#>Ro!5IA?RXv@tPrv(#V5s0t?JT_Oljda^^^+qN3trdjvb= zMkOZio9+|!5hMNOLS>KO4RLNst*pC!C%e_iFAd^U{}9c5^9IJ)#obqX3^+H549RG- zPiSoly`Y7g^(E=z?2HB4^G$J8`f07Uy!~7iwT^o$ea6Q%*kaa|U=?M{EaRm~_lW}g zA4MkNL*nm#eU8Ly;v`%X#MY$#dKfON5Iup*9!~e($E5O!)04l%LjNtIVp4lmaU6LZ z{8yYEp2(@>UQ$fj7IF(^+x?J5JOSSxia$&FiHHv$Ya%vdn$Mp!OTA^W1@9Oxk>Ba- z^>B4{bqDv?y@#u23#o{q(`Qb)^-*IVKm2y3a;WtPwSPcZUiyrU3;mIb?T?B2$Bg?r zvgjxh@91!W2i?HMCnrfPkS`o+rJUn0^`t6JiHD!1owM#*9`!HMRV*&F5XEF2H}h!j zLUgs#ZZ(avu5uS=k$IIQrXAL6VL@-0o10slzdS@8v9$LPH~QxdemloVFHT6k-})qX zX1M-G`DAl%)cN7&o~E?U^X;;(!>e`I=0~%~zCYno#%dqy$b0&VysIE{O#C(XXnN+; zn+02Yk&7REq|;prIBLE1Nlp#3?C=9K)7X-|M{L9H-2B^J%bz1y$wkvelmGeR?jE20 zUB_1I0#y(Gwjq!e78c5u#j+Rd4ExPPVOc6ULx}06#u`(!#%DKjO^t!$j_B_Mo0B{H z7?9aZt>$V#224s8wh^8o6{FF#-f_YL;x{(ukXM}s9dJ{^nEO5Q)t?%#cD{;EI z)6*-+-r$_F-O~%FKiC=huYu$#8Svh1rqUNcvr&Y GrT+s2NzADL literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..2e8a85cd015bed32ea08743d5cdf9dcd97e944dd GIT binary patch literal 1395 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..2e8a85cd015bed32ea08743d5cdf9dcd97e944dd GIT binary patch literal 1395 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 F{4Xl`JT3qL literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..8e1579f70b590569f6face92804b07132d3c8bfc GIT binary patch literal 3088 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 DRQIdn literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..8262c74153c3e3aafeeb77a4c7a78a0082b3826a GIT binary patch literal 3254 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 literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..0d1adac888a2cb16b993a74208398339d0da231b GIT binary patch literal 4746 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 E07X%&)Bpeg literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..7854e90fdcfda535542857f57ca19f1e1c74bad8 GIT binary patch literal 6112 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 NC_E%Gxb`Co@84e84ekH{ literal 0 HcmV?d00001 diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..7854e90fdcfda535542857f57ca19f1e1c74bad8 GIT binary patch literal 6112 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 + + #E83174 + #0D0D0D + #E83174 + diff --git a/apps/mobile/android/app/src/main/res/values/ic_launcher_background.xml b/apps/mobile/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 00000000..06781cec --- /dev/null +++ b/apps/mobile/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #0D0D0D + diff --git a/apps/mobile/android/app/src/main/res/values/strings.xml b/apps/mobile/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..23cb0f2b --- /dev/null +++ b/apps/mobile/android/app/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + T4 Code + T4 Code + com.lycaonsolutions.t4code + com.lycaonsolutions.t4code + diff --git a/apps/mobile/android/app/src/main/res/values/styles.xml b/apps/mobile/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..be874e54 --- /dev/null +++ b/apps/mobile/android/app/src/main/res/values/styles.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/xml/file_paths.xml b/apps/mobile/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 00000000..bd0c4d80 --- /dev/null +++ b/apps/mobile/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/mobile/android/build.gradle b/apps/mobile/android/build.gradle new file mode 100644 index 00000000..f8f0e43b --- /dev/null +++ b/apps/mobile/android/build.gradle @@ -0,0 +1,29 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.13.0' + classpath 'com.google.gms:google-services:4.4.4' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +apply from: "variables.gradle" + +allprojects { + repositories { + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/apps/mobile/android/capacitor.settings.gradle b/apps/mobile/android/capacitor.settings.gradle new file mode 100644 index 00000000..e0f161b8 --- /dev/null +++ b/apps/mobile/android/capacitor.settings.gradle @@ -0,0 +1,3 @@ +// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN +include ':capacitor-android' +project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@8.4.1_@capacitor+core@8.4.1/node_modules/@capacitor/android/capacitor') diff --git a/apps/mobile/android/gradle.properties b/apps/mobile/android/gradle.properties new file mode 100644 index 00000000..2e87c52f --- /dev/null +++ b/apps/mobile/android/gradle.properties @@ -0,0 +1,22 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true diff --git a/apps/mobile/android/gradle/wrapper/gradle-wrapper.jar b/apps/mobile/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..1b33c55baabb587c669f562ae36f953de2481846 GIT binary patch literal 43764 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/apps/mobile/android/gradlew.bat b/apps/mobile/android/gradlew.bat new file mode 100644 index 00000000..5eed7ee8 --- /dev/null +++ b/apps/mobile/android/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/apps/mobile/android/settings.gradle b/apps/mobile/android/settings.gradle new file mode 100644 index 00000000..3b4431d7 --- /dev/null +++ b/apps/mobile/android/settings.gradle @@ -0,0 +1,5 @@ +include ':app' +include ':capacitor-cordova-android-plugins' +project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') + +apply from: 'capacitor.settings.gradle' \ No newline at end of file diff --git a/apps/mobile/android/variables.gradle b/apps/mobile/android/variables.gradle new file mode 100644 index 00000000..ee4ba41c --- /dev/null +++ b/apps/mobile/android/variables.gradle @@ -0,0 +1,16 @@ +ext { + minSdkVersion = 24 + compileSdkVersion = 36 + targetSdkVersion = 36 + androidxActivityVersion = '1.11.0' + androidxAppCompatVersion = '1.7.1' + androidxCoordinatorLayoutVersion = '1.3.0' + androidxCoreVersion = '1.17.0' + androidxFragmentVersion = '1.8.9' + coreSplashScreenVersion = '1.2.0' + androidxWebkitVersion = '1.14.0' + junitVersion = '4.13.2' + androidxJunitVersion = '1.3.0' + androidxEspressoCoreVersion = '3.7.0' + cordovaAndroidVersion = '14.0.1' +} \ No newline at end of file diff --git a/apps/mobile/assets/android-icon-foreground.svg b/apps/mobile/assets/android-icon-foreground.svg new file mode 100644 index 00000000..4f297d5f --- /dev/null +++ b/apps/mobile/assets/android-icon-foreground.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/apps/mobile/capacitor.config.json b/apps/mobile/capacitor.config.json new file mode 100644 index 00000000..0e97a78d --- /dev/null +++ b/apps/mobile/capacitor.config.json @@ -0,0 +1,17 @@ +{ + "appId": "com.lycaonsolutions.t4code", + "appName": "T4 Code", + "webDir": "dist", + "loggingBehavior": "debug", + "appendUserAgent": " T4CodeMobile/0.1.9", + "android": { + "path": "android", + "minWebViewVersion": 60, + "allowMixedContent": false, + "webContentsDebuggingEnabled": false + }, + "server": { + "hostname": "localhost", + "androidScheme": "https" + } +} diff --git a/apps/mobile/package.json b/apps/mobile/package.json new file mode 100644 index 00000000..a63c0076 --- /dev/null +++ b/apps/mobile/package.json @@ -0,0 +1,20 @@ +{ + "name": "@t4-code/mobile", + "version": "0.1.9", + "private": true, + "type": "module", + "scripts": { + "prepare:web": "node ./scripts/prepare-web.mjs", + "sync:android": "pnpm prepare:web && cap sync android", + "build:android:debug": "pnpm sync:android && node ./scripts/run-gradle.mjs assembleDebug", + "build:android:release": "pnpm sync:android && node ./scripts/run-gradle.mjs assembleRelease", + "test": "node --test ./scripts/*.test.mjs" + }, + "dependencies": { + "@capacitor/core": "8.4.1" + }, + "devDependencies": { + "@capacitor/android": "8.4.1", + "@capacitor/cli": "8.4.1" + } +} diff --git a/apps/mobile/scripts/prepare-web.mjs b/apps/mobile/scripts/prepare-web.mjs new file mode 100644 index 00000000..b00081cd --- /dev/null +++ b/apps/mobile/scripts/prepare-web.mjs @@ -0,0 +1,56 @@ +import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const mobileRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const webDist = resolve(mobileRoot, "../web/dist"); +const mobileDist = resolve(mobileRoot, "dist"); +const sourceIndex = resolve(webDist, "index.html"); +const MOBILE_CONTENT_SECURITY_POLICY = [ + "default-src 'self'", + "script-src 'self'", + "style-src 'self' 'unsafe-inline'", + "font-src 'self' data:", + "img-src 'self' data: blob:", + "connect-src 'self' wss://*.ts.net:*", + "object-src 'none'", + "base-uri 'none'", + "frame-src 'none'", + "form-action 'none'", +].join("; "); + +let indexHtml; +try { + indexHtml = await readFile(sourceIndex, "utf8"); +} catch { + throw new Error("apps/web/dist is missing; run `pnpm build:web` before syncing the mobile shell"); +} + +if (!indexHtml.includes('

')) { + throw new Error("apps/web/dist/index.html is not a T4 Code web build"); +} +if (indexHtml.includes('http-equiv="Content-Security-Policy"')) { + throw new Error("apps/web/dist/index.html already contains a content security policy"); +} + +const mobileIndexHtml = indexHtml.replace( + " ", + ` \n `, +); +if (mobileIndexHtml === indexHtml) { + throw new Error("apps/web/dist/index.html is missing its closing head element"); +} + +await rm(mobileDist, { force: true, recursive: true }); +await mkdir(mobileDist, { recursive: true }); +await cp(webDist, mobileDist, { recursive: true }); +await writeFile(resolve(mobileDist, "index.html"), mobileIndexHtml, "utf8"); + +const buildMetadata = { + appId: "com.lycaonsolutions.t4code", + bundledWebApp: true, + nativeOrigin: "https://localhost", +}; +await writeFile(resolve(mobileDist, "mobile-build.json"), `${JSON.stringify(buildMetadata, null, 2)}\n`, "utf8"); + +console.log(`Prepared bundled T4 web assets in ${mobileDist}`); diff --git a/apps/mobile/scripts/prepare-web.test.mjs b/apps/mobile/scripts/prepare-web.test.mjs new file mode 100644 index 00000000..a6c96845 --- /dev/null +++ b/apps/mobile/scripts/prepare-web.test.mjs @@ -0,0 +1,64 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import test from "node:test"; + +const mobileRoot = resolve(import.meta.dirname, ".."); + +test("Capacitor uses bundled assets and a local secure origin", async () => { + const config = JSON.parse(await readFile(resolve(mobileRoot, "capacitor.config.json"), "utf8")); + + assert.equal(config.webDir, "dist"); + assert.equal(config.server.hostname, "localhost"); + assert.equal(config.server.androidScheme, "https"); + assert.equal(config.server.url, undefined); + assert.equal(config.server.allowNavigation, undefined); + assert.equal(config.server.cleartext, undefined); +}); + +test("mobile package pins one Capacitor release across core, CLI, and Android", async () => { + const packageJson = JSON.parse(await readFile(resolve(mobileRoot, "package.json"), "utf8")); + const core = packageJson.dependencies["@capacitor/core"]; + + assert.equal(core, "8.4.1"); + assert.equal(packageJson.devDependencies["@capacitor/cli"], core); + assert.equal(packageJson.devDependencies["@capacitor/android"], core); +}); + +test("Android credentials are encrypted by a registered Keystore plugin", async () => { + const sourceRoot = resolve( + mobileRoot, + "android/app/src/main/java/com/lycaonsolutions/t4code", + ); + const activity = await readFile(resolve(sourceRoot, "MainActivity.java"), "utf8"); + const plugin = await readFile(resolve(sourceRoot, "T4SecureStoragePlugin.java"), "utf8"); + + assert.match(activity, /registerPlugin\(T4SecureStoragePlugin\.class\)/); + assert.match(plugin, /@CapacitorPlugin\(name = "T4SecureStorage"\)/); + assert.match(plugin, /AES\/GCM\/NoPadding/); + assert.match(plugin, /AndroidKeyStore/); + assert.match(plugin, /setCredentials\(PluginCall call\)/); + assert.match(plugin, /getCredentials\(PluginCall call\)/); + assert.match(plugin, /clearCredentials\(PluginCall call\)/); + assert.doesNotMatch(plugin, /putString\([^,]+,\s*deviceToken\)/); +}); + +test("the bundled document restricts connections without constraining the hosted web build", async () => { + const prepareScript = await readFile(resolve(mobileRoot, "scripts/prepare-web.mjs"), "utf8"); + const hostedIndex = await readFile(resolve(mobileRoot, "../web/index.html"), "utf8"); + + assert.match(prepareScript, /connect-src 'self' wss:\/\/\*\.ts\.net:\*/); + assert.match(prepareScript, /http-equiv="Content-Security-Policy"/); + assert.doesNotMatch(prepareScript, /connect-src \*/); + assert.doesNotMatch(hostedIndex, /http-equiv="Content-Security-Policy"/); +}); + +test("the Android build verifies its Gradle distribution", async () => { + const wrapper = await readFile( + resolve(mobileRoot, "android/gradle/wrapper/gradle-wrapper.properties"), + "utf8", + ); + + assert.match(wrapper, /^distributionSha256Sum=[a-f0-9]{64}$/mu); + assert.match(wrapper, /^validateDistributionUrl=true$/mu); +}); diff --git a/apps/mobile/scripts/run-gradle.mjs b/apps/mobile/scripts/run-gradle.mjs new file mode 100644 index 00000000..72d806b1 --- /dev/null +++ b/apps/mobile/scripts/run-gradle.mjs @@ -0,0 +1,39 @@ +import { spawn } from "node:child_process"; +import { access } from "node:fs/promises"; +import { delimiter, resolve } from "node:path"; + +const mobileRoot = resolve(import.meta.dirname, ".."); +const gradlew = resolve(mobileRoot, "android/gradlew"); +const sdkRoot = process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? resolve(process.env.HOME ?? "", "Android/sdk"); +const javaHome = process.env.JAVA_HOME ?? "/usr/lib/jvm/java-21-openjdk-amd64"; + +await access(gradlew); +await access(resolve(javaHome, "bin/java")); + +const tasks = process.argv.slice(2); +if (tasks.length === 0) throw new Error("pass at least one Gradle task"); + +const child = spawn(gradlew, ["--no-daemon", ...tasks], { + cwd: resolve(mobileRoot, "android"), + env: { + ...process.env, + ANDROID_HOME: sdkRoot, + ANDROID_SDK_ROOT: sdkRoot, + JAVA_HOME: javaHome, + PATH: `${resolve(javaHome, "bin")}${delimiter}${resolve(sdkRoot, "platform-tools")}${delimiter}${process.env.PATH ?? ""}`, + }, + stdio: "inherit", +}); + +child.on("error", (error) => { + throw error; +}); + +const exitCode = await new Promise((resolveExit) => { + child.on("exit", (code, signal) => { + if (signal !== null) throw new Error(`Gradle terminated by ${signal}`); + resolveExit(code ?? 1); + }); +}); + +process.exitCode = exitCode; diff --git a/apps/site/package.json b/apps/site/package.json index 8a99846f..40fa8028 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/site", - "version": "0.1.8", + "version": "0.1.9", "private": true, "type": "module", "scripts": { diff --git a/apps/site/src/docs/content.ts b/apps/site/src/docs/content.ts index f12c088e..4b26f46f 100644 --- a/apps/site/src/docs/content.ts +++ b/apps/site/src/docs/content.ts @@ -11,7 +11,8 @@ import { OMP_RUNTIME_VERSION, OMP_UPSTREAM_COMMIT, OMP_UPSTREAM_URL, - RELEASE_ASSETS, + assetsFor, + primaryAsset, RELEASE_TAG, RELEASE_VERSION, RELEASES_URL, @@ -40,20 +41,41 @@ export interface DocGroup { readonly topics: readonly DocTopic[]; } -const linuxDeb = RELEASE_ASSETS[0]!; -const linuxAppImage = RELEASE_ASSETS[1]!; -const macDmg = RELEASE_ASSETS[2]!; -const macZip = RELEASE_ASSETS[3]!; +function assetOfKind(platform: "linux" | "mac", kind: "appimage" | "zip") { + const found = assetsFor(platform).find((asset) => asset.kind === kind); + if (!found) throw new Error(`no ${kind} asset for ${platform}`); + return found; +} + +const androidApk = primaryAsset("android"); +const linuxDeb = primaryAsset("linux"); +const linuxAppImage = assetOfKind("linux", "appimage"); +const macDmg = primaryAsset("mac"); +const macZip = assetOfKind("mac", "zip"); const install: DocTopic = { id: "install", title: "Install", - lede: "T4 Code ships for Linux (x86_64) and macOS (Apple Silicon). Download from the GitHub release, install, and you are ready for first run.", + lede: "T4 Code ships for Android, Linux (x86_64), and macOS (Apple Silicon). iOS TestFlight is coming soon.", blocks: [ { kind: "p", text: `All downloads live on the [v${RELEASE_VERSION} release page](${RELEASES_URL}).`, }, + { kind: "h2", id: "install-android", text: "Android" }, + { + kind: "p", + text: `Download the [Android APK](${androidApk.url}) on your phone and open it. Android may ask you to allow app installs from your browser before it opens the package.`, + }, + { + kind: "p", + text: "The Android app is a thin client. It connects to the T4 Code host that runs your OMP sessions; it does not run OMP or an app server on your phone.", + }, + { kind: "h2", id: "install-ios", text: "iPhone and iPad" }, + { + kind: "p", + text: "iOS TestFlight is coming soon. This page will carry the TestFlight link when the build is ready.", + }, { kind: "h2", id: "install-linux", text: "Linux (x86_64)" }, { kind: "p", text: "The `.deb` package is the smoothest path on Debian and Ubuntu:" }, { @@ -98,7 +120,7 @@ const install: DocTopic = { { kind: "h2", id: "install-requirements", text: "Requirements" }, { kind: "p", - text: `T4 Code is a desktop front end for [Oh My Pi](${OMP_URL}). You need an \`omp\` build with desktop appserver support installed on the machine that runs your sessions, either this one or a remote host you pair with.`, + text: `T4 Code is a client for [Oh My Pi](${OMP_URL}). You need an \`omp\` build with desktop appserver support installed on the machine that runs your sessions, either this one or a remote host you pair with.`, }, { kind: "p", @@ -114,9 +136,13 @@ const install: DocTopic = { const firstRun: DocTopic = { id: "first-run", title: "First run", - lede: "On first launch, T4 Code finds your local Oh My Pi install and manages its app server for you.", + lede: "Desktop builds can manage a local Oh My Pi app server. Android connects to the T4 gateway on your computer.", blocks: [ - { kind: "h2", id: "first-run-discovery", text: "How T4 finds omp" }, + { + kind: "note", + text: "The discovery and service steps below apply to Linux and macOS. On Android, connect Tailscale to the same tailnet as your computer, then enter the gateway's full HTTPS address in T4 Code.", + }, + { kind: "h2", id: "first-run-discovery", text: "How desktop T4 finds omp" }, { kind: "p", text: "T4 Code checks these places, in order, and uses the first match:" }, { kind: "ol", @@ -134,7 +160,7 @@ const firstRun: DocTopic = { kind: "p", text: "Before trusting a match, T4 runs `omp appserver status --json` and checks the answer. A build that cannot answer is skipped.", }, - { kind: "h2", id: "first-run-service", text: "Who keeps the app server running" }, + { kind: "h2", id: "first-run-service", text: "Who keeps the desktop app server running" }, { kind: "p", text: "T4 Code registers the app server with your operating system so it stays up across restarts:", diff --git a/apps/site/src/landing/Landing.tsx b/apps/site/src/landing/Landing.tsx index fd695226..b2670bf2 100644 --- a/apps/site/src/landing/Landing.tsx +++ b/apps/site/src/landing/Landing.tsx @@ -9,7 +9,7 @@ import { RELEASE_VERSION, RELEASES_URL, REPO_URL, - type Platform, + type DesktopPlatform, } from "../release.ts"; interface Feature { @@ -45,13 +45,17 @@ const FEATURES: readonly Feature[] = [ }, ]; -function DownloadButtons({ platform }: { platform: Platform }) { +function DownloadButtons({ platform }: { platform: DesktopPlatform }) { + const android = primaryAsset("android"); const main = primaryAsset(platform); - const other: Platform = platform === "linux" ? "mac" : "linux"; + const other: DesktopPlatform = platform === "linux" ? "mac" : "linux"; const otherMain = primaryAsset(other); return (
- + + Download Android APK + + Download for {platform === "linux" ? "Linux" : "macOS"} @@ -67,7 +71,7 @@ function DownloadButtons({ platform }: { platform: Platform }) { export function Landing() { // Render Linux first (matches the build host + crawler default), then // correct for macOS visitors after mount. Both buttons are always visible. - const [platform, setPlatform] = useState("linux"); + const [platform, setPlatform] = useState("linux"); useEffect(() => { setPlatform(detectPlatform(navigator.userAgent)); }, []); @@ -101,15 +105,15 @@ export function Landing() {

- v{RELEASE_VERSION} · Linux x86_64 · macOS Apple Silicon ·{" "} + v{RELEASE_VERSION} · Android APK · Linux x86_64 · macOS Apple Silicon · iOS TestFlight + coming soon ·{" "} all downloads @@ -129,8 +133,9 @@ export function Landing() {

What it does

- A desktop front end for the OMP runtime. There is no cloud service and no - account. T4 Code talks straight to your host over its typed protocol. + A desktop control surface and Android thin client for the OMP runtime. There is no + cloud service and no account. T4 Code talks straight to your host over its typed + protocol.

{FEATURES.map((feature) => ( @@ -173,6 +178,25 @@ export function Landing() { `T4 Code needs an [Oh My Pi](${OMP_URL}) build with desktop appserver support on the machine that runs your sessions. The [docs](/docs/) cover first run, pairing, and troubleshooting.`, )}

+
+
+

Available now

+

Android APK

+

+ Install T4 Code directly on your phone, then connect to the OMP host you already + run. +

+ + Download Android APK + +
+
+

iPhone & iPad

+

TestFlight coming soon

+

The TestFlight link will appear here when the iOS build is ready.

+
+
+

Desktop builds

Linux

@@ -193,17 +217,16 @@ export function Landing() { xattr -dr com.apple.quarantine "/Applications/T4 Code.app"

- Drag the app to Applications first. The command clears Gatekeeper's - quarantine flag; right-click → Open works too. + Drag the app to Applications first. The command clears Gatekeeper's quarantine + flag; right-click → Open works too.

- Heads up: the v{RELEASE_VERSION} macOS build is unsigned and - not notarized, so macOS warns before first launch. If you would rather not - run an unsigned binary,{" "} - {renderInline(`[build it from source](/docs/#build-from-source)`)}. The - whole app is public. + Heads up: the v{RELEASE_VERSION} macOS build is unsigned and not + notarized, so macOS warns before first launch. If you would rather not run an unsigned + binary, {renderInline(`[build it from source](/docs/#build-from-source)`)}. The whole + app is public.
@@ -211,7 +234,7 @@ export function Landing() {

Get T4 Code

-

Free, MIT-licensed, and built in the open.

+

Android and desktop builds are ready now. iOS TestFlight is coming soon.

    {RELEASE_ASSETS.map((asset) => ( diff --git a/apps/site/src/release.ts b/apps/site/src/release.ts index 4caff227..07b62c79 100644 --- a/apps/site/src/release.ts +++ b/apps/site/src/release.ts @@ -13,12 +13,13 @@ export const OMP_UPSTREAM_TAG = "v16.5.0"; export const OMP_UPSTREAM_COMMIT = "3047c27c332c5629c8e063283d349384c10c9a56"; export const OMP_UPSTREAM_URL = `${OMP_URL}/tree/${OMP_UPSTREAM_TAG}`; export const APP_WIRE_VERSION = "0.5.2"; -export const RELEASE_TAG = "v0.1.8"; -export const RELEASE_VERSION = "0.1.8"; +export const RELEASE_TAG = "v0.1.9"; +export const RELEASE_VERSION = "0.1.9"; export const RELEASES_URL = `${REPO_URL}/releases/tag/${RELEASE_TAG}`; -export type Platform = "linux" | "mac"; -export type AssetKind = "deb" | "appimage" | "dmg" | "zip"; +export type Platform = "android" | "linux" | "mac"; +export type DesktopPlatform = Exclude; +export type AssetKind = "apk" | "deb" | "appimage" | "dmg" | "zip"; export interface ReleaseAsset { readonly platform: Platform; @@ -47,30 +48,30 @@ function asset( } export const RELEASE_ASSETS: readonly ReleaseAsset[] = [ - asset("linux", "deb", "x86_64", "T4-Code-0.1.8-linux-amd64.deb", "Linux .deb"), - asset("linux", "appimage", "x86_64", "T4-Code-0.1.8-linux-x86_64.AppImage", "Linux AppImage"), - asset("mac", "dmg", "arm64", "T4-Code-0.1.8-mac-arm64.dmg", "macOS .dmg"), - asset("mac", "zip", "arm64", "T4-Code-0.1.8-mac-arm64.zip", "macOS .zip"), + asset("android", "apk", "universal", "T4-Code-0.1.9-android.apk", "Android APK"), + asset("linux", "deb", "x86_64", "T4-Code-0.1.9-linux-amd64.deb", "Linux .deb"), + asset("linux", "appimage", "x86_64", "T4-Code-0.1.9-linux-x86_64.AppImage", "Linux AppImage"), + asset("mac", "dmg", "arm64", "T4-Code-0.1.9-mac-arm64.dmg", "macOS .dmg"), + asset("mac", "zip", "arm64", "T4-Code-0.1.9-mac-arm64.zip", "macOS .zip"), ]; export function assetsFor(platform: Platform): readonly ReleaseAsset[] { return RELEASE_ASSETS.filter((a) => a.platform === platform); } -/** Preferred single download per platform: .deb on Linux, .dmg on macOS. */ +/** Preferred single download per platform: APK on Android, .deb on Linux, .dmg on macOS. */ export function primaryAsset(platform: Platform): ReleaseAsset { - const kind: AssetKind = platform === "linux" ? "deb" : "dmg"; + const kind: AssetKind = platform === "android" ? "apk" : platform === "linux" ? "deb" : "dmg"; const found = RELEASE_ASSETS.find((a) => a.platform === platform && a.kind === kind); if (!found) throw new Error(`no ${kind} asset for ${platform}`); return found; } /** - * Detect the visitor's platform from a user-agent string. Only Linux and - * Apple Silicon macOS builds exist; anything else falls back to Linux so the - * button always points at a real file. + * Detect the visitor's desktop platform from a user-agent string. Anything + * else falls back to Linux; the Android download is offered separately. */ -export function detectPlatform(userAgent: string): Platform { +export function detectPlatform(userAgent: string): DesktopPlatform { if (/mac os x|macintosh/i.test(userAgent)) return "mac"; return "linux"; } diff --git a/apps/site/src/site.css b/apps/site/src/site.css index 910a8694..e979e409 100644 --- a/apps/site/src/site.css +++ b/apps/site/src/site.css @@ -404,6 +404,63 @@ a.btn:hover { /* Install section */ +.mobile-install-grid { + display: grid; + grid-template-columns: minmax(0, 1.35fr) minmax(0, 0.65fr); + gap: 1.5rem; + margin-bottom: 2.25rem; +} + +.mobile-install-card { + min-width: 0; + display: flex; + flex-direction: column; + align-items: flex-start; + border: 1px solid var(--border); + border-radius: calc(var(--radius) + 2px); + background: var(--card); + padding: 1.4rem; +} + +.android-install-card { + border-color: color-mix(in srgb, var(--primary) 40%, var(--border)); + background: color-mix(in srgb, var(--primary) 7%, var(--card)); +} + +.mobile-install-card .platform-label { + margin: 0 0 0.35rem; + color: var(--accent-text); + font-size: 0.8rem; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.ios-install-card .platform-label { + color: var(--muted-foreground); +} + +.mobile-install-card h3 { + margin: 0 0 0.5rem; + font-size: 1.2rem; + letter-spacing: -0.01em; +} + +.mobile-install-card p:not(.platform-label) { + margin: 0 0 1.25rem; + color: var(--muted-foreground); + max-width: 48ch; +} + +.mobile-install-card .btn { + margin-top: auto; +} + +.desktop-install-title { + margin: 0 0 0.75rem; + font-size: 1rem; +} + .install-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -418,6 +475,7 @@ a.btn:hover { } @media (max-width: 900px) { + .mobile-install-grid, .install-grid { grid-template-columns: minmax(0, 1fr); } diff --git a/apps/site/test/docs-nav.test.ts b/apps/site/test/docs-nav.test.ts index 14d4ebea..0fc398cf 100644 --- a/apps/site/test/docs-nav.test.ts +++ b/apps/site/test/docs-nav.test.ts @@ -58,6 +58,8 @@ describe("resolveTopicForHash", () => { }); it("resolves a heading id to its owning topic", () => { + expect(resolveTopicForHash("#install-android")?.id).toBe("install"); + expect(resolveTopicForHash("#install-ios")?.id).toBe("install"); expect(resolveTopicForHash("#install-gatekeeper")?.id).toBe("install"); expect(resolveTopicForHash("#shortcuts-composer")?.id).toBe("keyboard-shortcuts"); }); diff --git a/apps/site/test/release.test.ts b/apps/site/test/release.test.ts index 275c1a18..5e1fd4d2 100644 --- a/apps/site/test/release.test.ts +++ b/apps/site/test/release.test.ts @@ -1,4 +1,4 @@ -// Release contract guard: exact v0.1.8 asset names and URLs, and the +// Release contract guard: exact v0.1.9 asset names and URLs, and the // platform-detection rule the hero download button relies on. import { describe, expect, it } from "vite-plus/test"; import { @@ -19,12 +19,13 @@ import { } from "../src/release.ts"; describe("release assets", () => { - it("carries the four contracted v0.1.8 filenames", () => { + it("carries the five contracted v0.1.9 filenames", () => { expect(RELEASE_ASSETS.map((a) => a.filename)).toEqual([ - "T4-Code-0.1.8-linux-amd64.deb", - "T4-Code-0.1.8-linux-x86_64.AppImage", - "T4-Code-0.1.8-mac-arm64.dmg", - "T4-Code-0.1.8-mac-arm64.zip", + "T4-Code-0.1.9-android.apk", + "T4-Code-0.1.9-linux-amd64.deb", + "T4-Code-0.1.9-linux-x86_64.AppImage", + "T4-Code-0.1.9-mac-arm64.dmg", + "T4-Code-0.1.9-mac-arm64.zip", ]); }); @@ -36,18 +37,21 @@ describe("release assets", () => { it("targets the public LycaonLLC repo", () => { expect(REPO_URL).toBe("https://github.com/LycaonLLC/t4-code"); - expect(RELEASE_TAG).toBe("v0.1.8"); - expect(RELEASE_VERSION).toBe("0.1.8"); + expect(RELEASE_TAG).toBe("v0.1.9"); + expect(RELEASE_VERSION).toBe("0.1.9"); }); it("splits assets by platform with correct architectures", () => { expect(assetsFor("linux").every((a) => a.arch === "x86_64")).toBe(true); expect(assetsFor("mac").every((a) => a.arch === "arm64")).toBe(true); + expect(assetsFor("android").every((a) => a.arch === "universal")).toBe(true); + expect(assetsFor("android")).toHaveLength(1); expect(assetsFor("linux")).toHaveLength(2); expect(assetsFor("mac")).toHaveLength(2); }); - it("picks .deb for Linux and .dmg for macOS as the primary download", () => { + it("picks the APK, .deb, and .dmg as primary downloads", () => { + expect(primaryAsset("android").kind).toBe("apk"); expect(primaryAsset("linux").kind).toBe("deb"); expect(primaryAsset("mac").kind).toBe("dmg"); }); diff --git a/apps/web/package.json b/apps/web/package.json index ca62fdad..6aa84e7c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/web", - "version": "0.1.8", + "version": "0.1.9", "private": true, "type": "module", "scripts": { diff --git a/apps/web/src/application.tsx b/apps/web/src/application.tsx new file mode 100644 index 00000000..c7492fd3 --- /dev/null +++ b/apps/web/src/application.tsx @@ -0,0 +1,18 @@ +import "./state/store-instance.ts"; + +import { TooltipProvider } from "@t4-code/ui"; +import { RouterProvider } from "@tanstack/react-router"; +import { StrictMode } from "react"; +import type { Root } from "react-dom/client"; + +import { router } from "./router.tsx"; + +export function renderApplication(root: Root): void { + root.render( + + + + + , + ); +} diff --git a/apps/web/src/components/MobileConnectionAction.tsx b/apps/web/src/components/MobileConnectionAction.tsx new file mode 100644 index 00000000..8036f57e --- /dev/null +++ b/apps/web/src/components/MobileConnectionAction.tsx @@ -0,0 +1,82 @@ +import { + Button, + Dialog, + DialogClose, + DialogDescription, + DialogFooter, + DialogHeader, + DialogPopup, + DialogTitle, + IconButton, + Tooltip, + TooltipPopup, + TooltipTrigger, +} from "@t4-code/ui"; +import { Cable } from "lucide-react"; +import { useState } from "react"; + +import { + clearNativeMobileConnection, + nativeMobilePlatform, + readStoredMobileBackend, +} from "../platform/native-mobile.ts"; + +export function MobileConnectionAction() { + const [open, setOpen] = useState(false); + const [clearing, setClearing] = useState(false); + if (nativeMobilePlatform() === null) return null; + + let origin = "Saved Tailnet host"; + try { + origin = readStoredMobileBackend()?.origin ?? origin; + } catch { + // The setup screen will repair invalid storage on the next launch. + } + + return ( + + + setOpen(true)} + size="icon-sm" + > + + + } + /> + Mobile connection + + + + Mobile connection + + This phone connects directly to the T4 gateway below. Changing it also removes the paired device credential from this phone. + + +

    {origin}

    + + }> + Keep this host + + + +
    +
    + ); +} diff --git a/apps/web/src/components/MobileConnectionScreen.tsx b/apps/web/src/components/MobileConnectionScreen.tsx new file mode 100644 index 00000000..22168159 --- /dev/null +++ b/apps/web/src/components/MobileConnectionScreen.tsx @@ -0,0 +1,108 @@ +import { BrandLockup, Button, Spinner } from "@t4-code/ui"; +import { Cable, LockKeyhole, Network } from "lucide-react"; +import { useState } from "react"; + +import { + parseTailnetBackend, + probeMobileBackend, + writeStoredMobileBackend, +} from "../platform/native-mobile.ts"; + +export function MobileConnectionScreen({ startupMessage }: { readonly startupMessage?: string }) { + const [address, setAddress] = useState(""); + const [message, setMessage] = useState(startupMessage ?? null); + const [checking, setChecking] = useState(false); + + return ( +
    +
    + +
    +
    +
    +
    +

    Connect to your T4 host

    +

    + T4 Code runs the interface on this phone. OMP and your projects stay on your computer. +

    + +
    { + event.preventDefault(); + if (checking) return; + setMessage(null); + let backend; + try { + backend = parseTailnetBackend(address); + } catch (error) { + setMessage(error instanceof Error ? error.message : "Enter a valid Tailnet address."); + return; + } + setChecking(true); + void probeMobileBackend(backend) + .then(() => { + writeStoredMobileBackend(backend); + window.location.reload(); + }) + .catch((error: unknown) => { + setMessage(error instanceof Error ? error.message : "T4 Code could not reach that host."); + setChecking(false); + }); + }} + > + + setAddress(event.target.value)} + placeholder="https://your-computer.your-tailnet.ts.net:8445" + spellCheck={false} + type="url" + value={address} + /> +

    + Use the full HTTPS address shown by the T4 gateway on your computer. +

    +

    + {message} +

    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    + ); +} diff --git a/apps/web/src/components/Titlebar.tsx b/apps/web/src/components/Titlebar.tsx index 1c88f1df..88ea9abe 100644 --- a/apps/web/src/components/Titlebar.tsx +++ b/apps/web/src/components/Titlebar.tsx @@ -8,6 +8,7 @@ import { Command, Moon, PanelLeft, Settings, Sun } from "lucide-react"; import { rendererPlatform, useWorkspace, workspaceStore } from "../state/store-instance.ts"; import { resolveTheme } from "../theme/theme.ts"; import { HostedAppAction } from "./HostedAppAction.tsx"; +import { MobileConnectionAction } from "./MobileConnectionAction.tsx"; import type { RailTogglePresentation } from "./rail-toggle.ts"; function ThemeToggle() { @@ -100,6 +101,7 @@ export function Titlebar({ )} + - - - - , -); +const root = createRoot(rootElement); + +void prepareNativeMobileBackend().then(async (boot) => { + if (boot.kind === "setup") { + root.render( + + + , + ); + return; + } + const { renderApplication } = await import("./application.tsx"); + renderApplication(root); +}); diff --git a/apps/web/src/platform/browser-shell-port.ts b/apps/web/src/platform/browser-shell-port.ts index 0d394748..41b20e2c 100644 --- a/apps/web/src/platform/browser-shell-port.ts +++ b/apps/web/src/platform/browser-shell-port.ts @@ -54,6 +54,11 @@ import type { import type { DesktopShellPort } from "@t4-code/client"; import { BrowserWebSocketTransport } from "./browser-transport.ts"; +import { + currentNativeMobileBackend, + nativeMobilePlatform, + persistNativeMobileCredentials, +} from "./native-mobile.ts"; const TARGET_ID = "remote"; @@ -123,6 +128,8 @@ function hasExplicitQueryConfig(params: URLSearchParams): boolean { } export function detectBackend(): BrowserBackendConfig | null { + const nativeConfig = currentNativeMobileBackend(); + if (nativeConfig !== null) return parseBackendPayload(nativeConfig); if (typeof document !== "undefined") { const script = document.getElementById("t4-backend"); if (script !== null) { @@ -166,6 +173,7 @@ export function createBrowserShellPort(options: BrowserShellPortOptions = {}): D if (config === null) return null; const backendConfig = config; + const mobilePlatform = nativeMobilePlatform(); const platform: "linux" | "darwin" = (() => { if (typeof navigator !== "undefined" && /mac/i.test(navigator.platform)) return "darwin"; return "linux"; @@ -215,14 +223,15 @@ export function createBrowserShellPort(options: BrowserShellPortOptions = {}): D capabilities: DEVICE_CAPABILITIES, requestedFeatures: ADDITIVE_FEATURES, authentication: () => authentication, - privilegedPairResult: (result) => { + privilegedPairResult: async (result) => { + await persistNativeMobileCredentials(result); authentication = { deviceId: result.deviceId, deviceToken: result.deviceToken }; }, client: { name: "T4 Code", - version: "0.1.8", - build: "browser", - platform: platform === "darwin" ? "darwin" : "linux", + version: "0.1.9", + build: mobilePlatform ?? "browser", + platform: mobilePlatform ?? (platform === "darwin" ? "darwin" : "linux"), }, reconnect: { attemptCap: 12, baseMs: 250, maxMs: 10_000 }, }); @@ -379,8 +388,8 @@ export function createBrowserShellPort(options: BrowserShellPortOptions = {}): D const result = await client.pairStart({ code: request.code, deviceId: browserDeviceId, - deviceName: "T4 Browser", - platform, + deviceName: mobilePlatform === null ? "T4 Browser" : `T4 ${mobilePlatform === "android" ? "Android" : "iOS"}`, + platform: mobilePlatform ?? platform, requestedCapabilities: DEVICE_CAPABILITIES, }); return { targetId: request.targetId, paired: result.type === "pair.ok" }; diff --git a/apps/web/src/platform/native-mobile.ts b/apps/web/src/platform/native-mobile.ts new file mode 100644 index 00000000..63fbc698 --- /dev/null +++ b/apps/web/src/platform/native-mobile.ts @@ -0,0 +1,230 @@ +import { deviceToken as validateDeviceToken } from "@t4-code/protocol"; + +export const MOBILE_BACKEND_STORAGE_KEY = "t4-code:mobile-backend:v1"; + +const MAX_URL_LENGTH = 2048; +const MAX_LABEL_LENGTH = 128; + +export type NativeMobilePlatform = "android" | "ios"; + +export interface StoredMobileBackend { + readonly version: 1; + readonly origin: string; + readonly wsUrl: string; + readonly label: string; +} + +export interface NativeMobileBackendConfig { + readonly wsUrl: string; + readonly label: string; + readonly deviceId?: string; + readonly deviceToken?: string; +} + +interface T4SecureStoragePlugin { + getCredentials(): Promise<{ + readonly credentials: { readonly deviceId: string; readonly deviceToken: string } | null; + }>; + setCredentials(options: { readonly deviceId: string; readonly deviceToken: string }): Promise; + clearCredentials(): Promise; +} + +interface CapacitorBridge { + readonly Plugins?: { readonly T4SecureStorage?: T4SecureStoragePlugin }; + readonly getPlatform?: () => string; + readonly isNativePlatform?: () => boolean; +} + +declare global { + interface Window { + Capacitor?: CapacitorBridge; + __t4MobileBackend?: NativeMobileBackendConfig; + } +} + +export type MobileBootResult = + | { readonly kind: "web" } + | { readonly kind: "ready"; readonly backend: StoredMobileBackend } + | { readonly kind: "setup"; readonly message?: string }; + +function secureStorage(): T4SecureStoragePlugin | null { + return window.Capacitor?.Plugins?.T4SecureStorage ?? null; +} + +export function nativeMobilePlatform(): NativeMobilePlatform | null { + if (typeof window === "undefined") return null; + const bridge = window.Capacitor; + if (bridge?.isNativePlatform?.() !== true) return null; + const platform = bridge.getPlatform?.(); + return platform === "android" || platform === "ios" ? platform : null; +} + +function requiredLabel(value: unknown): string { + if (typeof value !== "string" || value.length === 0 || value.length > MAX_LABEL_LENGTH) { + throw new Error("The saved host label is invalid."); + } + return value; +} + +export function parseTailnetBackend(value: string): StoredMobileBackend { + const trimmed = value.trim(); + if (trimmed.length === 0) throw new Error("Enter the HTTPS address shown by T4 Code on your computer."); + if (trimmed.length > MAX_URL_LENGTH) throw new Error("That address is too long."); + + const candidate = trimmed.includes("://") ? trimmed : `https://${trimmed}`; + let parsed: URL; + try { + parsed = new URL(candidate); + } catch { + throw new Error("Enter a valid HTTPS Tailnet address."); + } + if (parsed.protocol !== "https:") throw new Error("Use the HTTPS Tailnet address, not HTTP."); + if (parsed.username !== "" || parsed.password !== "") throw new Error("The address cannot contain credentials."); + if (parsed.pathname !== "/" || parsed.search !== "" || parsed.hash !== "") { + throw new Error("Enter the host address only, without a path, query, or fragment."); + } + const hostname = parsed.hostname.toLowerCase(); + if (hostname === "ts.net" || !hostname.endsWith(".ts.net")) { + throw new Error("Use the full Tailscale hostname ending in .ts.net."); + } + + const origin = parsed.origin; + const websocket = new URL(origin); + websocket.protocol = "wss:"; + websocket.pathname = "/v1/ws"; + const deviceName = hostname.slice(0, hostname.indexOf(".")); + return { + version: 1, + origin, + wsUrl: websocket.toString(), + label: requiredLabel(`T4 on ${deviceName}`), + }; +} + +export function readStoredMobileBackend(storage: Pick = window.localStorage): StoredMobileBackend | null { + const raw = storage.getItem(MOBILE_BACKEND_STORAGE_KEY); + if (raw === null) return null; + let value: unknown; + try { + value = JSON.parse(raw); + } catch { + throw new Error("The saved host address is damaged. Enter it again."); + } + if (value === null || typeof value !== "object" || Array.isArray(value)) { + throw new Error("The saved host address is damaged. Enter it again."); + } + const data = value as Record; + if (data.version !== 1 || typeof data.origin !== "string") { + throw new Error("The saved host address is from an unsupported app version."); + } + const parsed = parseTailnetBackend(data.origin); + if (data.wsUrl !== parsed.wsUrl || data.label !== parsed.label) { + throw new Error("The saved host address is inconsistent. Enter it again."); + } + return parsed; +} + +export function writeStoredMobileBackend( + backend: StoredMobileBackend, + storage: Pick = window.localStorage, +): void { + storage.setItem(MOBILE_BACKEND_STORAGE_KEY, JSON.stringify(backend)); +} + +export function currentNativeMobileBackend(): NativeMobileBackendConfig | null { + if (typeof window === "undefined") return null; + return window.__t4MobileBackend ?? null; +} + +export async function prepareNativeMobileBackend(): Promise { + const platform = nativeMobilePlatform(); + if (platform === null) return { kind: "web" }; + document.documentElement.dataset.platform = platform; + + let backend: StoredMobileBackend | null; + try { + backend = readStoredMobileBackend(); + } catch (error) { + return { kind: "setup", message: error instanceof Error ? error.message : "Enter the host address again." }; + } + if (backend === null) return { kind: "setup" }; + + const plugin = secureStorage(); + if (plugin === null) { + return { kind: "setup", message: "The Android security bridge did not start. Close T4 Code and open it again." }; + } + + let credentials: { readonly deviceId: string; readonly deviceToken: string } | null = null; + try { + const result = await plugin.getCredentials(); + if (result.credentials !== null) { + const deviceId = result.credentials.deviceId; + if (deviceId.length === 0 || deviceId.length > 256) throw new Error("invalid device id"); + credentials = { deviceId, deviceToken: validateDeviceToken(result.credentials.deviceToken, "deviceToken") }; + } + } catch { + await plugin.clearCredentials().catch(() => undefined); + } + + window.__t4MobileBackend = { + wsUrl: backend.wsUrl, + label: backend.label, + ...(credentials === null ? {} : credentials), + }; + return { kind: "ready", backend }; +} + +export async function persistNativeMobileCredentials(credentials: { + readonly deviceId: string; + readonly deviceToken: string; +}): Promise { + if (nativeMobilePlatform() === null) return; + const plugin = secureStorage(); + if (plugin === null) throw new Error("Android secure storage is unavailable"); + await plugin.setCredentials({ + deviceId: credentials.deviceId, + deviceToken: validateDeviceToken(credentials.deviceToken, "deviceToken"), + }); +} + +export async function clearNativeMobileConnection(): Promise { + const plugin = secureStorage(); + if (plugin !== null) await plugin.clearCredentials(); + window.localStorage.removeItem(MOBILE_BACKEND_STORAGE_KEY); + delete window.__t4MobileBackend; +} + +export async function probeMobileBackend( + backend: StoredMobileBackend, + options: { readonly timeoutMs?: number; readonly WebSocketImpl?: typeof WebSocket } = {}, +): Promise { + const WebSocketImpl = options.WebSocketImpl ?? window.WebSocket; + const timeoutMs = options.timeoutMs ?? 8_000; + await new Promise((resolve, reject) => { + let settled = false; + const socket = new WebSocketImpl(backend.wsUrl); + const finish = (error?: Error) => { + if (settled) return; + settled = true; + clearTimeout(timer); + socket.removeEventListener("open", onOpen); + socket.removeEventListener("error", onError); + socket.removeEventListener("close", onClose); + if (error === undefined) resolve(); + else reject(error); + }; + const onOpen = () => { + finish(); + socket.close(1000, "T4 mobile connection check"); + }; + const onError = () => finish(new Error("T4 Code could not reach that host. Check Tailscale and the address.")); + const onClose = () => finish(new Error("The host closed the connection before T4 Code could start.")); + const timer = setTimeout(() => { + socket.close(); + finish(new Error("The host did not answer. Check that Tailscale and the T4 gateway are running.")); + }, timeoutMs); + socket.addEventListener("open", onOpen); + socket.addEventListener("error", onError); + socket.addEventListener("close", onClose); + }); +} diff --git a/apps/web/test/browser-platform.test.ts b/apps/web/test/browser-platform.test.ts index cae21b1e..e09fcf06 100644 --- a/apps/web/test/browser-platform.test.ts +++ b/apps/web/test/browser-platform.test.ts @@ -51,6 +51,28 @@ describe("browser platform boundary", () => { expect(() => detectBackend()).toThrow(/invalid browser backend wsUrl/u); }); + it("prefers a prepared native backend without putting credentials in the URL", () => { + Object.defineProperty(globalThis, "document", { configurable: true, value: undefined }); + Object.defineProperty(globalThis, "window", { + configurable: true, + value: { + __t4MobileBackend: { + wsUrl: "wss://host.tailnet.ts.net:8445/v1/ws", + label: "T4 on host", + deviceId: "android-device", + deviceToken: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + }, + location: { search: "" }, + }, + }); + expect(detectBackend()).toEqual({ + wsUrl: "wss://host.tailnet.ts.net:8445/v1/ws", + label: "T4 on host", + deviceId: "android-device", + deviceToken: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + }); + }); + it("exposes one remote target and no local service lifecycle", async () => { setBackendScript(JSON.stringify({ wsUrl: "wss://omp.example/v1/ws", label: "Remote OMP" })); const shell = createBrowserShellPort(); diff --git a/apps/web/test/native-mobile.test.tsx b/apps/web/test/native-mobile.test.tsx new file mode 100644 index 00000000..a8e033ba --- /dev/null +++ b/apps/web/test/native-mobile.test.tsx @@ -0,0 +1,123 @@ +import { renderToStaticMarkup } from "react-dom/server"; +import { afterEach, describe, expect, it } from "vite-plus/test"; + +import { MobileConnectionScreen } from "../src/components/MobileConnectionScreen.tsx"; +import { + clearNativeMobileConnection, + MOBILE_BACKEND_STORAGE_KEY, + parseTailnetBackend, + prepareNativeMobileBackend, + probeMobileBackend, + readStoredMobileBackend, + writeStoredMobileBackend, +} from "../src/platform/native-mobile.ts"; + +const originalDocument = globalThis.document; +const originalWindow = globalThis.window; + +class MemoryStorage { + readonly values = new Map(); + getItem(key: string): string | null { return this.values.get(key) ?? null; } + setItem(key: string, value: string): void { this.values.set(key, value); } + removeItem(key: string): void { this.values.delete(key); } +} + +afterEach(() => { + Object.defineProperty(globalThis, "document", { configurable: true, value: originalDocument }); + Object.defineProperty(globalThis, "window", { configurable: true, value: originalWindow }); +}); + +describe("native mobile connection", () => { + it("normalizes a full Tailnet HTTPS origin and rejects unsafe addresses", () => { + expect(parseTailnetBackend("lycaon-bunker.tail9f9e1a.ts.net:8445")).toEqual({ + version: 1, + origin: "https://lycaon-bunker.tail9f9e1a.ts.net:8445", + wsUrl: "wss://lycaon-bunker.tail9f9e1a.ts.net:8445/v1/ws", + label: "T4 on lycaon-bunker", + }); + expect(() => parseTailnetBackend("http://host.tailnet.ts.net")).toThrow(/HTTPS/u); + expect(() => parseTailnetBackend("https://example.com")).toThrow(/\.ts\.net/u); + expect(() => parseTailnetBackend("https://host.tailnet.ts.net/admin")).toThrow(/host address only/u); + expect(() => parseTailnetBackend("https://user:pass@host.tailnet.ts.net")).toThrow(/credentials/u); + }); + + it("round-trips only validated nonsecret host configuration", () => { + const storage = new MemoryStorage(); + const backend = parseTailnetBackend("https://host.tailnet.ts.net:8445"); + writeStoredMobileBackend(backend, storage); + expect(readStoredMobileBackend(storage)).toEqual(backend); + + storage.setItem(MOBILE_BACKEND_STORAGE_KEY, JSON.stringify({ ...backend, wsUrl: "wss://evil.example/v1/ws" })); + expect(() => readStoredMobileBackend(storage)).toThrow(/inconsistent/u); + }); + + it("loads paired credentials from the native security bridge before app boot", async () => { + const storage = new MemoryStorage(); + const backend = parseTailnetBackend("https://host.tailnet.ts.net:8445"); + writeStoredMobileBackend(backend, storage); + const clearCredentials = () => Promise.resolve(); + Object.defineProperty(globalThis, "document", { configurable: true, value: { documentElement: { dataset: {} } } }); + Object.defineProperty(globalThis, "window", { + configurable: true, + value: { + localStorage: storage, + Capacitor: { + isNativePlatform: () => true, + getPlatform: () => "android", + Plugins: { + T4SecureStorage: { + getCredentials: () => Promise.resolve({ + credentials: { + deviceId: "android-device", + deviceToken: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + }, + }), + setCredentials: () => Promise.resolve(), + clearCredentials, + }, + }, + }, + }, + }); + + await expect(prepareNativeMobileBackend()).resolves.toEqual({ kind: "ready", backend }); + expect(window.__t4MobileBackend).toEqual({ + wsUrl: backend.wsUrl, + label: backend.label, + deviceId: "android-device", + deviceToken: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + }); + await clearNativeMobileConnection(); + expect(storage.getItem(MOBILE_BACKEND_STORAGE_KEY)).toBeNull(); + }); + + it("probes the exact WSS endpoint before saving", async () => { + class OpeningSocket { + static url = ""; + readonly listeners = new Map void>>(); + constructor(url: string | URL) { + OpeningSocket.url = String(url); + queueMicrotask(() => this.emit("open")); + } + addEventListener(name: string, listener: () => void): void { + const listeners = this.listeners.get(name) ?? new Set(); + listeners.add(listener); + this.listeners.set(name, listeners); + } + removeEventListener(name: string, listener: () => void): void { this.listeners.get(name)?.delete(listener); } + close(): void {} + private emit(name: string): void { for (const listener of this.listeners.get(name) ?? []) listener(); } + } + const backend = parseTailnetBackend("https://host.tailnet.ts.net:8445"); + await expect(probeMobileBackend(backend, { WebSocketImpl: OpeningSocket as unknown as typeof WebSocket })).resolves.toBeUndefined(); + expect(OpeningSocket.url).toBe(backend.wsUrl); + }); + + it("renders focused first-run instructions instead of fixture sessions", () => { + const markup = renderToStaticMarkup(); + expect(markup).toContain("Connect to your T4 host"); + expect(markup).toContain("Open Tailscale on this phone"); + expect(markup).toContain("h-12 w-full"); + expect(markup).not.toContain("Sample data"); + }); +}); diff --git a/compat/omp-app-matrix.json b/compat/omp-app-matrix.json index a6452958..403f4d46 100644 --- a/compat/omp-app-matrix.json +++ b/compat/omp-app-matrix.json @@ -34,6 +34,6 @@ }, "desktop": { "package": "@t4-code/protocol", - "version": "0.1.8" + "version": "0.1.9" } } diff --git a/docs/TAILNET_REMOTE.md b/docs/TAILNET_REMOTE.md index e70b39a9..c60d1b03 100644 --- a/docs/TAILNET_REMOTE.md +++ b/docs/TAILNET_REMOTE.md @@ -4,16 +4,17 @@ T4 Code can be used from a phone or another computer on the same Tailscale network. The remote path is intentionally small: ```text -phone browser - -> Tailscale Serve HTTPS (tailnet only) +phone browser or bundled T4 mobile UI + -> Tailscale Serve HTTPS/WSS (tailnet only) -> T4 gateway on 127.0.0.1:4194 -> OMP appserver Unix socket ``` There is no T4 password in this mode. Tailscale membership and your tailnet ACLs or grants are the access boundary. The gateway never listens on a LAN -interface, accepts WebSocket connections only from the exact configured -`.ts.net` HTTPS origin, and passes no token or password through the browser. +interface. It accepts WebSocket connections from the exact configured `.ts.net` +HTTPS origin and the two fixed Capacitor WebView origins described below. No +token or password is passed through the browser or mobile UI. > This is a source-hosted feature in the current release. The downloadable > desktop packages do not install or manage the Tailnet gateway. Keep the @@ -115,6 +116,37 @@ connected to the tailnet, the host gateway must be running, and OMP remains the owner of every session. The web app deliberately does not cache transcripts, host configuration, or runtime state for offline use. +## Native mobile client origins + +The Android APK and the planned iOS build bundle the T4 UI. They connect to the +same `/v1/ws` endpoint over WSS; they do not load the hosted website into the +WebView. Capacitor's documented defaults give those bundled pages these +origins: + +- Android: `https://localhost` +- iOS: `capacitor://localhost` + +The service installer writes those exact values to +`T4_NATIVE_ALLOWED_ORIGINS`. The gateway accepts them in addition to the one +Tailnet HTTPS origin passed through `--origin`. Empty values, `null`, wildcard +origins, alternate localhost schemes, and extra origins are rejected at +startup. These values must stay aligned with the mobile +`server.hostname`, `server.androidScheme`, and `server.iosScheme` settings. See +the [Capacitor configuration reference](https://capacitorjs.com/docs/config#schema) +for the platform defaults. + +The hosted web response keeps an exact `connect-src` entry for its configured +`wss://HOST.ts.net[:PORT]` endpoint. The bundled mobile document can connect to +user-selected profiles only under `wss://*.ts.net:*`. That subdomain pattern +restricts outbound destinations; it is never added to the gateway's accepted +WebSocket Origin list. Do not replace either policy with an unrestricted +`connect-src *`. + +An Origin header identifies the page that opened the WebSocket. It does not +prove that the caller is the signed T4 APK. Tailscale membership and ACLs remain +the access boundary, so the gateway must stay behind Tailscale Serve with +Funnel disabled. + ## Operate and update The lifecycle commands are the same on Linux and macOS: @@ -170,8 +202,10 @@ removes all of them. - The local HTTP gateway is fixed to `127.0.0.1` (or `::1` when explicitly configured by code); it cannot be configured to listen on a LAN address. -- The browser endpoint is an exact HTTPS `.ts.net` origin. Plain HTTP, public - domains, URL paths, embedded credentials, and wildcard origins are rejected. +- The hosted browser endpoint is an exact HTTPS `.ts.net` origin. The native + exceptions are exactly `https://localhost` and `capacitor://localhost`. + Plain HTTP Tailnet origins, public domains, URL paths, embedded credentials, + `null`, wildcard origins, and any other native origin are rejected. - Tailscale Serve terminates HTTPS and keeps the route tailnet-only. Do not substitute Funnel. - Every tailnet identity permitted to reach this node and port can operate the diff --git a/e2e/site-mobile-docs.spec.ts b/e2e/site-mobile-docs.spec.ts index b520bd15..927a75e0 100644 --- a/e2e/site-mobile-docs.spec.ts +++ b/e2e/site-mobile-docs.spec.ts @@ -122,6 +122,31 @@ test.afterAll(async () => { await site?.stop(); }); +test("offers the Android APK without hiding desktop downloads", async ({ page }) => { + await page.goto(`${site.url}/`, { waitUntil: "networkidle" }); + + const androidDownload = page.getByRole("link", { name: "Download Android APK" }).first(); + await expect(androidDownload).toBeVisible(); + await expect(androidDownload).toHaveAttribute( + "href", + "https://github.com/LycaonLLC/t4-code/releases/download/v0.1.9/T4-Code-0.1.9-android.apk", + ); + await expect(page.getByRole("link", { name: "Download for Linux" }).first()).toBeVisible(); + await expect(page.getByRole("link", { name: "macOS build" }).first()).toBeVisible(); + await expect(page.getByText("TestFlight coming soon", { exact: true }).first()).toBeVisible(); + + const geometry = await page.evaluate(() => ({ + clientWidth: document.documentElement.clientWidth, + documentScrollWidth: document.documentElement.scrollWidth, + bodyScrollWidth: document.body.scrollWidth, + })); + expect(geometry).toEqual({ + clientWidth: 320, + documentScrollWidth: 320, + bodyScrollWidth: 320, + }); +}); + test("every docs topic stays inside a 320px viewport", async ({ page }) => { for (const topic of TOPICS) { await page.goto(`${site.url}/docs/#${topic}`, { waitUntil: "networkidle" }); diff --git a/package.json b/package.json index 426aa544..b124c8ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/root", - "version": "0.1.8", + "version": "0.1.9", "private": true, "type": "module", "scripts": { diff --git a/packages/client/package.json b/packages/client/package.json index 34fb3676..28da762d 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/client", - "version": "0.1.8", + "version": "0.1.9", "private": true, "type": "module", "exports": { diff --git a/packages/client/src/omp-client-frames.ts b/packages/client/src/omp-client-frames.ts index 86cc0c83..cfedc2fc 100644 --- a/packages/client/src/omp-client-frames.ts +++ b/packages/client/src/omp-client-frames.ts @@ -66,7 +66,7 @@ export function sendClientHello( v: PROTOCOL_VERSION, type: "hello", protocol: { min: PROTOCOL_VERSION, max: PROTOCOL_VERSION }, - client: options.client ?? { name: "t4-code", version: "0.1.8", build: "client", platform: "electron" }, + client: options.client ?? { name: "t4-code", version: "0.1.9", build: "client", platform: "electron" }, requestedFeatures: [...(options.requestedFeatures ?? ["resume"])], savedCursors, ...(options.capabilities === undefined ? {} : { capabilities: { client: [...options.capabilities] } }), diff --git a/packages/fixture-server/package.json b/packages/fixture-server/package.json index 62e56997..86ba3e7a 100644 --- a/packages/fixture-server/package.json +++ b/packages/fixture-server/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/fixture-server", - "version": "0.1.8", + "version": "0.1.9", "private": true, "type": "module", "exports": { diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 3e02193a..c7229193 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/protocol", - "version": "0.1.8", + "version": "0.1.9", "private": true, "type": "module", "exports": { diff --git a/packages/remote/package.json b/packages/remote/package.json index dde2c5f1..c851f3ec 100644 --- a/packages/remote/package.json +++ b/packages/remote/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/remote", - "version": "0.1.8", + "version": "0.1.9", "private": true, "type": "module", "exports": { diff --git a/packages/service-manager/package.json b/packages/service-manager/package.json index f5037a8e..668aa1c9 100644 --- a/packages/service-manager/package.json +++ b/packages/service-manager/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/service-manager", - "version": "0.1.8", + "version": "0.1.9", "private": true, "type": "module", "exports": { diff --git a/packages/ui/package.json b/packages/ui/package.json index d3832da8..d3d299a7 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/ui", - "version": "0.1.8", + "version": "0.1.9", "private": true, "type": "module", "exports": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7616dc8e..259c4916 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,7 +64,7 @@ importers: version: 2.7.0 vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) apps/desktop: dependencies: @@ -107,7 +107,20 @@ importers: version: 26.15.6(electron-builder-squirrel-windows@26.15.6) vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) + + apps/mobile: + dependencies: + '@capacitor/core': + specifier: 8.4.1 + version: 8.4.1 + devDependencies: + '@capacitor/android': + specifier: 8.4.1 + version: 8.4.1(@capacitor/core@8.4.1) + '@capacitor/cli': + specifier: 8.4.1 + version: 8.4.1 apps/site: dependencies: @@ -132,13 +145,13 @@ importers: version: 19.2.3(@types/react@19.2.17) '@vitejs/plugin-react': specifier: ^6.0.0 - version: 6.0.3(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)) + version: 6.0.3(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) vite: specifier: npm:@voidzero-dev/vite-plus-core@0.2.2 - version: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)' + version: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@26.1.1)(jiti@2.7.0) + version: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3) apps/web: dependencies: @@ -214,7 +227,7 @@ importers: devDependencies: '@tailwindcss/vite': specifier: ^4.0.0 - version: 4.3.2(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)) + version: 4.3.2(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) '@types/react': specifier: 'catalog:' version: 19.2.17 @@ -223,16 +236,16 @@ importers: version: 19.2.3(@types/react@19.2.17) '@vitejs/plugin-react': specifier: ^6.0.0 - version: 6.0.3(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)) + version: 6.0.3(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) tailwindcss: specifier: ^4.0.0 version: 4.3.2 vite: specifier: npm:@voidzero-dev/vite-plus-core@0.2.2 - version: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)' + version: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@26.1.1)(jiti@2.7.0) + version: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3) packages/client: dependencies: @@ -251,7 +264,7 @@ importers: version: 8.5.13 vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) ws: specifier: 8.18.3 version: 8.18.3 @@ -273,7 +286,7 @@ importers: version: 8.5.13 vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) packages/protocol: dependencies: @@ -286,7 +299,7 @@ importers: version: 24.12.4 vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) packages/remote: dependencies: @@ -299,7 +312,7 @@ importers: version: 24.12.4 vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) packages/service-manager: devDependencies: @@ -308,7 +321,7 @@ importers: version: 24.12.4 vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) packages/ui: dependencies: @@ -345,7 +358,7 @@ importers: version: 19.2.6(react@19.2.6) vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@26.1.1)(jiti@2.7.0) + version: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3) packages: @@ -391,6 +404,19 @@ packages: '@blazediff/core@1.9.1': resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==} + '@capacitor/android@8.4.1': + resolution: {integrity: sha512-igtDCJ7QQn0P2qHFD9p4KXaa6V1b2PRNt+MxjVwtjTm/BJvqmiazOJq6rPjwFSZnfHm6iFoZk8TfzHd44pyBGw==} + peerDependencies: + '@capacitor/core': ^8.4.0 + + '@capacitor/cli@8.4.1': + resolution: {integrity: sha512-t7F2s7fFHCq113xgrggrmK6ctV0/8E5YfLNVLfPHp4GCTDO+tly9fZvWPf2/sOI8lMm18dLT43qbXLRTz/OZgw==} + engines: {node: '>=22.0.0'} + hasBin: true + + '@capacitor/core@8.4.1': + resolution: {integrity: sha512-xqhOGLbTAYeOWK+IDUNSjQJAPapQjRHrIcgk9PYp52or9zFTaaMko31uNi16N6W+CRJ8VrRram6fOYILkBG2Hg==} + '@electron/asar@3.4.1': resolution: {integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==} engines: {node: '>=10.12.0'} @@ -458,6 +484,38 @@ packages: '@fontsource/jetbrains-mono@5.2.8': resolution: {integrity: sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==} + '@ionic/cli-framework-output@2.2.8': + resolution: {integrity: sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-array@2.1.6': + resolution: {integrity: sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-fs@3.1.7': + resolution: {integrity: sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-object@2.1.6': + resolution: {integrity: sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-process@2.1.12': + resolution: {integrity: sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-stream@3.1.7': + resolution: {integrity: sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-subprocess@3.0.1': + resolution: {integrity: sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-terminal@2.3.5': + resolution: {integrity: sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==} + engines: {node: '>=16.0.0'} + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -1058,6 +1116,9 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/fs-extra@8.1.5': + resolution: {integrity: sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==} + '@types/fs-extra@9.0.13': resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} @@ -1093,6 +1154,9 @@ packages: '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + '@types/slice-ansi@4.0.0': + resolution: {integrity: sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1378,6 +1442,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + async-exit-hook@2.0.1: resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} engines: {node: '>=0.12.0'} @@ -1412,6 +1480,10 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} @@ -1419,6 +1491,10 @@ packages: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + bplist-parser@0.3.2: + resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} + engines: {node: '>= 5.10.0'} + brace-expansion@1.1.16: resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} @@ -1525,6 +1601,10 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@5.1.0: resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} engines: {node: '>= 6'} @@ -1591,6 +1671,10 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -1672,6 +1756,10 @@ packages: engines: {node: '>= 12.20.55'} hasBin: true + elementtree@0.1.7: + resolution: {integrity: sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==} + engines: {node: '>= 0.4.0'} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1833,6 +1921,10 @@ packages: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -1931,6 +2023,10 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@4.1.3: + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} @@ -1943,6 +2039,11 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -1958,6 +2059,10 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -2029,6 +2134,14 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + lazy-val@1.0.5: resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} @@ -2135,6 +2248,10 @@ packages: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -2365,6 +2482,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + native-run@2.0.3: + resolution: {integrity: sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q==} + engines: {node: '>=16.0.0'} + hasBin: true + node-abi@4.33.0: resolution: {integrity: sha512-vLBWCKb+7LWsX+TbfzWOkw0W81m377tyx3hOweBTjO43CXZnRGS1/JPWs20fr0PgZyDXk6ROYrylsEycK8raDA==} engines: {node: '>=22.12.0'} @@ -2404,6 +2526,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + oxfmt@0.57.0: resolution: {integrity: sha512-ZB7Bi+rGDSqmVIo9jwcLyFgjxXvQhDdU+jx+ZrVy6VRiVXK2+CHc4hO3J4dUQjHe7V0ymHB+MDuv5z+NhK07HA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2454,6 +2580,9 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} @@ -2472,6 +2601,10 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -2547,6 +2680,10 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} @@ -2597,6 +2734,10 @@ packages: readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} @@ -2648,6 +2789,11 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + rimraf@6.1.3: + resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} + engines: {node: 20 || >=22} + hasBin: true + roarr@2.15.4: resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} engines: {node: '>=8.0'} @@ -2658,6 +2804,9 @@ packages: sanitize-filename@1.6.4: resolution: {integrity: sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==} + sax@1.1.4: + resolution: {integrity: sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==} + sax@1.6.0: resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} engines: {node: '>=11.0.0'} @@ -2722,6 +2871,13 @@ packages: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2736,6 +2892,10 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} @@ -2801,6 +2961,9 @@ packages: resolution: {integrity: sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==} engines: {node: '>=6.0.0'} + through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + tiny-async-pool@1.3.0: resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==} @@ -2837,6 +3000,10 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -2857,6 +3024,11 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -2893,6 +3065,10 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + untildify@4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + unzipper@0.12.5: resolution: {integrity: sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==} @@ -3027,6 +3203,14 @@ packages: utf-8-validate: optional: true + xml2js@0.6.2: + resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + xmlbuilder@15.1.1: resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} engines: {node: '>=8.0'} @@ -3119,6 +3303,36 @@ snapshots: '@blazediff/core@1.9.1': {} + '@capacitor/android@8.4.1(@capacitor/core@8.4.1)': + dependencies: + '@capacitor/core': 8.4.1 + + '@capacitor/cli@8.4.1': + dependencies: + '@ionic/cli-framework-output': 2.2.8 + '@ionic/utils-subprocess': 3.0.1 + '@ionic/utils-terminal': 2.3.5 + commander: 12.1.0 + debug: 4.4.3 + env-paths: 2.2.1 + fs-extra: 11.3.6 + kleur: 4.1.5 + native-run: 2.0.3 + open: 8.4.2 + plist: 3.1.0 + prompts: 2.4.2 + rimraf: 6.1.3 + semver: 7.8.5 + tar: 7.5.19 + tslib: 2.8.1 + xml2js: 0.6.2 + transitivePeerDependencies: + - supports-color + + '@capacitor/core@8.4.1': + dependencies: + tslib: 2.8.1 + '@electron/asar@3.4.1': dependencies: commander: 5.1.0 @@ -3241,6 +3455,82 @@ snapshots: '@fontsource/jetbrains-mono@5.2.8': {} + '@ionic/cli-framework-output@2.2.8': + dependencies: + '@ionic/utils-terminal': 2.3.5 + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-array@2.1.6': + dependencies: + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-fs@3.1.7': + dependencies: + '@types/fs-extra': 8.1.5 + debug: 4.4.3 + fs-extra: 9.1.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-object@2.1.6': + dependencies: + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-process@2.1.12': + dependencies: + '@ionic/utils-object': 2.1.6 + '@ionic/utils-terminal': 2.3.5 + debug: 4.4.3 + signal-exit: 3.0.7 + tree-kill: 1.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-stream@3.1.7': + dependencies: + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-subprocess@3.0.1': + dependencies: + '@ionic/utils-array': 2.1.6 + '@ionic/utils-fs': 3.1.7 + '@ionic/utils-process': 2.1.12 + '@ionic/utils-stream': 3.1.7 + '@ionic/utils-terminal': 2.3.5 + cross-spawn: 7.0.6 + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-terminal@2.3.5': + dependencies: + '@types/slice-ansi': 4.0.0 + debug: 4.4.3 + signal-exit: 3.0.7 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + tslib: 2.8.1 + untildify: 4.0.0 + wrap-ansi: 7.0.0 + transitivePeerDependencies: + - supports-color + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.3 @@ -3688,12 +3978,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.2 '@tailwindcss/oxide-win32-x64-msvc': 4.3.2 - '@tailwindcss/vite@4.3.2(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0))': + '@tailwindcss/vite@4.3.2(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))': dependencies: '@tailwindcss/node': 4.3.2 '@tailwindcss/oxide': 4.3.2 tailwindcss: 4.3.2 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' '@tanstack/history@1.162.0': {} @@ -3763,6 +4053,10 @@ snapshots: '@types/estree@1.0.9': {} + '@types/fs-extra@8.1.5': + dependencies: + '@types/node': 26.1.1 + '@types/fs-extra@9.0.13': dependencies: '@types/node': 26.1.1 @@ -3803,6 +4097,8 @@ snapshots: dependencies: '@types/node': 26.1.1 + '@types/slice-ansi@4.0.0': {} + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -3849,45 +4145,45 @@ snapshots: '@ungap/structured-clone@1.3.2': {} - '@vitejs/plugin-react@6.0.3(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0))': + '@vitejs/plugin-react@6.0.3(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' - '@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0))(vitest@4.1.9)': + '@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9)': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0))(vitest@4.1.9) - vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)) + '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9) + vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)))': + '@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)))': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0))) - vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)) + '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))) + vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0))(vitest@4.1.9)': + '@vitest/browser@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) '@vitest/utils': 4.1.9 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)) + vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) ws: 8.21.0 transitivePeerDependencies: - bufferutil @@ -3895,16 +4191,16 @@ snapshots: - utf-8-validate - vite - '@vitest/browser@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)))': + '@vitest/browser@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)))': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) '@vitest/utils': 4.1.9 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)) + vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) ws: 8.21.0 transitivePeerDependencies: - bufferutil @@ -3921,21 +4217,21 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0))': + '@vitest/mocker@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))': dependencies: '@vitest/spy': 4.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)' - '@vitest/mocker@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0))': + '@vitest/mocker@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))': dependencies: '@vitest/spy': 4.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' '@vitest/pretty-format@4.1.9': dependencies: @@ -3961,7 +4257,7 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)': + '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)': dependencies: '@oxc-project/runtime': 0.138.0 '@oxc-project/types': 0.138.0 @@ -3971,8 +4267,9 @@ snapshots: '@types/node': 24.12.4 fsevents: 2.3.3 jiti: 2.7.0 + typescript: 6.0.3 - '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)': + '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)': dependencies: '@oxc-project/runtime': 0.138.0 '@oxc-project/types': 0.138.0 @@ -3982,6 +4279,7 @@ snapshots: '@types/node': 26.1.1 fsevents: 2.3.3 jiti: 2.7.0 + typescript: 6.0.3 '@voidzero-dev/vite-plus-darwin-arm64@0.2.2': optional: true @@ -4098,6 +4396,8 @@ snapshots: assertion-error@2.0.1: {} + astral-regex@2.0.0: {} + async-exit-hook@2.0.1: {} async@3.2.6: {} @@ -4118,11 +4418,17 @@ snapshots: base64-js@1.5.1: {} + big-integer@1.6.52: {} + bluebird@3.7.2: {} boolean@3.2.0: optional: true + bplist-parser@0.3.2: + dependencies: + big-integer: 1.6.52 + brace-expansion@1.1.16: dependencies: balanced-match: 1.0.2 @@ -4238,6 +4544,8 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@12.1.0: {} + commander@5.1.0: {} commander@9.5.0: @@ -4302,6 +4610,8 @@ snapshots: gopd: 1.2.0 optional: true + define-lazy-prop@2.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -4440,6 +4750,10 @@ snapshots: transitivePeerDependencies: - supports-color + elementtree@0.1.7: + dependencies: + sax: 1.1.4 + emoji-regex@8.0.0: {} end-of-stream@1.4.5: @@ -4605,6 +4919,12 @@ snapshots: dependencies: pump: 3.0.4 + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -4780,6 +5100,8 @@ snapshots: inherits@2.0.4: {} + ini@4.1.3: {} + inline-style-parser@0.2.7: {} is-alphabetical@2.0.1: {} @@ -4791,6 +5113,8 @@ snapshots: is-decimal@2.0.1: {} + is-docker@2.2.1: {} + is-fullwidth-code-point@3.0.0: {} is-hexadecimal@2.0.1: {} @@ -4799,6 +5123,10 @@ snapshots: is-plain-obj@4.1.0: {} + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + isarray@1.0.0: {} isbinaryfile@4.0.10: {} @@ -4854,6 +5182,10 @@ snapshots: dependencies: json-buffer: 3.0.1 + kleur@3.0.3: {} + + kleur@4.1.5: {} + lazy-val@1.0.5: {} lexical@0.41.0: {} @@ -4926,6 +5258,8 @@ snapshots: lowercase-keys@2.0.0: {} + lru-cache@11.5.2: {} + lru-cache@6.0.0: dependencies: yallist: 4.0.0 @@ -5348,6 +5682,22 @@ snapshots: nanoid@3.3.15: {} + native-run@2.0.3: + dependencies: + '@ionic/utils-fs': 3.1.7 + '@ionic/utils-terminal': 2.3.5 + bplist-parser: 0.3.2 + debug: 4.4.3 + elementtree: 0.1.7 + ini: 4.1.3 + plist: 3.1.0 + split2: 4.2.0 + through2: 4.0.2 + tslib: 2.8.1 + yauzl: 2.10.0 + transitivePeerDependencies: + - supports-color + node-abi@4.33.0: dependencies: semver: 7.8.5 @@ -5390,7 +5740,13 @@ snapshots: dependencies: mimic-fn: 2.1.0 - oxfmt@0.57.0(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)): + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + oxfmt@0.57.0(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)): dependencies: tinypool: 2.1.0 optionalDependencies: @@ -5413,9 +5769,9 @@ snapshots: '@oxfmt/binding-win32-arm64-msvc': 0.57.0 '@oxfmt/binding-win32-ia32-msvc': 0.57.0 '@oxfmt/binding-win32-x64-msvc': 0.57.0 - vite-plus: 0.2.2(@types/node@24.12.4)(jiti@2.7.0) + vite-plus: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) - oxfmt@0.57.0(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)): + oxfmt@0.57.0(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)): dependencies: tinypool: 2.1.0 optionalDependencies: @@ -5438,7 +5794,7 @@ snapshots: '@oxfmt/binding-win32-arm64-msvc': 0.57.0 '@oxfmt/binding-win32-ia32-msvc': 0.57.0 '@oxfmt/binding-win32-x64-msvc': 0.57.0 - vite-plus: 0.2.2(@types/node@26.1.1)(jiti@2.7.0) + vite-plus: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3) oxlint-tsgolint@0.24.0: optionalDependencies: @@ -5449,7 +5805,7 @@ snapshots: '@oxlint-tsgolint/win32-arm64': 0.24.0 '@oxlint-tsgolint/win32-x64': 0.24.0 - oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)): + oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.72.0 '@oxlint/binding-android-arm64': 1.72.0 @@ -5471,9 +5827,9 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.72.0 '@oxlint/binding-win32-x64-msvc': 1.72.0 oxlint-tsgolint: 0.24.0 - vite-plus: 0.2.2(@types/node@24.12.4)(jiti@2.7.0) + vite-plus: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) - oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)): + oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.72.0 '@oxlint/binding-android-arm64': 1.72.0 @@ -5495,7 +5851,7 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.72.0 '@oxlint/binding-win32-x64-msvc': 1.72.0 oxlint-tsgolint: 0.24.0 - vite-plus: 0.2.2(@types/node@26.1.1)(jiti@2.7.0) + vite-plus: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3) p-cancelable@2.1.1: {} @@ -5513,6 +5869,8 @@ snapshots: p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} + parse-entities@4.0.2: dependencies: '@types/unist': 2.0.11 @@ -5533,6 +5891,11 @@ snapshots: path-key@3.1.1: {} + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.2 + minipass: 7.1.3 + pathe@2.0.3: {} pe-library@0.4.1: {} @@ -5602,6 +5965,11 @@ snapshots: err-code: 2.0.3 retry: 0.12.0 + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + proper-lockfile@4.1.2: dependencies: graceful-fs: 4.2.11 @@ -5670,6 +6038,12 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + rehype-raw@7.0.0: dependencies: '@types/hast': 3.0.5 @@ -5743,6 +6117,11 @@ snapshots: dependencies: glob: 7.2.3 + rimraf@6.1.3: + dependencies: + glob: 13.0.6 + package-json-from-dist: 1.0.1 + roarr@2.15.4: dependencies: boolean: 3.2.0 @@ -5759,6 +6138,8 @@ snapshots: dependencies: truncate-utf8-bytes: 1.0.2 + sax@1.1.4: {} + sax@1.6.0: {} scheduler@0.27.0: {} @@ -5805,6 +6186,14 @@ snapshots: mrmime: 2.0.1 totalist: 3.0.1 + sisteransi@1.0.5: {} + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -5816,6 +6205,8 @@ snapshots: space-separated-tokens@2.0.2: {} + split2@4.2.0: {} + sprintf-js@1.1.3: optional: true @@ -5888,6 +6279,10 @@ snapshots: mkdirp: 0.5.6 rimraf: 2.6.3 + through2@4.0.2: + dependencies: + readable-stream: 3.6.2 + tiny-async-pool@1.3.0: dependencies: semver: 5.7.2 @@ -5915,6 +6310,8 @@ snapshots: totalist@3.0.1: {} + tree-kill@1.2.2: {} + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -5930,6 +6327,9 @@ snapshots: type-fest@2.19.0: {} + typescript@6.0.3: + optional: true + undici-types@7.16.0: {} undici-types@8.3.0: {} @@ -5973,6 +6373,8 @@ snapshots: universalify@2.0.1: {} + untildify@4.0.0: {} + unzipper@0.12.5: dependencies: bluebird: 3.7.2 @@ -6004,25 +6406,25 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0): + vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3): dependencies: '@oxc-project/types': 0.138.0 '@oxlint/plugins': 1.68.0 - '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0))(vitest@4.1.9) - '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0))(vitest@4.1.9) + '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9) + '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9) '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 '@vitest/spy': 4.1.9 '@vitest/utils': 4.1.9 - '@voidzero-dev/vite-plus-core': 0.2.2(@types/node@24.12.4)(jiti@2.7.0) - oxfmt: 0.57.0(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)) - oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)) + '@voidzero-dev/vite-plus-core': 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) + oxfmt: 0.57.0(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) + oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) oxlint-tsgolint: 0.24.0 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)' - vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)) + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)' + vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) optionalDependencies: '@voidzero-dev/vite-plus-darwin-arm64': 0.2.2 '@voidzero-dev/vite-plus-darwin-x64': 0.2.2 @@ -6062,25 +6464,25 @@ snapshots: - utf-8-validate - yaml - vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0): + vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3): dependencies: '@oxc-project/types': 0.138.0 '@oxlint/plugins': 1.68.0 - '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0))) - '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0))) + '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))) + '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))) '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 '@vitest/spy': 4.1.9 '@vitest/utils': 4.1.9 - '@voidzero-dev/vite-plus-core': 0.2.2(@types/node@26.1.1)(jiti@2.7.0) - oxfmt: 0.57.0(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)) - oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)) + '@voidzero-dev/vite-plus-core': 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3) + oxfmt: 0.57.0(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) + oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) oxlint-tsgolint: 0.24.0 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)' - vitest: 4.1.9(@types/node@26.1.1)(@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0))(vitest@4.1.9))(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)) + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' + vitest: 4.1.9(@types/node@26.1.1)(@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9))(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) optionalDependencies: '@voidzero-dev/vite-plus-darwin-arm64': 0.2.2 '@voidzero-dev/vite-plus-darwin-x64': 0.2.2 @@ -6120,10 +6522,10 @@ snapshots: - utf-8-validate - yaml - vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)): + vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)): dependencies: '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 @@ -6140,18 +6542,18 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)' why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.12.4 - '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0))(vitest@4.1.9) + '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9) transitivePeerDependencies: - msw - vitest@4.1.9(@types/node@26.1.1)(@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0))(vitest@4.1.9))(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)): + vitest@4.1.9(@types/node@26.1.1)(@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9))(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)): dependencies: '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 @@ -6168,11 +6570,11 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 26.1.1 - '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0))(vitest@4.1.9) + '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9) transitivePeerDependencies: - msw @@ -6215,6 +6617,13 @@ snapshots: ws@8.21.0: {} + xml2js@0.6.2: + dependencies: + sax: 1.6.0 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + xmlbuilder@15.1.1: {} y18n@5.0.8: {} diff --git a/scripts/check-release-consistency.mjs b/scripts/check-release-consistency.mjs index 07728c9d..038bb1d9 100644 --- a/scripts/check-release-consistency.mjs +++ b/scripts/check-release-consistency.mjs @@ -45,6 +45,7 @@ const VERSION_PATTERN = /^\d+\.\d+\.\d+$/u; export function expectedReleaseAssetNames(version) { return [ + `T4-Code-${version}-android.apk`, `T4-Code-${version}-linux-amd64.deb`, `T4-Code-${version}-linux-x86_64.AppImage`, `T4-Code-${version}-mac-arm64.dmg`, @@ -251,7 +252,9 @@ export function collectReleaseConsistencyErrors(files, releaseTag) { requireText(site, `"${filename}"`, "apps/site/src/release.ts", errors); } const siteAssetVersions = new Set( - [...site.matchAll(/T4-Code-(\d+\.\d+\.\d+)-(?:linux|mac)-/gu)].map((match) => match[1]), + [...site.matchAll(/T4-Code-(\d+\.\d+\.\d+)-(?:android|linux|mac)(?:\.|-)/gu)].map( + (match) => match[1], + ), ); for (const assetVersion of siteAssetVersions) { if (assetVersion !== version) { @@ -405,6 +408,15 @@ export function collectReleaseConsistencyErrors(files, releaseTag) { "ref: ${{ needs.verify.outputs.source_sha }}", "Confirm the release tag still resolves to the verified source", 'test "$(git rev-parse "${RELEASE_TAG}^{commit}")" = "$SOURCE_SHA"', + "build-android:", + "T4_ANDROID_KEYSTORE_BASE64", + "T4_ANDROID_KEYSTORE_PASSWORD", + "T4_ANDROID_KEY_ALIAS", + "T4_ANDROID_KEY_PASSWORD", + "pnpm --filter @t4-code/mobile build:android:release", + "apksigner verify --verbose", + "T4-Code-${VERSION}-android.apk", + "needs: [verify, build-android, build-linux, build-macos]", ]) { requireText(releaseWorkflow, expected, ".github/workflows/release.yml", errors); } diff --git a/scripts/check-release-consistency.test.mjs b/scripts/check-release-consistency.test.mjs index 97aed51f..8a66a793 100644 --- a/scripts/check-release-consistency.test.mjs +++ b/scripts/check-release-consistency.test.mjs @@ -17,23 +17,23 @@ function changed(path, replace) { } test("current source tree has one consistent release version", () => { - assert.deepEqual(collectReleaseConsistencyErrors(files, "v0.1.8"), []); + assert.deepEqual(collectReleaseConsistencyErrors(files, "v0.1.9"), []); }); test("rejects a tag that differs from the package version", () => { assert.ok( collectReleaseConsistencyErrors(files, "v9.9.9").some((error) => - error.includes("release tag v9.9.9 does not match v0.1.8"), + error.includes("release tag v9.9.9 does not match v0.1.9"), ), ); }); test("rejects workspace, site, README, and runtime version drift", () => { const cases = [ - ["apps/web/package.json", (text) => text.replace('"version": "0.1.8"', '"version": "0.1.3"')], - ["apps/site/src/release.ts", (text) => text.replace('RELEASE_TAG = "v0.1.8"', 'RELEASE_TAG = "v0.1.3"')], - ["README.md", (text) => text.replace("Download v0.1.8", "Download v0.1.3")], - ["apps/desktop/src/target-manager.ts", (text) => text.replace('version: "0.1.8"', 'version: "0.1.3"')], + ["apps/web/package.json", (text) => text.replace('"version": "0.1.9"', '"version": "0.1.3"')], + ["apps/site/src/release.ts", (text) => text.replace('RELEASE_TAG = "v0.1.9"', 'RELEASE_TAG = "v0.1.3"')], + ["README.md", (text) => text.replace("Download v0.1.9", "Download v0.1.3")], + ["apps/desktop/src/target-manager.ts", (text) => text.replace('version: "0.1.9"', 'version: "0.1.3"')], ["apps/site/src/docs/content.ts", (text) => text.replace('id: "troubleshooting-large-session"', 'id: "missing-large-session"')], ]; for (const [path, replace] of cases) { @@ -123,7 +123,7 @@ test("rejects stale README release URLs while allowing historical prose", () => ); assert.ok( collectReleaseConsistencyErrors(staleLink).some((error) => - error.includes("release URL for v0.1.3; expected v0.1.8"), + error.includes("release URL for v0.1.3; expected v0.1.9"), ), ); assert.deepEqual(collectReleaseConsistencyErrors(files), []); @@ -144,7 +144,10 @@ test("deploys release site source only after artifact publication", () => { ); assert.ok(releaseWorkflow.includes("ref: ${{ steps.source.outputs.source_sha }}")); assert.ok(releaseWorkflow.includes("ref: ${{ needs.verify.outputs.source_sha }}")); - assert.ok(releaseWorkflow.includes("needs: [verify, build-linux, build-macos]")); + assert.ok(releaseWorkflow.includes("needs: [verify, build-android, build-linux, build-macos]")); + assert.ok(releaseWorkflow.includes("pnpm --filter @t4-code/mobile build:android:release")); + assert.ok(releaseWorkflow.includes("T4_ANDROID_KEYSTORE_BASE64")); + assert.ok(releaseWorkflow.includes("apksigner verify --verbose")); assert.ok( releaseWorkflow.includes("Confirm the release tag still resolves to the verified source"), ); diff --git a/scripts/inspect-macos-dmg.test.mjs b/scripts/inspect-macos-dmg.test.mjs index ea57c250..65ed1eab 100644 --- a/scripts/inspect-macos-dmg.test.mjs +++ b/scripts/inspect-macos-dmg.test.mjs @@ -5,7 +5,7 @@ import { resolve } from "node:path"; import { findMountedApp, inspectMacosDmg } from "./inspect-macos-dmg.mjs"; -const dmgPath = "/tmp/T4-Code-0.1.8-mac-arm64.dmg"; +const dmgPath = "/tmp/T4-Code-0.1.9-mac-arm64.dmg"; const mountPoint = "/tmp/t4-code-dmg-test"; function directory(name) { diff --git a/scripts/tailnet-gateway.mjs b/scripts/tailnet-gateway.mjs index 77c9d6c3..bec56e99 100644 --- a/scripts/tailnet-gateway.mjs +++ b/scripts/tailnet-gateway.mjs @@ -17,6 +17,14 @@ const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000; const LOOPBACK_HOSTS = new Set(["127.0.0.1", "::1"]); const OMP_SOCKET_NAME = /^\.appserver-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.sock$/u; +// Capacitor v8 serves bundled assets from these origins when its documented +// default hostname and platform schemes are used. Keep this list exact: an +// Origin header identifies a browser context, not a signed mobile binary. +export const CAPACITOR_NATIVE_ORIGINS = Object.freeze([ + "https://localhost", + "capacitor://localhost", +]); + const MIME_TYPES = new Map([ [".css", "text/css; charset=utf-8"], [".html", "text/html; charset=utf-8"], @@ -61,6 +69,23 @@ export function normalizeAllowedOrigin(value) { return url.origin; } +export function normalizeNativeAllowedOrigins(value = CAPACITOR_NATIVE_ORIGINS) { + if (!Array.isArray(value)) { + throw new Error("T4_NATIVE_ALLOWED_ORIGINS must be an array"); + } + const origins = value.map((origin) => requiredText(origin, "T4_NATIVE_ALLOWED_ORIGINS", 128)); + const unique = new Set(origins); + if ( + unique.size !== CAPACITOR_NATIVE_ORIGINS.length || + !CAPACITOR_NATIVE_ORIGINS.every((origin) => unique.has(origin)) + ) { + throw new Error( + `T4_NATIVE_ALLOWED_ORIGINS must contain exactly ${CAPACITOR_NATIVE_ORIGINS.join(", ")}`, + ); + } + return [...CAPACITOR_NATIVE_ORIGINS]; +} + export function websocketUrlForOrigin(origin) { const url = new URL("/v1/ws", normalizeAllowedOrigin(origin)); url.protocol = "wss:"; @@ -312,6 +337,7 @@ export async function startTailnetGateway(input) { listenHost: input.listenHost ?? "127.0.0.1", listenPort: input.listenPort ?? DEFAULT_PORT, allowedOrigin: normalizeAllowedOrigin(input.allowedOrigin), + nativeAllowedOrigins: normalizeNativeAllowedOrigins(input.nativeAllowedOrigins), label: input.label ?? "OMP on this Tailnet host", heartbeatIntervalMs: input.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS, }; @@ -322,6 +348,7 @@ export async function startTailnetGateway(input) { if (!Number.isSafeInteger(options.heartbeatIntervalMs) || options.heartbeatIntervalMs < 10) { throw new Error("Tailnet gateway heartbeat interval is invalid"); } + const allowedSocketOrigins = new Set([options.allowedOrigin, ...options.nativeAllowedOrigins]); const activeBrowsers = new Map(); const webSockets = new WebSocketServer({ @@ -376,7 +403,7 @@ export async function startTailnetGateway(input) { rejectUpgrade(socket, "404 Not Found", "Not found"); return; } - if (request.headers.origin !== options.allowedOrigin) { + if (!allowedSocketOrigins.has(request.headers.origin)) { rejectUpgrade(socket, "403 Forbidden", "Origin not allowed"); return; } @@ -444,6 +471,10 @@ export function optionsFromEnvironment(environment = process.env) { listenHost: environment.T4_GATEWAY_HOST ?? "127.0.0.1", listenPort: port, allowedOrigin: environment.T4_ALLOWED_ORIGIN, + nativeAllowedOrigins: + environment.T4_NATIVE_ALLOWED_ORIGINS === undefined + ? undefined + : environment.T4_NATIVE_ALLOWED_ORIGINS.split(","), label: environment.T4_HOST_LABEL ?? "OMP on this Tailnet host", }; } diff --git a/scripts/tailnet-gateway.test.mjs b/scripts/tailnet-gateway.test.mjs index fae125f9..b43afa52 100644 --- a/scripts/tailnet-gateway.test.mjs +++ b/scripts/tailnet-gateway.test.mjs @@ -8,9 +8,12 @@ import { test } from "node:test"; import WebSocket, { WebSocketServer } from "ws"; import { + CAPACITOR_NATIVE_ORIGINS, cacheControlForStaticPath, injectBackendConfig, normalizeAllowedOrigin, + normalizeNativeAllowedOrigins, + optionsFromEnvironment, resolveAppSocket, safeStaticPath, startTailnetGateway, @@ -94,6 +97,33 @@ test("origin validation accepts only explicit Tailscale HTTPS origins", () => { } }); +test("native origin validation is pinned to Capacitor's Android and iOS defaults", () => { + assert.deepEqual(normalizeNativeAllowedOrigins(), ["https://localhost", "capacitor://localhost"]); + assert.deepEqual(normalizeNativeAllowedOrigins(["capacitor://localhost", "https://localhost"]), [ + ...CAPACITOR_NATIVE_ORIGINS, + ]); + for (const value of [ + ["*"], + ["null"], + ["http://localhost", "capacitor://localhost"], + ["https://localhost", "capacitor://localhost", "https://mobile.example.com"], + ]) { + assert.throws(() => normalizeNativeAllowedOrigins(value), /must contain exactly/u); + } +}); + +test("gateway environment parses the explicit comma-separated native origin set", () => { + const options = optionsFromEnvironment({ + T4_ALLOWED_ORIGIN: ALLOWED_ORIGIN, + T4_NATIVE_ALLOWED_ORIGINS: "https://localhost,capacitor://localhost", + XDG_RUNTIME_DIR: "/run/user/1000", + }); + assert.deepEqual(normalizeNativeAllowedOrigins(options.nativeAllowedOrigins), [ + "https://localhost", + "capacitor://localhost", + ]); +}); + test("backend injection is explicit, credential-free, and script-safe", () => { const source = ""; const injected = injectBackendConfig(source, { @@ -139,6 +169,12 @@ test("gateway serves configured app and reports real upstream health", async () assert.equal(indexResponse.status, 200); assert.match(await indexResponse.text(), /id="t4-backend"/u); assert.equal(indexResponse.headers.get("x-frame-options"), "DENY"); + const contentSecurityPolicy = indexResponse.headers.get("content-security-policy"); + assert.match( + contentSecurityPolicy ?? "", + /connect-src 'self' wss:\/\/host\.example-tailnet\.ts\.net:8445/u, + ); + assert.doesNotMatch(contentSecurityPolicy ?? "", /\*/u); const assetResponse = await fetch(`${running.url}/app.js`); assert.equal(assetResponse.status, 200); @@ -175,26 +211,32 @@ test("gateway serves configured app and reports real upstream health", async () } }); -test("gateway rejects cross-origin sockets and bridges the allowed browser to Unix", async () => { +test("gateway rejects cross-origin sockets and bridges only the web and native allowlist", async () => { const running = await fixture(); try { - const denied = new WebSocket(`${running.url.replace("http", "ws")}/v1/ws`, { - headers: { Origin: "https://attacker.example-tailnet.ts.net" }, - }); - const deniedStatus = await new Promise((resolvePromise, reject) => { - denied.once("unexpected-response", (_request, response) => resolvePromise(response.statusCode)); - denied.once("open", () => reject(new Error("cross-origin socket opened"))); - denied.once("error", () => {}); - }); - assert.equal(deniedStatus, 403); + for (const origin of ["https://attacker.example-tailnet.ts.net", "null", "*"]) { + const denied = new WebSocket(`${running.url.replace("http", "ws")}/v1/ws`, { + headers: { Origin: origin }, + }); + const deniedStatus = await new Promise((resolvePromise, reject) => { + denied.once("unexpected-response", (_request, response) => + resolvePromise(response.statusCode), + ); + denied.once("open", () => reject(new Error(`socket opened for denied origin ${origin}`))); + denied.once("error", () => {}); + }); + assert.equal(deniedStatus, 403); + } - const allowed = new WebSocket(`${running.url.replace("http", "ws")}/v1/ws`, { - headers: { Origin: ALLOWED_ORIGIN }, - }); - await websocketOpen(allowed); - allowed.send("hello"); - assert.equal(await websocketMessage(allowed), "upstream:hello"); - allowed.close(); + for (const origin of [ALLOWED_ORIGIN, ...CAPACITOR_NATIVE_ORIGINS]) { + const allowed = new WebSocket(`${running.url.replace("http", "ws")}/v1/ws`, { + headers: { Origin: origin }, + }); + await websocketOpen(allowed); + allowed.send(`hello:${origin}`); + assert.equal(await websocketMessage(allowed), `upstream:hello:${origin}`); + allowed.close(); + } } finally { await running.close(); } diff --git a/scripts/tailnet-service.mjs b/scripts/tailnet-service.mjs index 0668e494..6f5f854c 100644 --- a/scripts/tailnet-service.mjs +++ b/scripts/tailnet-service.mjs @@ -13,6 +13,7 @@ export const DEFAULT_GATEWAY_PORT = 4_194; const CONFIG_VERSION = 1; const MAX_OUTPUT_BYTES = 64 * 1024; const MAX_TEXT = 4_096; +const CAPACITOR_NATIVE_ORIGINS = Object.freeze(["https://localhost", "capacitor://localhost"]); function fail(message) { throw new Error(message); @@ -76,6 +77,19 @@ function normalizeServiceOrigin(value) { return url.origin; } +function normalizeServiceNativeOrigins(value = CAPACITOR_NATIVE_ORIGINS) { + if (!Array.isArray(value)) fail("native allowed origins must be an array"); + const origins = value.map((origin) => cleanText(origin, "native allowed origin", 128)); + const unique = new Set(origins); + if ( + unique.size !== CAPACITOR_NATIVE_ORIGINS.length || + !CAPACITOR_NATIVE_ORIGINS.every((origin) => unique.has(origin)) + ) { + fail(`native allowed origins must contain exactly ${CAPACITOR_NATIVE_ORIGINS.join(", ")}`); + } + return [...CAPACITOR_NATIVE_ORIGINS]; +} + export function servicePaths({ platform = process.platform, homeDirectory = homedir(), @@ -115,6 +129,7 @@ export function validateServiceConfig(input) { webRoot: absolutePath(input.webRoot ?? join(sourceRoot, "apps", "web", "dist"), "web root"), appSocket: absolutePath(input.appSocket, "OMP appserver socket"), allowedOrigin: normalizeServiceOrigin(input.allowedOrigin), + nativeAllowedOrigins: normalizeServiceNativeOrigins(input.nativeAllowedOrigins), port: gatewayPort(input.port ?? DEFAULT_GATEWAY_PORT), label: cleanText(input.label ?? "OMP on this Tailnet host", "host label", 128), }; @@ -138,6 +153,7 @@ function escapeXml(value) { function gatewayEnvironment(config) { return { T4_ALLOWED_ORIGIN: config.allowedOrigin, + T4_NATIVE_ALLOWED_ORIGINS: config.nativeAllowedOrigins.join(","), T4_GATEWAY_HOST: "127.0.0.1", T4_GATEWAY_PORT: String(config.port), T4_WEB_ROOT: config.webRoot, @@ -526,6 +542,7 @@ async function inspect(paths) { console.log(`health: ${healthResult.ok ? "healthy" : "unhealthy"}`); console.log(`local URL: http://127.0.0.1:${config.port}`); console.log(`allowed origin: ${config.allowedOrigin}`); + console.log(`native origins: ${config.nativeAllowedOrigins.join(", ")}`); if (supervisorResult?.stderr) console.log(`diagnostics: ${supervisorResult.stderr}`); if (definitionState !== "current" || !supervisorRunning || !healthResult.ok) { process.exitCode = 1; diff --git a/scripts/tailnet-service.test.mjs b/scripts/tailnet-service.test.mjs index 4d09385b..bbf79fb2 100644 --- a/scripts/tailnet-service.test.mjs +++ b/scripts/tailnet-service.test.mjs @@ -24,16 +24,24 @@ const CONFIG = { webRoot: "/home/alice/t4-code/apps/web/dist", appSocket: "/run/user/1000/omp/appserver.sock", allowedOrigin: "https://workstation.example-tailnet.ts.net:8445", + nativeAllowedOrigins: ["https://localhost", "capacitor://localhost"], port: DEFAULT_GATEWAY_PORT, label: "Alice's workstation", }; test("service config requires an exact Tailnet HTTPS origin and absolute local paths", () => { assert.deepEqual(validateServiceConfig(CONFIG), CONFIG); + const legacyConfig = { ...CONFIG }; + delete legacyConfig.nativeAllowedOrigins; + assert.deepEqual(validateServiceConfig(legacyConfig), CONFIG); assert.throws( () => validateServiceConfig({ ...CONFIG, allowedOrigin: "https://public.example.com" }), /Tailscale HTTPS origin/u, ); + assert.throws( + () => validateServiceConfig({ ...CONFIG, nativeAllowedOrigins: ["*"] }), + /must contain exactly/u, + ); assert.throws( () => validateServiceConfig({ ...CONFIG, webRoot: "apps/web/dist" }), /absolute path/u, @@ -53,6 +61,10 @@ test("Linux paths are user-scoped and the systemd unit is shell-free and loopbac const unit = renderSystemdUnit({ ...CONFIG, label: 'host "$(touch /tmp/nope)" 100%' }); assert.match(unit, /ExecStart="\/opt\/node\/bin\/node" "\/home\/alice\/t4-code\/scripts\/tailnet-gateway\.mjs"/u); assert.match(unit, /Environment="T4_GATEWAY_HOST=127\.0\.0\.1"/u); + assert.match( + unit, + /Environment="T4_NATIVE_ALLOWED_ORIGINS=https:\/\/localhost,capacitor:\/\/localhost"/u, + ); assert.match(unit, /Environment="T4_HOST_LABEL=host \\"\\\$\(touch \/tmp\/nope\)\\" 100%%"/u); assert.match(unit, /NoNewPrivileges=true/u); assert.match(unit, /ProtectHome=read-only/u); @@ -98,6 +110,10 @@ test("macOS paths and launch agent preserve argv and environment as XML data", ( assert.match(plist, /ProgramArguments<\/key>/u); assert.match(plist, /\/opt\/node\/bin\/node<\/string>/u); assert.match(plist, /T4_GATEWAY_HOST<\/key>\s+127\.0\.0\.1<\/string>/u); + assert.match( + plist, + /T4_NATIVE_ALLOWED_ORIGINS<\/key>\s+https:\/\/localhost,capacitor:\/\/localhost<\/string>/u, + ); assert.match(plist, /A&B <host><\/string>/u); assert.match(plist, /Umask<\/key>63<\/integer>/u); }); diff --git a/scripts/wait-for-release-assets.test.mjs b/scripts/wait-for-release-assets.test.mjs index 0bf9f647..b0c936da 100644 --- a/scripts/wait-for-release-assets.test.mjs +++ b/scripts/wait-for-release-assets.test.mjs @@ -5,14 +5,15 @@ import { releaseAssetUrls, waitForReleaseAssets } from "./wait-for-release-asset const quiet = { log() {} }; -test("builds the four package URLs and checksum URL for one release tag", () => { +test("builds the five package URLs and checksum URL for one release tag", () => { assert.deepEqual( - releaseAssetUrls("0.1.8").map(({ filename }) => filename), + releaseAssetUrls("0.1.9").map(({ filename }) => filename), [ - "T4-Code-0.1.8-linux-amd64.deb", - "T4-Code-0.1.8-linux-x86_64.AppImage", - "T4-Code-0.1.8-mac-arm64.dmg", - "T4-Code-0.1.8-mac-arm64.zip", + "T4-Code-0.1.9-android.apk", + "T4-Code-0.1.9-linux-amd64.deb", + "T4-Code-0.1.9-linux-x86_64.AppImage", + "T4-Code-0.1.9-mac-arm64.dmg", + "T4-Code-0.1.9-mac-arm64.zip", "SHA256SUMS.txt", ], ); @@ -20,19 +21,19 @@ test("builds the four package URLs and checksum URL for one release tag", () => test("passes only when every public release file returns HTTP 200", async () => { const result = await waitForReleaseAssets({ - version: "0.1.8", + version: "0.1.9", fetchImpl: async () => ({ status: 200 }), logger: quiet, }); assert.equal(result.attempts, 1); - assert.equal(result.assets.length, 5); + assert.equal(result.assets.length, 6); }); test("retries unavailable files and stays inside the configured timeout", async () => { let currentTime = 0; let calls = 0; const result = await waitForReleaseAssets({ - version: "0.1.8", + version: "0.1.9", timeoutMs: 100, intervalMs: 20, requestTimeoutMs: 5, @@ -49,7 +50,7 @@ test("fails closed at the timeout and reports filenames without response content let currentTime = 0; await assert.rejects( waitForReleaseAssets({ - version: "0.1.8", + version: "0.1.9", timeoutMs: 40, intervalMs: 25, requestTimeoutMs: 5,