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.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
|
}
|
|
@@ -1710,6 +2032,10 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1710
2032
|
const path = opts.meta.originPath ? ` (${escapeHtml(String(opts.meta.originPath))})` : "";
|
|
1711
2033
|
lines.push(`<strong>Sujet</strong> : ${escapeHtml(String(opts.meta.originLabel))}${path}`);
|
|
1712
2034
|
}
|
|
2035
|
+
if (opts.meta.consent === true) {
|
|
2036
|
+
const when = typeof opts.meta.consentedAt === "string" && opts.meta.consentedAt ? ` (${escapeHtml(opts.meta.consentedAt)})` : "";
|
|
2037
|
+
lines.push(`<strong>Consentement RGPD</strong> : oui${when}`);
|
|
2038
|
+
}
|
|
1713
2039
|
for (const record of opts.companies ?? []) {
|
|
1714
2040
|
if (!record.name && !record.siret) continue;
|
|
1715
2041
|
const bits = [record.name, record.siret ? `SIRET ${record.siret}` : ""].filter(Boolean);
|
|
@@ -1803,6 +2129,7 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1803
2129
|
});
|
|
1804
2130
|
}
|
|
1805
2131
|
}
|
|
2132
|
+
let usedFormsApi = false;
|
|
1806
2133
|
if (email && apiKey) {
|
|
1807
2134
|
const groups = groupByObject(form.definition, resolution.values);
|
|
1808
2135
|
for (const [object, props] of Object.entries(companyGroups)) {
|
|
@@ -1810,14 +2137,62 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1810
2137
|
}
|
|
1811
2138
|
const partition = await partitionBySchema(apiKey, groups);
|
|
1812
2139
|
rejected = partition.rejected;
|
|
1813
|
-
const
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
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
|
+
}
|
|
1821
2196
|
}
|
|
1822
2197
|
const domain = corporateDomain(email);
|
|
1823
2198
|
const companyProps = partition.accepted.company ?? {};
|
|
@@ -1841,7 +2216,7 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1841
2216
|
if (companyId) {
|
|
1842
2217
|
await hsJson(
|
|
1843
2218
|
apiKey,
|
|
1844
|
-
`${HS_BASE$
|
|
2219
|
+
`${HS_BASE$1}/crm/v4/objects/contacts/${contactId}/associations/default/companies/${companyId}`,
|
|
1845
2220
|
{ method: "PUT" }
|
|
1846
2221
|
);
|
|
1847
2222
|
}
|
|
@@ -1849,7 +2224,7 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1849
2224
|
strapi.log.warn(`[hubspot] company sync failed (contact OK) — ${err.message}`);
|
|
1850
2225
|
}
|
|
1851
2226
|
}
|
|
1852
|
-
if (contactId && config2.timelineNote) {
|
|
2227
|
+
if (contactId && config2.timelineNote && !usedFormsApi) {
|
|
1853
2228
|
try {
|
|
1854
2229
|
await createLeadNote(apiKey, {
|
|
1855
2230
|
contactId,
|
|
@@ -1884,168 +2259,6 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
1884
2259
|
}
|
|
1885
2260
|
return { submit };
|
|
1886
2261
|
}
|
|
1887
|
-
const HS_BASE$1 = "https://api.hubapi.com";
|
|
1888
|
-
const OBJECT_BY_TYPE_ID = {
|
|
1889
|
-
"0-1": "contact",
|
|
1890
|
-
"0-2": "company"
|
|
1891
|
-
};
|
|
1892
|
-
const TYPE_MAP = {
|
|
1893
|
-
single_line_text: "text",
|
|
1894
|
-
multi_line_text: "textarea",
|
|
1895
|
-
email: "email",
|
|
1896
|
-
phone: "tel",
|
|
1897
|
-
mobile_phone: "tel",
|
|
1898
|
-
number: "number",
|
|
1899
|
-
dropdown: "select",
|
|
1900
|
-
select: "select",
|
|
1901
|
-
radio: "radio",
|
|
1902
|
-
multiple_checkboxes: "checkbox",
|
|
1903
|
-
single_checkbox: "checkbox",
|
|
1904
|
-
booleancheckbox: "checkbox"
|
|
1905
|
-
};
|
|
1906
|
-
let counter$1 = 0;
|
|
1907
|
-
const makeId$1 = (prefix) => `${prefix}_${Date.now().toString(36)}${(counter$1 += 1).toString(36)}`;
|
|
1908
|
-
function convertCondition(parentFieldId, raw) {
|
|
1909
|
-
const operator = (raw.operator ?? "").toUpperCase();
|
|
1910
|
-
const values = (raw.values ?? []).map((v) => String(v)).filter((v) => v !== "");
|
|
1911
|
-
const spread = (op, logic) => values.length ? { logic, rules: values.map((value) => ({ field: parentFieldId, operator: op, value })) } : null;
|
|
1912
|
-
switch (operator) {
|
|
1913
|
-
case "EQ":
|
|
1914
|
-
case "SET_ANY":
|
|
1915
|
-
return spread("eq", "or");
|
|
1916
|
-
case "NEQ":
|
|
1917
|
-
case "NOT_SET_ANY":
|
|
1918
|
-
return spread("neq", "and");
|
|
1919
|
-
case "CONTAINS":
|
|
1920
|
-
return spread("contains", "or");
|
|
1921
|
-
case "GT":
|
|
1922
|
-
return spread("gt", "or");
|
|
1923
|
-
case "LT":
|
|
1924
|
-
return spread("lt", "or");
|
|
1925
|
-
case "SET":
|
|
1926
|
-
case "NOT_EMPTY":
|
|
1927
|
-
return { logic: "and", rules: [{ field: parentFieldId, operator: "notEmpty" }] };
|
|
1928
|
-
case "NOT_SET":
|
|
1929
|
-
case "EMPTY":
|
|
1930
|
-
return { logic: "and", rules: [{ field: parentFieldId, operator: "empty" }] };
|
|
1931
|
-
default:
|
|
1932
|
-
return null;
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
function convertField(raw, ctx) {
|
|
1936
|
-
const label = raw.label || raw.name || "";
|
|
1937
|
-
const name = raw.name ?? "";
|
|
1938
|
-
if (raw.hidden) {
|
|
1939
|
-
ctx.skipped.push({ code: "hidden-field", label });
|
|
1940
|
-
return [];
|
|
1941
|
-
}
|
|
1942
|
-
const object = OBJECT_BY_TYPE_ID[raw.objectTypeId ?? "0-1"];
|
|
1943
|
-
if (!object) {
|
|
1944
|
-
ctx.skipped.push({ code: "object", label, detail: raw.objectTypeId });
|
|
1945
|
-
return [];
|
|
1946
|
-
}
|
|
1947
|
-
const type = TYPE_MAP[raw.fieldType ?? ""];
|
|
1948
|
-
if (!type || !name) {
|
|
1949
|
-
ctx.skipped.push({ code: "field-type", label, detail: raw.fieldType });
|
|
1950
|
-
return [];
|
|
1951
|
-
}
|
|
1952
|
-
if (ctx.usedNames.has(name)) {
|
|
1953
|
-
ctx.skipped.push({ code: "duplicate", label, detail: name });
|
|
1954
|
-
return [];
|
|
1955
|
-
}
|
|
1956
|
-
ctx.usedNames.add(name);
|
|
1957
|
-
const options = (raw.options ?? []).map((o) => ({ value: String(o.value ?? ""), label: o.label || void 0 })).filter((o) => o.value !== "");
|
|
1958
|
-
const field = {
|
|
1959
|
-
id: makeId$1("fld"),
|
|
1960
|
-
name,
|
|
1961
|
-
label,
|
|
1962
|
-
type,
|
|
1963
|
-
required: Boolean(raw.required),
|
|
1964
|
-
placeholder: raw.placeholder || void 0,
|
|
1965
|
-
helpText: raw.description || void 0,
|
|
1966
|
-
...options.length ? { options } : {},
|
|
1967
|
-
hubspot: { object, property: name },
|
|
1968
|
-
...ctx.parentCondition ? { visibleIf: ctx.parentCondition } : {}
|
|
1969
|
-
};
|
|
1970
|
-
const out = [field];
|
|
1971
|
-
for (const dependent of raw.dependentFields ?? []) {
|
|
1972
|
-
if (!dependent.field) continue;
|
|
1973
|
-
const childLabel = dependent.field.label || dependent.field.name || "";
|
|
1974
|
-
const condition = dependent.dependentCondition ? convertCondition(field.id, dependent.dependentCondition) : null;
|
|
1975
|
-
if (!condition) {
|
|
1976
|
-
ctx.skipped.push({
|
|
1977
|
-
code: "condition",
|
|
1978
|
-
label: childLabel,
|
|
1979
|
-
detail: dependent.dependentCondition?.operator
|
|
1980
|
-
});
|
|
1981
|
-
}
|
|
1982
|
-
out.push(...convertField(dependent.field, { ...ctx, parentCondition: condition }));
|
|
1983
|
-
}
|
|
1984
|
-
return out;
|
|
1985
|
-
}
|
|
1986
|
-
function convertHubspotForm(raw) {
|
|
1987
|
-
const skipped = [];
|
|
1988
|
-
const usedNames = /* @__PURE__ */ new Set();
|
|
1989
|
-
const fields = [];
|
|
1990
|
-
for (const group of raw.fieldGroups ?? []) {
|
|
1991
|
-
if (group.richText && !(group.fields ?? []).length) {
|
|
1992
|
-
skipped.push({ code: "rich-text", detail: group.richText.slice(0, 80) });
|
|
1993
|
-
continue;
|
|
1994
|
-
}
|
|
1995
|
-
const groupFields = (group.fields ?? []).flatMap(
|
|
1996
|
-
(f) => convertField(f, { usedNames, skipped })
|
|
1997
|
-
);
|
|
1998
|
-
const topLevel = (group.fields ?? []).length;
|
|
1999
|
-
if (topLevel === 2 && groupFields.length >= 2) {
|
|
2000
|
-
groupFields[0].width = "half";
|
|
2001
|
-
groupFields[1].width = "half";
|
|
2002
|
-
}
|
|
2003
|
-
fields.push(...groupFields);
|
|
2004
|
-
}
|
|
2005
|
-
if (raw.legalConsentOptions && Object.keys(raw.legalConsentOptions).length) {
|
|
2006
|
-
skipped.push({ code: "legal-consent" });
|
|
2007
|
-
}
|
|
2008
|
-
const postSubmit = raw.configuration?.postSubmitAction;
|
|
2009
|
-
return {
|
|
2010
|
-
name: raw.name ?? "",
|
|
2011
|
-
submitLabel: raw.displayOptions?.submitButtonText || null,
|
|
2012
|
-
successMessage: postSubmit?.type === "thank_you" && postSubmit.value ? postSubmit.value : null,
|
|
2013
|
-
// HubSpot forms are single-page: one step, reorganizable in the builder.
|
|
2014
|
-
definition: { version: 1, steps: [{ id: makeId$1("stp"), fields }] },
|
|
2015
|
-
skipped
|
|
2016
|
-
};
|
|
2017
|
-
}
|
|
2018
|
-
async function hsGet(apiKey, path) {
|
|
2019
|
-
const res = await fetch(`${HS_BASE$1}${path}`, {
|
|
2020
|
-
headers: { Authorization: `Bearer ${apiKey}` }
|
|
2021
|
-
});
|
|
2022
|
-
if (!res.ok) {
|
|
2023
|
-
const body = await res.json().catch(() => ({}));
|
|
2024
|
-
throw Object.assign(new Error(body.message || `HubSpot ${res.status}`), {
|
|
2025
|
-
status: res.status
|
|
2026
|
-
});
|
|
2027
|
-
}
|
|
2028
|
-
return await res.json();
|
|
2029
|
-
}
|
|
2030
|
-
async function listHubspotForms(apiKey) {
|
|
2031
|
-
const out = [];
|
|
2032
|
-
let after;
|
|
2033
|
-
for (let page = 0; page < 10; page += 1) {
|
|
2034
|
-
const query2 = after ? `&after=${encodeURIComponent(after)}` : "";
|
|
2035
|
-
const res = await hsGet(apiKey, `/marketing/v3/forms/?limit=100&formTypes=hubspot${query2}`);
|
|
2036
|
-
for (const form of res.results ?? []) {
|
|
2037
|
-
if (form.id && !form.archived) {
|
|
2038
|
-
out.push({ id: form.id, name: form.name || form.id, updatedAt: form.updatedAt });
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
after = res.paging?.next?.after;
|
|
2042
|
-
if (!after) break;
|
|
2043
|
-
}
|
|
2044
|
-
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
2045
|
-
}
|
|
2046
|
-
async function fetchHubspotForm(apiKey, formId) {
|
|
2047
|
-
return hsGet(apiKey, `/marketing/v3/forms/${encodeURIComponent(formId)}`);
|
|
2048
|
-
}
|
|
2049
2262
|
let counter = 0;
|
|
2050
2263
|
const makeId = (prefix) => `${prefix}_${Date.now().toString(36)}${(counter += 1).toString(36)}`;
|
|
2051
2264
|
const slugifyName = (label) => label.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
@@ -2124,7 +2337,8 @@ const WRITABLE = [
|
|
|
2124
2337
|
"nextLabel",
|
|
2125
2338
|
"submitLabel",
|
|
2126
2339
|
"successMessage",
|
|
2127
|
-
"class"
|
|
2340
|
+
"class",
|
|
2341
|
+
"hubspotFormId"
|
|
2128
2342
|
];
|
|
2129
2343
|
const slugify = (value) => value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "form";
|
|
2130
2344
|
function createFormsAdminController(strapi) {
|
|
@@ -2489,6 +2703,7 @@ function createFormsAdminController(strapi) {
|
|
|
2489
2703
|
submitLabel: source.submitLabel,
|
|
2490
2704
|
successMessage: source.successMessage,
|
|
2491
2705
|
class: source.class,
|
|
2706
|
+
hubspotFormId: source.hubspotFormId ?? null,
|
|
2492
2707
|
definition: source.definition
|
|
2493
2708
|
};
|
|
2494
2709
|
if (targetId) {
|
|
@@ -2723,17 +2938,27 @@ const FORMS_ACTION = "plugin::hubspot.forms";
|
|
|
2723
2938
|
const config = {
|
|
2724
2939
|
default: {
|
|
2725
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",
|
|
2726
2947
|
// Objects whose properties are offered. Names from the standard set, or
|
|
2727
2948
|
// `{ name, path }` for a custom object type.
|
|
2728
2949
|
objects: ["contact", "company"],
|
|
2729
2950
|
validate: [],
|
|
2730
|
-
// Submission pipeline of the built forms
|
|
2951
|
+
// Submission pipeline of the built forms:
|
|
2731
2952
|
// - companyFromDomain: a corporate email upserts the Company (deduped by
|
|
2732
2953
|
// domain) and associates it to the contact;
|
|
2733
|
-
// - 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.
|
|
2734
2958
|
forms: {
|
|
2735
2959
|
companyFromDomain: true,
|
|
2736
2960
|
timelineNote: true,
|
|
2961
|
+
defaultFormId: "",
|
|
2737
2962
|
// Per-IP brakes on the public routes; 0 disables one.
|
|
2738
2963
|
rateLimit: { submitPerMinute: 6, searchPerMinute: 30 }
|
|
2739
2964
|
}
|
|
@@ -2889,10 +3114,21 @@ const controllers = {
|
|
|
2889
3114
|
if (!values || !Object.keys(values).length) ctx.throw(422, "Invalid submission");
|
|
2890
3115
|
const rawMeta = ctx.request.body?.meta ?? {};
|
|
2891
3116
|
const meta = {};
|
|
2892
|
-
for (const key of ["pagePath", "pageUrl", "originPath", "originLabel", "source"]) {
|
|
3117
|
+
for (const key of ["pagePath", "pageUrl", "pageName", "originPath", "originLabel", "source", "ipAddress"]) {
|
|
2893
3118
|
const v = rawMeta[key];
|
|
2894
3119
|
if (typeof v === "string" && v) meta[key] = v.slice(0, 500);
|
|
2895
3120
|
}
|
|
3121
|
+
if (rawMeta.consent === true) meta.consent = true;
|
|
3122
|
+
if (typeof rawMeta.consentedAt === "string" && rawMeta.consentedAt.trim()) {
|
|
3123
|
+
meta.consentedAt = rawMeta.consentedAt.trim().slice(0, 40);
|
|
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
|
+
}
|
|
2896
3132
|
const outcome = await strapi.plugin("hubspot").service("forms").submit(entry, values, meta);
|
|
2897
3133
|
if (!outcome.ok) {
|
|
2898
3134
|
ctx.status = 422;
|