@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/.env.example +16 -0
- package/README.md +122 -102
- package/dist/client/createUranoChat.js +4 -7
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.js +4 -10
- package/dist/client/recaptcha.d.ts +14 -0
- package/dist/client/recaptcha.js +36 -0
- package/dist/client/sse.js +2 -6
- package/dist/client/submitContactForm.d.ts +33 -2
- package/dist/client/submitContactForm.js +74 -6
- package/dist/crypto.js +6 -46
- package/dist/flavors/UranoSocialChatElement.js +4 -9
- package/dist/flavors/UranoSocialContactFormElement.js +17 -12
- package/dist/index.d.ts +5 -3
- package/dist/index.js +10 -26
- package/dist/react/UranoSocialChat.js +25 -62
- package/dist/react/UranoSocialContactForm.d.ts +4 -0
- package/dist/react/UranoSocialContactForm.js +46 -16
- package/dist/react/index.js +2 -8
- package/dist/server/antiSpam.d.ts +76 -0
- package/dist/server/antiSpam.js +204 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +4 -13
- package/dist/server/proxy.js +58 -12
- package/dist/server/visitorCookie.js +8 -13
- package/dist/server/websocket.js +8 -12
- package/dist/types.d.ts +30 -0
- package/dist/types.js +3 -2
- package/package.json +2 -1
package/dist/crypto.js
CHANGED
|
@@ -1,48 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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 =
|
|
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, '&')
|
|
@@ -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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
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
|
-
|
|
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, '&')
|
|
@@ -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
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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] =
|
|
44
|
-
const [messages, setMessages] =
|
|
45
|
-
const [streaming, setStreaming] =
|
|
46
|
-
const [error, setError] =
|
|
47
|
-
const [draft, setDraft] =
|
|
48
|
-
const chatRef =
|
|
49
|
-
|
|
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
|
-
|
|
54
|
-
const chat =
|
|
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
|
-
: ((
|
|
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
|
-
: ((
|
|
87
|
-
? (props.renderEmpty?.() || (
|
|
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
|
-
? (
|
|
90
|
-
: ((
|
|
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
|
-
: ((
|
|
61
|
+
: (_jsxs("form", { className: "urano-social-composer", onSubmit: (e) => {
|
|
99
62
|
e.preventDefault();
|
|
100
63
|
void onSubmit();
|
|
101
|
-
}, children: [(
|
|
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
|
-
: (
|
|
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 ((
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const [values, setValues] =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
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
|
-
|
|
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({
|
|
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 ((
|
|
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
|
-
: ((
|
|
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
|
}
|
package/dist/react/index.js
CHANGED
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
+
}>;
|