mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
feat: graduate web-push from experiment to always-on (#24310)
* Removes experiment `web-push`.
* Falls back to NoopWebpusher in case of error
* Checks browser capability in FE
* Adds note to agents getting-started docs regarding webpush without TLS
> 🤖
This commit is contained in:
+6
-19
@@ -843,27 +843,14 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
}
|
||||
|
||||
// Manage push notifications.
|
||||
experiments := coderd.ReadExperiments(options.Logger, options.DeploymentValues.Experiments.Value())
|
||||
if experiments.Enabled(codersdk.ExperimentWebPush) || buildinfo.IsDev() {
|
||||
if !strings.HasPrefix(options.AccessURL.String(), "https://") {
|
||||
options.Logger.Warn(ctx, "access URL is not HTTPS, so web push notifications may not work on some browsers", slog.F("access_url", options.AccessURL.String()))
|
||||
}
|
||||
webpusher, err := webpush.New(ctx, ptr.Ref(options.Logger.Named("webpush")), options.Database, options.AccessURL.String())
|
||||
if err != nil {
|
||||
options.Logger.Error(ctx, "failed to create web push dispatcher", slog.Error(err))
|
||||
options.Logger.Warn(ctx, "web push notifications will not work until the VAPID keys are regenerated")
|
||||
webpusher = &webpush.NoopWebpusher{
|
||||
Msg: "Web Push notifications are disabled due to a system error. Please contact your Coder administrator.",
|
||||
}
|
||||
}
|
||||
options.WebPushDispatcher = webpusher
|
||||
} else {
|
||||
options.WebPushDispatcher = &webpush.NoopWebpusher{
|
||||
// Users will likely not see this message as the endpoints return 404
|
||||
// if not enabled. Just in case...
|
||||
Msg: "Web Push notifications are an experimental feature and are disabled by default. Enable the 'web-push' experiment to use this feature.",
|
||||
webpusher, err := webpush.New(ctx, ptr.Ref(options.Logger.Named("webpush")), options.Database, options.AccessURL.String())
|
||||
if err != nil {
|
||||
options.Logger.Error(ctx, "failed to create web push dispatcher", slog.Error(err))
|
||||
webpusher = &webpush.NoopWebpusher{
|
||||
Msg: "Web Push notifications are disabled due to a system error. Please contact your Coder administrator.",
|
||||
}
|
||||
}
|
||||
options.WebPushDispatcher = webpusher
|
||||
|
||||
githubOAuth2ConfigParams, err := getGithubOAuth2ConfigParams(ctx, options.Database, vals)
|
||||
if err != nil {
|
||||
|
||||
Generated
-4
@@ -16176,7 +16176,6 @@ const docTemplate = `{
|
||||
"auto-fill-parameters",
|
||||
"notifications",
|
||||
"workspace-usage",
|
||||
"web-push",
|
||||
"oauth2",
|
||||
"agents",
|
||||
"mcp-server-http",
|
||||
@@ -16189,7 +16188,6 @@ const docTemplate = `{
|
||||
"ExperimentMCPServerHTTP": "Enables the MCP HTTP server functionality.",
|
||||
"ExperimentNotifications": "Sends notifications via SMTP and webhooks following certain events.",
|
||||
"ExperimentOAuth2": "Enables OAuth2 provider functionality.",
|
||||
"ExperimentWebPush": "Enables web push notifications through the browser.",
|
||||
"ExperimentWorkspaceBuildUpdates": "Enables publishing workspace build updates to the all builds pubsub channel.",
|
||||
"ExperimentWorkspaceUsage": "Enables the new workspace usage tracking."
|
||||
},
|
||||
@@ -16198,7 +16196,6 @@ const docTemplate = `{
|
||||
"This should not be taken out of experiments until we have redesigned the feature.",
|
||||
"Sends notifications via SMTP and webhooks following certain events.",
|
||||
"Enables the new workspace usage tracking.",
|
||||
"Enables web push notifications through the browser.",
|
||||
"Enables OAuth2 provider functionality.",
|
||||
"Enables agent-powered chat functionality.",
|
||||
"Enables the MCP HTTP server functionality.",
|
||||
@@ -16209,7 +16206,6 @@ const docTemplate = `{
|
||||
"ExperimentAutoFillParameters",
|
||||
"ExperimentNotifications",
|
||||
"ExperimentWorkspaceUsage",
|
||||
"ExperimentWebPush",
|
||||
"ExperimentOAuth2",
|
||||
"ExperimentAgents",
|
||||
"ExperimentMCPServerHTTP",
|
||||
|
||||
Generated
-4
@@ -14636,7 +14636,6 @@
|
||||
"auto-fill-parameters",
|
||||
"notifications",
|
||||
"workspace-usage",
|
||||
"web-push",
|
||||
"oauth2",
|
||||
"agents",
|
||||
"mcp-server-http",
|
||||
@@ -14649,7 +14648,6 @@
|
||||
"ExperimentMCPServerHTTP": "Enables the MCP HTTP server functionality.",
|
||||
"ExperimentNotifications": "Sends notifications via SMTP and webhooks following certain events.",
|
||||
"ExperimentOAuth2": "Enables OAuth2 provider functionality.",
|
||||
"ExperimentWebPush": "Enables web push notifications through the browser.",
|
||||
"ExperimentWorkspaceBuildUpdates": "Enables publishing workspace build updates to the all builds pubsub channel.",
|
||||
"ExperimentWorkspaceUsage": "Enables the new workspace usage tracking."
|
||||
},
|
||||
@@ -14658,7 +14656,6 @@
|
||||
"This should not be taken out of experiments until we have redesigned the feature.",
|
||||
"Sends notifications via SMTP and webhooks following certain events.",
|
||||
"Enables the new workspace usage tracking.",
|
||||
"Enables web push notifications through the browser.",
|
||||
"Enables OAuth2 provider functionality.",
|
||||
"Enables agent-powered chat functionality.",
|
||||
"Enables the MCP HTTP server functionality.",
|
||||
@@ -14669,7 +14666,6 @@
|
||||
"ExperimentAutoFillParameters",
|
||||
"ExperimentNotifications",
|
||||
"ExperimentWorkspaceUsage",
|
||||
"ExperimentWebPush",
|
||||
"ExperimentOAuth2",
|
||||
"ExperimentAgents",
|
||||
"ExperimentMCPServerHTTP",
|
||||
|
||||
@@ -1624,7 +1624,6 @@ func New(options *Options) *API {
|
||||
})
|
||||
})
|
||||
r.Route("/webpush", func(r chi.Router) {
|
||||
r.Use(httpmw.RequireExperimentWithDevBypass(api.Experiments, codersdk.ExperimentWebPush))
|
||||
r.Post("/subscription", api.postUserWebpushSubscription)
|
||||
r.Delete("/subscription", api.deleteUserWebpushSubscription)
|
||||
r.Post("/test", api.postUserPushNotificationTest)
|
||||
|
||||
@@ -386,9 +386,11 @@ func (n *Webpusher) PublicKey() string {
|
||||
return n.VAPIDPublicKey
|
||||
}
|
||||
|
||||
// NoopWebpusher is a Dispatcher that does nothing except return an error.
|
||||
// This is returned when web push notifications are disabled, or if there was an
|
||||
// error generating the VAPID keys.
|
||||
// NoopWebpusher is a Dispatcher that always fails, returning Msg as
|
||||
// the error. It is used as a fallback when VAPID key setup fails.
|
||||
// The underlying error is not included to avoid leaking internal
|
||||
// details (e.g. database errors) in API responses; it is logged at
|
||||
// the call site instead.
|
||||
type NoopWebpusher struct {
|
||||
Msg string
|
||||
}
|
||||
|
||||
@@ -405,3 +405,21 @@ func setupPushTestWithOptions(ctx context.Context, t *testing.T, db database.Sto
|
||||
|
||||
return manager, db, server.URL
|
||||
}
|
||||
|
||||
func TestNoopWebpusher(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
noop := &webpush.NoopWebpusher{
|
||||
Msg: "push disabled",
|
||||
}
|
||||
|
||||
dispatchErr := noop.Dispatch(context.Background(), uuid.New(), codersdk.WebpushMessage{})
|
||||
require.Error(t, dispatchErr)
|
||||
require.Contains(t, dispatchErr.Error(), "push disabled")
|
||||
|
||||
testErr := noop.Test(context.Background(), codersdk.WebpushSubscription{})
|
||||
require.Error(t, testErr)
|
||||
require.Contains(t, testErr.Error(), "push disabled")
|
||||
|
||||
require.Empty(t, noop.PublicKey())
|
||||
}
|
||||
|
||||
@@ -4355,7 +4355,6 @@ const (
|
||||
ExperimentAutoFillParameters Experiment = "auto-fill-parameters" // This should not be taken out of experiments until we have redesigned the feature.
|
||||
ExperimentNotifications Experiment = "notifications" // Sends notifications via SMTP and webhooks following certain events.
|
||||
ExperimentWorkspaceUsage Experiment = "workspace-usage" // Enables the new workspace usage tracking.
|
||||
ExperimentWebPush Experiment = "web-push" // Enables web push notifications through the browser.
|
||||
ExperimentOAuth2 Experiment = "oauth2" // Enables OAuth2 provider functionality.
|
||||
ExperimentAgents Experiment = "agents" // Enables agent-powered chat functionality.
|
||||
ExperimentMCPServerHTTP Experiment = "mcp-server-http" // Enables the MCP HTTP server functionality.
|
||||
@@ -4372,8 +4371,6 @@ func (e Experiment) DisplayName() string {
|
||||
return "SMTP and Webhook Notifications"
|
||||
case ExperimentWorkspaceUsage:
|
||||
return "Workspace Usage Tracking"
|
||||
case ExperimentWebPush:
|
||||
return "Browser Push Notifications"
|
||||
case ExperimentOAuth2:
|
||||
return "OAuth2 Provider Functionality"
|
||||
case ExperimentAgents:
|
||||
@@ -4384,7 +4381,7 @@ func (e Experiment) DisplayName() string {
|
||||
return "Workspace Build Updates Channel"
|
||||
default:
|
||||
// Split on hyphen and convert to title case
|
||||
// e.g. "web-push" -> "Web Push", "mcp-server-http" -> "Mcp Server Http"
|
||||
// e.g. "mcp-server-http" -> "Mcp Server Http"
|
||||
caser := cases.Title(language.English)
|
||||
return caser.String(strings.ReplaceAll(string(e), "-", " "))
|
||||
}
|
||||
@@ -4396,7 +4393,6 @@ var ExperimentsKnown = Experiments{
|
||||
ExperimentAutoFillParameters,
|
||||
ExperimentNotifications,
|
||||
ExperimentWorkspaceUsage,
|
||||
ExperimentWebPush,
|
||||
ExperimentOAuth2,
|
||||
ExperimentAgents,
|
||||
ExperimentMCPServerHTTP,
|
||||
|
||||
@@ -159,6 +159,18 @@ dedicated test or staging deployment to avoid disruption to production
|
||||
developer workflows. See [Early Access](./early-access.md) for the full
|
||||
set of expectations and limitations.
|
||||
|
||||
### Use HTTPS for push notifications
|
||||
|
||||
Coder Agents use browser push notifications to alert you when a task
|
||||
completes or needs attention. Most browsers require a secure (HTTPS)
|
||||
origin for the [Push API](https://developer.mozilla.org/en-US/docs/Web/API/Push_API)
|
||||
to work. If your access URL uses plain HTTP,
|
||||
push notifications may not function.
|
||||
|
||||
This does not affect agents themselves — only the browser notification
|
||||
delivery. If you terminate TLS at a reverse proxy, ensure the
|
||||
[access URL](../../admin/setup/index.md) is configured with an `https://` scheme.
|
||||
|
||||
### Set a deployment-wide system prompt
|
||||
|
||||
Administrators can set a system prompt that applies to all Coder Agents across the
|
||||
|
||||
Generated
+3
-3
@@ -4558,9 +4558,9 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value(s) |
|
||||
|-----------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `agents`, `auto-fill-parameters`, `example`, `mcp-server-http`, `notifications`, `oauth2`, `web-push`, `workspace-build-updates`, `workspace-usage` |
|
||||
| Value(s) |
|
||||
|-----------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `agents`, `auto-fill-parameters`, `example`, `mcp-server-http`, `notifications`, `oauth2`, `workspace-build-updates`, `workspace-usage` |
|
||||
|
||||
## codersdk.ExternalAPIKeyScopes
|
||||
|
||||
|
||||
Generated
-2
@@ -3550,7 +3550,6 @@ export type Experiment =
|
||||
| "mcp-server-http"
|
||||
| "notifications"
|
||||
| "oauth2"
|
||||
| "web-push"
|
||||
| "workspace-build-updates"
|
||||
| "workspace-usage";
|
||||
|
||||
@@ -3561,7 +3560,6 @@ export const Experiments: Experiment[] = [
|
||||
"mcp-server-http",
|
||||
"notifications",
|
||||
"oauth2",
|
||||
"web-push",
|
||||
"workspace-build-updates",
|
||||
"workspace-usage",
|
||||
];
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useEffect, useState } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { API } from "#/api/api";
|
||||
import { buildInfo } from "#/api/queries/buildInfo";
|
||||
import { experiments } from "#/api/queries/experiments";
|
||||
import { useEmbeddedMetadata } from "#/hooks/useEmbeddedMetadata";
|
||||
|
||||
interface WebpushNotifications {
|
||||
@@ -17,11 +16,12 @@ interface WebpushNotifications {
|
||||
export const useWebpushNotifications = (): WebpushNotifications => {
|
||||
const { metadata } = useEmbeddedMetadata();
|
||||
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
|
||||
const enabledExperimentsQuery = useQuery(experiments(metadata.experiments));
|
||||
|
||||
const [subscribed, setSubscribed] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const enabled = enabledExperimentsQuery.data?.includes("web-push") ?? false;
|
||||
const enabled =
|
||||
"Notification" in window &&
|
||||
"serviceWorker" in navigator &&
|
||||
!!buildInfoQuery.data?.webpush_public_key;
|
||||
|
||||
useEffect(() => {
|
||||
// Check if browser supports push notifications
|
||||
|
||||
Reference in New Issue
Block a user