this repo has no description

trying out to connect with just a phoenix simplified websocket client

tasty_chrome/images/icon128.png

This is a binary file and will not be displayed.

tasty_chrome/images/icon16.png

This is a binary file and will not be displayed.

tasty_chrome/images/icon32.png

This is a binary file and will not be displayed.

tasty_chrome/images/icon48.png

This is a binary file and will not be displayed.

+4 -2
tasty_chrome/manifest.json
··· 6 6 "permissions": [ 7 7 "activeTab", 8 8 "storage", 9 - "tabs" 9 + "tabs", 10 + "contextMenus" 10 11 ], 11 12 "host_permissions": [ 12 - "http://localhost:4000/*" 13 + "http://localhost:4000/*", 14 + "ws://localhost:4000/*" 13 15 ], 14 16 "background": { 15 17 "service_worker": "background.js"
+300
tasty_chrome/phoenix_simple.js
··· 1 + /** 2 + * Simplified Phoenix WebSocket Client 3 + * A lightweight alternative to the full Phoenix.js client 4 + */ 5 + 6 + class PhoenixSocket { 7 + constructor(endpoint, opts = {}) { 8 + this.endpoint = endpoint; 9 + this.params = opts.params || {}; 10 + this.timeout = opts.timeout || 10000; 11 + this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000; 12 + this.socket = null; 13 + this.channels = {}; 14 + this.messageCallbacks = []; 15 + this.connected = false; 16 + this.heartbeatTimer = null; 17 + this.pendingHeartbeatRef = null; 18 + this.ref = 0; 19 + 20 + // Bind methods to maintain context 21 + this.handleOpen = this.handleOpen.bind(this); 22 + this.handleMessage = this.handleMessage.bind(this); 23 + this.handleError = this.handleError.bind(this); 24 + this.handleClose = this.handleClose.bind(this); 25 + } 26 + 27 + connect() { 28 + if (this.socket) { 29 + return; 30 + } 31 + 32 + const url = this.buildUrl(); 33 + this.socket = new WebSocket(url); 34 + 35 + this.socket.onopen = this.handleOpen; 36 + this.socket.onmessage = this.handleMessage; 37 + this.socket.onerror = this.handleError; 38 + this.socket.onclose = this.handleClose; 39 + } 40 + 41 + disconnect() { 42 + if (this.socket) { 43 + this.stopHeartbeat(); 44 + this.socket.close(); 45 + this.socket = null; 46 + this.connected = false; 47 + } 48 + } 49 + 50 + handleOpen() { 51 + console.log(`Connected to ${this.endpoint}`); 52 + this.connected = true; 53 + this.startHeartbeat(); 54 + } 55 + 56 + handleMessage(event) { 57 + try { 58 + const message = JSON.parse(event.data); 59 + console.log('Received message:', message); 60 + 61 + // Handle heartbeat responses 62 + if (message.ref === this.pendingHeartbeatRef) { 63 + this.pendingHeartbeatRef = null; 64 + clearTimeout(this.heartbeatTimer); 65 + this.startHeartbeat(); 66 + return; 67 + } 68 + 69 + // Handle channel messages 70 + const { topic, event: eventName, payload, ref } = message; 71 + 72 + // Check if this is a response to a channel join 73 + if (eventName === 'phx_reply' && this.channels[topic]) { 74 + if (payload.status === 'ok') { 75 + this.channels[topic].joined = true; 76 + if (this.channels[topic].onJoin) { 77 + this.channels[topic].onJoin(payload); 78 + } 79 + } else if (payload.status === 'error') { 80 + if (this.channels[topic].onError) { 81 + this.channels[topic].onError(payload); 82 + } 83 + } 84 + } 85 + 86 + // Forward to channel callbacks 87 + if (this.channels[topic] && this.channels[topic].callbacks[eventName]) { 88 + this.channels[topic].callbacks[eventName].forEach(callback => { 89 + callback(payload, ref); 90 + }); 91 + } 92 + 93 + // Forward to general message callbacks 94 + this.messageCallbacks.forEach(callback => { 95 + callback(message); 96 + }); 97 + } catch (error) { 98 + console.error('Error parsing message:', error); 99 + } 100 + } 101 + 102 + handleError(error) { 103 + console.error('WebSocket error:', error); 104 + this.stopHeartbeat(); 105 + } 106 + 107 + handleClose(event) { 108 + console.log(`WebSocket closed: ${event.code} ${event.reason}`); 109 + this.connected = false; 110 + this.stopHeartbeat(); 111 + 112 + // Clean up channels 113 + Object.keys(this.channels).forEach(topic => { 114 + this.channels[topic].joined = false; 115 + }); 116 + } 117 + 118 + channel(topic, params = {}) { 119 + if (!this.channels[topic]) { 120 + this.channels[topic] = { 121 + topic, 122 + params, 123 + joined: false, 124 + callbacks: {}, 125 + onJoin: null, 126 + onError: null 127 + }; 128 + } 129 + 130 + return { 131 + join: () => this.joinChannel(topic), 132 + leave: () => this.leaveChannel(topic), 133 + on: (event, callback) => this.on(topic, event, callback), 134 + push: (event, payload) => this.push(topic, event, payload), 135 + onJoin: (callback) => { this.channels[topic].onJoin = callback; }, 136 + onError: (callback) => { this.channels[topic].onError = callback; } 137 + }; 138 + } 139 + 140 + joinChannel(topic) { 141 + if (!this.channels[topic]) { 142 + throw new Error(`No channel found for topic: ${topic}`); 143 + } 144 + 145 + const ref = this.makeRef(); 146 + const message = { 147 + topic, 148 + event: 'phx_join', 149 + payload: this.channels[topic].params, 150 + ref 151 + }; 152 + 153 + this.send(message); 154 + 155 + return new Promise((resolve, reject) => { 156 + const timeoutId = setTimeout(() => { 157 + reject(new Error('Join timeout')); 158 + }, this.timeout); 159 + 160 + this.channels[topic].onJoin = (payload) => { 161 + clearTimeout(timeoutId); 162 + resolve(payload); 163 + }; 164 + 165 + this.channels[topic].onError = (payload) => { 166 + clearTimeout(timeoutId); 167 + reject(new Error(payload.response?.reason || 'Join failed')); 168 + }; 169 + }); 170 + } 171 + 172 + leaveChannel(topic) { 173 + if (!this.channels[topic]) { 174 + return; 175 + } 176 + 177 + const ref = this.makeRef(); 178 + const message = { 179 + topic, 180 + event: 'phx_leave', 181 + payload: {}, 182 + ref 183 + }; 184 + 185 + this.send(message); 186 + delete this.channels[topic]; 187 + } 188 + 189 + on(topic, event, callback) { 190 + if (!this.channels[topic]) { 191 + throw new Error(`No channel found for topic: ${topic}`); 192 + } 193 + 194 + if (!this.channels[topic].callbacks[event]) { 195 + this.channels[topic].callbacks[event] = []; 196 + } 197 + 198 + this.channels[topic].callbacks[event].push(callback); 199 + 200 + return () => { 201 + this.channels[topic].callbacks[event] = this.channels[topic].callbacks[event].filter( 202 + cb => cb !== callback 203 + ); 204 + }; 205 + } 206 + 207 + push(topic, event, payload = {}) { 208 + if (!this.channels[topic]) { 209 + throw new Error(`No channel found for topic: ${topic}`); 210 + } 211 + 212 + const ref = this.makeRef(); 213 + const message = { 214 + topic, 215 + event, 216 + payload, 217 + ref 218 + }; 219 + 220 + this.send(message); 221 + 222 + return { 223 + receive: (status, callback) => { 224 + const replyEvent = `phx_reply`; 225 + 226 + const removeListener = this.on(topic, replyEvent, (payload, msgRef) => { 227 + if (msgRef === ref && payload.status === status) { 228 + removeListener(); 229 + callback(payload.response); 230 + } 231 + }); 232 + } 233 + }; 234 + } 235 + 236 + onMessage(callback) { 237 + this.messageCallbacks.push(callback); 238 + return () => { 239 + this.messageCallbacks = this.messageCallbacks.filter(cb => cb !== callback); 240 + }; 241 + } 242 + 243 + startHeartbeat() { 244 + this.stopHeartbeat(); 245 + this.heartbeatTimer = setTimeout(() => { 246 + if (this.connected) { 247 + this.pendingHeartbeatRef = this.makeRef(); 248 + this.push('phoenix', 'heartbeat', {}); 249 + } 250 + }, this.heartbeatIntervalMs); 251 + } 252 + 253 + stopHeartbeat() { 254 + clearTimeout(this.heartbeatTimer); 255 + this.heartbeatTimer = null; 256 + this.pendingHeartbeatRef = null; 257 + } 258 + 259 + makeRef() { 260 + this.ref += 1; 261 + return this.ref.toString(); 262 + } 263 + 264 + send(message) { 265 + if (!this.connected) { 266 + console.warn('Tried to send message while disconnected', message); 267 + return false; 268 + } 269 + 270 + try { 271 + this.socket.send(JSON.stringify(message)); 272 + return true; 273 + } catch (error) { 274 + console.error('Error sending message:', error); 275 + return false; 276 + } 277 + } 278 + 279 + buildUrl() { 280 + // Append websocket suffix and params 281 + let url = this.endpoint; 282 + if (!url.endsWith('/websocket')) { 283 + url = `${url}/websocket`; 284 + } 285 + 286 + // Add params as query string 287 + if (Object.keys(this.params).length > 0) { 288 + const params = new URLSearchParams(); 289 + Object.entries(this.params).forEach(([key, value]) => { 290 + params.append(key, value); 291 + }); 292 + url = `${url}?${params.toString()}`; 293 + } 294 + 295 + return url; 296 + } 297 + } 298 + 299 + // Export globally 300 + window.PhoenixSocket = PhoenixSocket;
+1 -1
tasty_chrome/popup.html
··· 192 192 </div> 193 193 </div> 194 194 195 - <script src="phoenix.js"></script> 195 + <script src="phoenix_simple.js"></script> 196 196 <script src="popup.js"></script> 197 197 </body> 198 198 </html>
+101 -23
tasty_chrome/popup.js
··· 18 18 urlInput.value = currentTab.url || ''; 19 19 }); 20 20 21 + // Check for quick bookmark data from context menu 22 + chrome.storage.local.get(['quickBookmark'], (result) => { 23 + if (result.quickBookmark) { 24 + titleInput.value = result.quickBookmark.title || ''; 25 + urlInput.value = result.quickBookmark.url || ''; 26 + // Clear the stored data 27 + chrome.storage.local.remove(['quickBookmark']); 28 + } 29 + }); 30 + 21 31 // Load saved token from storage 22 32 chrome.storage.local.get(['token'], (result) => { 23 33 if (result.token) { ··· 30 40 const token = tokenInput.value.trim(); 31 41 if (token) { 32 42 chrome.storage.local.set({ token }); 43 + console.log('Token saved to storage'); 33 44 } 34 45 }); 35 46 ··· 96 107 message.textContent = ''; 97 108 98 109 try { 99 - // Connect to Phoenix socket 100 - const socket = new Phoenix.Socket("ws://localhost:4000/socket", { 110 + console.log('Connecting to Phoenix socket...'); 111 + 112 + // Connect to Phoenix socket with our simplified client 113 + const socket = new PhoenixSocket("ws://localhost:4000/socket", { 101 114 params: { token } 102 115 }); 103 116 104 117 socket.connect(); 105 118 106 - // Join the client channel 107 - const channel = socket.channel(`bookmark:client:${token}`); 119 + // Create a timeout to handle connection failures 120 + const connectionTimeout = setTimeout(() => { 121 + showMessage('Connection timeout. Server not responding.', 'error'); 122 + resetForm(); 123 + }, 5000); 108 124 109 - channel.join() 110 - .receive("ok", resp => { 111 - console.log("Joined successfully", resp); 112 - sendBookmark(channel); 113 - }) 114 - .receive("error", resp => { 115 - console.error("Unable to join", resp); 116 - showMessage(`Error connecting: ${resp.reason || 'Unauthorized'}`, 'error'); 117 - resetForm(); 118 - }); 125 + // Wait for connection to establish 126 + setTimeout(() => { 127 + if (socket.connected) { 128 + clearTimeout(connectionTimeout); 129 + 130 + // Join the client channel 131 + const channelTopic = `bookmark:client:${token}`; 132 + console.log(`Joining channel: ${channelTopic}`); 133 + const channel = socket.channel(channelTopic); 134 + 135 + try { 136 + // Join the channel 137 + channel.join() 138 + .then(() => { 139 + console.log('Channel joined successfully'); 140 + sendBookmark(socket, channel); 141 + }) 142 + .catch(error => { 143 + console.error('Error joining channel:', error); 144 + showMessage(`Error joining channel: ${error.message}`, 'error'); 145 + resetForm(); 146 + }); 119 147 148 + // Add error handler 149 + channel.onError((resp) => { 150 + console.error('Channel error:', resp); 151 + showMessage(`Channel error: ${resp.reason || 'Unknown error'}`, 'error'); 152 + resetForm(); 153 + }); 154 + } catch (error) { 155 + console.error('Error setting up channel:', error); 156 + showMessage(`Error: ${error.message}`, 'error'); 157 + resetForm(); 158 + } 159 + } else { 160 + clearTimeout(connectionTimeout); 161 + console.error('WebSocket not connected'); 162 + showMessage('Could not establish WebSocket connection', 'error'); 163 + resetForm(); 164 + } 165 + }, 1000); 120 166 } catch (error) { 121 167 console.error('Connection error:', error); 122 168 showMessage(`Connection error: ${error.message}`, 'error'); ··· 124 170 } 125 171 }); 126 172 127 - function sendBookmark(channel) { 173 + function sendBookmark(socket, channel) { 174 + console.log('Preparing to send bookmark data'); 175 + 128 176 const payload = { 129 177 title: titleInput.value, 130 178 url: urlInput.value, ··· 132 180 tags: tags 133 181 }; 134 182 135 - channel.push("bookmark:create", payload) 136 - .receive("ok", resp => { 137 - console.log("Bookmark created", resp); 183 + console.log('Bookmark payload:', payload); 184 + 185 + try { 186 + const pushResult = channel.push('bookmark:create', payload); 187 + 188 + pushResult.receive('ok', (resp) => { 189 + console.log('Bookmark created successfully:', resp); 138 190 showMessage('Bookmark saved successfully!', 'success'); 139 - setTimeout(() => window.close(), 1500); 140 - }) 141 - .receive("error", resp => { 142 - console.error("Failed to create bookmark", resp); 143 - showMessage(`Error: ${formatErrors(resp.errors)}`, 'error'); 191 + 192 + // Close the popup after success 193 + setTimeout(() => { 194 + socket.disconnect(); 195 + window.close(); 196 + }, 1500); 197 + }); 198 + 199 + pushResult.receive('error', (resp) => { 200 + console.error('Failed to create bookmark:', resp); 201 + showMessage(`Error: ${formatErrors(resp)}`, 'error'); 202 + socket.disconnect(); 144 203 resetForm(); 145 204 }); 205 + 206 + // Set a timeout in case we don't get a response 207 + setTimeout(() => { 208 + if (saveButton.disabled) { 209 + console.warn('No response received from server'); 210 + showMessage('No response from server. Your bookmark may or may not have been saved.', 'error'); 211 + socket.disconnect(); 212 + resetForm(); 213 + } 214 + }, 10000); 215 + } catch (error) { 216 + console.error('Error sending bookmark:', error); 217 + showMessage(`Error: ${error.message}`, 'error'); 218 + socket.disconnect(); 219 + resetForm(); 220 + } 146 221 } 147 222 148 223 function formatErrors(errors) { 149 224 if (!errors) return 'Unknown error'; 150 225 226 + if (typeof errors === 'string') return errors; 227 + 151 228 return Object.entries(errors) 152 229 .map(([field, messages]) => `${field}: ${Array.isArray(messages) ? messages.join(', ') : messages}`) 153 230 .join('; '); ··· 156 233 function showMessage(text, type) { 157 234 message.textContent = text; 158 235 message.className = type; 236 + console.log(`Message (${type}): ${text}`); 159 237 } 160 238 161 239 function resetForm() {