import { WebHelperExtensionIds } from '@lenovo-software/lsa-clients-common'; import { Logger } from '../common/logger'; class LogQueue { private logEntries: any[]; constructor() { this.logEntries = []; } public pushLogEntry(logCallback?: Function, message?: any): void { if (logCallback && message) { this.logEntries.push({ logCallback: logCallback, message: message }); } } public write(): void { for (let i = 0; i < this.logEntries.length; i++) { let obj = this.logEntries[i]; if (obj.logCallback) { obj.logCallback(obj.message); } } this.flush(); } public flush(): void { this.logEntries = []; } } class ExtensionCommTimeout { private timeoutLength: number; private callback; private timerId: any; constructor(onExpired: Function, timeoutLength: number) { this.timeoutLength = timeoutLength; this.callback = onExpired; this.timerId = null; } public start(): void { this.timerId = setTimeout(this.callback, this.timeoutLength); } public stop(): void { if (this.timerId !== null) { clearTimeout(this.timerId); } } } class ExtensionCommTimeoutController { private timeouts: any[]; private logQueue: LogQueue; private logger = Logger.getInstance(); constructor(logQueue: LogQueue) { this.timeouts = []; this.logQueue = logQueue; } public writeToLog(loggingCallback: Function, message: any): void { if (this.logQueue) this.logQueue.pushLogEntry(loggingCallback, message); } public createCommTimeout(onExpired: Function, timeoutLength: number): void { if (!timeoutLength || timeoutLength <= 0) throw new Error('ExtensionCommTimeoutController.createCommTimeout(): Timeout must be greater than zero.'); if (!onExpired) throw new Error('ExtensionCommTimeoutController.createCommTimeout(): No callback defined.'); let newTimeout = new ExtensionCommTimeout(() => { this.writeToLog( this.logger.logMessage.bind(this.logger), 'ExtensionCommTimeoutController.createCommTimeout(): TIMEOUT OCCURRED' ); this.removeCommTimeout(); onExpired(); }, timeoutLength); newTimeout.start(); this.timeouts.push(newTimeout); this.writeToLog( this.logger.logMessage.bind(this.logger), 'ExtensionCommTimeoutController.createCommTimeout(): timeouts = ' + this.timeouts.length ); } public removeCommTimeout(): void { this.writeToLog( this.logger.logMessage.bind(this.logger), 'ExtensionCommTimeoutController.removeCommTimeout(): timeouts = ' + this.timeouts.length ); if (this.timeouts.length > 0) { let inst = this.timeouts.shift(); inst.stop(); } } public clear(): void { this.writeToLog(this.logger.logMessage.bind(this.logger), 'ExtensionCommTimeoutController.clear()'); while (this.timeouts.length > 0) { this.removeCommTimeout(); } } } class ValObserver { private val: any; constructor() { this.val = null; } public getVal(): any { return this.val; } public setVal(val: any): any { let ret = val !== this.val; this.val = val; return ret; } } class ExtensionComm { constructor() {} private timeoutLength: number = 5000; private foundAcceptance = new ValObserver(); private logQueue = new LogQueue(); private doLogging = true; private logger = Logger.getInstance(); public setCommTimeout(timeoutLength: number): void { this.timeoutLength = timeoutLength; } public getCommTimeout(): number { return this.timeoutLength; } public async sendToWebHelper(obj: any, callback?: Function): Promise { let extensions: any[] = await this.notifyExtensionSelfHosted(); for (let i = 0; i < extensions.length; i++) { //@ts-ignore chrome.runtime.sendMessage(extensions[i], obj, callback); } } public onResolve(timeouts: ExtensionCommTimeoutController, resolve: Function, resolveParams: any): void { if (timeouts) timeouts.clear(); let extIds = resolveParams && Array.isArray(resolveParams) ? resolveParams.length : 0; let conditionChange = this.foundAcceptance.setVal(extIds); if (conditionChange) { if (extIds > 0) resolveParams.forEach((extId: string) => this.writeToLog( this.logger.logMessage.bind(this.logger), "extensionComm.onResolve(): Extension '" + extId + "' accepts this app." ) ); else this.writeToLog( this.logger.logWarning.bind(this.logger), 'extensionComm.onResolve(): No extensions accept this app.' ); this.writeToLogForceFinal(); } else { this.logQueue.flush(); } resolve(resolveParams); } public writeToLog(logCallback?: Function, message?: any): void { this.logQueue.pushLogEntry(logCallback, message); } public writeToLogFinal(logCallback?: Function, message?: any): void { this.writeToLog(logCallback, message); if (this.doLogging) { this.logQueue.write(); this.doLogging = false; } this.logQueue.flush(); } public writeToLogForceFinal(logCallback?: Function, message?: any): void { this.doLogging = true; this.writeToLogFinal(logCallback, message); } public notifyExtensionSelfHosted(): Promise { return new Promise((resolve) => { this.writeToLog(this.logger.logMessage.bind(this.logger), 'extensionComm.notifyExtensionSelfHosted(+)'); if (!chrome.runtime.getManifest) { this.writeToLogFinal( this.logger.logMessage.bind(this.logger), 'extensionComm.notifyExtensionSelfHosted(): chrome.runtime.getManifest undefined.' ); resolve(WebHelperExtensionIds); } let manifestObj = chrome.runtime.getManifest(); if (!manifestObj || !manifestObj.hasOwnProperty('policy_url') || manifestObj.policy_url.length === 0) { this.writeToLogFinal( this.logger.logMessage.bind(this.logger), 'extensionComm.notifyExtensionSelfHosted(): No policy_url detected in manifest.' ); resolve(WebHelperExtensionIds); } this.extensionMessage( { message: 'LSAEnvironmentExt', data: { apiServer: manifestObj.policy_url } }, resolve ); }); } public extensionMessage(messageObj: any, resolve: Function): void { let resolved = false; let responses = WebHelperExtensionIds.length; let timeouts = new ExtensionCommTimeoutController(this.logQueue); this.writeToLog( this.logger.logMessage.bind(this.logger), 'extensionComm.notifyExtensionSelfHosted(): Sending environment notify...' ); for (let i = 0; i < WebHelperExtensionIds.length; i++) { this.writeToLog( this.logger.logMessage.bind(this.logger), 'extensionComm.notifyExtensionSelfHosted(): sending to ' + WebHelperExtensionIds[i] ); timeouts.createCommTimeout(() => { responses--; this.writeToLog( this.logger.logMessage.bind(this.logger), 'extensionComm.notifyExtensionSelfHosted(): (TIMEOUT) responses = ' + responses ); if (responses <= 0 && !resolved) { resolved = true; this.onResolve(timeouts, resolve, []); } }, this.timeoutLength); chrome.runtime.sendMessage(WebHelperExtensionIds[i], messageObj, (response) => { timeouts.removeCommTimeout(); if (chrome.runtime.lastError) { if ( !chrome.runtime.lastError.hasOwnProperty('message') || //@ts-ignore !chrome.runtime.lastError.message.includes('Receiving end does not exist') ) { this.logger.logError( 'extensionComm.notifyExtensionSelfHosted(): ' + JSON.stringify(chrome.runtime.lastError) ); } } if (response && response.data && response.data === 'success') { if (!resolved) { resolved = true; if (response.id) this.onResolve(timeouts, resolve, new Array(response.id)); else this.onResolve(timeouts, resolve, WebHelperExtensionIds); } } responses--; this.writeToLog( this.logger.logMessage.bind(this.logger), 'extensionComm.notifyExtensionSelfHosted(): responses = ' + responses ); if (responses <= 0 && !resolved) { resolved = true; this.onResolve(timeouts, resolve, []); } }); } } } export const extensionComm = new ExtensionComm();