1.1.9 • Published 5 months ago

@harmonixit/harmonix-everywhere v1.1.9

Weekly downloads
-
License
MIT
Repository
-
Last release
5 months ago

Harmonix Everywhere

A lightweight JavaScript library for integrating with the Harmonix Chrome extension. This library provides a simple interface to communicate with the Harmonix extension and open contacts or companies directly from your web application.

Installation

NPM

npm install @harmonixit/harmonix-everywhere

Package Info

  • Package: @harmonixit/harmonix-everywhere
  • Version: 1.1.8
  • Main: dist/index.js
  • Module: dist/index.esm.js
  • Types: dist/index.d.ts

Quick Start

import { Harmonix } from '@harmonixit/harmonix-everywhere';

// Create and initialize
const harmonix = new Harmonix({ debug: true });

// Check extension availability and open a contact
const openContact = async () => {
  const isAvailable = await harmonix.isExtensionAvailable();
  if (isAvailable) {
    await harmonix.initialize();
    
    const result = await harmonix.openContact('contact-123', 'Contact', {
      first_name: 'John',
      last_name: 'Doe',
      email: 'john@example.com'
    });
    
    console.log('Contact opened:', result);
  } else {
    console.log('Harmonix extension not available');
  }
};

Usage Examples

1. Basic Contact Opening

import { Harmonix } from '@harmonixit/harmonix-everywhere';

// Create a Harmonix client
const harmonix = new Harmonix({ debug: true });

// Initialize the client with default retry settings (30 attempts, 1 second interval)
harmonix.initialize().then(isInitialized => {
  if (isInitialized) {
    console.log('Harmonix extension is available and initialized');
    
    // Open a contact in the extension
    harmonix.openContact(
      'ext-123', // External ID for the contact
      'Contact', // Object type ('Contact' or 'Company')
      {
        first_name: 'John',
        last_name: 'Doe',
        email: 'john@example.com',
        phone_number: '+1234567890',
        job_role: 'Sales Manager',
        company_name: 'ACME Inc'
      }
    )
    .then(result => {
      console.log('Contact opened:', result);
    })
    .catch(error => {
      console.error('Failed to open contact:', error);
    });
  } else {
    console.log('Harmonix extension is not available');
  }
});

2. Auto-Initialization Pattern

import { Harmonix } from '@harmonixit/harmonix-everywhere';

// Create a Harmonix client with auto-initialization
const harmonix = new Harmonix({ 
  debug: true,
  autoInitialize: true,  // Start initialization immediately
  maxRetries: 30,        // Try for 30 seconds
  retryInterval: 1000    // Check every second
});

// The initialization process is already running in the background
// Later, when you need to use the extension, you can just call your methods
// The library will ensure initialization is complete before proceeding

// Example: When the user clicks a button to open a contact
document.getElementById('openContactButton').addEventListener('click', async () => {
  try {
    const result = await harmonix.openContact(
      'ext-123',
      'Contact',
      {
        first_name: 'John',
        last_name: 'Doe',
        email: 'john@example.com'
      }
    );
    
    console.log('Contact opened:', result);
  } catch (error) {
    console.error('Failed to open contact:', error);
  }
});

3. Company Opening

import { Harmonix } from '@harmonixit/harmonix-everywhere';

const harmonix = new Harmonix({ debug: true, autoInitialize: true });

// Open a company in Harmonix
const openCompany = async () => {
  try {
    const result = await harmonix.openContact(
      'company-techcorp',
      'Company',
      {
        name: 'TechCorp',
        industry: 'Enterprise Software',
        size: '2,500+ employees',
        revenue: '$500M - $1B',
        phone_number: '+12125551234',
        email: 'info@techcorp.com',
        linkedin_url: 'https://linkedin.com/company/techcorp',
        website: 'https://techcorp.com',
        city: 'New York',
        country: 'United States',
        address: '555 Tech Avenue, Floor 32'
      }
    );
    
    if (result.success) {
      console.log('Company opened successfully:', result.objectId);
    }
  } catch (error) {
    console.error('Failed to open company:', error);
  }
};

4. CRM Integration Example

// Real-world CRM integration example
import { Harmonix } from '@harmonixit/harmonix-everywhere';

class CRMHarmonixIntegration {
  constructor() {
    this.harmonix = new Harmonix({
      debug: true,
      autoInitialize: true,
      maxRetries: 30,
      retryInterval: 1000
    });
    
    this.setupEventListeners();
  }
  
  async setupEventListeners() {
    // Wait for DOM to be ready
    document.addEventListener('DOMContentLoaded', async () => {
      // Check extension availability
      const isAvailable = await this.harmonix.isExtensionAvailable();
      
      if (isAvailable) {
        // Enable Harmonix buttons
        this.enableHarmonixButtons();
      } else {
        // Disable and show unavailable state
        this.disableHarmonixButtons();
      }
    });
  }
  
  enableHarmonixButtons() {
    const buttons = document.querySelectorAll('[data-harmonix-action]');
    buttons.forEach(button => {
      button.disabled = false;
      button.addEventListener('click', this.handleHarmonixAction.bind(this));
    });
  }
  
  disableHarmonixButtons() {
    const buttons = document.querySelectorAll('[data-harmonix-action]');
    buttons.forEach(button => {
      button.disabled = true;
      button.title = 'Harmonix extension not available';
    });
  }
  
  async handleHarmonixAction(event) {
    const button = event.target;
    const action = button.dataset.harmonixAction;
    const objectId = button.dataset.objectId;
    const objectType = button.dataset.objectType;
    
    try {
      // Show loading state
      button.textContent = 'Opening in Harmonix...';
      button.disabled = true;
      
      // Extract data based on object type
      const objectData = this.extractObjectData(objectType);
      
      // Open in Harmonix
      const result = await this.harmonix.openContact(objectId, objectType, objectData);
      
      if (result.success) {
        // Show success state temporarily
        button.textContent = 'Opened Successfully!';
        setTimeout(() => {
          button.textContent = 'Open in Harmonix';
          button.disabled = false;
        }, 2000);
      }
    } catch (error) {
      console.error('Error opening in Harmonix:', error);
      button.textContent = 'Error - Try Again';
      setTimeout(() => {
        button.textContent = 'Open in Harmonix';
        button.disabled = false;
      }, 2000);
    }
  }
  
  extractObjectData(objectType) {
    if (objectType === 'Contact') {
      return this.extractContactDataFromPage();
    } else if (objectType === 'Company') {
      return this.extractCompanyDataFromPage();
    }
  }
  
  extractContactDataFromPage() {
    // Extract contact information from page DOM
    return {
      first_name: document.querySelector('[data-field="firstName"]')?.textContent || 'Unknown',
      last_name: document.querySelector('[data-field="lastName"]')?.textContent || 'Unknown',
      email: document.querySelector('[data-field="email"]')?.textContent,
      phone_number: document.querySelector('[data-field="phone"]')?.textContent,
      job_role: document.querySelector('[data-field="jobRole"]')?.textContent,
      company_name: document.querySelector('[data-field="company"]')?.textContent,
      linkedin_url: document.querySelector('[data-field="linkedin"]')?.href
    };
  }
  
  extractCompanyDataFromPage() {
    // Extract company information from page DOM
    return {
      name: document.querySelector('[data-field="companyName"]')?.textContent || 'Unknown',
      industry: document.querySelector('[data-field="industry"]')?.textContent,
      size: document.querySelector('[data-field="size"]')?.textContent,
      email: document.querySelector('[data-field="email"]')?.textContent,
      phone_number: document.querySelector('[data-field="phone"]')?.textContent,
      website: document.querySelector('[data-field="website"]')?.href,
      linkedin_url: document.querySelector('[data-field="linkedin"]')?.href
    };
  }
}

// Initialize the integration
new CRMHarmonixIntegration();

HTML for the CRM integration:

<!-- Contact page -->
<div class="contact-details">
  <h1><span data-field="firstName">Sarah</span> <span data-field="lastName">Johnson</span></h1>
  <p data-field="jobRole">Marketing Director</p>
  <p data-field="company">Acme Inc.</p>
  <a href="mailto:sarah@acme.com" data-field="email">sarah@acme.com</a>
  
  <button 
    data-harmonix-action="open" 
    data-object-id="contact-sarah-johnson" 
    data-object-type="Contact"
    class="btn-primary">
    Open in Harmonix
  </button>
</div>

<!-- Company page -->
<div class="company-details">
  <h1 data-field="companyName">TechCorp</h1>
  <p data-field="industry">Enterprise Software</p>
  <p data-field="size">2,500+ employees</p>
  <a href="https://techcorp.com" data-field="website">techcorp.com</a>
  
  <button 
    data-harmonix-action="open" 
    data-object-id="company-techcorp" 
    data-object-type="Company"
    class="btn-primary">
    Open in Harmonix
  </button>
</div>

API Reference

Constructor

new Harmonix(options)

Creates a new instance of the Harmonix client.

Options:

interface HarmonixOptions {
  debug?: boolean;           // Enables debug logging. Default: false
  autoInitialize?: boolean;  // Automatically initialize upon creation. Default: false
  maxRetries?: number;       // Maximum retry attempts when initializing. Default: 30
  retryInterval?: number;    // Interval between retries in ms. Default: 1000
  mainSelector?: string;     // Main selector for the extension. Default: 'body'
  headerHeight?: number;     // Header height in pixels. Default: 0
}

Example:

const harmonix = new Harmonix({
  debug: true,
  autoInitialize: true,
  maxRetries: 30,
  retryInterval: 1000
});

Core Methods

harmonix.isExtensionAvailable()

Checks if the Harmonix extension is installed and available.

Returns: Promise<boolean> - True if extension is available

Example:

const isAvailable = await harmonix.isExtensionAvailable();
if (isAvailable) {
  console.log('Extension is ready to use');
} else {
  console.log('Please install the Harmonix extension');
}

harmonix.initialize(maxRetries?, retryInterval?)

Initializes the Harmonix client and establishes a connection with the extension. The method will retry connecting to the extension if it's not immediately available.

Parameters:

  • maxRetries (number, optional): Maximum number of retry attempts. Default: 30
  • retryInterval (number, optional): Interval between retry attempts in milliseconds. Default: 1000

Returns: Promise<boolean> - True if initialization was successful

Example:

// Initialize with default settings (30 retries, 1 second intervals)
const success = await harmonix.initialize();

// Initialize with custom retry settings
const success = await harmonix.initialize(60, 500); // 60 attempts, 500ms intervals

if (success) {
  console.log('Successfully connected to Harmonix extension');
} else {
  console.log('Failed to connect to Harmonix extension');
}

harmonix.openContact(objectId, objectType, objectData)

Opens a contact or company in the Harmonix extension. This is the main method for integrating with Harmonix.

Parameters:

  • objectId (string): Unique identifier for the object in your system
  • objectType ('Contact' | 'Company'): Type of object to open
  • objectData (ContactInfo | CompanyInfo): Contact or company information

Returns: Promise<{success: boolean, objectId?: string}> - Result with success status and Harmonix object ID

Contact Data Fields (ContactInfo):

interface ContactInfo {
  first_name: string;           // Required: Contact's first name
  last_name: string;            // Required: Contact's last name
  email?: string;               // Primary email address
  secondary_email?: string;     // Secondary email address
  phone_number?: string;        // Primary phone number
  secondary_phone_number?: string; // Secondary phone number
  job_role?: string;            // Contact's job role or title
  company_name?: string;        // Company name where the contact works
  linkedin_url?: string;        // LinkedIn profile URL
  address?: string;             // Physical address
  city?: string;                // City
  country?: string;             // Country
  website?: string;             // Website URL
  assigned_to?: string;         // User id or email to assign the object to
  [key: string]: any;           // Additional custom fields
}

Company Data Fields (CompanyInfo):

interface CompanyInfo {
  name: string;                 // Required: Company name
  industry?: string;            // Industry or sector
  size?: string;                // Company size (number of employees)
  revenue?: string;             // Annual revenue
  email?: string;               // Primary email address
  phone_number?: string;        // Primary phone number
  linkedin_url?: string;        // LinkedIn profile URL
  address?: string;             // Physical address
  city?: string;                // City
  country?: string;             // Country
  website?: string;             // Website URL
  assigned_to?: string;         // User id or email to assign the object to
  [key: string]: any;           // Additional custom fields
}

Examples:

// Open a contact
const contactResult = await harmonix.openContact(
  'crm-contact-123',
  'Contact',
  {
    first_name: 'Sarah',
    last_name: 'Johnson',
    email: 'sarah.johnson@acme.com',
    phone_number: '+15551234567',
    job_role: 'Marketing Director',
    company_name: 'Acme Inc.',
    linkedin_url: 'https://linkedin.com/in/sarahjohnson'
  }
);

if (contactResult.success) {
  console.log('Contact opened with ID:', contactResult.objectId);
}

// Open a company
const companyResult = await harmonix.openContact(
  'crm-company-456',
  'Company',
  {
    name: 'TechCorp',
    industry: 'Enterprise Software',
    size: '2,500+ employees',
    email: 'info@techcorp.com',
    website: 'https://techcorp.com',
    linkedin_url: 'https://linkedin.com/company/techcorp'
  }
);

if (companyResult.success) {
  console.log('Company opened with ID:', companyResult.objectId);
}

harmonix.sendCommand(command, params?)

Sends a custom command to the Harmonix extension. This is a lower-level method that allows for custom integrations.

Parameters:

  • command (string): The command to send
  • params (any, optional): Parameters for the command

Returns: Promise<T> - The response from the extension

Example:

// Send a custom command
const result = await harmonix.sendCommand('customAction', {
  data: 'example'
});

console.log('Command result:', result);

Error Handling

The library uses standard JavaScript Promises and will throw errors for various failure conditions:

try {
  const result = await harmonix.openContact('id', 'Contact', {
    first_name: 'John',
    last_name: 'Doe'
  });
  
  if (result.success) {
    console.log('Success!');
  }
} catch (error) {
  if (error.message.includes('not available')) {
    console.log('Extension not installed or disabled');
  } else if (error.message.includes('requires')) {
    console.log('Missing required fields');
  } else {
    console.log('Unexpected error:', error.message);
  }
}

Type Definitions

The library exports TypeScript type definitions for better development experience:

import { Harmonix, HarmonixOptions, ContactInfo, CompanyInfo, ObjectType } from '@harmonixit/harmonix-everywhere';

Working Examples

The package includes a set of comprehensive example implementations showing how to integrate the Harmonix library in different scenarios.

Running the Examples

# Install dependencies
npm install

# Build the library first
npm run build

# Start the development server
npm run dev

Then open your browser to http://localhost:3001 to view the examples.

Included Examples

  1. Basic Usage Example (examples/index.html)

    • Simple form to open contacts in Harmonix
    • Extension availability checking
    • Basic error handling
  2. CRM Dashboard (examples/crm-example/index.html)

    • Complete CRM interface mockup
    • Auto-initialization pattern
    • Status indicators for extension availability
  3. Contact Detail Page (examples/crm-example/lead-page.html)

    • Real-world contact page integration
    • Data extraction from page DOM
    • Complete contact information handling
  4. Company Detail Page (examples/crm-example/company-page.html)

    • Company-specific integration example
    • Advanced data mapping
    • Button state management

Note: You need to have the Harmonix Chrome extension installed in your browser for the examples to work properly.

Best Practices

1. Extension Availability Checking

Always check if the extension is available before attempting to use it:

const harmonix = new Harmonix({ debug: true });

// Check availability first
const isAvailable = await harmonix.isExtensionAvailable();
if (!isAvailable) {
  // Show user-friendly message
  showExtensionNotAvailableMessage();
  return;
}

2. Graceful Degradation

Design your interface to work even when the extension is not available:

// Enable/disable Harmonix features based on availability
function updateUI(extensionAvailable) {
  const harmonixButtons = document.querySelectorAll('.harmonix-action');
  
  harmonixButtons.forEach(button => {
    if (extensionAvailable) {
      button.disabled = false;
      button.textContent = 'Open in Harmonix';
    } else {
      button.disabled = true;
      button.textContent = 'Extension Required';
      button.title = 'Please install the Harmonix Chrome extension';
    }
  });
}

3. Auto-Initialization for Better UX

Use auto-initialization to prepare the connection in the background:

// Initialize immediately when the page loads
const harmonix = new Harmonix({
  autoInitialize: true,
  maxRetries: 30,
  retryInterval: 1000
});

// The extension will be ready when users interact with your interface

4. Unique Object IDs

Always use unique, stable identifiers for your objects:

// Good: Use your system's unique identifiers
const objectId = `crm-contact-${contact.id}`;

// Bad: Using timestamps or random values
const objectId = `contact-${Date.now()}`;

5. Error Handling

Implement comprehensive error handling:

async function openInHarmonix(objectId, objectType, data) {
  try {
    const result = await harmonix.openContact(objectId, objectType, data);
    
    if (result.success) {
      showSuccessMessage(`${objectType} opened successfully`);
    } else {
      showErrorMessage('Failed to open in Harmonix');
    }
  } catch (error) {
    if (error.message.includes('not available')) {
      showInstallPrompt();
    } else if (error.message.includes('requires')) {
      showValidationError(error.message);
    } else {
      showGenericError('Something went wrong. Please try again.');
    }
  }
}

6. Data Validation

Validate required fields before sending to Harmonix:

function validateContactData(data) {
  const errors = [];
  
  if (!data.first_name) errors.push('First name is required');
  if (!data.last_name) errors.push('Last name is required');
  
  if (errors.length > 0) {
    throw new Error(`Validation failed: ${errors.join(', ')}`);
  }
}

function validateCompanyData(data) {
  if (!data.name) {
    throw new Error('Company name is required');
  }
}

Integration Patterns

React Integration

import React, { useState, useEffect } from 'react';
import { Harmonix } from '@harmonixit/harmonix-everywhere';

function ContactCard({ contact }) {
  const [harmonix] = useState(() => new Harmonix({ 
    debug: process.env.NODE_ENV === 'development',
    autoInitialize: true 
  }));
  const [extensionAvailable, setExtensionAvailable] = useState(false);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    harmonix.isExtensionAvailable().then(setExtensionAvailable);
  }, [harmonix]);

  const handleOpenInHarmonix = async () => {
    setLoading(true);
    
    try {
      const result = await harmonix.openContact(
        `contact-${contact.id}`,
        'Contact',
        {
          first_name: contact.firstName,
          last_name: contact.lastName,
          email: contact.email,
          phone_number: contact.phone,
          job_role: contact.jobTitle,
          company_name: contact.company
        }
      );
      
      if (result.success) {
        console.log('Contact opened successfully');
      }
    } catch (error) {
      console.error('Failed to open contact:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="contact-card">
      <h3>{contact.firstName} {contact.lastName}</h3>
      <p>{contact.jobTitle} at {contact.company}</p>
      
      <button
        onClick={handleOpenInHarmonix}
        disabled={!extensionAvailable || loading}
        className="harmonix-button"
      >
        {loading ? 'Opening...' : 'Open in Harmonix'}
      </button>
      
      {!extensionAvailable && (
        <p className="extension-warning">
          Harmonix extension required
        </p>
      )}
    </div>
  );
}

Vue.js Integration

<template>
  <div class="contact-card">
    <h3>{{ contact.firstName }} {{ contact.lastName }}</h3>
    <p>{{ contact.jobTitle }} at {{ contact.company }}</p>
    
    <button
      @click="openInHarmonix"
      :disabled="!extensionAvailable || loading"
      class="harmonix-button"
    >
      {{ loading ? 'Opening...' : 'Open in Harmonix' }}
    </button>
    
    <p v-if="!extensionAvailable" class="extension-warning">
      Harmonix extension required
    </p>
  </div>
</template>

<script>
import { Harmonix } from '@harmonixit/harmonix-everywhere';

export default {
  name: 'ContactCard',
  props: ['contact'],
  data() {
    return {
      harmonix: new Harmonix({ 
        debug: process.env.NODE_ENV === 'development',
        autoInitialize: true 
      }),
      extensionAvailable: false,
      loading: false
    };
  },
  
  async mounted() {
    this.extensionAvailable = await this.harmonix.isExtensionAvailable();
  },
  
  methods: {
    async openInHarmonix() {
      this.loading = true;
      
      try {
        const result = await this.harmonix.openContact(
          `contact-${this.contact.id}`,
          'Contact',
          {
            first_name: this.contact.firstName,
            last_name: this.contact.lastName,
            email: this.contact.email,
            phone_number: this.contact.phone,
            job_role: this.contact.jobTitle,
            company_name: this.contact.company
          }
        );
        
        if (result.success) {
          console.log('Contact opened successfully');
        }
      } catch (error) {
        console.error('Failed to open contact:', error);
      } finally {
        this.loading = false;
      }
    }
  }
};
</script>

Troubleshooting

Common Issues

1. Extension not detected

  • Ensure the Harmonix Chrome extension is installed and enabled
  • Check that the extension has necessary permissions
  • Try refreshing the page

2. Initialization timeout

  • Increase maxRetries and retryInterval for slower connections
  • Check browser console for error messages
  • Verify extension is running and accessible

3. Data not appearing in Harmonix

  • Ensure required fields are provided (first_name/last_name for contacts, name for companies)
  • Check that data is properly formatted
  • Use debug mode to see detailed logs

Debug Mode

Enable debug mode to see detailed logging:

const harmonix = new Harmonix({ debug: true });

This will log all communication with the extension to the browser console.

Development

Building the Library

# Install dependencies
npm install

# Build the library
npm run build

# Watch for changes during development
npm run watch

Testing

# Run tests
npm test

# Lint the code
npm run lint

Directory Structure

harmonix-everywhere/
├── src/
│   ├── harmonix.ts      # Main Harmonix class
│   ├── types.ts         # TypeScript type definitions
│   └── index.ts         # Library entry point
├── examples/            # Example implementations
│   ├── basic-usage.js   # Simple example
│   ├── index.html       # Basic example page
│   └── crm-example/     # Advanced CRM examples
├── dist/                # Built library files
└── tests/               # Test files

Support

For issues, questions, or contributions:

  1. Check the examples in the examples/ directory
  2. Enable debug mode for detailed logging
  3. Refer to the API documentation above
  4. Contact the development team for additional support

License

MIT

1.1.9

5 months ago

1.1.8

5 months ago

1.1.7

7 months ago