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