Skip to content

Browser handoff

Browser handoff is papio's institutional-access plane. When an eligible job has exhausted direct acquisition, assisted and delegated access modes can route it to the user's existing browser session. Conservative mode records institutional OpenURL availability without opening a handoff.

Not every handoff requires a login. Some open-access pages refuse non-browser downloads. papio actions list labels each parked job as either “open access — no login needed” or “sign in to your institution first.”

One ordinary browser, not browser automation

papio uses the browser you already use for institutional access. A browser extension opens its own tabs and connects to papio through a small local connector. papio stays in charge of jobs and state; the connector only passes short messages and never owns the queue or stores browser data.

papio never uses an automated or hidden browser. It does not launch a separate browser, run one in the background, copy your cookies, or fill in sign-in forms. Requests go through the browser session you use for institutional access.

flowchart LR
    D["papio<br/>background service"] <-->|"metadata; bounded page captures"| H[Local connector]
    H <-->|local link| E[Browser extension]
    E --> B["Your ordinary browser<br/>papio's own tabs"]
    B --> I["Library resolver<br/>and publisher"]
    E --> A["Download folder<br/>per job"]
    A --> D

The extension tracks only its own tabs. It runs provider-specific code only on sites you grant, notices when you return from your institution's login page without recording that page's address or title, and matches up the job's download. The extension can restart at any time; it keeps only a minimal tab-to-job mapping and asks papio for the authoritative state.

When papio closes its tabs

papio does not keep a tab for a paper it no longer needs. From extension 0.15.0, it closes the tab it opened for a paper in these cases:

  • papio filed the paper. The PDF viewer tab closes too.
  • The paper's job ended in another way, for example it was cancelled.
  • A newer papio tab for the same paper replaced it.
  • The paper parked waiting for you, and papio confirmed that it holds nothing live in that tab.

A tab that a publisher opens from a papio tab, such as a full-text link or a "View PDF" window, belongs to the same paper and closes with it.

papio never closes a tab that you pinned, or a tab that you moved out of papio's work window or tab group. Do one of these to keep a tab. Looking at a tab does not keep it: the tab in front of you stays open while you look at it, and papio closes it on a later pass after you switch away.

A tab inside papio's tab group that papio has no record of, such as a blank New Tab, closes after nobody has used it for 30 minutes, unless it is pinned or in front of you. papio does not close unrecorded tabs anywhere else.

When papio closes the tab of a paper it filed, a toast says "papio filed the paper and closed its tab." (for a batch: "papio filed N papers and closed their tabs."). Choose Reopen to open those papers again. If the browser put the extension to sleep in the meantime, Reopen opens the history page, which lists every filed paper.

PDF viewers that need a manual download

Some providers display a PDF but return HTML when its signed URL is fetched again. In a tab that papio opened for a paper, the browser saves that one response for you. Chrome turns the response into a download into the job's folder. Firefox keeps a copy of the PDF while its viewer shows it, then saves the copy into the job's folder. Neither browser asks the publisher for the file a second time. The daemon validates the saved file before it marks the job ready.

The browser shows papio that response only on a host it may access. When you allow a provider such as ScienceDirect, papio therefore also asks for the host that serves its PDF, https://*.sciencedirectassets.com/*, on the same switch, and removes both together.

If that cannot happen, papio keeps the PDF open and asks you to use the viewer's Download button. This occurs when the PDF opened before papio armed the tab, when papio has no access to the PDF's host, or when the response did not arrive complete. On Chrome, choose Send this PDF first, so that papio can adopt the file. Firefox gives an extension no way to adopt a download it did not start, so on Firefox the button only keeps your own copy. The notice itself does not authorize adoption; Send this PDF binds the current browser document. The daemon still validates the downloaded file before marking its job ready.

With compatible daemon and extension versions, this becomes a manual task and releases browser capacity for queued papers. Older daemons receive a diagnostic only, so they cannot provide this queue recovery.

Handoff surfaces

papio opens its own tabs on one of three surfaces, chosen automatically from your setting and what the browser supports:

  • work window (default) — one dedicated browser window, opened minimized and unfocused;
  • tab group — a collapsed "papio" tab group inside your current window;
  • in-window (legacy) — ordinary visible tabs in your current window.

The work window and the tab group both keep your ordinary tabs free of login and publisher pages; a dedicated work window is reused for later handoffs and reopened if you close it.

Tab-group handoff depends on a browser tab-groups API (chrome.tabGroups/chrome.tabs.group), which the extension detects at runtime rather than gating by browser — it runs on Chrome and on Firefox 139+. The extension's minimum supported Firefox version stays at 128.0, the ESR release many institutions run, so it keeps installing there even though tab groups aren't available yet: on Firefox 128 through 138, a tab-group choice automatically falls back to the work window. A work-window choice falls back further, to in-window tabs, on a browser with no windows API at all.

The extension surfaces the exact work tab only when a human decision is needed:

  • institutional authentication;
  • publisher terms requiring a decision; or
  • identity review.

After that step, a minimized work window or a collapsed tab group returns to the background and papio continues its work. This preserves the one-login-per-research-session model without asking papio to handle passwords, MFA, CAPTCHA tokens, or publisher credentials.

If your institution's sign-in reports a stale session

Institutional sign-ins are time-boxed: if login plus MFA takes long enough, the identity provider can reject the original handoff with a "stale" or "expired request" page. That page is a dead end, not a failure of your session — sign in first, then re-run papio actions open; every open mints a fresh resolver link.

The extension recognizes the common OpenAthens and Shibboleth failure pages itself. When it does, it restores and focuses the work window so the dead page is in front of you rather than hidden, records the outcome on the job's audit trail (papio jobs get <id> shows it), and retries the handoff tab through the resolver on your behalf. Retries share the handoff's authentication budget: after a few, the tab is deliberately left on the failure page and the job stays parked for you, rather than looping the resolver silently. Only the outcome and the identity provider's hostname are reported — never the page's contents.

Chrome and Firefox

The extension works the same way in Chrome and Firefox. papio installs a connector for each browser, and the connector checks each caller: Chrome supplies the configured extension ID; Firefox supplies the configured add-on ID. Leaving a browser's extension ID empty turns off that browser's connection.

The Firefox add-on is published on Firefox Add-ons. Its add-on ID is fixed as papio@orgmentem.com; the Firefox connector is set to allow that ID. Firefox treats host access as opt-in at runtime, so the extension options page includes a resolver-access grant alongside the per-provider grants.

One browser receives handoffs; every browser can still act

With papio installed in more than one browser — say the store extension in your daily browser and a development build in a second profile — exactly one browser receives the offer/handoff flow at a time. The first to connect wins; others wait, visibly, instead of silently taking over:

  • papio browser sessions lists the browser currently receiving handoffs and every waiting session, with extension versions and last-contact times.
  • papio browser use <id> (or --latest) moves handoffs to another browser on demand.
  • Quitting that browser releases the slot immediately; one that stops responding (crashed browser) yields to a live waiting session within about ten seconds.
  • papio doctor reports when other browsers are waiting; papio browser sessions lists them.

That slot decides where papio sends work it starts. It does not decide where you are allowed to act. Anything you initiate yourself — Acquire this page, Send PDF, reading the inbox — works in whichever browser you are actually clicking in, because your click already says which browser you mean. A waiting browser is acknowledged with its own connection and told it is waiting, so its popup stays fully usable rather than refusing everything and pointing you at a terminal command.

Two browsers cannot race a download, but the slot is not what prevents it: a single download authorization is outstanding at a time, so a second attempt is told papio is busy and can be retried, wherever it came from.

When papio asks you to press Download

Some publishers — Silverchair, which serves JAMA and Oxford University Press among others — hand your browser a link that works exactly once. It is signed for the session that opened it, and asking for it a second time returns "Your session has timed out" rather than the file. papio cannot fetch such a link, so it does not try.

Instead it prepares a place for the file and asks you to press the PDF viewer's own Download button. The file is already in your browser; that button writes it out without asking the publisher for anything. papio steers it into the capture it prepared, reads the paper's identity out of the file, and files it — or, when the document does not name itself anywhere papio can see, leaves it in your inbox to identify rather than guessing.

Two consequences worth knowing:

  • The paper you clicked wins over the tab's history. A handoff tab opened for one paper and then reused to read another files the paper you asked about, not the one the tab was opened for.
  • Firefox declines this rather than half-promising it. Firefox gives an extension no way to adopt a download it did not start, so papio says it could not save the PDF and that the viewer's Download button keeps your own copy, instead of asking for a click it could not act on.

The inbox stays current

The inbox refreshes the moment you return to its tab, so a job that finished while you were away is never stale by the time you look. While the tab stays open, it also checks in on its own every so often, so you don't need to leave and come back just to notice a new job land.

The toolbar badge keeps up on the extension's own wake cycle — its existing one-minute keepalive alarm — whether or not a papio tab is open. With nothing open, the browser decides how often a sleeping extension is woken; no design on this transport can beat that.

An auto-refresh never reorders the list while a confirmation dialog is open, an action is still in flight, or a dismissal is still inside its undo window — it waits until you're finished before applying what it learned.

Dismissing an inbox item

Dismissing an inbox item removes it immediately and delays the daemon call for a few seconds behind an Undo bar (keyboard u). Dismissing several rows uses one undo window. The daemon receives the dismissal when that window closes. It cannot undo the dismissal: dismissing an action that parks a job cancels the job, and a cancelled job cannot be retried. The bar identifies whether the dismissal cancelled an acquisition or closed a leftover row. Refreshing or leaving the page commits any pending dismissals.

Browser configuration

[browser] binds each installed browser and defines the default institution:

Key Purpose
extension_id Chrome extension ID allowed to use the connector; empty disables the Chrome bridge.
firefox_extension_id Firefox add-on ID allowed to use the connector; empty disables the Firefox bridge.
openurl_base_url Default institution's HTTPS OpenURL resolver base.
shibboleth_entity_id Optional default IdP entity ID for skipping a provider's WAYF selector.
proquest_account_id Optional default ProQuest account ID for the accountid append.
openathens_domain Optional default OpenAthens organisation domain (go.openathens.net/redirector/<domain>); sends the DOI browser route through the institution's redirector, with the OpenURL route as fallback.
download_adoption_root Root containing the per-job adopted downloads; when empty, papio uses <your download folder>/papio. It must be a papio directory inside the browser's own download directory — steering cannot reach anywhere else.
action_expiry_seconds Browser-offer expiry and the first human-action reminder threshold; later reminders back off per action.

[browser.resolvers.<name>] profiles replace the default institution for a selected job. They carry their own openurl_base_url and optional shibboleth_entity_id, proquest_account_id, openathens_domain, libkey_mode, libkey_library_id, and document_delivery; they never inherit a default identity.

Permissions and data boundary

The extension requests these regular permissions: nativeMessaging, activeTab, tabs, downloads, scripting, and storage for the connector link, tab tracking, and download adoption described above; alarms for the one-minute keepalive wake cycle that refreshes the pending-job count and connection state without a papio tab open; tabGroups for the "papio" tab group the extension uses to gather handoff tabs when tab-group mode is active; and webNavigation so that when you pick which paper a PDF belongs to, papio notices if that tab later navigates somewhere else and lets the choice lapse instead of filing the next document under it. webNavigation reports that a navigation happened and the address it went to; it reads no page content. idle reports only whether you are at the computer (active, idle or locked), with no install warning: papio waits to open a new sign-in tab until you are back, and uses it to notice you returned to a sign-in tab left for a long time. It never closes a sign-in tab because you were away. On Chrome, declarativeNetRequestWithHostAccess lets papio set Content-Disposition: attachment on a signed PDF viewer response, such as a ScienceDirect pdf.sciencedirectassets.com link, so that response downloads into the job's folder instead of opening in the viewer. The session rules name only papio's own handoff tabs and the tabs they open, match only responses whose content type is PDF, and are removed when the job has its download or ends. Chrome applies them only on hosts papio already has access to, and the permission adds no install warning. The rules need Chrome 128 or later; Firefox does not request this permission. On Firefox, webRequest, webRequestBlocking, and webRequestFilterResponse do the same job in a different way, and Chrome does not request them. For the same tabs and the same signed PDF responses, papio reads the response as it arrives, passes every byte to the viewer unchanged, and saves a copy into the job's folder with the downloads permission. The listener returns at once for every other response. It does not change, block, or redirect a request, and Firefox reports responses to it only on hosts papio already has access to. The PDF bytes go only to your download folder.

Host access splits into two tiers. Three host permissions are required and granted at install on Chrome: https://*.alma.exlibrisgroup.com/* and https://*.primo.exlibrisgroup.com/*, used to classify Ex Libris library-resolver pages, and https://login.openathens.net/*, used only to recognize OpenAthens's own stale-session error page and restore the work window (see above). Every provider domain is declared in optional_host_permissions instead, and is granted per source through the extension UI on a user gesture, revocable at any time. That optional tier also includes one broad entry, https://*/*, offered in the options page as a single All sites toggle for people who would rather not approve each provider individually; like every optional grant it is never requested at install and can be revoked at any time. papio does not request <all_urls>, cookies, or debugger, and it requests no host permission for Example University's or any other institution's own login domain. Selecting delegated mode does not grant a browser permission.

The link to the browser carries metadata for ordinary download and handoff frames, plus bounded, sanitized HTML page-capture frames for diagnostics, within papio's fixed message-size limit. Raw PDF bytes, cookies, credentials, screenshots, and secret- or signed-URL values never cross that link. For a selected download, the extension reports metadata such as the download item and final filename; the file itself lands under <download_adoption_root>/<job_id>/ for adoption and validation. Because Chrome's onDeterminingFilename can only rewrite a download to a path relative to the browser's download directory, that root is by construction <your download folder>/papio — which is also the effective default. See Configuration reference for download_adoption_root.

Institution-specific routing

The default [browser] institution can provide an openurl_base_url plus optional shibboleth_entity_id and proquest_account_id. The entity ID lets a provider's login jump straight to your institution's sign-in page instead of stopping at a “Where are you from?” chooser. A ProQuest account ID causes papio to append ?accountid= to the resolver link, which can unlock the institution's ProQuest route.

An openathens_domain adds nine DOI registrant prefixes to the publisher-first routes. PsychiatryOnline 10.1176, ACS 10.1021, and Science 10.1126 already go before the OpenURL route without OpenAthens. The additional prefixes cover publisher pages with measured redirector support: Elsevier 10.1016, T&F 10.1080, SAGE 10.1177, APA PsycNet 10.1037, Wiley 10.1002/10.1111, OUP 10.1093, Cambridge 10.1017, and Nature 10.1038. Other publishers with packaged adapters still start at the OpenURL route.

With an openathens_domain, papio wraps all these publisher-first routes in https://go.openathens.net/redirector/<domain>?url=https://doi.org/<doi>. It builds the URL from the configured domain and the job's validated DOI, never from a page. The redirector is a server redirect: it signs in to the publisher with your institution's identity provider and then opens the article. The extension treats that URL as a hop, not a sign-in page; login.openathens.net, connect.openathens.net and identity-provider pages remain sign-in pages. When the redirector route ends without a PDF, papio moves the job to the OpenURL route in a new attempt. An explicit papio actions retry-publisher uses the redirector too.

For multiple libraries, define [browser.resolvers.<name>] profiles. Every named profile carries its own openurl_base_url and optional shibboleth_entity_id, proquest_account_id, openathens_domain, libkey_mode, libkey_library_id, and document_delivery. A named profile never inherits the default profile's login identity, so a job stays with the institution that was selected for it. The complete key constraints and profile syntax are in the Configuration reference.