diff --git a/public/nip-49decrypt/connection-test.txt b/public/nip49-decrypt/connection-test.txt similarity index 100% rename from public/nip-49decrypt/connection-test.txt rename to public/nip49-decrypt/connection-test.txt diff --git a/public/nip-49decrypt/index.html b/public/nip49-decrypt/index.html similarity index 100% rename from public/nip-49decrypt/index.html rename to public/nip49-decrypt/index.html diff --git a/public/nostr-chat-widget-WIP.js b/public/nostr-chat-widget-WIP.js deleted file mode 100644 index d85cfd1..0000000 --- a/public/nostr-chat-widget-WIP.js +++ /dev/null @@ -1,738 +0,0 @@ -/** - * Nostr Chat Widget - Embeddable Version (Glassmorphism Design) - * - * EMBED IT WITH: - * - */ - -(function() { - 'use strict'; - - // Get configuration from script tag - const scriptTag = document.currentScript; - const csPubkey = scriptTag.getAttribute('data-nostr-pubkey') || 'PUBKEY_TO_RECEICE_MESSAGES'; - const brandName = scriptTag.getAttribute('data-brand-name') || 'Support Team Messaging'; - const primaryColor = scriptTag.getAttribute('data-color') || '#fdad01'; - const secondaryColor = scriptTag.getAttribute('data-color-secondary') || '#000000'; - - // Default relay configuration - const DEFAULT_RELAYS = [ - 'wss://relay.damus.io', - 'wss://relay.primal.net', - 'wss://nos.lol', - 'wss://relay.btcforplebs.com' - ]; - - // Add viewport meta tag for mobile optimization - let viewportMeta = document.querySelector('meta[name="viewport"]'); - if (!viewportMeta) { - viewportMeta = document.createElement('meta'); - viewportMeta.name = 'viewport'; - document.head.appendChild(viewportMeta); - } - viewportMeta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover'; - - // Inject custom styles with glassmorphism - const style = document.createElement('style'); - style.textContent = ` - .safe-area-bottom { - padding-bottom: max(env(safe-area-inset-bottom), 1rem); - } - #nostr-chat-widget-root > div { - pointer-events: auto !important; - z-index: 99999 !important; - } - .glass-morphism { - background: rgba(255, 255, 255, 0.08); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border: 1px solid rgba(255, 255, 255, 0.15); - } - .glass-morphism-light { - background: rgba(255, 255, 255, 0.03); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - } - .glass-input { - background: rgba(255, 255, 255, 0.2); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); - border: 1px solid rgba(255, 255, 255, 0.3); - } - .glass-input:focus { - background: rgba(255, 255, 255, 0.25); - border: 1px solid rgba(255, 255, 255, 0.4); - } - .glass-input::placeholder { - color: rgba(255, 255, 255, 0.6); - } - .mobile-input-container { - position: sticky; - bottom: 0; - left: 0; - right: 0; - background: transparent; - } - @media (max-width: 640px) { - html.chat-open { - overflow: hidden; - position: fixed; - width: 100%; - height: 100%; - } - body.chat-open { - overflow: hidden; - position: fixed; - width: 100%; - height: 100%; - } - #nostr-chat-widget-root .chat-window-mobile { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - bottom: 0 !important; - width: 100% !important; - height: 100vh !important; - height: 100dvh !important; - max-height: 100vh !important; - max-height: 100dvh !important; - border-radius: 0 !important; - display: flex !important; - flex-direction: column !important; - } - #nostr-chat-widget-root .chat-header-mobile { - flex-shrink: 0 !important; - } - #nostr-chat-widget-root .chat-messages-mobile { - flex: 1 1 0% !important; - overflow-y: auto !important; - overflow-x: hidden !important; - -webkit-overflow-scrolling: touch !important; - min-height: 0 !important; - overscroll-behavior: contain !important; - padding-bottom: 1rem !important; - } - #nostr-chat-widget-root .mobile-input-container { - position: sticky !important; - bottom: 0 !important; - flex-shrink: 0 !important; - z-index: 10 !important; - backdrop-filter: blur(20px) !important; - -webkit-backdrop-filter: blur(20px) !important; - padding-bottom: max(env(safe-area-inset-bottom), 1.5rem) !important; - } - } - `; - document.head.appendChild(style); - - // Create widget container - const widgetRoot = document.createElement('div'); - widgetRoot.id = 'nostr-chat-widget-root'; - document.body.appendChild(widgetRoot); - - // Import map for nostr-tools - const importMap = document.createElement('script'); - importMap.type = 'importmap'; - importMap.textContent = JSON.stringify({ - imports: { - 'nostr-tools': 'https://esm.sh/nostr-tools@1.17.0' - } - }); - document.head.appendChild(importMap); - - // Main widget script - const widgetScript = document.createElement('script'); - widgetScript.type = 'module'; - widgetScript.textContent = ` - import { - relayInit, - generatePrivateKey, - getPublicKey, - getEventHash, - signEvent, - nip19, - nip04 - } from 'nostr-tools'; - - const CONFIG = { - relays: ${JSON.stringify(DEFAULT_RELAYS)}, - csPubkey: '${csPubkey}', - brandName: '${brandName}', - primaryColor: '${primaryColor}', - secondaryColor: '${secondaryColor}' - }; - - let state = { - isOpen: false, - messages: [], - inputMessage: '', - myPrivKey: null, - myPubKey: null, - relays: [], - connected: false, - sessionId: null - }; - - function getSessionKey() { - const stored = localStorage.getItem('nostr_chat_session'); - if (stored) { - try { - const session = JSON.parse(stored); - if (Date.now() - session.created < 24 * 60 * 60 * 1000) { - return session.privKey; - } - } catch (e) {} - } - - const privKey = generatePrivateKey(); - localStorage.setItem('nostr_chat_session', JSON.stringify({ - privKey, - created: Date.now() - })); - return privKey; - } - - async function init() { - // Check for crypto.subtle availability (requires HTTPS) - if (!window.crypto || !window.crypto.subtle) { - state.connected = false; - addMessage('system', '⚠️ Secure connection required. Please use HTTPS.'); - console.error('crypto.subtle not available. Page must be served over HTTPS.'); - render(); - return; - } - - state.myPrivKey = getSessionKey(); - state.myPubKey = getPublicKey(state.myPrivKey); - state.sessionId = state.myPubKey.substring(0, 8); - - console.log('🔑 Session Identity:', nip19.npubEncode(state.myPubKey)); - console.log('📱 User Agent:', navigator.userAgent); - console.log('🌐 Connecting to relays...'); - - const relayPromises = CONFIG.relays.map(async (url) => { - try { - console.log(\`Attempting: \${url}\`); - const relay = relayInit(url); - - relay.on('connect', () => { - console.log(\`✓ Connected to \${url}\`); - checkConnection(); - }); - - relay.on('disconnect', () => { - console.log(\`✗ Disconnected from \${url}\`); - checkConnection(); - }); - - relay.on('error', (err) => { - console.error(\`❌ Relay error \${url}:\`, err); - }); - - await relay.connect(); - return relay; - } catch (error) { - console.error(\`Failed: \${url}:\`, error); - return null; - } - }); - - state.relays = (await Promise.all(relayPromises)).filter(r => r !== null); - - console.log(\`✓ Connected to \${state.relays.length}/\${CONFIG.relays.length} relays\`); - - if (state.relays.length === 0) { - addMessage('system', '⚠️ Failed to connect to any relays. Check console for details.'); - return; - } - - subscribeToReplies(); - loadPreviousMessages(); - - state.connected = true; - render(); - } - - function checkConnection() { - const connected = state.relays.some(r => r.status === 1); - state.connected = connected; - render(); - } - - function subscribeToReplies() { - const filters = [{ - kinds: [4], - '#p': [state.myPubKey], - authors: [CONFIG.csPubkey], - since: Math.floor(Date.now() / 1000) - 86400 - }]; - - console.log('🔔 Subscribing to replies...'); - - state.relays.forEach(relay => { - const sub = relay.sub(filters); - - sub.on('event', (event) => { - handleIncomingMessage(event); - }); - - sub.on('eose', () => { - console.log(\`✓ Subscribed: \${relay.url}\`); - }); - }); - } - - function loadPreviousMessages() { - const stored = localStorage.getItem(\`nostr_chat_messages_\${state.sessionId}\`); - if (stored) { - try { - const messages = JSON.parse(stored); - messages.forEach(msg => state.messages.push(msg)); - render(); - } catch (e) {} - } - } - - function saveMessages() { - localStorage.setItem(\`nostr_chat_messages_\${state.sessionId}\`, JSON.stringify(state.messages)); - } - - async function handleIncomingMessage(event) { - try { - if (state.messages.find(m => m.id === event.id)) { - return; - } - - console.log('📨 Received message'); - - const decryptedText = await nip04.decrypt( - state.myPrivKey, - event.pubkey, - event.content - ); - - const message = { - id: event.id, - text: decryptedText, - sender: 'cs', - timestamp: new Date(event.created_at * 1000).toISOString() - }; - - addMessage('cs', decryptedText, message); - - if (!document.hasFocus()) { - const originalTitle = document.title; - document.title = '💬 New message!'; - setTimeout(() => { - document.title = originalTitle; - }, 3000); - } - } catch (error) { - console.error('Error decrypting message:', error); - } - } - - async function sendMessage() { - if (!state.inputMessage.trim()) return; - if (!state.connected || state.relays.length === 0) { - addMessage('system', '⚠️ Not connected to relays. Please wait...'); - return; - } - - const messageText = state.inputMessage; - state.inputMessage = ''; - - // Show message immediately (optimistic UI) - const tempMessage = { - id: 'temp_' + Date.now(), - text: messageText, - sender: 'user', - timestamp: new Date().toISOString() - }; - state.messages.push(tempMessage); - render(); - scrollToBottom(); - - try { - console.log('🔐 Encrypting and sending...'); - - const encrypted = await nip04.encrypt( - state.myPrivKey, - CONFIG.csPubkey, - messageText - ); - - let event = { - kind: 4, - created_at: Math.floor(Date.now() / 1000), - tags: [['p', CONFIG.csPubkey]], - content: encrypted, - pubkey: state.myPubKey - }; - - event.id = getEventHash(event); - event.sig = signEvent(event, state.myPrivKey); - - let published = 0; - const publishPromises = state.relays.map(async (relay) => { - try { - await relay.publish(event); - published++; - console.log(\`✓ Published to \${relay.url}\`); - return true; - } catch (err) { - console.error(\`✗ Failed: \${relay.url}:\`, err); - return false; - } - }); - - await Promise.all(publishPromises); - - if (published === 0) { - // Remove the temp message - const msgIndex = state.messages.findIndex(m => m.id === tempMessage.id); - if (msgIndex !== -1) { - state.messages.splice(msgIndex, 1); - } - addMessage('system', '⚠️ Failed to send - no relay connections'); - return; - } - - console.log(\`✓ Published to \${published}/\${state.relays.length} relays\`); - - // Update temp message with real ID - const msgIndex = state.messages.findIndex(m => m.id === tempMessage.id); - if (msgIndex !== -1) { - state.messages[msgIndex].id = event.id; - } - saveMessages(); - - } catch (error) { - console.error('Error sending:', error); - // Remove the temp message on error - const msgIndex = state.messages.findIndex(m => m.id === tempMessage.id); - if (msgIndex !== -1) { - state.messages.splice(msgIndex, 1); - } - addMessage('system', '⚠️ Failed to send: ' + error.message); - render(); - } - } - - function addMessage(sender, text, fullMessage = null) { - const msg = fullMessage || { - id: Date.now().toString(), - text, - sender, - timestamp: new Date().toISOString() - }; - - state.messages.push(msg); - saveMessages(); - render(); - scrollToBottom(); - } - - function scrollToBottom() { - setTimeout(() => { - const container = document.getElementById('nostr-messages'); - if (container) { - // Smooth scroll on desktop, instant on mobile for better keyboard handling - if (window.innerWidth >= 640) { - container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); - } else { - container.scrollTop = container.scrollHeight; - } - } - }, 100); - } - - function escapeHtml(text) { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; - } - - function formatTime(timestamp) { - const date = new Date(timestamp); - return date.toLocaleTimeString('en-US', { - hour: 'numeric', - minute: '2-digit', - hour12: true - }); - } - - function render() { - const container = document.getElementById('nostr-chat-widget-root'); - - if (!container) return; - - if (!state.isOpen) { - container.innerHTML = \` -
h?l[c][f]=s+1:n.charAt(c-1)===i.charAt(f-1)?l[c][f]=l[c-1][f-1]:l[c][f]=Math.min(l[c-1][f-1]+1,Math.min(l[c][f-1]+1,l[c-1][f]+1)),l[c][f] Start a conversation{u();function Xg(r,e){var t=r.type,i=r.value,n,s;return e&&(s=e(r))!==void 0?s:t==="word"||t==="space"?i:t==="string"?(n=r.quote||"",n+i+(r.unclosed?"":n)):t==="comment"?"/*"+i+(r.unclosed?"":"*/"):t==="div"?(r.before||"")+i+(r.after||""):Array.isArray(r.nodes)?(n=Zg(r.nodes,e),t!=="function"?n:i+"("+(r.before||"")+n+(r.after||"")+(r.unclosed?"":")")):i}function Zg(r,e){var t,i;if(Array.isArray(r)){for(t="",i=r.length-1;~i;i-=1)t=Xg(r[i],e)+t;return t}return Xg(r,e)}Jg.exports=Zg});var ry=x((lq,ty)=>{u();var Cs="-".charCodeAt(0),_s="+".charCodeAt(0),Fl=".".charCodeAt(0),j2="e".charCodeAt(0),z2="E".charCodeAt(0);function U2(r){var e=r.charCodeAt(0),t;if(e===_s||e===Cs){if(t=r.charCodeAt(1),t>=48&&t<=57)return!0;var i=r.charCodeAt(2);return t===Fl&&i>=48&&i<=57}return e===Fl?(t=r.charCodeAt(1),t>=48&&t<=57):e>=48&&e<=57}ty.exports=function(r){var e=0,t=r.length,i,n,s;if(t===0||!U2(r))return!1;for(i=r.charCodeAt(e),(i===_s||i===Cs)&&e++;e{u();function Gy(r,e){var t=r.type,i=r.value,n,s;return e&&(s=e(r))!==void 0?s:t==="word"||t==="space"?i:t==="string"?(n=r.quote||"",n+i+(r.unclosed?"":n)):t==="comment"?"/*"+i+(r.unclosed?"":"*/"):t==="div"?(r.before||"")+i+(r.after||""):Array.isArray(r.nodes)?(n=Qy(r.nodes,e),t!=="function"?n:i+"("+(r.before||"")+n+(r.after||"")+(r.unclosed?"":")")):i}function Qy(r,e){var t,i;if(Array.isArray(r)){for(t="",i=r.length-1;~i;i-=1)t=Gy(r[i],e)+t;return t}return Gy(r,e)}Yy.exports=Qy});var Zy=x((o$,Xy)=>{u();var $s="-".charCodeAt(0),Ls="+".charCodeAt(0),su=".".charCodeAt(0),vO="e".charCodeAt(0),xO="E".charCodeAt(0);function kO(r){var e=r.charCodeAt(0),t;if(e===Ls||e===$s){if(t=r.charCodeAt(1),t>=48&&t<=57)return!0;var i=r.charCodeAt(2);return t===su&&i>=48&&i<=57}return e===su?(t=r.charCodeAt(1),t>=48&&t<=57):e>=48&&e<=57}Xy.exports=function(r){var e=0,t=r.length,i,n,s;if(t===0||!kO(r))return!1;for(i=r.charCodeAt(e),(i===Ls||i===$s)&&e++;e
-
${CONFIG.brandName}
- \${CONFIG.brandName}
- \${CONFIG.brandName}
+