All files / lib/core/runtime AvenxGuard.js

100% Statements 138/138
70% Branches 7/10
100% Functions 6/6
100% Lines 138/138

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 13947x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 53x 53x 53x 53x 53x 53x 53x 53x 53x 53x 53x 53x 53x 53x 53x 53x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 15x 15x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 31x 31x 31x 31x 31x 31x 47x 47x 47x 47x 47x 47x 2x 2x 47x 47x 47x 47x 47x 47x 47x 10x 10x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 1x 1x 47x  
/**
 * @file AvenxGuard.js
 * @description The base class for route guards, and the context they receive.
 *
 * ## Why a guard has a context
 *
 * A guard used to receive nothing. It answered `canActivate(to, from)` from the
 * route alone, and anything else it needed had to be reached through whatever
 * happened to be in lexical scope. For a guard compiled into the application
 * bundle that was an accident of concatenation, and when the compiler stopped
 * emitting a binding the guard's identifier silently became `undefined`.
 *
 * That left the ordinary case unwritable. "Is this visitor signed in?" is the
 * reason route guards exist, and the answer lives in a bridge — which a guard
 * had no supported way to read: it received no injection, and the template
 * sandbox refuses `window`. The only decisions a guard could make were the ones
 * the URL already carried.
 *
 * The context makes that dependency explicit rather than lexical. A guard is
 * handed the framework-level capabilities it is allowed to use, by the router
 * that invoked it, at the moment it runs.
 *
 * ## What the context deliberately does not carry
 *
 * Browser globals. The context exposes the application's bridges and the
 * router that is asking; it is not an escape hatch to `document`,
 * `localStorage` or `fetch`. A guard that genuinely needs a browser API should
 * read it through a bridge, which is the same rule the rest of the framework
 * follows and the reason a bridge read is traceable at all.
 * @module lib/core/runtime/AvenxGuard
 */
 
/**
 * The capabilities a guard is given when it runs.
 *
 * Constructed per navigation by the router. Nothing here is mutable state: the
 * bridges are the live reactive objects the rest of the application uses, so a
 * guard reading `context.bridge('session').signedIn` sees exactly what a
 * component reading the same bridge sees.
 */
export class GuardContext {
  /**
   * @param {object} [options] - Context sources.
   * @param {object} [options.bridges] - The application's bridge registry.
   * @param {object} [options.app] - The application instance.
   * @param {object} [options.router] - The router performing the navigation.
   */
  constructor({ bridges = {}, app = null, router = null } = {}) {
    /**
     * Registered bridges, by name.
     * @type {object}
     */
    this.bridges = bridges;
    /**
     * The application instance.
     * @type {object|null}
     */
    this.app = app;
    /**
     * The router performing this navigation.
     * @type {object|null}
     */
    this.router = router;
  }
 
  /**
   * Looks a bridge up by name.
   *
   * Returns `undefined` rather than throwing for an unknown name, so a guard
   * can decide what an absent bridge means. A guard that imports its bridge
   * directly does not need this; it exists for guards registered as plain
   * functions, and for looking a bridge up dynamically.
   * @param {string} name - The registered bridge name.
   * @returns {any} The bridge, or undefined when it is not registered.
   */
  bridge(name) {
    return this.bridges ? this.bridges[name] : undefined;
  }
}
 
/**
 * Base class for all route guards in Avenx.
 * Guards determine if a route transition should proceed, abort, or redirect.
 */
export class AvenxGuard {
  /**
   * Guards are constructed by the router, once per navigation, with the
   * context for that navigation.
   *
   * Subclasses that declare no constructor get this one, so an existing guard
   * written before contexts existed keeps working unchanged and simply gains
   * `this.$context`.
   * @param {GuardContext} [context] - The capabilities for this navigation.
   */
  constructor(context = new GuardContext()) {
    /**
     * The capabilities this guard was given.
     * @type {GuardContext}
     */
    this.$context = context;
  }
 
  /**
   * The application's bridges, by name.
   * @returns {object} The bridge registry.
   */
  get $bridges() {
    return this.$context ? this.$context.bridges : {};
  }
 
  /**
   * Looks a bridge up by name.
   * @param {string} name - The registered bridge name.
   * @returns {any} The bridge, or undefined.
   */
  $bridge(name) {
    return this.$context ? this.$context.bridge(name) : undefined;
  }
 
  /**
   * Determines whether the route can be activated.
   * Can return a boolean (true to allow, false to abort), a string (to redirect),
   * a custom control object (e.g. { cancel: true, silent: true } or { redirect: string, state?: object }),
   * or a Promise resolving to any of these.
   * @param {object} [to] - The target route.
   * @param {object} [from] - The route being left.
   * @param {GuardContext} [context] - The capabilities for this navigation, also
   *   available as `this.$context`. Passed as a third argument so that a guard
   *   registered as a plain function can reach it too.
   * @returns {boolean|string|object|Promise<boolean|string|object>} The decision.
   */
  // The parameters document what a subclass receives; the base implementation
  // admits every navigation and reads none of them.
  // eslint-disable-next-line no-unused-vars
  canActivate(to, from, context) {
    return true;
  }
}