nostr-components 0.2.7 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +115 -186
  2. package/dist/assets/{base-styles-BSEzBDsk.js → base-styles-Dmuzg8I4.js} +2 -2
  3. package/dist/assets/{base-styles-BSEzBDsk.js.map → base-styles-Dmuzg8I4.js.map} +1 -1
  4. package/dist/assets/{copy-delegation-B7y2q5Kn.js → copy-delegation-xzt-t_do.js} +2 -2
  5. package/dist/assets/{copy-delegation-B7y2q5Kn.js.map → copy-delegation-xzt-t_do.js.map} +1 -1
  6. package/dist/assets/dialog-component-Da1ZIYh9.js.map +1 -1
  7. package/dist/assets/{dialog-likers-BqDn2P_3.js → dialog-likers-B15m_NxI.js} +5 -5
  8. package/dist/assets/dialog-likers-B15m_NxI.js.map +1 -0
  9. package/dist/assets/index.esm-ByUtE_cm.js +2159 -0
  10. package/dist/assets/index.esm-ByUtE_cm.js.map +1 -0
  11. package/dist/assets/nip05-utils-BNBHUmkr.js.map +1 -1
  12. package/dist/assets/nostr-login-service-D2FmscPI.js +2 -0
  13. package/dist/assets/nostr-login-service-D2FmscPI.js.map +1 -0
  14. package/dist/assets/nostr-service-CA0Qx4nJ.js +266 -0
  15. package/dist/assets/nostr-service-CA0Qx4nJ.js.map +1 -0
  16. package/dist/assets/{nostr-user-component-Cbs97dlK.js → nostr-user-component-r-MUbTL6.js} +2 -2
  17. package/dist/assets/{nostr-user-component-Cbs97dlK.js.map → nostr-user-component-r-MUbTL6.js.map} +1 -1
  18. package/dist/assets/{pure-DPo-pzxM.js → pure-DOoUcNQv.js} +2 -2
  19. package/dist/assets/pure-DOoUcNQv.js.map +1 -0
  20. package/dist/assets/theme-BN1Bvweb.js.map +1 -1
  21. package/dist/assets/{user-resolver-CMmbtY9Y.js → user-resolver-ArI0680e.js} +2 -2
  22. package/dist/assets/{user-resolver-CMmbtY9Y.js.map → user-resolver-ArI0680e.js.map} +1 -1
  23. package/dist/assets/utils--bxLbhGF.js.map +1 -1
  24. package/dist/assets/zap-utils-QRxLBOst.js +2 -0
  25. package/dist/assets/zap-utils-QRxLBOst.js.map +1 -0
  26. package/dist/components/nostr-comment.es.js +26 -26
  27. package/dist/components/nostr-comment.es.js.map +1 -1
  28. package/dist/components/nostr-dm.es.js +2 -2
  29. package/dist/components/nostr-dm.es.js.map +1 -1
  30. package/dist/components/nostr-follow-button.es.js +6 -7
  31. package/dist/components/nostr-follow-button.es.js.map +1 -1
  32. package/dist/components/nostr-like.es.js +16 -16
  33. package/dist/components/nostr-like.es.js.map +1 -1
  34. package/dist/components/nostr-live-chat.es.js +3 -3
  35. package/dist/components/nostr-live-chat.es.js.map +1 -1
  36. package/dist/components/nostr-post.es.js +19 -19
  37. package/dist/components/nostr-post.es.js.map +1 -1
  38. package/dist/components/nostr-profile-badge.es.js +2 -2
  39. package/dist/components/nostr-profile-badge.es.js.map +1 -1
  40. package/dist/components/nostr-profile.es.js +5 -5
  41. package/dist/components/nostr-profile.es.js.map +1 -1
  42. package/dist/components/nostr-zap.es.js +24 -24
  43. package/dist/components/nostr-zap.es.js.map +1 -1
  44. package/dist/nostr-components.es.js +1 -1
  45. package/dist/nostr-components.es.js.map +1 -1
  46. package/dist/nostr-components.umd.js +2669 -301
  47. package/dist/nostr-components.umd.js.map +1 -1
  48. package/dist/src/common/constants.d.ts +1 -1
  49. package/dist/src/common/nostr-login-service.d.ts +26 -0
  50. package/dist/src/nostr-dm/nostr-dm.d.ts +1 -1
  51. package/dist/src/nostr-like/like-utils.d.ts +2 -6
  52. package/dist/src/nostr-live-chat/nostr-live-chat.d.ts +1 -1
  53. package/dist/src/nostr-zap/zap-utils.d.ts +4 -0
  54. package/package.json +5 -2
  55. package/dist/assets/dialog-likers-BqDn2P_3.js.map +0 -1
  56. package/dist/assets/nostr-service-CnaPxjc6.js +0 -78
  57. package/dist/assets/nostr-service-CnaPxjc6.js.map +0 -1
  58. package/dist/assets/pure-DPo-pzxM.js.map +0 -1
  59. package/dist/assets/zap-utils-KFUD_vTU.js +0 -2
  60. package/dist/assets/zap-utils-KFUD_vTU.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["formatTimestamp","ts","formatRelativeTime","now","messageTime","diffMs","diffSec","mins","hours","days","months","years","safeSenderClass","sender","sanitizeHtml","input","renderLiveChatInner","theme","recipientNpub","recipientName","recipientPicture","message","messages","isLoading","isFinding","isError","errorMessage","currentUserName","currentUserPicture","showWelcome","welcomeText","startChatText","maxMessageLength","maxLen","typed","remaining","counterClass","getNostrLogo","msg","getLoadingNostrich","getLiveChatStyles","_NostrLiveChat","__publicField","NostrService","userRelays","i","arr","DEFAULT_RELAYS","attr","value","recipientPub","nip19","e","errorMsg","nip05Attr","ndk","pubkey","nostr","isNos2x","resolve","nos2xResult","nos2xError","enableError","getKeyError","error","privateKey","NDKPrivateKeySigner","__vitePreload","sk","user","_a","_b","_c","_d","_e","directPubkey","_f","_g","_h","_i","profileError","_j","directError","fallbackError","allowed","lower","normalized","welcomeAttr","startAttr","onlineAttr","helpAttr","name","_oldValue","newValue","relays","npub","type","data","nip05","recipientNip05","resolveNip05","tempId","signer","NDKNip07Signer","err","event","NDKEvent","NDKKind","encryptionSucceeded","sentMessage","m","textarea","counterEl","currentUser","historyDaysAttr","since","historyDays","baseFilter1","baseFilter2","filter1","filter2","isSender","peer","t","existingMessageById","decryptedText","optimisticMessage","a","b","launcher","closeBtn","findButton","sendButton","startBtn","npubInput","supplier","s","renderOptions","styles","onlineText","helpText","innerOptions","inner","html","chatHistory","NostrLiveChat"],"ignoreList":[],"sources":["../../src/nostr-live-chat/render.ts","../../src/nostr-live-chat/nostr-live-chat.ts"],"sourcesContent":["import { Theme } from \"../common/types\";\nimport { getLoadingNostrich, getNostrLogo } from \"../common/theme\";\n\nexport interface ChatMessage {\n id: string;\n text: string;\n sender: 'me' | 'them';\n timestamp: number;\n status: 'sending' | 'sent' | 'failed';\n}\n\nfunction formatTimestamp(ts: number): string {\n try {\n return new Date(ts * 1000).toLocaleString();\n } catch {\n return '';\n }\n}\n\n/**\n * Format timestamp as relative time (e.g., \"2 mins ago\", \"1 month ago\")\n * @param ts Timestamp in seconds\n * @returns Formatted relative time string\n */\nfunction formatRelativeTime(ts: number): string {\n try {\n const now = Date.now();\n const messageTime = ts * 1000;\n const diffMs = now - messageTime;\n\n // Convert to seconds\n const diffSec = Math.floor(diffMs / 1000);\n\n if (diffSec < 60) {\n return 'just now';\n }\n\n // Minutes\n if (diffSec < 3600) {\n const mins = Math.floor(diffSec / 60);\n return `${mins} ${mins === 1 ? 'min' : 'mins'} ago`;\n }\n\n // Hours\n if (diffSec < 86400) {\n const hours = Math.floor(diffSec / 3600);\n return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;\n }\n\n // Days\n if (diffSec < 2592000) { // ~30 days\n const days = Math.floor(diffSec / 86400);\n return `${days} ${days === 1 ? 'day' : 'days'} ago`;\n }\n\n // Months\n if (diffSec < 31536000) { // ~365 days\n const months = Math.floor(diffSec / 2592000);\n return `${months} ${months === 1 ? 'month' : 'months'} ago`;\n }\n\n // Years\n const years = Math.floor(diffSec / 31536000);\n return `${years} ${years === 1 ? 'year' : 'years'} ago`;\n } catch {\n return '';\n }\n}\n\n// Whitelist sender to avoid injecting untrusted values into class names\nfunction safeSenderClass(sender: string): 'me' | 'them' {\n if (sender === 'me' || sender === 'them') return sender;\n // Default to 'them' for unknown values to ensure safe styling\n return 'them';\n}\n\nexport interface RenderLiveChatOptions {\n theme: Theme;\n recipientNpub: string | null;\n recipientName: string | null;\n recipientPicture: string | null;\n message: string;\n messages: ChatMessage[];\n isLoading: boolean;\n isFinding: boolean;\n isError: boolean;\n errorMessage: string;\n currentUserName?: string | null;\n currentUserPicture?: string | null;\n showWelcome?: boolean;\n welcomeText?: string;\n startChatText?: string;\n onlineText?: string;\n helpText?: string;\n maxMessageLength: number;\n}\n\n/**\n * Sanitizes a string to prevent XSS attacks\n * @param input String to sanitize\n * @returns Sanitized string with HTML special characters escaped\n */\nfunction sanitizeHtml(input: string | null | undefined): string {\n if (input === null || input === undefined) {\n return '';\n }\n return String(input)\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;');\n}\n\nexport function renderLiveChat({\n theme,\n recipientNpub,\n recipientName,\n recipientPicture,\n message,\n messages,\n isLoading,\n isFinding,\n isError,\n errorMessage,\n currentUserName,\n currentUserPicture,\n showWelcome,\n welcomeText,\n startChatText,\n onlineText,\n helpText,\n maxMessageLength,\n}: RenderLiveChatOptions): string {\n // Build inner chat UI only\n const { onlineText: _, helpText: __, ...innerOptions } = {\n theme,\n recipientNpub,\n recipientName,\n recipientPicture,\n message,\n messages,\n isLoading,\n isFinding,\n isError,\n errorMessage,\n currentUserName,\n currentUserPicture,\n showWelcome,\n welcomeText,\n startChatText,\n onlineText,\n helpText,\n maxMessageLength,\n };\n const inner = renderLiveChatInner(innerOptions);\n\n // Include styles + inner UI (legacy behavior)\n return `\n ${getLiveChatStyles(theme)}\n ${inner}\n `;\n}\n\n// Returns only the chat UI (no <style>) for composing inside wrappers\nexport function renderLiveChatInner({\n theme,\n recipientNpub,\n recipientName,\n recipientPicture,\n message,\n messages,\n isLoading,\n isFinding,\n isError,\n errorMessage,\n currentUserName,\n currentUserPicture,\n showWelcome,\n welcomeText,\n startChatText,\n maxMessageLength,\n}: Omit<RenderLiveChatOptions, 'onlineText' | 'helpText'>): string {\n const iconSize = 24;\n const maxLen = typeof maxMessageLength === 'number' ? maxMessageLength : 1000;\n const typed = (message || '').length;\n const remaining = Math.max(0, maxLen - typed);\n // Use warn class when remaining characters are <= 10\n const counterClass = remaining <= 10 ? 'nostr-chat-char-counter warn' : 'nostr-chat-char-counter';\n\n return `\n <div class=\"nostr-chat-container ${isError ? \"nostr-chat-error\" : \"\"}\">\n <div class=\"nostr-chat-header\">\n <div class=\"nostr-chat-header-left\">\n ${recipientNpub && recipientName\n ? `\n <div class=\"nostr-chat-recipient\">\n <div class=\"nostr-chat-recipient-info\">\n <img \n src=\"${sanitizeHtml(recipientPicture) || ''}\" \n alt=\"${sanitizeHtml(recipientName) || ''}\" \n class=\"nostr-chat-recipient-avatar\"\n onerror=\"this.onerror=null; this.src='https://via.placeholder.com/40';\"\n >\n <span class=\"nostr-chat-recipient-name\">${sanitizeHtml(recipientName) || sanitizeHtml(recipientNpub)}</span>\n </div>\n </div>\n `\n : `\n <div class=\"nostr-chat-recipient-placeholder\">\n ${getNostrLogo(iconSize, iconSize)}\n <span>Nostr Live Chat</span>\n </div>\n `\n }\n </div>\n <div class=\"nostr-chat-header-right\">\n ${currentUserName\n ? `\n <div class=\"nostr-chat-self\">\n <img \n src=\"${sanitizeHtml(currentUserPicture) || ''}\"\n alt=\"${sanitizeHtml(currentUserName) || 'You'}\"\n class=\"nostr-chat-self-avatar\"\n onerror=\"this.onerror=null; this.src='https://via.placeholder.com/32';\"\n >\n <span class=\"nostr-chat-self-name\">${sanitizeHtml(currentUserName)}</span>\n </div>\n ` : ''\n }\n <button class=\"nostr-chat-close-btn\" title=\"Minimize\">×</button>\n </div>\n </div>\n\n <div class=\"nostr-chat-content\">\n ${!recipientNpub\n ? `\n <div class=\"nostr-chat-npub-input-container\">\n <input type=\"text\" class=\"nostr-chat-npub-input\" placeholder=\"Enter recipient's npub/nip05 address...\" />\n <button class=\"nostr-chat-find-btn\" ${isFinding ? \"disabled\" : \"\"}>\n ${isFinding\n ? `${getLoadingNostrich()} <span>Finding...</span>`\n : `<span>Find</span>`\n }\n </button>\n </div>\n `\n : showWelcome\n ? `\n <div class=\"nostr-chat-welcome\">\n <div class=\"nostr-chat-welcome-text\">${sanitizeHtml(welcomeText) || 'Welcome!'}</div>\n <button class=\"nostr-chat-start-btn\">${sanitizeHtml(startChatText) || 'Start chat'}</button>\n </div>\n `\n : `\n <div class=\"nostr-chat-history\">\n ${messages.map(msg => `\n <div class=\"nostr-chat-message-row nostr-chat-message-${safeSenderClass((msg as any).sender)} ${msg.sender === 'me' && msg.status === 'sending' ? 'sending' : ''} ${msg.sender === 'me' && msg.status === 'failed' ? 'failed' : ''}\">\n <div class=\"nostr-chat-message-bubble\">${sanitizeHtml(msg.text)}</div>\n <div class=\"nostr-chat-message-meta\">\n <span class=\"nostr-chat-message-timestamp\" title=\"${sanitizeHtml(formatTimestamp(msg.timestamp))}\">${sanitizeHtml(formatRelativeTime(msg.timestamp))}</span>\n </div>\n </div>\n `).join('')}\n </div>\n <div class=\"nostr-chat-actions\">\n <textarea \n class=\"nostr-chat-textarea\" \n placeholder=\"Type your message...\"\n maxlength=\"${maxLen}\"\n >${sanitizeHtml(message)}</textarea>\n <div class=\"${counterClass}\" aria-live=\"polite\">${remaining} chars left</div>\n <button class=\"nostr-chat-send-btn\" ${isLoading ? \"disabled\" : \"\"}>\n ${isLoading\n ? getLoadingNostrich()\n : `Send`\n }\n </button>\n </div>\n `\n }\n </div>\n\n ${isError ? `<small class=\"nostr-chat-error-message\">${sanitizeHtml(errorMessage)}</small>` : \"\"}\n </div>\n `;\n}\n\nexport function getLiveChatStyles(theme: Theme): string {\n return `\n <style>\n :host {\n --nstrc-chat-background-dark: #222222;\n --nstrc-chat-background-light: #FFFFFF;\n --nstrc-chat-text-color-dark: #FFFFFF;\n --nstrc-chat-text-color-light: #000000;\n --nstrc-chat-border-dark: 1px solid #444444;\n --nstrc-chat-border-light: 1px solid #DDDDDD;\n --nstrc-chat-input-background-dark: #333333;\n --nstrc-chat-input-background-light: #F9F9F9;\n --nstrc-chat-my-message-background-dark: #5A3E85;\n --nstrc-chat-my-message-background-light: #E6DDF4;\n /* Unified accent variables with light/dark variants */\n --nstrc-chat-accent-color-dark: #5A3E85;\n --nstrc-chat-accent-color-light: #7E4FD2;\n --nstrc-chat-accent-text-color-dark: #FFFFFF;\n --nstrc-chat-accent-text-color-light: #FFFFFF;\n --nstrc-chat-their-message-background-dark: #3A3A3A;\n --nstrc-chat-their-message-background-light: #F0F0F0;\n --nstrc-chat-button-background-dark: #5A3E85;\n --nstrc-chat-button-background-light: #7E4FD2;\n --nstrc-chat-button-text-dark: #FFFFFF;\n --nstrc-chat-button-text-light: #FFFFFF;\n --nstrc-chat-error-color: #FF4D4F;\n --nstrc-chat-border-radius: 12px;\n \n --nstrc-chat-background: var(--nstrc-chat-background-${theme});\n --nstrc-chat-text-color: var(--nstrc-chat-text-color-${theme});\n --nstrc-chat-border: var(--nstrc-chat-border-${theme});\n --nstrc-chat-input-background: var(--nstrc-chat-input-background-${theme});\n --nstrc-chat-my-message-background: var(--nstrc-chat-my-message-background-${theme});\n --nstrc-chat-their-message-background: var(--nstrc-chat-their-message-background-${theme});\n --nstrc-chat-button-background: var(--nstrc-chat-button-background-${theme});\n --nstrc-chat-button-text: var(--nstrc-chat-button-text-${theme});\n --nstrc-chat-accent-color: var(--nstrc-chat-accent-color-${theme});\n --nstrc-chat-accent-text-color: var(--nstrc-chat-accent-text-color-${theme});\n }\n\n /* Floating modes shrink the host so it doesn't affect page layout */\n :host([display-type=\"fab\"]),\n :host([display-type=\"bottom-bar\"]),\n :host([display-type=\"full\"]) {\n width: 0;\n height: 0;\n }\n\n /* Floating panel wrapper */\n .nostr-chat-float-panel {\n position: fixed;\n bottom: 20px;\n right: 20px;\n z-index: 2147483000;\n box-shadow: 0 8px 24px rgba(0,0,0,0.18);\n border-radius: var(--nstrc-chat-border-radius);\n display: none;\n }\n .nostr-chat-float-panel.open { display: block; }\n\n /* Close (minimize) button for floating panel */\n .nostr-chat-close-btn {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n font-size: 16px;\n cursor: pointer;\n background: rgba(0,0,0,0.1);\n color: var(--nstrc-chat-text-color);\n border: none;\n transition: background-color 0.2s ease;\n }\n .nostr-chat-close-btn:hover {\n background: rgba(0,0,0,0.2);\n }\n \n /* Hide close button in embed mode */\n :host([display-type=\"embed\"]) .nostr-chat-close-btn {\n display: none;\n }\n\n /* Launcher: FAB */\n .nostr-chat-launcher.fab {\n position: fixed;\n bottom: 20px;\n right: 20px;\n z-index: 2147483000;\n display: flex;\n align-items: center;\n gap: 10px;\n font-family: Inter, sans-serif;\n }\n .nostr-chat-launcher .bubble {\n background: var(--nstrc-chat-background);\n color: var(--nstrc-chat-text-color);\n border: var(--nstrc-chat-border);\n border-radius: 14px;\n padding: 10px 12px;\n box-shadow: 0 6px 20px rgba(0,0,0,0.12);\n }\n .nostr-chat-launcher .bubble .title { font-weight: 700; font-size: 14px; }\n .nostr-chat-launcher .bubble .subtitle { font-size: 12px; opacity: 0.8; }\n .nostr-chat-launcher .fab-btn {\n width: 64px;\n height: 64px;\n border-radius: 50%;\n background: var(--nstrc-chat-accent-color);\n color: var(--nstrc-chat-accent-text-color);\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n cursor: pointer;\n box-shadow: 0 6px 20px rgba(0,0,0,0.18);\n font-size: 24px; /* Larger emoji/icon */\n }\n\n /* Launcher: Bottom bar */\n .nostr-chat-launcher.bottom-bar {\n position: fixed;\n left: 50%;\n transform: translateX(-50%);\n bottom: 20px;\n z-index: 2147483000;\n }\n .nostr-chat-launcher .bar-btn {\n padding: 12px 20px;\n border-radius: 999px;\n background: var(--nstrc-chat-accent-color);\n color: var(--nstrc-chat-accent-text-color);\n border: none;\n cursor: pointer;\n font-weight: 700;\n box-shadow: 0 6px 20px rgba(0,0,0,0.18);\n }\n\n .nostr-chat-container {\n display: flex;\n flex-direction: column;\n font-family: Inter, sans-serif;\n width: 100%;\n height: 100%;\n max-width: 400px;\n max-height: 600px;\n box-sizing: border-box;\n border-radius: var(--nstrc-chat-border-radius);\n border: var(--nstrc-chat-border);\n background-color: var(--nstrc-chat-background);\n color: var(--nstrc-chat-text-color);\n overflow: hidden;\n }\n\n .nostr-chat-header {\n padding: 12px 16px;\n border-bottom: var(--nstrc-chat-border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-shrink: 0;\n }\n \n .nostr-chat-header-right {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .nostr-chat-recipient, .nostr-chat-recipient-placeholder {\n display: flex;\n align-items: center; /* Ensures vertical centering */\n gap: 12px;\n }\n\n .nostr-chat-recipient-avatar {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n object-fit: cover;\n }\n\n .nostr-chat-recipient-info {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .nostr-chat-recipient-name {\n font-weight: 600;\n font-size: 16px;\n display: flex;\n align-items: center;\n height: 36px; /* Match avatar height */\n }\n\n .nostr-chat-self {\n display: flex;\n align-items: center;\n gap: 8px;\n opacity: 0.85;\n font-size: 12px;\n }\n .nostr-chat-self-avatar {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n object-fit: cover;\n }\n\n .nostr-chat-content {\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n flex-grow: 1;\n overflow-y: auto;\n }\n \n /* Scrollbar styling for dark theme */\n .nostr-chat-content::-webkit-scrollbar,\n .nostr-chat-history::-webkit-scrollbar {\n width: 8px;\n }\n .nostr-chat-content::-webkit-scrollbar-track,\n .nostr-chat-history::-webkit-scrollbar-track {\n background: transparent;\n }\n .nostr-chat-content::-webkit-scrollbar-thumb,\n .nostr-chat-history::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.2);\n border-radius: 4px;\n }\n .nostr-chat-content::-webkit-scrollbar-thumb:hover,\n .nostr-chat-history::-webkit-scrollbar-thumb:hover {\n background: rgba(255, 255, 255, 0.3);\n }\n \n .nostr-chat-history {\n display: flex;\n flex-direction: column;\n gap: 10px;\n flex-grow: 1;\n overflow-y: auto;\n padding-right: 10px; /* For scrollbar */\n padding-bottom: 16px;\n }\n\n .nostr-chat-message-row {\n display: flex;\n width: 100%;\n flex-direction: column;\n align-items: flex-start;\n }\n\n .nostr-chat-message-me {\n justify-content: flex-end;\n align-items: flex-end;\n }\n\n .nostr-chat-message-them {\n justify-content: flex-start;\n align-items: flex-start;\n }\n\n .nostr-chat-message-bubble {\n padding: 8px 12px;\n border-radius: 18px;\n max-width: 75%;\n word-wrap: break-word;\n }\n\n .nostr-chat-message-me .nostr-chat-message-bubble {\n background-color: var(--nstrc-chat-accent-color);\n color: var(--nstrc-chat-accent-text-color);\n border-radius: 15px 15px 0 15px;\n opacity: 1;\n }\n\n .nostr-chat-message-me.sending .nostr-chat-message-bubble {\n opacity: 0.5;\n }\n\n .nostr-chat-message-me.failed .nostr-chat-message-bubble {\n opacity: 1;\n border: 1px dashed var(--nstrc-chat-error-color);\n }\n\n .nostr-chat-message-them .nostr-chat-message-bubble {\n background-color: var(--nstrc-chat-their-message-background);\n border-bottom-left-radius: 4px;\n }\n\n /* Message timestamp under bubble, always visible but with opacity change on hover */\n .nostr-chat-message-meta {\n font-size: 10px;\n margin-top: 4px;\n opacity: 0.5;\n color: var(--nstrc-chat-text-color);\n max-width: 75%;\n transition: opacity 0.2s ease;\n height: 14px; /* Fixed height to prevent layout jumps */\n }\n .nostr-chat-message-row:hover .nostr-chat-message-meta {\n opacity: 0.9;\n }\n .nostr-chat-message-me .nostr-chat-message-meta {\n text-align: right;\n }\n .nostr-chat-message-them .nostr-chat-message-meta {\n text-align: left;\n }\n\n /* Slightly highlight timestamp for the last message */\n .nostr-chat-history > .nostr-chat-message-row:last-of-type .nostr-chat-message-meta {\n opacity: 0.75;\n }\n\n .nostr-chat-npub-input-container {\n display: flex;\n gap: 8px;\n align-items: center;\n padding: 20px 0;\n }\n\n .nostr-chat-npub-input {\n flex: 1;\n padding: 10px;\n box-sizing: border-box;\n border-radius: 4px;\n border: var(--nstrc-chat-border);\n background-color: var(--nstrc-chat-input-background);\n color: var(--nstrc-chat-text-color);\n }\n\n .nostr-chat-actions {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n border-top: var(--nstrc-chat-border);\n padding-top: 12px;\n }\n\n .nostr-chat-welcome {\n display: flex;\n flex-direction: column;\n gap: 12px;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: 12px 8px;\n }\n .nostr-chat-welcome-text {\n opacity: 0.9;\n }\n .nostr-chat-start-btn {\n padding: 10px 16px;\n border-radius: 20px;\n border: none;\n background-color: var(--nstrc-chat-button-background);\n color: var(--nstrc-chat-button-text);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n }\n\n .nostr-chat-textarea {\n flex-grow: 1;\n height: 40px;\n box-sizing: border-box;\n padding: 10px;\n border-radius: 20px;\n border: var(--nstrc-chat-border);\n background-color: var(--nstrc-chat-input-background);\n color: var(--nstrc-chat-text-color);\n font-family: Inter, sans-serif;\n resize: none;\n overflow-y: auto;\n }\n\n /* Character counter */\n .nostr-chat-char-counter {\n align-self: center;\n font-size: 12px;\n opacity: 0.75;\n color: var(--nstrc-chat-text-color);\n white-space: nowrap;\n min-width: max-content;\n }\n .nostr-chat-char-counter.warn {\n color: #ff8c00; /* Brighter orange color for warning */\n opacity: 1;\n font-weight: 700;\n }\n\n .nostr-chat-find-btn, .nostr-chat-send-btn {\n padding: 10px 16px;\n border-radius: 20px;\n border: none;\n background-color: var(--nstrc-chat-button-background);\n color: var(--nstrc-chat-button-text);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n }\n\n .nostr-chat-error-message {\n color: var(--nstrc-chat-error-color);\n font-size: 14px;\n padding: 0 16px 16px;\n text-align: center;\n }\n </style>\n `;\n}\n","/**\n * <nostr-live-chat>\n * Attributes:\n * - npub (optional): pre-set npub of recipient\n * - pubkey (optional): hex recipient pubkey\n * - nip05 (optional): user@domain nip-05 identifier (alternative to npub)\n * - relays (optional): comma-separated relay URLs (defaults to common public set)\n * - theme (optional): \"light\" | \"dark\" (default \"light\")\n * - display-type (optional): \"fab\" | \"bottom-bar\" | \"full\" | \"embed\" (default \"embed\")\n * - welcome-text (optional): custom text for the welcome screen\n * - start-chat-text (optional): label for the Start button on the welcome screen\n * - history-days (optional): positive integer N to load last N days; \"all\" or <=0 or omitted -> full history\n *\n * Behaviour:\n * • If neither npub nor nip05 is supplied the component shows an input + Find button.\n * • Entering an npub then clicking Find performs profile lookup; during lookup the Find button shows\n * spinner/\"Finding…\". On success the UI switches to the chat view.\n * • If nip05 attribute is provided we first resolve it to a pubkey via nip05 well-known file then look\n * up the profile as usual.\n * • The component will subscribe to incoming DMs and display the chat history.\n */\nimport { NDKNip07Signer, NDKEvent, NDKKind, NDKFilter, NDKSubscription } from \"@nostr-dev-kit/ndk\";\nimport { DEFAULT_RELAYS } from \"../common/constants\";\nimport { Theme } from \"../common/types\";\nimport { getLiveChatStyles, renderLiveChatInner, RenderLiveChatOptions } from \"./render\";\nimport { nip19 } from \"nostr-tools\";\nimport { NostrService } from \"../common/nostr-service\";\nimport { resolveNip05 } from \"../common/nip05-utils\";\n\ntype DisplayType = 'fab' | 'bottom-bar' | 'full' | 'embed';\n\ninterface Message {\n id: string;\n text: string;\n sender: 'me' | 'them';\n timestamp: number;\n status: 'sending' | 'sent' | 'failed';\n}\n\n// Using shared resolveNip05 utility from common/nip05-utils\n\nexport default class NostrLiveChat extends HTMLElement {\n private rendered: boolean = false;\n private nostrService: NostrService = NostrService.getInstance();\n private dmSubscription: NDKSubscription | null = null;\n\n private theme: Theme = \"light\";\n private recipientNpub: string | null = null;\n private recipientNip05: string | null = null;\n private recipientName: string | null = null;\n private recipientPicture: string | null = null;\n private recipientPubkey: string | null = null;\n private message: string = \"\";\n private messages: Message[] = [];\n\n // Current user (signer) info for \"Logged in as\" UI\n private currentUserPubkey: string | null = null;\n private currentUserNpub: string | null = null;\n private currentUserName: string | null = null;\n private currentUserPicture: string | null = null;\n\n // Display controls\n private static readonly DEFAULT_WELCOME_TEXT = \"Welcome! How can we help you today?\";\n private static readonly DEFAULT_START_CHAT_TEXT = \"Start chat\";\n private static readonly DEFAULT_ONLINE_TEXT = \"We're Online!\";\n private static readonly DEFAULT_HELP_TEXT = \"How may I help you today?\";\n private displayType: DisplayType = 'embed';\n private isOpen: boolean = false; // For floating modes\n private showWelcome: boolean = false; // Show welcome screen before starting chat\n private welcomeText: string = NostrLiveChat.DEFAULT_WELCOME_TEXT;\n private startChatText: string = NostrLiveChat.DEFAULT_START_CHAT_TEXT;\n private onlineText: string = NostrLiveChat.DEFAULT_ONLINE_TEXT;\n private helpText: string = NostrLiveChat.DEFAULT_HELP_TEXT;\n private readonly MESSAGE_MAX_LENGTH = 1000;\n\n // isLoading -> sending DM, isFinding -> looking up recipient\n private isLoading: boolean = false;\n private isFinding: boolean = false;\n private isError: boolean = false;\n private errorMessage: string = \"\";\n\n\n // Key management options\n private keySupplier: (() => string | Promise<string>) | null = null;\n\n // Event handlers\n private boundHandleFind: (() => void) | null = null;\n private boundHandleSend: (() => void) | null = null;\n private boundHandleTextareaChange: ((e: Event) => void) | null = null;\n private boundHandleLauncherClick: (() => void) | null = null;\n private boundHandleCloseClick: (() => void) | null = null;\n private boundHandleStartChat: (() => void) | null = null;\n private boundHandleNpubKeydown: ((e: Event) => void) | null = null;\n private resubscribeTimer: number | null = null;\n\n constructor() {\n super();\n this.attachShadow({ mode: \"open\" });\n }\n\n getRelays = () => {\n const userRelays = this.getAttribute(\"relays\");\n if (userRelays) {\n return userRelays\n .split(\",\")\n .map(r => r.trim())\n .filter(r => r.length > 0)\n .filter((r, i, arr) => arr.indexOf(r) === i);\n }\n return DEFAULT_RELAYS;\n };\n\n private async getCurrentUserInfo(): Promise<void> {\n try {\n const ndk = this.nostrService.getNDK();\n let pubkey: string | null = null;\n\n if (typeof window !== 'undefined' && (window as any).nostr) {\n const nostr = (window as any).nostr;\n console.log('Nostr extension found, checking for public key...');\n\n try {\n // Special handling for nos2x extension\n // Check if this is nos2x by looking for specific patterns in the API\n const isNos2x = !!(nostr.enable && typeof nostr.enable === 'function' &&\n nostr.getPublicKey && typeof nostr.getPublicKey === 'function');\n\n console.log('Is this nos2x extension?', isNos2x);\n\n if (isNos2x) {\n console.log('Using nos2x specific flow');\n try {\n // First enable the extension\n await nostr.enable();\n console.log('nos2x extension enabled');\n\n // Use a timeout to ensure extension is ready\n await new Promise(resolve => setTimeout(resolve, 100));\n\n // Get public key from nos2x\n const nos2xResult = await nostr.getPublicKey();\n console.log('nos2x returned pubkey:', nos2xResult);\n if (nos2xResult && typeof nos2xResult === 'string' && nos2xResult.length === 64) {\n pubkey = nos2xResult;\n console.log('Successfully got pubkey from nos2x:', pubkey);\n }\n } catch (nos2xError) {\n console.error('Error with nos2x extension:', nos2xError);\n }\n } else {\n // Generic extension handling\n // First try to enable the extension if it supports it\n if (typeof nostr.enable === 'function') {\n console.log('Attempting to enable generic extension...');\n try {\n await nostr.enable();\n console.log('Extension enabled successfully');\n } catch (enableError) {\n console.warn('Extension enable failed, continuing anyway:', enableError);\n }\n }\n\n // Try to get public key\n if (typeof nostr.getPublicKey === 'function') {\n console.log('Calling getPublicKey()...');\n try {\n const result = await nostr.getPublicKey();\n pubkey = result;\n console.log('Got pubkey from extension:', pubkey);\n } catch (getKeyError) {\n console.error('Error getting public key:', getKeyError);\n }\n } else if (nostr.getPublicKey) {\n console.log('Found getPublicKey property, resolving...');\n try {\n pubkey = await Promise.resolve(nostr.getPublicKey);\n console.log('Got pubkey from extension (property):', pubkey);\n } catch (getKeyError) {\n console.error('Error resolving public key:', getKeyError);\n }\n }\n }\n } catch (error) {\n console.error('Error in extension interaction:', error);\n // Don't re-throw, we'll continue with null pubkey\n }\n }\n\n if (!pubkey) {\n let privateKey: string | null = null;\n\n // Try in-memory key supplier first\n if (this.keySupplier) {\n try {\n privateKey = await this.keySupplier();\n } catch {\n // ignore key supplier errors\n }\n }\n\n if (privateKey) {\n try {\n const { NDKPrivateKeySigner } = await import('@nostr-dev-kit/ndk');\n let sk = privateKey;\n if (privateKey.startsWith('nsec')) {\n const decoded = nip19.decode(privateKey);\n sk = decoded.data as string;\n }\n const signer = new NDKPrivateKeySigner(sk);\n const u = await signer.user();\n pubkey = u.pubkey;\n } catch {\n // ignore\n }\n }\n }\n\n // NOTE: We won't save any sensitive info in local/session storage. \n // Signing always happens in nos2x/alby, or bunkers extensions.\n\n if (pubkey) {\n this.currentUserPubkey = pubkey;\n this.currentUserNpub = nip19.npubEncode(pubkey);\n console.log('Got pubkey from extension and will use it:', pubkey);\n\n try {\n const user = ndk.getUser({ pubkey });\n console.log('Fetching profile for user:', pubkey);\n await user.fetchProfile();\n console.log('Fetched profile:', user.profile);\n\n this.currentUserName = user.profile?.displayName || user.profile?.name || this.currentUserNpub?.substring(0, 10) || null;\n this.currentUserPicture = user.profile?.image || null;\n\n if (!this.currentUserName) {\n console.warn('No username found in profile');\n }\n if (!this.currentUserPicture) {\n console.warn('No profile picture found');\n }\n\n console.log('Profile info set:', {\n name: this.currentUserName,\n picture: this.currentUserPicture,\n npub: this.currentUserNpub\n });\n } catch (error) {\n console.error('Error fetching profile:', error);\n // Fallback to just showing the npub if we can't fetch the profile\n this.currentUserName = this.currentUserNpub?.substring(0, 10) || null;\n }\n this.render();\n } else {\n console.log('Checking for direct nos2x logs in console...');\n\n // Try to detect the pubkey from window level objects that nos2x might set\n try {\n // Wait a bit for the extension to initialize\n await new Promise(resolve => setTimeout(resolve, 500));\n\n // Try again once more with direct extension call\n if (typeof window !== 'undefined' && (window as any).nostr) {\n console.log('Trying direct call to extension again...');\n try {\n const directPubkey = await (window as any).nostr.getPublicKey();\n if (directPubkey && typeof directPubkey === 'string') {\n console.log('Got pubkey on second try:', directPubkey);\n // Call ourselves recursively with the pubkey we found\n this.currentUserPubkey = directPubkey;\n this.currentUserNpub = nip19.npubEncode(directPubkey);\n\n // Get user profile\n try {\n const user = ndk.getUser({ pubkey: directPubkey });\n console.log('Fetching profile for user on second try:', directPubkey);\n await user.fetchProfile();\n console.log('Fetched profile on second try:', user.profile);\n\n this.currentUserName = user.profile?.displayName || user.profile?.name || this.currentUserNpub?.substring(0, 10) || null;\n this.currentUserPicture = user.profile?.image || null;\n\n this.render();\n return;\n } catch (profileError) {\n console.error('Error fetching profile on second try:', profileError);\n this.currentUserName = this.currentUserNpub?.substring(0, 10) || null;\n this.render();\n return;\n }\n }\n } catch (directError) {\n console.error('Error getting pubkey on direct second try:', directError);\n }\n }\n } catch (fallbackError) {\n console.error('Error in fallback pubkey detection:', fallbackError);\n }\n console.warn('No pubkey available from extension - trying fallback method');\n }\n } catch {\n // ignore global errors\n }\n }\n\n private getDisplayType() {\n const attr = this.getAttribute('display-type');\n const allowed: DisplayType[] = ['fab', 'bottom-bar', 'full', 'embed'];\n const raw = attr ? String(attr) : '';\n const lower = raw.toLowerCase();\n const normalized = allowed.includes(lower as DisplayType) ? lower as DisplayType : 'embed';\n this.displayType = normalized;\n // Initialize open state\n if (this.displayType === 'full') {\n this.isOpen = true;\n } else if (this.displayType === 'fab' || this.displayType === 'bottom-bar') {\n if (this.isOpen === undefined || this.isOpen === null) this.isOpen = false;\n } else {\n this.isOpen = false;\n }\n // Reflect attribute for :host([display-type=...]) CSS to apply\n const current = this.getAttribute('display-type');\n if (current !== normalized) {\n this.setAttribute('display-type', normalized);\n }\n }\n\n getTheme = () => {\n const attr = this.getAttribute(\"theme\");\n const value = (attr || \"\").toLowerCase();\n if (value === \"light\" || value === \"dark\") {\n this.theme = value as Theme;\n } else {\n if (attr) {\n console.warn(`Invalid theme '${attr}'. Accepted values are 'light', 'dark'. Falling back to 'light'.`);\n }\n this.theme = \"light\";\n }\n };\n\n getRecipient = () => {\n const recipientPub = this.getAttribute(\"pubkey\");\n if (recipientPub) {\n try {\n this.recipientPubkey = recipientPub;\n this.recipientNpub = nip19.npubEncode(recipientPub);\n this.lookupRecipient(this.recipientNpub);\n return;\n } catch (e) {\n const errorMsg = `Invalid recipient pubkey \"${recipientPub}\": ${e instanceof Error ? e.message : String(e)}`;\n this.isError = true;\n this.errorMessage = errorMsg;\n console.error('nostr-live-chat:', errorMsg, e);\n this.render();\n return; // Stop fallback flow\n }\n }\n const nip05Attr = this.getAttribute(\"nip05\");\n if (nip05Attr) {\n this.recipientNip05 = nip05Attr;\n this.lookupRecipientByNip05();\n return;\n }\n\n const recipientNpub = this.getAttribute(\"npub\");\n if (recipientNpub) {\n this.recipientNpub = recipientNpub;\n this.lookupRecipient();\n }\n };\n\n connectedCallback() {\n if (!this.rendered) {\n this.getTheme();\n this.getDisplayType();\n // Apply initial textual attributes before first render\n const welcomeAttr = this.getAttribute('welcome-text');\n if (welcomeAttr) this.welcomeText = welcomeAttr;\n const startAttr = this.getAttribute('start-chat-text');\n if (startAttr) this.startChatText = startAttr;\n const onlineAttr = this.getAttribute('online-text');\n if (onlineAttr) this.onlineText = onlineAttr;\n const helpAttr = this.getAttribute('help-text');\n if (helpAttr) this.helpText = helpAttr;\n this.getRecipient();\n this.nostrService.connectToNostr(this.getRelays());\n this.getCurrentUserInfo();\n this.render();\n this.rendered = true;\n }\n }\n\n static get observedAttributes() {\n return [\n \"npub\",\n \"pubkey\",\n \"nip05\",\n \"relays\",\n \"theme\",\n \"display-type\",\n \"welcome-text\",\n \"start-chat-text\",\n \"online-text\", // New: FAB online text\n \"help-text\", // New: FAB help text\n \"history-days\",\n ];\n }\n\n // Stop any active DM subscription and clear debounce timer\n private unsubscribeFromDms() {\n if (this.dmSubscription) {\n try { this.dmSubscription.stop(); } catch { }\n this.dmSubscription = null;\n }\n if (this.resubscribeTimer) {\n clearTimeout(this.resubscribeTimer);\n this.resubscribeTimer = null;\n }\n }\n\n // Clear all recipient-related and chat UI state\n private clearRecipientAndChatState() {\n this.recipientPubkey = null;\n this.recipientNpub = null;\n this.recipientNip05 = null;\n this.recipientName = null;\n this.recipientPicture = null;\n this.messages = [];\n this.message = \"\";\n this.showWelcome = false; // reset start chat flag\n this.isLoading = false;\n this.isFinding = false;\n this.isError = false;\n this.errorMessage = \"\";\n }\n\n attributeChangedCallback(\n name: string,\n _oldValue: string | null,\n newValue: string | null\n ) {\n if (!this.rendered) return;\n\n if (name === \"npub\") {\n if (newValue === null) {\n // Attribute removed: stop subscription and clear state\n this.unsubscribeFromDms();\n this.clearRecipientAndChatState();\n this.render();\n return;\n }\n if (newValue !== _oldValue) {\n // Changed: stop subscription, clear state, then lookup new recipient\n this.unsubscribeFromDms();\n this.clearRecipientAndChatState();\n this.recipientNpub = newValue!;\n this.lookupRecipient();\n }\n return;\n } else if (name === 'pubkey') {\n if (newValue === null) {\n this.unsubscribeFromDms();\n this.clearRecipientAndChatState();\n this.render();\n return;\n }\n if (newValue !== _oldValue) {\n this.unsubscribeFromDms();\n this.clearRecipientAndChatState();\n try {\n this.recipientPubkey = newValue!;\n this.recipientNpub = nip19.npubEncode(newValue!);\n this.lookupRecipient(this.recipientNpub);\n } catch {\n // ignore invalid value\n }\n }\n return;\n } else if (name === \"nip05\") {\n if (newValue === null) {\n this.unsubscribeFromDms();\n this.clearRecipientAndChatState();\n this.render();\n return;\n }\n if (newValue !== _oldValue) {\n this.unsubscribeFromDms();\n this.clearRecipientAndChatState();\n this.recipientNip05 = newValue!;\n this.lookupRecipientByNip05();\n }\n return;\n } else if (name === \"relays\") {\n if (newValue !== _oldValue) {\n const relays = this.getRelays();\n const chatActive = this.recipientPubkey && !this.showWelcome;\n if (chatActive) {\n if (this.resubscribeTimer) {\n clearTimeout(this.resubscribeTimer);\n }\n this.resubscribeTimer = window.setTimeout(() => {\n // Unsubscribe from current DMs, reconnect to new relays, then resubscribe\n this.unsubscribeFromDms();\n this.nostrService.connectToNostr(relays);\n this.subscribeToDms();\n }, 300);\n } else {\n this.nostrService.connectToNostr(relays);\n }\n }\n } else if (name === \"theme\") {\n this.getTheme();\n this.render();\n } else if (name === 'display-type') {\n this.getDisplayType();\n this.render();\n } else if (name === 'welcome-text') {\n // Reset to default when attribute is removed (newValue === null)\n this.welcomeText = newValue !== null ? newValue : NostrLiveChat.DEFAULT_WELCOME_TEXT;\n this.render();\n } else if (name === 'start-chat-text') {\n // Reset to default when attribute is removed (newValue === null)\n this.startChatText = newValue !== null ? newValue : NostrLiveChat.DEFAULT_START_CHAT_TEXT;\n this.render();\n } else if (name === 'online-text') {\n // Reset to default when attribute is removed (newValue === null)\n this.onlineText = newValue !== null ? newValue : NostrLiveChat.DEFAULT_ONLINE_TEXT;\n this.render();\n } else if (name === 'help-text') {\n // Reset to default when attribute is removed (newValue === null)\n this.helpText = newValue !== null ? newValue : NostrLiveChat.DEFAULT_HELP_TEXT;\n this.render();\n } else if (name === 'history-days') {\n // If history window changes while a chat is active, resubscribe to reload history (debounced)\n if (newValue !== _oldValue && this.recipientPubkey && !this.showWelcome) {\n if (this.resubscribeTimer) {\n clearTimeout(this.resubscribeTimer);\n }\n this.resubscribeTimer = window.setTimeout(() => {\n this.subscribeToDms();\n }, 250);\n }\n }\n }\n\n private async lookupRecipient(npub?: string) {\n // Stop any existing DM subscription and reset chat state before new lookup\n this.unsubscribeFromDms();\n this.messages = [];\n this.showWelcome = false;\n\n this.isFinding = true;\n this.render();\n\n try {\n const recipientNpub = npub || this.recipientNpub;\n if (!recipientNpub) return;\n\n const { type, data } = nip19.decode(recipientNpub);\n if (type !== \"npub\") throw new Error(\"Invalid npub\");\n this.recipientPubkey = data as string;\n\n const user = this.nostrService.getNDK().getUser({ pubkey: this.recipientPubkey });\n await user.fetchProfile();\n\n this.recipientName = user.profile?.displayName || user.profile?.name || recipientNpub.substring(0, 10);\n this.recipientPicture = user.profile?.image || null;\n // Prepare welcome screen; user starts chat to subscribe\n this.showWelcome = true;\n this.messages = [];\n // Clear any previous error state on successful lookup\n this.isError = false;\n this.errorMessage = \"\";\n this.render(); // Re-render to show profile info & welcome view\n\n } catch (e: any) {\n this.isError = true;\n this.errorMessage = e.message;\n } finally {\n this.isFinding = false;\n this.render();\n }\n }\n\n // Resolve nip05 -> pubkey -> npub then delegate to lookupRecipient\n private async lookupRecipientByNip05(nip05?: string) {\n this.isFinding = true;\n this.render();\n\n try {\n const recipientNip05 = nip05 || this.recipientNip05;\n if (!recipientNip05) return;\n\n const pubkey = await resolveNip05(recipientNip05);\n const npub = nip19.npubEncode(pubkey);\n this.recipientNpub = npub;\n // Clear any previous error state on successful nip05 resolution\n this.isError = false;\n this.errorMessage = \"\";\n await this.lookupRecipient(npub);\n\n } catch (e: any) {\n this.isError = true;\n this.errorMessage = e.message;\n } finally {\n this.isFinding = false;\n this.render();\n }\n }\n\n private handleFindClick() {\n const input = this.shadowRoot!.querySelector(\".nostr-chat-npub-input\") as HTMLInputElement;\n const value = input.value.trim();\n // Reset previous error state when starting a new find\n this.isError = false;\n this.errorMessage = \"\";\n this.render();\n if (!value) return;\n\n if (value.startsWith(\"npub\")) {\n this.recipientNpub = value;\n this.lookupRecipient();\n } else if (value.includes(\"@\")) {\n this.recipientNip05 = value;\n this.lookupRecipientByNip05();\n } else {\n this.isError = true;\n this.errorMessage = \"Invalid input. Please provide an npub or nip05 address.\";\n this.render();\n }\n }\n\n private async handleSendClick() {\n if (!this.message.trim() || !this.recipientPubkey) return;\n if (this.message.length > this.MESSAGE_MAX_LENGTH) {\n this.isError = true;\n this.errorMessage = `Message is too long (max ${this.MESSAGE_MAX_LENGTH} characters).`;\n this.render();\n return;\n }\n\n this.isLoading = true;\n this.render();\n\n let tempId: string | null = null;\n\n try {\n const ndk = this.nostrService.getNDK();\n let signer;\n\n // Enhanced check for NIP-07 extension - verify if fully available with required methods\n if (typeof window !== 'undefined' &&\n (window as any).nostr &&\n (window as any).nostr.getPublicKey &&\n (window as any).nostr.signEvent) {\n try {\n signer = new NDKNip07Signer();\n } catch (err) {\n console.error(\"Error creating NIP-07 signer:\", err);\n this.isError = true;\n this.errorMessage = `Error connecting to Nostr extension: ${(err as Error).message}`;\n this.isLoading = false;\n this.render();\n return;\n }\n }\n\n if (!signer) {\n throw new Error(\"No signer available. Please install a NIP-07 extension or set a private key.\");\n }\n ndk.signer = signer;\n\n const event = new NDKEvent(ndk, {\n kind: NDKKind.EncryptedDirectMessage,\n tags: [[\"p\", this.recipientPubkey!]],\n created_at: Math.floor(Date.now() / 1000),\n });\n\n let encryptionSucceeded = false;\n\n // Try NIP-07 encryption first if available\n if (typeof (window as any).nostr?.nip04?.encrypt === 'function') {\n try {\n event.content = await (window as any).nostr.nip04.encrypt(\n this.recipientPubkey!,\n this.message.trim()\n );\n encryptionSucceeded = true;\n } catch (e: any) {\n console.error(\"NIP-07 encryption failed, falling back to local encryption:\", e);\n }\n }\n\n // Fall back to local encryption if NIP-07 failed or unavailable\n if (!encryptionSucceeded) {\n throw new Error(\"No private key available for encryption. Please use a NIP-07 extension.\");\n }\n\n tempId = `temp_${Date.now()}`;\n this.messages.push({\n id: tempId,\n text: this.message,\n sender: 'me',\n timestamp: event.created_at!,\n status: 'sending'\n });\n this.message = \"\"; // Clear input\n this.render();\n\n await event.publish();\n\n // Check if the optimistic message still exists (real event might have already arrived)\n const sentMessage = this.messages.find(m => m.id === tempId);\n if (sentMessage) {\n // Check if a real message with this ID already exists (race condition)\n const realMessage = this.messages.find(m => m.id === event.id && m.id !== tempId);\n if (realMessage) {\n // Real event arrived first, remove the optimistic message\n this.messages = this.messages.filter(m => m.id !== tempId);\n } else {\n // Update optimistic message with real event id\n sentMessage.id = event.id;\n sentMessage.status = 'sent';\n }\n this.render();\n }\n\n } catch (e: any) {\n this.isError = true;\n this.errorMessage = e.message;\n // If publish failed, mark the optimistic message as failed\n if (tempId) {\n const msg = this.messages.find(m => m.id === tempId);\n if (msg) {\n msg.status = 'failed';\n }\n this.render();\n }\n } finally {\n this.isLoading = false;\n this.render();\n }\n }\n\n private handleTextareaChange(e: Event) {\n const textarea = e.target as HTMLTextAreaElement;\n this.message = textarea.value;\n\n // Update character counter\n const remaining = Math.max(0, this.MESSAGE_MAX_LENGTH - this.message.length);\n const counterEl = this.shadowRoot?.querySelector('.nostr-chat-char-counter');\n if (counterEl) {\n counterEl.textContent = `${remaining} chars left`;\n counterEl.classList.toggle('warn', remaining <= 10);\n }\n }\n\n private handleStartChat() {\n this.showWelcome = false;\n this.subscribeToDms();\n this.render();\n }\n\n private async subscribeToDms() {\n if (this.dmSubscription) {\n this.dmSubscription.stop();\n }\n if (!this.recipientPubkey) return;\n\n // Determine current user using available signer (extension or local key)\n let currentUser: { pubkey: string } | null = null;\n try {\n if (typeof window !== 'undefined' && (window as any).nostr && (window as any).nostr.getPublicKey) {\n const pubkey = await (window as any).nostr.getPublicKey();\n currentUser = { pubkey };\n } else {\n let privateKey: string | null = null;\n\n // Try in-memory key supplier first\n if (this.keySupplier) {\n try {\n privateKey = await this.keySupplier();\n } catch {\n // ignore key supplier errors\n }\n }\n\n\n\n if (privateKey) {\n const { NDKPrivateKeySigner } = await import('@nostr-dev-kit/ndk');\n let sk = privateKey;\n if (privateKey.startsWith('nsec')) {\n const decoded = nip19.decode(privateKey);\n sk = decoded.data as string;\n }\n const signer = new NDKPrivateKeySigner(sk);\n currentUser = await signer.user();\n }\n }\n } catch (err) {\n console.error('Failed to determine current user for subscription', err);\n }\n\n if (!currentUser) {\n this.isError = true;\n this.errorMessage = 'No signer available. Please install a NIP-07 extension or set a private key to start the chat.';\n this.render();\n return;\n }\n\n // Reset messages for new recipient\n this.messages = [];\n\n // Determine history window from attribute: default full history when unset; numeric days > 0 => limit; 'all' or <=0 => full\n const historyDaysAttr = this.getAttribute('history-days');\n let since: number | undefined;\n if (!historyDaysAttr) {\n since = undefined; // attribute not set -> full history\n } else if (historyDaysAttr.toLowerCase() === 'all' || parseInt(historyDaysAttr, 10) <= 0) {\n since = undefined; // omit since -> full history\n } else {\n const historyDays = Math.max(1, parseInt(historyDaysAttr, 10) || 30);\n since = Math.floor((Date.now() - historyDays * 24 * 60 * 60 * 1000) / 1000);\n }\n\n // Base filters\n const baseFilter1: NDKFilter = {\n kinds: [NDKKind.EncryptedDirectMessage],\n '#p': [currentUser.pubkey],\n authors: [this.recipientPubkey!],\n };\n const baseFilter2: NDKFilter = {\n kinds: [NDKKind.EncryptedDirectMessage],\n '#p': [this.recipientPubkey!],\n authors: [currentUser.pubkey],\n };\n\n // Conditionally include since\n const filter1: NDKFilter = since !== undefined ? { ...baseFilter1, since } : baseFilter1;\n const filter2: NDKFilter = since !== undefined ? { ...baseFilter2, since } : baseFilter2;\n\n this.dmSubscription = this.nostrService.getNDK().subscribe([filter1, filter2], {\n closeOnEose: false,\n groupable: false\n });\n\n this.dmSubscription.on('event', async (event: NDKEvent) => {\n try {\n // Update current user info if missing\n if (!this.currentUserPubkey && currentUser) {\n this.currentUserPubkey = currentUser.pubkey;\n this.currentUserNpub = nip19.npubEncode(currentUser.pubkey);\n }\n // Determine if we are the sender or receiver of the event\n const isSender = event.pubkey === currentUser.pubkey;\n const peer = isSender ? event.tags.find(t => t[0] === 'p')?.[1] : event.pubkey;\n\n // Guard: malformed event without a peer (missing 'p' tag or pubkey)\n if (!peer) {\n try { console.debug('nostr-live-chat: skipping event with missing peer', event.id); } catch { }\n return;\n }\n\n // Validate that this message is between the current user and the recipient\n if (peer !== this.recipientPubkey) {\n // not for me\n return;\n }\n\n // For messages sent by current user, check if we have an optimistic version to upgrade\n if (isSender) {\n // First check if we already have a message with this exact ID (real event arrived first)\n const existingMessageById = this.messages.find(m => m.id === event.id);\n if (existingMessageById) {\n // Real event already exists, just update status if needed\n if (existingMessageById.status === 'sending') {\n existingMessageById.status = 'sent';\n this.render();\n }\n return;\n }\n }\n\n let decryptedText = \"\";\n\n // Guard again before any decryption — never decrypt with a missing peer\n if (!peer) {\n try { console.debug('nostr-live-chat: missing peer prior to decrypt, skipping', event.id); } catch { }\n return;\n }\n\n // Guard against missing or empty content before decryption\n if (!event.content || event.content.trim() === '') {\n try { console.debug('nostr-live-chat: missing or empty content prior to decrypt, skipping', event.id); } catch { }\n return;\n }\n\n if ((window as any).nostr && (window as any).nostr.nip04 && typeof (window as any).nostr.nip04.decrypt === \"function\") {\n try {\n decryptedText = await (window as any).nostr.nip04.decrypt(\n peer,\n event.content\n );\n } catch (e: any) {\n console.error(\"Failed to decrypt DM content:\", e);\n return;\n }\n } else {\n throw new Error(\"No private key available for decryption. Please use a NIP-07 extension.\");\n }\n\n // For sent messages, check if we have an optimistic version to upgrade\n if (isSender) {\n // Look for an optimistic message with matching content and timestamp\n const optimisticMessage = this.messages.find(m =>\n m.status === 'sending' &&\n m.sender === 'me' &&\n m.text === decryptedText &&\n Math.abs(m.timestamp - event.created_at!) < 2\n );\n\n if (optimisticMessage) {\n // Upgrade the optimistic message with the real event data\n optimisticMessage.id = event.id;\n optimisticMessage.status = 'sent';\n this.render();\n return;\n }\n }\n\n const message: Message = {\n id: event.id,\n text: decryptedText,\n sender: isSender ? 'me' : 'them',\n timestamp: event.created_at!,\n status: 'sent'\n };\n\n // Check for duplicates by ID to handle edge cases\n const isDuplicate = this.messages.find(m => m.id === message.id);\n\n if (!isDuplicate) {\n this.messages.push(message);\n this.messages.sort((a, b) => a.timestamp - b.timestamp);\n this.render();\n }\n } catch (e: any) {\n console.error(\"Failed to decrypt DM content:\", e);\n }\n });\n }\n\n private attachEventListeners() {\n // Floating launchers\n const launcher = this.shadowRoot!.querySelector('.nostr-chat-launcher');\n if (launcher) {\n if (this.boundHandleLauncherClick) launcher.removeEventListener('click', this.boundHandleLauncherClick);\n this.boundHandleLauncherClick = () => { this.isOpen = true; this.render(); };\n launcher.addEventListener('click', this.boundHandleLauncherClick);\n }\n const closeBtn = this.shadowRoot!.querySelector('.nostr-chat-close-btn');\n if (closeBtn) {\n if (this.boundHandleCloseClick) closeBtn.removeEventListener('click', this.boundHandleCloseClick);\n this.boundHandleCloseClick = (e?: Event) => {\n e?.stopPropagation();\n if (this.displayType === 'fab' || this.displayType === 'bottom-bar') {\n this.isOpen = false;\n }\n this.render();\n };\n closeBtn.addEventListener('click', this.boundHandleCloseClick);\n }\n\n const findButton = this.shadowRoot!.querySelector(\".nostr-chat-find-btn\");\n if (findButton) {\n if (this.boundHandleFind) findButton.removeEventListener(\"click\", this.boundHandleFind);\n this.boundHandleFind = this.handleFindClick.bind(this);\n findButton.addEventListener(\"click\", this.boundHandleFind);\n }\n\n const sendButton = this.shadowRoot!.querySelector(\".nostr-chat-send-btn\");\n if (sendButton) {\n if (this.boundHandleSend) sendButton.removeEventListener(\"click\", this.boundHandleSend);\n this.boundHandleSend = this.handleSendClick.bind(this);\n sendButton.addEventListener(\"click\", this.boundHandleSend);\n }\n\n const startBtn = this.shadowRoot!.querySelector('.nostr-chat-start-btn');\n if (startBtn) {\n if (this.boundHandleStartChat) startBtn.removeEventListener('click', this.boundHandleStartChat);\n this.boundHandleStartChat = this.handleStartChat.bind(this);\n startBtn.addEventListener('click', this.boundHandleStartChat);\n }\n\n const textarea = this.shadowRoot!.querySelector(\".nostr-chat-textarea\");\n if (textarea) {\n if (this.boundHandleTextareaChange) textarea.removeEventListener(\"input\", this.boundHandleTextareaChange);\n this.boundHandleTextareaChange = this.handleTextareaChange.bind(this);\n textarea.addEventListener(\"input\", this.boundHandleTextareaChange);\n }\n\n const npubInput = this.shadowRoot!.querySelector(\".nostr-chat-npub-input\") as HTMLElement | null;\n if (npubInput) {\n if (this.boundHandleNpubKeydown) npubInput.removeEventListener(\"keydown\", this.boundHandleNpubKeydown);\n this.boundHandleNpubKeydown = (e: Event) => {\n const ke = e as KeyboardEvent;\n if (ke.key === \"Enter\") this.handleFindClick();\n };\n npubInput.addEventListener(\"keydown\", this.boundHandleNpubKeydown as EventListener);\n }\n }\n\n disconnectedCallback() {\n if (this.dmSubscription) {\n this.dmSubscription.stop();\n }\n // Clean up event listeners\n const launcher = this.shadowRoot?.querySelector('.nostr-chat-launcher');\n if (launcher && this.boundHandleLauncherClick) launcher.removeEventListener('click', this.boundHandleLauncherClick);\n const closeBtn = this.shadowRoot?.querySelector('.nostr-chat-close-btn');\n if (closeBtn && this.boundHandleCloseClick) closeBtn.removeEventListener('click', this.boundHandleCloseClick);\n const findButton = this.shadowRoot?.querySelector(\".nostr-chat-find-btn\");\n if (findButton && this.boundHandleFind) findButton.removeEventListener(\"click\", this.boundHandleFind);\n\n const sendButton = this.shadowRoot?.querySelector(\".nostr-chat-send-btn\");\n if (sendButton && this.boundHandleSend) sendButton.removeEventListener(\"click\", this.boundHandleSend);\n\n const startBtn = this.shadowRoot?.querySelector('.nostr-chat-start-btn');\n if (startBtn && this.boundHandleStartChat) startBtn.removeEventListener('click', this.boundHandleStartChat);\n\n const textarea = this.shadowRoot?.querySelector(\".nostr-chat-textarea\");\n if (textarea && this.boundHandleTextareaChange) textarea.removeEventListener(\"input\", this.boundHandleTextareaChange);\n const npubInput = this.shadowRoot?.querySelector('.nostr-chat-npub-input') as HTMLElement | null;\n if (npubInput && this.boundHandleNpubKeydown) npubInput.removeEventListener('keydown', this.boundHandleNpubKeydown as EventListener);\n if (this.resubscribeTimer) {\n clearTimeout(this.resubscribeTimer);\n this.resubscribeTimer = null;\n }\n }\n\n // Public methods for key management configuration\n public setKeySupplier(supplier: (() => string | Promise<string>) | null): void {\n this.keySupplier = supplier;\n }\n\n private escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n }\n\n private render() {\n const renderOptions: RenderLiveChatOptions = {\n theme: this.theme,\n recipientNpub: this.recipientNpub,\n recipientName: this.recipientName,\n recipientPicture: this.recipientPicture,\n message: this.message,\n messages: this.messages,\n isLoading: this.isLoading,\n isFinding: this.isFinding,\n isError: this.isError,\n errorMessage: this.errorMessage,\n currentUserName: this.currentUserName,\n currentUserPicture: this.currentUserPicture,\n showWelcome: this.showWelcome,\n welcomeText: this.welcomeText,\n startChatText: this.startChatText,\n onlineText: this.onlineText,\n helpText: this.helpText,\n maxMessageLength: this.MESSAGE_MAX_LENGTH,\n };\n\n const styles = getLiveChatStyles(this.theme);\n const { onlineText, helpText, ...innerOptions } = renderOptions;\n const inner = renderLiveChatInner(innerOptions);\n\n let html = styles;\n if (this.displayType === 'embed') {\n html += inner;\n } else if (this.displayType === 'full') {\n html += `\n <div class=\"nostr-chat-float-panel open\">${inner}</div>\n `;\n } else if (this.displayType === 'fab') {\n html += `\n ${this.isOpen ? '' : `\n <div class=\"nostr-chat-launcher fab\" role=\"button\" aria-label=\"Open live chat\">\n <div class=\"bubble\">\n <div class=\"title\">${this.escapeHtml(this.onlineText)}</div>\n <div class=\"subtitle\">${this.escapeHtml(this.helpText)}</div>\n </div>\n <button class=\"fab-btn\" aria-label=\"Open chat\">💬</button>\n </div>\n `}\n <div class=\"nostr-chat-float-panel ${this.isOpen ? 'open' : ''}\">\n ${inner}\n </div>\n `;\n } else if (this.displayType === 'bottom-bar') {\n html += `\n ${this.isOpen ? '' : `\n <div class=\"nostr-chat-launcher bottom-bar\" role=\"button\" aria-label=\"Open live chat\">\n <button class=\"bar-btn\">Live chat</button>\n </div>\n `}\n <div class=\"nostr-chat-float-panel ${this.isOpen ? 'open' : ''}\">\n ${inner}\n </div>\n `;\n }\n\n this.shadowRoot!.innerHTML = html;\n this.attachEventListeners();\n\n // Scroll to bottom after render\n setTimeout(() => {\n const chatHistory = this.shadowRoot?.querySelector('.nostr-chat-history');\n if (chatHistory) {\n (chatHistory as HTMLElement).scrollTop = (chatHistory as HTMLElement).scrollHeight;\n }\n }, 0);\n }\n}\n\ncustomElements.define(\"nostr-live-chat\", NostrLiveChat);\n"],"file":"components/nostr-live-chat.es.js"}
1
+ {"version":3,"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["formatTimestamp","ts","formatRelativeTime","now","messageTime","diffMs","diffSec","mins","hours","days","months","years","safeSenderClass","sender","sanitizeHtml","input","renderLiveChatInner","theme","recipientNpub","recipientName","recipientPicture","message","messages","isLoading","isFinding","isError","errorMessage","currentUserName","currentUserPicture","showWelcome","welcomeText","startChatText","maxMessageLength","maxLen","typed","remaining","counterClass","getNostrLogo","msg","getLoadingNostrich","getLiveChatStyles","_NostrLiveChat","__publicField","NostrService","userRelays","i","arr","DEFAULT_RELAYS","attr","value","recipientPub","nip19","e","errorMsg","nip05Attr","ndk","pubkey","nostr","isNos2x","resolve","nos2xResult","nos2xError","enableError","getKeyError","error","privateKey","NDKPrivateKeySigner","__vitePreload","n","sk","user","_a","_b","_c","_d","_e","directPubkey","_f","_g","_h","_i","profileError","_j","directError","fallbackError","allowed","lower","normalized","welcomeAttr","startAttr","onlineAttr","helpAttr","name","_oldValue","newValue","relays","npub","type","data","nip05","recipientNip05","resolveNip05","tempId","signer","NDKNip07Signer","err","event","NDKEvent","NDKKind","encryptionSucceeded","sentMessage","m","textarea","counterEl","currentUser","historyDaysAttr","since","historyDays","baseFilter1","baseFilter2","filter1","filter2","isSender","peer","t","existingMessageById","decryptedText","optimisticMessage","a","b","launcher","closeBtn","findButton","sendButton","startBtn","npubInput","supplier","s","renderOptions","styles","onlineText","helpText","innerOptions","inner","html","chatHistory","NostrLiveChat"],"ignoreList":[],"sources":["../../src/nostr-live-chat/render.ts","../../src/nostr-live-chat/nostr-live-chat.ts"],"sourcesContent":["import { Theme } from \"../common/types\";\nimport { getLoadingNostrich, getNostrLogo } from \"../common/theme\";\n\nexport interface ChatMessage {\n id: string;\n text: string;\n sender: 'me' | 'them';\n timestamp: number;\n status: 'sending' | 'sent' | 'failed';\n}\n\nfunction formatTimestamp(ts: number): string {\n try {\n return new Date(ts * 1000).toLocaleString();\n } catch {\n return '';\n }\n}\n\n/**\n * Format timestamp as relative time (e.g., \"2 mins ago\", \"1 month ago\")\n * @param ts Timestamp in seconds\n * @returns Formatted relative time string\n */\nfunction formatRelativeTime(ts: number): string {\n try {\n const now = Date.now();\n const messageTime = ts * 1000;\n const diffMs = now - messageTime;\n\n // Convert to seconds\n const diffSec = Math.floor(diffMs / 1000);\n\n if (diffSec < 60) {\n return 'just now';\n }\n\n // Minutes\n if (diffSec < 3600) {\n const mins = Math.floor(diffSec / 60);\n return `${mins} ${mins === 1 ? 'min' : 'mins'} ago`;\n }\n\n // Hours\n if (diffSec < 86400) {\n const hours = Math.floor(diffSec / 3600);\n return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;\n }\n\n // Days\n if (diffSec < 2592000) { // ~30 days\n const days = Math.floor(diffSec / 86400);\n return `${days} ${days === 1 ? 'day' : 'days'} ago`;\n }\n\n // Months\n if (diffSec < 31536000) { // ~365 days\n const months = Math.floor(diffSec / 2592000);\n return `${months} ${months === 1 ? 'month' : 'months'} ago`;\n }\n\n // Years\n const years = Math.floor(diffSec / 31536000);\n return `${years} ${years === 1 ? 'year' : 'years'} ago`;\n } catch {\n return '';\n }\n}\n\n// Whitelist sender to avoid injecting untrusted values into class names\nfunction safeSenderClass(sender: string): 'me' | 'them' {\n if (sender === 'me' || sender === 'them') return sender;\n // Default to 'them' for unknown values to ensure safe styling\n return 'them';\n}\n\nexport interface RenderLiveChatOptions {\n theme: Theme;\n recipientNpub: string | null;\n recipientName: string | null;\n recipientPicture: string | null;\n message: string;\n messages: ChatMessage[];\n isLoading: boolean;\n isFinding: boolean;\n isError: boolean;\n errorMessage: string;\n currentUserName?: string | null;\n currentUserPicture?: string | null;\n showWelcome?: boolean;\n welcomeText?: string;\n startChatText?: string;\n onlineText?: string;\n helpText?: string;\n maxMessageLength: number;\n}\n\n/**\n * Sanitizes a string to prevent XSS attacks\n * @param input String to sanitize\n * @returns Sanitized string with HTML special characters escaped\n */\nfunction sanitizeHtml(input: string | null | undefined): string {\n if (input === null || input === undefined) {\n return '';\n }\n return String(input)\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;');\n}\n\nexport function renderLiveChat({\n theme,\n recipientNpub,\n recipientName,\n recipientPicture,\n message,\n messages,\n isLoading,\n isFinding,\n isError,\n errorMessage,\n currentUserName,\n currentUserPicture,\n showWelcome,\n welcomeText,\n startChatText,\n onlineText,\n helpText,\n maxMessageLength,\n}: RenderLiveChatOptions): string {\n // Build inner chat UI only\n const { onlineText: _, helpText: __, ...innerOptions } = {\n theme,\n recipientNpub,\n recipientName,\n recipientPicture,\n message,\n messages,\n isLoading,\n isFinding,\n isError,\n errorMessage,\n currentUserName,\n currentUserPicture,\n showWelcome,\n welcomeText,\n startChatText,\n onlineText,\n helpText,\n maxMessageLength,\n };\n const inner = renderLiveChatInner(innerOptions);\n\n // Include styles + inner UI (legacy behavior)\n return `\n ${getLiveChatStyles(theme)}\n ${inner}\n `;\n}\n\n// Returns only the chat UI (no <style>) for composing inside wrappers\nexport function renderLiveChatInner({\n theme,\n recipientNpub,\n recipientName,\n recipientPicture,\n message,\n messages,\n isLoading,\n isFinding,\n isError,\n errorMessage,\n currentUserName,\n currentUserPicture,\n showWelcome,\n welcomeText,\n startChatText,\n maxMessageLength,\n}: Omit<RenderLiveChatOptions, 'onlineText' | 'helpText'>): string {\n const iconSize = 24;\n const maxLen = typeof maxMessageLength === 'number' ? maxMessageLength : 1000;\n const typed = (message || '').length;\n const remaining = Math.max(0, maxLen - typed);\n // Use warn class when remaining characters are <= 10\n const counterClass = remaining <= 10 ? 'nostr-chat-char-counter warn' : 'nostr-chat-char-counter';\n\n return `\n <div class=\"nostr-chat-container ${isError ? \"nostr-chat-error\" : \"\"}\">\n <div class=\"nostr-chat-header\">\n <div class=\"nostr-chat-header-left\">\n ${recipientNpub && recipientName\n ? `\n <div class=\"nostr-chat-recipient\">\n <div class=\"nostr-chat-recipient-info\">\n <img \n src=\"${sanitizeHtml(recipientPicture) || ''}\" \n alt=\"${sanitizeHtml(recipientName) || ''}\" \n class=\"nostr-chat-recipient-avatar\"\n onerror=\"this.onerror=null; this.src='https://via.placeholder.com/40';\"\n >\n <span class=\"nostr-chat-recipient-name\">${sanitizeHtml(recipientName) || sanitizeHtml(recipientNpub)}</span>\n </div>\n </div>\n `\n : `\n <div class=\"nostr-chat-recipient-placeholder\">\n ${getNostrLogo(iconSize, iconSize)}\n <span>Nostr Live Chat</span>\n </div>\n `\n }\n </div>\n <div class=\"nostr-chat-header-right\">\n ${currentUserName\n ? `\n <div class=\"nostr-chat-self\">\n <img \n src=\"${sanitizeHtml(currentUserPicture) || ''}\"\n alt=\"${sanitizeHtml(currentUserName) || 'You'}\"\n class=\"nostr-chat-self-avatar\"\n onerror=\"this.onerror=null; this.src='https://via.placeholder.com/32';\"\n >\n <span class=\"nostr-chat-self-name\">${sanitizeHtml(currentUserName)}</span>\n </div>\n ` : ''\n }\n <button class=\"nostr-chat-close-btn\" title=\"Minimize\">×</button>\n </div>\n </div>\n\n <div class=\"nostr-chat-content\">\n ${!recipientNpub\n ? `\n <div class=\"nostr-chat-npub-input-container\">\n <input type=\"text\" class=\"nostr-chat-npub-input\" placeholder=\"Enter recipient's npub/nip05 address...\" />\n <button class=\"nostr-chat-find-btn\" ${isFinding ? \"disabled\" : \"\"}>\n ${isFinding\n ? `${getLoadingNostrich()} <span>Finding...</span>`\n : `<span>Find</span>`\n }\n </button>\n </div>\n `\n : showWelcome\n ? `\n <div class=\"nostr-chat-welcome\">\n <div class=\"nostr-chat-welcome-text\">${sanitizeHtml(welcomeText) || 'Welcome!'}</div>\n <button class=\"nostr-chat-start-btn\">${sanitizeHtml(startChatText) || 'Start chat'}</button>\n </div>\n `\n : `\n <div class=\"nostr-chat-history\">\n ${messages.map(msg => `\n <div class=\"nostr-chat-message-row nostr-chat-message-${safeSenderClass((msg as any).sender)} ${msg.sender === 'me' && msg.status === 'sending' ? 'sending' : ''} ${msg.sender === 'me' && msg.status === 'failed' ? 'failed' : ''}\">\n <div class=\"nostr-chat-message-bubble\">${sanitizeHtml(msg.text)}</div>\n <div class=\"nostr-chat-message-meta\">\n <span class=\"nostr-chat-message-timestamp\" title=\"${sanitizeHtml(formatTimestamp(msg.timestamp))}\">${sanitizeHtml(formatRelativeTime(msg.timestamp))}</span>\n </div>\n </div>\n `).join('')}\n </div>\n <div class=\"nostr-chat-actions\">\n <textarea \n class=\"nostr-chat-textarea\" \n placeholder=\"Type your message...\"\n maxlength=\"${maxLen}\"\n >${sanitizeHtml(message)}</textarea>\n <div class=\"${counterClass}\" aria-live=\"polite\">${remaining} chars left</div>\n <button class=\"nostr-chat-send-btn\" ${isLoading ? \"disabled\" : \"\"}>\n ${isLoading\n ? getLoadingNostrich()\n : `Send`\n }\n </button>\n </div>\n `\n }\n </div>\n\n ${isError ? `<small class=\"nostr-chat-error-message\">${sanitizeHtml(errorMessage)}</small>` : \"\"}\n </div>\n `;\n}\n\nexport function getLiveChatStyles(theme: Theme): string {\n return `\n <style>\n :host {\n --nstrc-chat-background-dark: #222222;\n --nstrc-chat-background-light: #FFFFFF;\n --nstrc-chat-text-color-dark: #FFFFFF;\n --nstrc-chat-text-color-light: #000000;\n --nstrc-chat-border-dark: 1px solid #444444;\n --nstrc-chat-border-light: 1px solid #DDDDDD;\n --nstrc-chat-input-background-dark: #333333;\n --nstrc-chat-input-background-light: #F9F9F9;\n --nstrc-chat-my-message-background-dark: #5A3E85;\n --nstrc-chat-my-message-background-light: #E6DDF4;\n /* Unified accent variables with light/dark variants */\n --nstrc-chat-accent-color-dark: #5A3E85;\n --nstrc-chat-accent-color-light: #7E4FD2;\n --nstrc-chat-accent-text-color-dark: #FFFFFF;\n --nstrc-chat-accent-text-color-light: #FFFFFF;\n --nstrc-chat-their-message-background-dark: #3A3A3A;\n --nstrc-chat-their-message-background-light: #F0F0F0;\n --nstrc-chat-button-background-dark: #5A3E85;\n --nstrc-chat-button-background-light: #7E4FD2;\n --nstrc-chat-button-text-dark: #FFFFFF;\n --nstrc-chat-button-text-light: #FFFFFF;\n --nstrc-chat-error-color: #FF4D4F;\n --nstrc-chat-border-radius: 12px;\n \n --nstrc-chat-background: var(--nstrc-chat-background-${theme});\n --nstrc-chat-text-color: var(--nstrc-chat-text-color-${theme});\n --nstrc-chat-border: var(--nstrc-chat-border-${theme});\n --nstrc-chat-input-background: var(--nstrc-chat-input-background-${theme});\n --nstrc-chat-my-message-background: var(--nstrc-chat-my-message-background-${theme});\n --nstrc-chat-their-message-background: var(--nstrc-chat-their-message-background-${theme});\n --nstrc-chat-button-background: var(--nstrc-chat-button-background-${theme});\n --nstrc-chat-button-text: var(--nstrc-chat-button-text-${theme});\n --nstrc-chat-accent-color: var(--nstrc-chat-accent-color-${theme});\n --nstrc-chat-accent-text-color: var(--nstrc-chat-accent-text-color-${theme});\n }\n\n /* Floating modes shrink the host so it doesn't affect page layout */\n :host([display-type=\"fab\"]),\n :host([display-type=\"bottom-bar\"]),\n :host([display-type=\"full\"]) {\n width: 0;\n height: 0;\n }\n\n /* Floating panel wrapper */\n .nostr-chat-float-panel {\n position: fixed;\n bottom: 20px;\n right: 20px;\n z-index: 2147483000;\n box-shadow: 0 8px 24px rgba(0,0,0,0.18);\n border-radius: var(--nstrc-chat-border-radius);\n display: none;\n }\n .nostr-chat-float-panel.open { display: block; }\n\n /* Close (minimize) button for floating panel */\n .nostr-chat-close-btn {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n font-size: 16px;\n cursor: pointer;\n background: rgba(0,0,0,0.1);\n color: var(--nstrc-chat-text-color);\n border: none;\n transition: background-color 0.2s ease;\n }\n .nostr-chat-close-btn:hover {\n background: rgba(0,0,0,0.2);\n }\n \n /* Hide close button in embed mode */\n :host([display-type=\"embed\"]) .nostr-chat-close-btn {\n display: none;\n }\n\n /* Launcher: FAB */\n .nostr-chat-launcher.fab {\n position: fixed;\n bottom: 20px;\n right: 20px;\n z-index: 2147483000;\n display: flex;\n align-items: center;\n gap: 10px;\n font-family: Inter, sans-serif;\n }\n .nostr-chat-launcher .bubble {\n background: var(--nstrc-chat-background);\n color: var(--nstrc-chat-text-color);\n border: var(--nstrc-chat-border);\n border-radius: 14px;\n padding: 10px 12px;\n box-shadow: 0 6px 20px rgba(0,0,0,0.12);\n }\n .nostr-chat-launcher .bubble .title { font-weight: 700; font-size: 14px; }\n .nostr-chat-launcher .bubble .subtitle { font-size: 12px; opacity: 0.8; }\n .nostr-chat-launcher .fab-btn {\n width: 64px;\n height: 64px;\n border-radius: 50%;\n background: var(--nstrc-chat-accent-color);\n color: var(--nstrc-chat-accent-text-color);\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n cursor: pointer;\n box-shadow: 0 6px 20px rgba(0,0,0,0.18);\n font-size: 24px; /* Larger emoji/icon */\n }\n\n /* Launcher: Bottom bar */\n .nostr-chat-launcher.bottom-bar {\n position: fixed;\n left: 50%;\n transform: translateX(-50%);\n bottom: 20px;\n z-index: 2147483000;\n }\n .nostr-chat-launcher .bar-btn {\n padding: 12px 20px;\n border-radius: 999px;\n background: var(--nstrc-chat-accent-color);\n color: var(--nstrc-chat-accent-text-color);\n border: none;\n cursor: pointer;\n font-weight: 700;\n box-shadow: 0 6px 20px rgba(0,0,0,0.18);\n }\n\n .nostr-chat-container {\n display: flex;\n flex-direction: column;\n font-family: Inter, sans-serif;\n width: 100%;\n height: 100%;\n max-width: 400px;\n max-height: 600px;\n box-sizing: border-box;\n border-radius: var(--nstrc-chat-border-radius);\n border: var(--nstrc-chat-border);\n background-color: var(--nstrc-chat-background);\n color: var(--nstrc-chat-text-color);\n overflow: hidden;\n }\n\n .nostr-chat-header {\n padding: 12px 16px;\n border-bottom: var(--nstrc-chat-border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-shrink: 0;\n }\n \n .nostr-chat-header-right {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .nostr-chat-recipient, .nostr-chat-recipient-placeholder {\n display: flex;\n align-items: center; /* Ensures vertical centering */\n gap: 12px;\n }\n\n .nostr-chat-recipient-avatar {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n object-fit: cover;\n }\n\n .nostr-chat-recipient-info {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .nostr-chat-recipient-name {\n font-weight: 600;\n font-size: 16px;\n display: flex;\n align-items: center;\n height: 36px; /* Match avatar height */\n }\n\n .nostr-chat-self {\n display: flex;\n align-items: center;\n gap: 8px;\n opacity: 0.85;\n font-size: 12px;\n }\n .nostr-chat-self-avatar {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n object-fit: cover;\n }\n\n .nostr-chat-content {\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n flex-grow: 1;\n overflow-y: auto;\n }\n \n /* Scrollbar styling for dark theme */\n .nostr-chat-content::-webkit-scrollbar,\n .nostr-chat-history::-webkit-scrollbar {\n width: 8px;\n }\n .nostr-chat-content::-webkit-scrollbar-track,\n .nostr-chat-history::-webkit-scrollbar-track {\n background: transparent;\n }\n .nostr-chat-content::-webkit-scrollbar-thumb,\n .nostr-chat-history::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.2);\n border-radius: 4px;\n }\n .nostr-chat-content::-webkit-scrollbar-thumb:hover,\n .nostr-chat-history::-webkit-scrollbar-thumb:hover {\n background: rgba(255, 255, 255, 0.3);\n }\n \n .nostr-chat-history {\n display: flex;\n flex-direction: column;\n gap: 10px;\n flex-grow: 1;\n overflow-y: auto;\n padding-right: 10px; /* For scrollbar */\n padding-bottom: 16px;\n }\n\n .nostr-chat-message-row {\n display: flex;\n width: 100%;\n flex-direction: column;\n align-items: flex-start;\n }\n\n .nostr-chat-message-me {\n justify-content: flex-end;\n align-items: flex-end;\n }\n\n .nostr-chat-message-them {\n justify-content: flex-start;\n align-items: flex-start;\n }\n\n .nostr-chat-message-bubble {\n padding: 8px 12px;\n border-radius: 18px;\n max-width: 75%;\n word-wrap: break-word;\n }\n\n .nostr-chat-message-me .nostr-chat-message-bubble {\n background-color: var(--nstrc-chat-accent-color);\n color: var(--nstrc-chat-accent-text-color);\n border-radius: 15px 15px 0 15px;\n opacity: 1;\n }\n\n .nostr-chat-message-me.sending .nostr-chat-message-bubble {\n opacity: 0.5;\n }\n\n .nostr-chat-message-me.failed .nostr-chat-message-bubble {\n opacity: 1;\n border: 1px dashed var(--nstrc-chat-error-color);\n }\n\n .nostr-chat-message-them .nostr-chat-message-bubble {\n background-color: var(--nstrc-chat-their-message-background);\n border-bottom-left-radius: 4px;\n }\n\n /* Message timestamp under bubble, always visible but with opacity change on hover */\n .nostr-chat-message-meta {\n font-size: 10px;\n margin-top: 4px;\n opacity: 0.5;\n color: var(--nstrc-chat-text-color);\n max-width: 75%;\n transition: opacity 0.2s ease;\n height: 14px; /* Fixed height to prevent layout jumps */\n }\n .nostr-chat-message-row:hover .nostr-chat-message-meta {\n opacity: 0.9;\n }\n .nostr-chat-message-me .nostr-chat-message-meta {\n text-align: right;\n }\n .nostr-chat-message-them .nostr-chat-message-meta {\n text-align: left;\n }\n\n /* Slightly highlight timestamp for the last message */\n .nostr-chat-history > .nostr-chat-message-row:last-of-type .nostr-chat-message-meta {\n opacity: 0.75;\n }\n\n .nostr-chat-npub-input-container {\n display: flex;\n gap: 8px;\n align-items: center;\n padding: 20px 0;\n }\n\n .nostr-chat-npub-input {\n flex: 1;\n padding: 10px;\n box-sizing: border-box;\n border-radius: 4px;\n border: var(--nstrc-chat-border);\n background-color: var(--nstrc-chat-input-background);\n color: var(--nstrc-chat-text-color);\n }\n\n .nostr-chat-actions {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n border-top: var(--nstrc-chat-border);\n padding-top: 12px;\n }\n\n .nostr-chat-welcome {\n display: flex;\n flex-direction: column;\n gap: 12px;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: 12px 8px;\n }\n .nostr-chat-welcome-text {\n opacity: 0.9;\n }\n .nostr-chat-start-btn {\n padding: 10px 16px;\n border-radius: 20px;\n border: none;\n background-color: var(--nstrc-chat-button-background);\n color: var(--nstrc-chat-button-text);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n }\n\n .nostr-chat-textarea {\n flex-grow: 1;\n height: 40px;\n box-sizing: border-box;\n padding: 10px;\n border-radius: 20px;\n border: var(--nstrc-chat-border);\n background-color: var(--nstrc-chat-input-background);\n color: var(--nstrc-chat-text-color);\n font-family: Inter, sans-serif;\n resize: none;\n overflow-y: auto;\n }\n\n /* Character counter */\n .nostr-chat-char-counter {\n align-self: center;\n font-size: 12px;\n opacity: 0.75;\n color: var(--nstrc-chat-text-color);\n white-space: nowrap;\n min-width: max-content;\n }\n .nostr-chat-char-counter.warn {\n color: #ff8c00; /* Brighter orange color for warning */\n opacity: 1;\n font-weight: 700;\n }\n\n .nostr-chat-find-btn, .nostr-chat-send-btn {\n padding: 10px 16px;\n border-radius: 20px;\n border: none;\n background-color: var(--nstrc-chat-button-background);\n color: var(--nstrc-chat-button-text);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n }\n\n .nostr-chat-error-message {\n color: var(--nstrc-chat-error-color);\n font-size: 14px;\n padding: 0 16px 16px;\n text-align: center;\n }\n </style>\n `;\n}\n","/**\n * <nostr-live-chat>\n * Attributes:\n * - npub (optional): pre-set npub of recipient\n * - pubkey (optional): hex recipient pubkey\n * - nip05 (optional): user@domain nip-05 identifier (alternative to npub)\n * - relays (optional): comma-separated relay URLs (defaults to common public set)\n * - theme (optional): \"light\" | \"dark\" (default \"light\")\n * - display-type (optional): \"fab\" | \"bottom-bar\" | \"full\" | \"embed\" (default \"embed\")\n * - welcome-text (optional): custom text for the welcome screen\n * - start-chat-text (optional): label for the Start button on the welcome screen\n * - history-days (optional): positive integer N to load last N days; \"all\" or <=0 or omitted -> full history\n *\n * Behaviour:\n * • If neither npub nor nip05 is supplied the component shows an input + Find button.\n * • Entering an npub then clicking Find performs profile lookup; during lookup the Find button shows\n * spinner/\"Finding…\". On success the UI switches to the chat view.\n * • If nip05 attribute is provided we first resolve it to a pubkey via nip05 well-known file then look\n * up the profile as usual.\n * • The component will subscribe to incoming DMs and display the chat history.\n */\nimport { NDKNip07Signer, NDKEvent, NDKKind, NDKFilter, NDKSubscription } from \"@nostr-dev-kit/ndk\";\nimport { DEFAULT_RELAYS } from \"../common/constants\";\nimport { Theme } from \"../common/types\";\nimport { getLiveChatStyles, renderLiveChatInner, RenderLiveChatOptions } from \"./render\";\nimport { nip19 } from \"nostr-tools\";\nimport { NostrService } from \"../common/nostr-service\";\nimport { resolveNip05 } from \"../common/nip05-utils\";\n\ntype DisplayType = 'fab' | 'bottom-bar' | 'full' | 'embed';\n\ninterface Message {\n id: string;\n text: string;\n sender: 'me' | 'them';\n timestamp: number;\n status: 'sending' | 'sent' | 'failed';\n}\n\n// Using shared resolveNip05 utility from common/nip05-utils\n\nexport default class NostrLiveChat extends HTMLElement {\n private rendered: boolean = false;\n private nostrService: NostrService = NostrService.getInstance();\n private dmSubscription: NDKSubscription | null = null;\n\n private theme: Theme = \"light\";\n private recipientNpub: string | null = null;\n private recipientNip05: string | null = null;\n private recipientName: string | null = null;\n private recipientPicture: string | null = null;\n private recipientPubkey: string | null = null;\n private message: string = \"\";\n private messages: Message[] = [];\n\n // Current user (signer) info for \"Logged in as\" UI\n private currentUserPubkey: string | null = null;\n private currentUserNpub: string | null = null;\n private currentUserName: string | null = null;\n private currentUserPicture: string | null = null;\n\n // Display controls\n private static readonly DEFAULT_WELCOME_TEXT = \"Welcome! How can we help you today?\";\n private static readonly DEFAULT_START_CHAT_TEXT = \"Start chat\";\n private static readonly DEFAULT_ONLINE_TEXT = \"We're Online!\";\n private static readonly DEFAULT_HELP_TEXT = \"How may I help you today?\";\n private displayType: DisplayType = 'embed';\n private isOpen: boolean = false; // For floating modes\n private showWelcome: boolean = false; // Show welcome screen before starting chat\n private welcomeText: string = NostrLiveChat.DEFAULT_WELCOME_TEXT;\n private startChatText: string = NostrLiveChat.DEFAULT_START_CHAT_TEXT;\n private onlineText: string = NostrLiveChat.DEFAULT_ONLINE_TEXT;\n private helpText: string = NostrLiveChat.DEFAULT_HELP_TEXT;\n private readonly MESSAGE_MAX_LENGTH = 1000;\n\n // isLoading -> sending DM, isFinding -> looking up recipient\n private isLoading: boolean = false;\n private isFinding: boolean = false;\n private isError: boolean = false;\n private errorMessage: string = \"\";\n\n\n // Key management options\n private keySupplier: (() => string | Promise<string>) | null = null;\n\n // Event handlers\n private boundHandleFind: (() => void) | null = null;\n private boundHandleSend: (() => void) | null = null;\n private boundHandleTextareaChange: ((e: Event) => void) | null = null;\n private boundHandleLauncherClick: (() => void) | null = null;\n private boundHandleCloseClick: (() => void) | null = null;\n private boundHandleStartChat: (() => void) | null = null;\n private boundHandleNpubKeydown: ((e: Event) => void) | null = null;\n private resubscribeTimer: number | null = null;\n\n constructor() {\n super();\n this.attachShadow({ mode: \"open\" });\n }\n\n getRelays = () => {\n const userRelays = this.getAttribute(\"relays\");\n if (userRelays) {\n return userRelays\n .split(\",\")\n .map(r => r.trim())\n .filter(r => r.length > 0)\n .filter((r, i, arr) => arr.indexOf(r) === i);\n }\n return DEFAULT_RELAYS;\n };\n\n private async getCurrentUserInfo(): Promise<void> {\n try {\n const ndk = this.nostrService.getNDK();\n let pubkey: string | null = null;\n\n if (typeof window !== 'undefined' && (window as any).nostr) {\n const nostr = (window as any).nostr;\n console.log('Nostr extension found, checking for public key...');\n\n try {\n // Special handling for nos2x extension\n // Check if this is nos2x by looking for specific patterns in the API\n const isNos2x = !!(nostr.enable && typeof nostr.enable === 'function' &&\n nostr.getPublicKey && typeof nostr.getPublicKey === 'function');\n\n console.log('Is this nos2x extension?', isNos2x);\n\n if (isNos2x) {\n console.log('Using nos2x specific flow');\n try {\n // First enable the extension\n await nostr.enable();\n console.log('nos2x extension enabled');\n\n // Use a timeout to ensure extension is ready\n await new Promise(resolve => setTimeout(resolve, 100));\n\n // Get public key from nos2x\n const nos2xResult = await nostr.getPublicKey();\n console.log('nos2x returned pubkey:', nos2xResult);\n if (nos2xResult && typeof nos2xResult === 'string' && nos2xResult.length === 64) {\n pubkey = nos2xResult;\n console.log('Successfully got pubkey from nos2x:', pubkey);\n }\n } catch (nos2xError) {\n console.error('Error with nos2x extension:', nos2xError);\n }\n } else {\n // Generic extension handling\n // First try to enable the extension if it supports it\n if (typeof nostr.enable === 'function') {\n console.log('Attempting to enable generic extension...');\n try {\n await nostr.enable();\n console.log('Extension enabled successfully');\n } catch (enableError) {\n console.warn('Extension enable failed, continuing anyway:', enableError);\n }\n }\n\n // Try to get public key\n if (typeof nostr.getPublicKey === 'function') {\n console.log('Calling getPublicKey()...');\n try {\n const result = await nostr.getPublicKey();\n pubkey = result;\n console.log('Got pubkey from extension:', pubkey);\n } catch (getKeyError) {\n console.error('Error getting public key:', getKeyError);\n }\n } else if (nostr.getPublicKey) {\n console.log('Found getPublicKey property, resolving...');\n try {\n pubkey = await Promise.resolve(nostr.getPublicKey);\n console.log('Got pubkey from extension (property):', pubkey);\n } catch (getKeyError) {\n console.error('Error resolving public key:', getKeyError);\n }\n }\n }\n } catch (error) {\n console.error('Error in extension interaction:', error);\n // Don't re-throw, we'll continue with null pubkey\n }\n }\n\n if (!pubkey) {\n let privateKey: string | null = null;\n\n // Try in-memory key supplier first\n if (this.keySupplier) {\n try {\n privateKey = await this.keySupplier();\n } catch {\n // ignore key supplier errors\n }\n }\n\n if (privateKey) {\n try {\n const { NDKPrivateKeySigner } = await import('@nostr-dev-kit/ndk');\n let sk = privateKey;\n if (privateKey.startsWith('nsec')) {\n const decoded = nip19.decode(privateKey);\n sk = decoded.data as string;\n }\n const signer = new NDKPrivateKeySigner(sk);\n const u = await signer.user();\n pubkey = u.pubkey;\n } catch {\n // ignore\n }\n }\n }\n\n // NOTE: We won't save any sensitive info in local/session storage. \n // Signing always happens in nos2x/alby, or bunkers extensions.\n\n if (pubkey) {\n this.currentUserPubkey = pubkey;\n this.currentUserNpub = nip19.npubEncode(pubkey);\n console.log('Got pubkey from extension and will use it:', pubkey);\n\n try {\n const user = ndk.getUser({ pubkey });\n console.log('Fetching profile for user:', pubkey);\n await user.fetchProfile();\n console.log('Fetched profile:', user.profile);\n\n this.currentUserName = user.profile?.displayName || user.profile?.name || this.currentUserNpub?.substring(0, 10) || null;\n this.currentUserPicture = user.profile?.image || null;\n\n if (!this.currentUserName) {\n console.warn('No username found in profile');\n }\n if (!this.currentUserPicture) {\n console.warn('No profile picture found');\n }\n\n console.log('Profile info set:', {\n name: this.currentUserName,\n picture: this.currentUserPicture,\n npub: this.currentUserNpub\n });\n } catch (error) {\n console.error('Error fetching profile:', error);\n // Fallback to just showing the npub if we can't fetch the profile\n this.currentUserName = this.currentUserNpub?.substring(0, 10) || null;\n }\n this.render();\n } else {\n console.log('Checking for direct nos2x logs in console...');\n\n // Try to detect the pubkey from window level objects that nos2x might set\n try {\n // Wait a bit for the extension to initialize\n await new Promise(resolve => setTimeout(resolve, 500));\n\n // Try again once more with direct extension call\n if (typeof window !== 'undefined' && (window as any).nostr) {\n console.log('Trying direct call to extension again...');\n try {\n const directPubkey = await (window as any).nostr.getPublicKey();\n if (directPubkey && typeof directPubkey === 'string') {\n console.log('Got pubkey on second try:', directPubkey);\n // Call ourselves recursively with the pubkey we found\n this.currentUserPubkey = directPubkey;\n this.currentUserNpub = nip19.npubEncode(directPubkey);\n\n // Get user profile\n try {\n const user = ndk.getUser({ pubkey: directPubkey });\n console.log('Fetching profile for user on second try:', directPubkey);\n await user.fetchProfile();\n console.log('Fetched profile on second try:', user.profile);\n\n this.currentUserName = user.profile?.displayName || user.profile?.name || this.currentUserNpub?.substring(0, 10) || null;\n this.currentUserPicture = user.profile?.image || null;\n\n this.render();\n return;\n } catch (profileError) {\n console.error('Error fetching profile on second try:', profileError);\n this.currentUserName = this.currentUserNpub?.substring(0, 10) || null;\n this.render();\n return;\n }\n }\n } catch (directError) {\n console.error('Error getting pubkey on direct second try:', directError);\n }\n }\n } catch (fallbackError) {\n console.error('Error in fallback pubkey detection:', fallbackError);\n }\n console.warn('No pubkey available from extension - trying fallback method');\n }\n } catch {\n // ignore global errors\n }\n }\n\n private getDisplayType() {\n const attr = this.getAttribute('display-type');\n const allowed: DisplayType[] = ['fab', 'bottom-bar', 'full', 'embed'];\n const raw = attr ? String(attr) : '';\n const lower = raw.toLowerCase();\n const normalized = allowed.includes(lower as DisplayType) ? lower as DisplayType : 'embed';\n this.displayType = normalized;\n // Initialize open state\n if (this.displayType === 'full') {\n this.isOpen = true;\n } else if (this.displayType === 'fab' || this.displayType === 'bottom-bar') {\n if (this.isOpen === undefined || this.isOpen === null) this.isOpen = false;\n } else {\n this.isOpen = false;\n }\n // Reflect attribute for :host([display-type=...]) CSS to apply\n const current = this.getAttribute('display-type');\n if (current !== normalized) {\n this.setAttribute('display-type', normalized);\n }\n }\n\n getTheme = () => {\n const attr = this.getAttribute(\"theme\");\n const value = (attr || \"\").toLowerCase();\n if (value === \"light\" || value === \"dark\") {\n this.theme = value as Theme;\n } else {\n if (attr) {\n console.warn(`Invalid theme '${attr}'. Accepted values are 'light', 'dark'. Falling back to 'light'.`);\n }\n this.theme = \"light\";\n }\n };\n\n getRecipient = () => {\n const recipientPub = this.getAttribute(\"pubkey\");\n if (recipientPub) {\n try {\n this.recipientPubkey = recipientPub;\n this.recipientNpub = nip19.npubEncode(recipientPub);\n this.lookupRecipient(this.recipientNpub);\n return;\n } catch (e) {\n const errorMsg = `Invalid recipient pubkey \"${recipientPub}\": ${e instanceof Error ? e.message : String(e)}`;\n this.isError = true;\n this.errorMessage = errorMsg;\n console.error('nostr-live-chat:', errorMsg, e);\n this.render();\n return; // Stop fallback flow\n }\n }\n const nip05Attr = this.getAttribute(\"nip05\");\n if (nip05Attr) {\n this.recipientNip05 = nip05Attr;\n this.lookupRecipientByNip05();\n return;\n }\n\n const recipientNpub = this.getAttribute(\"npub\");\n if (recipientNpub) {\n this.recipientNpub = recipientNpub;\n this.lookupRecipient();\n }\n };\n\n connectedCallback() {\n if (!this.rendered) {\n this.getTheme();\n this.getDisplayType();\n // Apply initial textual attributes before first render\n const welcomeAttr = this.getAttribute('welcome-text');\n if (welcomeAttr) this.welcomeText = welcomeAttr;\n const startAttr = this.getAttribute('start-chat-text');\n if (startAttr) this.startChatText = startAttr;\n const onlineAttr = this.getAttribute('online-text');\n if (onlineAttr) this.onlineText = onlineAttr;\n const helpAttr = this.getAttribute('help-text');\n if (helpAttr) this.helpText = helpAttr;\n this.getRecipient();\n this.nostrService.connectToNostr(this.getRelays());\n this.getCurrentUserInfo();\n this.render();\n this.rendered = true;\n }\n }\n\n static get observedAttributes() {\n return [\n \"npub\",\n \"pubkey\",\n \"nip05\",\n \"relays\",\n \"theme\",\n \"display-type\",\n \"welcome-text\",\n \"start-chat-text\",\n \"online-text\", // New: FAB online text\n \"help-text\", // New: FAB help text\n \"history-days\",\n ];\n }\n\n // Stop any active DM subscription and clear debounce timer\n private unsubscribeFromDms() {\n if (this.dmSubscription) {\n try { this.dmSubscription.stop(); } catch { }\n this.dmSubscription = null;\n }\n if (this.resubscribeTimer) {\n clearTimeout(this.resubscribeTimer);\n this.resubscribeTimer = null;\n }\n }\n\n // Clear all recipient-related and chat UI state\n private clearRecipientAndChatState() {\n this.recipientPubkey = null;\n this.recipientNpub = null;\n this.recipientNip05 = null;\n this.recipientName = null;\n this.recipientPicture = null;\n this.messages = [];\n this.message = \"\";\n this.showWelcome = false; // reset start chat flag\n this.isLoading = false;\n this.isFinding = false;\n this.isError = false;\n this.errorMessage = \"\";\n }\n\n attributeChangedCallback(\n name: string,\n _oldValue: string | null,\n newValue: string | null\n ) {\n if (!this.rendered) return;\n\n if (name === \"npub\") {\n if (newValue === null) {\n // Attribute removed: stop subscription and clear state\n this.unsubscribeFromDms();\n this.clearRecipientAndChatState();\n this.render();\n return;\n }\n if (newValue !== _oldValue) {\n // Changed: stop subscription, clear state, then lookup new recipient\n this.unsubscribeFromDms();\n this.clearRecipientAndChatState();\n this.recipientNpub = newValue!;\n this.lookupRecipient();\n }\n return;\n } else if (name === 'pubkey') {\n if (newValue === null) {\n this.unsubscribeFromDms();\n this.clearRecipientAndChatState();\n this.render();\n return;\n }\n if (newValue !== _oldValue) {\n this.unsubscribeFromDms();\n this.clearRecipientAndChatState();\n try {\n this.recipientPubkey = newValue!;\n this.recipientNpub = nip19.npubEncode(newValue!);\n this.lookupRecipient(this.recipientNpub);\n } catch {\n // ignore invalid value\n }\n }\n return;\n } else if (name === \"nip05\") {\n if (newValue === null) {\n this.unsubscribeFromDms();\n this.clearRecipientAndChatState();\n this.render();\n return;\n }\n if (newValue !== _oldValue) {\n this.unsubscribeFromDms();\n this.clearRecipientAndChatState();\n this.recipientNip05 = newValue!;\n this.lookupRecipientByNip05();\n }\n return;\n } else if (name === \"relays\") {\n if (newValue !== _oldValue) {\n const relays = this.getRelays();\n const chatActive = this.recipientPubkey && !this.showWelcome;\n if (chatActive) {\n if (this.resubscribeTimer) {\n clearTimeout(this.resubscribeTimer);\n }\n this.resubscribeTimer = window.setTimeout(() => {\n // Unsubscribe from current DMs, reconnect to new relays, then resubscribe\n this.unsubscribeFromDms();\n this.nostrService.connectToNostr(relays);\n this.subscribeToDms();\n }, 300);\n } else {\n this.nostrService.connectToNostr(relays);\n }\n }\n } else if (name === \"theme\") {\n this.getTheme();\n this.render();\n } else if (name === 'display-type') {\n this.getDisplayType();\n this.render();\n } else if (name === 'welcome-text') {\n // Reset to default when attribute is removed (newValue === null)\n this.welcomeText = newValue !== null ? newValue : NostrLiveChat.DEFAULT_WELCOME_TEXT;\n this.render();\n } else if (name === 'start-chat-text') {\n // Reset to default when attribute is removed (newValue === null)\n this.startChatText = newValue !== null ? newValue : NostrLiveChat.DEFAULT_START_CHAT_TEXT;\n this.render();\n } else if (name === 'online-text') {\n // Reset to default when attribute is removed (newValue === null)\n this.onlineText = newValue !== null ? newValue : NostrLiveChat.DEFAULT_ONLINE_TEXT;\n this.render();\n } else if (name === 'help-text') {\n // Reset to default when attribute is removed (newValue === null)\n this.helpText = newValue !== null ? newValue : NostrLiveChat.DEFAULT_HELP_TEXT;\n this.render();\n } else if (name === 'history-days') {\n // If history window changes while a chat is active, resubscribe to reload history (debounced)\n if (newValue !== _oldValue && this.recipientPubkey && !this.showWelcome) {\n if (this.resubscribeTimer) {\n clearTimeout(this.resubscribeTimer);\n }\n this.resubscribeTimer = window.setTimeout(() => {\n this.subscribeToDms();\n }, 250);\n }\n }\n }\n\n private async lookupRecipient(npub?: string) {\n // Stop any existing DM subscription and reset chat state before new lookup\n this.unsubscribeFromDms();\n this.messages = [];\n this.showWelcome = false;\n\n this.isFinding = true;\n this.render();\n\n try {\n const recipientNpub = npub || this.recipientNpub;\n if (!recipientNpub) return;\n\n const { type, data } = nip19.decode(recipientNpub);\n if (type !== \"npub\") throw new Error(\"Invalid npub\");\n this.recipientPubkey = data as string;\n\n const user = this.nostrService.getNDK().getUser({ pubkey: this.recipientPubkey });\n await user.fetchProfile();\n\n this.recipientName = user.profile?.displayName || user.profile?.name || recipientNpub.substring(0, 10);\n this.recipientPicture = user.profile?.image || null;\n // Prepare welcome screen; user starts chat to subscribe\n this.showWelcome = true;\n this.messages = [];\n // Clear any previous error state on successful lookup\n this.isError = false;\n this.errorMessage = \"\";\n this.render(); // Re-render to show profile info & welcome view\n\n } catch (e: any) {\n this.isError = true;\n this.errorMessage = e.message;\n } finally {\n this.isFinding = false;\n this.render();\n }\n }\n\n // Resolve nip05 -> pubkey -> npub then delegate to lookupRecipient\n private async lookupRecipientByNip05(nip05?: string) {\n this.isFinding = true;\n this.render();\n\n try {\n const recipientNip05 = nip05 || this.recipientNip05;\n if (!recipientNip05) return;\n\n const pubkey = await resolveNip05(recipientNip05);\n const npub = nip19.npubEncode(pubkey);\n this.recipientNpub = npub;\n // Clear any previous error state on successful nip05 resolution\n this.isError = false;\n this.errorMessage = \"\";\n await this.lookupRecipient(npub);\n\n } catch (e: any) {\n this.isError = true;\n this.errorMessage = e.message;\n } finally {\n this.isFinding = false;\n this.render();\n }\n }\n\n private handleFindClick() {\n const input = this.shadowRoot!.querySelector(\".nostr-chat-npub-input\") as HTMLInputElement;\n const value = input.value.trim();\n // Reset previous error state when starting a new find\n this.isError = false;\n this.errorMessage = \"\";\n this.render();\n if (!value) return;\n\n if (value.startsWith(\"npub\")) {\n this.recipientNpub = value;\n this.lookupRecipient();\n } else if (value.includes(\"@\")) {\n this.recipientNip05 = value;\n this.lookupRecipientByNip05();\n } else {\n this.isError = true;\n this.errorMessage = \"Invalid input. Please provide an npub or nip05 address.\";\n this.render();\n }\n }\n\n private async handleSendClick() {\n if (!this.message.trim() || !this.recipientPubkey) return;\n if (this.message.length > this.MESSAGE_MAX_LENGTH) {\n this.isError = true;\n this.errorMessage = `Message is too long (max ${this.MESSAGE_MAX_LENGTH} characters).`;\n this.render();\n return;\n }\n\n this.isLoading = true;\n this.render();\n\n let tempId: string | null = null;\n\n try {\n const ndk = this.nostrService.getNDK();\n let signer;\n\n // Enhanced check for NIP-07 extension - verify if fully available with required methods\n if (typeof window !== 'undefined' &&\n (window as any).nostr &&\n (window as any).nostr.getPublicKey &&\n (window as any).nostr.signEvent) {\n try {\n signer = new NDKNip07Signer();\n } catch (err) {\n console.error(\"Error creating NIP-07 signer:\", err);\n this.isError = true;\n this.errorMessage = `Error connecting to Nostr extension: ${(err as Error).message}`;\n this.isLoading = false;\n this.render();\n return;\n }\n }\n\n if (!signer) {\n throw new Error(\"No signer available. Please install a NIP-07 extension or set a private key.\");\n }\n ndk.signer = signer;\n\n const event = new NDKEvent(ndk, {\n kind: NDKKind.EncryptedDirectMessage,\n tags: [[\"p\", this.recipientPubkey!]],\n created_at: Math.floor(Date.now() / 1000),\n });\n\n let encryptionSucceeded = false;\n\n // Try NIP-07 encryption first if available\n if (typeof (window as any).nostr?.nip04?.encrypt === 'function') {\n try {\n event.content = await (window as any).nostr.nip04.encrypt(\n this.recipientPubkey!,\n this.message.trim()\n );\n encryptionSucceeded = true;\n } catch (e: any) {\n console.error(\"NIP-07 encryption failed, falling back to local encryption:\", e);\n }\n }\n\n // Fall back to local encryption if NIP-07 failed or unavailable\n if (!encryptionSucceeded) {\n throw new Error(\"No private key available for encryption. Please use a NIP-07 extension.\");\n }\n\n tempId = `temp_${Date.now()}`;\n this.messages.push({\n id: tempId,\n text: this.message,\n sender: 'me',\n timestamp: event.created_at!,\n status: 'sending'\n });\n this.message = \"\"; // Clear input\n this.render();\n\n await event.publish();\n\n // Check if the optimistic message still exists (real event might have already arrived)\n const sentMessage = this.messages.find(m => m.id === tempId);\n if (sentMessage) {\n // Check if a real message with this ID already exists (race condition)\n const realMessage = this.messages.find(m => m.id === event.id && m.id !== tempId);\n if (realMessage) {\n // Real event arrived first, remove the optimistic message\n this.messages = this.messages.filter(m => m.id !== tempId);\n } else {\n // Update optimistic message with real event id\n sentMessage.id = event.id;\n sentMessage.status = 'sent';\n }\n this.render();\n }\n\n } catch (e: any) {\n this.isError = true;\n this.errorMessage = e.message;\n // If publish failed, mark the optimistic message as failed\n if (tempId) {\n const msg = this.messages.find(m => m.id === tempId);\n if (msg) {\n msg.status = 'failed';\n }\n this.render();\n }\n } finally {\n this.isLoading = false;\n this.render();\n }\n }\n\n private handleTextareaChange(e: Event) {\n const textarea = e.target as HTMLTextAreaElement;\n this.message = textarea.value;\n\n // Update character counter\n const remaining = Math.max(0, this.MESSAGE_MAX_LENGTH - this.message.length);\n const counterEl = this.shadowRoot?.querySelector('.nostr-chat-char-counter');\n if (counterEl) {\n counterEl.textContent = `${remaining} chars left`;\n counterEl.classList.toggle('warn', remaining <= 10);\n }\n }\n\n private handleStartChat() {\n this.showWelcome = false;\n this.subscribeToDms();\n this.render();\n }\n\n private async subscribeToDms() {\n if (this.dmSubscription) {\n this.dmSubscription.stop();\n }\n if (!this.recipientPubkey) return;\n\n // Determine current user using available signer (extension or local key)\n let currentUser: { pubkey: string } | null = null;\n try {\n if (typeof window !== 'undefined' && (window as any).nostr && (window as any).nostr.getPublicKey) {\n const pubkey = await (window as any).nostr.getPublicKey();\n currentUser = { pubkey };\n } else {\n let privateKey: string | null = null;\n\n // Try in-memory key supplier first\n if (this.keySupplier) {\n try {\n privateKey = await this.keySupplier();\n } catch {\n // ignore key supplier errors\n }\n }\n\n\n\n if (privateKey) {\n const { NDKPrivateKeySigner } = await import('@nostr-dev-kit/ndk');\n let sk = privateKey;\n if (privateKey.startsWith('nsec')) {\n const decoded = nip19.decode(privateKey);\n sk = decoded.data as string;\n }\n const signer = new NDKPrivateKeySigner(sk);\n currentUser = await signer.user();\n }\n }\n } catch (err) {\n console.error('Failed to determine current user for subscription', err);\n }\n\n if (!currentUser) {\n this.isError = true;\n this.errorMessage = 'No signer available. Please install a NIP-07 extension or set a private key to start the chat.';\n this.render();\n return;\n }\n\n // Reset messages for new recipient\n this.messages = [];\n\n // Determine history window from attribute: default full history when unset; numeric days > 0 => limit; 'all' or <=0 => full\n const historyDaysAttr = this.getAttribute('history-days');\n let since: number | undefined;\n if (!historyDaysAttr) {\n since = undefined; // attribute not set -> full history\n } else if (historyDaysAttr.toLowerCase() === 'all' || parseInt(historyDaysAttr, 10) <= 0) {\n since = undefined; // omit since -> full history\n } else {\n const historyDays = Math.max(1, parseInt(historyDaysAttr, 10) || 30);\n since = Math.floor((Date.now() - historyDays * 24 * 60 * 60 * 1000) / 1000);\n }\n\n // Base filters\n const baseFilter1: NDKFilter = {\n kinds: [NDKKind.EncryptedDirectMessage],\n '#p': [currentUser.pubkey],\n authors: [this.recipientPubkey!],\n };\n const baseFilter2: NDKFilter = {\n kinds: [NDKKind.EncryptedDirectMessage],\n '#p': [this.recipientPubkey!],\n authors: [currentUser.pubkey],\n };\n\n // Conditionally include since\n const filter1: NDKFilter = since !== undefined ? { ...baseFilter1, since } : baseFilter1;\n const filter2: NDKFilter = since !== undefined ? { ...baseFilter2, since } : baseFilter2;\n\n this.dmSubscription = this.nostrService.getNDK().subscribe([filter1, filter2], {\n closeOnEose: false,\n groupable: false\n });\n\n this.dmSubscription.on('event', async (event: NDKEvent) => {\n try {\n // Update current user info if missing\n if (!this.currentUserPubkey && currentUser) {\n this.currentUserPubkey = currentUser.pubkey;\n this.currentUserNpub = nip19.npubEncode(currentUser.pubkey);\n }\n // Determine if we are the sender or receiver of the event\n const isSender = event.pubkey === currentUser.pubkey;\n const peer = isSender ? event.tags.find(t => t[0] === 'p')?.[1] : event.pubkey;\n\n // Guard: malformed event without a peer (missing 'p' tag or pubkey)\n if (!peer) {\n try { console.debug('nostr-live-chat: skipping event with missing peer', event.id); } catch { }\n return;\n }\n\n // Validate that this message is between the current user and the recipient\n if (peer !== this.recipientPubkey) {\n // not for me\n return;\n }\n\n // For messages sent by current user, check if we have an optimistic version to upgrade\n if (isSender) {\n // First check if we already have a message with this exact ID (real event arrived first)\n const existingMessageById = this.messages.find(m => m.id === event.id);\n if (existingMessageById) {\n // Real event already exists, just update status if needed\n if (existingMessageById.status === 'sending') {\n existingMessageById.status = 'sent';\n this.render();\n }\n return;\n }\n }\n\n let decryptedText = \"\";\n\n // Guard again before any decryption — never decrypt with a missing peer\n if (!peer) {\n try { console.debug('nostr-live-chat: missing peer prior to decrypt, skipping', event.id); } catch { }\n return;\n }\n\n // Guard against missing or empty content before decryption\n if (!event.content || event.content.trim() === '') {\n try { console.debug('nostr-live-chat: missing or empty content prior to decrypt, skipping', event.id); } catch { }\n return;\n }\n\n if ((window as any).nostr && (window as any).nostr.nip04 && typeof (window as any).nostr.nip04.decrypt === \"function\") {\n try {\n decryptedText = await (window as any).nostr.nip04.decrypt(\n peer,\n event.content\n );\n } catch (e: any) {\n console.error(\"Failed to decrypt DM content:\", e);\n return;\n }\n } else {\n throw new Error(\"No private key available for decryption. Please use a NIP-07 extension.\");\n }\n\n // For sent messages, check if we have an optimistic version to upgrade\n if (isSender) {\n // Look for an optimistic message with matching content and timestamp\n const optimisticMessage = this.messages.find(m =>\n m.status === 'sending' &&\n m.sender === 'me' &&\n m.text === decryptedText &&\n Math.abs(m.timestamp - event.created_at!) < 2\n );\n\n if (optimisticMessage) {\n // Upgrade the optimistic message with the real event data\n optimisticMessage.id = event.id;\n optimisticMessage.status = 'sent';\n this.render();\n return;\n }\n }\n\n const message: Message = {\n id: event.id,\n text: decryptedText,\n sender: isSender ? 'me' : 'them',\n timestamp: event.created_at!,\n status: 'sent'\n };\n\n // Check for duplicates by ID to handle edge cases\n const isDuplicate = this.messages.find(m => m.id === message.id);\n\n if (!isDuplicate) {\n this.messages.push(message);\n this.messages.sort((a, b) => a.timestamp - b.timestamp);\n this.render();\n }\n } catch (e: any) {\n console.error(\"Failed to decrypt DM content:\", e);\n }\n });\n }\n\n private attachEventListeners() {\n // Floating launchers\n const launcher = this.shadowRoot!.querySelector('.nostr-chat-launcher');\n if (launcher) {\n if (this.boundHandleLauncherClick) launcher.removeEventListener('click', this.boundHandleLauncherClick);\n this.boundHandleLauncherClick = () => { this.isOpen = true; this.render(); };\n launcher.addEventListener('click', this.boundHandleLauncherClick);\n }\n const closeBtn = this.shadowRoot!.querySelector('.nostr-chat-close-btn');\n if (closeBtn) {\n if (this.boundHandleCloseClick) closeBtn.removeEventListener('click', this.boundHandleCloseClick);\n this.boundHandleCloseClick = (e?: Event) => {\n e?.stopPropagation();\n if (this.displayType === 'fab' || this.displayType === 'bottom-bar') {\n this.isOpen = false;\n }\n this.render();\n };\n closeBtn.addEventListener('click', this.boundHandleCloseClick);\n }\n\n const findButton = this.shadowRoot!.querySelector(\".nostr-chat-find-btn\");\n if (findButton) {\n if (this.boundHandleFind) findButton.removeEventListener(\"click\", this.boundHandleFind);\n this.boundHandleFind = this.handleFindClick.bind(this);\n findButton.addEventListener(\"click\", this.boundHandleFind);\n }\n\n const sendButton = this.shadowRoot!.querySelector(\".nostr-chat-send-btn\");\n if (sendButton) {\n if (this.boundHandleSend) sendButton.removeEventListener(\"click\", this.boundHandleSend);\n this.boundHandleSend = this.handleSendClick.bind(this);\n sendButton.addEventListener(\"click\", this.boundHandleSend);\n }\n\n const startBtn = this.shadowRoot!.querySelector('.nostr-chat-start-btn');\n if (startBtn) {\n if (this.boundHandleStartChat) startBtn.removeEventListener('click', this.boundHandleStartChat);\n this.boundHandleStartChat = this.handleStartChat.bind(this);\n startBtn.addEventListener('click', this.boundHandleStartChat);\n }\n\n const textarea = this.shadowRoot!.querySelector(\".nostr-chat-textarea\");\n if (textarea) {\n if (this.boundHandleTextareaChange) textarea.removeEventListener(\"input\", this.boundHandleTextareaChange);\n this.boundHandleTextareaChange = this.handleTextareaChange.bind(this);\n textarea.addEventListener(\"input\", this.boundHandleTextareaChange);\n }\n\n const npubInput = this.shadowRoot!.querySelector(\".nostr-chat-npub-input\") as HTMLElement | null;\n if (npubInput) {\n if (this.boundHandleNpubKeydown) npubInput.removeEventListener(\"keydown\", this.boundHandleNpubKeydown);\n this.boundHandleNpubKeydown = (e: Event) => {\n const ke = e as KeyboardEvent;\n if (ke.key === \"Enter\") this.handleFindClick();\n };\n npubInput.addEventListener(\"keydown\", this.boundHandleNpubKeydown as EventListener);\n }\n }\n\n disconnectedCallback() {\n if (this.dmSubscription) {\n this.dmSubscription.stop();\n }\n // Clean up event listeners\n const launcher = this.shadowRoot?.querySelector('.nostr-chat-launcher');\n if (launcher && this.boundHandleLauncherClick) launcher.removeEventListener('click', this.boundHandleLauncherClick);\n const closeBtn = this.shadowRoot?.querySelector('.nostr-chat-close-btn');\n if (closeBtn && this.boundHandleCloseClick) closeBtn.removeEventListener('click', this.boundHandleCloseClick);\n const findButton = this.shadowRoot?.querySelector(\".nostr-chat-find-btn\");\n if (findButton && this.boundHandleFind) findButton.removeEventListener(\"click\", this.boundHandleFind);\n\n const sendButton = this.shadowRoot?.querySelector(\".nostr-chat-send-btn\");\n if (sendButton && this.boundHandleSend) sendButton.removeEventListener(\"click\", this.boundHandleSend);\n\n const startBtn = this.shadowRoot?.querySelector('.nostr-chat-start-btn');\n if (startBtn && this.boundHandleStartChat) startBtn.removeEventListener('click', this.boundHandleStartChat);\n\n const textarea = this.shadowRoot?.querySelector(\".nostr-chat-textarea\");\n if (textarea && this.boundHandleTextareaChange) textarea.removeEventListener(\"input\", this.boundHandleTextareaChange);\n const npubInput = this.shadowRoot?.querySelector('.nostr-chat-npub-input') as HTMLElement | null;\n if (npubInput && this.boundHandleNpubKeydown) npubInput.removeEventListener('keydown', this.boundHandleNpubKeydown as EventListener);\n if (this.resubscribeTimer) {\n clearTimeout(this.resubscribeTimer);\n this.resubscribeTimer = null;\n }\n }\n\n // Public methods for key management configuration\n public setKeySupplier(supplier: (() => string | Promise<string>) | null): void {\n this.keySupplier = supplier;\n }\n\n private escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n }\n\n private render() {\n const renderOptions: RenderLiveChatOptions = {\n theme: this.theme,\n recipientNpub: this.recipientNpub,\n recipientName: this.recipientName,\n recipientPicture: this.recipientPicture,\n message: this.message,\n messages: this.messages,\n isLoading: this.isLoading,\n isFinding: this.isFinding,\n isError: this.isError,\n errorMessage: this.errorMessage,\n currentUserName: this.currentUserName,\n currentUserPicture: this.currentUserPicture,\n showWelcome: this.showWelcome,\n welcomeText: this.welcomeText,\n startChatText: this.startChatText,\n onlineText: this.onlineText,\n helpText: this.helpText,\n maxMessageLength: this.MESSAGE_MAX_LENGTH,\n };\n\n const styles = getLiveChatStyles(this.theme);\n const { onlineText, helpText, ...innerOptions } = renderOptions;\n const inner = renderLiveChatInner(innerOptions);\n\n let html = styles;\n if (this.displayType === 'embed') {\n html += inner;\n } else if (this.displayType === 'full') {\n html += `\n <div class=\"nostr-chat-float-panel open\">${inner}</div>\n `;\n } else if (this.displayType === 'fab') {\n html += `\n ${this.isOpen ? '' : `\n <div class=\"nostr-chat-launcher fab\" role=\"button\" aria-label=\"Open live chat\">\n <div class=\"bubble\">\n <div class=\"title\">${this.escapeHtml(this.onlineText)}</div>\n <div class=\"subtitle\">${this.escapeHtml(this.helpText)}</div>\n </div>\n <button class=\"fab-btn\" aria-label=\"Open chat\">💬</button>\n </div>\n `}\n <div class=\"nostr-chat-float-panel ${this.isOpen ? 'open' : ''}\">\n ${inner}\n </div>\n `;\n } else if (this.displayType === 'bottom-bar') {\n html += `\n ${this.isOpen ? '' : `\n <div class=\"nostr-chat-launcher bottom-bar\" role=\"button\" aria-label=\"Open live chat\">\n <button class=\"bar-btn\">Live chat</button>\n </div>\n `}\n <div class=\"nostr-chat-float-panel ${this.isOpen ? 'open' : ''}\">\n ${inner}\n </div>\n `;\n }\n\n this.shadowRoot!.innerHTML = html;\n this.attachEventListeners();\n\n // Scroll to bottom after render\n setTimeout(() => {\n const chatHistory = this.shadowRoot?.querySelector('.nostr-chat-history');\n if (chatHistory) {\n (chatHistory as HTMLElement).scrollTop = (chatHistory as HTMLElement).scrollHeight;\n }\n }, 0);\n }\n}\n\nif (!customElements.get('nostr-live-chat')) {\n customElements.define('nostr-live-chat', NostrLiveChat);\n}\n"],"file":"components/nostr-live-chat.es.js"}