@uranotools/urano-social 0.2.0 → 0.3.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/dist/crypto.js CHANGED
@@ -1,48 +1,8 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.signHmacHex = signHmacHex;
37
- exports.verifyHmacHex = verifyHmacHex;
38
- exports.randomId = randomId;
39
- exports.signCookieValue = signCookieValue;
40
- exports.verifyCookieValue = verifyCookieValue;
41
- const crypto = __importStar(require("crypto"));
42
- function signHmacHex(payload, secret) {
1
+ import * as crypto from 'crypto';
2
+ export function signHmacHex(payload, secret) {
43
3
  return crypto.createHmac('sha256', secret).update(payload).digest('hex');
44
4
  }
45
- function verifyHmacHex(payload, signature, secret) {
5
+ export function verifyHmacHex(payload, signature, secret) {
46
6
  try {
47
7
  const expected = signHmacHex(payload, secret);
48
8
  const clean = String(signature || '').replace(/^(sha256=|sha1=)/i, '');
@@ -56,14 +16,14 @@ function verifyHmacHex(payload, signature, secret) {
56
16
  return false;
57
17
  }
58
18
  }
59
- function randomId(prefix = 'vis') {
19
+ export function randomId(prefix = 'vis') {
60
20
  return `${prefix}_${crypto.randomBytes(16).toString('hex')}`;
61
21
  }
62
- function signCookieValue(value, secret) {
22
+ export function signCookieValue(value, secret) {
63
23
  const sig = signHmacHex(value, secret);
64
24
  return `${value}.${sig}`;
65
25
  }
66
- function verifyCookieValue(signed, secret) {
26
+ export function verifyCookieValue(signed, secret) {
67
27
  const idx = signed.lastIndexOf('.');
68
28
  if (idx <= 0)
69
29
  return null;
@@ -1,9 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UranoSocialChatElement = void 0;
4
- exports.registerUranoSocialChat = registerUranoSocialChat;
5
- const createUranoChat_1 = require("../client/createUranoChat");
6
- class UranoSocialChatElement extends HTMLElement {
1
+ import { createUranoChat } from '../client/createUranoChat';
2
+ export class UranoSocialChatElement extends HTMLElement {
7
3
  static get observedAttributes() {
8
4
  return ['flavor', 'endpoint', 'title', 'open', 'displayname'];
9
5
  }
@@ -19,7 +15,7 @@ class UranoSocialChatElement extends HTMLElement {
19
15
  }
20
16
  connectedCallback() {
21
17
  this.#open = this.getAttribute('open') === 'true' || this.getAttribute('flavor') === 'sidebar';
22
- this.#chat = (0, createUranoChat_1.createUranoChat)({
18
+ this.#chat = createUranoChat({
23
19
  endpoint: this.getAttribute('endpoint') || '/api/urano-social',
24
20
  displayName: this.getAttribute('displayname') || undefined,
25
21
  onMessages: (msgs) => {
@@ -131,7 +127,6 @@ class UranoSocialChatElement extends HTMLElement {
131
127
  scroller.scrollTop = scroller.scrollHeight;
132
128
  }
133
129
  }
134
- exports.UranoSocialChatElement = UranoSocialChatElement;
135
130
  function escapeHtml(value) {
136
131
  return String(value)
137
132
  .replace(/&/g, '&amp;')
@@ -169,7 +164,7 @@ function getEmbeddedCss() {
169
164
  .urano-social-host[data-flavor=sidebar][data-open=false]{display:none}
170
165
  `;
171
166
  }
172
- function registerUranoSocialChat(tagName = 'urano-social-chat') {
167
+ export function registerUranoSocialChat(tagName = 'urano-social-chat') {
173
168
  if (typeof customElements === 'undefined')
174
169
  return;
175
170
  if (!customElements.get(tagName)) {
@@ -1,11 +1,8 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UranoSocialContactFormElement = void 0;
4
- exports.registerUranoSocialContactForm = registerUranoSocialContactForm;
5
- const submitContactForm_1 = require("../client/submitContactForm");
6
- class UranoSocialContactFormElement extends HTMLElement {
1
+ import { submitContactForm } from '../client/submitContactForm';
2
+ import { DEFAULT_HONEYPOT_FIELD } from '../types';
3
+ export class UranoSocialContactFormElement extends HTMLElement {
7
4
  static get observedAttributes() {
8
- return ['endpoint', 'title', 'submit-label'];
5
+ return ['endpoint', 'title', 'submit-label', 'recaptcha-site-key', 'honeypot-field'];
9
6
  }
10
7
  #shadow;
11
8
  #busy = false;
@@ -24,6 +21,7 @@ class UranoSocialContactFormElement extends HTMLElement {
24
21
  render() {
25
22
  const title = this.getAttribute('title') || 'Contáctanos';
26
23
  const submitLabel = this.getAttribute('submit-label') || 'Enviar';
24
+ const honeypotField = this.getAttribute('honeypot-field') || DEFAULT_HONEYPOT_FIELD;
27
25
  this.#shadow.innerHTML = `
28
26
  <style>${css}</style>
29
27
  <form class="us-contact" part="form">
@@ -32,6 +30,9 @@ class UranoSocialContactFormElement extends HTMLElement {
32
30
  <label>Email<input name="email" type="email" required autocomplete="email" /></label>
33
31
  <label>Asunto<input name="subject" type="text" /></label>
34
32
  <label>Mensaje<textarea name="message" rows="4" required></textarea></label>
33
+ <label class="hp" aria-hidden="true">${escapeHtml(honeypotField)}
34
+ <input name="${escapeHtml(honeypotField)}" type="text" tabindex="-1" autocomplete="off" />
35
+ </label>
35
36
  ${this.#error ? `<p class="err" part="error">${escapeHtml(this.#error)}</p>` : ''}
36
37
  ${this.#success ? `<p class="ok" part="success">${escapeHtml(this.#success)}</p>` : ''}
37
38
  <button type="submit" ${this.#busy ? 'disabled' : ''} part="submit">${escapeHtml(submitLabel)}</button>
@@ -48,12 +49,16 @@ class UranoSocialContactFormElement extends HTMLElement {
48
49
  this.#success = '';
49
50
  this.render();
50
51
  try {
51
- const result = await (0, submitContactForm_1.submitContactForm)({
52
+ const result = await submitContactForm({
52
53
  name: String(data.get('name') || ''),
53
54
  email: String(data.get('email') || ''),
54
55
  subject: String(data.get('subject') || ''),
55
56
  message: String(data.get('message') || ''),
56
- }, { endpoint: this.getAttribute('endpoint') || '/api/urano-social', useGet: true });
57
+ website: String(data.get(honeypotField) || ''),
58
+ }, {
59
+ endpoint: this.getAttribute('endpoint') || '/api/urano-social',
60
+ recaptchaSiteKey: this.getAttribute('recaptcha-site-key') || undefined,
61
+ });
57
62
  if (!result.ok)
58
63
  throw new Error(result.error || 'send_failed');
59
64
  this.#success = result.text
@@ -70,7 +75,6 @@ class UranoSocialContactFormElement extends HTMLElement {
70
75
  });
71
76
  }
72
77
  }
73
- exports.UranoSocialContactFormElement = UranoSocialContactFormElement;
74
78
  function escapeHtml(value) {
75
79
  return String(value)
76
80
  .replace(/&/g, '&amp;')
@@ -80,16 +84,17 @@ function escapeHtml(value) {
80
84
  }
81
85
  const css = `
82
86
  :host{all:initial;display:block;font-family:ui-sans-serif,system-ui,sans-serif;color:#e8eef6}
83
- .us-contact{display:flex;flex-direction:column;gap:10px;padding:16px;background:#1a2332;border:1px solid #2a3a4f;border-radius:14px;max-width:420px}
87
+ .us-contact{display:flex;flex-direction:column;gap:10px;padding:16px;background:#1a2332;border:1px solid #2a3a4f;border-radius:14px;max-width:420px;position:relative}
84
88
  h3{margin:0 0 4px;font-size:16px}
85
89
  label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:#8fa3b8}
90
+ label.hp{position:absolute;left:-10000px;top:auto;width:1px;height:1px;overflow:hidden}
86
91
  input,textarea{border:1px solid #2a3a4f;border-radius:10px;background:#0f1419;color:#e8eef6;padding:10px 12px;font:inherit}
87
92
  button{border:0;border-radius:10px;background:#3dba9a;color:#06241c;padding:10px 14px;font-weight:600;cursor:pointer}
88
93
  button:disabled{opacity:.55;cursor:not-allowed}
89
94
  .err{color:#e35d6a;font-size:12px;margin:0}
90
95
  .ok{color:#3dba9a;font-size:12px;margin:0}
91
96
  `;
92
- function registerUranoSocialContactForm(tagName = 'urano-social-contact') {
97
+ export function registerUranoSocialContactForm(tagName = 'urano-social-contact') {
93
98
  if (typeof customElements === 'undefined')
94
99
  return;
95
100
  if (!customElements.get(tagName)) {
package/dist/index.d.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  export { createUranoChat } from './client/createUranoChat';
2
2
  export type { UranoChat } from './client/createUranoChat';
3
3
  export { readSseStream, parseSseChunk } from './client/sse';
4
- export { submitContactForm } from './client/submitContactForm';
5
- export type { ContactFormPayload, ContactFormResult, SubmitContactFormOptions } from './client/submitContactForm';
4
+ export { submitContactForm, fetchContactTicket } from './client/submitContactForm';
5
+ export type { ContactFormPayload, ContactFormResult, ContactTicketResponse, SubmitContactFormOptions, } from './client/submitContactForm';
6
6
  export { createSocialProxy, socialProxyRoute } from './server/proxy';
7
7
  export { attachSocialWebSocket, buildWsEnvelope } from './server/websocket';
8
8
  export { registerUranoSocialChat, UranoSocialChatElement } from './flavors/UranoSocialChatElement';
9
9
  export type { SocialFlavor } from './flavors/UranoSocialChatElement';
10
10
  export { registerUranoSocialContactForm, UranoSocialContactFormElement } from './flavors/UranoSocialContactFormElement';
11
11
  export { signHmacHex, verifyHmacHex, randomId } from './crypto';
12
- export type { SocialProxyConfig, SocialClientMessage, SocialChatEvent, CreateUranoChatOptions, SocialSendOptions, SocialTransport, } from './types';
12
+ export { DEFAULT_HONEYPOT_FIELD, DEFAULT_RECAPTCHA_ACTION, } from './types';
13
+ export type { SocialProxyConfig, ContactAntiSpamConfig, SocialClientMessage, SocialChatEvent, CreateUranoChatOptions, SocialSendOptions, SocialTransport, } from './types';
14
+ export { loadRecaptchaV3, executeRecaptchaV3 } from './client/recaptcha';
package/dist/index.js CHANGED
@@ -1,26 +1,10 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.randomId = exports.verifyHmacHex = exports.signHmacHex = exports.UranoSocialContactFormElement = exports.registerUranoSocialContactForm = exports.UranoSocialChatElement = exports.registerUranoSocialChat = exports.buildWsEnvelope = exports.attachSocialWebSocket = exports.socialProxyRoute = exports.createSocialProxy = exports.submitContactForm = exports.parseSseChunk = exports.readSseStream = exports.createUranoChat = void 0;
4
- var createUranoChat_1 = require("./client/createUranoChat");
5
- Object.defineProperty(exports, "createUranoChat", { enumerable: true, get: function () { return createUranoChat_1.createUranoChat; } });
6
- var sse_1 = require("./client/sse");
7
- Object.defineProperty(exports, "readSseStream", { enumerable: true, get: function () { return sse_1.readSseStream; } });
8
- Object.defineProperty(exports, "parseSseChunk", { enumerable: true, get: function () { return sse_1.parseSseChunk; } });
9
- var submitContactForm_1 = require("./client/submitContactForm");
10
- Object.defineProperty(exports, "submitContactForm", { enumerable: true, get: function () { return submitContactForm_1.submitContactForm; } });
11
- var proxy_1 = require("./server/proxy");
12
- Object.defineProperty(exports, "createSocialProxy", { enumerable: true, get: function () { return proxy_1.createSocialProxy; } });
13
- Object.defineProperty(exports, "socialProxyRoute", { enumerable: true, get: function () { return proxy_1.socialProxyRoute; } });
14
- var websocket_1 = require("./server/websocket");
15
- Object.defineProperty(exports, "attachSocialWebSocket", { enumerable: true, get: function () { return websocket_1.attachSocialWebSocket; } });
16
- Object.defineProperty(exports, "buildWsEnvelope", { enumerable: true, get: function () { return websocket_1.buildWsEnvelope; } });
17
- var UranoSocialChatElement_1 = require("./flavors/UranoSocialChatElement");
18
- Object.defineProperty(exports, "registerUranoSocialChat", { enumerable: true, get: function () { return UranoSocialChatElement_1.registerUranoSocialChat; } });
19
- Object.defineProperty(exports, "UranoSocialChatElement", { enumerable: true, get: function () { return UranoSocialChatElement_1.UranoSocialChatElement; } });
20
- var UranoSocialContactFormElement_1 = require("./flavors/UranoSocialContactFormElement");
21
- Object.defineProperty(exports, "registerUranoSocialContactForm", { enumerable: true, get: function () { return UranoSocialContactFormElement_1.registerUranoSocialContactForm; } });
22
- Object.defineProperty(exports, "UranoSocialContactFormElement", { enumerable: true, get: function () { return UranoSocialContactFormElement_1.UranoSocialContactFormElement; } });
23
- var crypto_1 = require("./crypto");
24
- Object.defineProperty(exports, "signHmacHex", { enumerable: true, get: function () { return crypto_1.signHmacHex; } });
25
- Object.defineProperty(exports, "verifyHmacHex", { enumerable: true, get: function () { return crypto_1.verifyHmacHex; } });
26
- Object.defineProperty(exports, "randomId", { enumerable: true, get: function () { return crypto_1.randomId; } });
1
+ export { createUranoChat } from './client/createUranoChat';
2
+ export { readSseStream, parseSseChunk } from './client/sse';
3
+ export { submitContactForm, fetchContactTicket } from './client/submitContactForm';
4
+ export { createSocialProxy, socialProxyRoute } from './server/proxy';
5
+ export { attachSocialWebSocket, buildWsEnvelope } from './server/websocket';
6
+ export { registerUranoSocialChat, UranoSocialChatElement } from './flavors/UranoSocialChatElement';
7
+ export { registerUranoSocialContactForm, UranoSocialContactFormElement } from './flavors/UranoSocialContactFormElement';
8
+ export { signHmacHex, verifyHmacHex, randomId } from './crypto';
9
+ export { DEFAULT_HONEYPOT_FIELD, DEFAULT_RECAPTCHA_ACTION, } from './types';
10
+ export { loadRecaptchaV3, executeRecaptchaV3 } from './client/recaptcha';
@@ -1,57 +1,20 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.UranoSocialChat = UranoSocialChat;
37
- exports.useUranoChat = useUranoChat;
38
- const jsx_runtime_1 = require("react/jsx-runtime");
39
- const react_1 = __importStar(require("react"));
40
- const createUranoChat_1 = require("../client/createUranoChat");
41
- function UranoSocialChat(props) {
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { createUranoChat } from '../client/createUranoChat';
4
+ export function UranoSocialChat(props) {
42
5
  const flavor = props.flavor || 'floating';
43
- const [open, setOpen] = (0, react_1.useState)(props.open ?? props.defaultOpen ?? flavor === 'sidebar');
44
- const [messages, setMessages] = (0, react_1.useState)([]);
45
- const [streaming, setStreaming] = (0, react_1.useState)(false);
46
- const [error, setError] = (0, react_1.useState)('');
47
- const [draft, setDraft] = (0, react_1.useState)('');
48
- const chatRef = (0, react_1.useRef)(null);
49
- (0, react_1.useEffect)(() => {
6
+ const [open, setOpen] = useState(props.open ?? props.defaultOpen ?? flavor === 'sidebar');
7
+ const [messages, setMessages] = useState([]);
8
+ const [streaming, setStreaming] = useState(false);
9
+ const [error, setError] = useState('');
10
+ const [draft, setDraft] = useState('');
11
+ const chatRef = useRef(null);
12
+ useEffect(() => {
50
13
  if (props.open !== undefined)
51
14
  setOpen(props.open);
52
15
  }, [props.open]);
53
- (0, react_1.useEffect)(() => {
54
- const chat = (0, createUranoChat_1.createUranoChat)({
16
+ useEffect(() => {
17
+ const chat = createUranoChat({
55
18
  endpoint: props.endpoint || '/api/urano-social',
56
19
  displayName: props.displayName,
57
20
  onMessages: setMessages,
@@ -80,14 +43,14 @@ function UranoSocialChat(props) {
80
43
  };
81
44
  const header = props.renderHeader
82
45
  ? props.renderHeader({ title: props.title || 'Atención al cliente', onClose: () => setOpenBoth(false) })
83
- : ((0, jsx_runtime_1.jsxs)("div", { className: "urano-social-header", children: [(0, jsx_runtime_1.jsx)("h2", { children: props.title || 'Atención al cliente' }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setOpenBoth(false), "aria-label": "Cerrar", children: "\u00D7" })] }));
46
+ : (_jsxs("div", { className: "urano-social-header", children: [_jsx("h2", { children: props.title || 'Atención al cliente' }), _jsx("button", { type: "button", onClick: () => setOpenBoth(false), "aria-label": "Cerrar", children: "\u00D7" })] }));
84
47
  const messageList = props.renderMessages
85
48
  ? props.renderMessages({ messages })
86
- : ((0, jsx_runtime_1.jsx)("div", { className: "urano-social-messages", children: messages.length === 0
87
- ? (props.renderEmpty?.() || (0, jsx_runtime_1.jsx)("div", { className: "urano-social-empty", children: "Escribe tu consulta y te atendemos." }))
49
+ : (_jsx("div", { className: "urano-social-messages", children: messages.length === 0
50
+ ? (props.renderEmpty?.() || _jsx("div", { className: "urano-social-empty", children: "Escribe tu consulta y te atendemos." }))
88
51
  : messages.map((m) => (props.renderMessage
89
- ? (0, jsx_runtime_1.jsx)(react_1.default.Fragment, { children: props.renderMessage(m) }, m.id)
90
- : ((0, jsx_runtime_1.jsx)("div", { className: "urano-social-bubble", "data-role": m.role, "data-streaming": m.streaming ? 'true' : 'false', children: m.content }, m.id)))) }));
52
+ ? _jsx(React.Fragment, { children: props.renderMessage(m) }, m.id)
53
+ : (_jsx("div", { className: "urano-social-bubble", "data-role": m.role, "data-streaming": m.streaming ? 'true' : 'false', children: m.content }, m.id)))) }));
91
54
  const composer = props.renderComposer
92
55
  ? props.renderComposer({
93
56
  disabled: streaming,
@@ -95,24 +58,24 @@ function UranoSocialChat(props) {
95
58
  onChange: setDraft,
96
59
  onSubmit,
97
60
  })
98
- : ((0, jsx_runtime_1.jsxs)("form", { className: "urano-social-composer", onSubmit: (e) => {
61
+ : (_jsxs("form", { className: "urano-social-composer", onSubmit: (e) => {
99
62
  e.preventDefault();
100
63
  void onSubmit();
101
- }, children: [(0, jsx_runtime_1.jsx)("textarea", { rows: 1, value: draft, disabled: streaming, placeholder: "Escribe un mensaje\u2026", onChange: (e) => setDraft(e.target.value) }), (0, jsx_runtime_1.jsx)("button", { type: "submit", disabled: streaming || !draft.trim(), children: "Enviar" })] }));
64
+ }, children: [_jsx("textarea", { rows: 1, value: draft, disabled: streaming, placeholder: "Escribe un mensaje\u2026", onChange: (e) => setDraft(e.target.value) }), _jsx("button", { type: "submit", disabled: streaming || !draft.trim(), children: "Enviar" })] }));
102
65
  const launcher = flavor === 'floating'
103
66
  ? (props.renderLauncher
104
67
  ? props.renderLauncher({ open, onToggle: () => setOpenBoth(!open) })
105
- : (0, jsx_runtime_1.jsx)("button", { type: "button", className: "urano-social-launcher", onClick: () => setOpenBoth(!open), children: "\uD83D\uDCAC" }))
68
+ : _jsx("button", { type: "button", className: "urano-social-launcher", onClick: () => setOpenBoth(!open), children: "\uD83D\uDCAC" }))
106
69
  : null;
107
70
  if (flavor === 'headless')
108
71
  return null;
109
- return ((0, jsx_runtime_1.jsxs)("div", { className: ['urano-social-host', props.className].filter(Boolean).join(' '), "data-flavor": flavor, "data-open": String(open), children: [launcher, (0, jsx_runtime_1.jsxs)("div", { className: "urano-social-panel", children: [header, messageList, error ? (0, jsx_runtime_1.jsx)("div", { className: "urano-social-error", children: error }) : null, composer] })] }));
72
+ return (_jsxs("div", { className: ['urano-social-host', props.className].filter(Boolean).join(' '), "data-flavor": flavor, "data-open": String(open), children: [launcher, _jsxs("div", { className: "urano-social-panel", children: [header, messageList, error ? _jsx("div", { className: "urano-social-error", children: error }) : null, composer] })] }));
110
73
  }
111
- function useUranoChat(options) {
112
- const chat = (0, react_1.useMemo)(() => (0, createUranoChat_1.createUranoChat)({
74
+ export function useUranoChat(options) {
75
+ const chat = useMemo(() => createUranoChat({
113
76
  endpoint: options.endpoint,
114
77
  displayName: options.displayName,
115
78
  }), [options.endpoint, options.displayName]);
116
- (0, react_1.useEffect)(() => () => chat.destroy(), [chat]);
79
+ useEffect(() => () => chat.destroy(), [chat]);
117
80
  return chat;
118
81
  }
@@ -4,6 +4,10 @@ export interface UranoSocialContactFormProps {
4
4
  title?: string;
5
5
  submitLabel?: string;
6
6
  className?: string;
7
+ /** Public reCAPTCHA v3 site key — paste from Google admin; secret stays server-side. */
8
+ recaptchaSiteKey?: string;
9
+ recaptchaAction?: string;
10
+ honeypotField?: string;
7
11
  onSuccess?: (text: string) => void;
8
12
  onError?: (error: string) => void;
9
13
  renderFields?: (props: {
@@ -1,14 +1,29 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UranoSocialContactForm = UranoSocialContactForm;
4
- const jsx_runtime_1 = require("react/jsx-runtime");
5
- const react_1 = require("react");
6
- const submitContactForm_1 = require("../client/submitContactForm");
7
- function UranoSocialContactForm(props) {
8
- const [values, setValues] = (0, react_1.useState)({ name: '', email: '', subject: '', message: '' });
9
- const [busy, setBusy] = (0, react_1.useState)(false);
10
- const [error, setError] = (0, react_1.useState)('');
11
- const [success, setSuccess] = (0, react_1.useState)('');
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { fetchContactTicket, submitContactForm } from '../client/submitContactForm';
4
+ import { DEFAULT_HONEYPOT_FIELD } from '../types';
5
+ export function UranoSocialContactForm(props) {
6
+ const honeypotField = props.honeypotField || DEFAULT_HONEYPOT_FIELD;
7
+ const endpoint = props.endpoint || '/api/urano-social';
8
+ const [values, setValues] = useState({
9
+ name: '',
10
+ email: '',
11
+ subject: '',
12
+ message: '',
13
+ [honeypotField]: '',
14
+ });
15
+ const [ticket, setTicket] = useState('');
16
+ const [busy, setBusy] = useState(false);
17
+ const [error, setError] = useState('');
18
+ const [success, setSuccess] = useState('');
19
+ useEffect(() => {
20
+ let cancelled = false;
21
+ fetchContactTicket(endpoint)
22
+ .then((res) => { if (!cancelled)
23
+ setTicket(res.ticket); })
24
+ .catch(() => { });
25
+ return () => { cancelled = true; };
26
+ }, [endpoint]);
12
27
  const setValue = (key, value) => {
13
28
  setValues((prev) => ({ ...prev, [key]: value }));
14
29
  };
@@ -20,18 +35,33 @@ function UranoSocialContactForm(props) {
20
35
  setError('');
21
36
  setSuccess('');
22
37
  try {
23
- const result = await (0, submitContactForm_1.submitContactForm)({
38
+ const result = await submitContactForm({
24
39
  name: values.name,
25
40
  email: values.email,
26
41
  subject: values.subject,
27
42
  message: values.message,
28
- }, { endpoint: props.endpoint || '/api/urano-social', useGet: true });
43
+ website: values[honeypotField] || '',
44
+ contactTicket: ticket || undefined,
45
+ }, {
46
+ endpoint,
47
+ recaptchaSiteKey: props.recaptchaSiteKey,
48
+ recaptchaAction: props.recaptchaAction,
49
+ });
29
50
  if (!result.ok)
30
51
  throw new Error(result.error || 'send_failed');
31
52
  const msg = 'Enviado. Te responderemos por correo.';
32
53
  setSuccess(msg);
33
54
  props.onSuccess?.(result.text || msg);
34
- setValues({ name: '', email: '', subject: '', message: '' });
55
+ setValues({
56
+ name: '',
57
+ email: '',
58
+ subject: '',
59
+ message: '',
60
+ [honeypotField]: '',
61
+ });
62
+ const next = await fetchContactTicket(endpoint).catch(() => null);
63
+ if (next)
64
+ setTicket(next.ticket);
35
65
  }
36
66
  catch (e) {
37
67
  const msg = e?.message || 'send_failed';
@@ -42,7 +72,7 @@ function UranoSocialContactForm(props) {
42
72
  setBusy(false);
43
73
  }
44
74
  };
45
- return ((0, jsx_runtime_1.jsxs)("form", { className: ['urano-social-contact', props.className].filter(Boolean).join(' '), onSubmit: onSubmit, children: [(0, jsx_runtime_1.jsx)("h3", { children: props.title || 'Contáctanos' }), props.renderFields
75
+ return (_jsxs("form", { className: ['urano-social-contact', props.className].filter(Boolean).join(' '), onSubmit: onSubmit, children: [_jsx("h3", { children: props.title || 'Contáctanos' }), props.renderFields
46
76
  ? props.renderFields({ values, setValue, disabled: busy })
47
- : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("label", { children: ["Nombre", (0, jsx_runtime_1.jsx)("input", { value: values.name, disabled: busy, onChange: (e) => setValue('name', e.target.value) })] }), (0, jsx_runtime_1.jsxs)("label", { children: ["Email", (0, jsx_runtime_1.jsx)("input", { type: "email", required: true, value: values.email, disabled: busy, onChange: (e) => setValue('email', e.target.value) })] }), (0, jsx_runtime_1.jsxs)("label", { children: ["Asunto", (0, jsx_runtime_1.jsx)("input", { value: values.subject, disabled: busy, onChange: (e) => setValue('subject', e.target.value) })] }), (0, jsx_runtime_1.jsxs)("label", { children: ["Mensaje", (0, jsx_runtime_1.jsx)("textarea", { required: true, rows: 4, value: values.message, disabled: busy, onChange: (e) => setValue('message', e.target.value) })] })] })), error ? (0, jsx_runtime_1.jsx)("p", { className: "urano-social-error", children: error }) : null, success ? (0, jsx_runtime_1.jsx)("p", { style: { color: 'var(--us-accent, #3dba9a)', fontSize: 12 }, children: success }) : null, (0, jsx_runtime_1.jsx)("button", { type: "submit", disabled: busy, children: props.submitLabel || 'Enviar' })] }));
77
+ : (_jsxs(_Fragment, { children: [_jsxs("label", { children: ["Nombre", _jsx("input", { value: values.name, disabled: busy, onChange: (e) => setValue('name', e.target.value) })] }), _jsxs("label", { children: ["Email", _jsx("input", { type: "email", required: true, value: values.email, disabled: busy, onChange: (e) => setValue('email', e.target.value) })] }), _jsxs("label", { children: ["Asunto", _jsx("input", { value: values.subject, disabled: busy, onChange: (e) => setValue('subject', e.target.value) })] }), _jsxs("label", { children: ["Mensaje", _jsx("textarea", { required: true, rows: 4, value: values.message, disabled: busy, onChange: (e) => setValue('message', e.target.value) })] })] })), _jsxs("label", { "aria-hidden": "true", style: { position: 'absolute', left: '-10000px', top: 'auto', width: 1, height: 1, overflow: 'hidden' }, children: [honeypotField, _jsx("input", { tabIndex: -1, autoComplete: "off", name: honeypotField, value: values[honeypotField] || '', onChange: (e) => setValue(honeypotField, e.target.value) })] }), error ? _jsx("p", { className: "urano-social-error", children: error }) : null, success ? _jsx("p", { style: { color: 'var(--us-accent, #3dba9a)', fontSize: 12 }, children: success }) : null, _jsx("button", { type: "submit", disabled: busy, children: props.submitLabel || 'Enviar' })] }));
48
78
  }
@@ -1,8 +1,2 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UranoSocialContactForm = exports.useUranoChat = exports.UranoSocialChat = void 0;
4
- var UranoSocialChat_1 = require("./UranoSocialChat");
5
- Object.defineProperty(exports, "UranoSocialChat", { enumerable: true, get: function () { return UranoSocialChat_1.UranoSocialChat; } });
6
- Object.defineProperty(exports, "useUranoChat", { enumerable: true, get: function () { return UranoSocialChat_1.useUranoChat; } });
7
- var UranoSocialContactForm_1 = require("./UranoSocialContactForm");
8
- Object.defineProperty(exports, "UranoSocialContactForm", { enumerable: true, get: function () { return UranoSocialContactForm_1.UranoSocialContactForm; } });
1
+ export { UranoSocialChat, useUranoChat } from './UranoSocialChat';
2
+ export { UranoSocialContactForm } from './UranoSocialContactForm';
@@ -0,0 +1,76 @@
1
+ import { DEFAULT_HONEYPOT_FIELD, DEFAULT_RECAPTCHA_ACTION, type ContactAntiSpamConfig } from '../types';
2
+ export { DEFAULT_HONEYPOT_FIELD, DEFAULT_RECAPTCHA_ACTION };
3
+ export declare const DEFAULT_RECAPTCHA_MIN_SCORE = 0.5;
4
+ export declare const DEFAULT_CONTACT_MIN_AGE_MS = 2500;
5
+ export declare const DEFAULT_CONTACT_MAX_AGE_MS: number;
6
+ export declare const DEFAULT_CONTACT_MAX_PER_HOUR = 5;
7
+ export declare const DEFAULT_CONTACT_MAX_MESSAGE_CHARS = 4000;
8
+ export declare function resolveAntiSpamConfig(partial?: Partial<ContactAntiSpamConfig>): ContactAntiSpamConfig;
9
+ export declare function issueContactTicket(secret: string, now?: number): string;
10
+ export declare function verifyContactTicket(ticket: string | undefined, secret: string, opts: {
11
+ minAgeMs: number;
12
+ maxAgeMs: number;
13
+ }, now?: number): {
14
+ ok: true;
15
+ issuedAt: number;
16
+ } | {
17
+ ok: false;
18
+ error: string;
19
+ };
20
+ export declare function checkContactRateLimit(key: string, maxPerHour: number, now?: number): {
21
+ ok: true;
22
+ } | {
23
+ ok: false;
24
+ error: string;
25
+ retryAfterSec: number;
26
+ };
27
+ /** Test helper — clears in-memory rate buckets. */
28
+ export declare function resetContactRateLimits(): void;
29
+ export declare function validateContactFields(input: {
30
+ email?: unknown;
31
+ message?: unknown;
32
+ maxMessageChars: number;
33
+ }): {
34
+ ok: true;
35
+ email: string;
36
+ message: string;
37
+ } | {
38
+ ok: false;
39
+ error: string;
40
+ };
41
+ export declare function verifyRecaptchaV3(opts: {
42
+ secret: string;
43
+ token: string;
44
+ minScore: number;
45
+ expectedAction: string;
46
+ remoteIp?: string | null;
47
+ }): Promise<{
48
+ ok: true;
49
+ score: number;
50
+ } | {
51
+ ok: false;
52
+ error: string;
53
+ }>;
54
+ export declare function clientIpFromRequest(request: Request): string | null;
55
+ /**
56
+ * Runs contact anti-spam gates. On honeypot hit returns softOk so the proxy
57
+ * can fake a success without calling the AI webhook.
58
+ */
59
+ export declare function enforceContactAntiSpam(opts: {
60
+ body: Record<string, unknown>;
61
+ request: Request;
62
+ visitorId: string;
63
+ ticketSecret: string;
64
+ config: ContactAntiSpamConfig;
65
+ }): Promise<{
66
+ ok: true;
67
+ } | {
68
+ ok: false;
69
+ softOk: true;
70
+ fakeText: string;
71
+ } | {
72
+ ok: false;
73
+ status: number;
74
+ error: string;
75
+ retryAfterSec?: number;
76
+ }>;