All files hub.ts

100% Statements 65/65
87.1% Branches 27/31
100% Functions 20/20
100% Lines 60/60
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 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226    3x               3x           3x   26x                       47x 77x   26x                 3x 3x 3x 1x                     4x 4x 4x 1x 1x                     3x 2x             3x 4x 4x 4x 4x 1x 1x 1x                                 3x   9x   9x 9x                         3x 4x                               3x 3x 3x 3x   3x         3x 10x       3x 43x       3x 28x               3x 1x               3x 1x               3x 1x                     3x 1x               3x 3x 3x   2x               3x 3x 3x 3x     3x  
import { Breadcrumb, SentryEvent } from '@sentry/types';
import { Layer } from './interfaces';
import { Scope } from './scope';
 
/**
 * API compatibility version of this hub.
 *
 * WARNING: This number should only be incresed when the global interface
 * changes a and new methods are introduced.
 */
export const API_VERSION = 2;
 
/**
 * Internal class used to make sure we always have the latest internal functions
 * working in case we have a version conflict.
 */
export class Hub {
  /** Is a {@link Layer}[] containing the client and scope */
  private readonly stack: Layer[] = [];
 
  /**
   * Creates a new instance of the hub, will push one {@link Layer} into the
   * internal stack on creation.
   *
   * @param client bound to the hub.
   * @param scope bound to the hub.
   * @param version number, higher number means higher priority.
   */
  public constructor(
    client?: any,
    scope: Scope = new Scope(),
    private readonly version: number = API_VERSION,
  ) {
    this.stack.push({ client, scope });
  }
 
  /**
   * Internal helper function to call a method on the top client if it exists.
   *
   * @param method The method to call on the client/client.
   * @param args Arguments to pass to the client/fontend.
   */
  private invokeClient(method: string, ...args: any[]): void {
    const top = this.getStackTop();
    if (top && top.client && top.client[method]) {
      top.client[method](...args, top.scope);
    }
  }
 
  /**
   * Internal helper function to call an async method on the top client if it
   * exists.
   *
   * @param method The method to call on the client/client.
   * @param args Arguments to pass to the client/fontend.
   */
  private invokeClientAsync(method: string, ...args: any[]): void {
    const top = this.getStackTop();
    if (top && top.client && top.client[method]) {
      top.client[method](...args, top.scope).catch((err: any) => {
        console.error(err);
      });
    }
  }
 
  /**
   * Checks if this hub's version is older than the given version.
   *
   * @param version A version number to compare to.
   * @return True if the given version is newer; otherwise false.
   */
  public isOlderThan(version: number): boolean {
    return this.version < version;
  }
 
  /**
   * This binds the given client to the current scope.
   * @param client An SDK client (client) instance.
   */
  public bindClient(client?: any): void {
    const top = this.getStackTop();
    top.client = client;
    Eif (top && top.scope && client) {
      top.scope.addScopeListener((s: Scope) => {
        Eif (client.getBackend) {
          try {
            client.getBackend().storeScope(s);
          } catch {
            // Do nothing
          }
        }
      });
    }
  }
 
  /**
   * Create a new scope to store context information.
   *
   * The scope will be layered on top of the current one. It is isolated, i.e. all
   * breadcrumbs and context information added to this scope will be removed once
   * the scope ends. Be sure to always remove this scope with {@link this.popScope}
   * when the operation finishes or throws.
   */
  public pushScope(): void {
    // We want to clone the content of prev scope
    const stack = this.getStack();
    const parentScope =
      stack.length > 0 ? stack[stack.length - 1].scope : undefined;
    this.getStack().push({
      client: this.getClient(),
      scope: Scope.clone(parentScope),
    });
  }
 
  /**
   * Removes a previously pushed scope from the stack.
   *
   * This restores the state before the scope was pushed. All breadcrumbs and
   * context information added since the last call to {@link this.pushScope} are
   * discarded.
   */
  public popScope(): boolean {
    return this.getStack().pop() !== undefined;
  }
 
  /**
   * Creates a new scope with and executes the given operation within.
   * The scope is automatically removed once the operation
   * finishes or throws.
   *
   * This is essentially a convenience function for:
   *
   *     pushScope();
   *     callback();
   *     popScope();
   *
   * @param callback that will be enclosed into push/popScope.
   */
  public withScope(callback: (() => void)): void {
    this.pushScope();
    try {
      callback();
    } finally {
      this.popScope();
    }
  }
 
  /** Returns the client of the currently active scope. */
  public getClient(): any | undefined {
    return this.getStackTop().client;
  }
 
  /** Returns the scope stack for domains or the process. */
  public getStack(): Layer[] {
    return this.stack;
  }
 
  /** Returns the topmost scope layer in the order domain > local > process. */
  public getStackTop(): Layer {
    return this.stack[this.stack.length - 1];
  }
 
  /**
   * Captures an exception event and sends it to Sentry.
   *
   * @param exception An exception-like object.
   */
  public captureException(exception: any): void {
    this.invokeClientAsync('captureException', exception);
  }
 
  /**
   * Captures a message event and sends it to Sentry.
   *
   * @param message The message to send to Sentry.
   */
  public captureMessage(message: string): void {
    this.invokeClientAsync('captureMessage', message);
  }
 
  /**
   * Captures a manually created event and sends it to Sentry.
   *
   * @param event The event to send to Sentry.
   */
  public captureEvent(event: SentryEvent): void {
    this.invokeClientAsync('captureEvent', event);
  }
 
  /**
   * Records a new breadcrumb which will be attached to future events.
   *
   * Breadcrumbs will be added to subsequent events to provide more context on
   * user's actions prior to an error or crash.
   *
   * @param breadcrumb The breadcrumb to record.
   */
  public addBreadcrumb(breadcrumb: Breadcrumb): void {
    this.invokeClient('addBreadcrumb', breadcrumb);
  }
 
  /**
   * Callback to set context information onto the scope.
   *
   * @param callback Callback function that receives Scope.
   */
  public configureScope(callback: (scope: Scope) => void): void {
    const top = this.getStackTop();
    if (top.client && top.scope) {
      // TODO: freeze flag
      callback(top.scope);
    }
  }
 
  /**
   * This will be called to receive the event
   * @param callback
   */
  public addEventProcessor(callback: () => (event: SentryEvent) => void): void {
    const top = this.getStackTop();
    Eif (top.scope && top.client) {
      top.scope.addEventProcessor(callback());
    }
  }
}