TLD2 API Reference
Table of Contents
Overview
This document provides a complete reference for TLD2's internal APIs, message passing protocol, and developer interfaces. These APIs power communication between the background service worker, content scripts, and sidebar UI.
API Architecture
- Message Passing: chrome.runtime.sendMessage for async component communication
- Content Extraction: Readability.js integration via content script injection
- Summarization: Chrome AI Summarizer API and Gemini API dual-mode
- TTS: StreamingKokoroJS neural synthesis with Web Audio playback
- Storage: chrome.storage.local (settings, cache) and chrome.storage.sync (preferences)
Base URL
chrome-extension://[extension-id]/
All internal APIs use chrome.runtime messaging. No external HTTP endpoints.
Message Passing Protocol
TLD2 uses Chrome's message passing API for communication between extension components.
Protocol Structure
interface Message {
action: string; // Action identifier (e.g., 'extractContent', 'summarize')
data?: any; // Optional payload data
tabId?: number; // Optional tab ID for content script targeting
}
interface Response {
success: boolean; // Operation success status
data?: any; // Response payload
error?: string; // Error message if success = false
}
Sending Messages
// From content script or sidebar to background
const response = await chrome.runtime.sendMessage({
action: 'extractContent',
data: { url: window.location.href }
});
if (response.success) {
console.log('Content:', response.data);
} else {
console.error('Error:', response.error);
}
// From background to specific tab (content script)
const response = await chrome.tabs.sendMessage(tabId, {
action: 'highlightText',
data: { selector: '.article-content' }
});
Message Handlers (Background)
// background/service-worker.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
const { action, data } = message;
// Route to appropriate handler
switch (action) {
case 'extractContent':
handleExtractContent(data, sender.tab.id)
.then(result => sendResponse({ success: true, data: result }))
.catch(error => sendResponse({ success: false, error: error.message }));
return true; // Keep channel open for async response
case 'summarize':
handleSummarize(data)
.then(result => sendResponse({ success: true, data: result }))
.catch(error => sendResponse({ success: false, error: error.message }));
return true;
case 'generateTTS':
handleGenerateTTS(data)
.then(result => sendResponse({ success: true, data: result }))
.catch(error => sendResponse({ success: false, error: error.message }));
return true;
default:
sendResponse({ success: false, error: 'Unknown action' });
}
});
Supported Actions
| Action | Direction | Data | Response |
|---|---|---|---|
extractContent |
Sidebar → Background | { tabId: number } |
{ title, content, url } |
summarize |
Sidebar → Background | { content, length } |
{ summary: string } |
generateTTS |
Sidebar → Background | { text, voice, speed } |
{ audio: Float32Array } |
getSettings |
Any → Background | {} |
{ settings: object } |
updateSettings |
Sidebar → Background | { settings: object } |
{ success: true } |
openSidebar |
Background → System | { tabId: number } |
{ success: true } |
Content Extraction API
Extracts clean article content from web pages using Readability.js.
extractContent(tabId)
Extracts main article content from the specified tab.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tabId |
number | Yes | Chrome tab ID to extract content from |
Returns
interface ExtractedContent {
title: string; // Article title
content: string; // Main text content (plain text)
textContent: string; // Same as content (legacy compatibility)
length: number; // Content length in characters
excerpt: string; // Short excerpt/description
byline?: string; // Author byline
url: string; // Page URL
siteName?: string; // Site name
}
Example
async function extractArticle() {
const [currentTab] = await chrome.tabs.query({
active: true,
currentWindow: true
});
const response = await chrome.runtime.sendMessage({
action: 'extractContent',
data: { tabId: currentTab.id }
});
if (response.success) {
const { title, content, url } = response.data;
console.log(`Extracted "${title}" from ${url}`);
console.log(`Content length: ${content.length} characters`);
return response.data;
} else {
throw new Error(response.error);
}
}
Error Codes
| Error | Description |
|---|---|
NO_TAB |
Invalid or missing tab ID |
NO_CONTENT |
No extractable content found (PDF, image, etc.) |
EXTRACTION_FAILED |
Readability.js parsing failed |
PERMISSION_DENIED |
Cannot access tab (chrome:// or file:// URL) |
Implementation Details
// background/service-worker.js
async function handleExtractContent(data, tabId) {
// Inject Readability.js into tab
await chrome.scripting.executeScript({
target: { tabId: tabId },
files: ['lib/readability/Readability.js']
});
// Execute extraction
const results = await chrome.scripting.executeScript({
target: { tabId: tabId },
func: () => {
// Clone document for Readability
const documentClone = document.cloneNode(true);
const reader = new Readability(documentClone);
const article = reader.parse();
if (!article) {
throw new Error('NO_CONTENT');
}
return {
title: article.title || document.title,
content: article.textContent,
textContent: article.textContent,
length: article.length,
excerpt: article.excerpt,
byline: article.byline,
url: window.location.href,
siteName: article.siteName
};
}
});
return results[0].result;
}
Summarization API
Generates concise summaries using Chrome AI or Gemini API.
summarize(content, options)
Generates a summary of the provided content.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
content |
string | Yes | Article content to summarize |
length |
string | No | 'short' | 'medium' | 'long' (default: 'medium') |
provider |
string | No | 'chrome-ai' | 'gemini' (default: 'chrome-ai') |
geminiApiKey |
string | No | Required if provider = 'gemini' |
Returns
interface SummaryResponse {
summary: string; // Generated summary text
provider: string; // AI provider used ('chrome-ai' or 'gemini')
tokens?: number; // Token count (Gemini only)
model?: string; // Model name
}
Example (Chrome AI)
async function summarizeArticle(content) {
const response = await chrome.runtime.sendMessage({
action: 'summarize',
data: {
content: content,
length: 'medium',
provider: 'chrome-ai'
}
});
if (response.success) {
console.log('Summary:', response.data.summary);
return response.data.summary;
} else {
throw new Error(response.error);
}
}
Example (Gemini API)
async function summarizeWithGemini(content, apiKey) {
const response = await chrome.runtime.sendMessage({
action: 'summarize',
data: {
content: content,
length: 'medium',
provider: 'gemini',
geminiApiKey: apiKey
}
});
if (response.success) {
console.log('Summary from Gemini:', response.data.summary);
console.log('Tokens used:', response.data.tokens);
return response.data;
} else {
throw new Error(response.error);
}
}
Summary Length Guidelines
| Length | Target Words | Best For |
|---|---|---|
short |
50-100 words | Quick overviews, TL;DR |
medium |
150-300 words | Balanced detail (default) |
long |
300-500 words | Comprehensive summaries |
Implementation (Chrome AI)
async function summarizeWithChromeAI(content, length) {
// Check Chrome AI availability
const availability = await ai.summarizer.availability();
if (availability === 'no') {
throw new Error('CHROME_AI_UNAVAILABLE');
}
// Download model if needed
if (availability === 'after-download') {
console.log('Downloading Chrome AI model...');
// Model downloads automatically on first use
}
// Create summarizer
const summarizer = await ai.summarizer.create({
length: length || 'medium',
format: 'plain-text',
type: 'tl;dr'
});
// Generate summary
const summary = await summarizer.summarize(content);
return {
summary: summary,
provider: 'chrome-ai',
model: 'chrome-summarizer-v1'
};
}
Implementation (Gemini API)
async function summarizeWithGemini(content, length, apiKey) {
const lengthPrompts = {
short: 'in 50-100 words',
medium: 'in 150-300 words',
long: 'in 300-500 words'
};
const prompt = `Summarize the following article ${lengthPrompts[length] || lengthPrompts.medium}. Focus on key points and main ideas:\n\n${content}`;
const response = await fetch('https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key=' + apiKey, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{
parts: [{ text: prompt }]
}]
})
});
const data = await response.json();
if (data.error) {
throw new Error(`GEMINI_ERROR: ${data.error.message}`);
}
return {
summary: data.candidates[0].content.parts[0].text,
provider: 'gemini',
tokens: data.usageMetadata?.totalTokenCount,
model: 'gemini-2.0-flash-lite'
};
}
Text-to-Speech API
Generates neural speech audio using StreamingKokoroJS.
generateTTS(text, options)
Synthesizes speech audio from text input.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
text |
string | Yes | Text to synthesize (max 10,000 chars) |
voice |
string | No | Voice ID (default: 'af_sky') |
speed |
number | No | Playback speed 0.5-2.0 (default: 1.0) |
pitch |
number | No | Pitch adjustment 0.5-2.0 (default: 1.0) |
streaming |
boolean | No | Enable chunk streaming (default: false) |
Returns
interface TTSResponse {
audio: Float32Array; // Raw audio samples (24kHz, mono)
duration: number; // Audio duration in seconds
voice: string; // Voice used
sampleRate: number; // Sample rate (24000 Hz)
}
Example (Basic)
async function speakText(text) {
const response = await chrome.runtime.sendMessage({
action: 'generateTTS',
data: {
text: text,
voice: 'af_sky',
speed: 1.0
}
});
if (response.success) {
const { audio, duration } = response.data;
console.log(`Generated ${duration.toFixed(2)}s of audio`);
// Play audio
await playAudioBuffer(audio);
} else {
throw new Error(response.error);
}
}
Example (Streaming)
// For streaming, use port connection instead of sendMessage
const port = chrome.runtime.connect({ name: 'tts-stream' });
port.onMessage.addListener((message) => {
if (message.type === 'audio-chunk') {
// Play chunk immediately
playAudioChunk(message.audio);
} else if (message.type === 'complete') {
console.log('TTS streaming complete');
}
});
port.postMessage({
action: 'streamTTS',
data: {
text: longText,
voice: 'af_sky',
speed: 1.2
}
});
Available Voices
| Voice ID | Gender | Accent | Description |
|---|---|---|---|
af_sky |
Female | American | Clear, professional tone (default) |
af_nicole |
Female | American | Warm, friendly tone |
bm_fable |
Male | British | Authoritative, narrator style |
bm_lewis |
Male | British | Conversational, casual |
Web Audio Playback Helper
async function playAudioBuffer(audioData) {
const audioContext = new AudioContext();
// Create buffer from Float32Array
const audioBuffer = audioContext.createBuffer(
1, // Mono
audioData.length, // Samples
24000 // 24kHz sample rate
);
// Copy audio data
audioBuffer.getChannelData(0).set(audioData);
// Create source and play
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.start();
// Wait for completion
return new Promise(resolve => {
source.onended = resolve;
});
}
Storage API
TLD2 uses chrome.storage.local for settings and cache, and chrome.storage.sync for cross-device preferences.
Storage Schema
interface StorageSchema {
// Settings (chrome.storage.sync)
settings: {
voice: string; // Selected voice ID
speed: number; // Playback speed (0.5-2.0)
pitch: number; // Pitch adjustment (0.5-2.0)
pitchCorrection: boolean; // Auto pitch correction
autoplay: boolean; // Auto-play summaries
summaryLength: string; // 'short' | 'medium' | 'long'
provider: string; // 'chrome-ai' | 'gemini'
geminiApiKey: string; // Gemini API key
theme: string; // 'light' | 'dark' | 'auto'
};
// Cache (chrome.storage.local)
cache: {
[url: string]: {
summary: string;
timestamp: number;
provider: string;
}
};
// TTS model status (chrome.storage.local)
ttsModelLoaded: boolean;
}
getSettings()
async function getSettings() {
const response = await chrome.runtime.sendMessage({
action: 'getSettings'
});
if (response.success) {
return response.data.settings;
} else {
// Return defaults
return {
voice: 'af_sky',
speed: 1.0,
pitch: 1.0,
pitchCorrection: true,
autoplay: true,
summaryLength: 'medium',
provider: 'chrome-ai',
geminiApiKey: '',
theme: 'dark'
};
}
}
updateSettings(settings)
async function updateSettings(newSettings) {
const response = await chrome.runtime.sendMessage({
action: 'updateSettings',
data: { settings: newSettings }
});
if (response.success) {
console.log('Settings saved successfully');
return true;
} else {
throw new Error(response.error);
}
}
// Usage
await updateSettings({
voice: 'bm_fable',
speed: 1.2,
autoplay: false
});
Direct Storage Access
// Read from storage
const data = await chrome.storage.sync.get('settings');
console.log('Current settings:', data.settings);
// Write to storage
await chrome.storage.sync.set({
settings: {
voice: 'af_nicole',
speed: 1.5
}
});
// Listen for changes
chrome.storage.onChanged.addListener((changes, areaName) => {
if (areaName === 'sync' && changes.settings) {
console.log('Settings updated:', changes.settings.newValue);
applyNewSettings(changes.settings.newValue);
}
});
Cache Management
// Save summary to cache
async function cacheSummary(url, summary, provider) {
const cache = await chrome.storage.local.get('cache') || {};
cache[url] = {
summary: summary,
timestamp: Date.now(),
provider: provider
};
await chrome.storage.local.set({ cache: cache });
}
// Retrieve cached summary
async function getCachedSummary(url, maxAge = 86400000) {
const cache = await chrome.storage.local.get('cache') || {};
const cached = cache[url];
if (cached && (Date.now() - cached.timestamp) < maxAge) {
console.log('Using cached summary');
return cached.summary;
}
return null;
}
// Clear old cache entries
async function clearOldCache(maxAge = 604800000) {
const cache = await chrome.storage.local.get('cache') || {};
const now = Date.now();
Object.keys(cache).forEach(url => {
if (now - cache[url].timestamp > maxAge) {
delete cache[url];
}
});
await chrome.storage.local.set({ cache: cache });
console.log('Cache cleared');
}
Settings Management
Default Settings
const DEFAULT_SETTINGS = {
voice: 'af_sky',
speed: 1.0,
pitch: 1.0,
pitchCorrection: true,
autoplay: true,
summaryLength: 'medium',
provider: 'chrome-ai',
geminiApiKey: '',
theme: 'dark'
};
// Initialize settings on first install
chrome.runtime.onInstalled.addListener(async (details) => {
if (details.reason === 'install') {
await chrome.storage.sync.set({ settings: DEFAULT_SETTINGS });
console.log('Default settings initialized');
}
});
Settings Validation
function validateSettings(settings) {
const errors = [];
// Voice validation
const validVoices = ['af_sky', 'af_nicole', 'bm_fable', 'bm_lewis'];
if (!validVoices.includes(settings.voice)) {
errors.push('Invalid voice ID');
}
// Speed validation
if (settings.speed < 0.5 || settings.speed > 2.0) {
errors.push('Speed must be between 0.5 and 2.0');
}
// Pitch validation
if (settings.pitch < 0.5 || settings.pitch > 2.0) {
errors.push('Pitch must be between 0.5 and 2.0');
}
// Summary length validation
const validLengths = ['short', 'medium', 'long'];
if (!validLengths.includes(settings.summaryLength)) {
errors.push('Invalid summary length');
}
// Provider validation
const validProviders = ['chrome-ai', 'gemini'];
if (!validProviders.includes(settings.provider)) {
errors.push('Invalid AI provider');
}
// Gemini API key validation
if (settings.provider === 'gemini' && !settings.geminiApiKey) {
errors.push('Gemini API key required when using Gemini provider');
}
return errors;
}
Error Handling
Error Response Format
interface ErrorResponse {
success: false;
error: string; // Error message
code?: string; // Error code
details?: any; // Additional error details
}
Standard Error Codes
| Code | Description | Resolution |
|---|---|---|
NO_CONTENT |
No extractable content found | Navigate to article page |
CHROME_AI_UNAVAILABLE |
Chrome AI not available | Use Gemini or update Chrome |
GEMINI_ERROR |
Gemini API request failed | Check API key and quota |
TTS_INIT_FAILED |
TTS model initialization failed | Check GPU support, reload extension |
PERMISSION_DENIED |
Cannot access tab | Navigate to accessible page |
INVALID_SETTINGS |
Settings validation failed | Check settings values |
Error Handling Best Practices
async function handleAction(action, data) {
try {
const response = await chrome.runtime.sendMessage({
action: action,
data: data
});
if (response.success) {
return response.data;
} else {
// Handle specific error codes
switch (response.code) {
case 'NO_CONTENT':
showError('No article found on this page');
break;
case 'CHROME_AI_UNAVAILABLE':
showError('Chrome AI unavailable - switching to Gemini');
// Fallback to Gemini
return handleAction(action, { ...data, provider: 'gemini' });
case 'GEMINI_ERROR':
showError('Gemini API error - check your API key');
break;
default:
showError(`Error: ${response.error}`);
}
throw new Error(response.error);
}
} catch (error) {
console.error('Action failed:', error);
showError('An unexpected error occurred');
throw error;
}
}
function showError(message) {
const errorDiv = document.getElementById('error-message');
errorDiv.textContent = message;
errorDiv.style.display = 'block';
setTimeout(() => {
errorDiv.style.display = 'none';
}, 5000);
}