Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/zotero/zotero-connectors/llms.txt

Use this file to discover all available pages before exploring further.

Researchers at universities and other institutions typically access academic content through proxy servers — most commonly EZProxy or OpenAthens — that authenticate their institutional access. Without proxy awareness, the Zotero Connector would see proxy-rewritten URLs and fail to match translators to the real journal host, and would be unable to download PDFs that require institutional cookies. The Zotero.Proxies singleton (src/common/proxy.js) solves both problems: it recognises known proxy schemes, transparently redirects direct requests for known hosts through the proxy, and deproxifies URLs before they are passed to translators.

Core Concepts

Zotero.Proxies

The top-level singleton. Manages the list of known proxy configurations, runs detectors on incoming requests, handles transparent redirection, and persists configuration to extension preferences.

Zotero.Proxy

Represents a single proxy configuration. Holds a URL-rewriting scheme (e.g., %h.proxy.university.edu/%p), a compiled regexp, and a list of known proxied hostnames.

Zotero.Proxies.Detectors

A namespace of detector functions that inspect HTTP response headers to identify new proxies — currently EZProxy and OpenAthens.

Zotero.Proxy.OpenAthensProxy

A subclass of Zotero.Proxy specialised for OpenAthens redirector-style proxies, which work differently from URL-rewriting EZProxy schemes.

Proxy Scheme Parameters

URL-rewriting proxies are described by a toProperScheme string that uses parameter placeholders:
ParameterRegexp MatchMeaning
%h([a-zA-Z0-9]+[.\-][a-zA-Z0-9.\-]+)The proxied hostname
%p(.*?)The URL path
%u(.*?)The full URL (used in toProxyScheme login redirect)
For example, the EZProxy host-embedding scheme %h.proxy.university.edu/%p encodes nature.com/articles/s41586 as nature.com.proxy.university.edu/articles/s41586. The compileRegexp() method transforms the scheme string into a RegExp that Zotero.Proxies.proxyToProper() and toProper() use to extract the original hostname and path.

Initialisation: Zotero.Proxies.init()

this.init = async function() {
  if (Zotero.isSafari) return;   // proxy support requires WebRequest APIs
  this.transparent = false;
  this.proxies     = [];
  this.hosts       = {};
  this._redirectedTabIDs = {};
  this._loopPreventionTimestamp = Zotero.Prefs.get('proxies.loopPreventionTimestamp');

  this.openAthensHostRedirectTime =
    await Zotero.Utilities.Connector.createMV3PersistentObject('openAthensRedirectTime');

  this.loadPrefs();   // reads transparent, autoRecognize, disableByDomain
  // ...
  Zotero.Proxies.proxies = Zotero.Prefs.get('proxies.proxies').map(function(proxy) {
    proxy = Zotero.Proxies._createProxyInstance(proxy);
    for (let host of proxy.hosts) { Zotero.Proxies.hosts[host] = proxy; }
    return proxy;
  });

  if (this.transparent) {
    Zotero.Proxies.loadFromClient();  // merge proxies from Zotero desktop
  }
}
If transparent mode is enabled, init() also calls Zotero.Connector.callMethod('proxies', null) to merge any proxies the desktop client already knows about.

Transparent Redirect Mode

When Zotero.Proxies.transparent === true, the extension intercepts page navigations and automatically redirects known hosts through the configured proxy. Two WebExtension API listeners are registered:
  • webRequest.headersReceivedonHeadersReceived(): Runs _recognizeProxy() to detect new proxies from response headers. (Detection only — no redirect, since prefetch requests may not be committed as navigation.)
  • webNavigation.onCommittedonNavigationCommitted(): Handles both host association and transparent redirects, since at this point navigation is confirmed.
this.onNavigationCommitted = (details) => {
  this.checkForRedirectLoop(details);
  Zotero.Proxies.updateDisabledByDomain();
  if (Zotero.Proxies.disabledByDomain) return;

  for (let proxy of Zotero.Proxies.proxies) {
    proxy.maybeAddHost(details);   // learn new hosts for existing proxies
  }
  if (Zotero.Proxies.isPreventingRedirectLoops()) return;

  for (let proxy of Zotero.Proxies.proxies) {
    let redirect = proxy.maybeRedirect(details);
    if (redirect) {
      browser.tabs.update(details.tabId, {url: redirect.redirectUrl});
      break;
    }
  }
}

Proxy Detectors

EZProxy Detector

Zotero.Proxies.Detectors.EZProxy inspects the Server: EZproxy response header and the /login?url= or /login?qurl= query string pattern:
if (details.responseHeadersObject["server"] != "EZproxy") return false;
let loginURI = new URL(details.url);
var m = /(url|qurl)=([^&]+)/i.exec(loginURI.search);
if (loginURI.pathname !== "/login" || !m) return false;
When a redirect to a proxied host is detected, the detector constructs a Zotero.Proxy with:
  • toProperScheme: <proxied-host>.replace(<real-host>, "%h") + "/%p"
  • toProxyScheme: <login-origin>/login?qurl=%u
If no immediate redirect is available, an EZProxy.Listener is attached to headersReceived for that tab to sniff the eventual proxied URL (up to 20 navigation events).

OpenAthens Detector

Zotero.Proxies.Detectors.OpenAthens matches the OpenAthens redirector URL pattern:
var m = /^https?:\/\/go\.openathens\.net\/redirector\/([^?]*)\?url=([^&]+)/i.exec(details.url);
It creates a Zotero.Proxy.OpenAthensProxy with toProxyScheme: https://go.openathens.net/redirector/<name>?url=%u and seeds it with the first detected host.

Redirect Loop Prevention

The connector monitors for redirect loops caused by misconfigured proxies. Three constants control the mechanism:
this.REDIRECT_LOOP_TIMEOUT         = 3600e3;  // 1 hour — how long to suppress redirects
this.REDIRECT_LOOP_MONITOR_COUNT   = 4;        // max redirects tracked per tab
this.REDIRECT_LOOP_MONITOR_TIMEOUT = 5e3;      // 5 seconds — monitoring window
When a redirect occurs, the tab is enrolled in _redirectedTabIDs with the original host and a countdown. checkForRedirectLoop() runs on every onCommitted event:
  • If the browser returns to the original host within the monitoring window → loop detected → toggleRedirectLoopPrevention(true) suspends all proxy redirects for 1 hour.
  • If the countdown or timeout expires without a loop → the tab is unenrolled.
  • History navigation (back button, address bar) dismisses the monitor to avoid false positives.
this.toggleRedirectLoopPrevention = function(value) {
  this._loopPreventionTimestamp = value ? (Date.now() + this.REDIRECT_LOOP_TIMEOUT) : 0;
  Zotero.Prefs.set('proxies.loopPreventionTimestamp', this._loopPreventionTimestamp);
}

OpenAthens Redirect Interval

OpenAthens proxies must periodically re-authenticate to ensure the user’s institutional session remains valid. The connector re-redirects through OpenAthens once per:
const OPENATHENS_REDIRECT_INTERVAL = 12 * 60 * 60 * 1000;  // 12 hours
The last redirect time for each host is tracked in the MV3-persistent openAthensHostRedirectTime object. If less than 12 hours have elapsed since the last redirect for a given host, maybeRedirect() returns without acting.

Proxy CRUD: save() and remove()

Zotero.Proxies.save(proxy)

Upserts a proxy into Zotero.Proxies.proxies and Zotero.Proxies.hosts:
this.save = function(proxy) {
  let instance = Zotero.Proxies._createProxyInstance(proxy);
  // Trim whitespace from scheme and hosts
  // Drop extra hosts if scheme has no %h or %u (single-host proxies)
  // Upsert into proxies array
  // Update hosts map
  Zotero.Proxies.storeProxies();  // persist to Zotero.Prefs
}
Single-host proxies (those without %h or %u in toProperScheme) are automatically trimmed to a single entry in hosts.

Zotero.Proxies.remove(proxy)

Removes a proxy by ID from the list and clears its entries from the hosts map, then persists the updated list.

Zotero.Proxies.validate(proxy)

Validates a proxy configuration before saving. Returns a localisation key array on error or false on success. Common validation errors:
Error KeyCause
proxy_validate_hostProxyExistsAnother proxy already maps this host
proxy_validate_schemeDuplicateIdentical toProxyScheme already exists
proxy_validate_schemeUnmodifiedDefault placeholder scheme not modified
proxy_validate_schemeInvalidScheme is too short or trivial (e.g., %h/%p)
proxy_validate_schemeNoPathScheme lacks a %p path parameter

Host Blacklist and Whitelist

The connector refuses to automatically proxy certain well-known hosts to prevent EZProxy from silently proxying all Google and Wikipedia traffic:
const hostBlacklist = [
  /edu$/,
  /doi\.org$/,
  /google\.com$/,
  /wikipedia\.org$/,
  /^[^.]*$/,
  /doubleclick\.net$/,
  /^eutils.ncbi.nlm.nih.gov$/
];

const hostWhitelist = [
  /^scholar\.google\.com$/,
  /^muse\.jhu\.edu$/,
  /^(www\.)?journals\.uchicago\.edu$/
];
Whitelist entries override the blacklist, so scholar.google.com is still eligible for proxying even though google.com is blacklisted.

URL Deproxification: getPotentialProxies()

Before running translator detection, getWebTranslatorsForLocation() calls Zotero.Proxies.getPotentialProxies(url) to build a map of candidate deproxified URLs. For EZProxy subdomain schemes, it also applies heuristics:
  • Drops a 0- prefix (used by some III EZProxy deployments).
  • Tests hostname parts against the TLD list — when a part is a known TLD, everything preceding it is treated as the original hostname.
  • For HTTPS URLs, also replaces hyphens with dots to handle EZProxy’s HttpsHyphens mode (e.g., nature-com.proxy.edunature.com).
The returned { deproxifiedURL → proxyJSON | null } map lets translators match against the real host even when the browser is currently on a proxied URL.

Build docs developers (and LLMs) love