Адресные книги

Address Book Architecture - Main Classes and Data Flow

Based on the project data, here's a comprehensive outline of the main classes and data flow for the address book system:

1. Core Layer - Mozilla Address Book Manager

Main Class: @mozilla.org/abmanager;1 Service

The contract ID @mozilla.org/abmanager;1 is widely used throughout the project to obtain the nsIAbManager service. This contract ID should indeed point to the Mozilla Address Book Manager.

From the search results, I found that this contract ID is used in multiple files:

Key files using @mozilla.org/abmanager;1:

[desktopmail]/r7organizer/Application/Proxies/AddressBook/AddressBook.ts

[desktopmail]/r7organizer/mailnews/addrbook/src/nsAbAddressCollector.cpp

[desktopmail]/r7organizer/mailnews/addrbook/src/nsAbCardProperty.cpp

[desktopmail]/r7organizer/mailnews/addrbook/src/nsAbDirProperty.cpp

And many others

The contract ID @mozilla.org/abmanager;1 typically points to the nsIAbManager interface, which is the core Mozilla Address Book Manager interface.

The most likely correct implementation would be:

Interface: nsIAbManager - This is the XPCOM interface that defines the Address Book Manager API

Implementation: Usually named something like nsAbManager or similar (though I didn't find the exact implementation file in the search results)

In Mozilla/Thunderbird codebases, the @mozilla.org/abmanager;1 contract ID is registered to point to the class that implements the nsIAbManager interface. This class would be responsible for:

Managing address books

Creating, deleting, and modifying address books

Handling directory operations

Managing contacts and mailing lists

File: [desktopmail]/r7organizer/Application/Proxies/AddressBook/AddressBook.ts

Role: Central orchestrator for all address book operations

Key Properties and Methods:

// Core service access const addressBookManager = Cc['@mozilla.org/abmanager;1'].getService(); // Primary operations addressBookManager.directories // Access all address books addressBookManager.deleteAddressBook(uri) // Remove address books

Integration Points:

Direct access to Mozilla's XPCOM address book service

Provides directory enumeration and management

Foundation for all address book operations

2. Data Model Classes

Directory Class

File: [desktopmail]/r7organizer/Application/Proxies/AddressBook/types.ts

export type Directory = { // Core Identification UID: string; // Internal unique identifier URI: string; // Unique address book identifier dirPrefId: string; // Preference identifier // Content Information childCardCount: number; // Number of contacts description: string | null; dirName: string; // Display name fileName: string; // Storage filename // Type and Configuration dirType: number; // Storage type (JS, CardDAV, LDAP, etc.) isMailList: boolean; isRemote: boolean; // Remote/local flag isSecure: boolean; // Security flags readOnly: boolean; supportsMailingLists: boolean; // Metadata lastModifiedDate: number; listNickName: string | null; position: number; // Corporate Extensions (R7 specific) fromCS?: boolean; // Corporate sync flag userIdCS?: string; // Corporate user ID }

Address Book Proxy Layer

File: [desktopmail]/r7organizer/Application/Proxies/AddressBook/AddressBook.ts

Main Functions:

// Retrieve all directories with selected properties async function getAllDirectories(): Promise { const addressBookManager = Cc['@mozilla.org/abmanager;1'].getService(); const addressBooks = addressBookManager.directories; // Extract 16 key properties including corporate extensions const pickedProperties = [ 'UID', 'URI', 'childCardCount', 'description', 'dirName', 'dirPrefId', 'dirType', 'fileName', 'isMailList', 'isRemote', 'isSecure', 'lastModifiedDate', 'listNickName', 'position', 'readOnly', 'supportsMailingLists' ]; // Add corporate metadata addressBooks.map((book) => ({ ...pickedProperties, fromCS: book.getBoolValue('cs.isCorporate', false), userIdCS: book.getStringValue('cs.userId', null) })); } // Remove address book by URI async function remove(uri: string): Promise { const addressBookManager = Cc['@mozilla.org/abmanager;1'].getService(); addressBookManager.deleteAddressBook(uri); }

3. Storage Layer Classes

Address Book Storage Types

Based on dirType field in Directory class:

JS_DIRECTORY_TYPE - Local JavaScript-based storage

Purpose: Local file-based address books

Storage: Files on local filesystem

Access: Direct JavaScript APIs

CARDDAV_DIRECTORY_TYPE - Remote CardDAV servers

Purpose: Remote corporate directory management

Storage: Remote CardDAV servers

Access: HTTP/WebDAV protocols

Integration: CardDAVUtils, CardDAVDirectory

LDAP_DIRECTORY_TYPE - LDAP directory services

Purpose: Enterprise LDAP directories

Storage: LDAP servers

Access: LDAP protocol

ASYNC_DIRECTORY_TYPE - Asynchronous operations

Purpose: Non-blocking operations

Storage: Various async backends

Storage Integration Components

File: [desktopmail]/r7organizer/mail/components/addrbook/content/aboutAddressBook.js

// CardDAV Integration XPCOMUtils.defineLazyModuleGetters(this, { CardDAVUtils: "resource:///modules/CardDAVUtils.jsm", CardDAVDirectory: "resource:///modules/CardDAVDirectory.jsm", }); // vCard Processing XPCOMUtils.defineLazyModuleGetters(this, { VCardProperties: "resource:///modules/VCardUtils.jsm", VCardPropertyEntry: "resource:///modules/VCardUtils.jsm", ICAL: "resource:///modules/calendar/Ical.jsm", });

MailServices Integration

Primary Interface:

Directory management

Contact operations

Search functionality

Event handling

4. Corporate Sync Layer Classes

Core Sync Controller

File: [desktopmail]/r7organizer/mail/components/addrbook/content/aboutAddressBook.js

Main Sync Function: check(user)

async function check(user) { // Fetch from multiple CS API endpoints in parallel const [topLevelBooksResult, personalGroupsResult] = await Promise.allSettled([ withUnauthorizedRetryStrategy('v1/addressbook', { method: 'GET' }, user.userId), withUnauthorizedRetryStrategy('v1/Groups?Type=15', { method: 'GET' }, user.userId), ]); // Normalize and unify data from different sources const unified = [...topLevelBooks, ...personalGroups].map(book => ({ Id: book.Id, Name: book.Type === 0 ? "Личные контакты" : book.Name, rawUrl: book.CardUrl || book.Url, canShare: book.EntityType === 15 || hasGroupUrl })); // Resolve CardDAV URLs and filter const csBooks = unified .map(book => ({ ...book, resolvedUrl: resolveCardDavBookUrl(book.rawUrl, user), })) .filter(book => book.resolvedUrl); }

Corporate Authentication Management

// Ensure credentials are stored for CardDAV auth function ensureCSCredentials(url, user) { // Store credentials in Mozilla Login Manager console.log('r7ig ensureCSCredentials - user', user, url); } // Set corporate metadata on address books function setCSBookMetadata(book, user, csEntityId, csCanShare, csBookName) { // Mark book as corporate and store metadata book.setStringValue('cs.userId', user.userId); book.setBoolValue('cs.isCorporate', true); book.setStringValue('cs.entityId', csEntityId); book.setBoolValue('cs.canShare', csCanShare); book.setStringValue('cs.bookName', csBookName); }

Corporate Sync Orchestrator

Function: loadCSAB()

async function loadCSAB() { // Prevent concurrent sync operations with locking if (this._csabRunning) { this._csabQueued = true; return; } this._csabRunning = true; try { // Sync for all configured R7 CS users for (const user of window.r7.stores.$allR7CSUsers) { await this.check(user); } } finally { this._csabRunning = false; if (this._csabQueued) { this._csabQueued = false; this.loadCSAB(); // Process queued sync } } }

Corporate Sharing Management

File: [desktopmail]/r7organizer/mail/components/addrbook/content/r7AddressBookSharingDialog.js

window.onload = async () => { const [dialogArgs] = window?.arguments || []; window.__qwe__ = { USER_ID: dialogArgs?.userId ?? null, ENTITY_ID: dialogArgs?.entityId ?? null, }; // Load Vue3 framework and R7 components await import("chrome://messenger/content/vendor.js"); await import("chrome://messenger/content/r7-addons/r7AddressBookSharingDialog.js"); await import("chrome://messenger/content/r7-addons/store.js"); };

URL Resolution Service

function resolveCardDavBookUrl(rawUrl, user) { // Normalize potentially relative CardDAV URLs using user's domain // Strip /api/ prefix, handle relative paths // Return fully qualified CardDAV URL or null }

5. UI Layer Classes

Main Address Book Interface Controller

File: [desktopmail]/r7organizer/mail/components/addrbook/content/aboutAddressBook.js

Core UI Components Classes

AbTreeListbox Class

// Custom element for address book tree structure class AbTreeListbox extends tree-listbox { // Tree rendering and management _createCsBookGroup(userEmail, books) { // Group multiple corporate address books under user email } deleteSelected() { // Handle deletion of selected books with CS API integration if (selectedBook.fromCS) { // Call CS API for corporate book deletion } } }

AbCardSearchInput Class

// Custom search input with debounced functionality class AbCardSearchInput { // Real-time contact search with debouncing }

AbCardRow/AbTableCardRow Classes

// Custom row elements for contact display class AbCardRow { // Renders contact avatar, name, and email } class AbTableCardRow { // Compact table format for contact lists }

UI Management Objects

// Main UI controller objects const cardsPane = { // Contact list view management displayBook(uid) { // Update display to show contacts from specific address book } }; const detailsPane = { // Contact details view and editing editCurrentContact(vCard) { // Populate edit form with contact data }, saveCurrentContact() { // Validate and save contact data } }; const photoDialog = { // Contact photo management _save() { // Crop and save contact photo as JPEG } };

Tab Integration System

File: [desktopmail]/r7organizer/mail/components/addrbook/content/addressBookTab.js

Address Book Tab Type Class

var addressBookTabType = { __proto__: contentTabBaseType, name: "addressBookTab", // Tab lifecycle management openTab(aTab, aArgs) { // Initialize and render address book tab const browser = document.createXULElement("browser"); browser.setAttribute("src", "about:addressbook"); }, shouldSwitchTo(aArgs) { // Check if existing tab should be focused }, closeTab(aTab) { // Clean up tab resources }, persistTab(aTab) { // Serialize tab state for restoration }, restoreTab(aTabmail, aPersistedState) { // Restore tab from persisted state }, doCommand(aCommand, aTab) { // Handle tab-specific commands } };

Extension API Layer

File: [desktopmail]/r7organizer/mail/components/extensions/parent/ext-addressBook.js

Address Book Extension API Class

class addressBook { // Main API for Thunderbird extensions async addressBooks(queryInfo) { // List address books with filtering } async create(options) { // Create new address book } async update(id, updateProperties) { // Update existing address book } async delete(id) { // Delete address book } } class addressBookCache { // Data caching and event management findAddressBook(id) { // Find address book by ID } convert(node) { // Convert internal nodes to API format } } class ExtSearchBook { // Custom directory for search operations constructor(searchQuery) { // Initialize search directory } }

vCard and Photo Management

// vCard processing utilities function vCardPropertiesFromCard(card) { // Extract vCard properties from address book card } function addVCardPhotoEntry(vCardProperties, photoFile) { // Add photo to vCard properties } // Photo file management async function getPhotoFile(id) { // Retrieve contact photo by ID } async function setPhotoFile(id, file) { // Save contact photo }

6. Complete Data Flow Between Layers

Overall Architecture Flow

┌─────────────────────────────────────────────────────────────────┐ │ EXTENSION API LAYER │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Extensions │ │ Third-party │ │ Custom Add-ons│ │ │ │ ext-addressBook│ │ Integrations │ │ (R7) │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ UI LAYER │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ aboutAddressBook│ │ addressBookTab │ │ Sharing Dialog │ │ │ │ - cardsPane │ │ - Tab Management│ │ - Permission │ │ │ │ - detailsPane │ │ - Navigation │ │ - User/Entity │ │ │ │ - photoDialog │ │ - State Persistence│ │ Management │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ BUSINESS LOGIC LAYER │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ AddressBook │ │ Corporate Sync │ │ Tab Management │ │ │ │ Proxy │ │ Controller │ │ Integration │ │ │ │ - getAllDirs() │ │ - check() │ │ - addressBookTab│ │ │ │ - remove() │ │ - loadCSAB() │ │ - Session Mgmt │ │ │ │ - CS Metadata │ │ - Credentials │ │ │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ STORAGE LAYER │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Mozilla Service │ │ Storage Types │ │ Remote Sync │ │ │ │ @mozilla.org/ │ │ - JS/Local │ │ - CardDAV │ │ │ │ abmanager;1 │ │ - CardDAV │ │ - LDAP │ │ │ │ - directories │ │ - LDAP │ │ - CS API │ │ │ │ - delete │ │ - Async │ │ │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ └─────────────────────────────────────────────────────────────────┘

Key Data Flow Operations

1. Address Book Discovery Flow

// UI Layer → Business Logic → Storage Layer async function initializeAddressBooks() { // 1. UI triggers initialization window.addEventListener('load', loadCSAB); // 2. Business logic orchestrates sync async function loadCSAB() { // 3. For each corporate user for (const user of window.r7.stores.$allR7CSUsers) { // 4. Check remote CS API await check(user); // 5. Sync with local storage const addressBooks = getAllDirectories(); // 6. Update UI with results updateTreeListbox(addressBooks); } } }

2. Corporate Sync Data Flow

// Remote → Local → UI async function check(user) { // 1. Fetch from CS API endpoints const [topLevelBooks, personalGroups] = await Promise.allSettled([ withUnauthorizedRetryStrategy('v1/addressbook', { method: 'GET' }, user.userId), withUnauthorizedRetryStrategy('v1/Groups?Type=15', { method: 'GET' }, user.userId), ]); // 2. Normalize and unify data const unified = normalizeCSData(topLevelBooks, personalGroups); // 3. Resolve CardDAV URLs const csBooks = resolveCardDavUrls(unified, user); // 4. Ensure authentication credentials ensureCSCredentials(csBooks[0].resolvedUrl, user); // 5. Create/update local directories const localBooks = await syncToLocal(csBooks); // 6. Update UI tree structure updateUITreeWithCorporateBooks(user, localBooks); }

3. Contact Management Flow

// UI → Details → Storage // User selects contact for editing cardsPane.displayBook(bookUid); // UI loads contact details detailsPane.editCurrentContact(vCardData); // User makes changes and saves detailsPane.saveCurrentContact().then(() => { // 1. Validate contact data // 2. Convert to vCard format // 3. Save to address book via MailServices.ab // 4. If corporate book, sync with remote // 5. Update UI with saved changes });

4. Extension API Data Flow

// Extension → API → Storage → Events // Extension calls addressBooks API browser.addressBooks.addressBooks.list().then(books => { // 1. API validates request // 2. Queries storage via addressBookCache // 3. Converts to API format // 4. Returns to extension // 5. Triggers onAddressBookChanged events });

5. Tab Navigation Flow

// User Action → Tab Manager → UI Rendering // User opens address book tab addressBookTabType.openTab(tab, args) { // 1. Create tab container const browser = createBrowserElement(); // 2. Load about:addressbook browser.setAttribute("src", "about:addressbook"); // 3. Initialize UI components browser.addEventListener('about-addressbook-ready', () => { initializeAddressBookUI(); }); // 4. Persist state for session restore const state = persistTabState(tab); }

Event-Driven Updates

// Storage changes trigger UI updates MailServices.ab.addAddressBookListener({ onItemAdded: function(parentDir, item) { // Corporate book added → Update UI tree if (item.isCorporate) { AbTreeListbox.prototype._createCsBookGroup(user, books); } }, onItemRemoved: function(parentDir, item) { // Book removed → Update UI and cleanup updateTreeStructure(); }, onItemPropertyChanged: function(item, property, oldValue, newValue) { // Property changed → Refresh relevant UI components if (property === 'childCardCount') { refreshContactList(); } } });

Summary

This comprehensive analysis reveals a sophisticated multi-layered address book architecture with clear separation of concerns:

Key Architectural Strengths:

Clean Layer Separation: Each layer has distinct responsibilities with well-defined interfaces

Corporate Integration: Seamless R7 corporate sync with CardDAV and custom API endpoints

Extensible Design: Thunderbird extension API enables third-party integrations

Robust Data Flow: Event-driven updates ensure consistency across all layers

Flexible Storage: Support for multiple storage types (local JS, CardDAV, LDAP, Async)

Critical Integration Points:

Mozilla XPCOM Service: Foundation for all address book operations

Corporate Sync Controller: Manages bidirectional sync with R7 systems

Event System: Ensures UI consistency across multiple components

Extension API: Provides standardized access for third-party developers

Data Flow Patterns:

Top-Down: User actions flow from UI through business logic to storage

Bottom-Up: Storage changes propagate up through events to UI updates

Lateral: Corporate sync operations span multiple layers simultaneously

Extension Integration: Third-party code integrates via standardized APIs

This architecture successfully balances Thunderbird's native capabilities with corporate requirements while maintaining extensibility for future enhancements.