|  |  |  | /* global log, window, process, URL */ | 
					
						
							|  |  |  | const EventEmitter = require('events'); | 
					
						
							|  |  |  | const nodeFetch = require('node-fetch'); | 
					
						
							|  |  |  | const LokiAppDotNetAPI = require('./loki_app_dot_net_api'); | 
					
						
							|  |  |  | const StubAppDotNetAPI = require('../../integration_test/stubs/stub_app_dot_net_api.js'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class LokiPublicChatFactoryAPI extends EventEmitter { | 
					
						
							|  |  |  |   constructor(ourKey) { | 
					
						
							|  |  |  |     super(); | 
					
						
							|  |  |  |     this.ourKey = ourKey; | 
					
						
							|  |  |  |     this.servers = []; | 
					
						
							|  |  |  |     this.allMembers = []; | 
					
						
							|  |  |  |     // Multidevice states
 | 
					
						
							|  |  |  |     this.primaryUserProfileName = {}; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // MessageReceiver.connect calls this
 | 
					
						
							|  |  |  |   // start polling in all existing registered channels
 | 
					
						
							|  |  |  |   async open() { | 
					
						
							|  |  |  |     await Promise.all(this.servers.map(server => server.open())); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // MessageReceiver.close
 | 
					
						
							|  |  |  |   async close() { | 
					
						
							|  |  |  |     await Promise.all(this.servers.map(server => server.close())); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   static async validServer(serverUrl) { | 
					
						
							|  |  |  |     // test to make sure it's online (and maybe has a valid SSL cert)
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const url = new URL(serverUrl); | 
					
						
							|  |  |  |       // allow .loki (may only need an agent but not sure
 | 
					
						
							|  |  |  |       //              until we have a .loki to test with)
 | 
					
						
							|  |  |  |       process.env.NODE_TLS_REJECT_UNAUTHORIZED = url.host.match(/\.loki$/i) | 
					
						
							|  |  |  |         ? '0' | 
					
						
							|  |  |  |         : '1'; | 
					
						
							|  |  |  |       // FIXME: use proxy when we have open groups that work with proxy
 | 
					
						
							|  |  |  |       await nodeFetch(serverUrl); | 
					
						
							|  |  |  |       process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; | 
					
						
							|  |  |  |       // const txt = await res.text();
 | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |       process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; | 
					
						
							|  |  |  |       log.warn(`failing to created ${serverUrl}`, e.code, e.message); | 
					
						
							|  |  |  |       // bail out if not valid enough
 | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // server getter/factory
 | 
					
						
							|  |  |  |   async findOrCreateServer(serverUrl) { | 
					
						
							|  |  |  |     let thisServer = this.servers.find( | 
					
						
							|  |  |  |       server => server.baseServerUrl === serverUrl | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     if (!thisServer) { | 
					
						
							|  |  |  |       log.info(`LokiAppDotNetAPI creating ${serverUrl}`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (!await this.constructor.validServer(serverUrl)) { | 
					
						
							|  |  |  |         return null; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // after verification then we can start up all the pollers
 | 
					
						
							|  |  |  |       if (process.env.USE_STUBBED_NETWORK) { | 
					
						
							|  |  |  |         thisServer = new StubAppDotNetAPI(this.ourKey, serverUrl); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         thisServer = new LokiAppDotNetAPI(this.ourKey, serverUrl); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const gotToken = await thisServer.getOrRefreshServerToken(); | 
					
						
							|  |  |  |       if (!gotToken) { | 
					
						
							|  |  |  |         log.warn(`Invalid server ${serverUrl}`); | 
					
						
							|  |  |  |         return null; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       log.info(`set token ${thisServer.token} for ${serverUrl}`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       this.servers.push(thisServer); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return thisServer; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   static async getServerTime() { | 
					
						
							|  |  |  |     const url = `${window.getDefaultFileServer()}/loki/v1/time`; | 
					
						
							|  |  |  |     let timestamp = NaN; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const res = await nodeFetch(url); | 
					
						
							|  |  |  |       if (res.ok) { | 
					
						
							|  |  |  |         timestamp = await res.text(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |       return timestamp; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return Number(timestamp); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   static async getTimeDifferential() { | 
					
						
							|  |  |  |     // Get time differential between server and client in seconds
 | 
					
						
							|  |  |  |     const serverTime = await this.getServerTime(); | 
					
						
							|  |  |  |     const clientTime = Math.ceil(Date.now() / 1000); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (Number.isNaN(serverTime)) { | 
					
						
							|  |  |  |       return 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return serverTime - clientTime; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   static async setClockParams() { | 
					
						
							|  |  |  |     // Set server-client time difference
 | 
					
						
							|  |  |  |     const maxTimeDifferential = 30; | 
					
						
							|  |  |  |     const timeDifferential = await this.getTimeDifferential(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     window.clientClockSynced = Math.abs(timeDifferential) < maxTimeDifferential; | 
					
						
							|  |  |  |     return window.clientClockSynced; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // channel getter/factory
 | 
					
						
							|  |  |  |   async findOrCreateChannel(serverUrl, channelId, conversationId) { | 
					
						
							|  |  |  |     const server = await this.findOrCreateServer(serverUrl); | 
					
						
							|  |  |  |     if (!server) { | 
					
						
							|  |  |  |       log.error(`Failed to create server for: ${serverUrl}`); | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return server.findOrCreateChannel(this, channelId, conversationId); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // deallocate resources server uses
 | 
					
						
							|  |  |  |   unregisterChannel(serverUrl, channelId) { | 
					
						
							|  |  |  |     const i = this.servers.findIndex( | 
					
						
							|  |  |  |       server => server.baseServerUrl === serverUrl | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     if (i === -1) { | 
					
						
							|  |  |  |       log.warn(`Tried to unregister from nonexistent server ${serverUrl}`); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const thisServer = this.servers[i]; | 
					
						
							|  |  |  |     if (!thisServer) { | 
					
						
							|  |  |  |       log.warn(`Tried to unregister from nonexistent server ${i}`); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     thisServer.unregisterChannel(channelId); | 
					
						
							|  |  |  |     this.servers.splice(i, 1); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // shouldn't this be scoped per conversation?
 | 
					
						
							|  |  |  |   async getListOfMembers() { | 
					
						
							|  |  |  |     // enable in the next release
 | 
					
						
							|  |  |  |     /* | 
					
						
							|  |  |  |     let members = []; | 
					
						
							|  |  |  |     await Promise.all(this.servers.map(async server => { | 
					
						
							|  |  |  |       await Promise.all(server.channels.map(async channel => { | 
					
						
							|  |  |  |         const newMembers = await channel.getSubscribers(); | 
					
						
							|  |  |  |         members = [...members, ...newMembers]; | 
					
						
							|  |  |  |       })); | 
					
						
							|  |  |  |     })); | 
					
						
							|  |  |  |     const results = members.map(member => { | 
					
						
							|  |  |  |       return { authorPhoneNumber: member.username }; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     */ | 
					
						
							|  |  |  |     return this.allMembers; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // TODO: make this private (or remove altogether) when
 | 
					
						
							|  |  |  |   // we switch to polling the server for group members
 | 
					
						
							|  |  |  |   setListOfMembers(members) { | 
					
						
							|  |  |  |     this.allMembers = members; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async setProfileName(profileName) { | 
					
						
							|  |  |  |     await Promise.all( | 
					
						
							|  |  |  |       this.servers.map(async server => { | 
					
						
							|  |  |  |         await server.setProfileName(profileName); | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async setHomeServer(homeServer) { | 
					
						
							|  |  |  |     await Promise.all( | 
					
						
							|  |  |  |       this.servers.map(async server => { | 
					
						
							|  |  |  |         // this may fail
 | 
					
						
							|  |  |  |         // but we can't create a sql table to remember to retry forever
 | 
					
						
							|  |  |  |         // I think we just silently fail for now
 | 
					
						
							|  |  |  |         await server.setHomeServer(homeServer); | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async setAvatar(url, profileKey) { | 
					
						
							|  |  |  |     await Promise.all( | 
					
						
							|  |  |  |       this.servers.map(async server => { | 
					
						
							|  |  |  |         // this may fail
 | 
					
						
							|  |  |  |         // but we can't create a sql table to remember to retry forever
 | 
					
						
							|  |  |  |         // I think we just silently fail for now
 | 
					
						
							|  |  |  |         await server.setAvatar(url, profileKey); | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = LokiPublicChatFactoryAPI; |