api-debug.component.ts 3.44 KiB
import { HttpRequest, HttpResponseBase } from '@angular/common/http';
import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  EventEmitter,
  Inject,
  InjectionToken,
  Output,
} from '@angular/core';
import { DebugStateService } from '../debug-state.service';
import { ApiDebugInterceptor, DebugEvent } from './api-debug.interceptor';
interface RequestInfo {
  id: string;
  url: string;
  status: 'pending' | 'success' | 'error' | 'cancelled';
  method: string;
  response?: ResponseInfo;
interface ResponseInfo {
  status: number;
  statusText: string;
  token?: string;
  tokenLink?: string;
export const ApiBaseUrlToken = new InjectionToken<string>('apiBaseUrl');
@Component({
  selector: 'dbg-api-debug',
  templateUrl: './api-debug.component.html',
  styleUrls: ['./api-debug.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
export class ApiDebugComponent {
  @Output() public countChanged = new EventEmitter<number>();
  private readonly requests = new Map<string, RequestInfo>();
  private readonly unseen = new Set<string>();
  private readonly ids: string[] = [];
  constructor(
    interceptor: ApiDebugInterceptor,
    public readonly debug: DebugStateService,
    private readonly cd: ChangeDetectorRef,
    @Inject(ApiBaseUrlToken) private readonly apiPrefix: string
  ) {
    interceptor.events$.subscribe(ev => {
      this.handle(ev);
    });
  public isUnseen({ id }: RequestInfo): boolean {
    return this.unseen.has(id);
  public seen({ id }: RequestInfo): void {
    if (this.unseen.has(id)) {
      this.unseen.delete(id);
      this.cd.markForCheck();
  private handle(ev: DebugEvent): void {
    if (ev.type === 'request') {
      this.addRequest(ev.id, ev.request);
      this.countChanged.emit(this.ids.length);
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
this.cd.markForCheck(); return; } const request = this.requests.get(ev.id); if (!request || request.status !== 'pending') { return; } if (ev.type === 'response') { this.setResponse(request, ev.response); } else { this.cancelRequest(request); } this.cd.markForCheck(); } private addRequest( id: string, { urlWithParams, method }: HttpRequest<any> ): void { if (urlWithParams.startsWith(this.apiPrefix)) { urlWithParams = new URL(urlWithParams).pathname; } const req: RequestInfo = { id, method, url: urlWithParams, status: 'pending', }; this.requests.set(id, req); this.unseen.add(id); this.ids.unshift(id); } private setResponse( request: RequestInfo, { status, statusText, headers }: HttpResponseBase ): void { request.response = { status, statusText, token: headers.get('X-Debug-Token'), tokenLink: headers.get('X-Debug-Token-Link'), }; if (status < 100 || status >= 400) { request.status = 'error'; } else { request.status = 'success'; } } private cancelRequest(request: RequestInfo): void { request.status = 'cancelled'; } public get unseenCount(): number { return this.unseen.size; } public get totalCount(): number { return this.ids.length; } public get last(): RequestInfo | null { return this.ids.length ? this.requests.get(this.ids[0]) : null; } public get previous(): RequestInfo[] { return this.ids.slice(1).map(id => this.requests.get(id)); }
141142143144145146
public get all(): RequestInfo[] { return this.ids.map(id => this.requests.get(id)); } }