Refactor to make it more like the Android app
This commit is contained in:
		
							parent
							
								
									415ab57749
								
							
						
					
					
						commit
						3fac1c3432
					
				
					 9 changed files with 196 additions and 111 deletions
				
			
		|  | @ -1,7 +1,7 @@ | |||
| import {topicUrlJsonPoll, fetchLinesIterator, topicUrl} from "./utils"; | ||||
| 
 | ||||
| class Api { | ||||
|     static async poll(baseUrl, topic) { | ||||
|     async poll(baseUrl, topic) { | ||||
|         const url = topicUrlJsonPoll(baseUrl, topic); | ||||
|         const messages = []; | ||||
|         console.log(`[Api] Polling ${url}`); | ||||
|  | @ -11,7 +11,7 @@ class Api { | |||
|         return messages.sort((a, b) => { return a.time < b.time ? 1 : -1; }); // Newest first
 | ||||
|     } | ||||
| 
 | ||||
|     static async publish(baseUrl, topic, message) { | ||||
|     async publish(baseUrl, topic, message) { | ||||
|         const url = topicUrl(baseUrl, topic); | ||||
|         console.log(`[Api] Publishing message to ${url}`); | ||||
|         await fetch(url, { | ||||
|  | @ -21,4 +21,5 @@ class Api { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| export default Api; | ||||
| const api = new Api(); | ||||
| export default api; | ||||
|  |  | |||
							
								
								
									
										52
									
								
								web/src/app/Connection.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								web/src/app/Connection.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| class Connection { | ||||
|     constructor(wsUrl, subscriptionId, onNotification) { | ||||
|         this.wsUrl = wsUrl; | ||||
|         this.subscriptionId = subscriptionId; | ||||
|         this.onNotification = onNotification; | ||||
|         this.ws = null; | ||||
|     } | ||||
| 
 | ||||
|     start() { | ||||
|         const socket = new WebSocket(this.wsUrl); | ||||
|         socket.onopen = (event) => { | ||||
|             console.log(`[Connection] [${this.subscriptionId}] Connection established`); | ||||
|         } | ||||
|         socket.onmessage = (event) => { | ||||
|             console.log(`[Connection] [${this.subscriptionId}] Message received from server: ${event.data}`); | ||||
|             try { | ||||
|                 const data = JSON.parse(event.data); | ||||
|                 const relevantAndValid = | ||||
|                     data.event === 'message' && | ||||
|                     'id' in data && | ||||
|                     'time' in data && | ||||
|                     'message' in data; | ||||
|                 if (!relevantAndValid) { | ||||
|                     return; | ||||
|                 } | ||||
|                 this.onNotification(this.subscriptionId, data); | ||||
|             } catch (e) { | ||||
|                 console.log(`[Connection] [${this.subscriptionId}] Error handling message: ${e}`); | ||||
|             } | ||||
|         }; | ||||
|         socket.onclose = (event) => { | ||||
|             if (event.wasClean) { | ||||
|                 console.log(`[Connection] [${this.subscriptionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}`); | ||||
|             } else { | ||||
|                 console.log(`[Connection] [${this.subscriptionId}] Connection died`); | ||||
|             } | ||||
|         }; | ||||
|         socket.onerror = (event) => { | ||||
|             console.log(this.subscriptionId, `[Connection] [${this.subscriptionId}] ${event.message}`); | ||||
|         }; | ||||
|         this.ws = socket; | ||||
|     } | ||||
| 
 | ||||
|     cancel() { | ||||
|         if (this.ws !== null) { | ||||
|             this.ws.close(); | ||||
|             this.ws = null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export default Connection; | ||||
							
								
								
									
										36
									
								
								web/src/app/ConnectionManager.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								web/src/app/ConnectionManager.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| import Connection from "./Connection"; | ||||
| 
 | ||||
| export class ConnectionManager { | ||||
|     constructor() { | ||||
|         this.connections = new Map(); | ||||
|     } | ||||
| 
 | ||||
|     refresh(subscriptions, onNotification) { | ||||
|         console.log(`[ConnectionManager] Refreshing connections`); | ||||
|         const subscriptionIds = subscriptions.ids(); | ||||
|         const deletedIds = Array.from(this.connections.keys()).filter(id => !subscriptionIds.includes(id)); | ||||
| 
 | ||||
|         // Create and add new connections
 | ||||
|         subscriptions.forEach((id, subscription) => { | ||||
|             const added = !this.connections.get(id) | ||||
|             if (added) { | ||||
|                 const wsUrl = subscription.wsUrl(); | ||||
|                 const connection = new Connection(wsUrl, id, onNotification); | ||||
|                 this.connections.set(id, connection); | ||||
|                 console.log(`[ConnectionManager] Starting new connection ${id} using URL ${wsUrl}`); | ||||
|                 connection.start(); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         // Delete old connections
 | ||||
|         deletedIds.forEach(id => { | ||||
|             console.log(`[ConnectionManager] Closing connection ${id}`); | ||||
|             const connection = this.connections.get(id); | ||||
|             this.connections.delete(id); | ||||
|             connection.cancel(); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const connectionManager = new ConnectionManager(); | ||||
| export default connectionManager; | ||||
|  | @ -1,8 +1,10 @@ | |||
| import {topicUrl} from "./utils"; | ||||
| import Subscription from "./Subscription"; | ||||
| 
 | ||||
| const LocalStorage = { | ||||
|     getSubscriptions() { | ||||
| export class Repository { | ||||
|     loadSubscriptions() { | ||||
|         console.log(`[Repository] Loading subscriptions from localStorage`); | ||||
| 
 | ||||
|         const subscriptions = {}; | ||||
|         const rawSubscriptions = localStorage.getItem('subscriptions'); | ||||
|         if (rawSubscriptions === null) { | ||||
|  | @ -20,8 +22,12 @@ const LocalStorage = { | |||
|             console.log("LocalStorage", `Unable to deserialize subscriptions: ${e.message}`) | ||||
|             return {}; | ||||
|         } | ||||
|     }, | ||||
|     } | ||||
| 
 | ||||
|     saveSubscriptions(subscriptions) { | ||||
|         return; | ||||
|         console.log(`[Repository] Saving subscriptions ${subscriptions} to localStorage`); | ||||
| 
 | ||||
|         const serializedSubscriptions = Object.keys(subscriptions).map(k => { | ||||
|             const subscription = subscriptions[k]; | ||||
|             return { | ||||
|  | @ -32,6 +38,7 @@ const LocalStorage = { | |||
|         }); | ||||
|         localStorage.setItem('subscriptions', JSON.stringify(serializedSubscriptions)); | ||||
|     } | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| export default LocalStorage; | ||||
| const repository = new Repository(); | ||||
| export default repository; | ||||
|  | @ -6,24 +6,35 @@ export default class Subscription { | |||
|     topic = ''; | ||||
|     notifications = []; | ||||
|     lastActive = null; | ||||
| 
 | ||||
|     constructor(baseUrl, topic) { | ||||
|         this.id = topicUrl(baseUrl, topic); | ||||
|         this.baseUrl = baseUrl; | ||||
|         this.topic = topic; | ||||
|     } | ||||
| 
 | ||||
|     addNotification(notification) { | ||||
|         if (notification.time === null) { | ||||
|             return; | ||||
|             return this; | ||||
|         } | ||||
|         this.notifications.push(notification); | ||||
|         this.lastActive = notification.time; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     addNotifications(notifications) { | ||||
|         notifications.forEach(n => this.addNotification(n)); | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     url() { | ||||
|         return this.id; | ||||
|     } | ||||
| 
 | ||||
|     wsUrl() { | ||||
|         return topicUrlWs(this.baseUrl, this.topic); | ||||
|     } | ||||
| 
 | ||||
|     shortUrl() { | ||||
|         return shortTopicUrl(this.baseUrl, this.topic); | ||||
|     } | ||||
|  |  | |||
							
								
								
									
										52
									
								
								web/src/app/Subscriptions.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								web/src/app/Subscriptions.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| class Subscriptions { | ||||
|     constructor() { | ||||
|         this.subscriptions = new Map(); | ||||
|     } | ||||
| 
 | ||||
|     add(subscription) { | ||||
|         this.subscriptions.set(subscription.id, subscription); | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     get(subscriptionId) { | ||||
|         const subscription = this.subscriptions.get(subscriptionId); | ||||
|         if (subscription === undefined) return null; | ||||
|         return subscription; | ||||
|     } | ||||
| 
 | ||||
|     update(subscription) { | ||||
|         return this.add(subscription); | ||||
|     } | ||||
| 
 | ||||
|     remove(subscriptionId) { | ||||
|         this.subscriptions.delete(subscriptionId); | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     forEach(cb) { | ||||
|         this.subscriptions.forEach((value, key) => cb(key, value)); | ||||
|     } | ||||
| 
 | ||||
|     map(cb) { | ||||
|         return Array.from(this.subscriptions.values()) | ||||
|             .map(subscription => cb(subscription.id, subscription)); | ||||
|     } | ||||
| 
 | ||||
|     ids() { | ||||
|         return Array.from(this.subscriptions.keys()); | ||||
|     } | ||||
| 
 | ||||
|     firstOrNull() { | ||||
|         const first = this.subscriptions.values().next().value; | ||||
|         if (first === undefined) return null; | ||||
|         return first; | ||||
|     } | ||||
| 
 | ||||
|     clone() { | ||||
|         const c = new Subscriptions(); | ||||
|         c.subscriptions = new Map(this.subscriptions); | ||||
|         return c; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export default Subscriptions; | ||||
|  | @ -1,53 +0,0 @@ | |||
| 
 | ||||
| export default class WsConnection { | ||||
|     id = ''; | ||||
|     constructor(subscription, onChange) { | ||||
|         this.id = subscription.id; | ||||
|         this.subscription = subscription; | ||||
|         this.onChange = onChange; | ||||
|         this.ws = null; | ||||
|     } | ||||
|     start() { | ||||
|         const socket = new WebSocket(this.subscription.wsUrl()); | ||||
|         socket.onopen = (event) => { | ||||
|             console.log(this.id, "[open] Connection established"); | ||||
|         } | ||||
|         socket.onmessage = (event) => { | ||||
|             console.log(this.id, `[message] Data received from server: ${event.data}`); | ||||
|             try { | ||||
|                 const data = JSON.parse(event.data); | ||||
|                 const relevantAndValid = | ||||
|                     data.event === 'message' && | ||||
|                     'id' in data && | ||||
|                     'time' in data && | ||||
|                     'message' in data; | ||||
|                 if (!relevantAndValid) { | ||||
|                     return; | ||||
|                 } | ||||
|                 console.log('adding') | ||||
|                 this.subscription.addNotification(data); | ||||
|                 this.onChange(this.subscription); | ||||
|             } catch (e) { | ||||
|                 console.log(this.id, `[message] Error handling message: ${e}`); | ||||
|             } | ||||
|         }; | ||||
|         socket.onclose = (event) => { | ||||
|             if (event.wasClean) { | ||||
|                 console.log(this.id, `[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`); | ||||
|             } else { | ||||
|                 console.log(this.id, `[close] Connection died`); | ||||
|                 // e.g. server process killed or network down
 | ||||
|                 // event.code is usually 1006 in this case
 | ||||
|             } | ||||
|         }; | ||||
|         socket.onerror = (event) => { | ||||
|             console.log(this.id, `[error] ${event.message}`); | ||||
|         }; | ||||
|         this.ws = socket; | ||||
|     } | ||||
|     cancel() { | ||||
|         if (this.ws != null) { | ||||
|             this.ws.close(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue