santifer/career-ops · error · Error

senjob: invalid URL: ${url}

Error message

senjob: invalid URL: ${url}

What it means

assertSenjobUrl parses its argument with new URL(); malformed strings throw SyntaxError, which is caught and rethrown as this error prefixed with the senjob provider tag. It runs before every listing-page fetch, guaranteeing the provider never requests an unparseable URL.

Source

Thrown at providers/senjob.mjs:77

const POSTING_ANCHOR_RE =
  /<a\s[^>]*href="https:\/\/senjob\.com\/jobseekers\/[^"]*?_e_\d+\.html"[^>]*>([\s\S]*?)<\/a>/i;

/** The machine-readable publication date, hidden next to its localized form. */
const HIDDEN_ISO_DATE_RE = /display:\s*none;?\s*"?>\s*(\d{4}-\d{2}-\d{2})\s*</i;

/** @param {any} ctx @param {number} ms */
function sleep(ctx, ms) {
  if (typeof ctx?.sleep === 'function') return ctx.sleep(ms);
  return new Promise((r) => setTimeout(r, ms));
}

/** @param {string} url */
function assertSenjobUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`senjob: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`senjob: URL must use HTTPS: ${url}`);
  if (parsed.hostname !== TRUSTED_HOST) {
    throw new Error(`senjob: untrusted hostname "${parsed.hostname}" — must be ${TRUSTED_HOST}`);
  }
  return url;
}

/**
 * Collapse a markup fragment to its visible text.
 * Comments are stripped FIRST: the anchor bodies carry `<!-- d ico postulez -->`
 * between the title and a spacer image, and a naive tag strip would leave the
 * comment body sitting inside the title.
 * @param {string} fragment
 * @returns {string}
 */
export function visibleText(fragment) {
  return decodeEntities(

View on GitHub (pinned to 1696bec4d0)

Solutions

  1. Pass only URLs built by buildListUrl(page), which always yields the absolute https://senjob.com/offres-d-emploi.php form
  2. If calling directly, ensure the input is a fully-qualified absolute URL including the https:// scheme
  3. Trim/normalize any user- or config-supplied string before validation

Example fix

// before
assertSenjobUrl(entry.careers_url ?? 'senjob.com/offres-d-emploi.php');
// after
assertSenjobUrl(buildListUrl(1)); // https://senjob.com/offres-d-emploi.php
Defensive patterns

Strategy: validation

Validate before calling

function isValidAbsoluteUrl(url) {
  try { new URL(url); return true; } catch { return false; }
}
if (!isValidAbsoluteUrl(candidate)) throw new Error(`senjob: not an absolute URL: ${candidate}`);

Type guard

function isValidAbsoluteUrl(url) {
  try { new URL(url); return typeof url === 'string' && url.length > 0; } catch { return false; }
}

Try / catch

try {
  assertSenjobUrl(url);
} catch (err) {
  if (String(err.message).startsWith('senjob: invalid URL')) {
    console.error(`Rejected malformed URL: ${err.message}`);
    return null;
  }
  throw err;
}

Prevention

When it happens

Trigger: A string reaching the assertion that new URL() rejects: empty string, missing scheme ('senjob.com/offres-d-emploi.php'), whitespace/newlines, or a corrupted buildListUrl result. With the fixed LIST_URL constant this is only reachable via direct calls or edits to buildListUrl/entry-driven URL construction.

Common situations: Calling assertSenjobUrl from custom tooling with a configured (unvalidated) URL; someone changing LIST_URL to a relative path or templated placeholder; string concatenation injecting spaces.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@1696bec4d0 (2026-09-01). Data as JSON: /api/errors/2f4fe4d45b4f3e58. Report an issue: GitHub.