@uranotools/urano-social 0.0.0-stage → 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.
Files changed (41) hide show
  1. package/.env.example +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +212 -2
  4. package/dist/client/createUranoChat.d.ts +16 -0
  5. package/dist/client/createUranoChat.js +223 -0
  6. package/dist/client/index.d.ts +6 -0
  7. package/dist/client/index.js +4 -0
  8. package/dist/client/recaptcha.d.ts +14 -0
  9. package/dist/client/recaptcha.js +36 -0
  10. package/dist/client/sse.d.ts +3 -0
  11. package/dist/client/sse.js +104 -0
  12. package/dist/client/submitContactForm.d.ts +61 -0
  13. package/dist/client/submitContactForm.js +133 -0
  14. package/dist/crypto.d.ts +5 -0
  15. package/dist/crypto.js +35 -0
  16. package/dist/flavors/UranoSocialChatElement.d.ts +13 -0
  17. package/dist/flavors/UranoSocialChatElement.js +173 -0
  18. package/dist/flavors/UranoSocialContactFormElement.d.ts +9 -0
  19. package/dist/flavors/UranoSocialContactFormElement.js +103 -0
  20. package/dist/flavors/styles.css +257 -0
  21. package/dist/index.d.ts +14 -0
  22. package/dist/index.js +10 -0
  23. package/dist/react/UranoSocialChat.d.ts +38 -0
  24. package/dist/react/UranoSocialChat.js +81 -0
  25. package/dist/react/UranoSocialContactForm.d.ts +19 -0
  26. package/dist/react/UranoSocialContactForm.js +78 -0
  27. package/dist/react/index.d.ts +4 -0
  28. package/dist/react/index.js +2 -0
  29. package/dist/server/antiSpam.d.ts +76 -0
  30. package/dist/server/antiSpam.js +204 -0
  31. package/dist/server/index.d.ts +4 -0
  32. package/dist/server/index.js +4 -0
  33. package/dist/server/proxy.d.ts +16 -0
  34. package/dist/server/proxy.js +253 -0
  35. package/dist/server/visitorCookie.d.ts +11 -0
  36. package/dist/server/visitorCookie.js +38 -0
  37. package/dist/server/websocket.d.ts +21 -0
  38. package/dist/server/websocket.js +126 -0
  39. package/dist/types.d.ts +94 -0
  40. package/dist/types.js +3 -0
  41. package/package.json +92 -4
@@ -0,0 +1,126 @@
1
+ import { signHmacHex } from '../crypto';
2
+ import { resolveVisitorId, DEFAULT_COOKIE_NAME } from './visitorCookie';
3
+ /**
4
+ * Attaches an optional WebSocket bridge to a Node HTTP server.
5
+ * Next App Router cannot upgrade sockets in a route handler — use SSE there.
6
+ * Astro Node / custom servers can call this after `http.createServer`.
7
+ */
8
+ export function attachSocialWebSocket(server, partial) {
9
+ const webhookUrl = (partial?.webhookUrl || process.env.URANO_SOCIAL_WEBHOOK_URL || '').replace(/\/$/, '');
10
+ const webhookSecret = partial?.webhookSecret || process.env.URANO_SOCIAL_WEBHOOK_SECRET || '';
11
+ const cookieSecret = partial?.cookieSecret || process.env.URANO_SOCIAL_COOKIE_SECRET || webhookSecret;
12
+ const cookieName = partial?.cookieName || DEFAULT_COOKIE_NAME;
13
+ const path = partial?.path || '/api/urano-social/ws';
14
+ if (!webhookUrl || !webhookSecret) {
15
+ throw new Error('attachSocialWebSocket requires URANO_SOCIAL_WEBHOOK_URL and URANO_SOCIAL_WEBHOOK_SECRET');
16
+ }
17
+ const upstreamWs = webhookUrl.replace(/^http/, 'ws') + '/ws';
18
+ const onUpgrade = (req, socket, head) => {
19
+ const url = new URL(req.url || '/', 'http://localhost');
20
+ if (url.pathname !== path)
21
+ return;
22
+ const visitor = resolveVisitorId({
23
+ cookieHeader: req.headers.cookie,
24
+ cookieName,
25
+ cookieSecret,
26
+ });
27
+ // Minimal RFC6455 accept handshake, then bridge frames as text JSON.
28
+ // Prefer the host runtime's `ws` package when available; fall back to rejecting.
29
+ let WebSocketImpl;
30
+ try {
31
+ WebSocketImpl = require('ws');
32
+ }
33
+ catch {
34
+ socket.write('HTTP/1.1 501 Not Implemented\r\nConnection: close\r\n\r\n');
35
+ socket.destroy();
36
+ return;
37
+ }
38
+ const wss = new WebSocketImpl.Server({ noServer: true });
39
+ wss.handleUpgrade(req, socket, head, (client) => {
40
+ if (visitor.setCookie) {
41
+ // Cookie already set via prior HTTP; continue.
42
+ }
43
+ const upstream = new WebSocketImpl(upstreamWs);
44
+ let sessionId;
45
+ upstream.on('open', () => {
46
+ client.send(JSON.stringify({ type: 'proxy_ready', visitorId: visitor.visitorId }));
47
+ });
48
+ client.on('message', (data) => {
49
+ let msg;
50
+ try {
51
+ msg = JSON.parse(String(data));
52
+ }
53
+ catch {
54
+ client.send(JSON.stringify({ type: 'error', error: 'invalid_json' }));
55
+ return;
56
+ }
57
+ const payloadObj = {
58
+ visitorId: visitor.visitorId,
59
+ from: visitor.visitorId,
60
+ text: typeof msg.text === 'string' ? msg.text : undefined,
61
+ sessionId: typeof msg.sessionId === 'string' ? msg.sessionId : sessionId,
62
+ displayName: typeof msg.displayName === 'string' ? msg.displayName : undefined,
63
+ webhookAction: msg.webhookAction,
64
+ turnId: msg.turnId,
65
+ };
66
+ const payloadStr = JSON.stringify(payloadObj);
67
+ const signature = signHmacHex(payloadStr, webhookSecret);
68
+ upstream.send(JSON.stringify({
69
+ payload: payloadStr,
70
+ headers: {
71
+ 'x-urano-signature': signature,
72
+ 'Content-Type': 'application/json',
73
+ 'X-Urano-Protocol-Version': '1',
74
+ ...(msg.idempotencyKey ? { 'Idempotency-Key': msg.idempotencyKey } : {}),
75
+ },
76
+ }));
77
+ });
78
+ upstream.on('message', (data) => {
79
+ try {
80
+ const evt = JSON.parse(String(data));
81
+ if (evt?.sessionId)
82
+ sessionId = evt.sessionId;
83
+ if (evt?.type === 'session' && evt.sessionId)
84
+ sessionId = evt.sessionId;
85
+ }
86
+ catch { /* forward raw */ }
87
+ if (client.readyState === 1)
88
+ client.send(String(data));
89
+ });
90
+ const closeBoth = () => {
91
+ try {
92
+ client.close();
93
+ }
94
+ catch { /* ignore */ }
95
+ try {
96
+ upstream.close();
97
+ }
98
+ catch { /* ignore */ }
99
+ };
100
+ client.on('close', closeBoth);
101
+ upstream.on('close', closeBoth);
102
+ client.on('error', closeBoth);
103
+ upstream.on('error', (err) => {
104
+ try {
105
+ client.send(JSON.stringify({ type: 'error', error: err.message }));
106
+ }
107
+ catch { /* ignore */ }
108
+ closeBoth();
109
+ });
110
+ });
111
+ };
112
+ server.on('upgrade', onUpgrade);
113
+ return () => server.off('upgrade', onUpgrade);
114
+ }
115
+ /** Exported for tests — builds the WS envelope the upstream expects. */
116
+ export function buildWsEnvelope(payload, secret) {
117
+ const payloadStr = JSON.stringify(payload);
118
+ return {
119
+ payload: payloadStr,
120
+ headers: {
121
+ 'x-urano-signature': signHmacHex(payloadStr, secret),
122
+ 'Content-Type': 'application/json',
123
+ 'X-Urano-Protocol-Version': '1',
124
+ },
125
+ };
126
+ }
@@ -0,0 +1,94 @@
1
+ export type SocialTransport = 'sse' | 'wait' | 'ws' | 'async';
2
+ export type WebhookAction = 'status' | 'events' | 'cancel';
3
+ /** Shared honeypot field name (safe to import from browser bundles). */
4
+ export declare const DEFAULT_HONEYPOT_FIELD = "website";
5
+ export declare const DEFAULT_RECAPTCHA_ACTION = "contact";
6
+ export interface ContactAntiSpamConfig {
7
+ /** Master switch for contact gates. Default true. */
8
+ enabled?: boolean;
9
+ /** Hidden field name bots fill. Default `website`. */
10
+ honeypotField?: string;
11
+ /** Minimum ms between issuing a contact ticket and accepting submit. Default 2500. */
12
+ minSubmitAgeMs?: number;
13
+ /** Maximum ticket lifetime. Default 30 minutes. */
14
+ maxSubmitAgeMs?: number;
15
+ /** Max contact submits per visitor+IP per hour. Default 5. */
16
+ maxPerHour?: number;
17
+ /** Max message length. Default 4000. */
18
+ maxMessageChars?: number;
19
+ /** Google reCAPTCHA v3 secret (server-only). Env: URANO_SOCIAL_RECAPTCHA_SECRET */
20
+ recaptchaSecret?: string;
21
+ /** Minimum v3 score. Default 0.5. */
22
+ recaptchaMinScore?: number;
23
+ /** Expected grecaptcha action. Default `contact`. */
24
+ recaptchaAction?: string;
25
+ /**
26
+ * When a recaptcha secret is set, require a token (default true).
27
+ * Set false to verify only when the client sends a token.
28
+ */
29
+ requireRecaptcha?: boolean;
30
+ }
31
+ export interface SocialProxyConfig {
32
+ webhookUrl: string;
33
+ webhookSecret: string;
34
+ /** Secret used to sign the HttpOnly visitor cookie. Defaults to webhookSecret. */
35
+ cookieSecret?: string;
36
+ cookieName?: string;
37
+ cookieMaxAgeSec?: number;
38
+ /** Default display name forwarded as displayName when the client omits one. */
39
+ defaultDisplayName?: string;
40
+ /** Contact-form anti-spam (honeypot, ticket, rate limit, optional reCAPTCHA v3). */
41
+ antiSpam?: ContactAntiSpamConfig;
42
+ }
43
+ export interface SocialClientMessage {
44
+ id: string;
45
+ role: 'user' | 'assistant' | 'system';
46
+ content: string;
47
+ streaming?: boolean;
48
+ turnId?: string;
49
+ createdAt: number;
50
+ }
51
+ export interface SocialChatEvent {
52
+ type: string;
53
+ version?: number;
54
+ id?: string;
55
+ sequence?: number;
56
+ turnId?: string;
57
+ requestId?: string;
58
+ sessionId?: string;
59
+ chunk?: string;
60
+ response?: string;
61
+ agentId?: string;
62
+ toolsUsed?: string[];
63
+ error?: string;
64
+ status?: number;
65
+ [key: string]: unknown;
66
+ }
67
+ export interface SocialSendOptions {
68
+ text: string;
69
+ displayName?: string;
70
+ mode?: 'sse' | 'wait';
71
+ idempotencyKey?: string;
72
+ signal?: AbortSignal;
73
+ }
74
+ export interface SocialControlOptions {
75
+ action: WebhookAction;
76
+ turnId: string;
77
+ after?: string;
78
+ signal?: AbortSignal;
79
+ }
80
+ export interface CreateUranoChatOptions {
81
+ /** Site proxy endpoint, default `/api/urano-social`. */
82
+ endpoint?: string;
83
+ sessionId?: string | null;
84
+ displayName?: string;
85
+ preferredTransport?: 'auto' | 'sse' | 'wait' | 'ws';
86
+ onEvent?: (event: SocialChatEvent) => void;
87
+ onMessages?: (messages: SocialClientMessage[]) => void;
88
+ onError?: (error: Error) => void;
89
+ onStatus?: (status: {
90
+ connected: boolean;
91
+ streaming: boolean;
92
+ sessionId: string | null;
93
+ }) => void;
94
+ }
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ /** Shared honeypot field name (safe to import from browser bundles). */
2
+ export const DEFAULT_HONEYPOT_FIELD = 'website';
3
+ export const DEFAULT_RECAPTCHA_ACTION = 'contact';
package/package.json CHANGED
@@ -1,6 +1,94 @@
1
1
  {
2
2
  "name": "@uranotools/urano-social",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.0",
4
+ "description": "Customer-support chat widget for SalesAgentConnector MCP webhooks (SSE / WebSocket / wait)",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc && node scripts/copy-assets.cjs",
10
+ "prepare": "npm run build",
11
+ "prepublishOnly": "npm run build",
12
+ "test": "tsx --test test/*.test.ts",
13
+ "typecheck": "tsc --noEmit",
14
+ "publish:npm": "npm publish --access public"
15
+ },
16
+ "keywords": [
17
+ "urano",
18
+ "chat",
19
+ "customer-support",
20
+ "sse",
21
+ "websocket",
22
+ "salesagent",
23
+ "astro",
24
+ "nextjs"
25
+ ],
26
+ "author": "Urano Open Source",
27
+ "license": "MIT",
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "registry": "https://registry.npmjs.org/"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "LICENSE",
36
+ ".env.example"
37
+ ],
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ },
41
+ "exports": {
42
+ ".": {
43
+ "types": "./dist/index.d.ts",
44
+ "import": "./dist/index.js",
45
+ "require": "./dist/index.js",
46
+ "default": "./dist/index.js"
47
+ },
48
+ "./client": {
49
+ "types": "./dist/client/index.d.ts",
50
+ "import": "./dist/client/index.js",
51
+ "require": "./dist/client/index.js",
52
+ "default": "./dist/client/index.js"
53
+ },
54
+ "./server": {
55
+ "types": "./dist/server/index.d.ts",
56
+ "import": "./dist/server/index.js",
57
+ "require": "./dist/server/index.js",
58
+ "default": "./dist/server/index.js"
59
+ },
60
+ "./react": {
61
+ "types": "./dist/react/index.d.ts",
62
+ "import": "./dist/react/index.js",
63
+ "require": "./dist/react/index.js",
64
+ "default": "./dist/react/index.js"
65
+ },
66
+ "./flavors.css": "./dist/flavors/styles.css"
67
+ },
68
+ "peerDependencies": {
69
+ "react": ">=18.0.0"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "react": {
73
+ "optional": true
74
+ }
75
+ },
76
+ "optionalDependencies": {
77
+ "ws": "^8.18.0"
78
+ },
79
+ "devDependencies": {
80
+ "@types/node": "^20.11.0",
81
+ "@types/react": "^18.3.12",
82
+ "react": "^18.3.1",
83
+ "tsx": "^4.19.2",
84
+ "typescript": "^5.3.3"
85
+ },
86
+ "repository": {
87
+ "type": "git",
88
+ "url": "git+https://github.com/uranotools/urano-social.git"
89
+ },
90
+ "bugs": {
91
+ "url": "https://github.com/uranotools/urano-social/issues"
92
+ },
93
+ "homepage": "https://github.com/uranotools/urano-social#readme"
94
+ }