|
88 | 88 | box-shadow: 0 0 5px #00ff00; |
89 | 89 | } |
90 | 90 |
|
| 91 | + .notification-button { |
| 92 | + background: #003300; |
| 93 | + border: 1px solid #00ff00; |
| 94 | + color: #00ff00; |
| 95 | + padding: 5px 8px; |
| 96 | + font-family: inherit; |
| 97 | + font-size: 14px; |
| 98 | + cursor: pointer; |
| 99 | + transition: all 0.2s; |
| 100 | + border-radius: 3px; |
| 101 | + } |
| 102 | + |
| 103 | + .notification-button:hover { |
| 104 | + background: #00ff00; |
| 105 | + color: #000; |
| 106 | + box-shadow: 0 0 5px #00ff00; |
| 107 | + } |
| 108 | + |
| 109 | + .notification-button.enabled { |
| 110 | + background: #00ff00; |
| 111 | + color: #000; |
| 112 | + } |
| 113 | + |
| 114 | + .notification-button.disabled { |
| 115 | + background: #660000; |
| 116 | + border-color: #ff0000; |
| 117 | + color: #ff6666; |
| 118 | + opacity: 0.7; |
| 119 | + cursor: not-allowed; |
| 120 | + } |
| 121 | + |
91 | 122 | .status-group { |
92 | 123 | display: flex; |
93 | 124 | flex-direction: column; |
|
190 | 221 | font-size: 11px; |
191 | 222 | } |
192 | 223 |
|
| 224 | + .notification-button { |
| 225 | + padding: 3px 6px; |
| 226 | + font-size: 12px; |
| 227 | + } |
| 228 | + |
193 | 229 | .status-group { |
194 | 230 | align-items: flex-end; |
195 | 231 | gap: 1px; |
|
763 | 799 | <div class="title">RetroChat</div> |
764 | 800 | <div class="user-info"> |
765 | 801 | <input type="text" class="nickname-input" placeholder="Enter nickname..." value=""> |
| 802 | + <button class="notification-button" id="notificationButton" title="Enable notifications">🔔</button> |
766 | 803 | <div class="status-group"> |
767 | 804 | <span class="status connecting" id="status"> |
768 | 805 | Connecting... |
|
852 | 889 | this.initProtobuf(); |
853 | 890 | this.initElements(); |
854 | 891 | this.initEventListeners(); |
855 | | - this.requestNotificationPermission(); |
| 892 | + this.initNotificationButton(); |
856 | 893 | this.initWaku(); |
857 | 894 | } |
858 | 895 |
|
|
894 | 931 | return nickname; |
895 | 932 | } |
896 | 933 |
|
897 | | - async requestNotificationPermission() { |
| 934 | + initNotificationButton() { |
898 | 935 | if (!('Notification' in window)) { |
899 | 936 | console.log('This browser does not support notifications'); |
| 937 | + this.notificationButton.style.display = 'none'; |
900 | 938 | this.addSystemMessage('Browser notifications not supported'); |
901 | 939 | return; |
902 | 940 | } |
903 | 941 |
|
| 942 | + // Set initial button state |
| 943 | + this.updateNotificationButtonState(); |
| 944 | + |
| 945 | + // Add click handler for notification button |
| 946 | + this.notificationButton.addEventListener('click', () => { |
| 947 | + this.requestNotificationPermission(); |
| 948 | + }); |
| 949 | + |
| 950 | + // Check if we're in a PWA context |
| 951 | + const isPWA = window.matchMedia('(display-mode: standalone)').matches || |
| 952 | + window.navigator.standalone === true; |
| 953 | + |
| 954 | + if (isPWA) { |
| 955 | + this.addSystemMessage('Running in PWA mode - notifications should work better'); |
| 956 | + } else { |
| 957 | + this.addSystemMessage('For best notification support on mobile, add this app to your home screen'); |
| 958 | + } |
| 959 | + } |
| 960 | + |
| 961 | + updateNotificationButtonState() { |
| 962 | + const permission = Notification.permission; |
| 963 | + |
| 964 | + if (permission === 'granted') { |
| 965 | + this.notificationButton.className = 'notification-button enabled'; |
| 966 | + this.notificationButton.title = 'Notifications enabled'; |
| 967 | + this.notificationButton.textContent = '🔔'; |
| 968 | + this.notificationsEnabled = true; |
| 969 | + } else if (permission === 'denied') { |
| 970 | + this.notificationButton.className = 'notification-button disabled'; |
| 971 | + this.notificationButton.title = 'Notifications blocked - enable in browser settings'; |
| 972 | + this.notificationButton.textContent = '🔕'; |
| 973 | + this.notificationsEnabled = false; |
| 974 | + } else if (permission === 'default') { |
| 975 | + this.notificationButton.className = 'notification-button'; |
| 976 | + this.notificationButton.title = 'Click to request notification permission'; |
| 977 | + this.notificationButton.textContent = '🔔'; |
| 978 | + this.notificationsEnabled = false; |
| 979 | + } else { |
| 980 | + this.notificationButton.className = 'notification-button'; |
| 981 | + this.notificationButton.title = `Unknown permission state: ${permission}`; |
| 982 | + this.notificationButton.textContent = '🔔'; |
| 983 | + this.notificationsEnabled = false; |
| 984 | + } |
| 985 | + |
| 986 | + // Log state for debugging |
| 987 | + console.log('Button state updated:', permission, this.notificationsEnabled); |
| 988 | + } |
| 989 | + |
| 990 | + async requestNotificationPermission() { |
| 991 | + if (!('Notification' in window)) { |
| 992 | + this.addSystemMessage('Browser notifications not supported'); |
| 993 | + return; |
| 994 | + } |
| 995 | + |
| 996 | + // Log current permission state for debugging |
| 997 | + console.log('Current notification permission:', Notification.permission); |
| 998 | + this.addSystemMessage(`Current permission state: ${Notification.permission}`); |
| 999 | + |
| 1000 | + // If already granted, just update state |
904 | 1001 | if (Notification.permission === 'granted') { |
905 | 1002 | this.notificationsEnabled = true; |
906 | | - this.addSystemMessage('Browser notifications enabled'); |
| 1003 | + this.updateNotificationButtonState(); |
| 1004 | + this.addSystemMessage('Notifications are already enabled'); |
907 | 1005 | return; |
908 | 1006 | } |
909 | 1007 |
|
910 | | - if (Notification.permission !== 'denied') { |
| 1008 | + // If denied, show instruction message |
| 1009 | + if (Notification.permission === 'denied') { |
| 1010 | + this.addSystemMessage('Notifications are blocked. Please enable them in your browser settings and reload the page.'); |
| 1011 | + return; |
| 1012 | + } |
| 1013 | + |
| 1014 | + // For 'default' state (ask to allow), request permission |
| 1015 | + if (Notification.permission === 'default') { |
| 1016 | + this.addSystemMessage('Requesting notification permission...'); |
| 1017 | + |
| 1018 | + // Request permission (this must be user-initiated) |
911 | 1019 | try { |
912 | | - const permission = await Notification.requestPermission(); |
| 1020 | + let permission; |
| 1021 | + |
| 1022 | + // Try different methods for different browsers |
| 1023 | + if (Notification.requestPermission.length === 0) { |
| 1024 | + // Modern Promise-based API |
| 1025 | + permission = await Notification.requestPermission(); |
| 1026 | + } else { |
| 1027 | + // Older callback-based API (fallback for some mobile browsers) |
| 1028 | + permission = await new Promise((resolve) => { |
| 1029 | + Notification.requestPermission(resolve); |
| 1030 | + }); |
| 1031 | + } |
| 1032 | + |
| 1033 | + console.log('Permission result:', permission); |
| 1034 | + this.addSystemMessage(`Permission result: ${permission}`); |
| 1035 | + |
913 | 1036 | if (permission === 'granted') { |
914 | 1037 | this.notificationsEnabled = true; |
915 | | - this.addSystemMessage('Browser notifications enabled'); |
| 1038 | + this.addSystemMessage('Notifications enabled successfully!'); |
| 1039 | + |
| 1040 | + // Test notification |
| 1041 | + setTimeout(() => { |
| 1042 | + this.createNotification('System', 'Notifications are now working!', false); |
| 1043 | + }, 1000); |
| 1044 | + } else if (permission === 'denied') { |
| 1045 | + this.addSystemMessage('Notification permission denied by user'); |
916 | 1046 | } else { |
917 | | - this.addSystemMessage('Browser notifications disabled'); |
| 1047 | + this.addSystemMessage('Notification permission not granted (unknown state)'); |
918 | 1048 | } |
| 1049 | + |
| 1050 | + this.updateNotificationButtonState(); |
| 1051 | + |
919 | 1052 | } catch (error) { |
920 | 1053 | console.error('Error requesting notification permission:', error); |
921 | | - this.addSystemMessage('Failed to request notification permission'); |
| 1054 | + this.addSystemMessage(`Permission request failed: ${error.message}`); |
| 1055 | + |
| 1056 | + // For mobile browsers that don't support requestPermission() |
| 1057 | + if (error.name === 'NotAllowedError') { |
| 1058 | + this.addSystemMessage('NotAllowedError - Permission blocked by browser policy'); |
| 1059 | + } else if (error.message.includes('user gesture')) { |
| 1060 | + this.addSystemMessage('User gesture required - try clicking the button again'); |
| 1061 | + } else { |
| 1062 | + this.addSystemMessage('This may be due to browser restrictions on mobile - try adding to home screen'); |
| 1063 | + } |
922 | 1064 | } |
923 | 1065 | } else { |
924 | | - this.addSystemMessage('Browser notifications blocked'); |
| 1066 | + this.addSystemMessage(`Unexpected permission state: ${Notification.permission}`); |
925 | 1067 | } |
926 | 1068 | } |
927 | 1069 |
|
|
930 | 1072 | return; // Don't show notifications for our own messages |
931 | 1073 | } |
932 | 1074 |
|
| 1075 | + // Double-check permission before creating notification |
| 1076 | + if (Notification.permission !== 'granted') { |
| 1077 | + console.log('Notification permission not granted, skipping notification'); |
| 1078 | + return; |
| 1079 | + } |
| 1080 | + |
933 | 1081 | try { |
934 | 1082 | const title = isReply ? `${sender} replied in RetroChat` : `${sender} in RetroChat`; |
935 | 1083 | const body = text.length > 100 ? text.substring(0, 100) + '...' : text; |
936 | 1084 |
|
937 | | - const notification = new Notification(title, { |
| 1085 | + // Enhanced notification options for better mobile support |
| 1086 | + const options = { |
938 | 1087 | body: body, |
939 | 1088 | tag: 'retro-chat-message', |
| 1089 | + icon: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NiIgaGVpZ2h0PSI5NiIgdmlld0JveD0iMCAwIDk2IDk2Ij4KICA8cmVjdCB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIGZpbGw9IiMwMDAiLz4KICA8dGV4dCB4PSI0OCIgeT0iNTgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiMwMGZmMDAiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiIGZvbnQtc2l6ZT0iMzIiIGZvbnQtd2VpZ2h0PSJib2xkIj5SQzwvdGV4dD4KPC9zdmc+', |
| 1090 | + badge: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MiIgaGVpZ2h0PSI3MiIgdmlld0JveD0iMCAwIDcyIDcyIj4KICA8cmVjdCB3aWR0aD0iNzIiIGhlaWdodD0iNzIiIGZpbGw9IiMwMGZmMDAiLz4KICA8dGV4dCB4PSIzNiIgeT0iNDUiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiMwMDAiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiIGZvbnQtc2l6ZT0iMjQiIGZvbnQtd2VpZ2h0PSJib2xkIj5SQzwvdGV4dD4KPC9zdmc+', |
940 | 1091 | requireInteraction: false, |
941 | | - silent: false |
942 | | - }); |
| 1092 | + silent: false, |
| 1093 | + timestamp: Date.now(), |
| 1094 | + data: { |
| 1095 | + sender: sender, |
| 1096 | + text: text, |
| 1097 | + isReply: isReply, |
| 1098 | + timestamp: Date.now() |
| 1099 | + } |
| 1100 | + }; |
| 1101 | + |
| 1102 | + const notification = new Notification(title, options); |
943 | 1103 |
|
944 | | - // Auto-close notification after 5 seconds |
| 1104 | + // Auto-close notification after 8 seconds (longer for mobile) |
945 | 1105 | setTimeout(() => { |
946 | | - notification.close(); |
947 | | - }, 5000); |
| 1106 | + try { |
| 1107 | + notification.close(); |
| 1108 | + } catch (e) { |
| 1109 | + // Ignore if already closed |
| 1110 | + } |
| 1111 | + }, 8000); |
948 | 1112 |
|
949 | 1113 | // Focus window when notification is clicked |
950 | 1114 | notification.onclick = () => { |
951 | | - window.focus(); |
952 | | - notification.close(); |
| 1115 | + try { |
| 1116 | + window.focus(); |
| 1117 | + notification.close(); |
| 1118 | + |
| 1119 | + // Try to bring the window to foreground on mobile |
| 1120 | + if (document.visibilityState === 'hidden') { |
| 1121 | + // App was backgrounded, try to focus |
| 1122 | + document.addEventListener('visibilitychange', function focusOnceVisible() { |
| 1123 | + if (document.visibilityState === 'visible') { |
| 1124 | + window.focus(); |
| 1125 | + document.removeEventListener('visibilitychange', focusOnceVisible); |
| 1126 | + } |
| 1127 | + }, { once: true }); |
| 1128 | + } |
| 1129 | + } catch (e) { |
| 1130 | + console.log('Error handling notification click:', e); |
| 1131 | + } |
| 1132 | + }; |
| 1133 | + |
| 1134 | + // Handle notification error |
| 1135 | + notification.onerror = (error) => { |
| 1136 | + console.error('Notification error:', error); |
| 1137 | + this.addSystemMessage('Notification failed to display'); |
953 | 1138 | }; |
954 | 1139 |
|
| 1140 | + console.log('Notification created successfully'); |
| 1141 | + |
955 | 1142 | } catch (error) { |
956 | 1143 | console.error('Error creating notification:', error); |
957 | 1144 | console.error('Error details:', error.message); |
| 1145 | + |
| 1146 | + // Provide specific error feedback |
| 1147 | + if (error.name === 'NotAllowedError') { |
| 1148 | + this.addSystemMessage('Notifications blocked by browser'); |
| 1149 | + } else if (error.message.includes('ServiceWorker')) { |
| 1150 | + this.addSystemMessage('Notification failed: Service Worker issue'); |
| 1151 | + } else { |
| 1152 | + this.addSystemMessage('Notification failed: ' + error.message); |
| 1153 | + } |
958 | 1154 | } |
959 | 1155 | } |
960 | 1156 |
|
|
987 | 1183 | this.replyText = document.getElementById('replyText'); |
988 | 1184 | this.cancelReplyButton = document.getElementById('cancelReply'); |
989 | 1185 |
|
| 1186 | + // Notification button element |
| 1187 | + this.notificationButton = document.getElementById('notificationButton'); |
| 1188 | + |
990 | 1189 | // Set the generated nickname in the input field |
991 | 1190 | this.nicknameInput.value = this.nickname; |
992 | 1191 | } |
|
0 commit comments