@hoover-institution/hubspot-lib
Version:
A toolkit for deep integration with HubSpot's Marketing Events API with a plugin-based architecture.
141 lines (128 loc) • 4.49 kB
JavaScript
/**
* IN DEVELOPMENT
*
* Manages HubSpot contact operations using the HubSpot CRM API.
* Requires the HUBSPOT_KEY environment variable for authentication.
*
* @class ContactManager
* @private
*/
import axios from "axios";
export class ContactManager {
#client;
constructor() {
this.#client = axios.create({
baseURL: "https://api.hubapi.com/crm/v3/",
headers: {
Authorization: `Bearer ${process.env.HUBSPOT_KEY}`,
"Content-Type": "application/json",
},
timeout: 5000,
});
}
/**
* @typedef {Object} HubSpotContact
* @property {string} id
* @property {Object} properties
* @property {string} properties.createdate
* @property {string} properties.email
* @property {string} properties.firstname
* @property {string} properties.hs_object_id
* @property {string} properties.lastmodifieddate
* @property {string} properties.lastname
* @property {string} createdAt
* @property {string} updatedAt
* @property {boolean} archived
*/
/**
* Retrieves a contact by their email address, or creates one if not found.
*
* @async
* @param {string} email - The email address of the contact to search for.
* @param {string} [fullName=""] - The full name to use if creating a new contact.
* @returns {Promise<HubSpotContact>} The contact object, newly created or found.
* @throws {Error} If the request fails for reasons other than not found.
*/
async getContactByEmail(email, fullName = "") {
try {
const response = await this.#client.post("/objects/contacts/search", {
filterGroups: [
{
filters: [{ propertyName: "email", operator: "EQ", value: email }],
},
],
properties: ["email", "firstname", "lastname"],
});
if (!response.data.results[0]) {
const [firstName, ...lastNameParts] = fullName.trim().split(" ");
const lastName = lastNameParts.join(" ");
const createResponse = await this.#client.post("/objects/contacts", {
properties: {
email,
firstname: firstName || "",
lastname: lastName || "",
},
});
return createResponse.data;
}
return response.data.results[0] || null;
} catch (error) {
console.error("[ContactManager] Error in getContactByEmail:", error);
if (error.response?.status === 404) return null;
throw error;
}
}
// getContactById
/**
* Retrieves a contact by their unique HubSpot ID.
*
* @async
* @param {string} contactId - The unique HubSpot ID of the contact.
* @returns {Promise<HubSpotContact|null>} The contact object if found, otherwise null.
* @throws {Error} If the request fails for reasons other than not found.
*/
async getContactById(contactId) {
try {
const response = await this.#client.get(
`/objects/contacts/${contactId}`,
{
params: { properties: ["email", "firstname", "lastname"].join(",") },
}
);
return response.data || null;
} catch (error) {
if (error.response?.status === 404) return null;
throw error;
}
}
/**
* @typedef {Object} ContactListMembership
* @property {string} listId - The unique identifier of the list.
* @property {number} listVersion - The version number of the list.
* @property {boolean} isPublicList - Whether the list is public.
* @property {string} firstAddedTimestamp - The ISO timestamp when the contact was first added to the list.
* @property {string} lastAddedTimestamp - The ISO timestamp when the contact was last added to the list.
*/
/**
* Retrieves the contact lists that a contact with the specified email belongs to.
*
* @async
* @param {string} email - The email address of the contact to look up.
* @returns {Promise<ContactListMembership[]|null>} A promise that resolves to an array of contact list memberships,
* or null if the contact does not exist.
* @throws {Error} Throws an error if the API request fails.
*/
async getContactLists(email) {
const contact = await this.getContactByEmail(email);
if (!contact) return null;
const recordId = contact.id;
try {
const response = await this.#client.get(
`https://api.hubapi.com/crm/v3/lists/records/0-1/${recordId}/memberships`
);
return response.data.results || [];
} catch (error) {
throw error;
}
}
}