Compare commits

...

34 Commits

Author SHA1 Message Date
spinline
f2ca741188 Fix cache issue by updating service worker strategy and remove GitHub workflows
All checks were successful
Build MIPS Binary / build (push) Successful in 3m44s
2026-02-07 13:41:00 +03:00
spinline
767077195a chore: trigger ci build to use runner v3 (with file command)
All checks were successful
Build MIPS Binary / build (push) Successful in 4m16s
2026-02-07 01:26:33 +03:00
spinline
5539dc2289 chore: trigger ci build to use runner v2
Some checks failed
Build MIPS Binary / build (push) Failing after 4m17s
2026-02-07 00:50:13 +03:00
spinline
f99fc4a134 fix: use pure MIPS asm for __atomic_is_lock_free, remove unsupported --defsym
Some checks are pending
Build MIPS Binary / build (push) Waiting to run
2026-02-07 00:40:39 +03:00
spinline
f4d0351c5b chore: update runner shim and linker flags for mips atomics
Some checks failed
Build MIPS Binary / build (push) Failing after 3m52s
2026-02-07 00:19:36 +03:00
spinline
c2492b2749 fix(ci): let zig handle CRT object files via link-self-contained=no
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
2026-02-07 00:04:48 +03:00
spinline
18001ed5a2 fix(ci): use build-std for mips target in Dockerfile
Some checks failed
Build MIPS Binary / build (push) Failing after 4m8s
2026-02-06 23:58:19 +03:00
spinline
47da0fca55 ci: test updated runner environment
Some checks failed
Build MIPS Binary / build (push) Failing after 1m2s
2026-02-06 23:48:56 +03:00
spinline
0985f328e2 fix(ci): inject libatomic for mips and force static linking
Some checks failed
Build MIPS Binary / build (push) Failing after 1m2s
2026-02-06 23:41:30 +03:00
spinline
146b312b4c fix(runner): use reliable URL for act_runner and optimize targets
Some checks failed
Build MIPS Binary / build (push) Failing after 4m4s
2026-02-06 23:32:58 +03:00
spinline
153568e81d ci: trigger first super-fast build
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
2026-02-06 23:19:37 +03:00
spinline
fefe86da31 refactor: simplify workflow to use new custom runner with pre-installed tools
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
2026-02-06 22:34:40 +03:00
spinline
11ba548297 chore: remove failing publish workflow due to missing docker in runner
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
2026-02-06 22:31:50 +03:00
spinline
ce9fb6781a ci: configure runner to use host mode with mips-builder label
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
Publish Runner Image / Push Docker image to Docker Hub (push) Failing after 3m18s
2026-02-06 22:22:03 +03:00
spinline
4855b193d4 ci: add workflow to publish runner image to docker hub
Some checks failed
Publish Runner Image / Push Docker image to Docker Hub (push) Has been cancelled
Build MIPS Binary / build (push) Has been cancelled
2026-02-06 22:18:01 +03:00
spinline
1d636d63fa feat: add self-contained gitea runner dockerfile with all build tools included
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
2026-02-06 22:16:04 +03:00
spinline
db4edff957 ci: fix trunk binary arch download logic for arm64 runner
Some checks failed
Build MIPS Binary / build (push) Failing after 8m10s
2026-02-06 22:04:15 +03:00
spinline
921cba2cab ci: remove broken cache and use prebuilt trunk binary for speed
Some checks failed
Build MIPS Binary / build (push) Failing after 1m6s
2026-02-06 22:02:18 +03:00
spinline
c64c95233f ci: add action/cache to speed up builds
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
2026-02-06 21:49:09 +03:00
spinline
d17bfc88ad ci: replace docker build with cargo-zigbuild for mips cross-compilation
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
2026-02-06 21:45:35 +03:00
spinline
b646d0851c ci: remove --locked from trunk install to avoid yanked crate warnings
Some checks failed
Build MIPS Binary / build (push) Failing after 10m36s
2026-02-06 21:33:40 +03:00
spinline
42e03bd2e3 ci: manually install node 20 to path to fix structuredClone error
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
2026-02-06 21:31:23 +03:00
spinline
67a1d96b26 ci: debug tailwind build failure by running explicitly
Some checks failed
Build MIPS Binary / build (push) Failing after 9m30s
2026-02-06 21:20:59 +03:00
spinline
3187ed76b0 ci: install wasm-bindgen-cli from source to fix glibc error
Some checks failed
Build MIPS Binary / build (push) Failing after 10m54s
2026-02-06 21:05:50 +03:00
spinline
4dfce1096e ci: set nightly default and install Node 20 for trunk
Some checks failed
Build MIPS Binary / build (push) Failing after 9m9s
2026-02-06 20:53:41 +03:00
spinline
e3cfc11b65 ci: source cargo env in trunk/frontend steps
Some checks failed
Build MIPS Binary / build (push) Failing after 8m12s
2026-02-06 20:42:22 +03:00
spinline
f579431098 ci: install rustup in runner container if missing
Some checks failed
Build MIPS Binary / build (push) Failing after 1m2s
2026-02-06 20:41:05 +03:00
spinline
6014ec64e8 ci: replace checkout action with direct git fetch
Some checks failed
Build MIPS Binary / build (push) Failing after 1s
2026-02-06 20:39:46 +03:00
spinline
bdb30f33d8 ci: trigger server runner
Some checks failed
Build MIPS Binary / build (push) Failing after 34s
2026-02-06 20:33:16 +03:00
spinline
2ea2894664 ci: test build speed with cached image
All checks were successful
Build MIPS Binary / build (push) Successful in 4m47s
2026-02-06 19:22:37 +03:00
spinline
e329275956 ci: use cross-rs image with Rust for proper sysroot, cache builder image
All checks were successful
Build MIPS Binary / build (push) Successful in 7m39s
2026-02-06 19:10:57 +03:00
spinline
1f7af5fe30 ci: remove RUSTFLAGS, musl target is static by default
All checks were successful
Build MIPS Binary / build (push) Successful in 13m49s
2026-02-06 18:51:49 +03:00
spinline
bf54d2699e ci: force static linking with crt-static and -static flags
Some checks failed
Build MIPS Binary / build (push) Failing after 10m7s
2026-02-06 18:39:40 +03:00
spinline
46d61b5562 fix: suppress unused_assignments warning for got_first_message
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
2026-02-06 18:33:18 +03:00
6 changed files with 279 additions and 232 deletions

View File

@@ -9,52 +9,37 @@ env:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: mips-builder
steps: steps:
- uses: actions/checkout@v4 - name: Checkout
env:
- name: Setup Rust GIT_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: | run: |
rustup toolchain install nightly --profile minimal rm -rf .git
rustup target add wasm32-unknown-unknown --toolchain nightly git init .
rustup component add rust-src --toolchain nightly git remote add origin https://admin:$\{GIT_TOKEN\}@git.karatatar.com/admin/vibetorrent.git
rustc +nightly --version git fetch --depth=1 origin ${{ gitea.sha }}
git checkout FETCH_HEAD
- name: Install Trunk
run: |
if ! command -v trunk &> /dev/null; then
cargo install trunk --locked
fi
- name: Build Frontend - name: Build Frontend
run: | run: |
cd frontend cd frontend
npm install npm install
# Run Tailwind manually first
npx @tailwindcss/cli -i input.css -o public/tailwind.css
trunk build --release trunk build --release
- name: Install Cross
run: |
if ! command -v cross &> /dev/null; then
cargo install cross --locked
fi
- name: Build Backend (MIPS) - name: Build Backend (MIPS)
env:
# Ensure we are building a fully static binary
# -C link-self-contained=no: Let Zig (the linker) handle CRT objects (crt1.o, etc.)
RUSTFLAGS: "-C target-feature=+crt-static -C link-self-contained=no -C link-arg=-msoft-float"
CFLAGS_mips_unknown_linux_musl: "-msoft-float"
run: | run: |
docker run --rm --platform linux/amd64 \ cd backend
-v "$PWD":/project \ cargo zigbuild --target mips-unknown-linux-musl --release -Z build-std=std,panic_abort
-v cargo-cache:/usr/local/cargo/registry \ file target/mips-unknown-linux-musl/release/backend
-w /project \
rust:latest \
bash -c '
rustup toolchain install nightly --component rust-src &&
apt-get update -qq && apt-get install -y -qq musl-tools wget >/dev/null 2>&1 &&
wget -qO- https://musl.cc/mips-linux-musl-cross.tgz | tar xz -C /opt/ &&
export PATH="/opt/mips-linux-musl-cross/bin:$PATH" &&
export CARGO_TARGET_MIPS_UNKNOWN_LINUX_MUSL_LINKER=mips-linux-musl-gcc &&
cd backend &&
cargo +nightly build --target mips-unknown-linux-musl --release -Z build-std=std,panic_abort
'
- name: Rename Binary - name: Rename Binary
run: mv target/mips-unknown-linux-musl/release/backend target/mips-unknown-linux-musl/release/vibetorrent-mips run: mv target/mips-unknown-linux-musl/release/backend target/mips-unknown-linux-musl/release/vibetorrent-mips
@@ -72,10 +57,7 @@ jobs:
API_URL="${{ gitea.server_url }}/api/v1" API_URL="${{ gitea.server_url }}/api/v1"
# Create release # Create release
RELEASE_RESPONSE=$(curl -s -X POST "${API_URL}/repos/${REPO}/releases" \ RELEASE_RESPONSE=$(curl -s -X POST "${API_URL}/repos/${REPO}/releases" -H "Authorization: token ${RELEASE_TOKEN}" -H "Content-Type: application/json" -d "{
-H "Authorization: token ${RELEASE_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"tag_name\": \"${TAG}\", \"tag_name\": \"${TAG}\",
\"name\": \"Release ${TAG}\", \"name\": \"Release ${TAG}\",
\"body\": \"Automated build from commit ${{ gitea.sha }}\", \"body\": \"Automated build from commit ${{ gitea.sha }}\",
@@ -93,9 +75,6 @@ jobs:
fi fi
# Upload binary as release asset # Upload binary as release asset
curl -s -X POST "${API_URL}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=vibetorrent-mips" \ curl -s -X POST "${API_URL}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=vibetorrent-mips" -H "Authorization: token ${RELEASE_TOKEN}" -H "Content-Type: application/octet-stream" --data-binary @target/mips-unknown-linux-musl/release/vibetorrent-mips
-H "Authorization: token ${RELEASE_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary @target/mips-unknown-linux-musl/release/vibetorrent-mips
echo "Release ${TAG} created with binary attached." echo "Release ${TAG} created with binary attached."

View File

@@ -1,80 +0,0 @@
name: Build MIPS Binary
on:
push:
branches: [ "main" ]
workflow_dispatch:
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Debug - List Files (Pre-Build)
run: ls -R
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Rust
uses: dtolnay/rust-toolchain@nightly
with:
targets: wasm32-unknown-unknown
components: rust-src
- name: Rust Cache
uses: Swatinem/rust-cache@v2
- name: Install Trunk
uses: jetli/trunk-action@v0.5.0
with:
version: 'latest'
- name: Build Frontend
run: |
cd frontend
npm install
trunk build --release
- name: Install Cross
run: cargo install cross
- name: Build Backend (MIPS)
env:
RUSTUP_TOOLCHAIN: nightly
CROSS_NO_WARNINGS: 0
run: |
cd backend
cross build --target mips-unknown-linux-musl --release -Z build-std=std,panic_abort
- name: Debug - List Files
run: |
echo "Listing target directory..."
find target -maxdepth 5 || true
ls -R target/mips-unknown-linux-musl/release || true
- name: Rename Binary
run: mv target/mips-unknown-linux-musl/release/backend target/mips-unknown-linux-musl/release/vibetorrent-mips
- name: Generate Tag
id: tag
run: echo "release_tag=release-$(date +'%Y%m%d-%H%M')" >> $GITHUB_OUTPUT
- name: Create Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.tag.outputs.release_tag }}
name: Release ${{ steps.tag.outputs.release_tag }}
files: target/mips-unknown-linux-musl/release/vibetorrent-mips
draft: false
prerelease: false

94
docker/runner/Dockerfile Normal file
View File

@@ -0,0 +1,94 @@
# Use a base image that supports multi-arch (x64 and arm64)
# We use debian:bookworm-slim as base to install everything manually
# and then install the act_runner binary.
FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive
ENV PATH="/root/.cargo/bin:/root/.node/bin:/root/zig:${PATH}"
# 1. Install Basic Dependencies
RUN apt-get update && apt-get install -y \
curl \
git \
build-essential \
ca-certificates \
wget \
xz-utils \
libssl-dev \
pkg-config \
file \
jq \
# Needed for some crate compilations
protobuf-compiler \
&& rm -rf /var/lib/apt/lists/*
# 2. Install Node.js v20 (Manual install to support multi-arch cleanly)
RUN ARCH=$(dpkg --print-architecture) && \
if [ "$ARCH" = "amd64" ]; then NODE_ARCH="x64"; \
elif [ "$ARCH" = "arm64" ]; then NODE_ARCH="arm64"; fi && \
NODE_VER="v20.11.1" && \
curl -fsSL "https://nodejs.org/dist/$NODE_VER/node-$NODE_VER-linux-$NODE_ARCH.tar.xz" -o node.tar.xz && \
mkdir -p /root/.node && \
tar -xJf node.tar.xz -C /root/.node --strip-components=1 && \
rm node.tar.xz
# 3. Install Rust (Nightly) + Targets
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain nightly --profile minimal && \
. "$HOME/.cargo/env" && \
rustup target add wasm32-unknown-unknown && \
rustup component add rust-src
# 4. Install Zig (for Cross Compilation)
RUN ARCH=$(dpkg --print-architecture) && \
if [ "$ARCH" = "amd64" ]; then ZIG_ARCH="x86_64"; \
elif [ "$ARCH" = "arm64" ]; then ZIG_ARCH="aarch64"; fi && \
ZIG_VER="0.13.0" && \
curl -fsSL "https://ziglang.org/download/$ZIG_VER/zig-linux-$ZIG_ARCH-$ZIG_VER.tar.xz" -o zig.tar.xz && \
tar -xf zig.tar.xz && \
mv "zig-linux-$ZIG_ARCH-$ZIG_VER" /root/zig && \
rm zig.tar.xz
# 5. Fix: Create libatomic.a with __atomic_is_lock_free for MIPS
# MIPS musl static build misses __atomic_is_lock_free.
# We provide it via pure assembly to avoid Clang builtin conflicts.
RUN . "$HOME/.cargo/env" && \
printf '.text\n.globl __atomic_is_lock_free\n.type __atomic_is_lock_free, @function\n__atomic_is_lock_free:\n sltiu $v0, $a0, 5\n jr $ra\n nop\n.size __atomic_is_lock_free, .-__atomic_is_lock_free\n' > atomic.s && \
/root/zig/zig cc -target mips-linux-musl -msoft-float -c -o atomic.o atomic.s && \
/root/zig/zig ar rcs libatomic.a atomic.o && \
RUST_SYSROOT=$(rustc --print sysroot) && \
TARGET_LIB_DIR="$RUST_SYSROOT/lib/rustlib/mips-unknown-linux-musl/lib" && \
mkdir -p "$TARGET_LIB_DIR" && \
cp libatomic.a "$TARGET_LIB_DIR/" && \
rm atomic.s atomic.o libatomic.a
# 6. Install Tools (Trunk, cargo-zigbuild, wasm-bindgen protocol aligned)
# We install trunk binary to save time, others via cargo
RUN . "$HOME/.cargo/env" && \
# Install cargo-zigbuild
cargo install cargo-zigbuild && \
# Install trunk (Binary)
ARCH=$(dpkg --print-architecture) && \
if [ "$ARCH" = "amd64" ]; then TRUNK_ARCH="x86_64-unknown-linux-gnu"; \
elif [ "$ARCH" = "arm64" ]; then TRUNK_ARCH="aarch64-unknown-linux-gnu"; fi && \
wget -qO- "https://github.com/trunk-rs/trunk/releases/download/v0.21.5/trunk-$TRUNK_ARCH.tar.gz" | tar -xzf - -C /root/.cargo/bin/ && \
chmod +x /root/.cargo/bin/trunk && \
# Install wasm-bindgen-cli (Compiling from source to avoid glibc issues, doing it ONCE here)
cargo install wasm-bindgen-cli --version 0.2.108
# 7. Install Gitea Act Runner Binary
# We fetch the binary directly to run as the entrypoint
RUN ARCH=$(dpkg --print-architecture) && \
VERSION="0.2.11" && \
curl -fsSL -o /usr/local/bin/act_runner "https://dl.gitea.com/act_runner/$VERSION/act_runner-$VERSION-linux-$ARCH" && \
chmod +x /usr/local/bin/act_runner
# Create a volume for registration data
VOLUME /data
WORKDIR /data
# Define entrypoint to run the registration or daemon
# We will use a script to handle auto-registration if env vars are present
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -0,0 +1,23 @@
#!/bin/bash
set -e
# If GITEA_INSTANCE_URL and GITEA_RUNNER_TOKEN are provided, try to register
if [ -n "$GITEA_INSTANCE_URL" ] && [ -n "$GITEA_RUNNER_TOKEN" ]; then
if [ ! -f ".runner" ]; then
echo "Registering runner..."
# Register with label 'mips-builder' valid for host execution
# plus 'ubuntu-latest' mapped to host for convenience if needed
act_runner register \
--instance "$GITEA_INSTANCE_URL" \
--token "$GITEA_RUNNER_TOKEN" \
--name "vibetorrent-mips-runner-$(hostname)" \
--labels "mips-builder:host,ubuntu-latest:host" \
--no-interactive
else
echo "Runner already registered."
fi
fi
# Run the daemon
echo "Starting act_runner daemon..."
exec act_runner daemon

View File

@@ -145,7 +145,7 @@ pub fn provide_torrent_store() {
let max_backoff_ms: u32 = 30000; // Max 30 seconds let max_backoff_ms: u32 = 30000; // Max 30 seconds
let mut was_connected = false; let mut was_connected = false;
let mut disconnect_notified = false; // Track if we already showed disconnect toast let mut disconnect_notified = false; // Track if we already showed disconnect toast
let mut got_first_message = false; // Only count as "connected" after receiving data let mut got_first_message; // Only count as "connected" after receiving data
loop { loop {
let es_result = EventSource::new("/api/events"); let es_result = EventSource::new("/api/events");

View File

@@ -1,127 +1,158 @@
const CACHE_NAME = 'vibetorrent-v1'; const CACHE_NAME = "vibetorrent-v2";
const ASSETS_TO_CACHE = [ const ASSETS_TO_CACHE = [
'/', "/",
'/index.html', "/index.html",
'/manifest.json', "/manifest.json",
'/icon-192.png', "/icon-192.png",
'/icon-512.png' "/icon-512.png",
]; ];
// Install event - cache assets // Install event - cache assets
self.addEventListener('install', (event) => { self.addEventListener("install", (event) => {
console.log('[Service Worker] Installing...'); console.log("[Service Worker] Installing...");
event.waitUntil( event.waitUntil(
caches.open(CACHE_NAME).then((cache) => { caches
console.log('[Service Worker] Caching static assets'); .open(CACHE_NAME)
return cache.addAll(ASSETS_TO_CACHE); .then((cache) => {
}).then(() => { console.log("[Service Worker] Caching static assets");
console.log('[Service Worker] Skip waiting'); return cache.addAll(ASSETS_TO_CACHE);
return self.skipWaiting(); })
}) .then(() => {
); console.log("[Service Worker] Skip waiting");
return self.skipWaiting();
}),
);
}); });
// Activate event - clean old caches // Activate event - clean old caches
self.addEventListener('activate', (event) => { self.addEventListener("activate", (event) => {
console.log('[Service Worker] Activating...'); console.log("[Service Worker] Activating...");
event.waitUntil( event.waitUntil(
caches.keys().then((cacheNames) => { caches
return Promise.all( .keys()
cacheNames.map((key) => { .then((cacheNames) => {
if (key !== CACHE_NAME) { return Promise.all(
console.log('[Service Worker] Deleting old cache:', key); cacheNames.map((key) => {
return caches.delete(key); if (key !== CACHE_NAME) {
} console.log("[Service Worker] Deleting old cache:", key);
}) return caches.delete(key);
); }
}).then(() => { }),
console.log('[Service Worker] Claiming clients'); );
return self.clients.claim(); })
}) .then(() => {
); console.log("[Service Worker] Claiming clients");
return self.clients.claim();
}),
);
}); });
// Fetch event - network first, cache fallback for API calls // Fetch event - network first for HTML, cache fallback for API calls
self.addEventListener('fetch', (event) => { self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url); const url = new URL(event.request.url);
// Network-first strategy for API calls // Network-first strategy for API calls
if (url.pathname.startsWith('/api/')) { if (url.pathname.startsWith("/api/")) {
event.respondWith(
fetch(event.request)
.catch(() => {
// Could return cached API response or offline page
return new Response(
JSON.stringify({ error: 'Offline' }),
{ headers: { 'Content-Type': 'application/json' } }
);
})
);
return;
}
// Cache-first strategy for static assets
event.respondWith( event.respondWith(
caches.match(event.request).then((response) => { fetch(event.request).catch(() => {
return response || fetch(event.request).then((fetchResponse) => { // Could return cached API response or offline page
// Optionally cache new requests return new Response(JSON.stringify({ error: "Offline" }), {
if (fetchResponse && fetchResponse.status === 200) { headers: { "Content-Type": "application/json" },
const responseToCache = fetchResponse.clone(); });
caches.open(CACHE_NAME).then((cache) => { }),
cache.put(event.request, responseToCache);
});
}
return fetchResponse;
});
})
); );
return;
}
// Network-first strategy for HTML pages (entry points)
// This ensures users always get the latest version of the app
if (
event.request.mode === "navigate" ||
url.pathname.endsWith("index.html") ||
url.pathname === "/"
) {
event.respondWith(
fetch(event.request)
.then((response) => {
// Cache the latest version of the HTML
const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseToCache);
});
return response;
})
.catch(() => {
return caches.match(event.request);
}),
);
return;
}
// Cache-first strategy for static assets (JS, CSS, Images)
event.respondWith(
caches.match(event.request).then((response) => {
return (
response ||
fetch(event.request).then((fetchResponse) => {
// Optionally cache new requests
if (fetchResponse && fetchResponse.status === 200) {
const responseToCache = fetchResponse.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseToCache);
});
}
return fetchResponse;
})
);
}),
);
}); });
// Notification click event - focus or open app // Notification click event - focus or open app
self.addEventListener('notificationclick', (event) => { self.addEventListener("notificationclick", (event) => {
console.log('[Service Worker] Notification clicked:', event.notification.tag); console.log("[Service Worker] Notification clicked:", event.notification.tag);
event.notification.close(); event.notification.close();
event.waitUntil( event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { clients
// If app is already open, focus it .matchAll({ type: "window", includeUncontrolled: true })
for (let client of clientList) { .then((clientList) => {
if (client.url === '/' && 'focus' in client) { // If app is already open, focus it
return client.focus(); for (let client of clientList) {
} if (client.url === "/" && "focus" in client) {
} return client.focus();
// Otherwise open new window }
if (clients.openWindow) { }
return clients.openWindow('/'); // Otherwise open new window
} if (clients.openWindow) {
}) return clients.openWindow("/");
); }
}),
);
}); });
// Push notification event // Push notification event
self.addEventListener('push', (event) => { self.addEventListener("push", (event) => {
console.log('[Service Worker] Push notification received'); console.log("[Service Worker] Push notification received");
const data = event.data ? event.data.json() : {}; const data = event.data ? event.data.json() : {};
const title = data.title || 'VibeTorrent'; const title = data.title || "VibeTorrent";
const options = { const options = {
body: data.body || 'New notification', body: data.body || "New notification",
icon: data.icon || '/icon-192.png', icon: data.icon || "/icon-192.png",
badge: data.badge || '/icon-192.png', badge: data.badge || "/icon-192.png",
tag: data.tag || 'vibetorrent-notification', tag: data.tag || "vibetorrent-notification",
requireInteraction: false, requireInteraction: false,
// iOS-specific: vibrate pattern (if supported) // iOS-specific: vibrate pattern (if supported)
vibrate: [200, 100, 200], vibrate: [200, 100, 200],
// Add data for notification click handling // Add data for notification click handling
data: { data: {
url: data.url || '/', url: data.url || "/",
timestamp: Date.now() timestamp: Date.now(),
} },
}; };
console.log('[Service Worker] Showing notification:', title, options); console.log("[Service Worker] Showing notification:", title, options);
event.waitUntil( event.waitUntil(self.registration.showNotification(title, options));
self.registration.showNotification(title, options)
);
}); });