<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: kenlnetherland</title>
    <description>The latest articles on DEV Community by kenlnetherland (@kenlnetherland).</description>
    <link>https://dev.to/kenlnetherland</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1473120%2F29eb9121-d871-4276-96de-51666554c842.png</url>
      <title>DEV Community: kenlnetherland</title>
      <link>https://dev.to/kenlnetherland</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kenlnetherland"/>
    <language>en</language>
    <item>
      <title>How a VS Code Debugger Actually Works: DAP, CDP, and a C# Debug Adapter</title>
      <dc:creator>kenlnetherland</dc:creator>
      <pubDate>Mon, 31 Aug 2026 01:47:33 +0000</pubDate>
      <link>https://dev.to/kenlnetherland/how-a-vs-code-debugger-actually-works-dap-cdp-and-a-c-debug-adapter-2ddl</link>
      <guid>https://dev.to/kenlnetherland/how-a-vs-code-debugger-actually-works-dap-cdp-and-a-c-debug-adapter-2ddl</guid>
      <description>&lt;p&gt;&lt;em&gt;A practical look inside browser debugging—from pressing F5 in Visual Studio Code to controlling Chrome through the Debug Adapter Protocol and Chrome DevTools Protocol.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you press &lt;strong&gt;F5&lt;/strong&gt; in Visual Studio Code and a breakpoint suddenly stops JavaScript running inside Chrome, it can feel like VS Code is somehow directly controlling the browser.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;There's an entire conversation happening behind the scenes.&lt;/p&gt;

&lt;p&gt;Visual Studio Code speaks the &lt;strong&gt;Debug Adapter Protocol (DAP)&lt;/strong&gt;. Chrome speaks the &lt;strong&gt;Chrome DevTools Protocol (CDP)&lt;/strong&gt;. Something has to sit between them, translate those two worlds, manage the debugging session, track breakpoints, inspect variables, control execution, and report everything back to the editor.&lt;/p&gt;

&lt;p&gt;While building &lt;strong&gt;CloudIDEaaS JavaScript Debugger&lt;/strong&gt;, I decided to implement that middle layer as a C#/.NET debug adapter.&lt;/p&gt;

&lt;p&gt;The resulting architecture is surprisingly straightforward:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visual Studio Code
        |
        | Debug Adapter Protocol (DAP)
        v
C# Debug Adapter
        |
        | Chrome DevTools Protocol (CDP)
        | WebSocket
        v
      Chrome
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Understanding that pipeline changed the way I thought about browser debugging.&lt;/p&gt;

&lt;p&gt;In this article, I'll walk through what actually happens after you press &lt;strong&gt;F5&lt;/strong&gt;, how DAP and CDP fit together, and some of the problems a debug adapter has to solve to make the whole process feel seamless.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Happens When You Press F5?
&lt;/h2&gt;

&lt;p&gt;The process begins with the debugger configuration in &lt;code&gt;.vscode/launch.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For CloudIDEaaS, a minimal configuration looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Debug in Chrome"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cloudideaas-vscode-debugger"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8000/index.html"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you press &lt;strong&gt;F5&lt;/strong&gt;, Visual Studio Code sees the debugger &lt;code&gt;type&lt;/code&gt; and hands control to the extension registered to provide that debugger.&lt;/p&gt;

&lt;p&gt;The extension then starts the C# debug adapter as a separate process.&lt;/p&gt;

&lt;p&gt;At this point, VS Code isn't talking to Chrome. It's talking to the adapter.&lt;/p&gt;

&lt;p&gt;Communication between VS Code and the adapter takes place over standard input and output using DAP messages. Each message is framed with a header similar to HTTP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Length: 123

{ ...DAP JSON message... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first important request VS Code sends is &lt;code&gt;initialize&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The adapter responds by describing the debugging capabilities it supports. VS Code can then send requests such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;launch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setBreakpoints&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;configurationDone&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;threads&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stackTrace&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scopes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;variables&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evaluate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;next&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;continue&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;disconnect&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But those are &lt;strong&gt;DAP commands&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Chrome doesn't understand them.&lt;/p&gt;

&lt;p&gt;The debug adapter's job is to translate those requests into the appropriate Chrome DevTools Protocol commands—and translate Chrome's responses and events back into something Visual Studio Code understands.&lt;/p&gt;

&lt;p&gt;That's where CDP enters the picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting the Adapter to Chrome
&lt;/h2&gt;

&lt;p&gt;Once the debug adapter is running, it needs a completely different conversation with Chrome.&lt;/p&gt;

&lt;p&gt;Chrome exposes the &lt;strong&gt;Chrome DevTools Protocol (CDP)&lt;/strong&gt; through a debugging endpoint. After Chrome is launched with remote debugging enabled, the adapter discovers the appropriate debugging target and establishes a WebSocket connection.&lt;/p&gt;

&lt;p&gt;Through that connection, the adapter can enable the Chrome domains needed for debugging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Page.enable
Runtime.enable
Debugger.enable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These domains expose different pieces of Chrome's debugging functionality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Page&lt;/strong&gt; handles things such as navigation and page lifecycle events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime&lt;/strong&gt; provides access to JavaScript execution, objects, values, and expression evaluation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugger&lt;/strong&gt; provides breakpoints, script information, paused execution, call frames, and stepping.&lt;/p&gt;

&lt;p&gt;The adapter now sits between two active protocols:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VS Code                    C# Adapter                    Chrome
   |                            |                           |
   |-------- DAP -------------&amp;gt; |                           |
   |                            | -------- CDP -----------&amp;gt; |
   |                            | &amp;lt;------- CDP ------------ |
   | &amp;lt;------- DAP ------------- |                           |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the most important concepts behind the architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The adapter isn't simply forwarding messages.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;DAP and CDP describe debugging differently. The adapter has to maintain state and translate concepts between them.&lt;/p&gt;

&lt;p&gt;A VS Code breakpoint needs to become a Chrome breakpoint.&lt;/p&gt;

&lt;p&gt;A Chrome call frame needs to become a DAP stack frame.&lt;/p&gt;

&lt;p&gt;Chrome remote objects need to become variables that VS Code can display.&lt;/p&gt;

&lt;p&gt;A Chrome &lt;code&gt;Debugger.paused&lt;/code&gt; event needs to become a DAP &lt;code&gt;stopped&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;Commands such as &lt;strong&gt;Step Over&lt;/strong&gt;, &lt;strong&gt;Step Into&lt;/strong&gt;, &lt;strong&gt;Continue&lt;/strong&gt;, and &lt;strong&gt;Pause&lt;/strong&gt; have to travel in the opposite direction and become the corresponding CDP commands.&lt;/p&gt;

&lt;p&gt;So the adapter effectively becomes a translator—and a state manager—between the editor and the browser.&lt;/p&gt;

&lt;p&gt;But simply connecting the two protocols isn't enough.&lt;/p&gt;

&lt;p&gt;One of the more interesting problems appears before the application's JavaScript has even started running: &lt;strong&gt;how do you make sure a breakpoint in startup code is installed before Chrome executes that code?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Startup Breakpoint Problem
&lt;/h2&gt;

&lt;p&gt;A breakpoint isn't very useful if the code has already executed by the time the debugger installs it.&lt;/p&gt;

&lt;p&gt;This becomes especially important with JavaScript that runs immediately when a page loads.&lt;/p&gt;

&lt;p&gt;A naive debugger startup sequence might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch Chrome
     ↓
Load the application
     ↓
Connect the debugger
     ↓
Configure breakpoints
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's an obvious race condition.&lt;/p&gt;

&lt;p&gt;By the time VS Code sends the breakpoint configuration, Chrome may have already loaded the page and executed the JavaScript you wanted to debug.&lt;/p&gt;

&lt;p&gt;CloudIDEaaS handles this by reversing part of that sequence.&lt;/p&gt;

&lt;p&gt;Chrome initially launches without navigating to the application. The adapter connects to Chrome, enables the required CDP debugging domains, and tells VS Code that the debugger is ready.&lt;/p&gt;

&lt;p&gt;VS Code can then send its breakpoint configuration.&lt;/p&gt;

&lt;p&gt;The sequence becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch Chrome on about:blank
     ↓
Connect to Chrome through CDP
     ↓
Enable Page, Runtime, and Debugger
     ↓
Tell VS Code the debugger is initialized
     ↓
Receive breakpoint configuration from VS Code
     ↓
Receive configurationDone
     ↓
Navigate Chrome to the application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only after breakpoint configuration is complete does the adapter navigate Chrome to the application's actual URL.&lt;/p&gt;

&lt;p&gt;That seemingly small change is important.&lt;/p&gt;

&lt;p&gt;It means a breakpoint placed in JavaScript that executes during page startup can already be registered with Chrome before the application begins loading.&lt;/p&gt;

&lt;p&gt;This also demonstrates why a debug adapter needs to do more than translate individual commands.&lt;/p&gt;

&lt;p&gt;It has to coordinate &lt;strong&gt;timing, state, and lifecycle&lt;/strong&gt; across two independent systems.&lt;/p&gt;

&lt;p&gt;VS Code has its idea of when a debugging session is ready.&lt;/p&gt;

&lt;p&gt;Chrome has its own page and JavaScript execution lifecycle.&lt;/p&gt;

&lt;p&gt;The adapter has to make those two timelines behave like one debugging experience.&lt;/p&gt;

&lt;p&gt;Once execution finally stops at a breakpoint, another translation problem begins: turning Chrome's call frames, scopes, and remote JavaScript objects into the variables and call stack that appear inside VS Code.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Chrome Call Frames to VS Code Variables
&lt;/h2&gt;

&lt;p&gt;When Chrome reaches a breakpoint, CDP sends the adapter a &lt;code&gt;Debugger.paused&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;That event contains information about why execution stopped and, most importantly, the JavaScript call frames associated with the paused execution.&lt;/p&gt;

&lt;p&gt;The adapter translates that into DAP concepts that Visual Studio Code understands.&lt;/p&gt;

&lt;p&gt;The flow looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chrome
  |
  | Debugger.paused
  v
C# Debug Adapter
  |
  | DAP stopped event
  v
VS Code
  |
  | threads
  | stackTrace
  | scopes
  | variables
  v
C# Debug Adapter
  |
  | CDP runtime/debugger requests
  v
Chrome
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VS Code doesn't receive the entire JavaScript object graph when execution stops. Instead, it requests information as it needs it.&lt;/p&gt;

&lt;p&gt;For example, VS Code asks for the stack trace. The adapter maps Chrome's call frames into DAP stack frames.&lt;/p&gt;

&lt;p&gt;When you expand a stack frame, VS Code requests its scopes.&lt;/p&gt;

&lt;p&gt;When you expand a scope or object, VS Code requests its variables.&lt;/p&gt;

&lt;p&gt;This is where CDP's remote-object model becomes important.&lt;/p&gt;

&lt;p&gt;Chrome often represents JavaScript objects using an &lt;code&gt;objectId&lt;/code&gt; rather than returning the complete object. The adapter can retain that reference and later use CDP to retrieve the object's properties when VS Code asks for them.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chrome objectId
      ↓
Adapter reference
      ↓
DAP variablesReference
      ↓
VS Code expandable variable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows complex objects to appear naturally in the VS Code Variables panel without copying the entire JavaScript runtime state across the connection every time execution pauses.&lt;/p&gt;

&lt;p&gt;Expression evaluation follows a similar path.&lt;/p&gt;

&lt;p&gt;When you evaluate an expression in VS Code, the editor sends a DAP &lt;code&gt;evaluate&lt;/code&gt; request. The adapter determines the appropriate paused JavaScript context, sends the corresponding request to Chrome, receives the result, and translates that result back into a DAP response.&lt;/p&gt;

&lt;p&gt;The same basic pattern appears throughout the debugger:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VS Code asks for a debugging concept, the adapter translates it into Chrome's model, and then translates Chrome's answer back into VS Code's model.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That translation layer is what makes two very different protocols feel like a single debugger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breakpoints Are More Complicated Than They Look
&lt;/h2&gt;

&lt;p&gt;From the user's perspective, a breakpoint is simple: click beside a line of code and a red dot appears.&lt;/p&gt;

&lt;p&gt;Behind the scenes, the debugger has more work to do.&lt;/p&gt;

&lt;p&gt;When VS Code sends a DAP &lt;code&gt;setBreakpoints&lt;/code&gt; request, the adapter knows the source file and line where the developer wants execution to stop. Chrome, however, ultimately needs to associate that request with JavaScript it knows about.&lt;/p&gt;

&lt;p&gt;For browser debugging, URL-based breakpoints provide an important bridge.&lt;/p&gt;

&lt;p&gt;The adapter can register the breakpoint with Chrome before the corresponding script has loaded. Initially, that breakpoint may be unresolved.&lt;/p&gt;

&lt;p&gt;Later, when Chrome loads a matching script and determines the actual executable location, CDP reports that the breakpoint has been resolved.&lt;/p&gt;

&lt;p&gt;The adapter can then update VS Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VS Code
   |
   | setBreakpoints
   v
C# Debug Adapter
   |
   | Debugger.setBreakpointByUrl
   v
Chrome
   |
   | script loads
   | breakpoint resolves
   v
C# Debug Adapter
   |
   | DAP breakpoint event
   | verified = true
   v
VS Code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why a breakpoint can begin as unverified and later become verified without the developer doing anything.&lt;/p&gt;

&lt;p&gt;The adapter also has to maintain mappings between the identifiers used by each side.&lt;/p&gt;

&lt;p&gt;A breakpoint known to VS Code needs to remain associated with the corresponding CDP breakpoint identifier. Stack frames, scopes, objects, and source references require similar bookkeeping.&lt;/p&gt;

&lt;p&gt;Page navigation makes this even more interesting.&lt;/p&gt;

&lt;p&gt;When Chrome replaces its JavaScript execution environment, references associated with the previous environment can become invalid. Old call frames, object references, scope references, and script information may need to be discarded.&lt;/p&gt;

&lt;p&gt;But the user's source breakpoint intentions should survive.&lt;/p&gt;

&lt;p&gt;So when Chrome reports that its global object has been cleared, the adapter can clear transient runtime state while preserving the URL-based breakpoint registrations needed for the next page execution.&lt;/p&gt;

&lt;p&gt;That distinction is fundamental:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some debugger state belongs to the current JavaScript execution context. Other state represents the developer's debugging intent and needs to survive changes in that context.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Keeping those two kinds of state separate is one of the less visible responsibilities of a debug adapter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stepping Through JavaScript
&lt;/h2&gt;

&lt;p&gt;Once execution is paused, familiar debugger controls such as &lt;strong&gt;Step Over&lt;/strong&gt;, &lt;strong&gt;Step Into&lt;/strong&gt;, &lt;strong&gt;Step Out&lt;/strong&gt;, and &lt;strong&gt;Continue&lt;/strong&gt; become another translation exercise.&lt;/p&gt;

&lt;p&gt;Visual Studio Code expresses these actions as DAP requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;next
stepIn
stepOut
continue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adapter translates them into the corresponding Chrome DevTools Protocol commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Debugger.stepOver
Debugger.stepInto
Debugger.stepOut
Debugger.resume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sequence for &lt;strong&gt;Step Over&lt;/strong&gt;, for example, looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VS Code
   |
   | DAP next
   v
C# Debug Adapter
   |
   | CDP Debugger.stepOver
   v
Chrome
   |
   | JavaScript executes
   |
   | CDP Debugger.paused
   v
C# Debug Adapter
   |
   | DAP stopped
   v
VS Code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although the command translation itself is relatively simple, the debugger's state changes significantly during this process.&lt;/p&gt;

&lt;p&gt;Once Chrome resumes execution, references associated with the previous paused state—such as call frames, scopes, and some object references—can no longer be treated as current.&lt;/p&gt;

&lt;p&gt;When Chrome pauses again, the adapter receives a new set of call frames and builds a new representation of the paused execution state for VS Code.&lt;/p&gt;

&lt;p&gt;This creates a repeating lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUNNING
   ↓
Chrome pauses
   ↓
PAUSED
   ↓
VS Code inspects frames, scopes, and variables
   ↓
Developer steps or continues
   ↓
RUNNING
   ↓
Chrome pauses again
   ↓
PAUSED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the developer's perspective, clicking &lt;strong&gt;Step Over&lt;/strong&gt; simply moves the yellow execution marker to the next location.&lt;/p&gt;

&lt;p&gt;Underneath that small UI interaction, two protocols, three processes, and a collection of temporary runtime references are being coordinated.&lt;/p&gt;

&lt;p&gt;That's one of the things I found most interesting about implementing a debugger: the best debugging experience is often the one that hides how much work is happening underneath it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why C#?
&lt;/h2&gt;

&lt;p&gt;The Debug Adapter Protocol doesn't require a debugger to be written in the same language as the application being debugged.&lt;/p&gt;

&lt;p&gt;A debug adapter is a separate process that communicates with Visual Studio Code through a defined protocol. That means the implementation language is largely an architectural choice.&lt;/p&gt;

&lt;p&gt;For CloudIDEaaS, I chose &lt;strong&gt;C# and .NET&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The VS Code extension itself remains a small JavaScript layer responsible for integrating with VS Code and launching the adapter. The actual debugging logic lives in the C# process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VS Code Extension
    JavaScript
        |
        | launches
        v
C#/.NET Debug Adapter
        |
        | CDP over WebSocket
        v
      Chrome
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation has several advantages.&lt;/p&gt;

&lt;p&gt;The extension doesn't need to contain the entire debugger implementation. Its primary job is to register the debugger with VS Code and start the appropriate adapter process.&lt;/p&gt;

&lt;p&gt;The C# application can concentrate on protocol handling, Chrome communication, state management, breakpoint translation, object tracking, and debugging behavior.&lt;/p&gt;

&lt;p&gt;It also demonstrates something useful about DAP itself.&lt;/p&gt;

&lt;p&gt;DAP creates a boundary between the editor and the debugger implementation.&lt;/p&gt;

&lt;p&gt;VS Code doesn't need to know whether the adapter behind that boundary was written in JavaScript, TypeScript, C#, C++, Rust, Python, or something else.&lt;/p&gt;

&lt;p&gt;As long as both sides follow the protocol, they can communicate.&lt;/p&gt;

&lt;p&gt;The same principle applies on the other side of the adapter. Chrome doesn't care that Visual Studio Code initiated the debugging session. It sees a client communicating with its DevTools Protocol.&lt;/p&gt;

&lt;p&gt;That leaves the adapter sitting at a very clean architectural boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Editor concerns
      |
     DAP
      |
-----------------
   Debug Adapter
-----------------
      |
     CDP
      |
Browser concerns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For me, that separation was one of the most valuable lessons from building the project.&lt;/p&gt;

&lt;p&gt;A debugger that appears to be a tightly integrated feature of an editor can actually be several independent systems cooperating through well-defined protocols.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Building a Debugger Taught Me
&lt;/h2&gt;

&lt;p&gt;Before building CloudIDEaaS, I thought about browser debugging mostly from the developer's side of the screen: set a breakpoint, press F5, inspect some variables, step through the code, and find the problem.&lt;/p&gt;

&lt;p&gt;Building the adapter exposed everything underneath that experience.&lt;/p&gt;

&lt;p&gt;A debugging session isn't one continuous conversation. It's several independent systems maintaining enough shared state to create the illusion that they are one.&lt;/p&gt;

&lt;p&gt;Visual Studio Code has its debugging model.&lt;/p&gt;

&lt;p&gt;Chrome has its debugging model.&lt;/p&gt;

&lt;p&gt;DAP defines how the editor communicates with a debugger.&lt;/p&gt;

&lt;p&gt;CDP defines how a client controls and inspects Chrome.&lt;/p&gt;

&lt;p&gt;The adapter has to reconcile them.&lt;/p&gt;

&lt;p&gt;That led to a few lessons that extend beyond debugging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Protocols Create Powerful Boundaries
&lt;/h3&gt;

&lt;p&gt;DAP allowed me to implement the debugger in C# without requiring VS Code to understand anything about the implementation.&lt;/p&gt;

&lt;p&gt;CDP allowed that C# application to control Chrome without Chrome knowing anything about Visual Studio Code.&lt;/p&gt;

&lt;p&gt;Those protocol boundaries make the architecture surprisingly modular.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Is Often Harder Than Commands
&lt;/h3&gt;

&lt;p&gt;Sending &lt;code&gt;Debugger.stepOver&lt;/code&gt; isn't particularly complicated.&lt;/p&gt;

&lt;p&gt;Knowing which stack frames are still valid, which object references belong to the current pause, which breakpoints should survive navigation, and when VS Code should be told that something changed is much more interesting.&lt;/p&gt;

&lt;p&gt;A large part of debugger development is really &lt;strong&gt;state management&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timing Matters
&lt;/h3&gt;

&lt;p&gt;The startup-breakpoint problem is a good example.&lt;/p&gt;

&lt;p&gt;Every individual component can work correctly and the debugger can still fail simply because things happen in the wrong order.&lt;/p&gt;

&lt;p&gt;Launching Chrome on &lt;code&gt;about:blank&lt;/code&gt;, configuring the debugger first, and navigating afterward turns that race condition into a predictable sequence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simplicity at the Surface Requires Work Underneath
&lt;/h3&gt;

&lt;p&gt;The ultimate CloudIDEaaS workflow is intentionally simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Set breakpoint
     ↓
Press F5
     ↓
Debug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But making that workflow simple means the tooling has to handle the complexity somewhere else.&lt;/p&gt;

&lt;p&gt;That's the tradeoff I find interesting.&lt;/p&gt;

&lt;p&gt;Developer tools don't necessarily become better by exposing every capability and configuration option they possess.&lt;/p&gt;

&lt;p&gt;Sometimes good tooling means absorbing complexity so the developer doesn't have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It or Explore the Source
&lt;/h2&gt;

&lt;p&gt;CloudIDEaaS JavaScript Debugger is free and open source.&lt;/p&gt;

&lt;p&gt;If you're interested in straightforward F5 browser debugging, you can install it from the Visual Studio Marketplace:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=CloudIDEaaS.cloudideaas-vscode-debugger" rel="noopener noreferrer"&gt;CloudIDEaaS JavaScript Debugger on the Visual Studio Marketplace&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're more interested in how the debugger works, the complete source is available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/CloudIDEaaS/cloudideaas-vscode-debugger" rel="noopener noreferrer"&gt;CloudIDEaaS JavaScript Debugger on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The project includes the JavaScript VS Code extension layer and the C#/.NET debug adapter discussed throughout this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CloudIDEaaS?
&lt;/h2&gt;

&lt;p&gt;The architecture is only part of the story.&lt;/p&gt;

&lt;p&gt;CloudIDEaaS is designed around a broader idea: debugging straightforward JavaScript applications shouldn't require a complicated debugging environment.&lt;/p&gt;

&lt;p&gt;I've put together a complete breakdown of the problems CloudIDEaaS is designed to solve, who it's for, and the features, advantages, and benefits behind the project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://cloudideaas.blogspot.com/p/why-cloudideaas-javascript-debugger.html" rel="noopener noreferrer"&gt;Why CloudIDEaaS JavaScript Debugger?&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The goal is simple: fewer moving pieces between your code and the problem you're trying to solve.&lt;/p&gt;

&lt;p&gt;I also wrote about the broader motivation behind the project and why I think reducing developer-tool complexity can sometimes be more valuable than adding another feature:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://publications.lavedajones.com/vscode-debugger/index.html" rel="noopener noreferrer"&gt;When Developer Tools Become the Problem: Why I Built a Simpler JavaScript Debugger&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're working with DAP, CDP, browser debugging, or VS Code extension development, I'd be particularly interested in hearing about the architectural problems you've encountered.&lt;/p&gt;

&lt;p&gt;Sometimes the most interesting part of pressing &lt;strong&gt;F5&lt;/strong&gt; is everything that had to happen to make pressing F5 feel simple.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vscode</category>
      <category>debugging</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Wanted to Press F5 and Debug JavaScript — So I Built My Own VS Code Debugger</title>
      <dc:creator>kenlnetherland</dc:creator>
      <pubDate>Mon, 31 Aug 2026 00:25:20 +0000</pubDate>
      <link>https://dev.to/kenlnetherland/i-wanted-to-press-f5-and-debug-javascript-so-i-built-my-own-vs-code-debugger-1c23</link>
      <guid>https://dev.to/kenlnetherland/i-wanted-to-press-f5-and-debug-javascript-so-i-built-my-own-vs-code-debugger-1c23</guid>
      <description>&lt;p&gt;Sometimes software development reaches a point where the tools designed to make your job easier start becoming part of the job.&lt;/p&gt;

&lt;p&gt;I ran into that with browser debugging.&lt;/p&gt;

&lt;p&gt;I wanted something that should have been simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Set a breakpoint. Press F5. Debug my JavaScript.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead, I found myself spending too much time thinking about development servers, browser launch configuration, debugger connections, ports, profiles, and the debugging environment itself.&lt;/p&gt;

&lt;p&gt;That led to a simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if browser debugging could go back to convention over configuration?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;CloudIDEaaS JavaScript Debugger&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚡ The Goal: Press F5 and Debug
&lt;/h2&gt;

&lt;p&gt;The philosophy behind CloudIDEaaS is straightforward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Spend your time debugging your application instead of debugging your debugging environment.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a straightforward JavaScript or HTML project, I wanted the workflow to look like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set a breakpoint.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;F5&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Start debugging.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Behind those three steps, CloudIDEaaS can start the local web server, launch Chrome, establish the debugging connection, configure your breakpoints, and then load the application.&lt;/p&gt;

&lt;p&gt;The important part is that &lt;strong&gt;you don't have to think about most of that.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔴 Real Debugging Inside VS Code
&lt;/h2&gt;

&lt;p&gt;This isn't intended to replace Chrome DevTools or compete feature-for-feature with every large JavaScript debugging platform.&lt;/p&gt;

&lt;p&gt;It's focused on providing the debugging features I use most often directly inside Visual Studio Code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔴 Source and conditional breakpoints&lt;/li&gt;
&lt;li&gt;👣 Step over, step into, and step out&lt;/li&gt;
&lt;li&gt;▶️ Continue and pause&lt;/li&gt;
&lt;li&gt;🔍 Local variables and object inspection&lt;/li&gt;
&lt;li&gt;📚 Scopes and call stacks&lt;/li&gt;
&lt;li&gt;🧮 Expression evaluation&lt;/li&gt;
&lt;li&gt;⚠️ Exception breakpoint configuration&lt;/li&gt;
&lt;li&gt;🌐 A built-in local web server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One feature that was particularly important to me was &lt;strong&gt;startup breakpoints&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The debugger establishes the connection and configures your breakpoints before loading the application, making it possible to catch JavaScript that executes during startup.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 What's Actually Happening Under the Hood?
&lt;/h2&gt;

&lt;p&gt;Building the debugger also turned into an interesting exploration of how modern debugging actually works.&lt;/p&gt;

&lt;p&gt;The architecture looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visual Studio Code
        |
        | Debug Adapter Protocol (DAP)
        v
CloudIDEaaS C# Debug Adapter
        |
        | Chrome DevTools Protocol (CDP)
        | WebSocket
        v
      Chrome
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visual Studio Code communicates with the CloudIDEaaS debug adapter using Microsoft's &lt;strong&gt;Debug Adapter Protocol (DAP)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The adapter is written in &lt;strong&gt;C#/.NET&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On the other side, the adapter communicates with Chrome using the &lt;strong&gt;Chrome DevTools Protocol (CDP)&lt;/strong&gt; over WebSockets.&lt;/p&gt;

&lt;p&gt;That means the adapter essentially sits between two debugging worlds:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VS Code ↔ DAP ↔ CloudIDEaaS ↔ CDP ↔ Chrome&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you set a breakpoint in VS Code, the adapter translates that debugging request into the corresponding Chrome debugging operation.&lt;/p&gt;

&lt;p&gt;When Chrome pauses, reports a call frame, exposes a variable, or resolves a breakpoint, the adapter translates that information back into something VS Code understands.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Why Build Another JavaScript Debugger?
&lt;/h2&gt;

&lt;p&gt;That's a fair question.&lt;/p&gt;

&lt;p&gt;VS Code already has excellent JavaScript debugging capabilities, and Chrome DevTools is extremely powerful.&lt;/p&gt;

&lt;p&gt;CloudIDEaaS isn't an attempt to pretend those tools don't exist.&lt;/p&gt;

&lt;p&gt;The motivation is &lt;strong&gt;workflow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are plenty of JavaScript projects where I don't want to assemble a modern frontend toolchain just to debug code running in a browser.&lt;/p&gt;

&lt;p&gt;That can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plain JavaScript applications&lt;/li&gt;
&lt;li&gt;HTML/CSS/JavaScript projects&lt;/li&gt;
&lt;li&gt;Traditional web applications&lt;/li&gt;
&lt;li&gt;Legacy applications&lt;/li&gt;
&lt;li&gt;Educational projects&lt;/li&gt;
&lt;li&gt;Small prototypes and experiments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For those projects, I wanted debugging to feel more like the traditional IDE experience:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set breakpoint → Press F5 → Debug.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ Convention Over Configuration
&lt;/h2&gt;

&lt;p&gt;That principle became one of the main design goals of the project.&lt;/p&gt;

&lt;p&gt;Modern development tools are incredibly powerful, but power often brings configuration.&lt;/p&gt;

&lt;p&gt;CloudIDEaaS intentionally takes a narrower approach.&lt;/p&gt;

&lt;p&gt;If sensible defaults can eliminate another configuration step, that's generally the direction I want to take.&lt;/p&gt;

&lt;p&gt;The project isn't trying to become everything for everyone.&lt;/p&gt;

&lt;p&gt;It's trying to make one common development task &lt;strong&gt;simpler&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🆓 Free and Open Source
&lt;/h2&gt;

&lt;p&gt;CloudIDEaaS JavaScript Debugger is free and open source under the &lt;strong&gt;MIT license&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'm actively developing it, and feedback is welcome—particularly from developers working with straightforward JavaScript applications, traditional web projects, and older codebases.&lt;/p&gt;

&lt;p&gt;If you try it, I'd like to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What kind of JavaScript project are you debugging?&lt;/li&gt;
&lt;li&gt;What part of browser debugging causes you the most friction?&lt;/li&gt;
&lt;li&gt;What would you expect to happen automatically when you press F5?&lt;/li&gt;
&lt;li&gt;What debugging feature would make the biggest difference to your workflow?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 Try It
&lt;/h2&gt;

&lt;p&gt;You can install &lt;strong&gt;CloudIDEaaS JavaScript Debugger&lt;/strong&gt; from the Visual Studio Marketplace:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=CloudIDEaaS.cloudideaas-vscode-debugger" rel="noopener noreferrer"&gt;Install CloudIDEaaS JavaScript Debugger&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The source code is available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/CloudIDEaaS/cloudideaas-vscode-debugger" rel="noopener noreferrer"&gt;CloudIDEaaS JavaScript Debugger on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set a breakpoint.&lt;/p&gt;

&lt;p&gt;Press &lt;strong&gt;F5&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And spend your time debugging the application.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
`

## 📖 Further Reading

I also wrote about the broader philosophy behind CloudIDEaaS and why reducing developer-tool complexity can sometimes be more valuable than adding another feature:

**[When Developer Tools Become the Problem: Why I Built a Simpler JavaScript Debugger](https://publications.lavedajones.com/vscode-debugger/index.html)**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>javascript</category>
      <category>vscode</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>HttpInterceptor, Observable weirdness</title>
      <dc:creator>kenlnetherland</dc:creator>
      <pubDate>Mon, 06 May 2024 16:35:39 +0000</pubDate>
      <link>https://dev.to/kenlnetherland/httpinterceptor-observable-weirdness-58i7</link>
      <guid>https://dev.to/kenlnetherland/httpinterceptor-observable-weirdness-58i7</guid>
      <description>&lt;p&gt;Why does the following code work (tap.. next callback):&lt;/p&gt;

&lt;p&gt;`&lt;br&gt;
intercept(req: HttpRequest, next: HttpHandler): Observable {&lt;/p&gt;

&lt;p&gt;let ok: string;&lt;br&gt;
let started = Date.now();&lt;/p&gt;

&lt;p&gt;let observable = next.handle(req).pipe(&lt;br&gt;
  tap({&lt;br&gt;
    // Succeeds when there is a response; ignore other events&lt;br&gt;
    next: (event) =&amp;gt; {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  if (event instanceof HttpResponse) {
    ok = event.statusText;
  }
},
// Operation failed; error is an HttpErrorResponse
error: (_error) =&amp;gt; {
  ok = 'failed'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}),&lt;br&gt;
  // Log when response observable either completes or errors&lt;br&gt;
  finalize(() =&amp;gt; {&lt;br&gt;
    const elapsed = Date.now() - started;&lt;br&gt;
    const msg = &lt;code&gt;${req.method} "${req.urlWithParams}"&lt;br&gt;
      ${ok} in ${elapsed} ms.&lt;/code&gt;;&lt;br&gt;
  })&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;return observable;&lt;br&gt;
}&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;But this does not:&lt;/p&gt;

&lt;p&gt;`&lt;br&gt;
intercept(req: HttpRequest, next: HttpHandler): Observable {&lt;/p&gt;

&lt;p&gt;let ok: string;&lt;br&gt;
let started = Date.now();&lt;br&gt;
let observable = next.handle(req);&lt;/p&gt;

&lt;p&gt;observable.pipe(&lt;br&gt;
  tap({&lt;br&gt;
    // Succeeds when there is a response; ignore other events&lt;br&gt;
    next: (event) =&amp;gt; {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  if (event instanceof HttpResponse) {
    ok = event.statusText;
  }
},
// Operation failed; error is an HttpErrorResponse
error: (_error) =&amp;gt; {
  ok = 'failed'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}),&lt;br&gt;
  // Log when response observable either completes or errors&lt;br&gt;
  finalize(() =&amp;gt; {&lt;br&gt;
    const elapsed = Date.now() - started;&lt;br&gt;
    const msg = &lt;code&gt;${req.method} "${req.urlWithParams}"&lt;br&gt;
      ${ok} in ${elapsed} ms.&lt;/code&gt;;&lt;br&gt;
  })&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;return observable;&lt;br&gt;
}&lt;br&gt;
`&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
