@flamingo-stack/openframe-frontend-core 0.0.572 → 0.0.573-1933.5266.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-3ZCIC4MS.js → chunk-2BNPLJSU.js} +3 -3
- package/dist/{chunk-2IMAYSYH.cjs → chunk-47Y537OE.cjs} +19 -19
- package/dist/{chunk-2IMAYSYH.cjs.map → chunk-47Y537OE.cjs.map} +1 -1
- package/dist/{chunk-LMRBXA2F.cjs → chunk-5TVO35AW.cjs} +3 -3
- package/dist/{chunk-LMRBXA2F.cjs.map → chunk-5TVO35AW.cjs.map} +1 -1
- package/dist/{chunk-YSG6JYBP.cjs → chunk-5ZKZK3V2.cjs} +16 -16
- package/dist/{chunk-YSG6JYBP.cjs.map → chunk-5ZKZK3V2.cjs.map} +1 -1
- package/dist/{chunk-VKZDIRXC.cjs → chunk-66CKTUIV.cjs} +3 -3
- package/dist/{chunk-VKZDIRXC.cjs.map → chunk-66CKTUIV.cjs.map} +1 -1
- package/dist/{chunk-OQNIQVET.js → chunk-6QJYO3SB.js} +21 -3
- package/dist/chunk-6QJYO3SB.js.map +1 -0
- package/dist/{chunk-RSVRM35D.js → chunk-6ZC6IC6H.js} +2 -2
- package/dist/{chunk-7Y2MC6LN.cjs → chunk-7D3KGRH5.cjs} +37 -37
- package/dist/{chunk-7Y2MC6LN.cjs.map → chunk-7D3KGRH5.cjs.map} +1 -1
- package/dist/{chunk-R6UKDAGZ.js → chunk-BNAN4WLF.js} +6 -6
- package/dist/{chunk-JS3U2V7J.js → chunk-CNNRCN6S.js} +3 -3
- package/dist/{chunk-P5MRXAXN.cjs → chunk-D5GJWW6Z.cjs} +10 -10
- package/dist/{chunk-P5MRXAXN.cjs.map → chunk-D5GJWW6Z.cjs.map} +1 -1
- package/dist/{chunk-7A7SH2RI.js → chunk-E4W7RVFZ.js} +2 -2
- package/dist/{chunk-IGGHHVOD.js → chunk-EHQNJDP6.js} +3 -3
- package/dist/{chunk-L6C7SYRU.js → chunk-ET5V44BF.js} +4 -4
- package/dist/{chunk-MVR5MU7B.js → chunk-G5EH3A2U.js} +4 -4
- package/dist/{chunk-ZHI7PPI7.cjs → chunk-HV3SJHX2.cjs} +7 -7
- package/dist/{chunk-ZHI7PPI7.cjs.map → chunk-HV3SJHX2.cjs.map} +1 -1
- package/dist/{chunk-5PFN7X2H.cjs → chunk-IHW7HEFU.cjs} +22 -22
- package/dist/{chunk-5PFN7X2H.cjs.map → chunk-IHW7HEFU.cjs.map} +1 -1
- package/dist/{chunk-QCWTNMWZ.js → chunk-KELXUVJW.js} +25 -6
- package/dist/{chunk-QCWTNMWZ.js.map → chunk-KELXUVJW.js.map} +1 -1
- package/dist/{chunk-A3WAKKCV.cjs → chunk-LC4AKB7U.cjs} +3 -3
- package/dist/{chunk-A3WAKKCV.cjs.map → chunk-LC4AKB7U.cjs.map} +1 -1
- package/dist/{chunk-II52DW7X.js → chunk-OZGDWUVN.js} +2 -2
- package/dist/{chunk-3CWVBREW.cjs → chunk-P2MTEESX.cjs} +64 -46
- package/dist/chunk-P2MTEESX.cjs.map +1 -0
- package/dist/{chunk-GWDOWAHU.cjs → chunk-PLBXVJIN.cjs} +3 -3
- package/dist/{chunk-GWDOWAHU.cjs.map → chunk-PLBXVJIN.cjs.map} +1 -1
- package/dist/{chunk-6JZXYDWC.js → chunk-PVZZGQMZ.js} +5 -5
- package/dist/{chunk-W6PFJ6RY.cjs → chunk-QXEXMPXV.cjs} +7 -7
- package/dist/{chunk-W6PFJ6RY.cjs.map → chunk-QXEXMPXV.cjs.map} +1 -1
- package/dist/{chunk-VNYEX66A.js → chunk-SVARR3OQ.js} +6 -6
- package/dist/{chunk-ZIF4IJ5L.cjs → chunk-VKYND7JR.cjs} +53 -53
- package/dist/{chunk-ZIF4IJ5L.cjs.map → chunk-VKYND7JR.cjs.map} +1 -1
- package/dist/{chunk-IZBQBUDP.js → chunk-X3VKHMHA.js} +3 -3
- package/dist/{chunk-RXMY3LTP.js → chunk-X4M3KSFH.js} +2 -2
- package/dist/{chunk-5LFT3RJQ.cjs → chunk-Y24CV7JN.cjs} +17 -17
- package/dist/{chunk-5LFT3RJQ.cjs.map → chunk-Y24CV7JN.cjs.map} +1 -1
- package/dist/{chunk-GC3TT3UF.cjs → chunk-YNBE6G5I.cjs} +26 -7
- package/dist/chunk-YNBE6G5I.cjs.map +1 -0
- package/dist/{chunk-KXKMVZUG.js → chunk-YW2JM4EC.js} +2 -2
- package/dist/{chunk-LE4RPH3E.cjs → chunk-YYO5RZJU.cjs} +36 -36
- package/dist/{chunk-LE4RPH3E.cjs.map → chunk-YYO5RZJU.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +9 -9
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +118 -118
- package/dist/components/index.js +13 -13
- package/dist/components/meeting-scheduler/index.cjs +19 -19
- package/dist/components/meeting-scheduler/index.cjs.map +1 -1
- package/dist/components/meeting-scheduler/index.js +3 -3
- package/dist/components/meeting-scheduler/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/honeypot-field.d.ts +11 -0
- package/dist/components/ui/honeypot-field.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/hooks/index.cjs +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/use-humanity-signals.d.ts +1 -1
- package/dist/hooks/use-meeting-booking.d.ts +3 -3
- package/dist/hooks/use-meeting-booking.d.ts.map +1 -1
- package/dist/index.cjs +9 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/utils/humanity-signals.cjs +24 -5
- package/dist/utils/humanity-signals.cjs.map +1 -1
- package/dist/utils/humanity-signals.d.ts +27 -4
- package/dist/utils/humanity-signals.d.ts.map +1 -1
- package/dist/utils/humanity-signals.js +24 -6
- package/dist/utils/humanity-signals.js.map +1 -1
- package/dist/utils/index.cjs +24 -5
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js +24 -6
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ui/honeypot-field.tsx +27 -1
- package/src/hooks/use-meeting-booking.ts +7 -5
- package/src/utils/__tests__/hubspot-meetings-convention.test.ts +4 -2
- package/src/utils/__tests__/humanity-signals.test.ts +75 -0
- package/src/utils/humanity-signals.ts +56 -8
- package/dist/chunk-3CWVBREW.cjs.map +0 -1
- package/dist/chunk-GC3TT3UF.cjs.map +0 -1
- package/dist/chunk-OQNIQVET.js.map +0 -1
- /package/dist/{chunk-3ZCIC4MS.js.map → chunk-2BNPLJSU.js.map} +0 -0
- /package/dist/{chunk-RSVRM35D.js.map → chunk-6ZC6IC6H.js.map} +0 -0
- /package/dist/{chunk-R6UKDAGZ.js.map → chunk-BNAN4WLF.js.map} +0 -0
- /package/dist/{chunk-JS3U2V7J.js.map → chunk-CNNRCN6S.js.map} +0 -0
- /package/dist/{chunk-7A7SH2RI.js.map → chunk-E4W7RVFZ.js.map} +0 -0
- /package/dist/{chunk-IGGHHVOD.js.map → chunk-EHQNJDP6.js.map} +0 -0
- /package/dist/{chunk-L6C7SYRU.js.map → chunk-ET5V44BF.js.map} +0 -0
- /package/dist/{chunk-MVR5MU7B.js.map → chunk-G5EH3A2U.js.map} +0 -0
- /package/dist/{chunk-II52DW7X.js.map → chunk-OZGDWUVN.js.map} +0 -0
- /package/dist/{chunk-6JZXYDWC.js.map → chunk-PVZZGQMZ.js.map} +0 -0
- /package/dist/{chunk-VNYEX66A.js.map → chunk-SVARR3OQ.js.map} +0 -0
- /package/dist/{chunk-IZBQBUDP.js.map → chunk-X3VKHMHA.js.map} +0 -0
- /package/dist/{chunk-RXMY3LTP.js.map → chunk-X4M3KSFH.js.map} +0 -0
- /package/dist/{chunk-KXKMVZUG.js.map → chunk-YW2JM4EC.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
SoftwareInfo,
|
|
10
10
|
SoftwareSourceBadge,
|
|
11
11
|
ToolBadge
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-CNNRCN6S.js";
|
|
13
13
|
import {
|
|
14
14
|
AppHeader,
|
|
15
15
|
AppLayout,
|
|
@@ -28,14 +28,14 @@ import {
|
|
|
28
28
|
SlidingSidebar,
|
|
29
29
|
TicketAlertsButton,
|
|
30
30
|
TopNavigation
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-ET5V44BF.js";
|
|
32
32
|
import {
|
|
33
33
|
MobileNavigationDropdown,
|
|
34
34
|
MultiLevelNavigation,
|
|
35
35
|
StickySectionNav,
|
|
36
36
|
useSectionNavigation
|
|
37
|
-
} from "./chunk-
|
|
38
|
-
import "./chunk-
|
|
37
|
+
} from "./chunk-OZGDWUVN.js";
|
|
38
|
+
import "./chunk-X4M3KSFH.js";
|
|
39
39
|
import {
|
|
40
40
|
ADMIN_APPROVAL_REQUEST_CONTEXT_TYPE,
|
|
41
41
|
AIEnrichButton,
|
|
@@ -576,7 +576,7 @@ import {
|
|
|
576
576
|
withFirstTouchAttribution,
|
|
577
577
|
withWalkthroughOpenParam,
|
|
578
578
|
writeDismissCookie
|
|
579
|
-
} from "./chunk-
|
|
579
|
+
} from "./chunk-6QJYO3SB.js";
|
|
580
580
|
import "./chunk-L2SLFCXP.js";
|
|
581
581
|
import "./chunk-BJXDHKUX.js";
|
|
582
582
|
import {
|
|
@@ -586,6 +586,7 @@ import {
|
|
|
586
586
|
GraphQLIntrospector,
|
|
587
587
|
HONEYPOT_FIELD,
|
|
588
588
|
HUMANITY_SIGNAL_KEYS,
|
|
589
|
+
LEGACY_HONEYPOT_FIELD,
|
|
589
590
|
ToastCard,
|
|
590
591
|
Toaster,
|
|
591
592
|
batchFetchAuthenticatedImages,
|
|
@@ -682,7 +683,7 @@ import {
|
|
|
682
683
|
validateSchema,
|
|
683
684
|
validateVariables,
|
|
684
685
|
variablesToUrlParams
|
|
685
|
-
} from "./chunk-
|
|
686
|
+
} from "./chunk-KELXUVJW.js";
|
|
686
687
|
import "./chunk-ZRIVCLPR.js";
|
|
687
688
|
import {
|
|
688
689
|
CONTENT_REF_GROUPS,
|
|
@@ -2080,6 +2081,7 @@ export {
|
|
|
2080
2081
|
JsonDetailList,
|
|
2081
2082
|
JumpInIcon,
|
|
2082
2083
|
KnowledgeBaseLinksManager,
|
|
2084
|
+
LEGACY_HONEYPOT_FIELD,
|
|
2083
2085
|
LOG_SEVERITY_COLORS,
|
|
2084
2086
|
LOG_SEVERITY_LABELS,
|
|
2085
2087
|
Label,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/logs-list.tsx","../src/components/log-severity-dot.tsx","../src/types/logs.types.ts","../src/assets/index.ts"],"sourcesContent":["import { type FC, forwardRef, useRef } from 'react';\nimport type { LogsListProps, LogEntry } from '../types/logs.types';\nimport { cn } from '../utils/cn';\nimport { LogSeverityDot } from './log-severity-dot';\nimport { ToolIcon } from './tool-icon';\n\nconst formatTimestamp = (timestamp: string | Date): string => {\n const date = timestamp instanceof Date ? timestamp : new Date(timestamp);\n\n // UTC getters so the timestamp is identical on server (UTC) and client\n // (local) — otherwise React #418 hydration mismatch.\n const year = date.getUTCFullYear();\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n const hours = String(date.getUTCHours()).padStart(2, '0');\n const minutes = String(date.getUTCMinutes()).padStart(2, '0');\n\n return `${year}/${month}/${day},${hours}:${minutes}`;\n};\n\nconst LogCard: FC<{\n log: LogEntry;\n isLast: boolean;\n showConnector: boolean;\n onClick?: () => void;\n}> = ({ log, isLast, showConnector, onClick }) => {\n return (\n <div className=\"relative\">\n <div\n className={cn(\n 'relative box-border flex w-full items-start gap-3 rounded px-1 py-2',\n 'cursor-pointer transition-colors hover:bg-ods-bg-hover/50',\n )}\n onClick={onClick}\n role=\"button\"\n tabIndex={0}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n >\n <LogSeverityDot severity={log.severity} size=\"md\" />\n\n <div className=\"min-w-0 flex-1 space-y-1\">\n <p className=\"text-ods-text-primary text-h6\">{log.title}</p>\n <div className=\"flex items-center gap-2\">\n <p className=\"text-ods-text-secondary text-h5\">{formatTimestamp(log.timestamp)}</p>\n {log.toolType && <ToolIcon toolType={log.toolType} size={16} />}\n </div>\n </div>\n </div>\n\n {showConnector && !isLast && (\n <div\n className=\"absolute left-[15px] w-[2px] bg-ods-border\"\n style={{\n top: '28px',\n bottom: '-10px',\n }}\n aria-hidden=\"true\"\n />\n )}\n </div>\n );\n};\n\nexport const LogsList = forwardRef<HTMLDivElement, LogsListProps>(\n (\n {\n logs,\n maxHeight = '400px',\n showConnector = true,\n onLogClick,\n loading = false,\n emptyMessage = 'No logs to display',\n className,\n },\n ref,\n ) => {\n const containerRef = useRef<HTMLDivElement>(null);\n\n const isFullHeight = maxHeight === '100%';\n\n const getContainerStyles = () => {\n if (isFullHeight) return undefined;\n return { maxHeight, minHeight: '200px' };\n };\n\n const getContainerClasses = () => {\n if (isFullHeight) return 'h-full';\n return '';\n };\n\n if (loading) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-4',\n 'flex items-center justify-center',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div className=\"text-ods-text-muted text-h6\">Loading logs...</div>\n </div>\n );\n }\n\n if (logs.length === 0) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-4',\n 'flex items-center justify-center',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div className=\"text-ods-text-muted text-h6\">{emptyMessage}</div>\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n className={cn(\n 'relative rounded-lg border border-ods-border bg-ods-card',\n 'flex flex-col overflow-hidden',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div ref={containerRef} className=\"flex-1 overflow-y-auto px-4 py-3\">\n {logs.map((log, index) => (\n <LogCard\n key={log.id}\n log={log}\n isLast={index === logs.length - 1}\n showConnector={showConnector}\n onClick={() => onLogClick?.(log)}\n />\n ))}\n </div>\n </div>\n );\n },\n);\n\nLogsList.displayName = 'LogsList';\n","import { forwardRef } from 'react';\nimport { type LogSeverityDotProps, LOG_SEVERITY_COLORS, LOG_SEVERITY_LABELS } from '../types/logs.types';\nimport { cn } from '../utils/cn';\n\nexport const LogSeverityDot = forwardRef<HTMLDivElement, LogSeverityDotProps>(\n ({ severity, size = 'md', className }, ref) => {\n const sizeClasses = {\n sm: 'w-4 h-4',\n md: 'w-6 h-6',\n lg: 'w-8 h-8',\n };\n\n const dotSizeClasses = {\n sm: 'w-2 h-2',\n md: 'w-3 h-3',\n lg: 'w-4 h-4',\n };\n\n return (\n <div\n ref={ref}\n className={cn('relative flex shrink-0 items-center justify-center', sizeClasses[size], className)}\n aria-label={`${LOG_SEVERITY_LABELS[severity]} severity`}\n >\n <div\n className={cn('rounded-full', dotSizeClasses[size])}\n style={{ backgroundColor: LOG_SEVERITY_COLORS[severity] }}\n />\n </div>\n );\n },\n);\n\nLogSeverityDot.displayName = 'LogSeverityDot';\n","import type { ToolType } from './tool.types';\n\nexport type LogSeverity = 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL';\n\nexport interface LogEntry {\n id: string;\n ingestDay: string;\n eventType: string;\n severity: LogSeverity;\n title: string;\n timestamp: string | Date;\n toolType?: ToolType;\n toolIcon?: string;\n message?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface LogsListProps {\n logs: LogEntry[];\n maxHeight?: string | number;\n showConnector?: boolean;\n onLogClick?: (log: LogEntry) => void;\n loading?: boolean;\n emptyMessage?: string;\n className?: string;\n}\n\nexport interface LogSeverityDotProps {\n severity: LogSeverity;\n size?: 'sm' | 'md' | 'lg';\n className?: string;\n}\n\nexport const LOG_SEVERITY_COLORS: Record<LogSeverity, string> = {\n DEBUG: '#888888',\n INFO: '#888888',\n WARNING: '#e1b32f',\n ERROR: '#f36666',\n CRITICAL: '#b43b3b',\n};\n\nexport const LOG_SEVERITY_LABELS: Record<LogSeverity, string> = {\n DEBUG: 'Debug',\n INFO: 'Info',\n WARNING: 'Warning',\n ERROR: 'Error',\n CRITICAL: 'Critical',\n};\n","// Assets exports\n// Font files and other static assets\n// Platform utilities will be available when needed\n\nexport const assets = {} as const;\nexport * from './fae-avatar';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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","names":["forwardRef","jsx","forwardRef"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/logs-list.tsx","../src/components/log-severity-dot.tsx","../src/types/logs.types.ts","../src/assets/index.ts"],"sourcesContent":["import { type FC, forwardRef, useRef } from 'react';\nimport type { LogsListProps, LogEntry } from '../types/logs.types';\nimport { cn } from '../utils/cn';\nimport { LogSeverityDot } from './log-severity-dot';\nimport { ToolIcon } from './tool-icon';\n\nconst formatTimestamp = (timestamp: string | Date): string => {\n const date = timestamp instanceof Date ? timestamp : new Date(timestamp);\n\n // UTC getters so the timestamp is identical on server (UTC) and client\n // (local) — otherwise React #418 hydration mismatch.\n const year = date.getUTCFullYear();\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n const hours = String(date.getUTCHours()).padStart(2, '0');\n const minutes = String(date.getUTCMinutes()).padStart(2, '0');\n\n return `${year}/${month}/${day},${hours}:${minutes}`;\n};\n\nconst LogCard: FC<{\n log: LogEntry;\n isLast: boolean;\n showConnector: boolean;\n onClick?: () => void;\n}> = ({ log, isLast, showConnector, onClick }) => {\n return (\n <div className=\"relative\">\n <div\n className={cn(\n 'relative box-border flex w-full items-start gap-3 rounded px-1 py-2',\n 'cursor-pointer transition-colors hover:bg-ods-bg-hover/50',\n )}\n onClick={onClick}\n role=\"button\"\n tabIndex={0}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n >\n <LogSeverityDot severity={log.severity} size=\"md\" />\n\n <div className=\"min-w-0 flex-1 space-y-1\">\n <p className=\"text-ods-text-primary text-h6\">{log.title}</p>\n <div className=\"flex items-center gap-2\">\n <p className=\"text-ods-text-secondary text-h5\">{formatTimestamp(log.timestamp)}</p>\n {log.toolType && <ToolIcon toolType={log.toolType} size={16} />}\n </div>\n </div>\n </div>\n\n {showConnector && !isLast && (\n <div\n className=\"absolute left-[15px] w-[2px] bg-ods-border\"\n style={{\n top: '28px',\n bottom: '-10px',\n }}\n aria-hidden=\"true\"\n />\n )}\n </div>\n );\n};\n\nexport const LogsList = forwardRef<HTMLDivElement, LogsListProps>(\n (\n {\n logs,\n maxHeight = '400px',\n showConnector = true,\n onLogClick,\n loading = false,\n emptyMessage = 'No logs to display',\n className,\n },\n ref,\n ) => {\n const containerRef = useRef<HTMLDivElement>(null);\n\n const isFullHeight = maxHeight === '100%';\n\n const getContainerStyles = () => {\n if (isFullHeight) return undefined;\n return { maxHeight, minHeight: '200px' };\n };\n\n const getContainerClasses = () => {\n if (isFullHeight) return 'h-full';\n return '';\n };\n\n if (loading) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-4',\n 'flex items-center justify-center',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div className=\"text-ods-text-muted text-h6\">Loading logs...</div>\n </div>\n );\n }\n\n if (logs.length === 0) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-4',\n 'flex items-center justify-center',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div className=\"text-ods-text-muted text-h6\">{emptyMessage}</div>\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n className={cn(\n 'relative rounded-lg border border-ods-border bg-ods-card',\n 'flex flex-col overflow-hidden',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div ref={containerRef} className=\"flex-1 overflow-y-auto px-4 py-3\">\n {logs.map((log, index) => (\n <LogCard\n key={log.id}\n log={log}\n isLast={index === logs.length - 1}\n showConnector={showConnector}\n onClick={() => onLogClick?.(log)}\n />\n ))}\n </div>\n </div>\n );\n },\n);\n\nLogsList.displayName = 'LogsList';\n","import { forwardRef } from 'react';\nimport { type LogSeverityDotProps, LOG_SEVERITY_COLORS, LOG_SEVERITY_LABELS } from '../types/logs.types';\nimport { cn } from '../utils/cn';\n\nexport const LogSeverityDot = forwardRef<HTMLDivElement, LogSeverityDotProps>(\n ({ severity, size = 'md', className }, ref) => {\n const sizeClasses = {\n sm: 'w-4 h-4',\n md: 'w-6 h-6',\n lg: 'w-8 h-8',\n };\n\n const dotSizeClasses = {\n sm: 'w-2 h-2',\n md: 'w-3 h-3',\n lg: 'w-4 h-4',\n };\n\n return (\n <div\n ref={ref}\n className={cn('relative flex shrink-0 items-center justify-center', sizeClasses[size], className)}\n aria-label={`${LOG_SEVERITY_LABELS[severity]} severity`}\n >\n <div\n className={cn('rounded-full', dotSizeClasses[size])}\n style={{ backgroundColor: LOG_SEVERITY_COLORS[severity] }}\n />\n </div>\n );\n },\n);\n\nLogSeverityDot.displayName = 'LogSeverityDot';\n","import type { ToolType } from './tool.types';\n\nexport type LogSeverity = 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL';\n\nexport interface LogEntry {\n id: string;\n ingestDay: string;\n eventType: string;\n severity: LogSeverity;\n title: string;\n timestamp: string | Date;\n toolType?: ToolType;\n toolIcon?: string;\n message?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface LogsListProps {\n logs: LogEntry[];\n maxHeight?: string | number;\n showConnector?: boolean;\n onLogClick?: (log: LogEntry) => void;\n loading?: boolean;\n emptyMessage?: string;\n className?: string;\n}\n\nexport interface LogSeverityDotProps {\n severity: LogSeverity;\n size?: 'sm' | 'md' | 'lg';\n className?: string;\n}\n\nexport const LOG_SEVERITY_COLORS: Record<LogSeverity, string> = {\n DEBUG: '#888888',\n INFO: '#888888',\n WARNING: '#e1b32f',\n ERROR: '#f36666',\n CRITICAL: '#b43b3b',\n};\n\nexport const LOG_SEVERITY_LABELS: Record<LogSeverity, string> = {\n DEBUG: 'Debug',\n INFO: 'Info',\n WARNING: 'Warning',\n ERROR: 'Error',\n CRITICAL: 'Critical',\n};\n","// Assets exports\n// Font files and other static assets\n// Platform utilities will be available when needed\n\nexport const assets = {} as const;\nexport * from './fae-avatar';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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","names":["forwardRef","jsx","forwardRef"]}
|
|
@@ -1,23 +1,41 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/utils/humanity-signals.ts
|
|
4
|
-
var HONEYPOT_FIELD = "
|
|
4
|
+
var HONEYPOT_FIELD = "form_extra_note";
|
|
5
|
+
var LEGACY_HONEYPOT_FIELD = "contact_url_confirm";
|
|
5
6
|
var ELAPSED_MS_FIELD = "form_elapsed_ms";
|
|
6
7
|
var DEFAULT_MIN_FILL_MS = 700;
|
|
7
|
-
var HUMANITY_SIGNAL_KEYS = [HONEYPOT_FIELD, ELAPSED_MS_FIELD];
|
|
8
|
+
var HUMANITY_SIGNAL_KEYS = [HONEYPOT_FIELD, LEGACY_HONEYPOT_FIELD, ELAPSED_MS_FIELD];
|
|
8
9
|
function extractHumanitySignals(body) {
|
|
9
10
|
const b = body ?? {};
|
|
10
|
-
const rawHp = b[HONEYPOT_FIELD];
|
|
11
|
+
const rawHp = b[HONEYPOT_FIELD] ?? b[LEGACY_HONEYPOT_FIELD];
|
|
11
12
|
const honeypot = rawHp == null ? "" : String(rawHp);
|
|
12
13
|
const rawMs = b[ELAPSED_MS_FIELD];
|
|
13
14
|
const elapsedMs = typeof rawMs === "number" && Number.isFinite(rawMs) ? rawMs : null;
|
|
14
15
|
return { honeypot, elapsedMs };
|
|
15
16
|
}
|
|
17
|
+
function honeypotCopiedFromAnotherField(body, honeypot) {
|
|
18
|
+
const b = body ?? {};
|
|
19
|
+
const signalKeys = HUMANITY_SIGNAL_KEYS;
|
|
20
|
+
for (const [key, value] of Object.entries(b)) {
|
|
21
|
+
if (signalKeys.includes(key)) continue;
|
|
22
|
+
if (typeof value === "string" && value === honeypot) return true;
|
|
23
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
24
|
+
for (const nested of Object.values(value)) {
|
|
25
|
+
if (typeof nested === "string" && nested === honeypot) return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
16
31
|
function evaluateHumanitySignals(body, opts) {
|
|
17
32
|
const { honeypot, elapsedMs } = extractHumanitySignals(body);
|
|
18
|
-
|
|
33
|
+
const filled = honeypot.trim() !== "";
|
|
34
|
+
if (filled && !honeypotCopiedFromAnotherField(body, honeypot)) {
|
|
35
|
+
return { ok: false, reason: "honeypot" };
|
|
36
|
+
}
|
|
19
37
|
if (elapsedMs !== null && elapsedMs < opts.minFillMs) return { ok: false, reason: "too_fast" };
|
|
20
|
-
return { ok: true };
|
|
38
|
+
return filled ? { ok: true, note: "honeypot_autofill" } : { ok: true };
|
|
21
39
|
}
|
|
22
40
|
var splitCsvEnv = (s) => s?.split(",").map((t) => t.trim()).filter(Boolean) ?? [];
|
|
23
41
|
|
|
@@ -25,6 +43,7 @@ exports.DEFAULT_MIN_FILL_MS = DEFAULT_MIN_FILL_MS;
|
|
|
25
43
|
exports.ELAPSED_MS_FIELD = ELAPSED_MS_FIELD;
|
|
26
44
|
exports.HONEYPOT_FIELD = HONEYPOT_FIELD;
|
|
27
45
|
exports.HUMANITY_SIGNAL_KEYS = HUMANITY_SIGNAL_KEYS;
|
|
46
|
+
exports.LEGACY_HONEYPOT_FIELD = LEGACY_HONEYPOT_FIELD;
|
|
28
47
|
exports.evaluateHumanitySignals = evaluateHumanitySignals;
|
|
29
48
|
exports.extractHumanitySignals = extractHumanitySignals;
|
|
30
49
|
exports.splitCsvEnv = splitCsvEnv;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/humanity-signals.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"sources":["../../src/utils/humanity-signals.ts"],"names":[],"mappings":";;;AA8BO,IAAM,cAAA,GAAiB;AAMvB,IAAM,qBAAA,GAAwB;AAE9B,IAAM,gBAAA,GAAmB;AAEzB,IAAM,mBAAA,GAAsB;AAS5B,IAAM,oBAAA,GAAuB,CAAC,cAAA,EAAgB,qBAAA,EAAuB,gBAAgB;AASrF,SAAS,uBAAuB,IAAA,EAA+D;AACpG,EAAA,MAAM,CAAA,GAAK,QAAQ,EAAC;AAEpB,EAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,cAAc,CAAA,IAAK,EAAE,qBAAqB,CAAA;AAK1D,EAAA,MAAM,QAAA,GAAW,KAAA,IAAS,IAAA,GAAO,EAAA,GAAK,OAAO,KAAK,CAAA;AAClD,EAAA,MAAM,KAAA,GAAQ,EAAE,gBAAgB,CAAA;AAChC,EAAA,MAAM,SAAA,GAAY,OAAO,KAAA,KAAU,QAAA,IAAY,OAAO,QAAA,CAAS,KAAK,IAAI,KAAA,GAAQ,IAAA;AAChF,EAAA,OAAO,EAAE,UAAU,SAAA,EAAU;AAC/B;AASA,SAAS,8BAAA,CAA+B,MAAe,QAAA,EAA2B;AAChF,EAAA,MAAM,CAAA,GAAK,QAAQ,EAAC;AACpB,EAAA,MAAM,UAAA,GAAgC,oBAAA;AACtC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,CAAC,CAAA,EAAG;AAC5C,IAAA,IAAI,UAAA,CAAW,QAAA,CAAS,GAAG,CAAA,EAAG;AAC9B,IAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,UAAU,OAAO,IAAA;AAC5D,IAAA,IAAI,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,IAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/D,MAAA,KAAA,MAAW,MAAA,IAAU,MAAA,CAAO,MAAA,CAAO,KAAgC,CAAA,EAAG;AACpE,QAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,KAAW,UAAU,OAAO,IAAA;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AASO,SAAS,uBAAA,CAAwB,MAAe,IAAA,EAA8C;AACnG,EAAA,MAAM,EAAE,QAAA,EAAU,SAAA,EAAU,GAAI,uBAAuB,IAAI,CAAA;AAC3D,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,IAAA,EAAK,KAAM,EAAA;AACnC,EAAA,IAAI,MAAA,IAAU,CAAC,8BAAA,CAA+B,IAAA,EAAM,QAAQ,CAAA,EAAG;AAC7D,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,UAAA,EAAW;AAAA,EACzC;AACA,EAAA,IAAI,SAAA,KAAc,IAAA,IAAQ,SAAA,GAAY,IAAA,CAAK,SAAA,SAAkB,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,UAAA,EAAW;AAC7F,EAAA,OAAO,MAAA,GAAS,EAAE,EAAA,EAAI,IAAA,EAAM,MAAM,mBAAA,EAAoB,GAAI,EAAE,EAAA,EAAI,IAAA,EAAK;AACvE;AAGO,IAAM,cAAc,CAAC,CAAA,KAC1B,CAAA,EACI,KAAA,CAAM,GAAG,CAAA,CACV,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,MAAM,CAAA,CACjB,MAAA,CAAO,OAAO,KAAK","file":"humanity-signals.cjs","sourcesContent":["/**\n * Humanity signals — invisible bot-protection primitives shared by the lib's\n * public forms (client) and the hub's per-route `verifyHuman` gate (server).\n *\n * PURE + React-free on purpose: this module is a tsup SERVER entry (no\n * \"use client\" banner) so the hub can import it server-side without pulling a\n * client-reference boundary — same pattern as `schemas/contact-schema` and\n * `components/features/mux-origins`.\n *\n * Two origin-independent signals travel in the POST body: a honeypot (a hidden\n * field real users never fill) and timing (ms from form mount to submit).\n * `evaluateHumanitySignals` is the SINGLE source of truth for the block/allow\n * decision — the hub imports + calls it rather than re-implementing the rules.\n *\n * FALSE-POSITIVE HISTORY (2026-08-27): the honeypot was named\n * `contact_url_confirm`, and browser/password-manager autofill (which ignores\n * `autocomplete=\"off\"` and matches \"url\"/\"confirm\" name heuristics) filled it\n * for REAL users — every production `BOT_DETECTED` in the log window was a\n * legitimate Chrome user whose autofill tripped the decoy. Three layers now\n * prevent a recurrence; keep all three when touching this system:\n * 1. The field name avoids every autofill-heuristic token (name/email/\n * phone/url/website/confirm/company/address/code/…).\n * 2. `HoneypotField` renders `readOnly`-until-focus + password-manager\n * ignore attributes — autofill skips read-only inputs.\n * 3. `evaluateHumanitySignals` forgives a filled decoy whose value was\n * COPIED from another field in the same body (the autofill signature —\n * a human, not a bot).\n */\n\n/** Hidden honeypot field name. Deliberately free of autofill-heuristic tokens (see module doc). */\nexport const HONEYPOT_FIELD = 'form_extra_note';\n/**\n * Prior honeypot field name — still EVALUATED (a stale-lib embedder's client\n * keeps its bot protection) and still STRIPPED before upstream forwarding.\n * Remove once no deployed embedder ships a lib older than the rename.\n */\nexport const LEGACY_HONEYPOT_FIELD = 'contact_url_confirm';\n/** Client-measured ms between form mount and submit. */\nexport const ELAPSED_MS_FIELD = 'form_elapsed_ms';\n/** Default minimum fill time (ms). A submit faster than this is treated as a bot. */\nexport const DEFAULT_MIN_FILL_MS = 700;\n\n/**\n * Every humanity-signal key that rides in a public form's POST body.\n * Server-side handlers that forward form payloads upstream (HubSpot booking,\n * CRM pushes, …) MUST strip by THIS array — never hand-typed strings — so a\n * field rename here propagates everywhere and the honeypot value can never\n * silently leak into an upstream record.\n */\nexport const HUMANITY_SIGNAL_KEYS = [HONEYPOT_FIELD, LEGACY_HONEYPOT_FIELD, ELAPSED_MS_FIELD] as const;\n\n/** Keyed wire object produced by `useHumanitySignals().getSignals()` and spread into the POST body. */\nexport type HumanitySignals = Record<string, string | number>;\n\n/** Result of {@link evaluateHumanitySignals}. */\nexport type HumanityVerdict = { ok: true; note?: 'honeypot_autofill' } | { ok: false; reason: 'honeypot' | 'too_fast' };\n\n/** Tolerant reader — never throws; missing/garbage timing → null. */\nexport function extractHumanitySignals(body: unknown): { honeypot: string; elapsedMs: number | null } {\n const b = (body ?? {}) as Record<string, unknown>;\n // Current field name first; a stale-lib client still posts the legacy name.\n const rawHp = b[HONEYPOT_FIELD] ?? b[LEGACY_HONEYPOT_FIELD];\n // A legit client always sends a STRING here (getSignals → ref.value ?? ''),\n // so ANY present non-string value is a bot filling the decoy with a non-string\n // to dodge the empty-check — coerce to a (non-empty) string so it still trips.\n // null/undefined → '' = the correct \"field absent / unfilled\" allow case.\n const honeypot = rawHp == null ? '' : String(rawHp);\n const rawMs = b[ELAPSED_MS_FIELD];\n const elapsedMs = typeof rawMs === 'number' && Number.isFinite(rawMs) ? rawMs : null;\n return { honeypot, elapsedMs };\n}\n\n/**\n * Was the decoy value COPIED from a real field in the same body? That is the\n * autofill signature: browsers and password-manager extensions fill the hidden\n * input with the same value they put in a visible field (email, phone, …) —\n * a human with autofill, not a bot. Scans top-level string values plus one\n * nested level (the booking form's custom `formFields` object).\n */\nfunction honeypotCopiedFromAnotherField(body: unknown, honeypot: string): boolean {\n const b = (body ?? {}) as Record<string, unknown>;\n const signalKeys: readonly string[] = HUMANITY_SIGNAL_KEYS;\n for (const [key, value] of Object.entries(b)) {\n if (signalKeys.includes(key)) continue;\n if (typeof value === 'string' && value === honeypot) return true;\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n for (const nested of Object.values(value as Record<string, unknown>)) {\n if (typeof nested === 'string' && nested === honeypot) return true;\n }\n }\n }\n return false;\n}\n\n/**\n * SINGLE decision fn for honeypot + timing (the hub's `verifyHuman` imports + calls this):\n * - honeypot non-empty → bot (real users never fill the off-screen field) — UNLESS the\n * value was copied from another field in the body (autofill reached the decoy → human;\n * the verdict carries `note: 'honeypot_autofill'` so the caller can log it)\n * - elapsed below `minFillMs` → bot (humans take time; a MISSING timing value never blocks)\n */\nexport function evaluateHumanitySignals(body: unknown, opts: { minFillMs: number }): HumanityVerdict {\n const { honeypot, elapsedMs } = extractHumanitySignals(body);\n const filled = honeypot.trim() !== '';\n if (filled && !honeypotCopiedFromAnotherField(body, honeypot)) {\n return { ok: false, reason: 'honeypot' };\n }\n if (elapsedMs !== null && elapsedMs < opts.minFillMs) return { ok: false, reason: 'too_fast' };\n return filled ? { ok: true, note: 'honeypot_autofill' } : { ok: true };\n}\n\n/** Parse a comma-separated env string → trimmed, non-empty entries (undefined → []). */\nexport const splitCsvEnv = (s?: string): string[] =>\n s\n ?.split(',')\n .map(t => t.trim())\n .filter(Boolean) ?? [];\n"]}
|
|
@@ -11,9 +11,29 @@
|
|
|
11
11
|
* field real users never fill) and timing (ms from form mount to submit).
|
|
12
12
|
* `evaluateHumanitySignals` is the SINGLE source of truth for the block/allow
|
|
13
13
|
* decision — the hub imports + calls it rather than re-implementing the rules.
|
|
14
|
+
*
|
|
15
|
+
* FALSE-POSITIVE HISTORY (2026-08-27): the honeypot was named
|
|
16
|
+
* `contact_url_confirm`, and browser/password-manager autofill (which ignores
|
|
17
|
+
* `autocomplete="off"` and matches "url"/"confirm" name heuristics) filled it
|
|
18
|
+
* for REAL users — every production `BOT_DETECTED` in the log window was a
|
|
19
|
+
* legitimate Chrome user whose autofill tripped the decoy. Three layers now
|
|
20
|
+
* prevent a recurrence; keep all three when touching this system:
|
|
21
|
+
* 1. The field name avoids every autofill-heuristic token (name/email/
|
|
22
|
+
* phone/url/website/confirm/company/address/code/…).
|
|
23
|
+
* 2. `HoneypotField` renders `readOnly`-until-focus + password-manager
|
|
24
|
+
* ignore attributes — autofill skips read-only inputs.
|
|
25
|
+
* 3. `evaluateHumanitySignals` forgives a filled decoy whose value was
|
|
26
|
+
* COPIED from another field in the same body (the autofill signature —
|
|
27
|
+
* a human, not a bot).
|
|
28
|
+
*/
|
|
29
|
+
/** Hidden honeypot field name. Deliberately free of autofill-heuristic tokens (see module doc). */
|
|
30
|
+
export declare const HONEYPOT_FIELD = "form_extra_note";
|
|
31
|
+
/**
|
|
32
|
+
* Prior honeypot field name — still EVALUATED (a stale-lib embedder's client
|
|
33
|
+
* keeps its bot protection) and still STRIPPED before upstream forwarding.
|
|
34
|
+
* Remove once no deployed embedder ships a lib older than the rename.
|
|
14
35
|
*/
|
|
15
|
-
|
|
16
|
-
export declare const HONEYPOT_FIELD = "contact_url_confirm";
|
|
36
|
+
export declare const LEGACY_HONEYPOT_FIELD = "contact_url_confirm";
|
|
17
37
|
/** Client-measured ms between form mount and submit. */
|
|
18
38
|
export declare const ELAPSED_MS_FIELD = "form_elapsed_ms";
|
|
19
39
|
/** Default minimum fill time (ms). A submit faster than this is treated as a bot. */
|
|
@@ -25,12 +45,13 @@ export declare const DEFAULT_MIN_FILL_MS = 700;
|
|
|
25
45
|
* field rename here propagates everywhere and the honeypot value can never
|
|
26
46
|
* silently leak into an upstream record.
|
|
27
47
|
*/
|
|
28
|
-
export declare const HUMANITY_SIGNAL_KEYS: readonly ["contact_url_confirm", "form_elapsed_ms"];
|
|
48
|
+
export declare const HUMANITY_SIGNAL_KEYS: readonly ["form_extra_note", "contact_url_confirm", "form_elapsed_ms"];
|
|
29
49
|
/** Keyed wire object produced by `useHumanitySignals().getSignals()` and spread into the POST body. */
|
|
30
50
|
export type HumanitySignals = Record<string, string | number>;
|
|
31
51
|
/** Result of {@link evaluateHumanitySignals}. */
|
|
32
52
|
export type HumanityVerdict = {
|
|
33
53
|
ok: true;
|
|
54
|
+
note?: 'honeypot_autofill';
|
|
34
55
|
} | {
|
|
35
56
|
ok: false;
|
|
36
57
|
reason: 'honeypot' | 'too_fast';
|
|
@@ -42,7 +63,9 @@ export declare function extractHumanitySignals(body: unknown): {
|
|
|
42
63
|
};
|
|
43
64
|
/**
|
|
44
65
|
* SINGLE decision fn for honeypot + timing (the hub's `verifyHuman` imports + calls this):
|
|
45
|
-
* - honeypot non-empty → bot (real users never fill the off-screen field)
|
|
66
|
+
* - honeypot non-empty → bot (real users never fill the off-screen field) — UNLESS the
|
|
67
|
+
* value was copied from another field in the body (autofill reached the decoy → human;
|
|
68
|
+
* the verdict carries `note: 'honeypot_autofill'` so the caller can log it)
|
|
46
69
|
* - elapsed below `minFillMs` → bot (humans take time; a MISSING timing value never blocks)
|
|
47
70
|
*/
|
|
48
71
|
export declare function evaluateHumanitySignals(body: unknown, opts: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"humanity-signals.d.ts","sourceRoot":"","sources":["../../src/utils/humanity-signals.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"humanity-signals.d.ts","sourceRoot":"","sources":["../../src/utils/humanity-signals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,mGAAmG;AACnG,eAAO,MAAM,cAAc,oBAAoB,CAAC;AAChD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAC3D,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,oBAAoB,CAAC;AAClD,qFAAqF;AACrF,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,wEAAqE,CAAC;AAEvG,uGAAuG;AACvG,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAE9D,iDAAiD;AACjD,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,mBAAmB,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAAA;CAAE,CAAC;AAExH,qEAAqE;AACrE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,OAAO,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAYpG;AAwBD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,eAAe,CAQnG;AAED,wFAAwF;AACxF,eAAO,MAAM,WAAW,GAAI,IAAI,MAAM,KAAG,MAAM,EAIrB,CAAC"}
|
|
@@ -1,24 +1,42 @@
|
|
|
1
1
|
// src/utils/humanity-signals.ts
|
|
2
|
-
var HONEYPOT_FIELD = "
|
|
2
|
+
var HONEYPOT_FIELD = "form_extra_note";
|
|
3
|
+
var LEGACY_HONEYPOT_FIELD = "contact_url_confirm";
|
|
3
4
|
var ELAPSED_MS_FIELD = "form_elapsed_ms";
|
|
4
5
|
var DEFAULT_MIN_FILL_MS = 700;
|
|
5
|
-
var HUMANITY_SIGNAL_KEYS = [HONEYPOT_FIELD, ELAPSED_MS_FIELD];
|
|
6
|
+
var HUMANITY_SIGNAL_KEYS = [HONEYPOT_FIELD, LEGACY_HONEYPOT_FIELD, ELAPSED_MS_FIELD];
|
|
6
7
|
function extractHumanitySignals(body) {
|
|
7
8
|
const b = body ?? {};
|
|
8
|
-
const rawHp = b[HONEYPOT_FIELD];
|
|
9
|
+
const rawHp = b[HONEYPOT_FIELD] ?? b[LEGACY_HONEYPOT_FIELD];
|
|
9
10
|
const honeypot = rawHp == null ? "" : String(rawHp);
|
|
10
11
|
const rawMs = b[ELAPSED_MS_FIELD];
|
|
11
12
|
const elapsedMs = typeof rawMs === "number" && Number.isFinite(rawMs) ? rawMs : null;
|
|
12
13
|
return { honeypot, elapsedMs };
|
|
13
14
|
}
|
|
15
|
+
function honeypotCopiedFromAnotherField(body, honeypot) {
|
|
16
|
+
const b = body ?? {};
|
|
17
|
+
const signalKeys = HUMANITY_SIGNAL_KEYS;
|
|
18
|
+
for (const [key, value] of Object.entries(b)) {
|
|
19
|
+
if (signalKeys.includes(key)) continue;
|
|
20
|
+
if (typeof value === "string" && value === honeypot) return true;
|
|
21
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
22
|
+
for (const nested of Object.values(value)) {
|
|
23
|
+
if (typeof nested === "string" && nested === honeypot) return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
14
29
|
function evaluateHumanitySignals(body, opts) {
|
|
15
30
|
const { honeypot, elapsedMs } = extractHumanitySignals(body);
|
|
16
|
-
|
|
31
|
+
const filled = honeypot.trim() !== "";
|
|
32
|
+
if (filled && !honeypotCopiedFromAnotherField(body, honeypot)) {
|
|
33
|
+
return { ok: false, reason: "honeypot" };
|
|
34
|
+
}
|
|
17
35
|
if (elapsedMs !== null && elapsedMs < opts.minFillMs) return { ok: false, reason: "too_fast" };
|
|
18
|
-
return { ok: true };
|
|
36
|
+
return filled ? { ok: true, note: "honeypot_autofill" } : { ok: true };
|
|
19
37
|
}
|
|
20
38
|
var splitCsvEnv = (s) => s?.split(",").map((t) => t.trim()).filter(Boolean) ?? [];
|
|
21
39
|
|
|
22
|
-
export { DEFAULT_MIN_FILL_MS, ELAPSED_MS_FIELD, HONEYPOT_FIELD, HUMANITY_SIGNAL_KEYS, evaluateHumanitySignals, extractHumanitySignals, splitCsvEnv };
|
|
40
|
+
export { DEFAULT_MIN_FILL_MS, ELAPSED_MS_FIELD, HONEYPOT_FIELD, HUMANITY_SIGNAL_KEYS, LEGACY_HONEYPOT_FIELD, evaluateHumanitySignals, extractHumanitySignals, splitCsvEnv };
|
|
23
41
|
//# sourceMappingURL=humanity-signals.js.map
|
|
24
42
|
//# sourceMappingURL=humanity-signals.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/humanity-signals.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/utils/humanity-signals.ts"],"names":[],"mappings":";AA8BO,IAAM,cAAA,GAAiB;AAMvB,IAAM,qBAAA,GAAwB;AAE9B,IAAM,gBAAA,GAAmB;AAEzB,IAAM,mBAAA,GAAsB;AAS5B,IAAM,oBAAA,GAAuB,CAAC,cAAA,EAAgB,qBAAA,EAAuB,gBAAgB;AASrF,SAAS,uBAAuB,IAAA,EAA+D;AACpG,EAAA,MAAM,CAAA,GAAK,QAAQ,EAAC;AAEpB,EAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,cAAc,CAAA,IAAK,EAAE,qBAAqB,CAAA;AAK1D,EAAA,MAAM,QAAA,GAAW,KAAA,IAAS,IAAA,GAAO,EAAA,GAAK,OAAO,KAAK,CAAA;AAClD,EAAA,MAAM,KAAA,GAAQ,EAAE,gBAAgB,CAAA;AAChC,EAAA,MAAM,SAAA,GAAY,OAAO,KAAA,KAAU,QAAA,IAAY,OAAO,QAAA,CAAS,KAAK,IAAI,KAAA,GAAQ,IAAA;AAChF,EAAA,OAAO,EAAE,UAAU,SAAA,EAAU;AAC/B;AASA,SAAS,8BAAA,CAA+B,MAAe,QAAA,EAA2B;AAChF,EAAA,MAAM,CAAA,GAAK,QAAQ,EAAC;AACpB,EAAA,MAAM,UAAA,GAAgC,oBAAA;AACtC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,CAAC,CAAA,EAAG;AAC5C,IAAA,IAAI,UAAA,CAAW,QAAA,CAAS,GAAG,CAAA,EAAG;AAC9B,IAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,UAAU,OAAO,IAAA;AAC5D,IAAA,IAAI,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,IAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/D,MAAA,KAAA,MAAW,MAAA,IAAU,MAAA,CAAO,MAAA,CAAO,KAAgC,CAAA,EAAG;AACpE,QAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,KAAW,UAAU,OAAO,IAAA;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AASO,SAAS,uBAAA,CAAwB,MAAe,IAAA,EAA8C;AACnG,EAAA,MAAM,EAAE,QAAA,EAAU,SAAA,EAAU,GAAI,uBAAuB,IAAI,CAAA;AAC3D,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,IAAA,EAAK,KAAM,EAAA;AACnC,EAAA,IAAI,MAAA,IAAU,CAAC,8BAAA,CAA+B,IAAA,EAAM,QAAQ,CAAA,EAAG;AAC7D,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,UAAA,EAAW;AAAA,EACzC;AACA,EAAA,IAAI,SAAA,KAAc,IAAA,IAAQ,SAAA,GAAY,IAAA,CAAK,SAAA,SAAkB,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,UAAA,EAAW;AAC7F,EAAA,OAAO,MAAA,GAAS,EAAE,EAAA,EAAI,IAAA,EAAM,MAAM,mBAAA,EAAoB,GAAI,EAAE,EAAA,EAAI,IAAA,EAAK;AACvE;AAGO,IAAM,cAAc,CAAC,CAAA,KAC1B,CAAA,EACI,KAAA,CAAM,GAAG,CAAA,CACV,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,MAAM,CAAA,CACjB,MAAA,CAAO,OAAO,KAAK","file":"humanity-signals.js","sourcesContent":["/**\n * Humanity signals — invisible bot-protection primitives shared by the lib's\n * public forms (client) and the hub's per-route `verifyHuman` gate (server).\n *\n * PURE + React-free on purpose: this module is a tsup SERVER entry (no\n * \"use client\" banner) so the hub can import it server-side without pulling a\n * client-reference boundary — same pattern as `schemas/contact-schema` and\n * `components/features/mux-origins`.\n *\n * Two origin-independent signals travel in the POST body: a honeypot (a hidden\n * field real users never fill) and timing (ms from form mount to submit).\n * `evaluateHumanitySignals` is the SINGLE source of truth for the block/allow\n * decision — the hub imports + calls it rather than re-implementing the rules.\n *\n * FALSE-POSITIVE HISTORY (2026-08-27): the honeypot was named\n * `contact_url_confirm`, and browser/password-manager autofill (which ignores\n * `autocomplete=\"off\"` and matches \"url\"/\"confirm\" name heuristics) filled it\n * for REAL users — every production `BOT_DETECTED` in the log window was a\n * legitimate Chrome user whose autofill tripped the decoy. Three layers now\n * prevent a recurrence; keep all three when touching this system:\n * 1. The field name avoids every autofill-heuristic token (name/email/\n * phone/url/website/confirm/company/address/code/…).\n * 2. `HoneypotField` renders `readOnly`-until-focus + password-manager\n * ignore attributes — autofill skips read-only inputs.\n * 3. `evaluateHumanitySignals` forgives a filled decoy whose value was\n * COPIED from another field in the same body (the autofill signature —\n * a human, not a bot).\n */\n\n/** Hidden honeypot field name. Deliberately free of autofill-heuristic tokens (see module doc). */\nexport const HONEYPOT_FIELD = 'form_extra_note';\n/**\n * Prior honeypot field name — still EVALUATED (a stale-lib embedder's client\n * keeps its bot protection) and still STRIPPED before upstream forwarding.\n * Remove once no deployed embedder ships a lib older than the rename.\n */\nexport const LEGACY_HONEYPOT_FIELD = 'contact_url_confirm';\n/** Client-measured ms between form mount and submit. */\nexport const ELAPSED_MS_FIELD = 'form_elapsed_ms';\n/** Default minimum fill time (ms). A submit faster than this is treated as a bot. */\nexport const DEFAULT_MIN_FILL_MS = 700;\n\n/**\n * Every humanity-signal key that rides in a public form's POST body.\n * Server-side handlers that forward form payloads upstream (HubSpot booking,\n * CRM pushes, …) MUST strip by THIS array — never hand-typed strings — so a\n * field rename here propagates everywhere and the honeypot value can never\n * silently leak into an upstream record.\n */\nexport const HUMANITY_SIGNAL_KEYS = [HONEYPOT_FIELD, LEGACY_HONEYPOT_FIELD, ELAPSED_MS_FIELD] as const;\n\n/** Keyed wire object produced by `useHumanitySignals().getSignals()` and spread into the POST body. */\nexport type HumanitySignals = Record<string, string | number>;\n\n/** Result of {@link evaluateHumanitySignals}. */\nexport type HumanityVerdict = { ok: true; note?: 'honeypot_autofill' } | { ok: false; reason: 'honeypot' | 'too_fast' };\n\n/** Tolerant reader — never throws; missing/garbage timing → null. */\nexport function extractHumanitySignals(body: unknown): { honeypot: string; elapsedMs: number | null } {\n const b = (body ?? {}) as Record<string, unknown>;\n // Current field name first; a stale-lib client still posts the legacy name.\n const rawHp = b[HONEYPOT_FIELD] ?? b[LEGACY_HONEYPOT_FIELD];\n // A legit client always sends a STRING here (getSignals → ref.value ?? ''),\n // so ANY present non-string value is a bot filling the decoy with a non-string\n // to dodge the empty-check — coerce to a (non-empty) string so it still trips.\n // null/undefined → '' = the correct \"field absent / unfilled\" allow case.\n const honeypot = rawHp == null ? '' : String(rawHp);\n const rawMs = b[ELAPSED_MS_FIELD];\n const elapsedMs = typeof rawMs === 'number' && Number.isFinite(rawMs) ? rawMs : null;\n return { honeypot, elapsedMs };\n}\n\n/**\n * Was the decoy value COPIED from a real field in the same body? That is the\n * autofill signature: browsers and password-manager extensions fill the hidden\n * input with the same value they put in a visible field (email, phone, …) —\n * a human with autofill, not a bot. Scans top-level string values plus one\n * nested level (the booking form's custom `formFields` object).\n */\nfunction honeypotCopiedFromAnotherField(body: unknown, honeypot: string): boolean {\n const b = (body ?? {}) as Record<string, unknown>;\n const signalKeys: readonly string[] = HUMANITY_SIGNAL_KEYS;\n for (const [key, value] of Object.entries(b)) {\n if (signalKeys.includes(key)) continue;\n if (typeof value === 'string' && value === honeypot) return true;\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n for (const nested of Object.values(value as Record<string, unknown>)) {\n if (typeof nested === 'string' && nested === honeypot) return true;\n }\n }\n }\n return false;\n}\n\n/**\n * SINGLE decision fn for honeypot + timing (the hub's `verifyHuman` imports + calls this):\n * - honeypot non-empty → bot (real users never fill the off-screen field) — UNLESS the\n * value was copied from another field in the body (autofill reached the decoy → human;\n * the verdict carries `note: 'honeypot_autofill'` so the caller can log it)\n * - elapsed below `minFillMs` → bot (humans take time; a MISSING timing value never blocks)\n */\nexport function evaluateHumanitySignals(body: unknown, opts: { minFillMs: number }): HumanityVerdict {\n const { honeypot, elapsedMs } = extractHumanitySignals(body);\n const filled = honeypot.trim() !== '';\n if (filled && !honeypotCopiedFromAnotherField(body, honeypot)) {\n return { ok: false, reason: 'honeypot' };\n }\n if (elapsedMs !== null && elapsedMs < opts.minFillMs) return { ok: false, reason: 'too_fast' };\n return filled ? { ok: true, note: 'honeypot_autofill' } : { ok: true };\n}\n\n/** Parse a comma-separated env string → trimmed, non-empty entries (undefined → []). */\nexport const splitCsvEnv = (s?: string): string[] =>\n s\n ?.split(',')\n .map(t => t.trim())\n .filter(Boolean) ?? [];\n"]}
|
package/dist/utils/index.cjs
CHANGED
|
@@ -4529,23 +4529,41 @@ function buildDefaultHref(basePath, slug) {
|
|
|
4529
4529
|
}
|
|
4530
4530
|
|
|
4531
4531
|
// src/utils/humanity-signals.ts
|
|
4532
|
-
var HONEYPOT_FIELD = "
|
|
4532
|
+
var HONEYPOT_FIELD = "form_extra_note";
|
|
4533
|
+
var LEGACY_HONEYPOT_FIELD = "contact_url_confirm";
|
|
4533
4534
|
var ELAPSED_MS_FIELD = "form_elapsed_ms";
|
|
4534
4535
|
var DEFAULT_MIN_FILL_MS = 700;
|
|
4535
|
-
var HUMANITY_SIGNAL_KEYS = [HONEYPOT_FIELD, ELAPSED_MS_FIELD];
|
|
4536
|
+
var HUMANITY_SIGNAL_KEYS = [HONEYPOT_FIELD, LEGACY_HONEYPOT_FIELD, ELAPSED_MS_FIELD];
|
|
4536
4537
|
function extractHumanitySignals(body) {
|
|
4537
4538
|
const b = body ?? {};
|
|
4538
|
-
const rawHp = b[HONEYPOT_FIELD];
|
|
4539
|
+
const rawHp = b[HONEYPOT_FIELD] ?? b[LEGACY_HONEYPOT_FIELD];
|
|
4539
4540
|
const honeypot = rawHp == null ? "" : String(rawHp);
|
|
4540
4541
|
const rawMs = b[ELAPSED_MS_FIELD];
|
|
4541
4542
|
const elapsedMs = typeof rawMs === "number" && Number.isFinite(rawMs) ? rawMs : null;
|
|
4542
4543
|
return { honeypot, elapsedMs };
|
|
4543
4544
|
}
|
|
4545
|
+
function honeypotCopiedFromAnotherField(body, honeypot) {
|
|
4546
|
+
const b = body ?? {};
|
|
4547
|
+
const signalKeys = HUMANITY_SIGNAL_KEYS;
|
|
4548
|
+
for (const [key, value] of Object.entries(b)) {
|
|
4549
|
+
if (signalKeys.includes(key)) continue;
|
|
4550
|
+
if (typeof value === "string" && value === honeypot) return true;
|
|
4551
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
4552
|
+
for (const nested of Object.values(value)) {
|
|
4553
|
+
if (typeof nested === "string" && nested === honeypot) return true;
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
return false;
|
|
4558
|
+
}
|
|
4544
4559
|
function evaluateHumanitySignals(body, opts) {
|
|
4545
4560
|
const { honeypot, elapsedMs } = extractHumanitySignals(body);
|
|
4546
|
-
|
|
4561
|
+
const filled = honeypot.trim() !== "";
|
|
4562
|
+
if (filled && !honeypotCopiedFromAnotherField(body, honeypot)) {
|
|
4563
|
+
return { ok: false, reason: "honeypot" };
|
|
4564
|
+
}
|
|
4547
4565
|
if (elapsedMs !== null && elapsedMs < opts.minFillMs) return { ok: false, reason: "too_fast" };
|
|
4548
|
-
return { ok: true };
|
|
4566
|
+
return filled ? { ok: true, note: "honeypot_autofill" } : { ok: true };
|
|
4549
4567
|
}
|
|
4550
4568
|
var splitCsvEnv = (s) => s?.split(",").map((t) => t.trim()).filter(Boolean) ?? [];
|
|
4551
4569
|
|
|
@@ -4945,6 +4963,7 @@ exports.HONEYPOT_FIELD = HONEYPOT_FIELD;
|
|
|
4945
4963
|
exports.HUB_HEADER_OFFSET_PX = HUB_HEADER_OFFSET_PX;
|
|
4946
4964
|
exports.HUMANITY_SIGNAL_KEYS = HUMANITY_SIGNAL_KEYS;
|
|
4947
4965
|
exports.ICON_REGISTRY = ICON_REGISTRY;
|
|
4966
|
+
exports.LEGACY_HONEYPOT_FIELD = LEGACY_HONEYPOT_FIELD;
|
|
4948
4967
|
exports.MAX_MONTH_OFFSET = MAX_MONTH_OFFSET;
|
|
4949
4968
|
exports.OPENFRAME_DEV_SECTIONS = OPENFRAME_DEV_SECTIONS;
|
|
4950
4969
|
exports.OS_PLATFORMS = OS_PLATFORMS;
|