RDA Annotator is a cross-browser extension for creating and viewing annotations in web resources. It works on both HTML pages and PDF documents using a robust text anchoring system.
- Create annotations on web pages and PDFs
- View existing annotations with visual highlights
- Multi-frame support for complex pages with iframes
- Keycloak authentication with ORCID
- Elasticsearch backend for annotation storage and search
- Node.js 18 or higher
- pnpm package manager
# Install dependencies
pnpm install
# Configure environment
cp .env.example .env
# Edit .env with your Keycloak and API settings
# Start development server
pnpm devCreate a .env file with these required variables:
# Keycloak authentication
HOST_PERMISSION=https://keycloak.example.com/*
WXT_KEYCLOAK_URL=https://keycloak.example.com/realms/rda
WXT_KEYCLOAK_CLIENT_ID=rda-tiger-extension
# Backend API
WXT_API_ENDPOINT=http://localhost:3000
# Other configuration
WXT_KNOWLEDGE_BASE_URL=https://kb-rda.org
WXT_ANNOTATOR_VERSION=1.1.6Keycloak - OAuth2/PKCE authentication provider. Handles user login and token management for ORCID.
Elasticsearch - Backend for annotation storage and search. Annotations are stored as documents with selectors that describe their location.
PDF.js - Custom PDF viewer that allows annotations on PDF documents. The extension redirects PDF URLs to a custom viewer.
@webext-core/messaging - Type-safe message passing between extension contexts. Provides compile-time safety for cross-context communication.
entrypoints/ Extension entry points
background.ts Service worker managing extension state and PDF redirection
content/ Content scripts injected into web pages
sidebar/ React application for the sidebar UI
components/ Reusable React UI components
views/ Page components for sidebar routing
utils/ Business logic and utilities
anchoring/ Text and PDF anchoring algorithms
datasources/ RDA vocabulary fetchers
context/ React Context providers
types/ TypeScript interfaces
public/ Static assets
The extension runs in three separate JavaScript contexts that cannot directly share memory or call functions:
1. Background Service Worker (entrypoints/background.ts)
Manages extension state and handles PDF URL interception. When the extension is enabled and a user navigates to a PDF, the background script redirects to the custom PDF.js viewer. This context has access to browser tabs and storage APIs.
2. Content Scripts (entrypoints/content/)
Injected into every web page. Content scripts load annotations from Elasticsearch, anchor them to the DOM, and render visual highlights. They handle user interactions like clicking highlights and creating new annotations.
3. Sidebar (entrypoints/sidebar/)
A React application loaded in an iframe on the side of the page. The sidebar displays annotation details, provides a form for creating annotations, and manages user settings. It communicates with content scripts only via message passing.
Web pages often contain nested iframes. The extension handles this with a host-guest architecture:
- The top-level frame is the "host" and manages the sidebar
- Nested iframes are "guests" that detect annotations in their own DOM
- Guest frames send messages to the host when users interact with highlights
- The host forwards messages to the sidebar
This approach works even when iframes load content from different domains.
Annotations are stored as abstract "selectors" that describe where the annotation should appear. When loading a page, the extension converts these selectors into concrete DOM ranges.
The system uses multiple strategies:
- TextQuoteSelector - Matches exact text with context
- TextPositionSelector - Character offsets from document start
- RangeSelector - XPath to DOM nodes
The extension tries each strategy in order until one succeeds. If all strategies fail, the annotation is marked as "orphaned" and shown in a separate section.
The extension cannot annotate native PDF viewers because they don't expose the DOM. Instead:
- Background script intercepts PDF URLs when extension is enabled
- Redirects to custom PDF.js viewer:
pdfjs/web/viewer.html?file=[pdfUrl] - PDF.js renders the PDF into HTML canvas and text layers
- Annotations use page numbers and bounding boxes for positioning
- Content script translates these to visual highlights
Users authenticate with Keycloak using the OAuth2 PKCE flow. The authentication provider:
- Opens Keycloak ORCID login in a new window
- Exchanges authorization code for access and refresh tokens
- Stores tokens in extension storage (encrypted by browser)
- Automatically refreshes access tokens 30 seconds before expiry
- Provides authentication state to the sidebar via React Context
The project uses strict TypeScript. Run type checking before committing:
pnpm compileAt this point in time there is no test framework configured. Type checking and manual testing are the primary quality assurance methods.
Build for production:
# Build for Chrome/Edge
pnpm build
pnpm zip
# Build for Firefox
pnpm build:firefox
pnpm zip:firefoxThe build process creates a .zip file ready for distribution.
- WXT provides hot module replacement. Changes to code automatically reload the extension.
- Check the browser's developer tools for each context separately:
- Background: Right-click extension icon → Manage Extension → Service Worker
- Content script: Regular page DevTools console
- Sidebar: Right-click sidebar → Inspect
- Console logs use prefixes like
[RDA Background]and[RDA Content]to identify the context
Primary: Chrome and Edge (Chromium-based browsers)
The extension is built and tested primarily on Chromium browsers.
Secondary: Firefox
Firefox requires separate testing as there could be edge cases that are unsupported.
For detailed information about the architecture and components:
- Architecture Overview - System design and data flow
- Message Passing - Type-safe communication patterns
- Anchoring System - How text anchoring works
- Multi-Frame Coordination - Supporting complex pages with iframes
- AnnotationManager Component - Core annotation lifecycle management
- Highlighter Component - Visual highlight rendering
- Authentication - Keycloak OAuth flow
When contributing to this project:
- Run
pnpm compilebefore committing. - Use the existing code organization patterns. UI code belongs in
components/orviews/, business logic inutils/. - Follow the message passing protocol defined in
utils/messaging.ts. Add new messages by extending theProtocolMapinterface. - Test in both Chrome and Firefox if your changes affect extension APIs.
MIT