diff --git a/cli/server.go b/cli/server.go index 6acbed6b80..874212f739 100644 --- a/cli/server.go +++ b/cli/server.go @@ -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 { diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index a96b81e229..dc1fb32c27 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -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", diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index a1dcb82c3d..1a98288345 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -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", diff --git a/coderd/coderd.go b/coderd/coderd.go index 1c47410aee..834eaf6fb5 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -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) diff --git a/coderd/webpush/webpush.go b/coderd/webpush/webpush.go index 94f7d8da24..ee7ad47075 100644 --- a/coderd/webpush/webpush.go +++ b/coderd/webpush/webpush.go @@ -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 } diff --git a/coderd/webpush/webpush_test.go b/coderd/webpush/webpush_test.go index fdd394b286..1da6fcdd54 100644 --- a/coderd/webpush/webpush_test.go +++ b/coderd/webpush/webpush_test.go @@ -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()) +} diff --git a/codersdk/deployment.go b/codersdk/deployment.go index b2c05a700a..aff273f041 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -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, diff --git a/docs/ai-coder/agents/getting-started.md b/docs/ai-coder/agents/getting-started.md index 78a543f1d4..dff1435e5c 100644 --- a/docs/ai-coder/agents/getting-started.md +++ b/docs/ai-coder/agents/getting-started.md @@ -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 diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 563c33f971..78901d2026 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -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 diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index b5e51c96be..b1dc175d4f 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -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", ]; diff --git a/site/src/contexts/useWebpushNotifications.ts b/site/src/contexts/useWebpushNotifications.ts index 888301f362..7fe62913d9 100644 --- a/site/src/contexts/useWebpushNotifications.ts +++ b/site/src/contexts/useWebpushNotifications.ts @@ -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(false); const [loading, setLoading] = useState(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