index.ts110 lines · main
| 1 | import 'server-only' |
| 2 | |
| 3 | import { CustomerIOClient, type CustomerIOConfig } from './customerio' |
| 4 | import { HubSpotClient, type HubSpotConfig } from './hubspot' |
| 5 | import { NotionClient, type NotionConfig } from './notion' |
| 6 | |
| 7 | export type { CustomerIOConfig } from './customerio' |
| 8 | export type { HubSpotConfig } from './hubspot' |
| 9 | export type { NotionConfig } from './notion' |
| 10 | export { CustomerIOClient } from './customerio' |
| 11 | export { HubSpotClient } from './hubspot' |
| 12 | export { NotionClient } from './notion' |
| 13 | |
| 14 | export interface CRMConfig { |
| 15 | hubspot?: HubSpotConfig |
| 16 | customerio?: CustomerIOConfig |
| 17 | notion?: NotionConfig |
| 18 | } |
| 19 | |
| 20 | export interface CRMEventOptions { |
| 21 | /** Email used as identifier */ |
| 22 | email: string |
| 23 | /** Fields submitted to HubSpot form (key = HubSpot field name, value = field value) */ |
| 24 | hubspotFields?: Record<string, string> |
| 25 | /** HubSpot form context */ |
| 26 | context?: { pageUri?: string; pageName?: string } |
| 27 | /** Legal consent text for HubSpot */ |
| 28 | consent?: string |
| 29 | /** Event name for Customer.io tracking */ |
| 30 | event?: string |
| 31 | /** Properties attached to the Customer.io event */ |
| 32 | properties?: Record<string, unknown> |
| 33 | /** Profile attributes to set on the Customer.io contact */ |
| 34 | customerioProfile?: Record<string, unknown> |
| 35 | /** Notion page creation — values are keyed by Notion database column name */ |
| 36 | notion?: { |
| 37 | databaseId: string |
| 38 | properties: Record<string, unknown> |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | export class CRMClient { |
| 43 | private hubspot: HubSpotClient | null |
| 44 | private customerio: CustomerIOClient | null |
| 45 | private notion: NotionClient | null |
| 46 | |
| 47 | constructor(config: CRMConfig) { |
| 48 | this.hubspot = config.hubspot ? new HubSpotClient(config.hubspot) : null |
| 49 | this.customerio = config.customerio ? new CustomerIOClient(config.customerio) : null |
| 50 | this.notion = config.notion ? new NotionClient(config.notion) : null |
| 51 | } |
| 52 | |
| 53 | /** |
| 54 | * Submit an event to configured providers in parallel. |
| 55 | * Each provider is invoked only when both its client and its payload are present. |
| 56 | */ |
| 57 | async submitEvent(options: CRMEventOptions): Promise<{ errors: Error[] }> { |
| 58 | const promises: Promise<void>[] = [] |
| 59 | const errors: Error[] = [] |
| 60 | |
| 61 | if (this.hubspot && options.hubspotFields) { |
| 62 | const fields = { ...options.hubspotFields } |
| 63 | if (!fields.email) fields.email = options.email |
| 64 | |
| 65 | promises.push( |
| 66 | this.hubspot |
| 67 | .submitForm(fields, { |
| 68 | pageUri: options.context?.pageUri, |
| 69 | pageName: options.context?.pageName, |
| 70 | consent: options.consent, |
| 71 | }) |
| 72 | .catch((err) => { |
| 73 | errors.push(new Error(`HubSpot: ${err.message}`)) |
| 74 | }) |
| 75 | ) |
| 76 | } |
| 77 | |
| 78 | if (this.customerio && options.event) { |
| 79 | promises.push( |
| 80 | (async () => { |
| 81 | try { |
| 82 | if (options.customerioProfile) { |
| 83 | await this.customerio!.identify(options.email, options.customerioProfile) |
| 84 | } |
| 85 | await this.customerio!.track(options.email, options.event!, { |
| 86 | ...options.properties, |
| 87 | email: options.email, |
| 88 | submitted_at: new Date().toISOString(), |
| 89 | }) |
| 90 | } catch (err: any) { |
| 91 | errors.push(new Error(`Customer.io: ${err.message}`)) |
| 92 | } |
| 93 | })() |
| 94 | ) |
| 95 | } |
| 96 | |
| 97 | if (this.notion && options.notion) { |
| 98 | const { databaseId, properties } = options.notion |
| 99 | promises.push( |
| 100 | this.notion.createDatabasePage(databaseId, properties).catch((err) => { |
| 101 | errors.push(new Error(`Notion: ${err.message}`)) |
| 102 | }) |
| 103 | ) |
| 104 | } |
| 105 | |
| 106 | await Promise.all(promises) |
| 107 | |
| 108 | return { errors } |
| 109 | } |
| 110 | } |