diff --git a/package.json b/package.json index a1147eaf..2dfeb456 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "description": "Reference App to demonstrate Firebolt APIs and Lifecycle", "dependencies": { "@apidevtools/json-schema-ref-parser": "9.1.2", - "@firebolt-js/discovery-sdk": "1.7.0", - "@firebolt-js/manage-sdk": "1.7.0", - "@firebolt-js/sdk": "1.7.0", + "@firebolt-js/discovery-sdk": "1.8.0-next-major.4", + "@firebolt-js/manage-sdk": "1.8.0-next-major.4", + "@firebolt-js/sdk": "1.8.0-next-major.4", "@lightningjs/core": "2.11.0", "@lightningjs/sdk": "5.5.6", "@lightningjs/ui-components": "2.25.1", diff --git a/src/App.js b/src/App.js index 22b1ec38..cdb89940 100644 --- a/src/App.js +++ b/src/App.js @@ -39,6 +39,7 @@ import { withAnnouncer } from '@lightningjs/ui-components'; const Base = withAnnouncer(lng.Application); import Toast, { eventEmitter } from './Toast'; import IntentReader from 'IntentReader'; +const packagejson = require('../package.json'); export default class App extends Base { static _template() { @@ -90,6 +91,10 @@ export default class App extends Base { this.accessibilityCheck(voiceAnnouncement); }); this.toastStates = []; + const sdkVersionFromPackageJson = packagejson.dependencies['@firebolt-js/sdk']; + const pattern = /^([2-9]|\d{2,})\.\d+\.\d+$|^1\.(8|9|\d{2,})\.\d+/; + process.env.IS_BIDIRECTIONAL_SDK = pattern.test(sdkVersionFromPackageJson); + console.log('process.env.IS_BIDIRECTIONAL_SDK----:', process.env.IS_BIDIRECTIONAL_SDK); this.overlayed = false; this.overlayDismissTimer = null; const appUrl = window.location; @@ -126,6 +131,7 @@ export default class App extends Base { process.env.PUBSUB_SUBSCRIBE_TOPIC_SUFFIX = new URLSearchParams(appUrl.search).get('pubSubSubscribeSuffix'); process.env.PUBSUB_PUBLISH_TOPIC_SUFFIX = new URLSearchParams(appUrl.search).get('pubSubPublishSuffix'); process.env.REGION = new URLSearchParams(appUrl.search).get('region'); + process.env.SDK_VERSION = new URLSearchParams(appUrl.search).get('sdkVersion'); if (platform) { process.env.PLATFORM = platform; diff --git a/src/EventInvocation.js b/src/EventInvocation.js index 66dc6e7f..e2246917 100644 --- a/src/EventInvocation.js +++ b/src/EventInvocation.js @@ -26,19 +26,20 @@ import { dereferenceOpenRPC, errorSchemaCheck } from './utils/Utils'; import { MODULE_MAP } from './FireboltExampleInvoker'; import { CONSTANTS } from './constant'; -import Transport from '@firebolt-js/sdk/dist/lib/Transport/index.mjs'; +import Transport from 'Transport'; const Validator = require('jsonschema').Validator; const validator = new Validator(); const eventHandlerMap = new Map(); -const eventHistory = []; +const eventHistoryMap = new Map(); const logger = require('./utils/Logger')('EventInvocation.js'); class EventHandler { - constructor(moduleWithEventName, schemaList) { + constructor(moduleWithEventName, schemaList, skipParsing = false) { this.moduleWithEventName = moduleWithEventName; const event = moduleWithEventName.split('.')[1]; - this.eventName = this.parseEventName(event); + this.eventName = moduleWithEventName; + this.event = skipParsing ? event : this.parseEventName(event); if (process.env.STANDALONE == true) { this.eventSchema = this.getSchema(moduleWithEventName, schemaList); } @@ -52,7 +53,7 @@ class EventHandler { } // Return short event name getEventName() { - return this.eventName; + return this.event; } // Fetch schema from dereferenced RPC using event name getSchema(moduleWithEventName, schemaList) { @@ -77,7 +78,6 @@ class EventHandler { const eventSchemaResponse = this.eventSchemaValidation(eventData); eventDataObject = { eventName: this.eventName, - eventListenerId: this.eventListener.eventListenerId, eventResponse: eventData, eventSchemaResult: eventSchemaResponse, eventTime: new Date(), @@ -85,12 +85,16 @@ class EventHandler { } else { eventDataObject = { eventName: this.eventName, - eventListenerId: this.eventListener.id, eventResponse: eventData, eventTime: new Date(), }; } - eventHistory.push(eventDataObject); + const eventMap = eventHistoryMap.get(this.eventName); + if (!eventMap) { + eventHistoryMap.set(this.eventName, [eventDataObject]); + } else { + eventMap.push(eventDataObject); + } } // Schema validation for resolved event data eventSchemaValidation(eventResponse) { @@ -113,19 +117,211 @@ class EventHandler { } // Return queried number of events from history getEventHistory(numberOfEvents) { - return eventHistory.slice(-numberOfEvents); + return eventHistoryMap.slice(-numberOfEvents); } } -export class EventInvocation { - // This method accepts the message params and return the listener response and schema response - async northBoundEventHandling(message) { - const eventParams = message.params; - const moduleWithEventName = eventParams.event; - const params = eventParams.params; - const [listenerResponse, uniqueListenerKey] = await this.registerEvent(moduleWithEventName, params); - const registrationResponse = {}; +class EventRegistrationInterface { + async clearEventListeners(event) { + try { + checkEventNameFormat(event); + const [sdkType, module, eventMethodWithoutModule, eventName] = this.parseEventNameAndModuleAndSDKType(event); + if (process.env.COMMUNICATION_MODE == CONSTANTS.SDK) { + const resolvedModule = this.getModuleMap(sdkType, module); + if (!resolvedModule.clear || typeof resolvedModule.clear !== 'function') { + throw new Error(`Module- ${module} from sdk- ${module} does not support event de-registration.`); + } + resolvedModule.clear(eventName); + } else if (process.env.COMMUNICATION_MODE == CONSTANTS.TRANSPORT) { + const args = { listen: false }; + if (process.env.IS_BIDIRECTIONAL_SDK === true || (typeof process.env.IS_BIDIRECTIONAL_SDK === 'string' && process.env.IS_BIDIRECTIONAL_SDK.toLowerCase() === 'true')) { + await Transport.request(`${module}.${eventMethodWithoutModule}`, args); + } else { + await Transport.send(module, eventMethodWithoutModule, args); + } + } + return true; + } catch (err) { + logger.error('Error while clearing event listeners: ' + err.message); + const response = { + error: { + code: 'FCAError', + message: 'Error while clearing event listeners: ' + err.message, + }, + }; + return response; + } + } + + /** + * parseEventNameAndModuleAndSDKType + * This method parses the module with event name to extract SDK type, module, event name without module, and formatted event name. + * @param {String} moduleWithEventName - The module with event name in the format 'sdkType_moduleName.onEventName'. + * @returns - array containing sdkType, module, eventNameWithoutModule, and formatted eventName. + * @example + * parseEventNameAndModuleAndSDKType('sdk_foo.onExampleEvent') + * parseEventNameAndModuleAndSDKType('foo.onExampleEvent') + * returns ['firebolt', 'foo', 'onExampleEvent', 'exampleEvent'] + */ + parseEventNameAndModuleAndSDKType(moduleWithEventName) { + let sdkType; + let module; + if (!moduleWithEventName.includes('_')) { + sdkType = CONSTANTS.CORE.toLowerCase(); + module = moduleWithEventName.split('.')[0].toLowerCase(); + } else { + sdkType = moduleWithEventName.split('_')[0].toLowerCase(); + module = moduleWithEventName.split('.')[0].toLowerCase(); + module = module.split('_')[1]; + } + sdkType = process.env.SDK_TYPE ? process.env.SDK_TYPE : sdkType; + const eventMethodWithoutModule = moduleWithEventName.split('.')[1]; + let eventName = eventMethodWithoutModule.slice(2); + eventName = eventName.charAt(0).toLowerCase() + eventName.slice(1); + return [sdkType, module, eventMethodWithoutModule, eventName]; + } + + // Return event history for the provided unique key + getHistory(eventKey, numberOfEvents) { + return eventHandlerMap.get(eventKey).getHistory(numberOfEvents); + } + // Registering the event in Transport mode + async registerEventInTransport(methodName, params) { + const module = methodName.split('.')[0].toLowerCase(); + const method = methodName.split('.')[1]; + const args = Object.assign({ listen: true }, params); + return await Transport.listen(module, method, args); + } + + clearAllListeners(eventHandlerMap) { + logger.info('Clearing registered listeners' + JSON.stringify(Object.fromEntries(eventHandlerMap)), 'clearAllListeners'); + try { + eventHistoryMap.clear(); + if (eventHandlerMap.size >= 1) { + eventHandlerMap.forEach(async (EventHandlerObject, _uniqueListenerKey) => { + // The key in the eventHandlermap is in the format SDK_ModuleName- + const eventNameWithModuleName = EventHandlerObject.moduleWithEventName; + const eventName = EventHandlerObject.event; + const [sdkType, module, eventMethodWithoutModule] = this.parseEventNameAndModuleAndSDKType(eventNameWithModuleName); + logger.info('Unregistered event- ' + eventNameWithModuleName, 'clearAllListeners'); + + // Events are cleared using Firebolt SDK + if (process.env.COMMUNICATION_MODE == CONSTANTS.SDK) { + const resolvedModule = this.getModuleMap(sdkType, module); + if (!resolvedModule.listen || typeof resolvedModule.listen !== 'function') { + throw new Error(`Module- ${module} from sdk- ${module} does not support event de-registration.`); + } + resolvedModule.clear(eventName); + } + // Events are cleared by using Transport layer and thus bypassing SDK + else if (process.env.COMMUNICATION_MODE == CONSTANTS.TRANSPORT) { + const args = { listen: false }; + if (process.env.IS_BIDIRECTIONAL_SDK === true || (typeof process.env.IS_BIDIRECTIONAL_SDK === 'string' && process.env.IS_BIDIRECTIONAL_SDK.toLowerCase() === 'true')) { + await Transport.request(eventNameWithModuleName, args); + } else { + await Transport.send(module, eventMethodWithoutModule, args); + } + } + }); + eventHandlerMap.clear(); + logger.info('After clearing listeners' + JSON.stringify(eventHandlerMap), 'clearAllListeners'); + return CONSTANTS.CLEARED_LISTENERS; + } else { + logger.info('No active Listeners', 'clearAllListeners'); + return CONSTANTS.NO_ACTIVE_LISTENERS; + } + } catch (err) { + logger.error('Error while clearing all event listeners' + err.message, 'clearAllListeners'); + const response = { error: { code: 'FCAError', message: 'Error while clearing all event listeners: ' + err.message } }; + return response; + } + } + + /** + * getModuleMap + * This method parses the module with event name to extract SDK type, module, event name without module, and formatted event name. + * @param {String} sdkType - Sdk type, e.g., 'core', 'extenal', etc. + * @param {String} module - Type of module, e.g., 'account', 'discovery', etc. + * @example + * getModuleMap('core', 'discovery') + * getModuleMap('core', 'discovery') + */ + getModuleMap(sdkType, module) { + if (!MODULE_MAP[sdkType] || !MODULE_MAP[sdkType][module]) { + throw new Error(`Module ${module} from sdk ${sdkType} does not exist.`); + } + return MODULE_MAP[sdkType][module]; + } +} +// 1.0 Implementation +class EventRegistration extends EventRegistrationInterface { + // This method will listen to event and capture the event response after triggering + async registerEvent(moduleWithEventName, params) { + const paramlist = []; + const [sdkType, module] = this.parseEventNameAndModuleAndSDKType(moduleWithEventName); + const [schemaList, invokedSdk] = await dereferenceOpenRPC(sdkType); + const EventHandlerObject = new EventHandler(moduleWithEventName, schemaList); + const eventName = EventHandlerObject.getEventName(); + let eventRegistrationID; + for (const key in params) { + paramlist.push(params[key]); + } + // To prevent uncaught exceptions when it received invalid eventName or module names. + try { + if (process.env.COMMUNICATION_MODE == CONSTANTS.TRANSPORT) { + if (moduleWithEventName.includes('_')) { + moduleWithEventName = moduleWithEventName.split('_')[1]; + } + + const { id, promise } = await this.registerEventInTransport(moduleWithEventName, params); + const result = await promise; + if (result && result.message) { + throw result.message; + } + + // Recieving Event Response + const emit = (eventId, value) => { + if (id == eventId && !CONSTANTS.EXCLUDED_VALUES.includes(value)) { + EventHandlerObject.handleEvent(value); + } + }; + Transport.addEventEmitter(emit); + eventRegistrationID = id; + } else if (process.env.COMMUNICATION_MODE == CONSTANTS.SDK) { + const resolvedModule = this.getModuleMap(sdkType, module); + if (!resolvedModule.listen || typeof resolvedModule.listen !== 'function') { + throw new Error(`Module ${module} from sdk ${module} does not support event listening.`); + } + eventRegistrationID = await resolvedModule.listen(eventName, (result) => { + if (!CONSTANTS.EXCLUDED_VALUES.includes(result)) { + EventHandlerObject.handleEvent(result); + } + }); + } + } catch (err) { + logger.error('No listener Id :' + JSON.stringify(err), 'registerEvent'); + return [err, null]; + } + + // Construct unique key for event handler. Using the event registration ID. + if (eventRegistrationID) { + const eventNameWithoutSDK = moduleWithEventName.includes('_') ? moduleWithEventName.split('_')[1] : moduleWithEventName; + const uniqueListenerKey = eventNameWithoutSDK + '-' + eventRegistrationID; + eventHandlerMap.set(uniqueListenerKey, EventHandlerObject); + return [eventRegistrationID, uniqueListenerKey]; + } else { + logger.error('No listener Id received from SDK', 'registerEvent'); + return ['No listener Id received from SDK', null]; + } + } + + eventListenerResponseHandler(moduleWithEventName, response) { + if (CONSTANTS.EXCLUDED_VALUES.includes(response)) { + response = [`No event listener response received for ${moduleWithEventName}`]; + } + const [listenerResponse, uniqueListenerKey] = response; + const registrationResponse = {}; if (process.env.STANDALONE == true) { registrationResponse['eventName'] = moduleWithEventName; registrationResponse['eventListenerId'] = uniqueListenerKey; @@ -184,172 +380,229 @@ export class EventInvocation { } } - // This method will listen to event and capture the event response after triggering - async registerEvent(moduleWithEventName, params) { - const paramlist = []; - const [sdkType, module] = this.getSdkTypeAndModule(moduleWithEventName); - const [schemaList, invokedSdk] = await dereferenceOpenRPC(sdkType); - const EventHandlerObject = new EventHandler(moduleWithEventName, schemaList); - const eventName = EventHandlerObject.getEventName(); - let eventRegistrationID; - for (const key in params) { - if (params.hasOwnProperty(key)) { - paramlist.push(params[key]); + // Return the event response object for the eventName passed as the param + getEventResponse(message) { + try { + let filteredEventDataObjectList; + const eventName = message.params.event; + if (!eventName) { + throw new Error('Invalid parameters: event name is required'); + } + if (process.env.STANDALONE == true) { + filteredEventDataObjectList = eventHistoryMap.get(eventName.split('-')[0]); + } else { + filteredEventDataObjectList = eventHistoryMap.get(eventName); + } + if (filteredEventDataObjectList && filteredEventDataObjectList.length) { + const eventDataObject = filteredEventDataObjectList[filteredEventDataObjectList.length - 1]; + return eventDataObject; + } else { + const eventDataObject = { [eventName]: null }; + return eventDataObject; } + } catch (err) { + return { error: { code: 'FCAError', message: 'Event response fetch error: ' + err.message } }; } - // To prevent uncaught exceptions when it received invalid eventName or module names. - try { - if (process.env.COMMUNICATION_MODE == CONSTANTS.TRANSPORT) { - if (moduleWithEventName.includes('_')) { - moduleWithEventName = moduleWithEventName.split('_')[1]; - } + } - const { id, promise } = await this.registerEventInTransport(moduleWithEventName, params); - const result = await promise; - if (result && result.message) { - throw result.message; - } + // This method will clear the eventListeners and the event hsitory for the listener as a part of FCA + clearAllListeners() { + return super.clearAllListeners(eventHandlerMap); + } +} - // Recieving Event Response - const emit = (eventId, value) => { - if (id == eventId && !CONSTANTS.EXCLUDED_VALUES.includes(value)) { - EventHandlerObject.handleEvent(value); - } - }; - Transport.addEventEmitter(emit); - eventRegistrationID = id; - } else if (process.env.COMMUNICATION_MODE == CONSTANTS.SDK) { - const resolvedModule = MODULE_MAP[sdkType][module]; - eventRegistrationID = await resolvedModule.listen(eventName, (result) => { - if (!CONSTANTS.EXCLUDED_VALUES.includes(result)) { - EventHandlerObject.handleEvent(result); - } - }); +// 2.0 Implementation +class EventRegistrationV2 extends EventRegistrationInterface { + // This method will listen to an event and capture the event response after triggering + async registerEvent(moduleWithEventName, params) { + const [sdkType, module] = this.parseEventNameAndModuleAndSDKType(moduleWithEventName); + const [schemaList] = await dereferenceOpenRPC(sdkType); + const EventHandlerObject = new EventHandler(moduleWithEventName, schemaList, true); + const eventName = EventHandlerObject.getEventName(); + let eventRegistrationID; + + try { + if (process.env.COMMUNICATION_MODE === CONSTANTS.TRANSPORT) { + eventRegistrationID = await this.handleTransportEvent(moduleWithEventName, eventName, EventHandlerObject); + } else if (process.env.COMMUNICATION_MODE === CONSTANTS.SDK) { + eventRegistrationID = await this.handleSdkEvent(sdkType, module, eventName, moduleWithEventName, EventHandlerObject); } } catch (err) { - logger.error('No listener Id :' + JSON.stringify(err), 'registerEvent'); - return [err, null]; + logger.error(`Error registering event: ${JSON.stringify(err)}`, 'registerEvent'); + return err; } - // Construct unique key for event handler. A UUID can be added to the key to make it more unique. if (eventRegistrationID) { - const eventNameWithoutSDK = moduleWithEventName.includes('_') ? moduleWithEventName.split('_')[1] : moduleWithEventName; - const uniqueListenerKey = eventNameWithoutSDK + '-' + eventRegistrationID; - eventHandlerMap.set(uniqueListenerKey, EventHandlerObject); - return [eventRegistrationID, uniqueListenerKey]; + eventHandlerMap.set(moduleWithEventName, EventHandlerObject); + return eventRegistrationID; + } else { + logger.error('No listener Id received from SDK', 'registerEvent v2'); + return 'No listener Id received from SDK'; } } - clearEventListeners(event) { + // Helper method to handle Transport events + async handleTransportEvent(moduleWithEventName, eventName, EventHandlerObject) { + const event = moduleWithEventName.charAt(0).toLowerCase() + moduleWithEventName.slice(1); + const args = { listen: true }; + const emit = (value) => { + if (!CONSTANTS.EXCLUDED_VALUES.includes(value)) { + EventHandlerObject.handleEvent(value); + } + }; + Transport.subscribe(event, emit); + return await Transport.request(event, args); + } + + // Helper method to handle SDK events + async handleSdkEvent(sdkType, module, eventName, moduleWithEventName, EventHandlerObject) { + const resolvedModule = this.getModuleMap(sdkType, module); + if (!resolvedModule.listen || typeof resolvedModule.listen !== 'function') { + throw new Error(`Module ${module} from sdk ${module} does not support event listening.`); + } + return await resolvedModule.listen(eventName, (result) => { + if (!CONSTANTS.EXCLUDED_VALUES.includes(result)) { + EventHandlerObject.handleEvent(result); + } + }); + } + + // Return the event response object for the eventName passed as the param + getEventResponse(message) { try { - const [sdkType, module] = this.getSdkTypeAndModule(event); - let eventName = event.split('.')[1]; - eventName = eventName.slice(2); - eventName = eventName.charAt(0).toLowerCase() + eventName.slice(1); - if (process.env.COMMUNICATION_MODE == CONSTANTS.SDK) { - MODULE_MAP[sdkType][module].clear(eventName); - } else if (process.env.COMMUNICATION_MODE == CONSTANTS.TRANSPORT) { - const args = Object.assign({ listen: false }); - Transport.send(module, 'on' + eventName[0].toUpperCase() + eventName.substr(1), args); + const eventName = message.params.event; + if (!eventName) { + throw new Error('Invalid parameters: event name is required'); } - return true; + const filteredEvents = eventHistoryMap.get(eventName); + return filteredEvents && filteredEvents.length ? filteredEvents[filteredEvents.length - 1] : { [eventName]: null }; } catch (err) { - logger.error('Error while clearing event listeners: ' + err.message); - const response = { - error: { - code: 'FCAError', - message: 'Error while clearing event listeners: ' + err.message, - }, + return { error: { code: 'FCAError', message: `Event response fetch error: ${err.message}` } }; + } + } + + eventListenerResponseHandler(moduleWithEventName, response) { + const registrationResponse = {}; + registrationResponse['jsonrpc'] = '2.0'; + if (response && Number.isInteger(response) && response > 0) { + registrationResponse['id'] = response; + registrationResponse['result'] = { + listening: true, + event: moduleWithEventName, }; - return response; + eventHandlerMap.get(moduleWithEventName).setEventListener(registrationResponse); + } else if (response && response.hasOwnProperty('listening') && response.listening) { + registrationResponse['jsonrpc'] = '2.0'; + registrationResponse['id'] = null; + registrationResponse['result'] = response; + eventHandlerMap.get(moduleWithEventName).setEventListener(registrationResponse); + } else { + registrationResponse['error'] = response; + registrationResponse['id'] = null; } + return registrationResponse; } // This method will clear the eventListeners and the event hsitory for the listener as a part of FCA clearAllListeners() { - logger.info('Clearing registered listeners' + JSON.stringify(eventHandlerMap), 'clearAllListeners'); - try { - if (eventHandlerMap.size >= 1) { - eventHandlerMap.forEach((EventHandlerObject, uniqueListenerKey) => { - // The key in the eventhHanldermap is in the format SDK_ModuleName- - const eventNameWithModuleName = EventHandlerObject.moduleWithEventName; - const eventName = EventHandlerObject.eventName; - const eventRegistrationID = uniqueListenerKey.split('-')[1]; - const [sdkType, module] = this.getSdkTypeAndModule(eventNameWithModuleName); - logger.info('Unregister event ' + eventNameWithModuleName + ' registration ID ' + eventRegistrationID, 'clearAllListeners'); + return super.clearAllListeners(eventHandlerMap); + } +} - // Events are cleared using Firebolt SDK - if (process.env.COMMUNICATION_MODE == CONSTANTS.SDK) { - MODULE_MAP[sdkType][module].clear(eventName); - } - // Events are cleared by using Transport layer and thus bypassing SDK - else if (process.env.COMMUNICATION_MODE == CONSTANTS.TRANSPORT) { - const args = Object.assign({ listen: false }); - Transport.send(module, 'on' + eventName[0].toUpperCase() + eventName.substr(1), args); - } - }); - eventHandlerMap.clear(); - logger.info('After clearing listeners' + JSON.stringify(eventHandlerMap), 'clearAllListeners'); - return 'Cleared Listeners'; - } else { - logger.info('No active Listeners', 'clearAllListeners'); - return 'No active listeners'; +function checkEventNameFormat(moduleWithEventName) { + if (!moduleWithEventName) { + throw new Error('Invalid parameters: event name is required'); + } + const methodNameRegex = /^(?!.*\.$)[^.].*?\.[^_]*_?[^.]*$/; + if (!methodNameRegex.test(moduleWithEventName)) { + throw new Error(`Invalid event name format: ${moduleWithEventName}, expected format is 'moduleName.onEventName' or sdkType_moduleName.onEventName'`); + } +} + +export class EventInvocation { + constructor() { + this.eventRegistration = this.initializeEventRegistration(); + } + + // Initialize Event Registration based on SDK version + initializeEventRegistration() { + if (process.env.IS_BIDIRECTIONAL_SDK === true || (typeof process.env.IS_BIDIRECTIONAL_SDK === 'string' && process.env.IS_BIDIRECTIONAL_SDK.toLowerCase() === 'true')) { + return new EventRegistrationV2(); + } else { + return new EventRegistration(); + } + } + + async northBoundEventHandling(message) { + try { + if (!message || !message.params || !message.params.event) { + throw new Error('Invalid parameters: event name is required'); } - } catch (err) { - logger.error('Error while clearing all event listeners' + err, 'clearAllListeners'); - const response = { error: { code: 'FCAError', message: 'Error while clearing all event listeners: ' + err.message } }; - return response; + const { event: moduleWithEventName, params } = message.params; + checkEventNameFormat(moduleWithEventName); + const response = await this.eventRegistration.registerEvent(moduleWithEventName, params); + return this.eventRegistration.eventListenerResponseHandler(moduleWithEventName, response); + } catch (error) { + return this.handleError('northBoundEventHandling', error); } } - // Check and assign SDK type from incoming params - getSdkTypeAndModule(moduleWithEventName) { - let sdkType; - let module; - if (!moduleWithEventName.includes('_')) { - sdkType = CONSTANTS.CORE.toLowerCase(); - module = moduleWithEventName.split('.')[0].toLowerCase(); - } else { - sdkType = moduleWithEventName.split('_')[0].toLowerCase(); - module = moduleWithEventName.split('.')[0].toLowerCase(); - module = module.split('_')[1]; + async clearEventListeners(event) { + try { + return await this.eventRegistration.clearEventListeners(event); + } catch (error) { + return this.handleError('clearEventListeners', error); + } + } + + // This method will clear the eventListeners and the event hsitory for the listener as a part of FCA + clearAllListeners() { + try { + return this.eventRegistration.clearAllListeners(); + } catch (error) { + return this.handleError('clearAllListeners', error); + } + } + + // Check and assign SDK type from incoming param + parseEventNameAndModuleAndSDKType(moduleWithEventName) { + try { + return this.eventRegistration.parseEventNameAndModuleAndSDKType(moduleWithEventName); + } catch (error) { + return this.handleError('parseEventNameAndModuleAndSDKType', error); } - sdkType = process.env.SDK_TYPE ? process.env.SDK_TYPE : sdkType; - return [sdkType, module]; } // Return event history for the provided unique key getHistory(eventKey, numberOfEvents) { - return eventHandlerMap.get(eventKey).getHistory(numberOfEvents); + try { + return this.eventRegistration.getHistory(eventKey, numberOfEvents); + } catch (error) { + return this.handleError('getHistory', error); + } } // Return the event response object for the eventName passed as the param getEventResponse(message) { try { - let filteredEventDataObjectList; - const eventName = message.params.event; - if (process.env.STANDALONE == true) { - filteredEventDataObjectList = eventHistory.filter((element) => element.eventListenerId == eventName); - } else { - filteredEventDataObjectList = eventHistory.filter((element) => element.eventListenerId.toString() == eventName.split('-').pop()); - } - if (filteredEventDataObjectList.length) { - const eventDataObject = filteredEventDataObjectList[filteredEventDataObjectList.length - 1]; - return eventDataObject; - } else { - const eventDataObject = { [eventName]: null }; - return eventDataObject; - } - } catch (err) { - return { error: { code: 'FCAError', message: 'Event response fetch error: ' + err.message } }; + return this.eventRegistration.getEventResponse(message); + } catch (error) { + return this.handleError('getEventResponse', error); } } // Registering the event in Transport mode async registerEventInTransport(methodName, params) { - const module = methodName.split('.')[0].toLowerCase(); - const method = methodName.split('.')[1]; - const args = Object.assign({ listen: true }, params); - return await Transport.listen(module, method, args); + try { + return await this.eventRegistration.registerEventInTransport(methodName, params); + } catch (error) { + return this.handleError('registerEventInTransport', error); + } + } + + // Centralized error handling + handleError(methodName, error) { + console.error(`Error in ${methodName}:`, error.message); + return { error: { code: 'FCAError', message: `Error in ${methodName}: ${error.message}` } }; } } diff --git a/src/FireboltExampleInvoker.js b/src/FireboltExampleInvoker.js index 35967101..61af0da3 100644 --- a/src/FireboltExampleInvoker.js +++ b/src/FireboltExampleInvoker.js @@ -109,8 +109,12 @@ export default class FireboltExampleInvoker { return await methodFn(...params); } else if (method.match(/^on[A-Z][a-zA-Z]+$/) && moduleClass.listen) { let id; + let event = method[2].toLowerCase() + method.substr(3); console.log('params:', params); - const event = method[2].toLowerCase() + method.substr(3); + if (process.env.IS_BIDIRECTIONAL_SDK === true || (typeof process.env.IS_BIDIRECTIONAL_SDK === 'string' && process.env.IS_BIDIRECTIONAL_SDK.toLowerCase() === 'true')) { + event = method; + } + if (params.length == 1 && params[0] === true) { id = await moduleClass.listen(event, (e) => { logger.error(e, 'invoke'); diff --git a/src/FireboltTransportInvoker.js b/src/FireboltTransportInvoker.js index c54e08ea..8f6ffb78 100644 --- a/src/FireboltTransportInvoker.js +++ b/src/FireboltTransportInvoker.js @@ -16,9 +16,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import Transport from '@firebolt-js/sdk/dist/lib/Transport'; +import Transport from 'Transport'; const logger = require('./utils/Logger')('FireboltTransportInvoker.js'); let getInvoker; + try { getInvoker = require('../plugins/FireboltExtensionInvoker').getInvoker; } catch (err) { @@ -51,7 +52,10 @@ export default class FireboltTransportInvoker { const invoker = getInvoker(sdk); if (sdk && invoker) { return await invoker.send(module, method, jsonParams); + } else if (process.env.IS_BIDIRECTIONAL_SDK === true || (typeof process.env.IS_BIDIRECTIONAL_SDK === 'string' && process.env.IS_BIDIRECTIONAL_SDK.toLowerCase() === 'true')) { + return await Transport.request(`${module}.${method}`, jsonParams); } else { + // Default to transport return await Transport.send(module, method, jsonParams); } } else { diff --git a/src/LifeCycleHistory.js b/src/LifeCycleHistory.js index 4cd04996..71ae15fc 100644 --- a/src/LifeCycleHistory.js +++ b/src/LifeCycleHistory.js @@ -48,11 +48,35 @@ export default class LifecycleHistory { async init(appInstance = null) { lifecycleValidation = process.env.LIFECYCLE_VALIDATION; const lifecycleModule = await assignModuleCapitalization('Lifecycle'); - await Lifecycle.listen('inactive', this._recordHistory.bind(this, lifecycleModule + '.onInactive')); - await Lifecycle.listen('foreground', this._recordHistory.bind(this, lifecycleModule + '.onForeground')); - Lifecycle.listen('background', this._recordHistory.bind(this, lifecycleModule + '.onBackground')); - Lifecycle.listen('suspended', this._recordHistory.bind(this, lifecycleModule + '.onSuspended')); - Lifecycle.listen('unloading', async (event) => { + const isBidirectionalSdk = String(process.env.IS_BIDIRECTIONAL_SDK).toLowerCase() === 'true'; + const eventNames = isBidirectionalSdk + ? { + inactive: 'onInactive', + foreground: 'onForeground', + background: 'onBackground', + suspended: 'onSuspended', + unloading: 'onUnloading', + navigateTo: 'onNavigateTo', + } + : { + inactive: 'inactive', + foreground: 'foreground', + background: 'background', + suspended: 'suspended', + unloading: 'unloading', + navigateTo: 'navigateTo', + }; + const inactiveEvent = eventNames.inactive; + const foregroundEvent = eventNames.foreground; + const backgroundEvent = eventNames.background; + const suspendedEvent = eventNames.suspended; + const unloadingEvent = eventNames.unloading; + const navigateToEvent = eventNames.navigateTo; + await Lifecycle.listen(inactiveEvent, this._recordHistory.bind(this, lifecycleModule + '.onInactive')); + await Lifecycle.listen(foregroundEvent, this._recordHistory.bind(this, lifecycleModule + '.onForeground')); + Lifecycle.listen(backgroundEvent, this._recordHistory.bind(this, lifecycleModule + '.onBackground')); + Lifecycle.listen(suspendedEvent, this._recordHistory.bind(this, lifecycleModule + '.onSuspended')); + Lifecycle.listen(unloadingEvent, async (event) => { let schemaResult, validationResult; await getschemaValidationDone(lifecycleModule + '.onUnloading', event, 'core').then((res) => { schemaResult = res; @@ -100,7 +124,7 @@ export default class LifecycleHistory { } } // register for Discovery.onNavigateTo event - Discovery.listen('navigateTo', async (event) => { + Discovery.listen(navigateToEvent, async (event) => { logger.info('Printing onNavigate To event received: ' + JSON.stringify(event)); try { diff --git a/src/MediaView.js b/src/MediaView.js index d5e74e5a..81d6e0d1 100644 --- a/src/MediaView.js +++ b/src/MediaView.js @@ -68,7 +68,19 @@ export default class MediaView extends lng.Component { _init() { const p = this.tag('Player'); - Lifecycle.listen('inactive', (event) => { + const isBidirectionalSdk = String(process.env.IS_BIDIRECTIONAL_SDK).toLowerCase() === 'true'; + const eventNames = isBidirectionalSdk + ? { + inactive: 'onInactive', + foreground: 'onForeground', + } + : { + inactive: 'inactive', + foreground: 'foreground', + }; + const inactiveEvent = eventNames.inactive; + const foregroundEvent = eventNames.foreground; + Lifecycle.listen(inactiveEvent, (event) => { if (event.state && this.tag('Row.ToggleInactive').checked) { if (p.isPlaying()) { logger.info('Unpausing video', '_init'); @@ -76,7 +88,7 @@ export default class MediaView extends lng.Component { } } }); - Lifecycle.listen('foreground', (event) => { + Lifecycle.listen(foregroundEvent, (event) => { if (event.state && this.tag('Row.ToggleInactive').checked) { const p = this.tag('Player'); if (!p.isPlaying()) { diff --git a/src/constant.js b/src/constant.js index e6f28bd3..e7ea8bfe 100644 --- a/src/constant.js +++ b/src/constant.js @@ -194,4 +194,6 @@ export const CONSTANTS = { SLA_VALIDATION_INTENT: 'sla-validation', OPENRPC_URL: 'https://rdkcentral.github.io/firebolt/requirements/latest/specifications/firebolt-open-rpc.json', DEFAULT_SLA: 300, + CLEARED_LISTENERS: 'Cleared Listeners', + NO_ACTIVE_LISTENERS: 'No active listeners', }; diff --git a/src/pubsub/handlers/ClearEventListeners.js b/src/pubsub/handlers/ClearEventListeners.js index 10a19de0..fc959143 100644 --- a/src/pubsub/handlers/ClearEventListeners.js +++ b/src/pubsub/handlers/ClearEventListeners.js @@ -28,7 +28,7 @@ export default class ClearEventListeners extends BaseHandler { async handle(message) { const eventInvokerInfo = new EventInvocation(); - const validationReport = eventInvokerInfo.clearAllListeners(); + const validationReport = await eventInvokerInfo.clearAllListeners(); return JSON.stringify({ report: validationReport }); } } diff --git a/src/pubsub/handlers/RegisterEventHandler.js b/src/pubsub/handlers/RegisterEventHandler.js index c60cb539..bbbb6e3b 100644 --- a/src/pubsub/handlers/RegisterEventHandler.js +++ b/src/pubsub/handlers/RegisterEventHandler.js @@ -40,6 +40,7 @@ export default class RegisterEventHandler extends BaseHandler { if (message.context) { process.env.COMMUNICATION_MODE = message.context.communicationMode; } + process.env.IS_NOT_SUPPORTED_API = message.isNotSupportedApi; const eventInvokerInfo = new EventInvocation(); let sdkType; if (!message.params.event.includes('_')) { diff --git a/src/pubsub/handlers/clearEventHandler.js b/src/pubsub/handlers/clearEventHandler.js index 94082477..7d971fd3 100644 --- a/src/pubsub/handlers/clearEventHandler.js +++ b/src/pubsub/handlers/clearEventHandler.js @@ -38,7 +38,7 @@ export default class clearEventHandler extends BaseHandler { process.env.SDK_TYPE = sdkType; } try { - const validationReport = eventInvokerInfo.clearEventListeners(message.params.event); + const validationReport = await eventInvokerInfo.clearEventListeners(message.params.event); return JSON.stringify({ report: validationReport }); } catch (e) { const result = { diff --git a/test/jest.config.js b/test/jest.config.js index 89d1d76b..63ca2dad 100644 --- a/test/jest.config.js +++ b/test/jest.config.js @@ -36,6 +36,7 @@ module.exports = { '^CensorData$': '/../src/source/censorData.json', '^RunTestHandler$': '/../src/pubsub/handlers/RunTestHandler.js', }, + resolver: '/jest.transport-resolver.js', transform: { '^.+\\.[tj]s$': 'babel-jest', '^.+\\.mjs$': 'babel-jest', diff --git a/test/jest.setup.js b/test/jest.setup.js index a1bc9b1b..9f0bfb2e 100644 --- a/test/jest.setup.js +++ b/test/jest.setup.js @@ -183,6 +183,8 @@ jest.mock('../src/constant', () => { SLA_VALIDATION_INTENT: 'sla-validation', OPENRPC_URL: 'https://rdkcentral.github.io/firebolt/requirements/latest/specifications/firebolt-open-rpc.json', DEFAULT_SLA: 300, + CLEARED_LISTENERS: 'Cleared Listeners', + NO_ACTIVE_LISTENERS: 'No active listeners', }; return { CONSTANTS }; diff --git a/test/jest.transport-resolver.js b/test/jest.transport-resolver.js new file mode 100644 index 00000000..f4422eef --- /dev/null +++ b/test/jest.transport-resolver.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const path = require('path'); + +module.exports = (request, options) => { + const defaultResolve = options.defaultResolver; + // Check if the module to resolve is 'Transport' + if (request === 'Transport') { + try { + // Get the list of files in the specified directory + const files = fs.readdirSync('node_modules/@firebolt-js/sdk/dist/lib/'); + // If the 'Gateway' file exists, then the sdk version is 2.0.0 or later, so resolve to 'Gateway' path + if (files.includes('Gateway')) { + console.log('Using Gateway from @firebolt-js/sdk'); + return path.resolve(__dirname, '../node_modules/@firebolt-js/sdk/dist/lib/Gateway/index.mjs'); + } else { + console.log('Using Transport from @firebolt-js/sdk'); + return path.resolve(__dirname, '../node_modules/@firebolt-js/sdk/dist/lib/Transport/index.mjs'); + } + } catch (err) { + console.error(`Error resolving files: ${err.message}`); + return defaultResolve(request, options); + } + } + return defaultResolve(request, options); +}; diff --git a/test/unit/EventInvocation.test.js b/test/unit/EventInvocation.test.js index b30fc89c..f1eee5c5 100644 --- a/test/unit/EventInvocation.test.js +++ b/test/unit/EventInvocation.test.js @@ -18,7 +18,7 @@ import { EventInvocation } from '../../src/EventInvocation'; import { MODULE_MAP } from '../../src/FireboltExampleInvoker'; -import Transport from '@firebolt-js/sdk/dist/lib/Transport/index.mjs'; +import Transport from 'Transport'; const schemaList = { openrpc: '1.2.4', @@ -401,12 +401,24 @@ const schemaList = { ], }; +process.env.IS_BIDIRECTIONAL_SDK = false; + // Mocking $abc library and its functions const mockFireboltExampleInvoker = { invoke: () => {}, }; +// Mock the logger module +jest.mock('../../src/utils/Logger', () => { + const loggerMock = { + error: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; + return jest.fn(() => loggerMock); +}); + jest.mock('../../src/utils/Utils', () => { const originalUtils = jest.requireActual('../../src/utils/Utils'); return { @@ -470,21 +482,6 @@ jest.mock('../../src/FireboltExampleInvoker', () => { }; }); -jest.mock('../../node_modules/@firebolt-js/sdk/dist/lib/Transport/index.mjs', () => { - let callId = 0; - callId++; - return { - listen: jest.fn().mockImplementation(() => { - console.log('Returning promise and id'); - const result = { id: callId, promise: Promise.resolve('success') }; - console.log('Returning result: ' + JSON.stringify(result)); - return result; - }), - send: jest.fn(), - addEventEmitter: jest.fn(), - }; -}); - jest.mock('@firebolt-js/sdk', () => { return { Lifecycle: { @@ -562,13 +559,74 @@ describe('EventInvocation', () => { }); }); describe('northBoundEventHandling and registerEvent', () => { - let eventInvocation; + let actualVersion; beforeAll(() => { jest.clearAllMocks(); + jest.resetModules(); console.log('initializing eventInvocation'); process.env.COMMUNICATION_MODE = 'SDK'; eventInvocation = new EventInvocation(); }); + beforeEach(() => { + actualVersion = process.env.IS_BIDIRECTIONAL_SDK; + }); + afterEach(() => { + process.env.IS_BIDIRECTIONAL_SDK = actualVersion; + }); + let eventInvocation; + test('validate EventInvocation method and throw error for invalid event', async () => { + const eventParams = { params: { event: 'onmodulechanged' } }; + const error = { + error: { + code: 'FCAError', + message: "Error in northBoundEventHandling: Invalid event name format: onmodulechanged, expected format is 'moduleName.onEventName' or sdkType_moduleName.onEventName'", + }, + }; + const result = await eventInvocation.northBoundEventHandling(eventParams); + expect(result).toStrictEqual(error); + }); + test('validate EventInvocation method and throw error for no event name', async () => { + const eventParams = { params: { event: null } }; + const error = { + error: { + code: 'FCAError', + message: 'Error in northBoundEventHandling: Invalid parameters: event name is required', + }, + }; + const result = await eventInvocation.northBoundEventHandling(eventParams); + expect(result).toStrictEqual(error); + }); + test('validate EventInvocation method and throw error for params', async () => { + const eventParams = { params: null }; + const error = { + error: { + code: 'FCAError', + message: 'Error in northBoundEventHandling: Invalid parameters: event name is required', + }, + }; + const result = await eventInvocation.northBoundEventHandling(eventParams); + expect(result).toStrictEqual(error); + }); + test('validate EventInvocation method and throw error module_map method not found', async () => { + const eventParams = { params: { event: 'mocksdk.mockeventmodule1' } }; + const error = { + jsonrpc: '2.0', + id: null, + error: 'Module mocksdk from sdk core does not exist.', + }; + const result = await eventInvocation.northBoundEventHandling(eventParams); + expect(result.error.message).toBe(error.error); + }); + test('validate EventInvocation method and throw error module_map module not found', async () => { + const eventParams = { params: { event: 'mocksdk1.mockeventmodule' } }; + const error = { + jsonrpc: '2.0', + id: null, + error: 'Module mocksdk1 from sdk core does not exist.', + }; + const result = await eventInvocation.northBoundEventHandling(eventParams); + expect(result.error.message).toBe(error.error); + }); test('validate EventInvocation method with communicationMode SDK', async () => { const eventParams = { params: { event: 'mocksdk_mockmodule.onmodulechanged' } }; const expectedResponse = { @@ -582,8 +640,9 @@ describe('EventInvocation', () => { const result = await eventInvocation.northBoundEventHandling(eventParams); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); expect(MODULE_MAP.mocksdk.mockmodule.listen).toHaveBeenCalled(); - expect(result.id).toBe(expectedResponse.id); + expect(result.id).toBeGreaterThan(0); expect(result.result).not.toBeNull(); + expect(result.result).toStrictEqual(expectedResponse.result); }); test('should fail if not supported api returns a valid response and not error object', async () => { @@ -645,6 +704,7 @@ describe('EventInvocation', () => { await eventInvocation.getEventResponse(message); }); + // Check on how to mock the Gateway from firebolt v2 and use it in the test test('validate EventInvocation method with communicationMode Transport', async () => { process.env.COMMUNICATION_MODE = 'Transport'; const eventParams = { params: { event: 'mocksdk_mockmodule.onmodulechanged' } }; @@ -656,62 +716,158 @@ describe('EventInvocation', () => { }, id: 1, }; - - const result = await eventInvocation.northBoundEventHandling(eventParams); - console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(Transport.listen).toHaveBeenCalled(); - expect(result.id).toBe(expectedResponse.id); - expect(result.result).toStrictEqual(expectedResponse.result); + let eventtInvocationImport, transport; + if (Transport.request == null) { + // v1 events + jest.mock('../../node_modules/@firebolt-js/sdk/dist/lib/Transport/index.mjs', () => { + let callId = 0; + callId++; + return { + listen: jest.fn().mockImplementation(() => { + console.log('Returning promise and id'); + const result = { id: callId, promise: Promise.resolve('success') }; + console.log('Returning result: ' + JSON.stringify(result)); + return result; + }), + send: jest.fn(), + addEventEmitter: jest.fn(), + }; + }); + process.env.IS_BIDIRECTIONAL_SDK = false; + // re-import the modules after mocking it + eventtInvocationImport = require('../../src/EventInvocation'); + transport = require('../../node_modules/@firebolt-js/sdk/dist/lib/Transport/index.mjs'); + } else { + // v2 events + jest.doMock('../../node_modules/@firebolt-js/sdk/dist/lib/Gateway/index.mjs', () => { + let callId = 0; + callId++; + return { + request: jest.fn().mockImplementation(() => { + console.log('Returning promise and id'); + const result = { id: callId, promise: Promise.resolve('success') }; + console.log('Returning result: ' + JSON.stringify(result)); + return result; + }), + subscribe: jest.fn().mockImplementation((eventName, callBack) => { + console.log('subscribe function called for event: ' + eventName); + }), + }; + }); + process.env.IS_BIDIRECTIONAL_SDK = true; + // re-import the modules after mocking it + eventtInvocationImport = require('../../src/EventInvocation'); + transport = require('../../node_modules/@firebolt-js/sdk/dist/lib/Gateway/index.mjs'); + } + // re-import the modules after mocking it + const eventInvocation = eventtInvocationImport.EventInvocation; + const eventInvocationTest = new eventInvocation(); + const result = await eventInvocationTest.northBoundEventHandling(eventParams); + if (transport.request == null) { + expect(transport.listen).toHaveBeenCalled(); + } else { + expect(transport.request).toHaveBeenCalled(); + expect(transport.subscribe).toHaveBeenCalled(); + } }); }); describe('clearEventListeners', () => { let eventInvocation; + let actualVersion; beforeAll(() => { jest.clearAllMocks(); + jest.resetModules(); console.log('initializing eventInvocation'); process.env.COMMUNICATION_MODE = 'SDK'; eventInvocation = new EventInvocation(); }); - test('should call clear on the eventName and return true', () => { + beforeEach(() => { + actualVersion = process.env.IS_BIDIRECTIONAL_SDK; + }); + afterEach(() => { + process.env.IS_BIDIRECTIONAL_SDK = actualVersion; + }); + test('should call clear on the eventName and return true', async () => { const event = 'mocksdk_mockmodule.onmodulechanged'; - const result = eventInvocation.clearEventListeners(event); + const result = await eventInvocation.clearEventListeners(event); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); expect(result).toBe(true); expect(MODULE_MAP.mocksdk.mockmodule.clear).toHaveBeenCalledWith(event.split('.')[1].slice(2)); }); - test('should return error on issues with event name', () => { + test('should return error on issues with event name', async () => { const event = 'onmodulechanged'; - const result = eventInvocation.clearEventListeners(event); + const result = await eventInvocation.clearEventListeners(event); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); expect(result.error).toBeDefined(); expect(result.error.code).toBe('FCAError'); expect(result.error.message).toBeDefined(); }); - test('should return error on issues with event name', () => { + test('should return error on issues with event name', async () => { const event = 'mockmodule.modulechanged'; - const result = eventInvocation.clearEventListeners(event); + const result = await eventInvocation.clearEventListeners(event); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); expect(result.error).toBeDefined(); expect(result.error.code).toBe('FCAError'); expect(result.error.message).toBeDefined(); }); - test('should clear listener by sending listen false when communication mode is transport', () => { + test('should clear listener by sending listen false when communication mode is transport', async () => { const event = 'mocksdk_mockmodule.onmodulechanged'; process.env.COMMUNICATION_MODE = 'Transport'; - const result = eventInvocation.clearEventListeners(event); + let eventInvocationImport, transportImport; + if (Transport.request == null) { + // v1 events + jest.mock('../../node_modules/@firebolt-js/sdk/dist/lib/Transport/index.mjs', () => { + let callId = 0; + callId++; + return { + send: jest.fn(), + }; + }); + process.env.IS_BIDIRECTIONAL_SDK = false; + // re-import the modules after mocking it + eventInvocationImport = require('../../src/EventInvocation'); + transportImport = require('Transport'); + } else { + // v2 events + jest.doMock('../../node_modules/@firebolt-js/sdk/dist/lib/Gateway/index.mjs', () => { + let callId = 0; + callId++; + return { + request: jest.fn().mockImplementation(() => { + console.log('Returning promise and id'); + const result = { id: callId, promise: Promise.resolve('success') }; + console.log('Returning result: ' + JSON.stringify(result)); + return result; + }), + }; + }); + process.env.IS_BIDIRECTIONAL_SDK = true; + // re-import the modules after mocking it + eventInvocationImport = require('../../src/EventInvocation'); + transportImport = require('Transport'); + } + const eventInvocation = eventInvocationImport.EventInvocation; + const eventInvocationTest = new eventInvocation(); + const result = await eventInvocationTest.clearEventListeners(event); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); expect(result).toBe(true); - expect(Transport.send).toHaveBeenCalledWith('mockmodule', 'onModulechanged', { - listen: false, - }); + if (Transport.request == null) { + expect(transportImport.send).toHaveBeenCalledWith('mockmodule', 'onmodulechanged', { + listen: false, + }); + } else { + expect(transportImport.request).toHaveBeenCalledWith('mockmodule.onmodulechanged', { + listen: false, + }); + } }); }); - describe('getSdkTypeAndModule', () => { + describe('parseEventName', () => { let eventInvocation; beforeAll(async () => { jest.clearAllMocks(); @@ -719,27 +875,19 @@ describe('EventInvocation', () => { eventInvocation = new EventInvocation(); }); test('should return CORE as sdkType when only module.method is passed', () => { - const [sdkType, module] = eventInvocation.getSdkTypeAndModule('mockModule.mockMethod'); + const [sdkType, module, eventMethodWithoutModule, eventName] = eventInvocation.parseEventNameAndModuleAndSDKType('mockModule.onMockMethod'); expect(sdkType).toBe('core'); expect(module).toBe('mockmodule'); + expect(eventMethodWithoutModule).toBe('onMockMethod'); + expect(eventName).toBe('mockMethod'); }); test('should return provided sdk as sdkType when sdk_module.method is passed', () => { - const [sdkType, module] = eventInvocation.getSdkTypeAndModule('mocksdk_mockModule.mockMethod'); + const [sdkType, module, eventMethodWithoutModule, eventName] = eventInvocation.parseEventNameAndModuleAndSDKType('mocksdk_mockModule.onMockMethod'); expect(sdkType).toBe('mocksdk'); expect(module).toBe('mockmodule'); - }); - - test('will return core as sdktype and module as method if only method is passed', () => { - const [sdkType, module] = eventInvocation.getSdkTypeAndModule('mockMethod'); - expect(sdkType).toBe('core'); - expect(module).toBe('mockmethod'); - }); - - test('will return module as empty and core as sdktype if no input is provided', () => { - const [sdkType, module] = eventInvocation.getSdkTypeAndModule(''); - expect(sdkType).toBe('core'); - expect(module).toBe(''); + expect(eventMethodWithoutModule).toBe('onMockMethod'); + expect(eventName).toBe('mockMethod'); }); }); @@ -766,18 +914,16 @@ describe('EventInvocation', () => { test('should return event object with response - single event fired', () => { currentCallback({ foo: 'bar1' }); - const message = { params: { event: 'accessibility.onClosedCaptionsSettingsChanged-6' } }; + const message = { params: { event: 'mocksdk_mockmodule.onmodulechanged' } }; const expectedResponse = { - eventName: 'modulechanged', - eventListenerId: 6, + eventName: 'mocksdk_mockmodule.onmodulechanged', eventResponse: { foo: 'bar1' }, - eventTime: '2023-05-10T14:27:35.806Z', + eventTime: '2025-03-20T09:45:10.557Z', }; result = eventInvocation.getEventResponse(message); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); expect(result).toMatchObject({ eventName: expectedResponse.eventName, - eventListenerId: expectedResponse.eventListenerId, eventResponse: expectedResponse.eventResponse, }); expect(result.eventTime).toBeDefined(); @@ -786,10 +932,9 @@ describe('EventInvocation', () => { test('should return event object with last response - multiple events fired', () => { currentCallback({ foo: 'bar2' }); - const message = { params: { event: 'accessibility.onClosedCaptionsSettingsChanged-6' } }; + const message = { params: { event: 'mocksdk_mockmodule.onmodulechanged' } }; const expectedResponse = { - eventName: 'modulechanged', - eventListenerId: 6, + eventName: 'mocksdk_mockmodule.onmodulechanged', eventResponse: { foo: 'bar2' }, eventTime: '2023-05-10T14:18:18.347Z', }; @@ -797,7 +942,6 @@ describe('EventInvocation', () => { console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); expect(result).toMatchObject({ eventName: expectedResponse.eventName, - eventListenerId: expectedResponse.eventListenerId, eventResponse: expectedResponse.eventResponse, }); expect(result.eventTime).toBeDefined(); diff --git a/test/unit/IntentReader.test.js b/test/unit/IntentReader.test.js index 1a06e1ff..6fd928fa 100644 --- a/test/unit/IntentReader.test.js +++ b/test/unit/IntentReader.test.js @@ -102,6 +102,16 @@ jest.mock('../../src/FireboltTransportInvoker', () => { }; }); +// Mock the logger module +jest.mock('../../src/utils/Logger', () => { + const loggerMock = { + error: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; + return jest.fn(() => loggerMock); +}); + jest.mock('../../src/Toast', () => { const eventEmitter = { emit: jest.fn(), diff --git a/test/unit/LifeCycleHistory.test.js b/test/unit/LifeCycleHistory.test.js index 1e9f7c41..b6b47fad 100644 --- a/test/unit/LifeCycleHistory.test.js +++ b/test/unit/LifeCycleHistory.test.js @@ -34,14 +34,6 @@ jest.mock('../../src/utils/Utils', () => { }; }); -jest.mock('@firebolt-js/sdk/dist/lib/Transport/index.mjs', () => { - return { - send: () => { - return {}; - }, - }; -}); - jest.mock('../../src/FireboltExampleInvoker', () => { return { get: () => { @@ -50,20 +42,22 @@ jest.mock('../../src/FireboltExampleInvoker', () => { }; }); -jest.mock('../../src/FireboltTransportInvoker', () => { - return { - get: () => { - return mockFireboltTransportInvoker; - }, - }; -}); - jest.mock('../../src/pubsub/handlers/RegisterProviderHandler', () => { return jest.fn().mockImplementation(() => ({ handle: jest.fn().mockResolvedValue(JSON.stringify({ report: 'registered' })), })); }); +// Mock the logger module +jest.mock('../../src/utils/Logger', () => { + const loggerMock = { + error: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; + return jest.fn(() => loggerMock); +}); + const mockCallBackRes = { state: 'foreground', source: '' }; jest.mock('@firebolt-js/sdk', () => { return { diff --git a/test/unit/MethodFilters.test.js b/test/unit/MethodFilters.test.js index ba53f649..26f127cf 100644 --- a/test/unit/MethodFilters.test.js +++ b/test/unit/MethodFilters.test.js @@ -35,6 +35,16 @@ jest.mock('@firebolt-js/sdk/dist/lib/Transport/index.mjs', () => { }; }); +// Mock the logger module +jest.mock('../../src/utils/Logger', () => { + const loggerMock = { + error: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; + return jest.fn(() => loggerMock); +}); + jest.mock('@firebolt-js/sdk', () => { return { Accessibility: {}, diff --git a/test/unit/MethodInvoker.test.js b/test/unit/MethodInvoker.test.js index 440ba14b..d8ba6487 100644 --- a/test/unit/MethodInvoker.test.js +++ b/test/unit/MethodInvoker.test.js @@ -227,6 +227,16 @@ jest.mock('../../src/FireboltExampleInvoker', () => { }; }); +// Mock the logger module +jest.mock('../../src/utils/Logger', () => { + const loggerMock = { + error: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; + return jest.fn(() => loggerMock); +}); + jest.mock('../../src/utils/Utils', () => { const originalUtils = jest.requireActual('../../src/utils/Utils'); return { diff --git a/test/unit/RunTestHandler.test.js b/test/unit/RunTestHandler.test.js index 9fa38a66..31d409e5 100644 --- a/test/unit/RunTestHandler.test.js +++ b/test/unit/RunTestHandler.test.js @@ -52,6 +52,16 @@ jest.mock('../../src/utils/Utils', () => { }; }); +// Mock the logger module +jest.mock('../../src/utils/Logger', () => { + const loggerMock = { + error: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; + return jest.fn(() => loggerMock); +}); + const methodsToBeExcluded = 'Mock.Method,module.mockMethod,event.onMockEvent'; describe('RunTestHandler', () => { diff --git a/test/unit/Utils.test.js b/test/unit/Utils.test.js index b8ae0337..0e2272c1 100644 --- a/test/unit/Utils.test.js +++ b/test/unit/Utils.test.js @@ -37,6 +37,17 @@ jest.mock('@firebolt-js/sdk/dist/lib/Transport/index.mjs', () => { }, }; }); + +// Mock the logger module +jest.mock('../../src/utils/Logger', () => { + const loggerMock = { + error: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; + return jest.fn(() => loggerMock); +}); + describe('Utils test cases', () => { test('validate dereferenceOpenRPC', async () => { await dereferenceOpenRPC('core'); diff --git a/test/unit/lifecycleRecordHandler.test.js b/test/unit/lifecycleRecordHandler.test.js index 90df2a87..570edc34 100644 --- a/test/unit/lifecycleRecordHandler.test.js +++ b/test/unit/lifecycleRecordHandler.test.js @@ -31,6 +31,16 @@ jest.mock('../../src/LifeCycleHistory', () => { }; }); +// Mock the logger module +jest.mock('../../src/utils/Logger', () => { + const loggerMock = { + error: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; + return jest.fn(() => loggerMock); +}); + let LifecycleRecordHandler; describe('LifecycleRecordHandler Test Case', () => { diff --git a/test/unit/test_runner.test.js b/test/unit/test_runner.test.js index 91a17ff7..43fa8ade 100644 --- a/test/unit/test_runner.test.js +++ b/test/unit/test_runner.test.js @@ -18,6 +18,7 @@ import { Test_Runner } from '../../src/Test_Runner'; import { CONSTANTS } from '../../src/constant'; +import Transport from 'Transport'; const Validator = require('jsonschema').Validator; const { v4: uuidv4 } = require('uuid'); @@ -510,14 +511,20 @@ jest.mock('../../src/FireboltExampleInvoker', () => ({ get: () => mockFireboltExampleInvoker, })); -jest.mock('@firebolt-js/sdk/dist/lib/Transport/index.mjs', () => ({ - send: jest.fn().mockReturnValue({}), -})); - jest.mock('../../src/FireboltTransportInvoker', () => ({ get: () => mockFireboltTransportInvoker, })); +// Mock the logger module +jest.mock('../../src/utils/Logger', () => { + const loggerMock = { + error: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; + return jest.fn(() => loggerMock); +}); + jest.mock('@firebolt-js/sdk', () => ({ Accessibility: {}, Account: {}, @@ -613,6 +620,8 @@ jest.mock('../../src/MethodFilters', () => ({ })), })); +process.env.IS_BIDIRECTIONAL_SDK = false; + describe('Test_Runner test cases', () => { beforeEach(() => { runner = new Test_Runner(); @@ -627,6 +636,9 @@ describe('Test_Runner test cases', () => { }); describe('northBoundSchemaValidationAndReportGeneration Scenarios', () => { + beforeAll(() => { + jest.resetModules(); + }); test('should return empty result when dereference call fails for SDK', async () => { mockShouldDereferencerFail = true; result = await runner.northBoundSchemaValidationAndReportGeneration('SDK'); @@ -793,7 +805,50 @@ describe('Test_Runner test cases', () => { ], }; process.env.COMMUNICATION_MODE = 'Transport'; - result = await runner.northBoundSchemaValidationAndReportGeneration([CONSTANTS.CORE]); + let runner; + if (Transport.request == null) { + // v1 events + jest.mock('../../node_modules/@firebolt-js/sdk/dist/lib/Transport/index.mjs', () => { + let callId = 0; + callId++; + return { + listen: jest.fn().mockImplementation(() => { + console.log('Returning promise and id'); + const result = { id: callId, promise: Promise.resolve('success') }; + console.log('Returning result: ' + JSON.stringify(result)); + return result; + }), + send: jest.fn(), + addEventEmitter: jest.fn(), + }; + }); + process.env.IS_BIDIRECTIONAL_SDK = false; + // re-import the modules after mocking it + const testRunner = require('../../src/Test_Runner').Test_Runner; + runner = new testRunner(); + } else { + // v2 events + jest.doMock('Transport', () => { + let callId = 0; + callId++; + return { + request: jest.fn().mockImplementation(() => { + console.log('Returning promise and id'); + const result = { id: callId, promise: Promise.resolve('success') }; + console.log('Returning result: ' + JSON.stringify(result)); + return result; + }), + subscribe: jest.fn().mockImplementation((eventName, callBack) => { + console.log('subscribe function called for event: ' + eventName); + }), + }; + }); + process.env.IS_BIDIRECTIONAL_SDK = true; + // re-import the modules after mocking it + const testRunner = require('../../src/Test_Runner').Test_Runner; + runner = new testRunner(); + } + const result = await runner.northBoundSchemaValidationAndReportGeneration([CONSTANTS.CORE]); const extractedResult = result.find((obj) => obj.title === 'Account.id'); extractedResult.code = JSON.parse(extractedResult.code); expect(extractedResult.code['Schema Validation'].Status).toEqual('passed'); diff --git a/webpack.dev.js b/webpack.dev.js index 4a89f498..67f78dd3 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -115,6 +115,10 @@ module.exports = { name: 'RunTestHandler', alias: ['/plugins/runTestHandler.js', '/src/pubsub/handlers/RunTestHandler.js'], }, + { + name: 'Transport', + alias: ['@firebolt-js/sdk/dist/lib/Gateway/index.mjs', '@firebolt-js/sdk/dist/lib/Transport/index.mjs'], + }, ], 'resolve' ), diff --git a/webpack.prod.js b/webpack.prod.js index d1892351..35183425 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -98,6 +98,10 @@ module.exports = { name: 'RunTestHandler', alias: ['/plugins/runTestHandler.js', '/src/pubsub/handlers/RunTestHandler.js'], }, + { + name: 'Transport', + alias: ['@firebolt-js/sdk/dist/lib/Gateway/index.mjs', '@firebolt-js/sdk/dist/lib/Transport/index.mjs'], + }, ], 'resolve' ),