@dma-sdk/hubspot 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants/types.d.ts +23 -0
- package/dist/constants/types.js +26 -0
- package/dist/hubspot-service.d.ts +88 -0
- package/dist/hubspot-service.js +182 -0
- package/dist/interfaces/hubspot-type-converter.d.ts +3 -0
- package/dist/interfaces/hubspot-type-converter.js +3 -0
- package/dist/interfaces/token.d.ts +8 -0
- package/dist/interfaces/token.js +2 -0
- package/dist/interfaces/user.d.ts +29 -0
- package/dist/interfaces/user.js +2 -0
- package/dist/types/auth/hs-signature-data.d.ts +11 -0
- package/dist/types/auth/hs-signature-data.js +13 -0
- package/dist/types/object/contact.d.ts +19 -0
- package/dist/types/object/contact.js +21 -0
- package/dist/utils/type-converter.d.ts +2 -0
- package/dist/utils/type-converter.js +15 -0
- package/package.json +19 -0
- package/src/constants/types.ts +23 -0
- package/src/hubspot-service.ts +307 -0
- package/src/interfaces/hubspot-type-converter.ts +4 -0
- package/src/interfaces/token.ts +8 -0
- package/src/interfaces/user.ts +34 -0
- package/src/types/auth/hs-signature-data.ts +13 -0
- package/src/types/object/contact.ts +25 -0
- package/src/utils/type-converter.ts +18 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const hsDataTypes: {
|
|
2
|
+
string: string;
|
|
3
|
+
number: string;
|
|
4
|
+
boolean: string;
|
|
5
|
+
date: string;
|
|
6
|
+
datetime: string;
|
|
7
|
+
single_select: string;
|
|
8
|
+
multiple_select: string;
|
|
9
|
+
checkbox: string;
|
|
10
|
+
calculation: string;
|
|
11
|
+
phone_number: string;
|
|
12
|
+
user: string;
|
|
13
|
+
file: string;
|
|
14
|
+
score: string;
|
|
15
|
+
currency: string;
|
|
16
|
+
rich_text: string;
|
|
17
|
+
owner: string;
|
|
18
|
+
location: string;
|
|
19
|
+
json: string;
|
|
20
|
+
percentage: string;
|
|
21
|
+
ip_address: string;
|
|
22
|
+
video: string;
|
|
23
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hsDataTypes = void 0;
|
|
4
|
+
exports.hsDataTypes = {
|
|
5
|
+
string: "string",
|
|
6
|
+
number: "number",
|
|
7
|
+
boolean: "bool",
|
|
8
|
+
date: "date",
|
|
9
|
+
datetime: "datetime",
|
|
10
|
+
single_select: "enumeration",
|
|
11
|
+
multiple_select: "enumeration",
|
|
12
|
+
checkbox: "bool",
|
|
13
|
+
calculation: "calculation_equation",
|
|
14
|
+
phone_number: "phone_number",
|
|
15
|
+
user: "hubspot_user",
|
|
16
|
+
file: "file",
|
|
17
|
+
score: "score",
|
|
18
|
+
currency: "currency",
|
|
19
|
+
rich_text: "rich_text",
|
|
20
|
+
owner: "owner",
|
|
21
|
+
location: "location",
|
|
22
|
+
json: "json",
|
|
23
|
+
percentage: "number",
|
|
24
|
+
ip_address: "ip_address",
|
|
25
|
+
video: "video"
|
|
26
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { HubspotUser } from './interfaces/user';
|
|
2
|
+
import { Configuration, Filter } from '@hubspot/api-client/lib/codegen/crm/objects';
|
|
3
|
+
import { SimplePublicObjectInputForCreate } from '@hubspot/api-client/lib/codegen/crm/objects/taxes/models/all';
|
|
4
|
+
import { SimplePublicObject } from '@hubspot/api-client/lib/codegen/crm/objects/tasks/models/all';
|
|
5
|
+
import { LabelsBetweenObjectPair } from '@hubspot/api-client/lib/codegen/crm/associations/v4';
|
|
6
|
+
import { TokenResponseIF } from '@hubspot/api-client/lib/codegen/oauth';
|
|
7
|
+
import { TokenResponse } from './interfaces/token';
|
|
8
|
+
import { CollectionResponsePropertyNoPaging, PropertyCreate, PropertyGroup, PropertyGroupCreate } from '@hubspot/api-client/lib/codegen/crm/properties';
|
|
9
|
+
import { HubspotSignatureData } from './types/auth/hs-signature-data';
|
|
10
|
+
export declare class HubspotService {
|
|
11
|
+
static HS_BASE_URL: string;
|
|
12
|
+
static readonly hubspotObjectType: {
|
|
13
|
+
contacts: string;
|
|
14
|
+
notes: string;
|
|
15
|
+
};
|
|
16
|
+
static readonly hubspotPropertyTypes: {
|
|
17
|
+
string: string;
|
|
18
|
+
number: string;
|
|
19
|
+
boolean: string;
|
|
20
|
+
date: string;
|
|
21
|
+
datetime: string;
|
|
22
|
+
single_select: string;
|
|
23
|
+
multiple_select: string;
|
|
24
|
+
checkbox: string;
|
|
25
|
+
calculation: string;
|
|
26
|
+
phone_number: string;
|
|
27
|
+
user: string;
|
|
28
|
+
file: string;
|
|
29
|
+
score: string;
|
|
30
|
+
currency: string;
|
|
31
|
+
rich_text: string;
|
|
32
|
+
owner: string;
|
|
33
|
+
location: string;
|
|
34
|
+
json: string;
|
|
35
|
+
percentage: string;
|
|
36
|
+
ip_address: string;
|
|
37
|
+
video: string;
|
|
38
|
+
};
|
|
39
|
+
private client;
|
|
40
|
+
private hsRedirectUriOauth;
|
|
41
|
+
private hsClientId;
|
|
42
|
+
private hsClientSecret;
|
|
43
|
+
private typeConvert;
|
|
44
|
+
private apiMethods;
|
|
45
|
+
constructor(hsRedirectUriOauth: string, hsClientId: string, hsClientSecret: string);
|
|
46
|
+
exchangeCodeForTokens(params: {
|
|
47
|
+
code: string;
|
|
48
|
+
}): Promise<TokenResponseIF>;
|
|
49
|
+
getUserInfoByToken(accessToken: string): Promise<HubspotUser>;
|
|
50
|
+
verifyFetchReqSignature(params: {
|
|
51
|
+
version: string;
|
|
52
|
+
data: HubspotSignatureData;
|
|
53
|
+
}): Promise<boolean>;
|
|
54
|
+
refreshAccessToken(refreshToken: string): Promise<TokenResponse>;
|
|
55
|
+
listObjectProperties(params: {
|
|
56
|
+
objectType: string;
|
|
57
|
+
types?: string[];
|
|
58
|
+
accessToken?: string;
|
|
59
|
+
}): Promise<CollectionResponsePropertyNoPaging>;
|
|
60
|
+
listObjects(objectType: string, filters: Filter[], accessToken: string): Promise<SimplePublicObject[] | null>;
|
|
61
|
+
createObject(objectType: string, properties: SimplePublicObjectInputForCreate, accessToken: string): Promise<SimplePublicObject | {
|
|
62
|
+
id: string;
|
|
63
|
+
}>;
|
|
64
|
+
getObject(objectType: string, objectId: string, accessToken: string): Promise<SimplePublicObject>;
|
|
65
|
+
getObjectProps(objectType: string, objectId: string, props: string[], // nome delle props da restituire
|
|
66
|
+
accessToken: string): Promise<SimplePublicObject>;
|
|
67
|
+
getObjectWithPropsType(objectType: string, objectId: string, accessToken: string, types?: string[]): Promise<SimplePublicObject & {
|
|
68
|
+
propertiesWithTypes: {
|
|
69
|
+
[key: string]: {
|
|
70
|
+
type: string;
|
|
71
|
+
label: string;
|
|
72
|
+
value: string | null;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
}>;
|
|
76
|
+
updateObject(objectType: string, objectId: string, properties: {
|
|
77
|
+
[key: string]: string;
|
|
78
|
+
}, accessToken: string, options?: Configuration): Promise<SimplePublicObject>;
|
|
79
|
+
associateObjects(objectTypeFirst: string, objectTypeSecond: string, objectIdFirst: string, objectIdSecond: string, accessToken: string): Promise<LabelsBetweenObjectPair>;
|
|
80
|
+
createObjectProperties(objectType: string, properties: PropertyCreate[], accessToken: string): Promise<any>;
|
|
81
|
+
createPropertyGroup(objectType: string, group: PropertyGroupCreate, accessToken: string): Promise<PropertyGroup>;
|
|
82
|
+
convertPropertyValues(properties: {
|
|
83
|
+
[key: string]: any;
|
|
84
|
+
}, convertionMap: {
|
|
85
|
+
[propertyName: string]: PropertyCreate;
|
|
86
|
+
}): Record<string, any>;
|
|
87
|
+
private fetchApi;
|
|
88
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HubspotService = void 0;
|
|
4
|
+
const api_client_1 = require("@hubspot/api-client");
|
|
5
|
+
const type_converter_1 = require("./utils/type-converter");
|
|
6
|
+
const types_1 = require("./constants/types");
|
|
7
|
+
const api_client_2 = require("@hubspot/api-client");
|
|
8
|
+
const fetch_1 = require("@dma-sdk/utils/src/fetch");
|
|
9
|
+
// Per ogni metodo che viene creato, è fondamentale che
|
|
10
|
+
// il client venga inizializzato con il token di accesso
|
|
11
|
+
// altrimenti non funzionerà correttamente.
|
|
12
|
+
class HubspotService {
|
|
13
|
+
constructor(hsRedirectUriOauth, hsClientId, hsClientSecret) {
|
|
14
|
+
this.apiMethods = {
|
|
15
|
+
getUserInfoByToken: '/oauth/v1/access-tokens',
|
|
16
|
+
refreshToken: '/oauth/v1/token',
|
|
17
|
+
createObjectProperties: '/crm/v3/properties/{objectType}/batch/create',
|
|
18
|
+
};
|
|
19
|
+
;
|
|
20
|
+
(this.hsRedirectUriOauth = hsRedirectUriOauth),
|
|
21
|
+
(this.hsClientId = hsClientId),
|
|
22
|
+
(this.hsClientSecret = hsClientSecret);
|
|
23
|
+
this.typeConvert = type_converter_1.typeConverter;
|
|
24
|
+
this.client = new api_client_1.Client();
|
|
25
|
+
}
|
|
26
|
+
async exchangeCodeForTokens(params) {
|
|
27
|
+
return await this.client.oauth.tokensApi.create('authorization_code', params.code, this.hsRedirectUriOauth, this.hsClientId, this.hsClientSecret);
|
|
28
|
+
}
|
|
29
|
+
async getUserInfoByToken(accessToken) {
|
|
30
|
+
const response = await this.client.apiRequest({
|
|
31
|
+
method: 'GET',
|
|
32
|
+
path: `${this.apiMethods.getUserInfoByToken}/${accessToken}`,
|
|
33
|
+
});
|
|
34
|
+
const jsonBody = await response.json();
|
|
35
|
+
const userAttributes = {
|
|
36
|
+
token: jsonBody.token,
|
|
37
|
+
user: jsonBody.user,
|
|
38
|
+
hubDomain: jsonBody.hub_domain,
|
|
39
|
+
scopes: jsonBody.scopes,
|
|
40
|
+
hubId: jsonBody.hub_id,
|
|
41
|
+
appId: jsonBody.app_id,
|
|
42
|
+
expiresIn: jsonBody.expires_in,
|
|
43
|
+
userId: jsonBody.user_id,
|
|
44
|
+
tokenType: jsonBody.token_type,
|
|
45
|
+
signedAccessToken: jsonBody.signed_access_token,
|
|
46
|
+
};
|
|
47
|
+
return userAttributes;
|
|
48
|
+
}
|
|
49
|
+
async verifyFetchReqSignature(params) {
|
|
50
|
+
const queryString = (params.data.qs && Object.keys(params.data.qs).length > 0) ? `?${new URLSearchParams(params.data.qs).toString()}` : '';
|
|
51
|
+
const url = decodeURIComponent(`https://${params.data.domainName}${params.data.path}${queryString}`);
|
|
52
|
+
return api_client_2.Signature.isValid({
|
|
53
|
+
method: params.data.httpMethod,
|
|
54
|
+
signatureVersion: params.version,
|
|
55
|
+
signature: params.data.signature,
|
|
56
|
+
requestBody: params.data.body,
|
|
57
|
+
timestamp: params.data.timestamp,
|
|
58
|
+
url,
|
|
59
|
+
clientSecret: this.hsClientSecret,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async refreshAccessToken(refreshToken) {
|
|
63
|
+
const token = await this.client.oauth.tokensApi.create('refresh_token', undefined, this.hsRedirectUriOauth, this.hsClientId, this.hsClientSecret, refreshToken);
|
|
64
|
+
return {
|
|
65
|
+
accessToken: token.accessToken,
|
|
66
|
+
expiresIn: token.expiresIn,
|
|
67
|
+
refreshToken: token.refreshToken,
|
|
68
|
+
tokenType: token.tokenType,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async listObjectProperties(params) {
|
|
72
|
+
const { objectType, types, accessToken } = params;
|
|
73
|
+
if (accessToken)
|
|
74
|
+
this.client.setAccessToken(accessToken);
|
|
75
|
+
const props = await this.client.crm.properties.coreApi.getAll(objectType);
|
|
76
|
+
if (!types)
|
|
77
|
+
return props;
|
|
78
|
+
return { results: props.results.filter((prop) => types.includes(prop.fieldType) || types.includes(prop.type)) };
|
|
79
|
+
}
|
|
80
|
+
async listObjects(objectType, filters, accessToken) {
|
|
81
|
+
this.client.setAccessToken(accessToken);
|
|
82
|
+
const response = await this.client.crm.objects.searchApi.doSearch(objectType, {
|
|
83
|
+
filterGroups: [
|
|
84
|
+
{
|
|
85
|
+
filters: filters,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
return response.results || null;
|
|
90
|
+
}
|
|
91
|
+
async createObject(objectType, properties, accessToken) {
|
|
92
|
+
this.client.setAccessToken(accessToken);
|
|
93
|
+
const response = await this.client.crm.objects.basicApi.create(objectType, properties);
|
|
94
|
+
return response;
|
|
95
|
+
}
|
|
96
|
+
async getObject(objectType, objectId, accessToken) {
|
|
97
|
+
this.client.setAccessToken(accessToken);
|
|
98
|
+
return await this.client.crm.objects.basicApi.getById(objectType, objectId);
|
|
99
|
+
}
|
|
100
|
+
async getObjectProps(objectType, objectId, props, // nome delle props da restituire
|
|
101
|
+
accessToken) {
|
|
102
|
+
this.client.setAccessToken(accessToken);
|
|
103
|
+
return await this.client.crm.objects.basicApi.getById(objectType, objectId, props);
|
|
104
|
+
}
|
|
105
|
+
async getObjectWithPropsType(objectType, objectId, accessToken, types) {
|
|
106
|
+
this.client.setAccessToken(accessToken);
|
|
107
|
+
const props = await this.listObjectProperties({ objectType, types });
|
|
108
|
+
const data = await this.getObjectProps(objectType, objectId, props.results.map((prop) => prop.name), accessToken);
|
|
109
|
+
let formattedProp = {};
|
|
110
|
+
props.results.forEach((property) => {
|
|
111
|
+
if (!data.properties[property.name]) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
formattedProp[property.name] = {
|
|
115
|
+
type: property.fieldType,
|
|
116
|
+
label: property.label,
|
|
117
|
+
value: data.properties[property.name]
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
...data,
|
|
122
|
+
propertiesWithTypes: formattedProp
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async updateObject(objectType, objectId, properties, accessToken, options) {
|
|
126
|
+
this.client.setAccessToken(accessToken);
|
|
127
|
+
const response = await this.client.crm.objects.basicApi.update(objectType, objectId, {
|
|
128
|
+
properties: {
|
|
129
|
+
...properties
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
return response;
|
|
133
|
+
}
|
|
134
|
+
async associateObjects(objectTypeFirst, objectTypeSecond, objectIdFirst, objectIdSecond, accessToken) {
|
|
135
|
+
this.client.setAccessToken(accessToken);
|
|
136
|
+
const response = await this.client.crm.associations.v4.basicApi.create(objectTypeFirst, objectIdFirst, objectTypeSecond, objectIdSecond, []);
|
|
137
|
+
return response;
|
|
138
|
+
}
|
|
139
|
+
async createObjectProperties(objectType, properties, accessToken) {
|
|
140
|
+
const options = {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
headers: {
|
|
143
|
+
Authorization: `Bearer ${accessToken}`,
|
|
144
|
+
'Content-Type': 'application/json'
|
|
145
|
+
},
|
|
146
|
+
body: JSON.stringify({
|
|
147
|
+
inputs: properties
|
|
148
|
+
})
|
|
149
|
+
};
|
|
150
|
+
const response = await this.fetchApi(this.apiMethods.createObjectProperties.replace('{objectType}', objectType), options);
|
|
151
|
+
return await response.json();
|
|
152
|
+
}
|
|
153
|
+
async createPropertyGroup(objectType, group, accessToken) {
|
|
154
|
+
this.client.setAccessToken(accessToken);
|
|
155
|
+
return await this.client.crm.properties.groupsApi.create(objectType, group);
|
|
156
|
+
}
|
|
157
|
+
convertPropertyValues(properties, convertionMap) {
|
|
158
|
+
let convertedProps = {};
|
|
159
|
+
Object.entries(properties).forEach(([key, value]) => {
|
|
160
|
+
var _a;
|
|
161
|
+
if (value === undefined)
|
|
162
|
+
return;
|
|
163
|
+
const convertionFunc = this.typeConvert[(_a = convertionMap[key]) === null || _a === void 0 ? void 0 : _a.type];
|
|
164
|
+
if (convertionFunc === undefined) {
|
|
165
|
+
convertedProps[key] = value;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
convertedProps[key] = convertionFunc(value);
|
|
169
|
+
});
|
|
170
|
+
return convertedProps;
|
|
171
|
+
}
|
|
172
|
+
async fetchApi(endpoint, options, queryParams) {
|
|
173
|
+
return await (0, fetch_1.fetchApi)(HubspotService.HS_BASE_URL, endpoint, options, queryParams);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
exports.HubspotService = HubspotService;
|
|
177
|
+
HubspotService.HS_BASE_URL = 'https://api.hubapi.com';
|
|
178
|
+
HubspotService.hubspotObjectType = {
|
|
179
|
+
contacts: 'contacts',
|
|
180
|
+
notes: 'notes',
|
|
181
|
+
};
|
|
182
|
+
HubspotService.hubspotPropertyTypes = types_1.hsDataTypes;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface HubspotUser {
|
|
2
|
+
token: string;
|
|
3
|
+
user: string;
|
|
4
|
+
hubDomain: string;
|
|
5
|
+
scopes: string[];
|
|
6
|
+
hubId: string;
|
|
7
|
+
appId: number;
|
|
8
|
+
expiresIn: number;
|
|
9
|
+
userId: number;
|
|
10
|
+
tokenType: string;
|
|
11
|
+
signedAccessToken: string;
|
|
12
|
+
}
|
|
13
|
+
export interface HubspotFetchUser {
|
|
14
|
+
hubId: string;
|
|
15
|
+
email: string;
|
|
16
|
+
userId: number;
|
|
17
|
+
}
|
|
18
|
+
export interface ContextHubspotUser {
|
|
19
|
+
token: string;
|
|
20
|
+
user: string;
|
|
21
|
+
hubDomain: string;
|
|
22
|
+
scopes: string[];
|
|
23
|
+
hubId: string;
|
|
24
|
+
appId: number;
|
|
25
|
+
expiresIn: number;
|
|
26
|
+
userId: number;
|
|
27
|
+
tokenType: string;
|
|
28
|
+
signedAccessToken: string;
|
|
29
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const hubspotSignatureDataSchema: z.ZodObject<{
|
|
3
|
+
signature: z.ZodString;
|
|
4
|
+
timestamp: z.ZodCoercedNumber<unknown>;
|
|
5
|
+
domainName: z.ZodString;
|
|
6
|
+
path: z.ZodString;
|
|
7
|
+
httpMethod: z.ZodString;
|
|
8
|
+
body: z.ZodString;
|
|
9
|
+
qs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
export type HubspotSignatureData = z.infer<typeof hubspotSignatureDataSchema>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hubspotSignatureDataSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
exports.hubspotSignatureDataSchema = zod_1.z.object({
|
|
6
|
+
signature: zod_1.z.string(),
|
|
7
|
+
timestamp: zod_1.z.coerce.number(),
|
|
8
|
+
domainName: zod_1.z.string(),
|
|
9
|
+
path: zod_1.z.string(),
|
|
10
|
+
httpMethod: zod_1.z.string(),
|
|
11
|
+
body: zod_1.z.string(),
|
|
12
|
+
qs: zod_1.z.record(zod_1.z.string(), zod_1.z.string()).optional(),
|
|
13
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const contactSchema: z.ZodObject<{
|
|
3
|
+
createdAt: z.ZodDate;
|
|
4
|
+
id: z.ZodString;
|
|
5
|
+
properties: z.ZodRecord<z.ZodString, z.ZodAny>;
|
|
6
|
+
updatedAt: z.ZodOptional<z.ZodDate>;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export declare const contactWithPropsTypeSchema: z.ZodObject<{
|
|
9
|
+
id: z.ZodString;
|
|
10
|
+
propertiesWithTypes: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
11
|
+
type: z.ZodString;
|
|
12
|
+
label: z.ZodString;
|
|
13
|
+
value: z.ZodNullable<z.ZodString>;
|
|
14
|
+
}, z.core.$strip>>;
|
|
15
|
+
createdAt: z.ZodDate;
|
|
16
|
+
updatedAt: z.ZodOptional<z.ZodDate>;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
export type ContactGet = z.infer<typeof contactSchema>;
|
|
19
|
+
export type ContactWithPropsTypeGet = z.infer<typeof contactWithPropsTypeSchema>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.contactWithPropsTypeSchema = exports.contactSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
exports.contactSchema = zod_1.z.object({
|
|
6
|
+
createdAt: zod_1.z.date(),
|
|
7
|
+
id: zod_1.z.string(),
|
|
8
|
+
properties: zod_1.z.record(zod_1.z.string(), zod_1.z.any()),
|
|
9
|
+
updatedAt: zod_1.z.date().optional(),
|
|
10
|
+
});
|
|
11
|
+
exports.contactWithPropsTypeSchema = exports.contactSchema
|
|
12
|
+
.extend({
|
|
13
|
+
propertiesWithTypes: zod_1.z.record(zod_1.z.string(), zod_1.z.object({
|
|
14
|
+
type: zod_1.z.string(),
|
|
15
|
+
label: zod_1.z.string(),
|
|
16
|
+
value: zod_1.z.string().nullable()
|
|
17
|
+
})),
|
|
18
|
+
})
|
|
19
|
+
.omit({
|
|
20
|
+
properties: true
|
|
21
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.typeConverter = void 0;
|
|
4
|
+
const types_1 = require("../constants/types");
|
|
5
|
+
exports.typeConverter = {
|
|
6
|
+
[types_1.hsDataTypes.date]: (data) => {
|
|
7
|
+
return (new Date(data)).setUTCHours(0, 0, 0, 0);
|
|
8
|
+
},
|
|
9
|
+
[types_1.hsDataTypes.datetime]: (data) => {
|
|
10
|
+
return new Date(data).getTime();
|
|
11
|
+
},
|
|
12
|
+
[types_1.hsDataTypes.number]: (data) => {
|
|
13
|
+
return (typeof data === "number") ? data : parseFloat(data);
|
|
14
|
+
}
|
|
15
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dma-sdk/hubspot",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "DMA SDK – Hubspot service",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepare": "npm run build"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@dma-sdk/utils": "^1.0.0",
|
|
13
|
+
"@hubspot/api-client": "^13.4.0",
|
|
14
|
+
"zod": "^4.3.5"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"typescript": "^5.2.0"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const hsDataTypes = {
|
|
2
|
+
string: "string",
|
|
3
|
+
number: "number",
|
|
4
|
+
boolean: "bool",
|
|
5
|
+
date: "date",
|
|
6
|
+
datetime: "datetime",
|
|
7
|
+
single_select: "enumeration",
|
|
8
|
+
multiple_select: "enumeration",
|
|
9
|
+
checkbox: "bool",
|
|
10
|
+
calculation: "calculation_equation",
|
|
11
|
+
phone_number: "phone_number",
|
|
12
|
+
user: "hubspot_user",
|
|
13
|
+
file: "file",
|
|
14
|
+
score: "score",
|
|
15
|
+
currency: "currency",
|
|
16
|
+
rich_text: "rich_text",
|
|
17
|
+
owner: "owner",
|
|
18
|
+
location: "location",
|
|
19
|
+
json: "json",
|
|
20
|
+
percentage: "number",
|
|
21
|
+
ip_address: "ip_address",
|
|
22
|
+
video: "video"
|
|
23
|
+
};
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { Client as HubspotClient } from '@hubspot/api-client'
|
|
2
|
+
import { HubspotUser } from './interfaces/user'
|
|
3
|
+
import { Configuration, Filter } from '@hubspot/api-client/lib/codegen/crm/objects'
|
|
4
|
+
import { SimplePublicObjectInputForCreate } from '@hubspot/api-client/lib/codegen/crm/objects/taxes/models/all'
|
|
5
|
+
import { SimplePublicObject } from '@hubspot/api-client/lib/codegen/crm/objects/tasks/models/all'
|
|
6
|
+
import { LabelsBetweenObjectPair } from '@hubspot/api-client/lib/codegen/crm/associations/v4'
|
|
7
|
+
import { TokenResponseIF } from '@hubspot/api-client/lib/codegen/oauth'
|
|
8
|
+
import { typeConverter } from './utils/type-converter'
|
|
9
|
+
import { HubspotTypeConverter } from './interfaces/hubspot-type-converter'
|
|
10
|
+
import { hsDataTypes } from './constants/types'
|
|
11
|
+
import { Signature } from '@hubspot/api-client'
|
|
12
|
+
import { TokenResponse } from './interfaces/token'
|
|
13
|
+
import { CollectionResponsePropertyNoPaging, PropertyCreate, PropertyGroup, PropertyGroupCreate } from '@hubspot/api-client/lib/codegen/crm/properties'
|
|
14
|
+
import { HubspotSignatureData } from './types/auth/hs-signature-data'
|
|
15
|
+
import { fetchApi } from '@dma-sdk/utils/src/fetch'
|
|
16
|
+
|
|
17
|
+
// Per ogni metodo che viene creato, è fondamentale che
|
|
18
|
+
// il client venga inizializzato con il token di accesso
|
|
19
|
+
// altrimenti non funzionerà correttamente.
|
|
20
|
+
|
|
21
|
+
export class HubspotService {
|
|
22
|
+
public static HS_BASE_URL = 'https://api.hubapi.com'
|
|
23
|
+
public static readonly hubspotObjectType = {
|
|
24
|
+
contacts: 'contacts',
|
|
25
|
+
notes: 'notes',
|
|
26
|
+
}
|
|
27
|
+
public static readonly hubspotPropertyTypes = hsDataTypes
|
|
28
|
+
|
|
29
|
+
private client: HubspotClient
|
|
30
|
+
private hsRedirectUriOauth: string
|
|
31
|
+
private hsClientId: string
|
|
32
|
+
private hsClientSecret: string
|
|
33
|
+
private typeConvert: HubspotTypeConverter
|
|
34
|
+
|
|
35
|
+
private apiMethods = {
|
|
36
|
+
getUserInfoByToken: '/oauth/v1/access-tokens',
|
|
37
|
+
refreshToken: '/oauth/v1/token',
|
|
38
|
+
createObjectProperties: '/crm/v3/properties/{objectType}/batch/create',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
constructor(hsRedirectUriOauth: string, hsClientId: string, hsClientSecret: string) {
|
|
43
|
+
;(this.hsRedirectUriOauth = hsRedirectUriOauth),
|
|
44
|
+
(this.hsClientId = hsClientId),
|
|
45
|
+
(this.hsClientSecret = hsClientSecret)
|
|
46
|
+
this.typeConvert = typeConverter
|
|
47
|
+
|
|
48
|
+
this.client = new HubspotClient()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async exchangeCodeForTokens(params: { code: string }): Promise<TokenResponseIF> {
|
|
52
|
+
return await this.client.oauth.tokensApi.create(
|
|
53
|
+
'authorization_code',
|
|
54
|
+
params.code,
|
|
55
|
+
this.hsRedirectUriOauth,
|
|
56
|
+
this.hsClientId,
|
|
57
|
+
this.hsClientSecret
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async getUserInfoByToken(accessToken: string): Promise<HubspotUser> {
|
|
62
|
+
const response = await this.client.apiRequest({
|
|
63
|
+
method: 'GET',
|
|
64
|
+
path: `${this.apiMethods.getUserInfoByToken}/${accessToken}`,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const jsonBody = await response.json()
|
|
68
|
+
const userAttributes = {
|
|
69
|
+
token: jsonBody.token,
|
|
70
|
+
user: jsonBody.user,
|
|
71
|
+
hubDomain: jsonBody.hub_domain,
|
|
72
|
+
scopes: jsonBody.scopes,
|
|
73
|
+
hubId: jsonBody.hub_id,
|
|
74
|
+
appId: jsonBody.app_id,
|
|
75
|
+
expiresIn: jsonBody.expires_in,
|
|
76
|
+
userId: jsonBody.user_id,
|
|
77
|
+
tokenType: jsonBody.token_type,
|
|
78
|
+
signedAccessToken: jsonBody.signed_access_token,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return userAttributes
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async verifyFetchReqSignature(params: { version: string, data: HubspotSignatureData }): Promise<boolean> {
|
|
85
|
+
const queryString: string = (params.data.qs && Object.keys(params.data.qs).length > 0) ? `?${new URLSearchParams(params.data.qs).toString()}` : ''
|
|
86
|
+
const url: string = decodeURIComponent(`https://${params.data.domainName}${params.data.path}${queryString}`)
|
|
87
|
+
|
|
88
|
+
return Signature.isValid({
|
|
89
|
+
method: params.data.httpMethod,
|
|
90
|
+
signatureVersion: params.version,
|
|
91
|
+
signature: params.data.signature,
|
|
92
|
+
requestBody: params.data.body,
|
|
93
|
+
timestamp: params.data.timestamp,
|
|
94
|
+
url,
|
|
95
|
+
clientSecret: this.hsClientSecret,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async refreshAccessToken(refreshToken: string): Promise<TokenResponse> {
|
|
100
|
+
const token = await this.client.oauth.tokensApi.create(
|
|
101
|
+
'refresh_token',
|
|
102
|
+
undefined,
|
|
103
|
+
this.hsRedirectUriOauth,
|
|
104
|
+
this.hsClientId,
|
|
105
|
+
this.hsClientSecret,
|
|
106
|
+
refreshToken
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
accessToken: token.accessToken,
|
|
111
|
+
expiresIn: token.expiresIn,
|
|
112
|
+
refreshToken: token.refreshToken,
|
|
113
|
+
tokenType: token.tokenType,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async listObjectProperties(params: {
|
|
119
|
+
objectType: string,
|
|
120
|
+
types?: string[], // tipi di props da restituire
|
|
121
|
+
accessToken?: string
|
|
122
|
+
}): Promise<CollectionResponsePropertyNoPaging> {
|
|
123
|
+
const { objectType, types, accessToken } = params
|
|
124
|
+
if(accessToken) this.client.setAccessToken(accessToken)
|
|
125
|
+
const props = await this.client.crm.properties.coreApi.getAll(objectType);
|
|
126
|
+
|
|
127
|
+
if(!types) return props
|
|
128
|
+
|
|
129
|
+
return { results: props.results.filter((prop) => types.includes(prop.fieldType) || types.includes(prop.type)) }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async listObjects(
|
|
133
|
+
objectType: string,
|
|
134
|
+
filters: Filter[],
|
|
135
|
+
accessToken: string
|
|
136
|
+
): Promise<SimplePublicObject[] | null> {
|
|
137
|
+
this.client.setAccessToken(accessToken)
|
|
138
|
+
const response = await this.client.crm.objects.searchApi.doSearch(objectType, {
|
|
139
|
+
filterGroups: [
|
|
140
|
+
{
|
|
141
|
+
filters: filters,
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
return response.results || null
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async createObject(
|
|
150
|
+
objectType: string,
|
|
151
|
+
properties: SimplePublicObjectInputForCreate,
|
|
152
|
+
accessToken: string
|
|
153
|
+
): Promise<SimplePublicObject | { id: string }> {
|
|
154
|
+
this.client.setAccessToken(accessToken)
|
|
155
|
+
const response = await this.client.crm.objects.basicApi.create(objectType, properties)
|
|
156
|
+
|
|
157
|
+
return response
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async getObject(
|
|
161
|
+
objectType: string,
|
|
162
|
+
objectId: string,
|
|
163
|
+
accessToken: string
|
|
164
|
+
): Promise<SimplePublicObject> {
|
|
165
|
+
this.client.setAccessToken(accessToken)
|
|
166
|
+
return await this.client.crm.objects.basicApi.getById(
|
|
167
|
+
objectType,
|
|
168
|
+
objectId
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async getObjectProps(
|
|
173
|
+
objectType: string,
|
|
174
|
+
objectId: string,
|
|
175
|
+
props: string[], // nome delle props da restituire
|
|
176
|
+
accessToken: string
|
|
177
|
+
): Promise<SimplePublicObject> {
|
|
178
|
+
this.client.setAccessToken(accessToken)
|
|
179
|
+
return await this.client.crm.objects.basicApi.getById(
|
|
180
|
+
objectType,
|
|
181
|
+
objectId,
|
|
182
|
+
props
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async getObjectWithPropsType(
|
|
187
|
+
objectType: string,
|
|
188
|
+
objectId: string,
|
|
189
|
+
accessToken: string,
|
|
190
|
+
types?: string[]
|
|
191
|
+
): Promise<SimplePublicObject & { propertiesWithTypes: { [key: string]: { type: string, label: string, value: string | null } }}> {
|
|
192
|
+
this.client.setAccessToken(accessToken)
|
|
193
|
+
const props = await this.listObjectProperties({ objectType, types })
|
|
194
|
+
const data = await this.getObjectProps(
|
|
195
|
+
objectType,
|
|
196
|
+
objectId,
|
|
197
|
+
props.results.map((prop) => prop.name),
|
|
198
|
+
accessToken
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
let formattedProp: { [key: string]: { type: string, label: string, value: string | null } } = {}
|
|
203
|
+
props.results.forEach((property) => {
|
|
204
|
+
if(!data.properties[property.name]){
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
formattedProp[property.name] = {
|
|
209
|
+
type: property.fieldType,
|
|
210
|
+
label: property.label,
|
|
211
|
+
value: data.properties[property.name]
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
...data,
|
|
217
|
+
propertiesWithTypes: formattedProp
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async updateObject(
|
|
222
|
+
objectType: string,
|
|
223
|
+
objectId: string,
|
|
224
|
+
properties: {
|
|
225
|
+
[key: string]: string;
|
|
226
|
+
},
|
|
227
|
+
accessToken: string,
|
|
228
|
+
options?: Configuration
|
|
229
|
+
): Promise<SimplePublicObject> {
|
|
230
|
+
this.client.setAccessToken(accessToken)
|
|
231
|
+
const response = await this.client.crm.objects.basicApi.update(objectType, objectId, {
|
|
232
|
+
properties: {
|
|
233
|
+
...properties
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
return response
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async associateObjects(
|
|
241
|
+
objectTypeFirst: string,
|
|
242
|
+
objectTypeSecond: string,
|
|
243
|
+
objectIdFirst: string,
|
|
244
|
+
objectIdSecond: string,
|
|
245
|
+
accessToken: string
|
|
246
|
+
): Promise<LabelsBetweenObjectPair> {
|
|
247
|
+
this.client.setAccessToken(accessToken)
|
|
248
|
+
const response = await this.client.crm.associations.v4.basicApi.create(
|
|
249
|
+
objectTypeFirst,
|
|
250
|
+
objectIdFirst,
|
|
251
|
+
objectTypeSecond,
|
|
252
|
+
objectIdSecond,
|
|
253
|
+
[]
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
return response
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async createObjectProperties(
|
|
260
|
+
objectType: string,
|
|
261
|
+
properties: PropertyCreate[],
|
|
262
|
+
accessToken: string
|
|
263
|
+
) {
|
|
264
|
+
const options = {
|
|
265
|
+
method: 'POST',
|
|
266
|
+
headers: {
|
|
267
|
+
Authorization: `Bearer ${accessToken}`,
|
|
268
|
+
'Content-Type': 'application/json'
|
|
269
|
+
},
|
|
270
|
+
body: JSON.stringify({
|
|
271
|
+
inputs: properties
|
|
272
|
+
})
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const response = await this.fetchApi(this.apiMethods.createObjectProperties.replace('{objectType}', objectType), options)
|
|
276
|
+
return await response.json()
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async createPropertyGroup(
|
|
280
|
+
objectType: string,
|
|
281
|
+
group: PropertyGroupCreate,
|
|
282
|
+
accessToken: string
|
|
283
|
+
): Promise<PropertyGroup> {
|
|
284
|
+
this.client.setAccessToken(accessToken)
|
|
285
|
+
return await this.client.crm.properties.groupsApi.create(objectType, group);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
public convertPropertyValues(properties: { [key: string]: any }, convertionMap: { [propertyName: string]: PropertyCreate }) {
|
|
289
|
+
let convertedProps: Record<string, any> = {}
|
|
290
|
+
Object.entries(properties).forEach(([key, value]) => {
|
|
291
|
+
if(value === undefined) return
|
|
292
|
+
const convertionFunc = this.typeConvert[convertionMap[key]?.type]
|
|
293
|
+
if(convertionFunc === undefined) {
|
|
294
|
+
convertedProps[key] = value
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
convertedProps[key] = convertionFunc(value)
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
return convertedProps
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private async fetchApi(endpoint: string, options: {}, queryParams?: {}): Promise<any> {
|
|
305
|
+
return await fetchApi(HubspotService.HS_BASE_URL, endpoint, options, queryParams)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface HubspotUser {
|
|
2
|
+
token: string;
|
|
3
|
+
user: string;
|
|
4
|
+
hubDomain: string;
|
|
5
|
+
scopes: string[];
|
|
6
|
+
hubId: string;
|
|
7
|
+
appId: number;
|
|
8
|
+
expiresIn: number;
|
|
9
|
+
userId: number;
|
|
10
|
+
tokenType: string;
|
|
11
|
+
signedAccessToken: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export interface HubspotFetchUser {
|
|
16
|
+
hubId: string,
|
|
17
|
+
email: string,
|
|
18
|
+
userId: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export interface ContextHubspotUser {
|
|
23
|
+
token: string;
|
|
24
|
+
user: string;
|
|
25
|
+
hubDomain: string;
|
|
26
|
+
scopes: string[];
|
|
27
|
+
hubId: string;
|
|
28
|
+
appId: number;
|
|
29
|
+
expiresIn: number;
|
|
30
|
+
userId: number;
|
|
31
|
+
tokenType: string;
|
|
32
|
+
signedAccessToken: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const hubspotSignatureDataSchema = z.object({
|
|
4
|
+
signature: z.string(),
|
|
5
|
+
timestamp: z.coerce.number(),
|
|
6
|
+
domainName: z.string(),
|
|
7
|
+
path: z.string(),
|
|
8
|
+
httpMethod: z.string(),
|
|
9
|
+
body: z.string(),
|
|
10
|
+
qs: z.record(z.string(), z.string()).optional(),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export type HubspotSignatureData = z.infer<typeof hubspotSignatureDataSchema>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const contactSchema = z.object({
|
|
4
|
+
createdAt: z.date(),
|
|
5
|
+
id: z.string(),
|
|
6
|
+
properties: z.record(z.string(), z.any()),
|
|
7
|
+
updatedAt: z.date().optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export const contactWithPropsTypeSchema = contactSchema
|
|
12
|
+
.extend({
|
|
13
|
+
propertiesWithTypes: z.record(z.string(), z.object({
|
|
14
|
+
type: z.string(),
|
|
15
|
+
label: z.string(),
|
|
16
|
+
value: z.string().nullable()
|
|
17
|
+
})),
|
|
18
|
+
})
|
|
19
|
+
.omit({
|
|
20
|
+
properties: true
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
export type ContactGet = z.infer<typeof contactSchema>
|
|
25
|
+
export type ContactWithPropsTypeGet = z.infer<typeof contactWithPropsTypeSchema>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { hsDataTypes } from "../constants/types";
|
|
2
|
+
import { HubspotTypeConverter } from "../interfaces/hubspot-type-converter";
|
|
3
|
+
import { string } from "zod";
|
|
4
|
+
|
|
5
|
+
export const typeConverter: HubspotTypeConverter = {
|
|
6
|
+
[hsDataTypes.date]:
|
|
7
|
+
(data: string) => {
|
|
8
|
+
return (new Date(data)).setUTCHours(0, 0, 0, 0);
|
|
9
|
+
},
|
|
10
|
+
[hsDataTypes.datetime]:
|
|
11
|
+
(data: string) => {
|
|
12
|
+
return new Date(data).getTime();
|
|
13
|
+
},
|
|
14
|
+
[hsDataTypes.number]:
|
|
15
|
+
(data: string | number) => {
|
|
16
|
+
return (typeof data === "number") ? data : parseFloat(data);
|
|
17
|
+
}
|
|
18
|
+
}
|