All files / ui index.ts

75% Statements 30/40
77.08% Branches 37/48
72.72% Functions 8/11
76.31% Lines 29/38

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 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                                    1x 21x 21x       21x       21x           21x                     21x                     21x                     21x                                                                                         45x                                                         1x               8x 216x   8x   72x   72x 72x 54x       72x 72x   54x 18x     54x 18x         72x   72x 72x     72x       72x      
import type {
  UiNode,
  UiNodeAnchorAttributes,
  UiNodeAttributes,
  UiNodeGroupEnum,
  UiNodeImageAttributes,
  UiNodeInputAttributes,
  UiNodeInputAttributesTypeEnum,
  UiNodeScriptAttributes,
  UiNodeTextAttributes,
} from "@ory/client"
 
/**
 * Returns the node's label.
 *
 * @param node
 * @return label
 */
export const getNodeLabel = (node: UiNode): string => {
  const attributes = node.attributes
  Iif (isUiNodeAnchorAttributes(attributes)) {
    return attributes.title.text
  }
 
  Iif (isUiNodeImageAttributes(attributes)) {
    return node.meta.label?.text || ""
  }
 
  Iif (isUiNodeInputAttributes(attributes)) {
    if (attributes.label?.text) {
      return attributes.label.text
    }
  }
 
  return node.meta.label?.text || ""
}
 
/**
 * A TypeScript type guard for nodes of the type <a>
 *
 * @param attrs
 */
export function isUiNodeAnchorAttributes(
  attrs: UiNodeAttributes,
): attrs is UiNodeAnchorAttributes & { node_type: "a" } {
  return attrs.node_type === "a"
}
 
/**
 * A TypeScript type guard for nodes of the type <img>
 *
 * @param attrs
 */
export function isUiNodeImageAttributes(
  attrs: UiNodeAttributes,
): attrs is UiNodeImageAttributes & { node_type: "img" } {
  return attrs.node_type === "img"
}
 
/**
 * A TypeScript type guard for nodes of the type <input>
 *
 * @param attrs
 */
export function isUiNodeInputAttributes(
  attrs: UiNodeAttributes,
): attrs is UiNodeInputAttributes & { node_type: "input" } {
  return attrs.node_type === "input"
}
 
/**
 * A TypeScript type guard for nodes of the type <span>{text}</span>
 *
 * @param attrs
 */
export function isUiNodeTextAttributes(
  attrs: UiNodeAttributes,
): attrs is UiNodeTextAttributes & { node_type: "text" } {
  return attrs.node_type === "text"
}
 
/**
 * A TypeScript type guard for nodes of the type <script>
 *
 * @param attrs
 */
export function isUiNodeScriptAttributes(
  attrs: UiNodeAttributes,
): attrs is UiNodeScriptAttributes & { node_type: "script" } {
  return attrs.node_type === "script"
}
 
/**
 * Returns a node's ID.
 *
 * @param attributes
 */
export function getNodeId({ attributes }: UiNode) {
  if (isUiNodeInputAttributes(attributes)) {
    return attributes.name
  } else {
    return attributes.id
  }
}
 
/**
 * Return the node input attribute type
 * In <input> elements we have a variety of types, such as text, password, email, etc.
 * When the attribute is null or the `type` attribute is not present, we assume it has no defined type.
 * @param attr
 * @returns type of node
 */
export const getNodeInputType = (attr: any): string => attr?.["type"] ?? ""
 
export type FilterNodesByGroups = {
  nodes: Array<UiNode>
  groups?: Array<UiNodeGroupEnum | string> | UiNodeGroupEnum | string
  withoutDefaultGroup?: boolean
  attributes?:
    | Array<UiNodeInputAttributesTypeEnum | string>
    | UiNodeInputAttributesTypeEnum
    | string
  withoutDefaultAttributes?: boolean
  excludeAttributes?:
    | Array<UiNodeInputAttributesTypeEnum | string>
    | UiNodeInputAttributesTypeEnum
    | string
}
 
/**
 * Filters nodes by their groups and attributes.
 * If no filtering options are specified, all nodes are returned.
 * Will always add default nodes unless `withoutDefaultGroup` is true.
 * Will always add default attributes unless `withoutDefaultAttributes` is true.
 * @param {Object} filterNodesByGroups - An object containing the nodes and the filtering options.
 * @param {Array<UiNode>} filterNodesByGroups.nodes - An array of nodes.
 * @param {Array<UiNodeGroupEnum | string> | string} filterNodesByGroups.groups - An array or comma seperated strings of groups to filter by.
 * @param {boolean} filterNodesByGroups.withoutDefaultGroup - If true, will not add default nodes under the 'default' category.
 * @param {Array<UiNodeInputAttributesTypeEnum | string> | string} filterNodesByGroups.attributes - An array or comma seperated strings of attributes to filter by.
 * @param {boolean} filterNodesByGroups.withoutDefaultAttributes - If true, will not add default attributes such as 'hidden' and 'script'.
 */
export const filterNodesByGroups = ({
  nodes,
  groups,
  withoutDefaultGroup,
  attributes,
  withoutDefaultAttributes,
  excludeAttributes,
}: FilterNodesByGroups) => {
  const search = (s: Array<string> | string) =>
    typeof s === "string" ? s.split(",") : s
 
  return nodes.filter(({ group, attributes: attr }) => {
    // if we have not specified any group or attribute filters, return all nodes
    Iif (!groups && !attributes && !excludeAttributes) return true
 
    const g = search(groups) || []
    if (!withoutDefaultGroup) {
      g.push("default")
    }
 
    // filter the attributes
    const a = search(attributes) || []
    if (!withoutDefaultAttributes) {
      // always add hidden fields e.g. csrf
      if (group.includes("default")) {
        a.push("hidden")
      }
      // automatically add the necessary fields for webauthn and totp
      if (group.includes("webauthn") || group.includes("totp")) {
        a.push("input", "script")
      }
    }
 
    // filter the attributes to exclude
    const ea = search(excludeAttributes) || []
 
    const filterGroup = groups ? g.includes(group) : true
    const filterAttributes = attributes
      ? a.includes(getNodeInputType(attr))
      : true
    const filterExcludeAttributes = excludeAttributes
      ? !ea.includes(getNodeInputType(attr))
      : true
 
    return filterGroup && filterAttributes && filterExcludeAttributes
  })
}