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

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:

1.

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

2.

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<Directory[]> {
  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<void> {
  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:

1.

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

2.

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

3.

Extensible Design: Thunderbird extension API enables third-party integrations

4.

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

5.

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:

1.

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

2.

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

3.

Lateral: Corporate sync operations span multiple layers simultaneously

4.

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.