strapi-plugin-hubspot 0.13.1 → 0.14.0
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/CHANGELOG.md +25 -0
- package/README.md +42 -25
- package/dist/_chunks/{Forms-lrzuWzoB.js → Forms-Cch-7zei.js} +38 -3
- package/dist/_chunks/{Forms-CHB-sKem.mjs → Forms-ws6d8rdC.mjs} +38 -3
- package/dist/_chunks/{HubspotFormInput-YI09keTC.mjs → HubspotFormInput-18pWTITT.mjs} +1 -1
- package/dist/_chunks/{HubspotFormInput-Bb6it0nP.js → HubspotFormInput-M2l_8w_e.js} +1 -1
- package/dist/_chunks/{HubspotObjectInput-D3A6xzb3.js → HubspotObjectInput-BDO99qXs.js} +3 -3
- package/dist/_chunks/{HubspotObjectInput-CzLKT9oj.mjs → HubspotObjectInput-DshusXZP.mjs} +3 -3
- package/dist/_chunks/{HubspotPropertyInput-DicskLcN.mjs → HubspotPropertyInput-C-kHDgwx.mjs} +3 -3
- package/dist/_chunks/{HubspotPropertyInput-Dss3eUlL.js → HubspotPropertyInput-XGnH2tHn.js} +3 -3
- package/dist/_chunks/{Settings-4CbX3SPK.mjs → Settings-Bm7WkzK1.mjs} +2 -2
- package/dist/_chunks/{Settings-Ch8tPZAV.js → Settings-CRnsu6ob.js} +2 -2
- package/dist/_chunks/{en-PYFibnDc.js → en-BXMU5CjT.js} +4 -0
- package/dist/_chunks/{en-D8DJiSxd.mjs → en-DKV-8FWP.mjs} +4 -0
- package/dist/_chunks/{fr-yHk4sSEl.js → fr-Dx4XmZ4X.js} +4 -0
- package/dist/_chunks/{fr-BMjixfXn.mjs → fr-GB6T3ZyU.mjs} +4 -0
- package/dist/_chunks/{index-BAvedd2m.js → index-BlaG8oIq.js} +6 -6
- package/dist/_chunks/{index-BE2ribUL.mjs → index-CPVfElYB.mjs} +6 -6
- package/dist/_chunks/{objectLabels-DGJxYjKz.mjs → objectLabels-6OUnIDB5.mjs} +1 -1
- package/dist/_chunks/{objectLabels-CycoU0zY.js → objectLabels-CPc21Tdd.js} +1 -1
- package/dist/_chunks/{useHubspotSchema-DRfaI6mF.js → useHubspotSchema-0MvCM1AC.js} +1 -1
- package/dist/_chunks/{useHubspotSchema-e31UsYb2.mjs → useHubspotSchema-qCUs2gIX.mjs} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/src/builder/types.d.ts +2 -0
- package/dist/server/index.js +417 -189
- package/dist/server/index.mjs +417 -189
- package/dist/server/src/__tests__/hsforms.test.d.ts +1 -0
- package/dist/server/src/content-types.d.ts +8 -0
- package/dist/server/src/forms.d.ts +10 -6
- package/dist/server/src/hsforms.d.ts +75 -0
- package/dist/server/src/importHubspot.d.ts +1 -0
- package/dist/server/src/index.d.ts +12 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -1247,6 +1247,13 @@ const contentTypes = {
|
|
|
1247
1247
|
submitLabel: localizedString(),
|
|
1248
1248
|
successMessage: localizedString("text"),
|
|
1249
1249
|
class: { type: "string" },
|
|
1250
|
+
// Marketing form GUID of the connected HubSpot portal. Test portal
|
|
1251
|
+
// today, production later — swap the private app + this GUID, no
|
|
1252
|
+
// code change. Empty = CRM upsert fallback (no native conversion).
|
|
1253
|
+
hubspotFormId: {
|
|
1254
|
+
type: "string",
|
|
1255
|
+
pluginOptions: { i18n: { localized: false } }
|
|
1256
|
+
},
|
|
1250
1257
|
definition: {
|
|
1251
1258
|
type: "json",
|
|
1252
1259
|
required: true,
|
|
@@ -1450,6 +1457,312 @@ function resolveSubmission(definition, rawValues) {
|
|
|
1450
1457
|
const unknown = Object.keys(rawValues).filter((key) => !declared.has(key));
|
|
1451
1458
|
return { values, hidden, missingRequired, unknown };
|
|
1452
1459
|
}
|
|
1460
|
+
const HS_BASE$2 = "https://api.hubapi.com";
|
|
1461
|
+
const OBJECT_BY_TYPE_ID = {
|
|
1462
|
+
"0-1": "contact",
|
|
1463
|
+
"0-2": "company"
|
|
1464
|
+
};
|
|
1465
|
+
const TYPE_MAP = {
|
|
1466
|
+
single_line_text: "text",
|
|
1467
|
+
multi_line_text: "textarea",
|
|
1468
|
+
email: "email",
|
|
1469
|
+
phone: "tel",
|
|
1470
|
+
mobile_phone: "tel",
|
|
1471
|
+
number: "number",
|
|
1472
|
+
dropdown: "select",
|
|
1473
|
+
select: "select",
|
|
1474
|
+
radio: "radio",
|
|
1475
|
+
multiple_checkboxes: "checkbox",
|
|
1476
|
+
single_checkbox: "checkbox",
|
|
1477
|
+
booleancheckbox: "checkbox"
|
|
1478
|
+
};
|
|
1479
|
+
let counter$1 = 0;
|
|
1480
|
+
const makeId$1 = (prefix) => `${prefix}_${Date.now().toString(36)}${(counter$1 += 1).toString(36)}`;
|
|
1481
|
+
function convertCondition(parentFieldId, raw) {
|
|
1482
|
+
const operator = (raw.operator ?? "").toUpperCase();
|
|
1483
|
+
const values = (raw.values ?? []).map((v) => String(v)).filter((v) => v !== "");
|
|
1484
|
+
const spread = (op, logic) => values.length ? { logic, rules: values.map((value) => ({ field: parentFieldId, operator: op, value })) } : null;
|
|
1485
|
+
switch (operator) {
|
|
1486
|
+
case "EQ":
|
|
1487
|
+
case "SET_ANY":
|
|
1488
|
+
return spread("eq", "or");
|
|
1489
|
+
case "NEQ":
|
|
1490
|
+
case "NOT_SET_ANY":
|
|
1491
|
+
return spread("neq", "and");
|
|
1492
|
+
case "CONTAINS":
|
|
1493
|
+
return spread("contains", "or");
|
|
1494
|
+
case "GT":
|
|
1495
|
+
return spread("gt", "or");
|
|
1496
|
+
case "LT":
|
|
1497
|
+
return spread("lt", "or");
|
|
1498
|
+
case "SET":
|
|
1499
|
+
case "NOT_EMPTY":
|
|
1500
|
+
return { logic: "and", rules: [{ field: parentFieldId, operator: "notEmpty" }] };
|
|
1501
|
+
case "NOT_SET":
|
|
1502
|
+
case "EMPTY":
|
|
1503
|
+
return { logic: "and", rules: [{ field: parentFieldId, operator: "empty" }] };
|
|
1504
|
+
default:
|
|
1505
|
+
return null;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
function convertField(raw, ctx) {
|
|
1509
|
+
const label = raw.label || raw.name || "";
|
|
1510
|
+
const name = raw.name ?? "";
|
|
1511
|
+
if (raw.hidden) {
|
|
1512
|
+
ctx.skipped.push({ code: "hidden-field", label });
|
|
1513
|
+
return [];
|
|
1514
|
+
}
|
|
1515
|
+
const object = OBJECT_BY_TYPE_ID[raw.objectTypeId ?? "0-1"];
|
|
1516
|
+
if (!object) {
|
|
1517
|
+
ctx.skipped.push({ code: "object", label, detail: raw.objectTypeId });
|
|
1518
|
+
return [];
|
|
1519
|
+
}
|
|
1520
|
+
const type = TYPE_MAP[raw.fieldType ?? ""];
|
|
1521
|
+
if (!type || !name) {
|
|
1522
|
+
ctx.skipped.push({ code: "field-type", label, detail: raw.fieldType });
|
|
1523
|
+
return [];
|
|
1524
|
+
}
|
|
1525
|
+
if (ctx.usedNames.has(name)) {
|
|
1526
|
+
ctx.skipped.push({ code: "duplicate", label, detail: name });
|
|
1527
|
+
return [];
|
|
1528
|
+
}
|
|
1529
|
+
ctx.usedNames.add(name);
|
|
1530
|
+
const options = (raw.options ?? []).map((o) => ({ value: String(o.value ?? ""), label: o.label || void 0 })).filter((o) => o.value !== "");
|
|
1531
|
+
const field = {
|
|
1532
|
+
id: makeId$1("fld"),
|
|
1533
|
+
name,
|
|
1534
|
+
label,
|
|
1535
|
+
type,
|
|
1536
|
+
required: Boolean(raw.required),
|
|
1537
|
+
placeholder: raw.placeholder || void 0,
|
|
1538
|
+
helpText: raw.description || void 0,
|
|
1539
|
+
...options.length ? { options } : {},
|
|
1540
|
+
hubspot: { object, property: name },
|
|
1541
|
+
...ctx.parentCondition ? { visibleIf: ctx.parentCondition } : {}
|
|
1542
|
+
};
|
|
1543
|
+
const out = [field];
|
|
1544
|
+
for (const dependent of raw.dependentFields ?? []) {
|
|
1545
|
+
if (!dependent.field) continue;
|
|
1546
|
+
const childLabel = dependent.field.label || dependent.field.name || "";
|
|
1547
|
+
const condition = dependent.dependentCondition ? convertCondition(field.id, dependent.dependentCondition) : null;
|
|
1548
|
+
if (!condition) {
|
|
1549
|
+
ctx.skipped.push({
|
|
1550
|
+
code: "condition",
|
|
1551
|
+
label: childLabel,
|
|
1552
|
+
detail: dependent.dependentCondition?.operator
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
out.push(...convertField(dependent.field, { ...ctx, parentCondition: condition }));
|
|
1556
|
+
}
|
|
1557
|
+
return out;
|
|
1558
|
+
}
|
|
1559
|
+
function convertHubspotForm(raw) {
|
|
1560
|
+
const skipped = [];
|
|
1561
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
1562
|
+
const fields = [];
|
|
1563
|
+
for (const group of raw.fieldGroups ?? []) {
|
|
1564
|
+
if (group.richText && !(group.fields ?? []).length) {
|
|
1565
|
+
skipped.push({ code: "rich-text", detail: group.richText.slice(0, 80) });
|
|
1566
|
+
continue;
|
|
1567
|
+
}
|
|
1568
|
+
const groupFields = (group.fields ?? []).flatMap(
|
|
1569
|
+
(f) => convertField(f, { usedNames, skipped })
|
|
1570
|
+
);
|
|
1571
|
+
const topLevel = (group.fields ?? []).length;
|
|
1572
|
+
if (topLevel === 2 && groupFields.length >= 2) {
|
|
1573
|
+
groupFields[0].width = "half";
|
|
1574
|
+
groupFields[1].width = "half";
|
|
1575
|
+
}
|
|
1576
|
+
fields.push(...groupFields);
|
|
1577
|
+
}
|
|
1578
|
+
if (raw.legalConsentOptions && Object.keys(raw.legalConsentOptions).length) {
|
|
1579
|
+
skipped.push({ code: "legal-consent" });
|
|
1580
|
+
}
|
|
1581
|
+
const postSubmit = raw.configuration?.postSubmitAction;
|
|
1582
|
+
return {
|
|
1583
|
+
name: raw.name ?? "",
|
|
1584
|
+
submitLabel: raw.displayOptions?.submitButtonText || null,
|
|
1585
|
+
successMessage: postSubmit?.type === "thank_you" && postSubmit.value ? postSubmit.value : null,
|
|
1586
|
+
hubspotFormId: raw.id ?? null,
|
|
1587
|
+
// HubSpot forms are single-page: one step, reorganizable in the builder.
|
|
1588
|
+
definition: { version: 1, steps: [{ id: makeId$1("stp"), fields }] },
|
|
1589
|
+
skipped
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
async function hsGet(apiKey, path) {
|
|
1593
|
+
const res = await fetch(`${HS_BASE$2}${path}`, {
|
|
1594
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1595
|
+
});
|
|
1596
|
+
if (!res.ok) {
|
|
1597
|
+
const body = await res.json().catch(() => ({}));
|
|
1598
|
+
throw Object.assign(new Error(body.message || `HubSpot ${res.status}`), {
|
|
1599
|
+
status: res.status
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
return await res.json();
|
|
1603
|
+
}
|
|
1604
|
+
async function listHubspotForms(apiKey) {
|
|
1605
|
+
const out = [];
|
|
1606
|
+
let after;
|
|
1607
|
+
for (let page = 0; page < 10; page += 1) {
|
|
1608
|
+
const query2 = after ? `&after=${encodeURIComponent(after)}` : "";
|
|
1609
|
+
const res = await hsGet(apiKey, `/marketing/v3/forms/?limit=100&formTypes=hubspot${query2}`);
|
|
1610
|
+
for (const form of res.results ?? []) {
|
|
1611
|
+
if (form.id && !form.archived) {
|
|
1612
|
+
out.push({ id: form.id, name: form.name || form.id, updatedAt: form.updatedAt });
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
after = res.paging?.next?.after;
|
|
1616
|
+
if (!after) break;
|
|
1617
|
+
}
|
|
1618
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
1619
|
+
}
|
|
1620
|
+
async function fetchHubspotForm(apiKey, formId) {
|
|
1621
|
+
return hsGet(apiKey, `/marketing/v3/forms/${encodeURIComponent(formId)}`);
|
|
1622
|
+
}
|
|
1623
|
+
const GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1624
|
+
const PORTAL = /^\d{1,12}$/;
|
|
1625
|
+
const HUTK = /^[a-f0-9]{8,128}$/i;
|
|
1626
|
+
const SHAPE_TTL_MS = 5 * 60 * 1e3;
|
|
1627
|
+
const DEFAULT_CONSENT_TEXT = "J'accepte que mes données soient utilisées pour me recontacter.";
|
|
1628
|
+
function isFormGuid(value) {
|
|
1629
|
+
return GUID.test(value.trim());
|
|
1630
|
+
}
|
|
1631
|
+
function sanitizeHutk(value) {
|
|
1632
|
+
if (typeof value !== "string") return void 0;
|
|
1633
|
+
const trimmed = value.trim();
|
|
1634
|
+
return HUTK.test(trimmed) ? trimmed : void 0;
|
|
1635
|
+
}
|
|
1636
|
+
function sanitizePortalId(value) {
|
|
1637
|
+
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
1638
|
+
const trimmed = String(value).trim();
|
|
1639
|
+
return PORTAL.test(trimmed) ? trimmed : void 0;
|
|
1640
|
+
}
|
|
1641
|
+
function sanitizeIp(value) {
|
|
1642
|
+
if (typeof value !== "string") return void 0;
|
|
1643
|
+
const trimmed = value.trim();
|
|
1644
|
+
return trimmed && trimmed.length <= 45 ? trimmed : void 0;
|
|
1645
|
+
}
|
|
1646
|
+
function formsHost(region) {
|
|
1647
|
+
const r = (region || "eu1").trim().toLowerCase();
|
|
1648
|
+
if (!r || r === "na1" || r === "na") return "api.hsforms.com";
|
|
1649
|
+
return `api-${r}.hsforms.com`;
|
|
1650
|
+
}
|
|
1651
|
+
function formsSubmitUrl(portalId, formGuid, region = "eu1") {
|
|
1652
|
+
return `https://${formsHost(region)}/submissions/v3/integration/secure/submit/${encodeURIComponent(portalId)}/${encodeURIComponent(formGuid)}`;
|
|
1653
|
+
}
|
|
1654
|
+
function toHsFields(props) {
|
|
1655
|
+
return Object.entries(props).filter(([, value]) => value !== "").map(([name, value]) => ({ name, value }));
|
|
1656
|
+
}
|
|
1657
|
+
function filterToFormFields(fields, allowed) {
|
|
1658
|
+
if (!allowed) return fields;
|
|
1659
|
+
return fields.filter((field) => allowed.has(field.name));
|
|
1660
|
+
}
|
|
1661
|
+
function formFieldNames(raw) {
|
|
1662
|
+
const names = [];
|
|
1663
|
+
for (const group of raw.fieldGroups ?? []) {
|
|
1664
|
+
for (const field of group.fields ?? []) {
|
|
1665
|
+
if (field.name) names.push(field.name);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
return names;
|
|
1669
|
+
}
|
|
1670
|
+
const shapeCache = /* @__PURE__ */ new Map();
|
|
1671
|
+
async function loadFormShape(apiKey, formGuid) {
|
|
1672
|
+
const cached = shapeCache.get(formGuid);
|
|
1673
|
+
if (cached && Date.now() - cached.at < SHAPE_TTL_MS) {
|
|
1674
|
+
return { names: cached.names, hasLegalConsent: cached.hasLegalConsent };
|
|
1675
|
+
}
|
|
1676
|
+
try {
|
|
1677
|
+
const raw = await fetchHubspotForm(apiKey, formGuid);
|
|
1678
|
+
const names = new Set(formFieldNames(raw));
|
|
1679
|
+
const hasLegalConsent = Boolean(
|
|
1680
|
+
raw.legalConsentOptions && typeof raw.legalConsentOptions === "object" && raw.legalConsentOptions.type && raw.legalConsentOptions.type !== "none"
|
|
1681
|
+
);
|
|
1682
|
+
shapeCache.set(formGuid, { at: Date.now(), names, hasLegalConsent });
|
|
1683
|
+
return { names, hasLegalConsent };
|
|
1684
|
+
} catch {
|
|
1685
|
+
return null;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
async function findContactIdByEmail(apiKey, email) {
|
|
1689
|
+
const res = await fetch("https://api.hubapi.com/crm/v3/objects/contacts/search", {
|
|
1690
|
+
method: "POST",
|
|
1691
|
+
headers: {
|
|
1692
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1693
|
+
"Content-Type": "application/json"
|
|
1694
|
+
},
|
|
1695
|
+
body: JSON.stringify({
|
|
1696
|
+
filterGroups: [
|
|
1697
|
+
{ filters: [{ propertyName: "email", operator: "EQ", value: email }] }
|
|
1698
|
+
],
|
|
1699
|
+
properties: ["email"],
|
|
1700
|
+
limit: 1
|
|
1701
|
+
})
|
|
1702
|
+
});
|
|
1703
|
+
const body = await res.json().catch(() => ({}));
|
|
1704
|
+
if (!res.ok) return void 0;
|
|
1705
|
+
return body.results?.[0]?.id;
|
|
1706
|
+
}
|
|
1707
|
+
function contextPayload(ctx) {
|
|
1708
|
+
const out = {};
|
|
1709
|
+
const hutk = sanitizeHutk(ctx?.hutk);
|
|
1710
|
+
if (hutk) out.hutk = hutk;
|
|
1711
|
+
if (typeof ctx?.pageUri === "string" && ctx.pageUri.trim()) {
|
|
1712
|
+
out.pageUri = ctx.pageUri.trim().slice(0, 1e3);
|
|
1713
|
+
}
|
|
1714
|
+
if (typeof ctx?.pageName === "string" && ctx.pageName.trim()) {
|
|
1715
|
+
out.pageName = ctx.pageName.trim().slice(0, 200);
|
|
1716
|
+
}
|
|
1717
|
+
const ip = sanitizeIp(ctx?.ipAddress);
|
|
1718
|
+
if (ip) out.ipAddress = ip;
|
|
1719
|
+
return out;
|
|
1720
|
+
}
|
|
1721
|
+
async function postForm(url, apiKey, payload) {
|
|
1722
|
+
const res = await fetch(url, {
|
|
1723
|
+
method: "POST",
|
|
1724
|
+
headers: {
|
|
1725
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1726
|
+
"Content-Type": "application/json"
|
|
1727
|
+
},
|
|
1728
|
+
body: JSON.stringify(payload)
|
|
1729
|
+
});
|
|
1730
|
+
if (res.ok) return { ok: true, status: res.status };
|
|
1731
|
+
const body = await res.json().catch(() => ({}));
|
|
1732
|
+
const error = body.errors?.[0]?.message || body.message || `HubSpot ${res.status}`;
|
|
1733
|
+
return { ok: false, status: res.status, error };
|
|
1734
|
+
}
|
|
1735
|
+
async function submitMarketingForm(input) {
|
|
1736
|
+
const portalId = sanitizePortalId(input.portalId);
|
|
1737
|
+
const formGuid = input.formGuid.trim();
|
|
1738
|
+
if (!portalId || !isFormGuid(formGuid) || !input.fields.length) {
|
|
1739
|
+
return { ok: false, status: 0, error: "Missing portal, form GUID or fields" };
|
|
1740
|
+
}
|
|
1741
|
+
const url = formsSubmitUrl(portalId, formGuid, input.region);
|
|
1742
|
+
const context = contextPayload(input.context);
|
|
1743
|
+
const base = {
|
|
1744
|
+
fields: input.fields,
|
|
1745
|
+
...Object.keys(context).length ? { context } : {}
|
|
1746
|
+
};
|
|
1747
|
+
const withConsent = input.consent?.given && input.formHasLegalConsent !== false ? {
|
|
1748
|
+
...base,
|
|
1749
|
+
legalConsentOptions: {
|
|
1750
|
+
consent: {
|
|
1751
|
+
consentToProcess: true,
|
|
1752
|
+
text: input.consent.text?.trim() || DEFAULT_CONSENT_TEXT
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
} : base;
|
|
1756
|
+
let result = await postForm(url, input.apiKey, withConsent);
|
|
1757
|
+
if (!result.ok && result.status === 400 && withConsent !== base) {
|
|
1758
|
+
result = await postForm(url, input.apiKey, base);
|
|
1759
|
+
}
|
|
1760
|
+
if (!result.ok && result.status === 400 && input.fields.length > 1) {
|
|
1761
|
+
const email = input.fields.find((f) => f.name === "email");
|
|
1762
|
+
if (email) result = await postForm(url, input.apiKey, { ...base, fields: [email] });
|
|
1763
|
+
}
|
|
1764
|
+
return result;
|
|
1765
|
+
}
|
|
1453
1766
|
function publicForm(entry) {
|
|
1454
1767
|
return {
|
|
1455
1768
|
slug: entry.slug,
|
|
@@ -1558,9 +1871,9 @@ function sanitizeRawValues(raw) {
|
|
|
1558
1871
|
}
|
|
1559
1872
|
return values;
|
|
1560
1873
|
}
|
|
1561
|
-
const HS_BASE$
|
|
1562
|
-
const HS_COMPANIES = `${HS_BASE$
|
|
1563
|
-
const HS_NOTES = `${HS_BASE$
|
|
1874
|
+
const HS_BASE$1 = "https://api.hubapi.com";
|
|
1875
|
+
const HS_COMPANIES = `${HS_BASE$1}/crm/v3/objects/companies`;
|
|
1876
|
+
const HS_NOTES = `${HS_BASE$1}/crm/v3/objects/notes`;
|
|
1564
1877
|
const ASSOC_NOTE_TO_CONTACT = 202;
|
|
1565
1878
|
const ASSOC_NOTE_TO_COMPANY = 190;
|
|
1566
1879
|
const escapeHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -1618,8 +1931,17 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1618
1931
|
const formsConfig = () => ({
|
|
1619
1932
|
companyFromDomain: true,
|
|
1620
1933
|
timelineNote: true,
|
|
1934
|
+
defaultFormId: "",
|
|
1621
1935
|
...strapi.plugin("hubspot").config("forms", {})
|
|
1622
1936
|
});
|
|
1937
|
+
const stringProps = (props) => {
|
|
1938
|
+
const out = {};
|
|
1939
|
+
for (const [key, value] of Object.entries(props)) {
|
|
1940
|
+
if (value === "" || value == null) continue;
|
|
1941
|
+
out[key] = typeof value === "boolean" ? value ? "true" : "false" : String(value);
|
|
1942
|
+
}
|
|
1943
|
+
return out;
|
|
1944
|
+
};
|
|
1623
1945
|
async function partitionBySchema(apiKey, groups) {
|
|
1624
1946
|
let schema;
|
|
1625
1947
|
try {
|
|
@@ -1659,20 +1981,20 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1659
1981
|
});
|
|
1660
1982
|
const existingId = found.results?.[0]?.id;
|
|
1661
1983
|
if (!existingId) return void 0;
|
|
1662
|
-
const
|
|
1663
|
-
for (const [key, value] of Object.entries(props))
|
|
1984
|
+
const stringProps2 = {};
|
|
1985
|
+
for (const [key, value] of Object.entries(props)) stringProps2[key] = String(value);
|
|
1664
1986
|
await hsJson(apiKey, `${HS_COMPANIES}/${existingId}`, {
|
|
1665
1987
|
method: "PATCH",
|
|
1666
|
-
body: { properties:
|
|
1988
|
+
body: { properties: stringProps2 }
|
|
1667
1989
|
});
|
|
1668
1990
|
return existingId;
|
|
1669
1991
|
}
|
|
1670
1992
|
async function createCompany(apiKey, props) {
|
|
1671
|
-
const
|
|
1672
|
-
for (const [key, value] of Object.entries(props))
|
|
1993
|
+
const stringProps2 = {};
|
|
1994
|
+
for (const [key, value] of Object.entries(props)) stringProps2[key] = String(value);
|
|
1673
1995
|
const created = await hsJson(apiKey, HS_COMPANIES, {
|
|
1674
1996
|
method: "POST",
|
|
1675
|
-
body: { properties:
|
|
1997
|
+
body: { properties: stringProps2 }
|
|
1676
1998
|
});
|
|
1677
1999
|
return created.id;
|
|
1678
2000
|
}
|
|
@@ -1685,19 +2007,19 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1685
2007
|
limit: 1
|
|
1686
2008
|
}
|
|
1687
2009
|
});
|
|
1688
|
-
const
|
|
1689
|
-
for (const [key, value] of Object.entries(props))
|
|
2010
|
+
const stringProps2 = { domain };
|
|
2011
|
+
for (const [key, value] of Object.entries(props)) stringProps2[key] = String(value);
|
|
1690
2012
|
const existingId = found.results?.[0]?.id;
|
|
1691
2013
|
if (existingId) {
|
|
1692
2014
|
await hsJson(apiKey, `${HS_COMPANIES}/${existingId}`, {
|
|
1693
2015
|
method: "PATCH",
|
|
1694
|
-
body: { properties:
|
|
2016
|
+
body: { properties: stringProps2 }
|
|
1695
2017
|
});
|
|
1696
2018
|
return existingId;
|
|
1697
2019
|
}
|
|
1698
2020
|
const created = await hsJson(apiKey, HS_COMPANIES, {
|
|
1699
2021
|
method: "POST",
|
|
1700
|
-
body: { properties:
|
|
2022
|
+
body: { properties: stringProps2 }
|
|
1701
2023
|
});
|
|
1702
2024
|
return created.id;
|
|
1703
2025
|
}
|
|
@@ -1807,6 +2129,7 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1807
2129
|
});
|
|
1808
2130
|
}
|
|
1809
2131
|
}
|
|
2132
|
+
let usedFormsApi = false;
|
|
1810
2133
|
if (email && apiKey) {
|
|
1811
2134
|
const groups = groupByObject(form.definition, resolution.values);
|
|
1812
2135
|
for (const [object, props] of Object.entries(companyGroups)) {
|
|
@@ -1814,14 +2137,62 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1814
2137
|
}
|
|
1815
2138
|
const partition = await partitionBySchema(apiKey, groups);
|
|
1816
2139
|
rejected = partition.rejected;
|
|
1817
|
-
const
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
2140
|
+
const portalId = sanitizePortalId(strapi.plugin("hubspot").config("portalId", ""));
|
|
2141
|
+
const region = String(strapi.plugin("hubspot").config("region", "eu1") || "eu1");
|
|
2142
|
+
const ownGuid = typeof form.hubspotFormId === "string" ? form.hubspotFormId.trim() : "";
|
|
2143
|
+
const formGuid = isFormGuid(ownGuid) ? ownGuid : isFormGuid(config2.defaultFormId) ? config2.defaultFormId.trim() : "";
|
|
2144
|
+
if (portalId && formGuid) {
|
|
2145
|
+
try {
|
|
2146
|
+
const shape = await loadFormShape(apiKey, formGuid);
|
|
2147
|
+
const fields = filterToFormFields(
|
|
2148
|
+
toHsFields({ ...stringProps(partition.accepted.contact ?? {}), email }),
|
|
2149
|
+
shape?.names ?? null
|
|
2150
|
+
);
|
|
2151
|
+
if (fields.length && !fields.some((f) => f.name === "email")) {
|
|
2152
|
+
fields.unshift({ name: "email", value: email });
|
|
2153
|
+
}
|
|
2154
|
+
const submitted = await submitMarketingForm({
|
|
2155
|
+
apiKey,
|
|
2156
|
+
portalId,
|
|
2157
|
+
formGuid,
|
|
2158
|
+
region,
|
|
2159
|
+
fields: fields.length ? fields : [{ name: "email", value: email }],
|
|
2160
|
+
context: {
|
|
2161
|
+
hutk: sanitizeHutk(meta.hutk),
|
|
2162
|
+
pageUri: typeof meta.pageUrl === "string" ? meta.pageUrl : void 0,
|
|
2163
|
+
pageName: typeof meta.pageName === "string" && meta.pageName || (form.title || form.name) || void 0,
|
|
2164
|
+
ipAddress: sanitizeIp(meta.ipAddress)
|
|
2165
|
+
},
|
|
2166
|
+
consent: { given: meta.consent === true },
|
|
2167
|
+
formHasLegalConsent: shape?.hasLegalConsent
|
|
2168
|
+
});
|
|
2169
|
+
if (submitted.ok) {
|
|
2170
|
+
hubspotSynced = true;
|
|
2171
|
+
usedFormsApi = true;
|
|
2172
|
+
try {
|
|
2173
|
+
contactId = await findContactIdByEmail(apiKey, email);
|
|
2174
|
+
} catch (err) {
|
|
2175
|
+
strapi.log.warn(
|
|
2176
|
+
`[hubspot] contact lookup after form submit failed — ${err.message}`
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2179
|
+
} else {
|
|
2180
|
+
strapi.log.warn(`[hubspot] Forms API submit failed — ${submitted.error}`);
|
|
2181
|
+
}
|
|
2182
|
+
} catch (err) {
|
|
2183
|
+
strapi.log.warn(`[hubspot] Forms API submit failed — ${err.message}`);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
if (!usedFormsApi) {
|
|
2187
|
+
const result = await strapi.plugin("hubspot").service("submit").upsert({
|
|
2188
|
+
object: "contact",
|
|
2189
|
+
idProperty: "email",
|
|
2190
|
+
properties: { ...partition.accepted.contact ?? {}, email }
|
|
2191
|
+
});
|
|
2192
|
+
if (result.ok) {
|
|
2193
|
+
hubspotSynced = true;
|
|
2194
|
+
contactId = result.id;
|
|
2195
|
+
}
|
|
1825
2196
|
}
|
|
1826
2197
|
const domain = corporateDomain(email);
|
|
1827
2198
|
const companyProps = partition.accepted.company ?? {};
|
|
@@ -1845,7 +2216,7 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1845
2216
|
if (companyId) {
|
|
1846
2217
|
await hsJson(
|
|
1847
2218
|
apiKey,
|
|
1848
|
-
`${HS_BASE$
|
|
2219
|
+
`${HS_BASE$1}/crm/v4/objects/contacts/${contactId}/associations/default/companies/${companyId}`,
|
|
1849
2220
|
{ method: "PUT" }
|
|
1850
2221
|
);
|
|
1851
2222
|
}
|
|
@@ -1853,7 +2224,7 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1853
2224
|
strapi.log.warn(`[hubspot] company sync failed (contact OK) — ${err.message}`);
|
|
1854
2225
|
}
|
|
1855
2226
|
}
|
|
1856
|
-
if (contactId && config2.timelineNote) {
|
|
2227
|
+
if (contactId && config2.timelineNote && !usedFormsApi) {
|
|
1857
2228
|
try {
|
|
1858
2229
|
await createLeadNote(apiKey, {
|
|
1859
2230
|
contactId,
|
|
@@ -1888,168 +2259,6 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1888
2259
|
}
|
|
1889
2260
|
return { submit };
|
|
1890
2261
|
}
|
|
1891
|
-
const HS_BASE$1 = "https://api.hubapi.com";
|
|
1892
|
-
const OBJECT_BY_TYPE_ID = {
|
|
1893
|
-
"0-1": "contact",
|
|
1894
|
-
"0-2": "company"
|
|
1895
|
-
};
|
|
1896
|
-
const TYPE_MAP = {
|
|
1897
|
-
single_line_text: "text",
|
|
1898
|
-
multi_line_text: "textarea",
|
|
1899
|
-
email: "email",
|
|
1900
|
-
phone: "tel",
|
|
1901
|
-
mobile_phone: "tel",
|
|
1902
|
-
number: "number",
|
|
1903
|
-
dropdown: "select",
|
|
1904
|
-
select: "select",
|
|
1905
|
-
radio: "radio",
|
|
1906
|
-
multiple_checkboxes: "checkbox",
|
|
1907
|
-
single_checkbox: "checkbox",
|
|
1908
|
-
booleancheckbox: "checkbox"
|
|
1909
|
-
};
|
|
1910
|
-
let counter$1 = 0;
|
|
1911
|
-
const makeId$1 = (prefix) => `${prefix}_${Date.now().toString(36)}${(counter$1 += 1).toString(36)}`;
|
|
1912
|
-
function convertCondition(parentFieldId, raw) {
|
|
1913
|
-
const operator = (raw.operator ?? "").toUpperCase();
|
|
1914
|
-
const values = (raw.values ?? []).map((v) => String(v)).filter((v) => v !== "");
|
|
1915
|
-
const spread = (op, logic) => values.length ? { logic, rules: values.map((value) => ({ field: parentFieldId, operator: op, value })) } : null;
|
|
1916
|
-
switch (operator) {
|
|
1917
|
-
case "EQ":
|
|
1918
|
-
case "SET_ANY":
|
|
1919
|
-
return spread("eq", "or");
|
|
1920
|
-
case "NEQ":
|
|
1921
|
-
case "NOT_SET_ANY":
|
|
1922
|
-
return spread("neq", "and");
|
|
1923
|
-
case "CONTAINS":
|
|
1924
|
-
return spread("contains", "or");
|
|
1925
|
-
case "GT":
|
|
1926
|
-
return spread("gt", "or");
|
|
1927
|
-
case "LT":
|
|
1928
|
-
return spread("lt", "or");
|
|
1929
|
-
case "SET":
|
|
1930
|
-
case "NOT_EMPTY":
|
|
1931
|
-
return { logic: "and", rules: [{ field: parentFieldId, operator: "notEmpty" }] };
|
|
1932
|
-
case "NOT_SET":
|
|
1933
|
-
case "EMPTY":
|
|
1934
|
-
return { logic: "and", rules: [{ field: parentFieldId, operator: "empty" }] };
|
|
1935
|
-
default:
|
|
1936
|
-
return null;
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
function convertField(raw, ctx) {
|
|
1940
|
-
const label = raw.label || raw.name || "";
|
|
1941
|
-
const name = raw.name ?? "";
|
|
1942
|
-
if (raw.hidden) {
|
|
1943
|
-
ctx.skipped.push({ code: "hidden-field", label });
|
|
1944
|
-
return [];
|
|
1945
|
-
}
|
|
1946
|
-
const object = OBJECT_BY_TYPE_ID[raw.objectTypeId ?? "0-1"];
|
|
1947
|
-
if (!object) {
|
|
1948
|
-
ctx.skipped.push({ code: "object", label, detail: raw.objectTypeId });
|
|
1949
|
-
return [];
|
|
1950
|
-
}
|
|
1951
|
-
const type = TYPE_MAP[raw.fieldType ?? ""];
|
|
1952
|
-
if (!type || !name) {
|
|
1953
|
-
ctx.skipped.push({ code: "field-type", label, detail: raw.fieldType });
|
|
1954
|
-
return [];
|
|
1955
|
-
}
|
|
1956
|
-
if (ctx.usedNames.has(name)) {
|
|
1957
|
-
ctx.skipped.push({ code: "duplicate", label, detail: name });
|
|
1958
|
-
return [];
|
|
1959
|
-
}
|
|
1960
|
-
ctx.usedNames.add(name);
|
|
1961
|
-
const options = (raw.options ?? []).map((o) => ({ value: String(o.value ?? ""), label: o.label || void 0 })).filter((o) => o.value !== "");
|
|
1962
|
-
const field = {
|
|
1963
|
-
id: makeId$1("fld"),
|
|
1964
|
-
name,
|
|
1965
|
-
label,
|
|
1966
|
-
type,
|
|
1967
|
-
required: Boolean(raw.required),
|
|
1968
|
-
placeholder: raw.placeholder || void 0,
|
|
1969
|
-
helpText: raw.description || void 0,
|
|
1970
|
-
...options.length ? { options } : {},
|
|
1971
|
-
hubspot: { object, property: name },
|
|
1972
|
-
...ctx.parentCondition ? { visibleIf: ctx.parentCondition } : {}
|
|
1973
|
-
};
|
|
1974
|
-
const out = [field];
|
|
1975
|
-
for (const dependent of raw.dependentFields ?? []) {
|
|
1976
|
-
if (!dependent.field) continue;
|
|
1977
|
-
const childLabel = dependent.field.label || dependent.field.name || "";
|
|
1978
|
-
const condition = dependent.dependentCondition ? convertCondition(field.id, dependent.dependentCondition) : null;
|
|
1979
|
-
if (!condition) {
|
|
1980
|
-
ctx.skipped.push({
|
|
1981
|
-
code: "condition",
|
|
1982
|
-
label: childLabel,
|
|
1983
|
-
detail: dependent.dependentCondition?.operator
|
|
1984
|
-
});
|
|
1985
|
-
}
|
|
1986
|
-
out.push(...convertField(dependent.field, { ...ctx, parentCondition: condition }));
|
|
1987
|
-
}
|
|
1988
|
-
return out;
|
|
1989
|
-
}
|
|
1990
|
-
function convertHubspotForm(raw) {
|
|
1991
|
-
const skipped = [];
|
|
1992
|
-
const usedNames = /* @__PURE__ */ new Set();
|
|
1993
|
-
const fields = [];
|
|
1994
|
-
for (const group of raw.fieldGroups ?? []) {
|
|
1995
|
-
if (group.richText && !(group.fields ?? []).length) {
|
|
1996
|
-
skipped.push({ code: "rich-text", detail: group.richText.slice(0, 80) });
|
|
1997
|
-
continue;
|
|
1998
|
-
}
|
|
1999
|
-
const groupFields = (group.fields ?? []).flatMap(
|
|
2000
|
-
(f) => convertField(f, { usedNames, skipped })
|
|
2001
|
-
);
|
|
2002
|
-
const topLevel = (group.fields ?? []).length;
|
|
2003
|
-
if (topLevel === 2 && groupFields.length >= 2) {
|
|
2004
|
-
groupFields[0].width = "half";
|
|
2005
|
-
groupFields[1].width = "half";
|
|
2006
|
-
}
|
|
2007
|
-
fields.push(...groupFields);
|
|
2008
|
-
}
|
|
2009
|
-
if (raw.legalConsentOptions && Object.keys(raw.legalConsentOptions).length) {
|
|
2010
|
-
skipped.push({ code: "legal-consent" });
|
|
2011
|
-
}
|
|
2012
|
-
const postSubmit = raw.configuration?.postSubmitAction;
|
|
2013
|
-
return {
|
|
2014
|
-
name: raw.name ?? "",
|
|
2015
|
-
submitLabel: raw.displayOptions?.submitButtonText || null,
|
|
2016
|
-
successMessage: postSubmit?.type === "thank_you" && postSubmit.value ? postSubmit.value : null,
|
|
2017
|
-
// HubSpot forms are single-page: one step, reorganizable in the builder.
|
|
2018
|
-
definition: { version: 1, steps: [{ id: makeId$1("stp"), fields }] },
|
|
2019
|
-
skipped
|
|
2020
|
-
};
|
|
2021
|
-
}
|
|
2022
|
-
async function hsGet(apiKey, path) {
|
|
2023
|
-
const res = await fetch(`${HS_BASE$1}${path}`, {
|
|
2024
|
-
headers: { Authorization: `Bearer ${apiKey}` }
|
|
2025
|
-
});
|
|
2026
|
-
if (!res.ok) {
|
|
2027
|
-
const body = await res.json().catch(() => ({}));
|
|
2028
|
-
throw Object.assign(new Error(body.message || `HubSpot ${res.status}`), {
|
|
2029
|
-
status: res.status
|
|
2030
|
-
});
|
|
2031
|
-
}
|
|
2032
|
-
return await res.json();
|
|
2033
|
-
}
|
|
2034
|
-
async function listHubspotForms(apiKey) {
|
|
2035
|
-
const out = [];
|
|
2036
|
-
let after;
|
|
2037
|
-
for (let page = 0; page < 10; page += 1) {
|
|
2038
|
-
const query2 = after ? `&after=${encodeURIComponent(after)}` : "";
|
|
2039
|
-
const res = await hsGet(apiKey, `/marketing/v3/forms/?limit=100&formTypes=hubspot${query2}`);
|
|
2040
|
-
for (const form of res.results ?? []) {
|
|
2041
|
-
if (form.id && !form.archived) {
|
|
2042
|
-
out.push({ id: form.id, name: form.name || form.id, updatedAt: form.updatedAt });
|
|
2043
|
-
}
|
|
2044
|
-
}
|
|
2045
|
-
after = res.paging?.next?.after;
|
|
2046
|
-
if (!after) break;
|
|
2047
|
-
}
|
|
2048
|
-
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
2049
|
-
}
|
|
2050
|
-
async function fetchHubspotForm(apiKey, formId) {
|
|
2051
|
-
return hsGet(apiKey, `/marketing/v3/forms/${encodeURIComponent(formId)}`);
|
|
2052
|
-
}
|
|
2053
2262
|
let counter = 0;
|
|
2054
2263
|
const makeId = (prefix) => `${prefix}_${Date.now().toString(36)}${(counter += 1).toString(36)}`;
|
|
2055
2264
|
const slugifyName = (label) => label.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
@@ -2128,7 +2337,8 @@ const WRITABLE = [
|
|
|
2128
2337
|
"nextLabel",
|
|
2129
2338
|
"submitLabel",
|
|
2130
2339
|
"successMessage",
|
|
2131
|
-
"class"
|
|
2340
|
+
"class",
|
|
2341
|
+
"hubspotFormId"
|
|
2132
2342
|
];
|
|
2133
2343
|
const slugify = (value) => value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "form";
|
|
2134
2344
|
function createFormsAdminController(strapi) {
|
|
@@ -2493,6 +2703,7 @@ function createFormsAdminController(strapi) {
|
|
|
2493
2703
|
submitLabel: source.submitLabel,
|
|
2494
2704
|
successMessage: source.successMessage,
|
|
2495
2705
|
class: source.class,
|
|
2706
|
+
hubspotFormId: source.hubspotFormId ?? null,
|
|
2496
2707
|
definition: source.definition
|
|
2497
2708
|
};
|
|
2498
2709
|
if (targetId) {
|
|
@@ -2727,17 +2938,27 @@ const FORMS_ACTION = "plugin::hubspot.forms";
|
|
|
2727
2938
|
const config = {
|
|
2728
2939
|
default: {
|
|
2729
2940
|
apiKey: "",
|
|
2941
|
+
// HubSpot account that receives form submissions. A test portal id is
|
|
2942
|
+
// fine in development; production swaps this env var, not the code.
|
|
2943
|
+
portalId: "",
|
|
2944
|
+
// Hosting region of that portal (`eu1`, `na1`, …). Drives the Forms API
|
|
2945
|
+
// host (`api-eu1.hsforms.com`) — CRM stays on api.hubapi.com.
|
|
2946
|
+
region: "eu1",
|
|
2730
2947
|
// Objects whose properties are offered. Names from the standard set, or
|
|
2731
2948
|
// `{ name, path }` for a custom object type.
|
|
2732
2949
|
objects: ["contact", "company"],
|
|
2733
2950
|
validate: [],
|
|
2734
|
-
// Submission pipeline of the built forms
|
|
2951
|
+
// Submission pipeline of the built forms:
|
|
2735
2952
|
// - companyFromDomain: a corporate email upserts the Company (deduped by
|
|
2736
2953
|
// domain) and associates it to the contact;
|
|
2737
|
-
// - timelineNote:
|
|
2954
|
+
// - timelineNote: recap note on the CRM-upsert fallback only (off when
|
|
2955
|
+
// submissions go through the marketing Forms API);
|
|
2956
|
+
// - defaultFormId: portal-wide marketing form GUID when a builder form
|
|
2957
|
+
// doesn't set its own — swap with the portal at production cutover.
|
|
2738
2958
|
forms: {
|
|
2739
2959
|
companyFromDomain: true,
|
|
2740
2960
|
timelineNote: true,
|
|
2961
|
+
defaultFormId: "",
|
|
2741
2962
|
// Per-IP brakes on the public routes; 0 disables one.
|
|
2742
2963
|
rateLimit: { submitPerMinute: 6, searchPerMinute: 30 }
|
|
2743
2964
|
}
|
|
@@ -2893,7 +3114,7 @@ const controllers = {
|
|
|
2893
3114
|
if (!values || !Object.keys(values).length) ctx.throw(422, "Invalid submission");
|
|
2894
3115
|
const rawMeta = ctx.request.body?.meta ?? {};
|
|
2895
3116
|
const meta = {};
|
|
2896
|
-
for (const key of ["pagePath", "pageUrl", "originPath", "originLabel", "source"]) {
|
|
3117
|
+
for (const key of ["pagePath", "pageUrl", "pageName", "originPath", "originLabel", "source", "ipAddress"]) {
|
|
2897
3118
|
const v = rawMeta[key];
|
|
2898
3119
|
if (typeof v === "string" && v) meta[key] = v.slice(0, 500);
|
|
2899
3120
|
}
|
|
@@ -2901,6 +3122,13 @@ const controllers = {
|
|
|
2901
3122
|
if (typeof rawMeta.consentedAt === "string" && rawMeta.consentedAt.trim()) {
|
|
2902
3123
|
meta.consentedAt = rawMeta.consentedAt.trim().slice(0, 40);
|
|
2903
3124
|
}
|
|
3125
|
+
if (typeof rawMeta.hutk === "string" && rawMeta.hutk.trim()) {
|
|
3126
|
+
meta.hutk = rawMeta.hutk.trim().slice(0, 128);
|
|
3127
|
+
}
|
|
3128
|
+
if (!meta.ipAddress) {
|
|
3129
|
+
const ip = ctx.request.ip;
|
|
3130
|
+
if (ip) meta.ipAddress = ip.slice(0, 45);
|
|
3131
|
+
}
|
|
2904
3132
|
const outcome = await strapi.plugin("hubspot").service("forms").submit(entry, values, meta);
|
|
2905
3133
|
if (!outcome.ok) {
|
|
2906
3134
|
ctx.status = 422;
|