All files / app/service origin-map.service.ts

45.74% Statements 43/94
32.05% Branches 25/78
42.85% Functions 21/49
44.77% Lines 30/67

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 1621x           1x   1x               6x   6x     6x 6x 6x 6x                                                                       4x     4x             4x 4x 4x 4x                                                                                                               4x 4x 4x 2x 2x             32x 216x 72x 24x 48x   8x   16x 8x       4x   8x            
import { Injectable } from '@angular/core';
import { uniq } from 'lodash-es';
import { runInAction } from 'mobx';
import { catchError, Observable, of, switchMap } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Ref } from '../model/ref';
import { isPushing, isReplicating } from '../mods/sync/origin';
import { Store } from '../store/store';
import { defaultOrigin, subOrigin } from '../util/tag';
import { AdminService } from './admin.service';
import { RefService } from './api/ref.service';
import { ConfigService } from './config.service';
 
@Injectable({
  providedIn: 'root'
})
export class OriginMapService {
 
  private origins: Ref[] = [];
 
  constructor(
    private config: ConfigService,
    private admin: AdminService,
    private refs: RefService,
    private store: Store,
  ) { }
 
  get init$() {
    this.origins = [];
    if (!this.admin.getPlugin('+plugin/origin')) return of(null);
    return this.loadOrigins$().pipe(
      tap(() => runInAction(() => {
        this.store.origins.origins = this.origins;
        this.store.origins.list = this.list;
        this.store.origins.lookup = this.lookup;
        this.store.origins.tunnelLookup = this.tunnelLookup;
        this.store.origins.reverseLookup = this.reverseLookup;
        this.store.origins.originMap = this.originMap;
      })),
      catchError(err => {
        console.error("Error looking up origin cross references.");
        console.error(err);
        return of(null)
      }),
    );
  }
 
  private loadOrigins$(page = 0): Observable<null> {
    const alreadyLoaded = page * this.config.fetchBatch;
    if (alreadyLoaded >= this.config.maxOrigins) {
      console.error(`Too many origins to load, only loaded ${alreadyLoaded}. Increase maxOrigins to load more.`)
      return of(null);
    }
    return this.refs.page({ query: '+plugin/origin', page, size: this.config.fetchBatch, obsolete: null as any }).pipe(
      tap(batch => this.origins.push(...batch.content)),
      switchMap(batch => !batch.content.length ? of(null) : this.loadOrigins$(page + 1)),
    );
  }
 
  private get api() {
    Iif (this.config.api.startsWith('//')) {
      return location.protocol + this.config.api;
    }
    return this.config.api;
  }
 
  /**
   * Searches push configs to list api aliases.
   */
  private get selfApis(): Map<string, string> {
    const config = (remote?: Ref): any => remote?.plugins?.['+plugin/origin'];
    const trimUrl = (url: string) => url.endsWith('/') ? url.substring(0, url.length - 1) : url;
    const remotesForOrigin = (origin: string) => this.origins.filter(remote => remote.origin === origin);
    return new Map([
      [this.api, this.store.account.origin],
      ...remotesForOrigin(this.store.account.origin)
        .filter(remote => isPushing(remote, ''))
        .map(remote => [trimUrl(remote.url), config(remote).remote]),
    ] as [string, string][]);
  }
 
  /**
   * Lists all visible origins.
   */
  private get list(): string[] {
    const config = (remote?: Ref): any => remote?.plugins?.['+plugin/origin'];
    const remotesForOrigin = (origin: string) => this.origins.filter(remote => remote.origin === origin);
    return uniq([
      this.store.account.origin,
      ...remotesForOrigin(this.store.account.origin)
        .map(remote => subOrigin(remote.origin, config(remote)?.local)),
    ]);
  }
 
  /**
   * Maps local-alias -> api.
   */
  private get lookup(): Map<string, string> {
    const config = (remote?: Ref): any => remote?.plugins?.['+plugin/origin'];
    const trimUrl = (url: string) => url.endsWith('/') ? url.substring(0, url.length - 1) : url;
    const remotesForOrigin = (origin: string) => this.origins.filter(remote => remote.origin === origin);
    return new Map([
      [this.store.account.origin, this.api],
      ...remotesForOrigin(this.store.account.origin)
        .map(remote => [subOrigin(remote.origin, config(remote)?.local), trimUrl(remote.url)]),
    ] as [string, string][]);
  }
 
  /**
   * Maps local-alias -> ssh user.
   */
  private get tunnelLookup(): Map<string, string> {
    const config = (remote?: Ref): any => remote?.plugins?.['+plugin/origin'];
    const tunnel = (remote: Ref): any => remote.plugins?.['+plugin/origin/tunnel'];
    const remotesForOrigin = (origin: string) => this.origins.filter(remote => remote.origin === origin);
    return new Map([
      [this.store.account.origin, this.api],
        ...remotesForOrigin(this.store.account.origin)
          .map(remote => [subOrigin(remote.origin, config(remote)?.local), {
            ...tunnel(remote),
            remoteUser: defaultOrigin(tunnel(remote)?.remoteUser || '', remote.origin),
          }]),
    ] as [string, string][]);
  }
 
  /**
   * Maps local-alias -> remote-alias-to-self.
   */
  private get reverseLookup(): Map<string, string> {
    const config = (remote?: Ref): any => remote?.plugins?.['+plugin/origin'];
    return new Map(this.origins
      .filter(remote => isReplicating(this.store.account.origin || '', remote, this.selfApis))
      .filter(remote => config(remote)?.local)
      .map(remote => [remote.origin || '', config(remote)?.local]));
  }
 
  /**
   * Maps local-alias -> remote-alias -> local-alias.
   */
  private get originMap(): Map<string, Map<string, string>> {
    const config = (remote: Ref): any => remote.plugins?.['+plugin/origin'];
    const remotesForOrigin = (origin: string) => this.origins.filter(remote => remote.origin === origin);
    const trimUrl = (url: string) => url.endsWith('/') ? url.substring(0, url.length - 1) : url;
    const findLocalAlias = (url: string) => remotesForOrigin(this.store.account.origin)
      .filter(remote => trimUrl(remote.url) === url)
      [0] || undefined;
    const originMapFor = (remote: Ref): Map<string, string> => new Map(
      remotesForOrigin(subOrigin(this.store.account.origin, config(remote)?.local))
        .filter(nested => findLocalAlias(trimUrl(nested.url)) !== undefined)
        .map(nested => [
          config(nested)?.local || '',
          config(findLocalAlias(trimUrl(nested.url))!)?.local || ''
        ]));
    return new Map(
      remotesForOrigin(this.store.account.origin || '')
        .map(remote => [
          subOrigin(this.store.account.origin, config(remote)?.local),
          originMapFor(remote)
        ]));
  }
}