Representative interview topic

Frontend interview: How should action.navigate cooperate with click fallback?

FrontendMedium
Offer.cc Editorial TeamPublished Updated

Question

Design an order notification with view, confirm, and help actions, then explain navigation priority, fallback handling, and idempotency for each action.

1. Prompt and scope

An order-status notification offers View order, Confirm delivery, and Contact support actions. The app may be closed, a window may already exist, or the session may be expired. Design the flow with Notification actions and explain how browser navigation cooperates with notificationclick.

2. What the interviewer is testing

  • Understand that body navigate and action navigate are separate URLs, with an action URL taking precedence over custom handling.
  • Know that an action without a URL reaches notificationclick, and keep async work alive with event.waitUntil.
  • Validate same-origin URLs, permissions, authentication state, and Service Worker lifetime.
  • Use tags, business idempotency keys, and client recovery to avoid duplicate confirmations or windows.

3. Questions to clarify first

  1. Can Confirm delivery be a GET navigation, or must it be a POST API with confirmation?
  2. Should an expired session open login first and resume the action afterward?
  3. May the support action target an external customer-service domain?
  4. Should an existing window be focused, messaged, or should a new window open?

4. Thirty-second answer

I would map read-only view and help actions to allowlisted same-origin URLs, while leaving the side-effecting confirmation action without navigate. The Service Worker handles that action through an idempotent API and then routes to the result page. Every URL is checked, asynchronous work is wrapped in waitUntil, and an existing controlled window is focused before opening a fallback.

5. Step-by-step deep dive

Step 1: Declare body and action URLs

js
await self.registration.showNotification("Order #123", {
  body: "Choose an action",
  tag: "order-123",
  navigate: "/orders/123",
  data: { orderId: "123", version: 4 },
  actions: [
    { action: "view", title: "View order", navigate: "/orders/123" },
    { action: "confirm", title: "Confirm delivery" },
    { action: "help", title: "Contact support", navigate: "/support/orders/123" },
  ],
});

Body and action URLs should be validated same-origin routes. Use tag to update or coalesce notifications for one order, and keep data to non-sensitive identifiers needed for recovery.

Step 2: Separate read-only and side-effecting actions

The view and help actions only navigate and may be handled by the browser. Confirm has no navigate, so it reaches notificationclick; it must call an idempotent server API rather than encode a state change in query parameters.

Step 3: Implement notificationclick fallback

js
self.addEventListener("notificationclick", (event) => {
  event.notification.close();
  const { orderId, version } = event.notification.data ?? {};
  if (event.action !== "confirm" || !orderId) return;

  event.waitUntil(confirmDelivery(orderId, version).then(() =>
    focusOrOpen(`/orders/${encodeURIComponent(orderId)}?confirmed=1`)));
});

Production code should catch network failures, stale versions, and unauthorized responses and let the result page present recovery. waitUntil keeps the Service Worker event alive until the promise settles.

Step 4: Handle windows and login recovery

focusOrOpen should match a controlled same-origin window, send a validated route through postMessage, and call clients.openWindow only when no suitable window exists. If login expired, carry only short-lived target state; after sign-in, the page must fetch and authorize the order again.

Step 5: Security, permission, and idempotency

Notification permission controls display, not order authorization. Restrict protocol, origin, and path; use order ID, version, or an idempotency key to prevent duplicate confirmations. Dismissal, duplicate tags, and simultaneous device clicks need explicit server state-machine behavior.

6. Model high-quality answer

I would use same-origin navigate for view and help, and handle Confirm delivery in notificationclick because it has a side effect. The Service Worker uses waitUntil to call an idempotent API with the order version and key, then focuses an existing window or opens the result route. Every URL is allowlisted, permission is not authorization, and login recovery rechecks access. Tests cover body and action clicks, repeats, offline mode, stale versions, and multiple windows.

7. Common mistakes

  • Trigger confirmation through a GET URL → prefetch or repeats cause side effects → call an idempotent POST from the event.
  • Assume an action without a URL opens the body URL → behavior is ambiguous → handle it explicitly in notificationclick.
  • Put the full order in data → sensitive information leaks → carry an identifier and refetch authorized data.
  • Omit waitUntil around async work → the Worker may terminate early → manage every critical promise.
  • Always create a new window → split state → match and focus a same-origin window first.

8. Follow-up questions

Follow-up 1: Which wins, action.navigate or notificationclick?

When an action has its own navigate, the browser can use that URL. An action without one needs custom notificationclick handling.

Follow-up 2: Why not put confirmation in the URL?

Navigation can be prefetched, replayed, or clicked repeatedly and cannot safely represent a side effect. Confirmation belongs to an authorized, idempotent server state transition.

Follow-up 3: How do you recover from an expired login?

Store short-lived, integrity-protected target state when opening login. After sign-in, the page fetches the order, checks authorization, and then restores the result route.

Follow-up 4: How do you prove no duplicate execution?

Trigger body and action clicks repeatedly across windows and devices with the same order version and idempotency key, then assert that the server performs one confirmation transition.

Public sources

Related questions