Skip to content

nshah1d/contacts-hub

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Contacts Hub

PHP JavaScript Zero Dependencies Licence

A privacy-first, self-hosted contact manager for VCF and CSV exports from Apple Contacts, Google Contacts, and Microsoft Outlook. Parses, normalises, and renders contact libraries locally. No uploads. No third-party APIs. No build toolchain.

All contact data is processed in the browser. Nothing leaves the machine.


What It Does

Contact exports from Apple iOS, Google Contacts, and Outlook all produce fundamentally incompatible files. Apple VCF uses X-ABLabel grouping and quoted-printable encoding for non-ASCII names. Google exports multi-value fields with ::: delimiters. Outlook CSV uses different column headers for the same data.

Contacts Hub reads all of them.

Core capabilities:

  • Multi-library management. A PHP endpoint auto-discovers every .vcf and .csv file on the server at boot. Switch between libraries instantly from the sidebar.
  • Cross-format parsing. Dedicated parsers for VCF 3.0 and RFC 4180 CSV normalise both into a unified display schema. Apple, Google, and Outlook formats are handled without manual configuration.
  • Full-text search. A fuzzy subsequence search runs across name, organisation, phone numbers, and email addresses simultaneously.
  • Phone normalisation. A normalisation pipeline strips international dialling prefixes to derive canonical comparison keys, enabling reliable duplicate detection across contacts stored in mixed formats (e.g., +971507306079 and 00971507306079 resolve to the same key).
  • Duplicate detection. A per-library cross-reference pass identifies contacts sharing any normalised phone key and flags them with inline indicators.
  • In-browser CRUD. Add, edit, and delete contacts directly in the UI. Every mutation serialises the entire library back to VCF and persists it to the server via write.php. Restricted to VCF libraries.
  • CSV-to-VCF conversion. A "Convert to VCF" action on any CSV library creates a new, independently editable VCF file on the server. A selective import modal allows merging new contacts from a CSV source into an existing VCF library, with phone-key-based deduplication.
  • Full library export. Download any library as a single compiled VCF file. Available for both VCF and CSV libraries.
  • My Card pinning. Set MY_NAME in config.js to pin your own contact card to the top of every library list, surface a "My Card" badge in the detail pane in place of Edit and Delete controls, and personalise the share sheet text. Disabled when MY_NAME is empty.
  • Single-contact sharing. Generate a per-contact VCF blob and share it via the Web Share API (mobile) or download it directly (desktop).
  • Token authentication. Both PHP endpoints require a matching X-Nexus-Token header. Unauthenticated requests receive HTTP 403.
  • Zero dependencies. No npm, no build step, no framework. One HTML file, one CSS file, one JavaScript module, two PHP scripts, one config file.

Architecture

flowchart TB
    subgraph Browser ["Browser — Vanilla JavaScript (ES Module)"]
        config["config.js\nSCAN_TOKEN · MY_NAME"]
        app["app.js\nVCF Parser · CSV Parser · Normalisation Engine\nDuplicate Detector · CRUD · CSV Converter\nSearch · Export · Sharing"]
        ui["index.html + style.css\nWebOS SPA Shell · Tokyo Night · Responsive 3-Pane Layout"]
        config -->|import| app
    end

    subgraph Server ["PHP Server (local or hosted)"]
        scan["scan.php\nFile Discovery · Token Authentication Guard"]
        write["write.php\nVCF Write · Token Guard · Path Traversal Guard"]
        files["Contact Files\n*.vcf · *.csv"]
    end

    app -->|"GET scan.php\nX-Nexus-Token header"| scan
    scan -->|"JSON [filenames]"| app
    app -->|"GET *.vcf / *.csv\nX-Nexus-Token header"| files
    files -->|"Raw file content"| app
    app -->|"POST write.php\n{action, filename, vcfContent}"| write
    write -->|"Writes *.vcf to filesystem"| files
Loading

At boot, window.onload calls scan.php to retrieve the file list, then issues parallel fetch() calls for every discovered file. Each file is parsed immediately and stored in libraries[]. recomputeDuplicates() runs on every library before the sidebar renders.

Full architecture reference: docs/ARCHITECTURE.md


Requirements

Component Minimum
PHP 8.0
Browser Chrome 90+, Firefox 90+, Safari 15+, Edge 90+
Contact exports Apple VCF, Google Contacts CSV or VCF, Outlook CSV or VCF

No other dependencies.


Quick Start

1. Clone the repository.

git clone https://github.com/nshah1d/contacts-hub.git
cd contacts-hub

2. Add contact files.

Place .vcf or .csv export files directly in the project root. The application discovers all files matching *.{vcf,VCF,csv,CSV} at boot.

contacts-hub/
├── app.js
├── config.js
├── index.html
├── scan.php
├── style.css
├── write.php
│
├── iCloud_Export.vcf        ← Apple Contacts export
├── Google_Contacts.csv      ← Google Contacts CSV export
└── Outlook_Export.csv       ← Outlook CSV export

3. Generate a token.

The token is a shared secret used by both config.js and the PHP endpoints. Any random hex string of at least 32 characters is appropriate.

# Linux / macOS
openssl rand -hex 32
# Windows (PowerShell)
$b = New-Object byte[] 32
[System.Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($b)
[System.BitConverter]::ToString($b).Replace('-','').ToLower()

4. Configure the application.

Open config.js and set both values:

export const MY_NAME  = 'Your Name';
export const SCAN_TOKEN = 'your-generated-token';

Open scan.php and set the matching constant on line 2:

define('EXPECTED_TOKEN', 'your-generated-token');

Open write.php and set the same constant on line 2:

define('EXPECTED_TOKEN', 'your-generated-token');

All three token values must be identical. MY_NAME activates the My Card feature: when set, the application locates the contact whose display name matches (case-insensitive) and pins it to the top of every library list with a distinct visual indicator, replaces the Edit and Delete controls in the detail pane with a "My Card" badge, and personalises the share sheet text. Leave MY_NAME empty to disable the feature entirely.

5. Start a local server.

php -S localhost:8000

6. Open the application.

Navigate to http://localhost:8000 in any modern browser.


Directory Layout

contacts-hub/
│
├── app.js          # All client-side logic: parsers, CRUD, duplicate detection,
│                   # CSV conversion, export, sharing, search, UI rendering
├── config.js       # User configuration: MY_NAME, SCAN_TOKEN
├── index.html      # SPA shell: 3-pane layout, edit/add/import modals, SEO metadata
├── scan.php        # PHP backend: file discovery, token authentication
├── style.css       # Full design system: WebOS aesthetic, Tokyo Night palette,
│                   # responsive breakpoints
├── write.php       # PHP backend: VCF write, token guard, path traversal guard
├── robots.txt      # Disallows all crawling of hosted instances
│
├── docs/
│   ├── ARCHITECTURE.md   # Technical reference: parsers, normalisation, CRUD, CSV tools
│   └── CONFIGURATION.md  # Full configuration and deployment reference
│
├── SECURITY.md
└── LICENSE

Supported Export Formats

Apple Contacts (VCF)

Apple VCF exports use X-ABLabel grouping for custom phone and email labels, and QUOTED-PRINTABLE encoding for contact names containing non-ASCII characters (Arabic, accented Latin, CJK). The VCF parser handles both:

  • Line unfolding: CRLF-space and LF-space continuations are collapsed before parsing.
  • X-ABLabel resolution: group prefix (item1., item2.) is matched to the label value and attached to the corresponding TEL or EMAIL entry.
  • Quoted-printable decoding: =XX sequences are converted to %XX URI encoding and resolved by decodeURIComponent(), correctly handling multi-byte UTF-8 sequences.

Google Contacts (CSV / VCF)

Google CSV exports use ::: as a multi-value separator within a single cell (e.g., multiple phone numbers in one column). The CSV parser splits on ::: after the RFC 4180 cell boundary is resolved. Google VCF exports are handled by the standard VCF parser.

Microsoft Outlook (CSV / VCF)

Outlook CSV exports use distinct column header names (First Name, Last Name, Business Phone, etc.). The column detection uses candidate-list matching against known Outlook, Google, and generic header variants. Outlook VCF exports may use QUOTED-PRINTABLE encoding; the same decoding pipeline applies.


Configuration

Full reference: docs/CONFIGURATION.md

Variable File Description
MY_NAME config.js Your display name. Activates My Card pinning when set. Leave empty to disable.
SCAN_TOKEN config.js Token sent as X-Nexus-Token header on all fetch() calls.
EXPECTED_TOKEN scan.php Server-side token for the file discovery endpoint. Must match SCAN_TOKEN.
EXPECTED_TOKEN write.php Server-side token for the VCF write endpoint. Must match SCAN_TOKEN.

Both PHP files ship with empty token constants. Set all three to the same non-empty value before use.


Deployment

For local use, the PHP built-in server (php -S localhost:8000) is sufficient.

For a hosted deployment on any PHP-enabled web server:

  1. Upload app.js, config.js, index.html, robots.txt, scan.php, style.css, and write.php to a directory on the server.
  2. Place contact export files (.vcf, .csv) in the same directory.
  3. Set EXPECTED_TOKEN in both scan.php and write.php, and SCAN_TOKEN in config.js, to matching non-empty values.
  4. Update the seven metadata fields in index.html (og:url, og:image, og:site_name, twitter:url, twitter:image, link rel="icon", link rel="apple-touch-icon") to the live deployment URL.
  5. Access the directory via HTTPS.

Full deployment reference: docs/CONFIGURATION.md


Security

Full reference: SECURITY.md

  • All contact parsing is client-side. scan.php returns only filenames. write.php accepts only a filename and VCF content string. Neither endpoint reads or transmits contact field data.
  • Every request to both PHP endpoints requires a valid X-Nexus-Token header. Absent or mismatched tokens receive HTTP 403 before any filesystem operation runs.
  • write.php enforces basename() on the submitted filename and rejects any value whose extension is not .vcf, preventing path traversal and non-VCF writes.
  • robots.txt ships with Disallow: / to prevent search engines from indexing hosted instances.
  • No analytics, no telemetry, no external scripts beyond the Google Fonts CDN.


Architected by Nauman Shahid


Portfolio GitHub LinkedIn


Licensed under the MIT Licence.

About

Privacy-first contact manager for Apple, Google, and Outlook exports. Parses VCF and CSV files locally in the browser. No uploads. Zero dependencies.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors