Commit Graph
12 Commits
Author SHA1 Message Date
silverwind 7308c5fb97 enhance(websocket): improve client synchronization (#38847)
Keep notification counts and stopwatches in sync when tabs open or
reconnect, closing the gap behind [this
flake](https://github.com/go-gitea/gitea/actions/runs/31312525323/job/93242429394).

1. Send current notification counts and stopwatch state on each
WebSocket connection, replacing the separate state fetches tabs made on
every connect.
2. Tabs joining an already open connection fetch current state once,
since its snapshot went out before they attached.
3. Send events queued during the handshake before the snapshot, so older
events can't roll it back.
4. Wait for the Redis SUBSCRIBE acknowledgement, so the snapshot read
after subscribing can't miss an event.
2026-10-29 05:18:17 +00:00
13d0f24423 feat: Replace SSE with WebSocket for UI notifications (#36965)
* Closes #36942
* Fixes #19265 

Replaces the SSE-based push channel (`/user/events`) with a WebSocket
endpoint (`/-/ws`).

### What changes

- **New `/-/ws` endpoint** (authenticated). One WebSocket per origin,
shared across tabs via a single `SharedWorker`.
- **Pubsub broker** (`services/pubsub`) for fan-out by topic, behind a
`Broker` interface. `MemoryBroker` is the default (single process); a
Redis backend is available for multi-process setups, configured via
`[websocket].PUBSUB_TYPE` / `PUBSUB_CONN_STR`. The internal Gitea queue
was not usable here because it has FIFO/single-consumer semantics.
- **Push-only event production.** Events are emitted by write-triggered
notifiers — `NotificationCountChange`, `PublishStopwatchesForUser`, and
the logout publisher — wired into the existing `notify.Notifier`
interface. No server-side pollers.
- **Typed pub/sub on the client.** `web_src/js/modules/worker.ts` is a
singleton transport; features subscribe per event type via
`onUserEvent('notification-count', cb)` instead of branching on
`event.data.type`.
- **Wire contract** (`UserEventType` union) is shared between the worker
and consumers via `web_src/js/types.ts`, kept in sync with
`services/websocket/events.go`.
- **Client-side periodic polling fallback** kicks in only when the
WebSocket cannot be established (e.g. proxy blocks WS, browser lacks
module-SharedWorker support).

### What's removed

- `modules/eventsource` (SSE manager, run loop, messenger).
- `/user/events` route and `tests/integration/eventsource_test.go`.
- All server-side polling for stopwatches and notification counts.

### Stopwatch multi-tab fix

The navbar stopwatch icon was previously rendered conditionally on `{{if
$activeStopwatch}}`, so tabs loaded before the timer started had no DOM
element to update. The icon and popup are now always rendered (toggled
with `tw-hidden`), and the start/stop/cancel handlers POST silently so
all open tabs reflect the change in real time.

### Deployment note

WebSocket needs the upgrade headers to pass through a reverse proxy,
e.g. for nginx:

```nginx
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
```

Without them the WebSocket cannot be established, and after 3
consecutive failed opens the shared worker signals `push-unavailable`:
the notification count and stopwatch fall back to periodic polling on
the existing `[ui.notification]` timeouts. Real-time push is lost, the
features keep working. The reverse-proxy docs need the same note (see
the `docs-update-needed` label).

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Epid <rexmrj@gmail.com>
2026-07-27 07:46:00 +00:00
silverwindandwxiaoguang 804b9bf120 chore: upgrade eslint plugins, remove eslint-plugin-github (#38046)
- Bump `eslint`, `typescript-eslint` and `eslint-plugin-unicorn` (to
v68), and configure the rules added in unicorn v66/v67/v68.
- Remove `eslint-plugin-github` and its workarounds (rules, type stub,
pnpm peer override, in-code `eslint-disable` comments); the rules worth
keeping are covered by `unicorn` equivalents.
- Apply the resulting fixes and autofixes across the JS codebase.

_Prepared with Claude (Opus 4.8)._

---------

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-06-21 08:07:13 +02:00
wxiaoguangandsilverwind 2644bb8490 Remove htmx (#37224)
Close #35059

Slightly improved the "fetch action" framework and started adding tests for it.

---------

Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: silverwind <me@silverwind.io>
2026-04-15 17:26:26 +00:00
de478c4b6f Add e2e tests for server push events (#36879)
Add e2e tests for the three server push features:
- **Notification count**: verifies badge appears when another user
creates an issue
- **Stopwatch**: verifies stopwatch element is rendered when a stopwatch
is active
- **Logout propagation**: verifies logout in one tab triggers redirect
in another

Tests are transport-agnostic in preparation for a future WebSocket
migration.

---------

Co-authored-by: Claude (Opus 4.6) <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-03-27 11:49:11 +01:00
silverwind 4a9ac53862 Migrate to import.meta.env and clean up types and eslint (#36362)
`import.meta.env` is supported in both vitest and webpack [as of
recent](https://github.com/webpack/webpack/pull/19996), so replace all
previous use of `process.env` with it. Current usage is limited to test
files, I've also verified it works in actual frontend code.

`webpack/module` is added to typescript types which includes the
definition for `import.meta.env`. I've also made the eslint globals more
precise. Finally, `__webpack_public_path__` is removed from our type
definitions because `webpack/module` also provides it.
2026-01-15 11:01:23 +00:00
46d7adefe0 Enable TypeScript strictNullChecks (#35843)
A big step towards enabling strict mode in Typescript.

There was definitely a good share of potential bugs while refactoring
this. When in doubt, I opted to keep the potentially broken behaviour.
Notably, the `DOMEvent` type is gone, it was broken and we're better of
with type assertions on `e.target`.

---------

Signed-off-by: silverwind <me@silverwind.io>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-12-03 02:13:16 +00:00
ea809a5220 Partially refresh notifications list (#35010)
This PR prevents full reloads for the notifications list when changing a
notifications status (read, unread, pinned).

---------

Co-authored-by: Anton Bracke <anton.bracke@fastleansmart.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-07-10 04:15:14 +00:00
wxiaoguang ef736b7e27 Refactor legacy JS (#33115) 2025-01-06 17:38:42 +08:00
silverwind c0e80dbe26 Enable Typescript strictFunctionTypes (#32911)
1. Enable
[strictFunctionTypes](https://www.typescriptlang.org/tsconfig/#strictFunctionTypes)
2. Introduce `DOMEvent` helper type which sets `e.target`. Surely not
totally correct with that `Partial` but seems to work.
3. Various type-related refactors, change objects in
`eventsource.sharedworker.ts` to `Map`.
2024-12-22 02:59:25 +08:00
silverwindandwxiaoguang c8ea41b049 Fix remaining typescript issues, enable tsc (#32840)
Fixes 79 typescript errors. Discovered at least two bugs in
`notifications.ts`, and I'm pretty sure this feature was at least
partially broken and may still be, I don't really know how to test it.

After this, only like ~10 typescript errors remain in the codebase but
those are harder to solve.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-16 05:02:32 +08:00
silverwind 5791a73e75 Convert frontend code to typescript (#31559)
None of the frontend js/ts files was touched besides these two commands
(edit: no longer true, I touched one file in
https://github.com/go-gitea/gitea/pull/31559/commits/61105d0618e285d97e95044bfb64415f364a4526
because of a deprecation that was not showing before the rename).

`tsc` currently reports 778 errors, so I have disabled it in CI as
planned.

Everything appears to work fine.
2024-07-07 15:32:30 +00:00