event.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. 'use strict';
  2. const logger = require('../logger');
  3. const nantum = require('../modules/nantum');
  4. const { vtnId } = require('../config');
  5. function calculateEventStatus(notificationTime, startTime, endTime) {
  6. const nowMillis = new Date().getTime();
  7. if (nowMillis < new Date(startTime).getTime()) {
  8. return 'far';
  9. }
  10. if (nowMillis > new Date(endTime).getTime()) {
  11. return 'completed';
  12. }
  13. return 'active';
  14. }
  15. function calculateDurationSeconds(startTime, endTime) {
  16. return Math.round(
  17. (new Date(endTime).getTime() - new Date(startTime).getTime()) / 1000,
  18. );
  19. }
  20. function calculateDuration(startTime, endTime) {
  21. return `PT${calculateDurationSeconds(startTime, endTime)}S`;
  22. }
  23. function calculateNotificationDuration(notificationTime, startTime) {
  24. if (!notificationTime) {
  25. return 'PT0S';
  26. }
  27. return calculateDuration(notificationTime, startTime);
  28. }
  29. function calculateEventIntervals(eventInfoValues, eventDurationSeconds) {
  30. //TODO: this is likely incorrect. Get more details on the event_info_value data model.
  31. let result = [];
  32. for (let i = 0; i < eventInfoValues.length; i++) {
  33. const eventInfoValue = eventInfoValues[i];
  34. const nextOffset =
  35. i === eventInfoValues.length - 1
  36. ? eventDurationSeconds - eventInfoValue.timeOffset
  37. : eventInfoValues[i + 1].timeOffset;
  38. result.push({
  39. signalPayloads: [eventInfoValue.value],
  40. duration: `PT${nextOffset}S`,
  41. uid: `${i + 1}`,
  42. });
  43. }
  44. return result;
  45. }
  46. function calculateEventSignals(eventInstances, eventDurationSeconds) {
  47. return eventInstances.map(eventInstance => {
  48. return {
  49. signalName: eventInstance.event_type_id,
  50. signalId: '112233445566',
  51. signalType: 'level',
  52. intervals: calculateEventIntervals(
  53. eventInstance.event_info_values,
  54. eventDurationSeconds,
  55. ),
  56. };
  57. });
  58. }
  59. function convertToOadrEvents(nantumEvent) {
  60. if (!nantumEvent.dr_event_data) {
  61. // no event
  62. return [];
  63. }
  64. const nowMillis = new Date().getTime();
  65. if (
  66. nowMillis < new Date(nantumEvent.dr_event_data.notification_time).getTime()
  67. ) {
  68. return []; // not in the notification period yet
  69. }
  70. return [
  71. {
  72. eventDescriptor: {
  73. eventId: nantumEvent.event_identifier,
  74. modificationNumber: nantumEvent.event_mod_number,
  75. marketContext: 'http://MarketContext1',
  76. createdDateTime: '2020-04-14T16:06:39.000Z',
  77. eventStatus: calculateEventStatus(
  78. nantumEvent.dr_event_data.notification_time,
  79. nantumEvent.dr_event_data.start_time,
  80. nantumEvent.dr_event_data.end_time,
  81. ),
  82. testEvent: nantumEvent.test_event,
  83. priority: 0,
  84. },
  85. activePeriod: {
  86. startDate: nantumEvent.dr_event_data.start_time,
  87. duration: calculateDuration(
  88. nantumEvent.dr_event_data.start_time,
  89. nantumEvent.dr_event_data.end_time,
  90. ),
  91. notificationDuration: calculateNotificationDuration(
  92. nantumEvent.dr_event_data.notification_time,
  93. nantumEvent.dr_event_data.start_time,
  94. ),
  95. },
  96. signals: {
  97. event: calculateEventSignals(
  98. nantumEvent.dr_event_data.event_instance,
  99. calculateDurationSeconds(
  100. nantumEvent.dr_event_data.start_time,
  101. nantumEvent.dr_event_data.end_time,
  102. ),
  103. ),
  104. },
  105. target: {
  106. venId: [nantumEvent.client_id],
  107. },
  108. responseRequired: 'always',
  109. },
  110. ];
  111. }
  112. async function retrieveEvents(
  113. obj,
  114. clientCertificateCn,
  115. clientCertificateFingerprint,
  116. ) {
  117. logger.info(
  118. 'retrieveEvents',
  119. obj,
  120. clientCertificateCn,
  121. clientCertificateFingerprint,
  122. );
  123. const requestVenId = obj.venId;
  124. if (!requestVenId) {
  125. const error = new Error('No VenID in request');
  126. error.responseCode = 452;
  127. throw error;
  128. }
  129. if (requestVenId !== clientCertificateFingerprint) {
  130. // as per certification item #512, venId MUST be case-sensitive
  131. const error = new Error('VenID does not match certificate');
  132. error.responseCode = 452;
  133. throw error;
  134. }
  135. if (!clientCertificateCn) {
  136. const error = new Error('Could not determine CN from client certificate');
  137. error.responseCode = 452;
  138. throw error;
  139. }
  140. const event = await nantum.fetchEvent(requestVenId);
  141. return {
  142. responseCode: '200',
  143. responseDescription: 'OK',
  144. responseRequestId: obj.requestId || '',
  145. requestId: obj.requestId || '',
  146. vtnId: vtnId,
  147. events: convertToOadrEvents(event),
  148. };
  149. }
  150. async function updateOptType(
  151. oadrCreatedEvent,
  152. clientCertificateCn,
  153. clientCertificateFingerprint,
  154. ) {
  155. logger.info(
  156. 'updateOptType',
  157. oadrCreatedEvent,
  158. clientCertificateCn,
  159. clientCertificateFingerprint,
  160. );
  161. const requestVenId = oadrCreatedEvent.venId;
  162. if (!requestVenId) {
  163. const error = new Error('No VenID in request');
  164. error.responseCode = 452;
  165. throw error;
  166. }
  167. if (requestVenId !== clientCertificateFingerprint) {
  168. // as per certification item #512, venId MUST be case-sensitive
  169. const error = new Error('VenID does not match certificate');
  170. error.responseCode = 452;
  171. throw error;
  172. }
  173. const nantumRegistration = await nantum.fetchRegistration(requestVenId);
  174. const opted = nantumRegistration.opted || [];
  175. //TODO: more validation: VEN may opt into an event that doesn't exist. VEN may opt into an old version of an event
  176. // (modificationNumber doesn't match). May opt into a completed event. Indicate error(s) to client.
  177. for (const eventResponse of oadrCreatedEvent.eventResponses) {
  178. // remove existing opts for this eventId
  179. nantumRegistration.opted = [
  180. ...opted.filter(optedItem => optedItem.eventId !== eventResponse.eventId),
  181. ];
  182. nantumRegistration.opted.push({
  183. eventId: eventResponse.eventId,
  184. modificationNumber: eventResponse.modificationNumber,
  185. optType: eventResponse.optType,
  186. });
  187. await nantum.updateRegistration(nantumRegistration);
  188. }
  189. return {
  190. responseCode: '200',
  191. responseDescription: 'OK',
  192. venId: clientCertificateFingerprint,
  193. };
  194. }
  195. async function filterOutAcknowledgedEvents(venId, events) {
  196. const nantumRegistration = await nantum.fetchRegistration(venId);
  197. const opted = nantumRegistration.opted || [];
  198. return events.filter(
  199. event =>
  200. opted.filter(
  201. optedItem =>
  202. optedItem.eventId === event.eventDescriptor.eventId &&
  203. optedItem.modificationNumber ===
  204. event.eventDescriptor.modificationNumber,
  205. ).length === 0,
  206. );
  207. }
  208. async function pollForEvents(
  209. obj,
  210. clientCertificateCn,
  211. clientCertificateFingerprint,
  212. ) {
  213. logger.info(
  214. 'pollForEvents',
  215. obj,
  216. clientCertificateCn,
  217. clientCertificateFingerprint,
  218. );
  219. const requestVenId = obj.venId;
  220. if (!requestVenId) {
  221. const error = new Error('No VenID in request');
  222. error.responseCode = 452;
  223. throw error;
  224. }
  225. if (requestVenId !== clientCertificateFingerprint) {
  226. // as per certification item #512, venId MUST be case-sensitive
  227. const error = new Error('VenID does not match certificate');
  228. error.responseCode = 452;
  229. throw error;
  230. }
  231. if (!clientCertificateCn) {
  232. const error = new Error('Could not determine CN from client certificate');
  233. error.responseCode = 452;
  234. throw error;
  235. }
  236. const event = await nantum.fetchEvent(requestVenId);
  237. const filteredEvents = await filterOutAcknowledgedEvents(
  238. requestVenId,
  239. convertToOadrEvents(event),
  240. );
  241. if (filteredEvents.length > 0) {
  242. return {
  243. responseCode: '200',
  244. responseDescription: 'OK',
  245. responseRequestId: '', // required field, but empty is allowed as per spec
  246. requestId: '',
  247. vtnId: vtnId,
  248. events: filteredEvents,
  249. };
  250. }
  251. return undefined;
  252. }
  253. module.exports = {
  254. pollForEvents,
  255. retrieveEvents,
  256. updateOptType,
  257. };