All files / src tags.ts

95% Statements 19/20
94.44% Branches 17/18
100% Functions 2/2
95% Lines 19/20

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                                            36x 19x   17x       70x       70x 19x 6x   13x     51x 18x     33x 3x   30x   30x 6x 14x     24x 39x       18x    
/**
 * Rejects NaN / Infinity and circular references in `tags`.
 *
 * `TagsPrimitive` is `number`, so `{ score: Number('oops') }` type-checks.
 * JSON has no literal for those values. Each platform rewrites them
 * differently (`null` on web, `0` on iOS via `NSNumber.intValue`, usually
 * `null` on Android) and identification still succeeds. Throw instead of
 * storing the wrong tag.
 *
 * A circular reference makes this walk recurse forever. Reject it with a
 * clean error instead of letting it blow the call stack.
 *
 * `Date`, `Map`, and class instances are already a type error on `TagsValue`.
 * This walk does not reject them. The RN bridge and JS agent do not agree on
 * those values (a `Date` is an ISO string on web and `{}` on native), but we
 * choose to ignore that.
 *
 * Size is a server `payload_too_large`.
 * https://docs.fingerprint.com/docs/tagging-information
 */
 
export function validateTags(tags?: unknown): void {
  if (tags === null || tags === undefined) {
    return
  }
  walk(tags, 'tags', [])
}
 
function walk(value: unknown, path: string, ancestors: unknown[]): void {
  Iif (value === null || value === undefined) {
    return
  }
 
  if (typeof value === 'number') {
    if (!Number.isFinite(value)) {
      throw new TypeError(`${path} must be a finite number`)
    }
    return
  }
 
  if (typeof value !== 'object') {
    return
  }
 
  if (ancestors.includes(value)) {
    throw new TypeError(`${path} contains a circular reference`)
  }
  ancestors.push(value)
 
  if (Array.isArray(value)) {
    for (const [index, entry] of value.entries()) {
      walk(entry, `${path}[${String(index)}]`, ancestors)
    }
  } else {
    for (const [key, entry] of Object.entries(value)) {
      walk(entry, `${path}['${key}']`, ancestors)
    }
  }
 
  ancestors.pop()
}