var fs = require('fs'); var axios = require('axios'); var express = require('express'); const auth = require('../cfg/auth.js'); const FILE_PATH = require('./last-modified-date.json'); const app = express(); const port = 3000; app.use(express.json()); var server = app.listen(3000, () => { console.log('Please your hubspot instance and next click on http://localhost:3000/install or http://localhost:3000/renew') }) // used to initiate the authorization flow to connect the app with Hubspot app.get('/install', async (req, res) => { // Build the auth URL const authUrl = 'https://app.hubspot.com/oauth/authorize' + `?client_id=${encodeURIComponent(auth.hClientId)}` + `&scope=${encodeURIComponent(auth.hScopes.join(' '))}` + `&redirect_uri=${encodeURIComponent(auth.hCallback)}` + `&state=${encodeURIComponent('happy')}` // Redirect the user return res.redirect(authUrl); }); app.get('/renew', async (req, res) => { const refreshTokenProof = { grant_type: 'refresh_token', client_id: auth.hClientId, client_secret: auth.hClientSecret, redirect_uri: auth.hCallback, refresh_token: auth.refresh_token // TODO: da capire }; var token=await exchangeForTokens(refreshTokenProof); if (token.message) { return res.send(`error: ${token.message}`); } token.issued_at=Date.now(); fs.writeFileSync( configPath, JSON.stringify(token)); res.send(`ok`); server.close(); }); // converts the temporary authorization code into working API credentials app.get('/oauth-callback', async (req, res) => { console.log(res); if (req.query.code) { console.log(' > Received an authorization token'); console.log(req.query); const authCodeProof = { grant_type: 'authorization_code', client_id: auth.hClientId, client_secret: auth.hClientSecret, redirect_uri: auth.hCallback, code: req.query.code }; // Step 4 // Exchange the authorization code for an access token and refresh token console.log('===> Step 4: Exchanging authorization code for an access token and refresh token'); const token = await exchangeForTokens( authCodeProof); if (token.message) { return res.redirect(`/error?msg=${token.message}`); } token.issued_at=Date.now(); fs.writeFileSync( FILE_PATH, JSON.stringify({ lastRun: token }, null, 2)); console.log(token); res.send(`ok`); server.close(); } }); async function exchangeForTokens(exchangeProof) { try { var body=`grant_type=${exchangeProof.grant_type}&`; body+=`client_id=${exchangeProof.client_id}&`; body+=`client_secret=${exchangeProof.client_secret}&`; body+=`redirect_uri=${exchangeProof.redirect_uri}&`; if (exchangeProof.code) body+=`code=${exchangeProof.code}`; if (exchangeProof.refresh_token) body+=`refresh_token=${exchangeProof.refresh_token}`; console.log(body); var responseBody = await axios.post('https://api.hubapi.com/oauth/v1/token', body, { headers: { 'Content-Type': 'application/x-www-form-urlencoded'} }); //console.log(responseBody); const tokens = responseBody.data; console.log(' > Received an access token and refresh token'); console.log(tokens); return tokens; } catch (e) { console.error(` > Error exchanging ${exchangeProof.grant_type} for access token`); console.log(e.response.data); return e.response.data; } } var hubspot = { baseurl: 'https://api.hubapi.com', credentials: {}, tokenFile: '', cachedToken: null, tokenExpiry: null, logresult: function (resp) { console.log('internal--->'); console.log(JSON.stringify(resp, null, 2)); }, setTokenFile: function (filename) { this.tokenFile = filename; }, setClientId: function (clientId) { this.credentials.clientId = clientId; }, setClientSecret: function (clientSecret) { this.credentials.clientSecret = clientSecret; }, setRedirectUri: function (redirectUri) { this.credentials.redirectUri = redirectUri; }, setRefreshToken: function (refreshToken) { this.credentials.refreshToken = refreshToken; }, loadConfig: function (configPath) { try { const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); this.credentials.clientId = config.CLIENT_ID; this.credentials.clientSecret = config.CLIENT_SECRET; this.credentials.redirectUri = config.REDIRECT_URI; this.credentials.refreshToken = config.REFRESH_TOKEN; } catch (e) { console.error('Error loading config:', e.message); } }, getAccessToken: async function () { console.log('getAccessToken'); const body = new URLSearchParams({ grant_type: 'refresh_token', client_id: this.credentials.clientId, client_secret: this.credentials.clientSecret, refresh_token: this.credentials.refreshToken }).toString(); try { const response = await axios.post(`${this.baseurl}/oauth/v1/token`, body, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); return response.data; } catch (error) { console.log('getAccessToken error:', error.response?.data); throw error; } }, checkToken: async function () { if (!this.isTokenValid()) { try { const tokenData = await this.getAccessToken(); if (this.tokenFile) { fs.writeFileSync(this.tokenFile, JSON.stringify(tokenData, null, 2)); } this.cachedToken = tokenData.access_token; this.tokenExpiry = tokenData.expires_in + Date.now(); console.log('New access token generated'); return tokenData; } catch (err) { console.log('checkToken: error on getting a new token!'); console.log('err', err); throw err; } } else { return { access_token: this.cachedToken }; } }, getTokenFromFile: async function () { let tokenData=JSON.parse( fs.readFileSync(this.tokenFile) ); this.credentials.refreshToken = tokenData.refresh_token; this.cachedToken = tokenData.access_token; this.tokenExpiry = tokenData.expires_in; }, isTokenValid: function () { return this.cachedToken && this.tokenExpiry > Date.now(); }, createContact: async function (contactData) { await this.checkToken(); try { const response = await axios.post(`${this.baseurl}/crm/v3/objects/contacts`, contactData, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); this.logresult(response.data); return response.data; } catch (e) { console.error('Error creating contact:', e.response?.data); throw e; } }, updateContact: async function (contactId, contactData) { await this.checkToken(); try { const response = await axios.patch(`${this.baseurl}/crm/v3/objects/contacts/${contactId}`, contactData, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); this.logresult(response.data); return response.data; } catch (e) { console.error('Error updating contact:', e.response?.data); throw e; } }, getContact: async function (contactId) { await this.checkToken(); try { const response = await axios.get(`${this.baseurl}/crm/v3/objects/contacts/${contactId}`, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); this.logresult(response.data); return response.data; } catch (e) { console.error('Error fetching contact:', e.response?.data); throw e; } }, deleteContact: async function (contactId) { await this.checkToken(); try { const response = await axios.delete(`${this.baseurl}/crm/v3/objects/contacts/${contactId}`, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); this.logresult(response.data); return response.data; } catch (e) { console.error('Error deleting contact:', e.response?.data); throw e; } }, createCompany: async function (companyData) { await this.checkToken(); try { const response = await axios.post(`${this.baseurl}/crm/v3/objects/companies`, companyData, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); this.logresult(response.data); return response.data; } catch (e) { console.error('Error creating company:', e.response?.data); throw e; } }, updateCompany: async function (companyId, companyData) { await this.checkToken(); try { const response = await axios.patch(`${this.baseurl}/crm/v3/objects/companies/${companyId}`, companyData, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); this.logresult(response.data); return response.data; } catch (e) { console.error('Error updating company:', e.response?.data); throw e; } }, getCompany: async function (companyId) { await this.checkToken(); try { const response = await axios.get(`${this.baseurl}/crm/v3/objects/companies/${companyId}`, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); this.logresult(response.data); return response.data; } catch (e) { console.error('Error fetching company:', e.response?.data); throw e; } }, associateContactWithCompany: async function (contactId, companyId) { await this.checkToken(); try { const response = await axios.put( `${this.baseurl}/crm/v3/associations/companies/${companyId}/contacts/${contactId}`, {}, { headers: { Authorization: `Bearer ${this.cachedToken}` } } ); console.log('Contact associated with company:', response.data); } catch (e) { console.error('Error associating contact with company:', e.response?.data); } }, deleteCompany: async function (companyId) { await this.checkToken(); try { const response = await axios.delete(`${this.baseurl}/crm/v3/objects/companies/${companyId}`, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); this.logresult(response.data); return response.data; } catch (e) { console.error('Error deleting company:', e.response?.data); throw e; } }, createDeal: async function (dealData) { await this.checkToken(); try { const response = await axios.post(`${this.baseurl}/crm/v3/objects/deals`, dealData, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); this.logresult(response.data); return response.data; } catch (e) { console.error('Error creating deal:', e.response?.data); throw e; } }, updateDeal: async function (dealId, dealData) { await this.checkToken(); try { const response = await axios.patch(`${this.baseurl}/crm/v3/objects/deals/${dealId}`, dealData, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); this.logresult(response.data); return response.data; } catch (e) { console.error('Error updating deal:', e.response?.data); throw e; } }, deleteDeal: async function (dealId) { await this.checkToken(); try { const response = await axios.delete(`${this.baseurl}/crm/v3/objects/deals/${dealId}`, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); this.logresult(response.data); return response.data; } catch (e) { console.error('Error deleting deal:', e.response?.data); throw e; } }, getObjectProperties: async function (object) { await this.checkToken(); try { const response = await axios.get(`${this.baseurl}/crm/v3/properties/${object}/?archived=false`, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); this.logresult(response.data); return response.data; } catch (e) { console.error('Error fetching properties:', e.response?.data); throw e; } }, getFile: async function (fileId) { await this.checkToken(); try { const response = await axios.get(`${this.baseurl}/files/v3/files/${fileId}/signed-url`, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); return response.data; } catch (e) { console.error('Error fetching file:', e.response?.data); } }, getCRMRecord: async function (object, recordId, properties, associations, after, limit) { await this.checkToken(); try { let url=`${this.baseurl}/crm/v3/objects/${object}`; if (recordId) url+=`/${recordId}`; var other=[] if (properties) { let props=properties.join(','); other.push('properties='+props); } // console.log(url); if (associations) { let assoc=associations.join(','); other.push('associations='+assoc); } if (limit) other.push(`limit=${limit}`); if (after) other.push(`after=${after}`); url+='?'+other.join('&'); console.log("getCRMRecord",url); const response = await axios.get(url, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); return response.data; } catch (e) { console.error('Error fetching file:', e.response?.data); } }, createCRMRecord: async function (object, recordData) { await this.checkToken(); try { let url=`${this.baseurl}/crm/v3/objects/${object}`; console.log("createCRMRecord",url); const response = await axios.post(url, recordData, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); return response.data; } catch (e) { console.error('Error fetching file:', e.response?.data); } }, updateCRMRecord: async function (object, recordId, recordData) { await this.checkToken(); try { let url=`${this.baseurl}/crm/v3/objects/${object}/${recordId}`; console.log("updateCRMRecord",url); const response = await axios.patch(url, recordData, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); return response.data; } catch (e) { console.error('Error fetching file:', e.response?.data); } }, deleteCRMRecord: async function (object, recordId) { await this.checkToken(); try { let url=`${this.baseurl}/crm/v3/objects/${object}/${recordId}`; console.log("deleteCRMRecord",url); const response = await axios.delete(url, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); return response.data; } catch (e) { console.error('Error fetching file:', e.response?.data); } }, getBatchCRMRecord: async function (object, recordIds, properties) { await this.checkToken(); try { let url=`${this.baseurl}/crm/v3/objects/${object}/batch/read`; let inputs=[] if (recordIds) { recordIds.forEach( id => { inputs.push( { "id":id } ); }); } let body={ properties: properties, inputs: inputs } // console.log(JSON.stringify(body, null,2)); const response = await axios.post(url, body, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); return response.data; } catch (e) { console.error('Error batch reading:', e.response?.data); } }, createBatchCRMRecord: async function (object, inputs) { await this.checkToken(); try { let url=`${this.baseurl}/crm/v3/objects/${object}/batch/create`; let body={ inputs: inputs } // console.log(JSON.stringify(body, null,2)); const response = await axios.post(url, body, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); return response.data; } catch (e) { console.error('Error batch reading:', e.response?.data); } }, createBatchCRMAssociations: async function (fromObjectType, toObjectType, inputs) { await this.checkToken(); try { let url=`${this.baseurl}/crm/v3/associations/${fromObjectType}/${toObjectType}/batch/create`; let body={ inputs: inputs } console.log("url",url); // console.log(JSON.stringify(body, null,2)); const response = await axios.post(url, body, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); return response.data; } catch (e) { console.error('Error batch reading:', e.response?.data); } }, upsertBatchCRMRecord: async function (object, inputs) { await this.checkToken(); try { let url=`${this.baseurl}/crm/v3/objects/${object}/batch/upsert`; let body={ inputs: inputs } // console.log(JSON.stringify(body, null,2)); const response = await axios.post(url, body, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); return response.data; } catch (e) { console.error('Error batch reading:', e.response?.data); } }, createCustomEvent: async function (timestamp, eventTemplateId, objectId, tokens) { // Timestamps can be in milliseconds epoch time or in ISO 8601 format. // other possible associations: // "email": "a.test.contact@email.com", // "utk": "89b5afb740d41f4cd6651ac5237edf09" await this.checkToken(); try { let url=`${this.baseurl}/crm/v3/timeline/events`; let body={ timestamp: timestamp, eventTemplateId: eventTemplateId, objectId: objectId, tokens: tokens, } const response = await axios.post(url, body, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); return response.data; } catch (e) { console.error('Error batch reading:', e.response?.data); } }, search: async function (objectType, filters = [], properties = [], pagination = { limit: 10, after: null }, query = '') { await this.checkToken(); const url = `${this.baseurl}/crm/v3/objects/${objectType}/search`; const { limit, after } = pagination; try { const requestData = { properties: properties, limit: limit, sorts: [ { "propertyName": "createdate", "direction": "DESCENDING" } ] }; if (filters) requestData.filterGroups=[{ filters }]; // Include the 'after' parameter if it's provided (for paging through results) if (after) { requestData.after = after; } // If a query is provided, include it in the request body if (query) { requestData.query = query; } console.log("body", objectType, requestData); const response = await axios.post(url, requestData, { headers: { Authorization: `Bearer ${this.cachedToken}` } }); this.logresult(response.data); // Return the response along with the paging data (if any) return { total: response.data.total, results: response.data.results, paging: response.data.paging || null // `paging.next.after` can be used for the next request }; } catch (e) { console.error(`Error searching ${objectType}:`, e.response?.data); throw e; } } }; module.exports = hubspot;