@envive-ai/react-hooks 0.3.63 → 0.3.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/application/models/featureGates.cjs +2 -1
  2. package/dist/application/models/featureGates.d.cts +2 -1
  3. package/dist/application/models/featureGates.d.ts +2 -1
  4. package/dist/application/models/featureGates.js +2 -1
  5. package/dist/application/utils/elementObserver.d.cts +2 -2
  6. package/dist/atoms/app/index.d.cts +7 -7
  7. package/dist/atoms/app/variant.d.cts +6 -6
  8. package/dist/atoms/chat/chatState.d.cts +19 -19
  9. package/dist/atoms/chat/chatState.d.ts +19 -19
  10. package/dist/atoms/chat/form.d.cts +2 -2
  11. package/dist/atoms/chat/form.d.ts +2 -2
  12. package/dist/atoms/chat/index.d.cts +2 -2
  13. package/dist/atoms/chat/index.d.ts +3 -3
  14. package/dist/atoms/chat/lastMessage.d.cts +2 -2
  15. package/dist/atoms/chat/lastMessage.d.ts +2 -2
  16. package/dist/atoms/chat/messageQueue.d.cts +7 -7
  17. package/dist/atoms/chat/messageQueue.d.ts +6 -6
  18. package/dist/atoms/chat/performanceMetrics.d.cts +6 -6
  19. package/dist/atoms/chat/performanceMetrics.d.ts +6 -6
  20. package/dist/atoms/chat/renderedWidgetRefs.d.cts +3 -3
  21. package/dist/atoms/chat/renderedWidgetRefs.d.ts +2 -2
  22. package/dist/atoms/chat/replies.d.cts +3 -3
  23. package/dist/atoms/chat/replies.d.ts +3 -3
  24. package/dist/atoms/chat/suggestions.d.cts +3 -3
  25. package/dist/atoms/chat/suggestions.d.ts +2 -2
  26. package/dist/atoms/envive/enviveConfig.d.cts +14 -14
  27. package/dist/atoms/envive/enviveConfig.d.ts +14 -14
  28. package/dist/atoms/globalSearch/globalSearch.d.cts +5 -5
  29. package/dist/atoms/globalSearch/globalSearch.d.ts +5 -5
  30. package/dist/atoms/org/customerService.d.cts +6 -6
  31. package/dist/atoms/org/customerService.d.ts +6 -6
  32. package/dist/atoms/org/graphqlConfig.d.cts +4 -4
  33. package/dist/atoms/org/graphqlConfig.d.ts +4 -4
  34. package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
  35. package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
  36. package/dist/atoms/org/orgAnalyticsConfig.d.cts +4 -4
  37. package/dist/atoms/org/orgAnalyticsConfig.d.ts +4 -4
  38. package/dist/atoms/search/chatSearch.d.cts +17 -17
  39. package/dist/atoms/search/chatSearch.d.ts +17 -17
  40. package/dist/atoms/search/searchAPI.d.cts +13 -13
  41. package/dist/atoms/search/searchAPI.d.ts +13 -13
  42. package/dist/atoms/search/types.d.cts +1 -1
  43. package/dist/atoms/search/utils.d.cts +1 -1
  44. package/dist/atoms/widget/chatPreviewLoading.d.cts +2 -2
  45. package/dist/atoms/widget/chatPreviewLoading.d.ts +2 -2
  46. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.cts +2 -2
  47. package/dist/contexts/types.d.cts +1 -1
  48. package/dist/contexts/types.d.ts +1 -1
  49. package/dist/contexts/typesV3.d.cts +1 -1
  50. package/dist/contexts/typesV3.d.ts +1 -1
  51. package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.cts +2 -2
  52. package/dist/hooks/Intersection/useIntersection.cjs +9 -2
  53. package/dist/hooks/Intersection/useIntersection.d.cts +2 -2
  54. package/dist/hooks/Intersection/useIntersection.d.ts +2 -2
  55. package/dist/hooks/Intersection/useIntersection.js +9 -2
  56. package/dist/hooks/SystemSettingsContext/useSystemSettingsContext.d.cts +2 -2
  57. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.cjs +12 -3
  58. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.cts +2 -1
  59. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.ts +2 -1
  60. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.js +13 -4
  61. package/dist/hooks/WidgetLoadDiagnostics/index.cjs +4 -0
  62. package/dist/hooks/WidgetLoadDiagnostics/index.d.cts +2 -0
  63. package/dist/hooks/WidgetLoadDiagnostics/index.d.ts +2 -0
  64. package/dist/hooks/WidgetLoadDiagnostics/index.js +3 -0
  65. package/dist/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.cjs +122 -0
  66. package/dist/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.d.cts +48 -0
  67. package/dist/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.d.ts +48 -0
  68. package/dist/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.js +120 -0
  69. package/dist/hooks/utils.d.cts +1 -1
  70. package/dist/hooks/utils.d.ts +1 -1
  71. package/dist/services/amplitudeService/eventNames.cjs +2 -1
  72. package/dist/services/amplitudeService/eventNames.d.cts +2 -1
  73. package/dist/services/amplitudeService/eventNames.d.ts +2 -1
  74. package/dist/services/amplitudeService/eventNames.js +2 -1
  75. package/dist/services/ga4ProjectionService/ga4EventSchema.cjs +3 -2
  76. package/dist/services/ga4ProjectionService/ga4EventSchema.js +3 -2
  77. package/dist/services/hardcopyService/hardcopyService.cjs +12 -3
  78. package/dist/services/hardcopyService/hardcopyService.d.cts +3 -1
  79. package/dist/services/hardcopyService/hardcopyService.d.ts +3 -1
  80. package/dist/services/hardcopyService/hardcopyService.js +12 -3
  81. package/package.json +5 -1
  82. package/src/application/models/featureGates.ts +6 -0
  83. package/src/hooks/Intersection/useIntersection.ts +11 -0
  84. package/src/hooks/TrackComponentVisibleEvent/__tests__/useTrackComponentVisibleEvent.test.tsx +64 -0
  85. package/src/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.ts +23 -4
  86. package/src/hooks/WidgetLoadDiagnostics/__tests__/useWidgetLoadDiagnostics.test.ts +161 -0
  87. package/src/hooks/WidgetLoadDiagnostics/index.ts +5 -0
  88. package/src/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.ts +178 -0
  89. package/src/services/amplitudeService/eventNames.ts +4 -0
  90. package/src/services/ga4ProjectionService/ga4EventSchema.ts +5 -0
  91. package/src/services/hardcopyService/__tests__/hardcopyService.test.ts +16 -5
  92. package/src/services/hardcopyService/hardcopyService.ts +10 -2
@@ -203,6 +203,7 @@ var HardcopyService = class {
203
203
  request_id: widgetTextRequest.requestId
204
204
  }
205
205
  });
206
+ let fallbackReason;
206
207
  try {
207
208
  const response = await withTimeout(commerce_api_default.getHardcopy(widgetTextRequest), HARDCOPY_TIMEOUT_MS);
208
209
  if (response) {
@@ -218,15 +219,23 @@ var HardcopyService = class {
218
219
  ...response.aiSuggestionsAttributionBySlot ? { ai_suggestions_attribution_by_slot: response.aiSuggestionsAttributionBySlot } : {}
219
220
  }
220
221
  });
221
- return convertedResponse;
222
+ return {
223
+ ...convertedResponse,
224
+ servedFrom: "api"
225
+ };
222
226
  }
223
227
  } catch (error) {
224
228
  logger.logError("error getting hardcopy from backend", error);
229
+ fallbackReason = error instanceof Error && error.message.includes("timed out") ? "timeout" : "error";
225
230
  }
226
231
  const mockResponse = MOCK_HARDCOPY_RESPONSE[request.widgetType];
227
232
  if (mockResponse) {
228
233
  logger.logDebug("no real response, using mock", mockResponse);
229
- return mockResponse;
234
+ return {
235
+ ...mockResponse,
236
+ servedFrom: "static",
237
+ fallbackReason: fallbackReason ?? "error"
238
+ };
230
239
  }
231
240
  throw new Error(`No hardcopy response found for widget type: ${request.widgetType}`);
232
241
  }
@@ -245,4 +254,4 @@ var MockHardcopyService = class {
245
254
 
246
255
  //#endregion
247
256
  export { HARDCOPY_TIMEOUT_MS, HardcopyService, MOCK_HARDCOPY_RESPONSE, MockHardcopyService };
248
- //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGFyZGNvcHlTZXJ2aWNlLmpzIiwibmFtZXMiOlsiTG9nZ2VyIiwidGltZXI6IFJldHVyblR5cGU8dHlwZW9mIHNldFRpbWVvdXQ+IiwiTU9DS19IQVJEQ09QWV9SRVNQT05TRTogUGFydGlhbDxSZWNvcmQ8V2lkZ2V0VHlwZVYzLCBIYXJkY29weVJlc3BvbnNlPj4iLCJ1dWlkIiwiY29uZmlnOiBIYXJkY29weVNlcnZpY2VDb25maWciLCJ3aWRnZXRUZXh0UmVxdWVzdDogV2lkZ2V0VGV4dFJlcXVlc3QiLCJ3aXJlV2lkZ2V0VHlwZTogc3RyaW5nIiwiQ29tbWVyY2VBcGlDbGllbnQiLCJvdmVycmlkZXM6IFBhcnRpYWw8UmVjb3JkPFdpZGdldFR5cGVWMywgSGFyZGNvcHlSZXNwb25zZT4+Il0sInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3NlcnZpY2VzL2hhcmRjb3B5U2VydmljZS9oYXJkY29weVNlcnZpY2UudHMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgdjQgYXMgdXVpZCB9IGZyb20gJ3V1aWQnO1xuaW1wb3J0IENvbW1lcmNlQXBpQ2xpZW50IGZyb20gJ3NyYy9hcHBsaWNhdGlvbi9jb21tZXJjZS1hcGknO1xuaW1wb3J0IHsgV2lkZ2V0U3RyaW5nLCBXaWRnZXRUZXh0IH0gZnJvbSAnc3JjL2FwcGxpY2F0aW9uL21vZGVscy9hcGkvd2lkZ2V0VGV4dCc7XG5pbXBvcnQgeyBXaWRnZXRUZXh0UmVxdWVzdCB9IGZyb20gJ3NyYy9hcHBsaWNhdGlvbi9tb2RlbHMvYXBpL3dpZGdldFRleHRSZXF1ZXN0JztcbmltcG9ydCB7IHRvQ2FtZWxDYXNlIH0gZnJvbSAnc3JjL2FwcGxpY2F0aW9uL21vZGVscy91dGlscy9zbmFrZVRvQ2FtZWxUcmFuc2Zvcm1lcic7XG5pbXBvcnQgeyBnZXRRdWVyeVBhcmFtIH0gZnJvbSAnc3JjL2FwcGxpY2F0aW9uL3V0aWxzJztcbmltcG9ydCB7IGNvcmVXaWRnZXRUeXBlVG9BcGlXaWRnZXRUeXBlIH0gZnJvbSAnc3JjL2FwcGxpY2F0aW9uL3V0aWxzL3dpZGdldFRleHRSZXF1ZXN0VG9BcGlSZXF1ZXN0JztcbmltcG9ydCB7IFVzZXJFdmVudCB9IGZyb20gJ3NyYy9hcHBsaWNhdGlvbi9tb2RlbHMnO1xuaW1wb3J0IExvZ2dlciBmcm9tICdzcmMvYXBwbGljYXRpb24vbG9nZ2luZy9sb2dnZXInO1xuaW1wb3J0IHsgQXBwRGV0YWlscyB9IGZyb20gJ3NyYy9hdG9tcy9hcHAnO1xuaW1wb3J0IHsgZ2V0QXRvbVN0b3JlIH0gZnJvbSAnc3JjL2F0b21zL2F0b21TdG9yZS9hdG9tU3RvcmUnO1xuaW1wb3J0IHsgcmVzb2x2ZWRCYXNlQ29uZmlnVmVyc2lvbkF0b20gfSBmcm9tICdzcmMvYXRvbXMvZW52aXZlL3Jlc29sdmVkQmFzZUNvbmZpZ1ZlcnNpb24nO1xuaW1wb3J0IHsgV2lkZ2V0VHlwZVYzIH0gZnJvbSAnc3JjL2NvbnRleHRzL3R5cGVzVjMnO1xuaW1wb3J0IHsgRW52aXZlTWV0cmljc0V2ZW50TmFtZSwgVHJhY2tFdmVudFBhcmFtcyB9IGZyb20gJy4uL2FtcGxpdHVkZVNlcnZpY2UnO1xuXG5jb25zdCBsb2dnZXIgPSBuZXcgTG9nZ2VyKCdoYXJkY29weVNlcnZpY2UnKTtcblxuLy8gTG9uZ2VzdCBhIGhhcmRjb3B5IHJlcXVlc3QgbWF5IGhhbmcgYmVmb3JlIHdlIHN0b3Agd2FpdGluZyBmb3IgaXQuIFRoZSBiYWNrZW5kLWVycm9yXG4vLyBwYXRoIGJlbG93IGFscmVhZHkgZmFsbHMgYmFjayB0byBzdGF0aWMgY29weSwgYnV0IGEgcmVxdWVzdCB0aGF0IG5ldmVyIHNldHRsZXMgYXQgYWxsXG4vLyBuZXZlciByZWFjaGVzIHRoYXQgY2F0Y2gg4oCUIGl0IGxlZnQgdGhlIHdpZGdldCdzIGxvYWRpbmcgbGF0Y2ggdW5yZXNvbHZlZCBmb3JldmVyLCB3aGljaFxuLy8gaXMgdGhlIFwiUExQIHN0YXlpbmcgaW4gbG9hZGluZyBzdGF0ZVwiIGZhaWx1cmUuIFJhY2luZyB0aGUgcmVxdWVzdCBhZ2FpbnN0IHRoaXMgdGltZW91dFxuLy8gdHVybnMgYSBoYW5nIGludG8gdGhlIHNhbWUgZmFsbGJhY2sgcGF0aCBhbiBlcnJvciB0YWtlcywgc28gdGhlIHdpZGdldCBhbHdheXMgbGVhdmVzIGl0c1xuLy8gbG9hZGluZyBzdGF0ZS5cbi8vXG4vLyBLZXB0IHVuZGVyIHRoZSB+NC01cyBhdCB3aGljaCBzZXNzaW9uIHJlcGxheXMgc2hvdyBzaG9wcGVycyBhYmFuZG9uIGEgc3RpbGwtbG9hZGluZ1xuLy8gd2lkZ2V0OiBhIHJlc2N1ZSB0aGF0IGZpcmVzIGFmdGVyIHRoZXkndmUgbGVmdCBoZWxwcyBubyBvbmUuIFdoZW4gaXQgZmlyZXMsIHRoZSBjYXRjaFxuLy8gYmVsb3cgbG9ncyBhbiBlcnJvciAobWVzc2FnZSBpbmNsdWRlcyBcInRpbWVkIG91dFwiKSDigJQgdGhhdCBsb2cgaXMgdGhlIHNpZ25hbCBtb25pdG9yaW5nXG4vLyBjYW4gY291bnQgdG8gbWVhc3VyZSBob3cgb2Z0ZW4gaGFyZGNvcHkgc3RhbGxzLlxuZXhwb3J0IGNvbnN0IEhBUkRDT1BZX1RJTUVPVVRfTVMgPSA0MDAwO1xuXG4vLyBSZWplY3QgaWYgYHByb21pc2VgIGhhcyBub3Qgc2V0dGxlZCB3aXRoaW4gYG1zYC4gVGhlIHVuZGVybHlpbmcgcmVxdWVzdCBpcyBub3QgYWJvcnRlZFxuLy8gKGEgbGF0ZSByZXNwb25zZSBpcyBzaW1wbHkgZGlzY2FyZGVkKSDigJQgdGhlIG9ubHkgZ29hbCBpcyB0byBzdG9wICp3YWl0aW5nKiwgc28gdGhlXG4vLyBjYWxsZXIncyBlcnJvciBoYW5kbGluZyBjYW4gcnVuLlxuY29uc3Qgd2l0aFRpbWVvdXQgPSA8VD4ocHJvbWlzZTogUHJvbWlzZTxUPiwgbXM6IG51bWJlcik6IFByb21pc2U8VD4gPT4ge1xuICBsZXQgdGltZXI6IFJldHVyblR5cGU8dHlwZW9mIHNldFRpbWVvdXQ+O1xuICBjb25zdCB0aW1lb3V0ID0gbmV3IFByb21pc2U8bmV2ZXI+KChfLCByZWplY3QpID0+IHtcbiAgICB0aW1lciA9IHNldFRpbWVvdXQoKCkgPT4gcmVqZWN0KG5ldyBFcnJvcihgaGFyZGNvcHkgcmVxdWVzdCB0aW1lZCBvdXQgYWZ0ZXIgJHttc31tc2ApKSwgbXMpO1xuICB9KTtcbiAgcmV0dXJuIFByb21pc2UucmFjZShbcHJvbWlzZSwgdGltZW91dF0pLmZpbmFsbHkoKCkgPT4gY2xlYXJUaW1lb3V0KHRpbWVyKSkgYXMgUHJvbWlzZTxUPjtcbn07XG5cbmV4cG9ydCBpbnRlcmZhY2UgSGFyZGNvcHlSZXF1ZXN0IHtcbiAgd2lkZ2V0VHlwZTogV2lkZ2V0VHlwZVYzO1xuICB1c2VyRXZlbnQ6IFVzZXJFdmVudDtcbn1cblxuZXhwb3J0IHR5cGUgSGFyZGNvcHlSZXNwb25zZSA9IHtcbiAgcmVzcG9uc2VJZDogc3RyaW5nO1xuICBsYW5ndWFnZTogc3RyaW5nO1xuICByYXdWYWx1ZXM/OiBSZWNvcmQ8c3RyaW5nLCBXaWRnZXRTdHJpbmcgfCBXaWRnZXRTdHJpbmdbXT47XG4gIHZhbHVlczogUmVjb3JkPHN0cmluZywgc3RyaW5nIHwgc3RyaW5nW10+O1xufTtcblxuZXhwb3J0IGludGVyZmFjZSBJSGFyZGNvcHlTZXJ2aWNlIHtcbiAgZ2V0SGFyZGNvcHkocmVxdWVzdDogSGFyZGNvcHlSZXF1ZXN0KTogUHJvbWlzZTxIYXJkY29weVJlc3BvbnNlPjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBIYXJkY29weVNlcnZpY2VDb25maWcge1xuICBnZXRBcHBEZXRhaWxzOiAoKSA9PiBBcHBEZXRhaWxzO1xuICBnZXRGZWF0dXJlRmxhZ3M6ICgpID0+IFJlY29yZDxzdHJpbmcsIGJvb2xlYW4+O1xuICB0cmFja0V2ZW50PzogKHBhcmFtczogVHJhY2tFdmVudFBhcmFtcykgPT4gUHJvbWlzZTx2b2lkPjtcbn1cblxuLy8gU2FtcGxlIHJlc3BvbnNlcyB1c2VkIGJvdGggYXMgZmFsbGJhY2sgd2hlbiB0aGUgYmFja2VuZCBmYWlscyBhbmQgYnkgTW9ja0hhcmRjb3B5U2VydmljZS5cbmV4cG9ydCBjb25zdCBNT0NLX0hBUkRDT1BZX1JFU1BPTlNFOiBQYXJ0aWFsPFJlY29yZDxXaWRnZXRUeXBlVjMsIEhhcmRjb3B5UmVzcG9uc2U+PiA9IHtcbiAgW1dpZGdldFR5cGVWMy5Qcm9tcHRDYXJvdXNlbFYzXToge1xuICAgIHJlc3BvbnNlSWQ6IHV1aWQoKSxcbiAgICBsYW5ndWFnZTogJ2VuJyxcbiAgICB2YWx1ZXM6IHtcbiAgICAgIHByb21wdEJ1dHRvblRleHRzOiBbXG4gICAgICAgICdXaGF0IGFyZSB5b3VyIGJlc3Qgc2VsbGVycz8nLFxuICAgICAgICAnU2hvdyBtZSBzdW1tZXIgZHJlc3NlcycsXG4gICAgICAgICdEbyB5b3UgaGF2ZSBmcmVlIHNoaXBwaW5nPycsXG4gICAgICAgICdXaGF0IHNpemVzIGFyZSBhdmFpbGFibGU/JyxcbiAgICAgICAgJ0NhbiBJIHJldHVybiBpdGVtcz8nLFxuICAgICAgXSxcbiAgICB9LFxuICB9LFxuICBbV2lkZ2V0VHlwZVYzLlRpdGxlZFByb21wdENhcm91c2VsVjNdOiB7XG4gICAgcmVzcG9uc2VJZDogJ21vY2stcmVzcG9uc2UtaWQnLFxuICAgIGxhbmd1YWdlOiAnZW4nLFxuICAgIHZhbHVlczoge1xuICAgICAgdGl0bGVMYWJlbDogJ05lZWQgc29tZSBoZWxwPycsXG4gICAgICBwcm9tcHRCdXR0b25UZXh0czogW1xuICAgICAgICAnV2hhdCBhcmUgeW91ciBiZXN0IHNlbGxlcnM/JyxcbiAgICAgICAgJ1Nob3cgbWUgc3VtbWVyIGRyZXNzZXMnLFxuICAgICAgICAnRG8geW91IGhhdmUgZnJlZSBzaGlwcGluZz8nLFxuICAgICAgICAnV2hhdCBzaXplcyBhcmUgYXZhaWxhYmxlPycsXG4gICAgICAgICdDYW4gSSByZXR1cm4gaXRlbXM/JyxcbiAgICAgIF0sXG4gICAgfSxcbiAgfSxcbiAgW1dpZGdldFR5cGVWMy5Tb2NpYWxQcm9vZlYzXToge1xuICAgIHJlc3BvbnNlSWQ6ICdtb2NrLXJlc3BvbnNlLWlkJyxcbiAgICBsYW5ndWFnZTogJ2VuJyxcbiAgICB2YWx1ZXM6IHtcbiAgICAgIHRpdGxlTGFiZWw6ICdTaG9wcGluZyBBc3Npc3RhbnQnLFxuICAgICAgbnVtYmVyT2ZDdXN0b21lcnNUZXh0OiAnMTIzIGN1c3RvbWVycycsXG4gICAgICBjdXN0b21lclF1ZXJ5VGV4dDogJ2ZvdW5kIHdoYXQgdGhleSB3ZXJlIGxvb2tpbmcgZm9yLi4uJyxcbiAgICAgIHByaW1hcnlCdXR0b25UZXh0OiAnU3RhcnQgc2hvcHBpbmcnLFxuICAgICAgc2Vjb25kYXJ5QnV0dG9uVGl0bGVUZXh0OiAnUG9wdWxhciBzZWFyY2hlcycsXG4gICAgICBzZWNvbmRhcnlCdXR0b25UZXh0czogW1xuICAgICAgICAnV2h5IHNob3VsZCBJIGJ1eSBmcm9tIHlvdT8nLFxuICAgICAgICAnSG93IGRvIEkgcmV0dXJuIGFuIGl0ZW0/JyxcbiAgICAgICAgJ05ldyBhcnJpdmFscycsXG4gICAgICAgICdTYWxlIGl0ZW1zJyxcbiAgICAgICAgJ1BvcHVsYXIgaXRlbXMnLFxuICAgICAgXSxcbiAgICAgIHRleHRGaWVsZFBsYWNlaG9sZGVyVGV4dDogJ0FzayBtZSBhbnl0aGluZy4uLicsXG4gICAgfSxcbiAgfSxcbiAgW1dpZGdldFR5cGVWMy5UeXBpbmdBbmltYXRpb25WM106IHtcbiAgICByZXNwb25zZUlkOiAnbW9jay1yZXNwb25zZS1pZCcsXG4gICAgbGFuZ3VhZ2U6ICdlbicsXG4gICAgdmFsdWVzOiB7XG4gICAgICB0aXRsZUxhYmVsOiAnU2hvcHBpbmcgQXNzaXN0YW50JyxcbiAgICAgIGhlYWRsaW5lVGV4dDogJ05lZWQgaGVscCBmaW5kaW5nJyxcbiAgICAgIGFuaW1hdGVkVGV4dFNlcXVlbmNlOiBbXG4gICAgICAgICd0aGUgcGVyZmVjdCBnaWZ0IGZvciB5b3VyIGxvdmVkIG9uZXM/JyxcbiAgICAgICAgJ3NvbWV0aGluZyBzcGVjaWFsIGZvciB0aGUgaG9saWRheXM/JyxcbiAgICAgICAgJ3RoZSBpZGVhbCBwcmVzZW50IGZvciBzb21lb25lIHNwZWNpYWw/JyxcbiAgICAgIF0sXG4gICAgICBoaW50VGV4dDogJ0FzayBtZSBhbnl0aGluZycsXG4gICAgICBwcm9tcHRCdXR0b25UZXh0czogW1xuICAgICAgICAnV2hhdCBhcmUgeW91ciBiZXN0IHNlbGxlcnM/JyxcbiAgICAgICAgJ1Nob3cgbWUgc3VtbWVyIGRyZXNzZXMnLFxuICAgICAgICAnRG8geW91IGhhdmUgZnJlZSBzaGlwcGluZz8nLFxuICAgICAgICAnV2hhdCBzaXplcyBhcmUgYXZhaWxhYmxlPycsXG4gICAgICBdLFxuICAgICAgdGV4dEZpZWxkQXJpYUxhYmVsOiAnQXNrIG1lIGFueXRoaW5nJyxcbiAgICB9LFxuICB9LFxuICBbV2lkZ2V0VHlwZVYzLkNoYXRQcmV2aWV3Q29tcGFyaXNvblYzXToge1xuICAgIHJlc3BvbnNlSWQ6ICdtb2NrLXJlc3BvbnNlLWlkJyxcbiAgICBsYW5ndWFnZTogJ2VuJyxcbiAgICB2YWx1ZXM6IHtcbiAgICAgIHRpdGxlTGFiZWw6ICdTaG9wcGluZyBBc3Npc3RhbnQnLFxuICAgICAgaGVhZGxpbmVUZXh0OiAnTmVlZCBoZWxwIGNvbXBhcmluZz8nLFxuICAgICAgdGV4dEZpZWxkUGxhY2Vob2xkZXJUZXh0OiAnQXNrIG1lIGFueXRoaW5nLi4uJyxcbiAgICB9LFxuICB9LFxuICBbV2lkZ2V0VHlwZVYzLkNoYXRQcmV2aWV3TG9hZGluZ1YzXToge1xuICAgIHJlc3BvbnNlSWQ6ICdtb2NrLXJlc3BvbnNlLWlkJyxcbiAgICBsYW5ndWFnZTogJ2VuJyxcbiAgICB2YWx1ZXM6IHtcbiAgICAgIHRpdGxlTGFiZWw6ICdTaG9wcGluZyBBc3Npc3RhbnQnLFxuICAgICAgdGV4dEZpZWxkUGxhY2Vob2xkZXJUZXh0OiAnQXNrIG1lIGFueXRoaW5nLi4uJyxcbiAgICB9LFxuICB9LFxuICBbV2lkZ2V0VHlwZVYzLkNoYXRQcmV2aWV3VjNdOiB7XG4gICAgcmVzcG9uc2VJZDogJ21vY2stcmVzcG9uc2UtaWQnLFxuICAgIGxhbmd1YWdlOiAnZW4nLFxuICAgIHZhbHVlczoge1xuICAgICAgdGl0bGVMYWJlbDogJ1Nob3BwaW5nIEFzc2lzdGFudCcsXG4gICAgICB0ZXh0RmllbGRQbGFjZWhvbGRlclRleHQ6ICdBc2sgbWUgYW55dGhpbmcuLi4nLFxuICAgIH0sXG4gIH0sXG4gIFtXaWRnZXRUeXBlVjMuUHJvbXB0QnV0dG9uQ2Fyb3VzZWxXaXRoSW1hZ2VWM106IHtcbiAgICByZXNwb25zZUlkOiAnbW9jay1yZXNwb25zZS1pZCcsXG4gICAgbGFuZ3VhZ2U6ICdlbicsXG4gICAgdmFsdWVzOiB7XG4gICAgICB0aXRsZUxhYmVsOiAnTmVlZCBzb21lIGhlbHA/JyxcbiAgICAgIHByb21wdEJ1dHRvbnNUZXh0czogWydIZWxwIG1lIGZpbmQgYSBwcm9kdWN0JywgXCJXaGF0J3Mgb24geW91ciBtaW5kP1wiLCAnVGFsayB0byBhIGh1bWFuJ10sXG4gICAgICB0ZXh0RmllbGRQbGFjZWhvbGRlclRleHQ6ICdBc2sgbWUgYW55dGhpbmcuLi4nLFxuICAgIH0sXG4gIH0sXG4gIFtXaWRnZXRUeXBlVjMuUHJvZHVjdENhcmRWM106IHtcbiAgICByZXNwb25zZUlkOiAnbW9jay1yZXNwb25zZS1pZCcsXG4gICAgbGFuZ3VhZ2U6ICdlbicsXG4gICAgdmFsdWVzOiB7XG4gICAgICBoZWFkbGluZTogJ0Rpc2NvdmVyIFlvdXIgUGVyZmVjdCBTdHlsZScsXG4gICAgICBhbmltYXRlZFRleHQ6IFtcbiAgICAgICAgJ3RoZSBwZXJmZWN0IG91dGZpdCBmb3IgYW55IG9jY2FzaW9uJyxcbiAgICAgICAgJ3RyZW5kaW5nIGZhc2hpb24gdGhhdCBmaXRzIHlvdXIgbGlmZXN0eWxlJyxcbiAgICAgICAgJ3F1YWxpdHkgcGllY2VzIHRoYXQgbGFzdCcsXG4gICAgICBdLFxuICAgICAgcHJvbXB0czogW1xuICAgICAgICAnV2hhdCBhcmUgeW91ciBiZXN0IHNlbGxlcnM/JyxcbiAgICAgICAgJ1Nob3cgbWUgc3VtbWVyIGRyZXNzZXMnLFxuICAgICAgICAnRG8geW91IGhhdmUgZnJlZSBzaGlwcGluZz8nLFxuICAgICAgICAnV2hhdCBzaXplcyBhcmUgYXZhaWxhYmxlPycsXG4gICAgICAgICdDYW4gSSByZXR1cm4gaXRlbXM/JyxcbiAgICAgIF0sXG4gICAgICBwbGFjZWhvbGRlcjogJ0FzayBtZSBhbnl0aGluZy4uLicsXG4gICAgfSxcbiAgfSxcbiAgW1dpZGdldFR5cGVWMy5GbG9hdGluZ0NoYXRWM106IHtcbiAgICByZXNwb25zZUlkOiAnbW9jay1yZXNwb25zZS1pZCcsXG4gICAgbGFuZ3VhZ2U6ICdlbicsXG4gICAgdmFsdWVzOiB7XG4gICAgICB3ZWxjb21lTWVzc2FnZVRpdGxlOiAnV2VsY29tZSB0byBFbnZpdmUgQUknLFxuICAgICAgd2VsY29tZU1lc3NhZ2VUZXh0OlxuICAgICAgICAnWW91ciBwZXJzb25hbCBzaG9wcGVyIHRyYWluZWQgb24gdGhlIGNvbGxlY3RpdmUga25vd2xlZGdlIG9mIHRob3VzYW5kcyBvZiByZWFsIGV4cGVydHMhJyxcbiAgICAgIGNoYXRGb290ZXJUZXh0RmllbGRQbGFjZWhvbGRlclRleHQ6ICdBc2sgbWUgYW55dGhpbmcuLi4nLFxuICAgICAgZGlzY2xhaW1lclRleHQ6ICdUaGlzIGlzIGEgZGlzY2xhaW1lci4uLicsXG4gICAgICBsZWZ0VG9nZ2xlTGFiZWw6ICdTaG9wIHdpdGggQUknLFxuICAgICAgcmlnaHRUb2dnbGVMYWJlbDogJ0xpdmUgc3VwcG9ydCcsXG4gICAgfSxcbiAgfSxcbiAgW1dpZGdldFR5cGVWMy5GdWxsUGFnZVNhbGVzQWdlbnRWM106IHtcbiAgICByZXNwb25zZUlkOiAnbW9jay1yZXNwb25zZS1pZCcsXG4gICAgbGFuZ3VhZ2U6ICdlbicsXG4gICAgdmFsdWVzOiB7XG4gICAgICB3ZWxjb21lTWVzc2FnZVRpdGxlOiAnV2VsY29tZSB0byBFbnZpdmUgQUknLFxuICAgICAgd2VsY29tZU1lc3NhZ2VUZXh0OlxuICAgICAgICAnWW91ciBwZXJzb25hbCBzaG9wcGVyIHRyYWluZWQgb24gdGhlIGNvbGxlY3RpdmUga25vd2xlZGdlIG9mIHRob3VzYW5kcyBvZiByZWFsIGV4cGVydHMhJyxcbiAgICAgIGNoYXRGb290ZXJUZXh0RmllbGRQbGFjZWhvbGRlclRleHQ6ICdBc2sgbWUgYW55dGhpbmcuLi4nLFxuICAgICAgZGlzY2xhaW1lclRleHQ6ICdUaGlzIGlzIGEgZGlzY2xhaW1lci4uLicsXG4gICAgICB3ZWxjb21lT3ZlcmxheUxpbmtMaXN0VGl0bGU6ICdFeHBsb3JlIG1vcmUnLFxuICAgIH0sXG4gIH0sXG59O1xuXG5jb25zdCBjb252ZXJ0VG9IYXJkY29weVJlc3BvbnNlID0gKHJlc3BvbnNlOiBXaWRnZXRUZXh0KTogSGFyZGNvcHlSZXNwb25zZSA9PiAoe1xuICByZXNwb25zZUlkOiByZXNwb25zZS5yZXNwb25zZUlkLFxuICBsYW5ndWFnZTogcmVzcG9uc2UubGFuZ3VhZ2UsXG4gIHJhd1ZhbHVlczogcmVzcG9uc2UudmFsdWVzLFxuICB2YWx1ZXM6IE9iamVjdC5mcm9tRW50cmllcyhcbiAgICBPYmplY3QuZW50cmllcyhyZXNwb25zZS52YWx1ZXMpLm1hcCgoW2tleSwgdmFsdWVdKSA9PiBbXG4gICAgICB0b0NhbWVsQ2FzZShrZXkpLFxuICAgICAgdmFsdWUubGVuZ3RoID09PSAxID8gdmFsdWVbMF0udmFsdWUgOiB2YWx1ZS5tYXAodiA9PiB2LnZhbHVlKSxcbiAgICBdKSxcbiAgKSxcbn0pO1xuXG5leHBvcnQgY2xhc3MgSGFyZGNvcHlTZXJ2aWNlIGltcGxlbWVudHMgSUhhcmRjb3B5U2VydmljZSB7XG4gIGNvbnN0cnVjdG9yKHByaXZhdGUgY29uZmlnOiBIYXJkY29weVNlcnZpY2VDb25maWcpIHt9XG5cbiAgYXN5bmMgZ2V0SGFyZGNvcHkocmVxdWVzdDogSGFyZGNvcHlSZXF1ZXN0KTogUHJvbWlzZTxIYXJkY29weVJlc3BvbnNlPiB7XG4gICAgY29uc3Qgb3ZlcnJpZGVDb25maWdWZXJzaW9uID1cbiAgICAgIGdldFF1ZXJ5UGFyYW0oJ3NwaWZmeV9jb25maWdfdmVyc2lvbicpIHx8XG4gICAgICBnZXRRdWVyeVBhcmFtKCdlbnZpdmVfY29uZmlnX3ZlcnNpb24nKSB8fFxuICAgICAgZ2V0QXRvbVN0b3JlKCkuZ2V0KHJlc29sdmVkQmFzZUNvbmZpZ1ZlcnNpb25BdG9tKSB8fFxuICAgICAgdW5kZWZpbmVkO1xuXG4gICAgY29uc3Qgd2lkZ2V0VGV4dFJlcXVlc3Q6IFdpZGdldFRleHRSZXF1ZXN0ID0ge1xuICAgICAgcmVxdWVzdElkOiB1dWlkKCksXG4gICAgICB3aWRnZXRUeXBlOiByZXF1ZXN0LndpZGdldFR5cGUsXG4gICAgICBjb250ZXh0OiB0aGlzLmNvbmZpZy5nZXRBcHBEZXRhaWxzKCksXG4gICAgICBmZWF0dXJlRmxhZ3M6IHRoaXMuY29uZmlnLmdldEZlYXR1cmVGbGFncygpLFxuICAgICAgbGFuZ3VhZ2U6IG5hdmlnYXRvci5sYW5ndWFnZXNbMF0gfHwgJ2VuJyxcbiAgICAgIG92ZXJyaWRlQ29uZmlnVmVyc2lvbixcbiAgICAgIHVybDogd2luZG93LmxvY2F0aW9uLmhyZWYsXG4gICAgfTtcblxuICAgIC8vIFRoZSBXaWRnZXQgVGV4dCBldmVudHMgbXVzdCBjYXJyeSB0aGUgQVBJLWZhY2luZyAodHJhbnNsYXRlZCkgd2lkZ2V0IHR5cGUsIG5vdCB0aGVcbiAgICAvLyBpbnRlcm5hbCBvbmU6IHRoZSBBSS1zdWdnZXN0aW9ucyBhY2NvdW50aW5nIGpvaW5zIHRoZXNlIGV2ZW50cyBhZ2FpbnN0IHRoZSBiYWNrZW5kXG4gICAgLy8gcmVnaXN0cnksIHdoaWNoIGlzIGtleWVkIGJ5IHRoZSB3aXJlIHR5cGUgKGUuZy4gUHJvZHVjdENhcmRWMyBpcyBzZW50IOKAlCBhbmQgc3RvcmVkIOKAlFxuICAgIC8vIGFzIEltYWdlUHJvbXB0Q2FyZFYzKS4gU3RhbXBpbmcgdGhlIGludGVybmFsIG5hbWUgaGVyZSB6ZXJvZWQgdGhvc2Ugc3RhdHMuIEFuXG4gICAgLy8gdW5tYXBwZWQgdHlwZSBrZWVwcyB0aGUgaW50ZXJuYWwgbmFtZSDigJQgYW5hbHl0aWNzIG11c3QgbmV2ZXIgYmxvY2sgc2VydmluZy5cbiAgICBsZXQgd2lyZVdpZGdldFR5cGU6IHN0cmluZyA9IHJlcXVlc3Qud2lkZ2V0VHlwZTtcbiAgICB0cnkge1xuICAgICAgd2lyZVdpZGdldFR5cGUgPSBjb3JlV2lkZ2V0VHlwZVRvQXBpV2lkZ2V0VHlwZShyZXF1ZXN0LndpZGdldFR5cGUpO1xuICAgIH0gY2F0Y2gge1xuICAgICAgbG9nZ2VyLmxvZ0RlYnVnKCdubyB3aXJlIG1hcHBpbmcgZm9yIHdpZGdldCB0eXBlIOKAlCBrZWVwaW5nIGludGVybmFsIG5hbWUnLCB7XG4gICAgICAgIHdpZGdldFR5cGU6IHJlcXVlc3Qud2lkZ2V0VHlwZSxcbiAgICAgIH0pO1xuICAgIH1cblxuICAgIHRoaXMuY29uZmlnLnRyYWNrRXZlbnQ/Lih7XG4gICAgICBldmVudE5hbWU6IEVudml2ZU1ldHJpY3NFdmVudE5hbWUuV2lkZ2V0VGV4dFJlcXVlc3QsXG4gICAgICBldmVudFByb3BzOiB7XG4gICAgICAgIHdpZGdldF90eXBlOiB3aXJlV2lkZ2V0VHlwZSxcbiAgICAgICAgcmVxdWVzdF9pZDogd2lkZ2V0VGV4dFJlcXVlc3QucmVxdWVzdElkLFxuICAgICAgfSxcbiAgICB9KTtcblxuICAgIHRyeSB7XG4gICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHdpdGhUaW1lb3V0KFxuICAgICAgICBDb21tZXJjZUFwaUNsaWVudC5nZXRIYXJkY29weSh3aWRnZXRUZXh0UmVxdWVzdCksXG4gICAgICAgIEhBUkRDT1BZX1RJTUVPVVRfTVMsXG4gICAgICApO1xuICAgICAgaWYgKHJlc3BvbnNlKSB7XG4gICAgICAgIGNvbnN0IGNvbnZlcnRlZFJlc3BvbnNlID0gY29udmVydFRvSGFyZGNvcHlSZXNwb25zZShyZXNwb25zZSk7XG4gICAgICAgIGxvZ2dlci5sb2dEZWJ1ZygnY29udmVydGVkIHJlc3BvbnNlJywgY29udmVydGVkUmVzcG9uc2UpO1xuICAgICAgICB0aGlzLmNvbmZpZy50cmFja0V2ZW50Py4oe1xuICAgICAgICAgIGV2ZW50TmFtZTogRW52aXZlTWV0cmljc0V2ZW50TmFtZS5XaWRnZXRUZXh0UmVzcG9uc2UsXG4gICAgICAgICAgZXZlbnRQcm9wczoge1xuICAgICAgICAgICAgd2lkZ2V0X3R5cGU6IHdpcmVXaWRnZXRUeXBlLFxuICAgICAgICAgICAgcmVzcG9uc2VfaWQ6IHJlc3BvbnNlLnJlc3BvbnNlSWQsXG4gICAgICAgICAgICB3aWRnZXRfdGV4dDogcmVzcG9uc2UudmFsdWVzLFxuICAgICAgICAgICAgcmVxdWVzdF9pZDogd2lkZ2V0VGV4dFJlcXVlc3QucmVxdWVzdElkLFxuICAgICAgICAgICAgLy8gV2hpY2ggbWV0aG9kIHNlcnZlZCBlYWNoIG92ZXJyaWRkZW4gc2xvdCDigJQgdHJhaW5pbmcvYXR0cmlidXRpb25cbiAgICAgICAgICAgIC8vIHJlYWRzIHRoaXMgZnJvbSB0aGUgYW5hbHl0aWNzIGV2ZW50OyBwYXNzZWQgdGhyb3VnaCB2ZXJiYXRpbS5cbiAgICAgICAgICAgIC4uLihyZXNwb25zZS5haVN1Z2dlc3Rpb25zQXR0cmlidXRpb25CeVNsb3RcbiAgICAgICAgICAgICAgPyB7IGFpX3N1Z2dlc3Rpb25zX2F0dHJpYnV0aW9uX2J5X3Nsb3Q6IHJlc3BvbnNlLmFpU3VnZ2VzdGlvbnNBdHRyaWJ1dGlvbkJ5U2xvdCB9XG4gICAgICAgICAgICAgIDoge30pLFxuICAgICAgICAgIH0sXG4gICAgICAgIH0pO1xuICAgICAgICByZXR1cm4gY29udmVydGVkUmVzcG9uc2U7XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGxvZ2dlci5sb2dFcnJvcignZXJyb3IgZ2V0dGluZyBoYXJkY29weSBmcm9tIGJhY2tlbmQnLCBlcnJvcik7XG4gICAgfVxuXG4gICAgY29uc3QgbW9ja1Jlc3BvbnNlID0gTU9DS19IQVJEQ09QWV9SRVNQT05TRVtyZXF1ZXN0LndpZGdldFR5cGVdO1xuICAgIGlmIChtb2NrUmVzcG9uc2UpIHtcbiAgICAgIGxvZ2dlci5sb2dEZWJ1Zygnbm8gcmVhbCByZXNwb25zZSwgdXNpbmcgbW9jaycsIG1vY2tSZXNwb25zZSk7XG4gICAgICByZXR1cm4gbW9ja1Jlc3BvbnNlO1xuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoYE5vIGhhcmRjb3B5IHJlc3BvbnNlIGZvdW5kIGZvciB3aWRnZXQgdHlwZTogJHtyZXF1ZXN0LndpZGdldFR5cGV9YCk7XG4gIH1cbn1cblxuZXhwb3J0IGNsYXNzIE1vY2tIYXJkY29weVNlcnZpY2UgaW1wbGVtZW50cyBJSGFyZGNvcHlTZXJ2aWNlIHtcbiAgY29uc3RydWN0b3IocHJpdmF0ZSBvdmVycmlkZXM6IFBhcnRpYWw8UmVjb3JkPFdpZGdldFR5cGVWMywgSGFyZGNvcHlSZXNwb25zZT4+ID0ge30pIHt9XG5cbiAgYXN5bmMgZ2V0SGFyZGNvcHkocmVxdWVzdDogSGFyZGNvcHlSZXF1ZXN0KTogUHJvbWlzZTxIYXJkY29weVJlc3BvbnNlPiB7XG4gICAgY29uc3QgZmFsbGJhY2tXaWRnZXRUeXBlID1cbiAgICAgIHJlcXVlc3Qud2lkZ2V0VHlwZSA9PT0gV2lkZ2V0VHlwZVYzLlByb2R1Y3RDYXJkVjNcbiAgICAgICAgPyBXaWRnZXRUeXBlVjMuSW1hZ2VQcm9tcHRDYXJkVjNcbiAgICAgICAgOiByZXF1ZXN0LndpZGdldFR5cGU7XG5cbiAgICBjb25zdCByZXNwb25zZSA9XG4gICAgICB0aGlzLm92ZXJyaWRlc1tyZXF1ZXN0LndpZGdldFR5cGVdID8/XG4gICAgICB0aGlzLm92ZXJyaWRlc1tmYWxsYmFja1dpZGdldFR5cGVdID8/XG4gICAgICBNT0NLX0hBUkRDT1BZX1JFU1BPTlNFW3JlcXVlc3Qud2lkZ2V0VHlwZV07XG5cbiAgICBpZiAoIXJlc3BvbnNlKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYE5vIGhhcmRjb3B5IHJlc3BvbnNlIGZvdW5kIGZvciB3aWRnZXQgdHlwZTogJHtyZXF1ZXN0LndpZGdldFR5cGV9YCk7XG4gICAgfVxuICAgIHJldHVybiByZXNwb25zZTtcbiAgfVxufVxuIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7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
257
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGFyZGNvcHlTZXJ2aWNlLmpzIiwibmFtZXMiOlsiTG9nZ2VyIiwidGltZXI6IFJldHVyblR5cGU8dHlwZW9mIHNldFRpbWVvdXQ+IiwiTU9DS19IQVJEQ09QWV9SRVNQT05TRTogUGFydGlhbDxSZWNvcmQ8V2lkZ2V0VHlwZVYzLCBIYXJkY29weVJlc3BvbnNlPj4iLCJ1dWlkIiwiY29uZmlnOiBIYXJkY29weVNlcnZpY2VDb25maWciLCJ3aWRnZXRUZXh0UmVxdWVzdDogV2lkZ2V0VGV4dFJlcXVlc3QiLCJ3aXJlV2lkZ2V0VHlwZTogc3RyaW5nIiwiZmFsbGJhY2tSZWFzb246ICd0aW1lb3V0JyB8ICdlcnJvcicgfCB1bmRlZmluZWQiLCJDb21tZXJjZUFwaUNsaWVudCIsIm92ZXJyaWRlczogUGFydGlhbDxSZWNvcmQ8V2lkZ2V0VHlwZVYzLCBIYXJkY29weVJlc3BvbnNlPj4iXSwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvc2VydmljZXMvaGFyZGNvcHlTZXJ2aWNlL2hhcmRjb3B5U2VydmljZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyB2NCBhcyB1dWlkIH0gZnJvbSAndXVpZCc7XG5pbXBvcnQgQ29tbWVyY2VBcGlDbGllbnQgZnJvbSAnc3JjL2FwcGxpY2F0aW9uL2NvbW1lcmNlLWFwaSc7XG5pbXBvcnQgeyBXaWRnZXRTdHJpbmcsIFdpZGdldFRleHQgfSBmcm9tICdzcmMvYXBwbGljYXRpb24vbW9kZWxzL2FwaS93aWRnZXRUZXh0JztcbmltcG9ydCB7IFdpZGdldFRleHRSZXF1ZXN0IH0gZnJvbSAnc3JjL2FwcGxpY2F0aW9uL21vZGVscy9hcGkvd2lkZ2V0VGV4dFJlcXVlc3QnO1xuaW1wb3J0IHsgdG9DYW1lbENhc2UgfSBmcm9tICdzcmMvYXBwbGljYXRpb24vbW9kZWxzL3V0aWxzL3NuYWtlVG9DYW1lbFRyYW5zZm9ybWVyJztcbmltcG9ydCB7IGdldFF1ZXJ5UGFyYW0gfSBmcm9tICdzcmMvYXBwbGljYXRpb24vdXRpbHMnO1xuaW1wb3J0IHsgY29yZVdpZGdldFR5cGVUb0FwaVdpZGdldFR5cGUgfSBmcm9tICdzcmMvYXBwbGljYXRpb24vdXRpbHMvd2lkZ2V0VGV4dFJlcXVlc3RUb0FwaVJlcXVlc3QnO1xuaW1wb3J0IHsgVXNlckV2ZW50IH0gZnJvbSAnc3JjL2FwcGxpY2F0aW9uL21vZGVscyc7XG5pbXBvcnQgTG9nZ2VyIGZyb20gJ3NyYy9hcHBsaWNhdGlvbi9sb2dnaW5nL2xvZ2dlcic7XG5pbXBvcnQgeyBBcHBEZXRhaWxzIH0gZnJvbSAnc3JjL2F0b21zL2FwcCc7XG5pbXBvcnQgeyBnZXRBdG9tU3RvcmUgfSBmcm9tICdzcmMvYXRvbXMvYXRvbVN0b3JlL2F0b21TdG9yZSc7XG5pbXBvcnQgeyByZXNvbHZlZEJhc2VDb25maWdWZXJzaW9uQXRvbSB9IGZyb20gJ3NyYy9hdG9tcy9lbnZpdmUvcmVzb2x2ZWRCYXNlQ29uZmlnVmVyc2lvbic7XG5pbXBvcnQgeyBXaWRnZXRUeXBlVjMgfSBmcm9tICdzcmMvY29udGV4dHMvdHlwZXNWMyc7XG5pbXBvcnQgeyBFbnZpdmVNZXRyaWNzRXZlbnROYW1lLCBUcmFja0V2ZW50UGFyYW1zIH0gZnJvbSAnLi4vYW1wbGl0dWRlU2VydmljZSc7XG5cbmNvbnN0IGxvZ2dlciA9IG5ldyBMb2dnZXIoJ2hhcmRjb3B5U2VydmljZScpO1xuXG4vLyBMb25nZXN0IGEgaGFyZGNvcHkgcmVxdWVzdCBtYXkgaGFuZyBiZWZvcmUgd2Ugc3RvcCB3YWl0aW5nIGZvciBpdC4gVGhlIGJhY2tlbmQtZXJyb3Jcbi8vIHBhdGggYmVsb3cgYWxyZWFkeSBmYWxscyBiYWNrIHRvIHN0YXRpYyBjb3B5LCBidXQgYSByZXF1ZXN0IHRoYXQgbmV2ZXIgc2V0dGxlcyBhdCBhbGxcbi8vIG5ldmVyIHJlYWNoZXMgdGhhdCBjYXRjaCDigJQgaXQgbGVmdCB0aGUgd2lkZ2V0J3MgbG9hZGluZyBsYXRjaCB1bnJlc29sdmVkIGZvcmV2ZXIsIHdoaWNoXG4vLyBpcyB0aGUgXCJQTFAgc3RheWluZyBpbiBsb2FkaW5nIHN0YXRlXCIgZmFpbHVyZS4gUmFjaW5nIHRoZSByZXF1ZXN0IGFnYWluc3QgdGhpcyB0aW1lb3V0XG4vLyB0dXJucyBhIGhhbmcgaW50byB0aGUgc2FtZSBmYWxsYmFjayBwYXRoIGFuIGVycm9yIHRha2VzLCBzbyB0aGUgd2lkZ2V0IGFsd2F5cyBsZWF2ZXMgaXRzXG4vLyBsb2FkaW5nIHN0YXRlLlxuLy9cbi8vIEtlcHQgdW5kZXIgdGhlIH40LTVzIGF0IHdoaWNoIHNlc3Npb24gcmVwbGF5cyBzaG93IHNob3BwZXJzIGFiYW5kb24gYSBzdGlsbC1sb2FkaW5nXG4vLyB3aWRnZXQ6IGEgcmVzY3VlIHRoYXQgZmlyZXMgYWZ0ZXIgdGhleSd2ZSBsZWZ0IGhlbHBzIG5vIG9uZS4gV2hlbiBpdCBmaXJlcywgdGhlIGNhdGNoXG4vLyBiZWxvdyBsb2dzIGFuIGVycm9yIChtZXNzYWdlIGluY2x1ZGVzIFwidGltZWQgb3V0XCIpIOKAlCB0aGF0IGxvZyBpcyB0aGUgc2lnbmFsIG1vbml0b3Jpbmdcbi8vIGNhbiBjb3VudCB0byBtZWFzdXJlIGhvdyBvZnRlbiBoYXJkY29weSBzdGFsbHMuXG5leHBvcnQgY29uc3QgSEFSRENPUFlfVElNRU9VVF9NUyA9IDQwMDA7XG5cbi8vIFJlamVjdCBpZiBgcHJvbWlzZWAgaGFzIG5vdCBzZXR0bGVkIHdpdGhpbiBgbXNgLiBUaGUgdW5kZXJseWluZyByZXF1ZXN0IGlzIG5vdCBhYm9ydGVkXG4vLyAoYSBsYXRlIHJlc3BvbnNlIGlzIHNpbXBseSBkaXNjYXJkZWQpIOKAlCB0aGUgb25seSBnb2FsIGlzIHRvIHN0b3AgKndhaXRpbmcqLCBzbyB0aGVcbi8vIGNhbGxlcidzIGVycm9yIGhhbmRsaW5nIGNhbiBydW4uXG5jb25zdCB3aXRoVGltZW91dCA9IDxUPihwcm9taXNlOiBQcm9taXNlPFQ+LCBtczogbnVtYmVyKTogUHJvbWlzZTxUPiA9PiB7XG4gIGxldCB0aW1lcjogUmV0dXJuVHlwZTx0eXBlb2Ygc2V0VGltZW91dD47XG4gIGNvbnN0IHRpbWVvdXQgPSBuZXcgUHJvbWlzZTxuZXZlcj4oKF8sIHJlamVjdCkgPT4ge1xuICAgIHRpbWVyID0gc2V0VGltZW91dCgoKSA9PiByZWplY3QobmV3IEVycm9yKGBoYXJkY29weSByZXF1ZXN0IHRpbWVkIG91dCBhZnRlciAke21zfW1zYCkpLCBtcyk7XG4gIH0pO1xuICByZXR1cm4gUHJvbWlzZS5yYWNlKFtwcm9taXNlLCB0aW1lb3V0XSkuZmluYWxseSgoKSA9PiBjbGVhclRpbWVvdXQodGltZXIpKSBhcyBQcm9taXNlPFQ+O1xufTtcblxuZXhwb3J0IGludGVyZmFjZSBIYXJkY29weVJlcXVlc3Qge1xuICB3aWRnZXRUeXBlOiBXaWRnZXRUeXBlVjM7XG4gIHVzZXJFdmVudDogVXNlckV2ZW50O1xufVxuXG5leHBvcnQgdHlwZSBIYXJkY29weVJlc3BvbnNlID0ge1xuICByZXNwb25zZUlkOiBzdHJpbmc7XG4gIGxhbmd1YWdlOiBzdHJpbmc7XG4gIHJhd1ZhbHVlcz86IFJlY29yZDxzdHJpbmcsIFdpZGdldFN0cmluZyB8IFdpZGdldFN0cmluZ1tdPjtcbiAgdmFsdWVzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmcgfCBzdHJpbmdbXT47XG4gIC8vIEhvdyB0aGUgY29weSB3YXMgc2VydmVkLCBmb3Igd2lkZ2V0LWxvYWQgZGlhZ25vc3RpY3M6ICdhcGknID0gbGl2ZSBiYWNrZW5kIHJlc3BvbnNlLFxuICAvLyAnc3RhdGljJyA9IGZlbGwgYmFjayB0byB0aGUgYnVuZGxlZCBNT0NLX0hBUkRDT1BZX1JFU1BPTlNFIGFmdGVyIGEgYmFja2VuZCBlcnJvciBvciB0aW1lb3V0LlxuICBzZXJ2ZWRGcm9tPzogJ2FwaScgfCAnc3RhdGljJztcbiAgZmFsbGJhY2tSZWFzb24/OiAndGltZW91dCcgfCAnZXJyb3InO1xufTtcblxuZXhwb3J0IGludGVyZmFjZSBJSGFyZGNvcHlTZXJ2aWNlIHtcbiAgZ2V0SGFyZGNvcHkocmVxdWVzdDogSGFyZGNvcHlSZXF1ZXN0KTogUHJvbWlzZTxIYXJkY29weVJlc3BvbnNlPjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBIYXJkY29weVNlcnZpY2VDb25maWcge1xuICBnZXRBcHBEZXRhaWxzOiAoKSA9PiBBcHBEZXRhaWxzO1xuICBnZXRGZWF0dXJlRmxhZ3M6ICgpID0+IFJlY29yZDxzdHJpbmcsIGJvb2xlYW4+O1xuICB0cmFja0V2ZW50PzogKHBhcmFtczogVHJhY2tFdmVudFBhcmFtcykgPT4gUHJvbWlzZTx2b2lkPjtcbn1cblxuLy8gU2FtcGxlIHJlc3BvbnNlcyB1c2VkIGJvdGggYXMgZmFsbGJhY2sgd2hlbiB0aGUgYmFja2VuZCBmYWlscyBhbmQgYnkgTW9ja0hhcmRjb3B5U2VydmljZS5cbmV4cG9ydCBjb25zdCBNT0NLX0hBUkRDT1BZX1JFU1BPTlNFOiBQYXJ0aWFsPFJlY29yZDxXaWRnZXRUeXBlVjMsIEhhcmRjb3B5UmVzcG9uc2U+PiA9IHtcbiAgW1dpZGdldFR5cGVWMy5Qcm9tcHRDYXJvdXNlbFYzXToge1xuICAgIHJlc3BvbnNlSWQ6IHV1aWQoKSxcbiAgICBsYW5ndWFnZTogJ2VuJyxcbiAgICB2YWx1ZXM6IHtcbiAgICAgIHByb21wdEJ1dHRvblRleHRzOiBbXG4gICAgICAgICdXaGF0IGFyZSB5b3VyIGJlc3Qgc2VsbGVycz8nLFxuICAgICAgICAnU2hvdyBtZSBzdW1tZXIgZHJlc3NlcycsXG4gICAgICAgICdEbyB5b3UgaGF2ZSBmcmVlIHNoaXBwaW5nPycsXG4gICAgICAgICdXaGF0IHNpemVzIGFyZSBhdmFpbGFibGU/JyxcbiAgICAgICAgJ0NhbiBJIHJldHVybiBpdGVtcz8nLFxuICAgICAgXSxcbiAgICB9LFxuICB9LFxuICBbV2lkZ2V0VHlwZVYzLlRpdGxlZFByb21wdENhcm91c2VsVjNdOiB7XG4gICAgcmVzcG9uc2VJZDogJ21vY2stcmVzcG9uc2UtaWQnLFxuICAgIGxhbmd1YWdlOiAnZW4nLFxuICAgIHZhbHVlczoge1xuICAgICAgdGl0bGVMYWJlbDogJ05lZWQgc29tZSBoZWxwPycsXG4gICAgICBwcm9tcHRCdXR0b25UZXh0czogW1xuICAgICAgICAnV2hhdCBhcmUgeW91ciBiZXN0IHNlbGxlcnM/JyxcbiAgICAgICAgJ1Nob3cgbWUgc3VtbWVyIGRyZXNzZXMnLFxuICAgICAgICAnRG8geW91IGhhdmUgZnJlZSBzaGlwcGluZz8nLFxuICAgICAgICAnV2hhdCBzaXplcyBhcmUgYXZhaWxhYmxlPycsXG4gICAgICAgICdDYW4gSSByZXR1cm4gaXRlbXM/JyxcbiAgICAgIF0sXG4gICAgfSxcbiAgfSxcbiAgW1dpZGdldFR5cGVWMy5Tb2NpYWxQcm9vZlYzXToge1xuICAgIHJlc3BvbnNlSWQ6ICdtb2NrLXJlc3BvbnNlLWlkJyxcbiAgICBsYW5ndWFnZTogJ2VuJyxcbiAgICB2YWx1ZXM6IHtcbiAgICAgIHRpdGxlTGFiZWw6ICdTaG9wcGluZyBBc3Npc3RhbnQnLFxuICAgICAgbnVtYmVyT2ZDdXN0b21lcnNUZXh0OiAnMTIzIGN1c3RvbWVycycsXG4gICAgICBjdXN0b21lclF1ZXJ5VGV4dDogJ2ZvdW5kIHdoYXQgdGhleSB3ZXJlIGxvb2tpbmcgZm9yLi4uJyxcbiAgICAgIHByaW1hcnlCdXR0b25UZXh0OiAnU3RhcnQgc2hvcHBpbmcnLFxuICAgICAgc2Vjb25kYXJ5QnV0dG9uVGl0bGVUZXh0OiAnUG9wdWxhciBzZWFyY2hlcycsXG4gICAgICBzZWNvbmRhcnlCdXR0b25UZXh0czogW1xuICAgICAgICAnV2h5IHNob3VsZCBJIGJ1eSBmcm9tIHlvdT8nLFxuICAgICAgICAnSG93IGRvIEkgcmV0dXJuIGFuIGl0ZW0/JyxcbiAgICAgICAgJ05ldyBhcnJpdmFscycsXG4gICAgICAgICdTYWxlIGl0ZW1zJyxcbiAgICAgICAgJ1BvcHVsYXIgaXRlbXMnLFxuICAgICAgXSxcbiAgICAgIHRleHRGaWVsZFBsYWNlaG9sZGVyVGV4dDogJ0FzayBtZSBhbnl0aGluZy4uLicsXG4gICAgfSxcbiAgfSxcbiAgW1dpZGdldFR5cGVWMy5UeXBpbmdBbmltYXRpb25WM106IHtcbiAgICByZXNwb25zZUlkOiAnbW9jay1yZXNwb25zZS1pZCcsXG4gICAgbGFuZ3VhZ2U6ICdlbicsXG4gICAgdmFsdWVzOiB7XG4gICAgICB0aXRsZUxhYmVsOiAnU2hvcHBpbmcgQXNzaXN0YW50JyxcbiAgICAgIGhlYWRsaW5lVGV4dDogJ05lZWQgaGVscCBmaW5kaW5nJyxcbiAgICAgIGFuaW1hdGVkVGV4dFNlcXVlbmNlOiBbXG4gICAgICAgICd0aGUgcGVyZmVjdCBnaWZ0IGZvciB5b3VyIGxvdmVkIG9uZXM/JyxcbiAgICAgICAgJ3NvbWV0aGluZyBzcGVjaWFsIGZvciB0aGUgaG9saWRheXM/JyxcbiAgICAgICAgJ3RoZSBpZGVhbCBwcmVzZW50IGZvciBzb21lb25lIHNwZWNpYWw/JyxcbiAgICAgIF0sXG4gICAgICBoaW50VGV4dDogJ0FzayBtZSBhbnl0aGluZycsXG4gICAgICBwcm9tcHRCdXR0b25UZXh0czogW1xuICAgICAgICAnV2hhdCBhcmUgeW91ciBiZXN0IHNlbGxlcnM/JyxcbiAgICAgICAgJ1Nob3cgbWUgc3VtbWVyIGRyZXNzZXMnLFxuICAgICAgICAnRG8geW91IGhhdmUgZnJlZSBzaGlwcGluZz8nLFxuICAgICAgICAnV2hhdCBzaXplcyBhcmUgYXZhaWxhYmxlPycsXG4gICAgICBdLFxuICAgICAgdGV4dEZpZWxkQXJpYUxhYmVsOiAnQXNrIG1lIGFueXRoaW5nJyxcbiAgICB9LFxuICB9LFxuICBbV2lkZ2V0VHlwZVYzLkNoYXRQcmV2aWV3Q29tcGFyaXNvblYzXToge1xuICAgIHJlc3BvbnNlSWQ6ICdtb2NrLXJlc3BvbnNlLWlkJyxcbiAgICBsYW5ndWFnZTogJ2VuJyxcbiAgICB2YWx1ZXM6IHtcbiAgICAgIHRpdGxlTGFiZWw6ICdTaG9wcGluZyBBc3Npc3RhbnQnLFxuICAgICAgaGVhZGxpbmVUZXh0OiAnTmVlZCBoZWxwIGNvbXBhcmluZz8nLFxuICAgICAgdGV4dEZpZWxkUGxhY2Vob2xkZXJUZXh0OiAnQXNrIG1lIGFueXRoaW5nLi4uJyxcbiAgICB9LFxuICB9LFxuICBbV2lkZ2V0VHlwZVYzLkNoYXRQcmV2aWV3TG9hZGluZ1YzXToge1xuICAgIHJlc3BvbnNlSWQ6ICdtb2NrLXJlc3BvbnNlLWlkJyxcbiAgICBsYW5ndWFnZTogJ2VuJyxcbiAgICB2YWx1ZXM6IHtcbiAgICAgIHRpdGxlTGFiZWw6ICdTaG9wcGluZyBBc3Npc3RhbnQnLFxuICAgICAgdGV4dEZpZWxkUGxhY2Vob2xkZXJUZXh0OiAnQXNrIG1lIGFueXRoaW5nLi4uJyxcbiAgICB9LFxuICB9LFxuICBbV2lkZ2V0VHlwZVYzLkNoYXRQcmV2aWV3VjNdOiB7XG4gICAgcmVzcG9uc2VJZDogJ21vY2stcmVzcG9uc2UtaWQnLFxuICAgIGxhbmd1YWdlOiAnZW4nLFxuICAgIHZhbHVlczoge1xuICAgICAgdGl0bGVMYWJlbDogJ1Nob3BwaW5nIEFzc2lzdGFudCcsXG4gICAgICB0ZXh0RmllbGRQbGFjZWhvbGRlclRleHQ6ICdBc2sgbWUgYW55dGhpbmcuLi4nLFxuICAgIH0sXG4gIH0sXG4gIFtXaWRnZXRUeXBlVjMuUHJvbXB0QnV0dG9uQ2Fyb3VzZWxXaXRoSW1hZ2VWM106IHtcbiAgICByZXNwb25zZUlkOiAnbW9jay1yZXNwb25zZS1pZCcsXG4gICAgbGFuZ3VhZ2U6ICdlbicsXG4gICAgdmFsdWVzOiB7XG4gICAgICB0aXRsZUxhYmVsOiAnTmVlZCBzb21lIGhlbHA/JyxcbiAgICAgIHByb21wdEJ1dHRvbnNUZXh0czogWydIZWxwIG1lIGZpbmQgYSBwcm9kdWN0JywgXCJXaGF0J3Mgb24geW91ciBtaW5kP1wiLCAnVGFsayB0byBhIGh1bWFuJ10sXG4gICAgICB0ZXh0RmllbGRQbGFjZWhvbGRlclRleHQ6ICdBc2sgbWUgYW55dGhpbmcuLi4nLFxuICAgIH0sXG4gIH0sXG4gIFtXaWRnZXRUeXBlVjMuUHJvZHVjdENhcmRWM106IHtcbiAgICByZXNwb25zZUlkOiAnbW9jay1yZXNwb25zZS1pZCcsXG4gICAgbGFuZ3VhZ2U6ICdlbicsXG4gICAgdmFsdWVzOiB7XG4gICAgICBoZWFkbGluZTogJ0Rpc2NvdmVyIFlvdXIgUGVyZmVjdCBTdHlsZScsXG4gICAgICBhbmltYXRlZFRleHQ6IFtcbiAgICAgICAgJ3RoZSBwZXJmZWN0IG91dGZpdCBmb3IgYW55IG9jY2FzaW9uJyxcbiAgICAgICAgJ3RyZW5kaW5nIGZhc2hpb24gdGhhdCBmaXRzIHlvdXIgbGlmZXN0eWxlJyxcbiAgICAgICAgJ3F1YWxpdHkgcGllY2VzIHRoYXQgbGFzdCcsXG4gICAgICBdLFxuICAgICAgcHJvbXB0czogW1xuICAgICAgICAnV2hhdCBhcmUgeW91ciBiZXN0IHNlbGxlcnM/JyxcbiAgICAgICAgJ1Nob3cgbWUgc3VtbWVyIGRyZXNzZXMnLFxuICAgICAgICAnRG8geW91IGhhdmUgZnJlZSBzaGlwcGluZz8nLFxuICAgICAgICAnV2hhdCBzaXplcyBhcmUgYXZhaWxhYmxlPycsXG4gICAgICAgICdDYW4gSSByZXR1cm4gaXRlbXM/JyxcbiAgICAgIF0sXG4gICAgICBwbGFjZWhvbGRlcjogJ0FzayBtZSBhbnl0aGluZy4uLicsXG4gICAgfSxcbiAgfSxcbiAgW1dpZGdldFR5cGVWMy5GbG9hdGluZ0NoYXRWM106IHtcbiAgICByZXNwb25zZUlkOiAnbW9jay1yZXNwb25zZS1pZCcsXG4gICAgbGFuZ3VhZ2U6ICdlbicsXG4gICAgdmFsdWVzOiB7XG4gICAgICB3ZWxjb21lTWVzc2FnZVRpdGxlOiAnV2VsY29tZSB0byBFbnZpdmUgQUknLFxuICAgICAgd2VsY29tZU1lc3NhZ2VUZXh0OlxuICAgICAgICAnWW91ciBwZXJzb25hbCBzaG9wcGVyIHRyYWluZWQgb24gdGhlIGNvbGxlY3RpdmUga25vd2xlZGdlIG9mIHRob3VzYW5kcyBvZiByZWFsIGV4cGVydHMhJyxcbiAgICAgIGNoYXRGb290ZXJUZXh0RmllbGRQbGFjZWhvbGRlclRleHQ6ICdBc2sgbWUgYW55dGhpbmcuLi4nLFxuICAgICAgZGlzY2xhaW1lclRleHQ6ICdUaGlzIGlzIGEgZGlzY2xhaW1lci4uLicsXG4gICAgICBsZWZ0VG9nZ2xlTGFiZWw6ICdTaG9wIHdpdGggQUknLFxuICAgICAgcmlnaHRUb2dnbGVMYWJlbDogJ0xpdmUgc3VwcG9ydCcsXG4gICAgfSxcbiAgfSxcbiAgW1dpZGdldFR5cGVWMy5GdWxsUGFnZVNhbGVzQWdlbnRWM106IHtcbiAgICByZXNwb25zZUlkOiAnbW9jay1yZXNwb25zZS1pZCcsXG4gICAgbGFuZ3VhZ2U6ICdlbicsXG4gICAgdmFsdWVzOiB7XG4gICAgICB3ZWxjb21lTWVzc2FnZVRpdGxlOiAnV2VsY29tZSB0byBFbnZpdmUgQUknLFxuICAgICAgd2VsY29tZU1lc3NhZ2VUZXh0OlxuICAgICAgICAnWW91ciBwZXJzb25hbCBzaG9wcGVyIHRyYWluZWQgb24gdGhlIGNvbGxlY3RpdmUga25vd2xlZGdlIG9mIHRob3VzYW5kcyBvZiByZWFsIGV4cGVydHMhJyxcbiAgICAgIGNoYXRGb290ZXJUZXh0RmllbGRQbGFjZWhvbGRlclRleHQ6ICdBc2sgbWUgYW55dGhpbmcuLi4nLFxuICAgICAgZGlzY2xhaW1lclRleHQ6ICdUaGlzIGlzIGEgZGlzY2xhaW1lci4uLicsXG4gICAgICB3ZWxjb21lT3ZlcmxheUxpbmtMaXN0VGl0bGU6ICdFeHBsb3JlIG1vcmUnLFxuICAgIH0sXG4gIH0sXG59O1xuXG5jb25zdCBjb252ZXJ0VG9IYXJkY29weVJlc3BvbnNlID0gKHJlc3BvbnNlOiBXaWRnZXRUZXh0KTogSGFyZGNvcHlSZXNwb25zZSA9PiAoe1xuICByZXNwb25zZUlkOiByZXNwb25zZS5yZXNwb25zZUlkLFxuICBsYW5ndWFnZTogcmVzcG9uc2UubGFuZ3VhZ2UsXG4gIHJhd1ZhbHVlczogcmVzcG9uc2UudmFsdWVzLFxuICB2YWx1ZXM6IE9iamVjdC5mcm9tRW50cmllcyhcbiAgICBPYmplY3QuZW50cmllcyhyZXNwb25zZS52YWx1ZXMpLm1hcCgoW2tleSwgdmFsdWVdKSA9PiBbXG4gICAgICB0b0NhbWVsQ2FzZShrZXkpLFxuICAgICAgdmFsdWUubGVuZ3RoID09PSAxID8gdmFsdWVbMF0udmFsdWUgOiB2YWx1ZS5tYXAodiA9PiB2LnZhbHVlKSxcbiAgICBdKSxcbiAgKSxcbn0pO1xuXG5leHBvcnQgY2xhc3MgSGFyZGNvcHlTZXJ2aWNlIGltcGxlbWVudHMgSUhhcmRjb3B5U2VydmljZSB7XG4gIGNvbnN0cnVjdG9yKHByaXZhdGUgY29uZmlnOiBIYXJkY29weVNlcnZpY2VDb25maWcpIHt9XG5cbiAgYXN5bmMgZ2V0SGFyZGNvcHkocmVxdWVzdDogSGFyZGNvcHlSZXF1ZXN0KTogUHJvbWlzZTxIYXJkY29weVJlc3BvbnNlPiB7XG4gICAgY29uc3Qgb3ZlcnJpZGVDb25maWdWZXJzaW9uID1cbiAgICAgIGdldFF1ZXJ5UGFyYW0oJ3NwaWZmeV9jb25maWdfdmVyc2lvbicpIHx8XG4gICAgICBnZXRRdWVyeVBhcmFtKCdlbnZpdmVfY29uZmlnX3ZlcnNpb24nKSB8fFxuICAgICAgZ2V0QXRvbVN0b3JlKCkuZ2V0KHJlc29sdmVkQmFzZUNvbmZpZ1ZlcnNpb25BdG9tKSB8fFxuICAgICAgdW5kZWZpbmVkO1xuXG4gICAgY29uc3Qgd2lkZ2V0VGV4dFJlcXVlc3Q6IFdpZGdldFRleHRSZXF1ZXN0ID0ge1xuICAgICAgcmVxdWVzdElkOiB1dWlkKCksXG4gICAgICB3aWRnZXRUeXBlOiByZXF1ZXN0LndpZGdldFR5cGUsXG4gICAgICBjb250ZXh0OiB0aGlzLmNvbmZpZy5nZXRBcHBEZXRhaWxzKCksXG4gICAgICBmZWF0dXJlRmxhZ3M6IHRoaXMuY29uZmlnLmdldEZlYXR1cmVGbGFncygpLFxuICAgICAgbGFuZ3VhZ2U6IG5hdmlnYXRvci5sYW5ndWFnZXNbMF0gfHwgJ2VuJyxcbiAgICAgIG92ZXJyaWRlQ29uZmlnVmVyc2lvbixcbiAgICAgIHVybDogd2luZG93LmxvY2F0aW9uLmhyZWYsXG4gICAgfTtcblxuICAgIC8vIFRoZSBXaWRnZXQgVGV4dCBldmVudHMgbXVzdCBjYXJyeSB0aGUgQVBJLWZhY2luZyAodHJhbnNsYXRlZCkgd2lkZ2V0IHR5cGUsIG5vdCB0aGVcbiAgICAvLyBpbnRlcm5hbCBvbmU6IHRoZSBBSS1zdWdnZXN0aW9ucyBhY2NvdW50aW5nIGpvaW5zIHRoZXNlIGV2ZW50cyBhZ2FpbnN0IHRoZSBiYWNrZW5kXG4gICAgLy8gcmVnaXN0cnksIHdoaWNoIGlzIGtleWVkIGJ5IHRoZSB3aXJlIHR5cGUgKGUuZy4gUHJvZHVjdENhcmRWMyBpcyBzZW50IOKAlCBhbmQgc3RvcmVkIOKAlFxuICAgIC8vIGFzIEltYWdlUHJvbXB0Q2FyZFYzKS4gU3RhbXBpbmcgdGhlIGludGVybmFsIG5hbWUgaGVyZSB6ZXJvZWQgdGhvc2Ugc3RhdHMuIEFuXG4gICAgLy8gdW5tYXBwZWQgdHlwZSBrZWVwcyB0aGUgaW50ZXJuYWwgbmFtZSDigJQgYW5hbHl0aWNzIG11c3QgbmV2ZXIgYmxvY2sgc2VydmluZy5cbiAgICBsZXQgd2lyZVdpZGdldFR5cGU6IHN0cmluZyA9IHJlcXVlc3Qud2lkZ2V0VHlwZTtcbiAgICB0cnkge1xuICAgICAgd2lyZVdpZGdldFR5cGUgPSBjb3JlV2lkZ2V0VHlwZVRvQXBpV2lkZ2V0VHlwZShyZXF1ZXN0LndpZGdldFR5cGUpO1xuICAgIH0gY2F0Y2gge1xuICAgICAgbG9nZ2VyLmxvZ0RlYnVnKCdubyB3aXJlIG1hcHBpbmcgZm9yIHdpZGdldCB0eXBlIOKAlCBrZWVwaW5nIGludGVybmFsIG5hbWUnLCB7XG4gICAgICAgIHdpZGdldFR5cGU6IHJlcXVlc3Qud2lkZ2V0VHlwZSxcbiAgICAgIH0pO1xuICAgIH1cblxuICAgIHRoaXMuY29uZmlnLnRyYWNrRXZlbnQ/Lih7XG4gICAgICBldmVudE5hbWU6IEVudml2ZU1ldHJpY3NFdmVudE5hbWUuV2lkZ2V0VGV4dFJlcXVlc3QsXG4gICAgICBldmVudFByb3BzOiB7XG4gICAgICAgIHdpZGdldF90eXBlOiB3aXJlV2lkZ2V0VHlwZSxcbiAgICAgICAgcmVxdWVzdF9pZDogd2lkZ2V0VGV4dFJlcXVlc3QucmVxdWVzdElkLFxuICAgICAgfSxcbiAgICB9KTtcblxuICAgIC8vIFJlYXNvbiB0aGUgQVBJIHBhdGggZmFpbGVkLCBzdXJmYWNlZCBvbiB0aGUgc3RhdGljIGZhbGxiYWNrIGZvciB3aWRnZXQtbG9hZCBkaWFnbm9zdGljcy5cbiAgICBsZXQgZmFsbGJhY2tSZWFzb246ICd0aW1lb3V0JyB8ICdlcnJvcicgfCB1bmRlZmluZWQ7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgd2l0aFRpbWVvdXQoXG4gICAgICAgIENvbW1lcmNlQXBpQ2xpZW50LmdldEhhcmRjb3B5KHdpZGdldFRleHRSZXF1ZXN0KSxcbiAgICAgICAgSEFSRENPUFlfVElNRU9VVF9NUyxcbiAgICAgICk7XG4gICAgICBpZiAocmVzcG9uc2UpIHtcbiAgICAgICAgY29uc3QgY29udmVydGVkUmVzcG9uc2UgPSBjb252ZXJ0VG9IYXJkY29weVJlc3BvbnNlKHJlc3BvbnNlKTtcbiAgICAgICAgbG9nZ2VyLmxvZ0RlYnVnKCdjb252ZXJ0ZWQgcmVzcG9uc2UnLCBjb252ZXJ0ZWRSZXNwb25zZSk7XG4gICAgICAgIHRoaXMuY29uZmlnLnRyYWNrRXZlbnQ/Lih7XG4gICAgICAgICAgZXZlbnROYW1lOiBFbnZpdmVNZXRyaWNzRXZlbnROYW1lLldpZGdldFRleHRSZXNwb25zZSxcbiAgICAgICAgICBldmVudFByb3BzOiB7XG4gICAgICAgICAgICB3aWRnZXRfdHlwZTogd2lyZVdpZGdldFR5cGUsXG4gICAgICAgICAgICByZXNwb25zZV9pZDogcmVzcG9uc2UucmVzcG9uc2VJZCxcbiAgICAgICAgICAgIHdpZGdldF90ZXh0OiByZXNwb25zZS52YWx1ZXMsXG4gICAgICAgICAgICByZXF1ZXN0X2lkOiB3aWRnZXRUZXh0UmVxdWVzdC5yZXF1ZXN0SWQsXG4gICAgICAgICAgICAvLyBXaGljaCBtZXRob2Qgc2VydmVkIGVhY2ggb3ZlcnJpZGRlbiBzbG90IOKAlCB0cmFpbmluZy9hdHRyaWJ1dGlvblxuICAgICAgICAgICAgLy8gcmVhZHMgdGhpcyBmcm9tIHRoZSBhbmFseXRpY3MgZXZlbnQ7IHBhc3NlZCB0aHJvdWdoIHZlcmJhdGltLlxuICAgICAgICAgICAgLi4uKHJlc3BvbnNlLmFpU3VnZ2VzdGlvbnNBdHRyaWJ1dGlvbkJ5U2xvdFxuICAgICAgICAgICAgICA/IHsgYWlfc3VnZ2VzdGlvbnNfYXR0cmlidXRpb25fYnlfc2xvdDogcmVzcG9uc2UuYWlTdWdnZXN0aW9uc0F0dHJpYnV0aW9uQnlTbG90IH1cbiAgICAgICAgICAgICAgOiB7fSksXG4gICAgICAgICAgfSxcbiAgICAgICAgfSk7XG4gICAgICAgIHJldHVybiB7IC4uLmNvbnZlcnRlZFJlc3BvbnNlLCBzZXJ2ZWRGcm9tOiAnYXBpJyB9O1xuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBsb2dnZXIubG9nRXJyb3IoJ2Vycm9yIGdldHRpbmcgaGFyZGNvcHkgZnJvbSBiYWNrZW5kJywgZXJyb3IpO1xuICAgICAgZmFsbGJhY2tSZWFzb24gPVxuICAgICAgICBlcnJvciBpbnN0YW5jZW9mIEVycm9yICYmIGVycm9yLm1lc3NhZ2UuaW5jbHVkZXMoJ3RpbWVkIG91dCcpID8gJ3RpbWVvdXQnIDogJ2Vycm9yJztcbiAgICB9XG5cbiAgICBjb25zdCBtb2NrUmVzcG9uc2UgPSBNT0NLX0hBUkRDT1BZX1JFU1BPTlNFW3JlcXVlc3Qud2lkZ2V0VHlwZV07XG4gICAgaWYgKG1vY2tSZXNwb25zZSkge1xuICAgICAgbG9nZ2VyLmxvZ0RlYnVnKCdubyByZWFsIHJlc3BvbnNlLCB1c2luZyBtb2NrJywgbW9ja1Jlc3BvbnNlKTtcbiAgICAgIHJldHVybiB7IC4uLm1vY2tSZXNwb25zZSwgc2VydmVkRnJvbTogJ3N0YXRpYycsIGZhbGxiYWNrUmVhc29uOiBmYWxsYmFja1JlYXNvbiA/PyAnZXJyb3InIH07XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcihgTm8gaGFyZGNvcHkgcmVzcG9uc2UgZm91bmQgZm9yIHdpZGdldCB0eXBlOiAke3JlcXVlc3Qud2lkZ2V0VHlwZX1gKTtcbiAgfVxufVxuXG5leHBvcnQgY2xhc3MgTW9ja0hhcmRjb3B5U2VydmljZSBpbXBsZW1lbnRzIElIYXJkY29weVNlcnZpY2Uge1xuICBjb25zdHJ1Y3Rvcihwcml2YXRlIG92ZXJyaWRlczogUGFydGlhbDxSZWNvcmQ8V2lkZ2V0VHlwZVYzLCBIYXJkY29weVJlc3BvbnNlPj4gPSB7fSkge31cblxuICBhc3luYyBnZXRIYXJkY29weShyZXF1ZXN0OiBIYXJkY29weVJlcXVlc3QpOiBQcm9taXNlPEhhcmRjb3B5UmVzcG9uc2U+IHtcbiAgICBjb25zdCBmYWxsYmFja1dpZGdldFR5cGUgPVxuICAgICAgcmVxdWVzdC53aWRnZXRUeXBlID09PSBXaWRnZXRUeXBlVjMuUHJvZHVjdENhcmRWM1xuICAgICAgICA/IFdpZGdldFR5cGVWMy5JbWFnZVByb21wdENhcmRWM1xuICAgICAgICA6IHJlcXVlc3Qud2lkZ2V0VHlwZTtcblxuICAgIGNvbnN0IHJlc3BvbnNlID1cbiAgICAgIHRoaXMub3ZlcnJpZGVzW3JlcXVlc3Qud2lkZ2V0VHlwZV0gPz9cbiAgICAgIHRoaXMub3ZlcnJpZGVzW2ZhbGxiYWNrV2lkZ2V0VHlwZV0gPz9cbiAgICAgIE1PQ0tfSEFSRENPUFlfUkVTUE9OU0VbcmVxdWVzdC53aWRnZXRUeXBlXTtcblxuICAgIGlmICghcmVzcG9uc2UpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgTm8gaGFyZGNvcHkgcmVzcG9uc2UgZm91bmQgZm9yIHdpZGdldCB0eXBlOiAke3JlcXVlc3Qud2lkZ2V0VHlwZX1gKTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3BvbnNlO1xuICB9XG59XG4iXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7O0FBZUEsTUFBTSxTQUFTLElBQUlBLGVBQU8sa0JBQWtCO0FBYTVDLE1BQWEsc0JBQXNCO0FBS25DLE1BQU0sZUFBa0IsU0FBcUIsT0FBMkI7Q0FDdEUsSUFBSUM7Q0FDSixNQUFNLFVBQVUsSUFBSSxTQUFnQixHQUFHLFdBQVc7QUFDaEQsVUFBUSxpQkFBaUIsdUJBQU8sSUFBSSxNQUFNLG9DQUFvQyxHQUFHLElBQUksQ0FBQyxFQUFFLEdBQUc7R0FDM0Y7QUFDRixRQUFPLFFBQVEsS0FBSyxDQUFDLFNBQVMsUUFBUSxDQUFDLENBQUMsY0FBYyxhQUFhLE1BQU0sQ0FBQzs7QUE4QjVFLE1BQWFDLHlCQUEwRTtFQUNwRixhQUFhLG1CQUFtQjtFQUMvQixZQUFZQyxJQUFNO0VBQ2xCLFVBQVU7RUFDVixRQUFRLEVBQ04sbUJBQW1CO0dBQ2pCO0dBQ0E7R0FDQTtHQUNBO0dBQ0E7R0FDRCxFQUNGO0VBQ0Y7RUFDQSxhQUFhLHlCQUF5QjtFQUNyQyxZQUFZO0VBQ1osVUFBVTtFQUNWLFFBQVE7R0FDTixZQUFZO0dBQ1osbUJBQW1CO0lBQ2pCO0lBQ0E7SUFDQTtJQUNBO0lBQ0E7SUFDRDtHQUNGO0VBQ0Y7RUFDQSxhQUFhLGdCQUFnQjtFQUM1QixZQUFZO0VBQ1osVUFBVTtFQUNWLFFBQVE7R0FDTixZQUFZO0dBQ1osdUJBQXVCO0dBQ3ZCLG1CQUFtQjtHQUNuQixtQkFBbUI7R0FDbkIsMEJBQTBCO0dBQzFCLHNCQUFzQjtJQUNwQjtJQUNBO0lBQ0E7SUFDQTtJQUNBO0lBQ0Q7R0FDRCwwQkFBMEI7R0FDM0I7RUFDRjtFQUNBLGFBQWEsb0JBQW9CO0VBQ2hDLFlBQVk7RUFDWixVQUFVO0VBQ1YsUUFBUTtHQUNOLFlBQVk7R0FDWixjQUFjO0dBQ2Qsc0JBQXNCO0lBQ3BCO0lBQ0E7SUFDQTtJQUNEO0dBQ0QsVUFBVTtHQUNWLG1CQUFtQjtJQUNqQjtJQUNBO0lBQ0E7SUFDQTtJQUNEO0dBQ0Qsb0JBQW9CO0dBQ3JCO0VBQ0Y7RUFDQSxhQUFhLDBCQUEwQjtFQUN0QyxZQUFZO0VBQ1osVUFBVTtFQUNWLFFBQVE7R0FDTixZQUFZO0dBQ1osY0FBYztHQUNkLDBCQUEwQjtHQUMzQjtFQUNGO0VBQ0EsYUFBYSx1QkFBdUI7RUFDbkMsWUFBWTtFQUNaLFVBQVU7RUFDVixRQUFRO0dBQ04sWUFBWTtHQUNaLDBCQUEwQjtHQUMzQjtFQUNGO0VBQ0EsYUFBYSxnQkFBZ0I7RUFDNUIsWUFBWTtFQUNaLFVBQVU7RUFDVixRQUFRO0dBQ04sWUFBWTtHQUNaLDBCQUEwQjtHQUMzQjtFQUNGO0VBQ0EsYUFBYSxrQ0FBa0M7RUFDOUMsWUFBWTtFQUNaLFVBQVU7RUFDVixRQUFRO0dBQ04sWUFBWTtHQUNaLG9CQUFvQjtJQUFDO0lBQTBCO0lBQXdCO0lBQWtCO0dBQ3pGLDBCQUEwQjtHQUMzQjtFQUNGO0VBQ0EsYUFBYSxnQkFBZ0I7RUFDNUIsWUFBWTtFQUNaLFVBQVU7RUFDVixRQUFRO0dBQ04sVUFBVTtHQUNWLGNBQWM7SUFDWjtJQUNBO0lBQ0E7SUFDRDtHQUNELFNBQVM7SUFDUDtJQUNBO0lBQ0E7SUFDQTtJQUNBO0lBQ0Q7R0FDRCxhQUFhO0dBQ2Q7RUFDRjtFQUNBLGFBQWEsaUJBQWlCO0VBQzdCLFlBQVk7RUFDWixVQUFVO0VBQ1YsUUFBUTtHQUNOLHFCQUFxQjtHQUNyQixvQkFDRTtHQUNGLG9DQUFvQztHQUNwQyxnQkFBZ0I7R0FDaEIsaUJBQWlCO0dBQ2pCLGtCQUFrQjtHQUNuQjtFQUNGO0VBQ0EsYUFBYSx1QkFBdUI7RUFDbkMsWUFBWTtFQUNaLFVBQVU7RUFDVixRQUFRO0dBQ04scUJBQXFCO0dBQ3JCLG9CQUNFO0dBQ0Ysb0NBQW9DO0dBQ3BDLGdCQUFnQjtHQUNoQiw2QkFBNkI7R0FDOUI7RUFDRjtDQUNGO0FBRUQsTUFBTSw2QkFBNkIsY0FBNEM7Q0FDN0UsWUFBWSxTQUFTO0NBQ3JCLFVBQVUsU0FBUztDQUNuQixXQUFXLFNBQVM7Q0FDcEIsUUFBUSxPQUFPLFlBQ2IsT0FBTyxRQUFRLFNBQVMsT0FBTyxDQUFDLEtBQUssQ0FBQyxLQUFLLFdBQVcsQ0FDcEQsWUFBWSxJQUFJLEVBQ2hCLE1BQU0sV0FBVyxJQUFJLE1BQU0sR0FBRyxRQUFRLE1BQU0sS0FBSSxNQUFLLEVBQUUsTUFBTSxDQUM5RCxDQUFDLENBQ0g7Q0FDRjtBQUVELElBQWEsa0JBQWIsTUFBeUQ7Q0FDdkQsWUFBWSxBQUFRQyxRQUErQjtFQUEvQjs7Q0FFcEIsTUFBTSxZQUFZLFNBQXFEO0VBQ3JFLE1BQU0sd0JBQ0osY0FBYyx3QkFBd0IsSUFDdEMsY0FBYyx3QkFBd0IsSUFDdEMsY0FBYyxDQUFDLElBQUksOEJBQThCLElBQ2pEO0VBRUYsTUFBTUMsb0JBQXVDO0dBQzNDLFdBQVdGLElBQU07R0FDakIsWUFBWSxRQUFRO0dBQ3BCLFNBQVMsS0FBSyxPQUFPLGVBQWU7R0FDcEMsY0FBYyxLQUFLLE9BQU8saUJBQWlCO0dBQzNDLFVBQVUsVUFBVSxVQUFVLE1BQU07R0FDcEM7R0FDQSxLQUFLLE9BQU8sU0FBUztHQUN0QjtFQU9ELElBQUlHLGlCQUF5QixRQUFRO0FBQ3JDLE1BQUk7QUFDRixvQkFBaUIsOEJBQThCLFFBQVEsV0FBVztVQUM1RDtBQUNOLFVBQU8sU0FBUywyREFBMkQsRUFDekUsWUFBWSxRQUFRLFlBQ3JCLENBQUM7O0FBR0osT0FBSyxPQUFPLGFBQWE7R0FDdkIsV0FBVyx1QkFBdUI7R0FDbEMsWUFBWTtJQUNWLGFBQWE7SUFDYixZQUFZLGtCQUFrQjtJQUMvQjtHQUNGLENBQUM7RUFHRixJQUFJQztBQUNKLE1BQUk7R0FDRixNQUFNLFdBQVcsTUFBTSxZQUNyQkMscUJBQWtCLFlBQVksa0JBQWtCLEVBQ2hELG9CQUNEO0FBQ0QsT0FBSSxVQUFVO0lBQ1osTUFBTSxvQkFBb0IsMEJBQTBCLFNBQVM7QUFDN0QsV0FBTyxTQUFTLHNCQUFzQixrQkFBa0I7QUFDeEQsU0FBSyxPQUFPLGFBQWE7S0FDdkIsV0FBVyx1QkFBdUI7S0FDbEMsWUFBWTtNQUNWLGFBQWE7TUFDYixhQUFhLFNBQVM7TUFDdEIsYUFBYSxTQUFTO01BQ3RCLFlBQVksa0JBQWtCO01BRzlCLEdBQUksU0FBUyxpQ0FDVCxFQUFFLG9DQUFvQyxTQUFTLGdDQUFnQyxHQUMvRSxFQUFFO01BQ1A7S0FDRixDQUFDO0FBQ0YsV0FBTztLQUFFLEdBQUc7S0FBbUIsWUFBWTtLQUFPOztXQUU3QyxPQUFPO0FBQ2QsVUFBTyxTQUFTLHVDQUF1QyxNQUFNO0FBQzdELG9CQUNFLGlCQUFpQixTQUFTLE1BQU0sUUFBUSxTQUFTLFlBQVksR0FBRyxZQUFZOztFQUdoRixNQUFNLGVBQWUsdUJBQXVCLFFBQVE7QUFDcEQsTUFBSSxjQUFjO0FBQ2hCLFVBQU8sU0FBUyxnQ0FBZ0MsYUFBYTtBQUM3RCxVQUFPO0lBQUUsR0FBRztJQUFjLFlBQVk7SUFBVSxnQkFBZ0Isa0JBQWtCO0lBQVM7O0FBRTdGLFFBQU0sSUFBSSxNQUFNLCtDQUErQyxRQUFRLGFBQWE7OztBQUl4RixJQUFhLHNCQUFiLE1BQTZEO0NBQzNELFlBQVksQUFBUUMsWUFBNkQsRUFBRSxFQUFFO0VBQWpFOztDQUVwQixNQUFNLFlBQVksU0FBcUQ7RUFDckUsTUFBTSxxQkFDSixRQUFRLGVBQWUsYUFBYSxnQkFDaEMsYUFBYSxvQkFDYixRQUFRO0VBRWQsTUFBTSxXQUNKLEtBQUssVUFBVSxRQUFRLGVBQ3ZCLEtBQUssVUFBVSx1QkFDZix1QkFBdUIsUUFBUTtBQUVqQyxNQUFJLENBQUMsU0FDSCxPQUFNLElBQUksTUFBTSwrQ0FBK0MsUUFBUSxhQUFhO0FBRXRGLFNBQU8ifQ==
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envive-ai/react-hooks",
3
- "version": "0.3.63",
3
+ "version": "0.3.64",
4
4
  "description": "React hooks for connecting to Envive AI services.",
5
5
  "keywords": [
6
6
  "react",
@@ -414,6 +414,10 @@
414
414
  "import": "./dist/hooks/WidgetInteraction/utils.js",
415
415
  "require": "./dist/hooks/WidgetInteraction/utils.cjs"
416
416
  },
417
+ "./hooks/WidgetLoadDiagnostics": {
418
+ "import": "./dist/hooks/WidgetLoadDiagnostics/index.js",
419
+ "require": "./dist/hooks/WidgetLoadDiagnostics/index.cjs"
420
+ },
417
421
  "./interceptors": {
418
422
  "import": "./dist/interceptors/index.js",
419
423
  "require": "./dist/interceptors/index.cjs"
@@ -46,4 +46,10 @@ export enum FeatureGates {
46
46
  // instead of the legacy getProductsConfigByVersion / getColorsAndFrontend
47
47
  // path.
48
48
  UseUnifiedConfig = 'use_unified_config',
49
+ // Kill switch for the [Envive] Diagnostics widget-load events (mount / outcome / stall).
50
+ // Diagnostics are ON by default for every org so we can gather a cross-merchant baseline and
51
+ // tell whether one merchant's stuck rate is anomalous. Set this gate true — globally or
52
+ // per-org, via Statsig — to shut them off (e.g. after the data-gathering window, or if the
53
+ // volume is a problem).
54
+ IsWidgetDiagnosticsDisabled = 'is_widget_diagnostics_disabled',
49
55
  }
@@ -40,12 +40,19 @@ export const useIntersection = (
40
40
  element: RefObject<HTMLElement>,
41
41
  rootMargin: string,
42
42
  logContext?: Record<string, unknown>,
43
+ // Called the first time visibility is forced by the reconcile fallback (observer stayed
44
+ // silent for an on-screen element). Lets callers record that this widget's visibility came
45
+ // from the fallback rather than the observer — the diagnostic for observer failures.
46
+ onFallbackEngaged?: () => void,
43
47
  ) => {
44
48
  const [isVisible, setIsVisible] = useState(false);
45
49
  const observerRef = useRef<IntersectionObserver | null>(null);
46
50
  const observedNodeRef = useRef<HTMLElement | null>(null);
47
51
  const logContextRef = useRef(logContext);
48
52
  logContextRef.current = logContext;
53
+ const onFallbackEngagedRef = useRef(onFallbackEngaged);
54
+ onFallbackEngagedRef.current = onFallbackEngaged;
55
+ const fallbackEngagedRef = useRef(false);
49
56
 
50
57
  // Runs after every render so it catches when element.current transitions from null to a
51
58
  // mounted DOM node, AND when the node under the ref is replaced. The replacement case is
@@ -112,6 +119,10 @@ export const useIntersection = (
112
119
  },
113
120
  );
114
121
  }
122
+ if (!fallbackEngagedRef.current) {
123
+ fallbackEngagedRef.current = true;
124
+ onFallbackEngagedRef.current?.();
125
+ }
115
126
  setIsVisible(true);
116
127
  }
117
128
  };
@@ -0,0 +1,64 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { RefObject } from 'react';
3
+ import { useTrackComponentVisibleEvent } from '../useTrackComponentVisibleEvent';
4
+
5
+ // Control the observer's isVisible and capture the fallback callback.
6
+ let mockIsVisible = false;
7
+ let capturedOnFallback: (() => void) | undefined;
8
+
9
+ vi.mock('src/hooks/Intersection/useIntersection', () => ({
10
+ useIntersection: (
11
+ _el: unknown,
12
+ _rm: unknown,
13
+ _ctx: unknown,
14
+ onFallback?: () => void,
15
+ ): boolean => {
16
+ capturedOnFallback = onFallback;
17
+ return mockIsVisible;
18
+ },
19
+ }));
20
+
21
+ vi.mock('src/contexts/amplitudeContext/amplitudeContext', () => ({
22
+ useAmplitude: () => ({ trackEvent: vi.fn(), isReady: true }),
23
+ }));
24
+
25
+ vi.mock('jotai', async (importOriginal: () => Promise<typeof import('jotai')>) => {
26
+ const actual = await importOriginal();
27
+ return { ...actual, useAtomValue: () => null };
28
+ });
29
+
30
+ const ref = { current: document.createElement('div') } as RefObject<HTMLElement>;
31
+
32
+ beforeEach(() => {
33
+ mockIsVisible = false;
34
+ capturedOnFallback = undefined;
35
+ });
36
+
37
+ describe('useTrackComponentVisibleEvent visibilitySource', () => {
38
+ it("stays 'pending' until visibility is established (so an eager load never claims 'observer')", () => {
39
+ const { result } = renderHook(() => useTrackComponentVisibleEvent(ref, {}));
40
+ expect(result.current.isVisible).toBe(false);
41
+ expect(result.current.visibilitySource).toBe('pending');
42
+ });
43
+
44
+ it("resolves to 'observer' when the observer reports the element visible", () => {
45
+ const { result, rerender } = renderHook(() => useTrackComponentVisibleEvent(ref, {}));
46
+ act(() => {
47
+ mockIsVisible = true;
48
+ rerender();
49
+ });
50
+ expect(result.current.visibilitySource).toBe('observer');
51
+ });
52
+
53
+ it("records 'fallback' when the reconcile fallback forces visibility (observer stayed silent)", () => {
54
+ const { result, rerender } = renderHook(() => useTrackComponentVisibleEvent(ref, {}));
55
+ act(() => {
56
+ // Fallback fires first (as it does in useIntersection, before its own setIsVisible)...
57
+ capturedOnFallback?.();
58
+ // ...then visibility flips true.
59
+ mockIsVisible = true;
60
+ rerender();
61
+ });
62
+ expect(result.current.visibilitySource).toBe('fallback');
63
+ });
64
+ });
@@ -1,5 +1,5 @@
1
1
  import { useAtomValue } from 'jotai';
2
- import { RefObject, useEffect, useRef } from 'react';
2
+ import { RefObject, useEffect, useRef, useState } from 'react';
3
3
  import Logger from 'src/application/logging/logger';
4
4
  import { pageVariantInfoAtom } from 'src/atoms/app';
5
5
  import { useAmplitude } from 'src/contexts/amplitudeContext/amplitudeContext';
@@ -34,10 +34,29 @@ export const useTrackComponentVisibleEvent = (
34
34
  eventProps?: Record<string, unknown>,
35
35
  rootMargin: string = '0px',
36
36
  enabled: boolean = true,
37
- ): { isVisible: boolean } => {
37
+ ): { isVisible: boolean; visibilitySource: 'observer' | 'fallback' | 'pending' } => {
38
+ // How visibility was established, for widget-load diagnostics:
39
+ // - 'pending' — not confirmed by either path yet. Stays this way for a `deferLoading: false`
40
+ // widget whose requests settle before the observer ever reports, so we don't
41
+ // falsely claim 'observer' for a load that never waited on visibility.
42
+ // - 'observer' — the IntersectionObserver reported the element visible.
43
+ // - 'fallback' — the reconcile fallback had to force it (observer stayed silent on-screen).
44
+ // State (not a ref) so consumers reliably re-render when the source resolves.
45
+ const [visibilitySource, setVisibilitySource] = useState<'observer' | 'fallback' | 'pending'>(
46
+ 'pending',
47
+ );
38
48
  // eventProps (widget_config_id, widget_type, …) double as log context so the hook's
39
49
  // visibility-fallback error identifies which widget it rescued.
40
- const isVisible = useIntersection(element, rootMargin, eventProps);
50
+ const isVisible = useIntersection(element, rootMargin, eventProps, () => {
51
+ // The observer stayed silent on-screen and the fallback forced visibility. This is recorded
52
+ // before useIntersection's own setIsVisible, so it wins over the 'observer' resolution below.
53
+ setVisibilitySource(prev => (prev === 'pending' ? 'fallback' : prev));
54
+ });
55
+ // Resolve 'pending' → 'observer' once the observer establishes visibility; a 'fallback'
56
+ // already recorded above is preserved.
57
+ useEffect(() => {
58
+ if (isVisible) setVisibilitySource(prev => (prev === 'pending' ? 'observer' : prev));
59
+ }, [isVisible]);
41
60
  const hasTrackedEvent = useRef(false);
42
61
  const { trackEvent } = useAmplitude();
43
62
  const variantInfo = useAtomValue(pageVariantInfoAtom);
@@ -87,5 +106,5 @@ export const useTrackComponentVisibleEvent = (
87
106
  });
88
107
  hasTrackedEvent.current = true;
89
108
  }, [enabled, isVisible, eventProps, trackEvent]);
90
- return { isVisible };
109
+ return { isVisible, visibilitySource };
91
110
  };
@@ -0,0 +1,161 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { WidgetTypeV3 } from 'src/contexts/typesV3';
3
+ import {
4
+ WIDGET_LOAD_STALL_MS,
5
+ WidgetLoadDiagnosticsInput,
6
+ useWidgetLoadDiagnostics,
7
+ } from '../useWidgetLoadDiagnostics';
8
+
9
+ const trackEvent = vi.fn();
10
+
11
+ vi.mock('src/contexts/amplitudeContext/amplitudeContext', () => ({
12
+ useAmplitude: () => ({ trackEvent, isReady: true }),
13
+ }));
14
+
15
+ // Keep jotai's atom() working; only stub useAtomValue so we don't need a Provider.
16
+ vi.mock('jotai', async (importOriginal: () => Promise<typeof import('jotai')>) => {
17
+ const actual = await importOriginal();
18
+ return { ...actual, useAtomValue: () => null };
19
+ });
20
+
21
+ const base: WidgetLoadDiagnosticsInput = {
22
+ enabled: true,
23
+ loadId: 'load-1',
24
+ widgetType: WidgetTypeV3.ProductCardV3,
25
+ widgetConfigId: 'cfg-1',
26
+ startLoading: false,
27
+ ownRequestsSettled: false,
28
+ hardcopyContent: undefined,
29
+ visibilitySource: 'observer',
30
+ pendingRequests: ['hardcopy'],
31
+ hardcopyErrorReason: undefined,
32
+ };
33
+
34
+ // Pull the eventProps of the diagnostics call whose `type` matches.
35
+ const propsOfType = (type: string): Record<string, unknown> | undefined =>
36
+ trackEvent.mock.calls
37
+ .map((c: [{ eventProps: Record<string, unknown> }]) => c[0].eventProps)
38
+ .find((p: Record<string, unknown>) => p.type === type);
39
+
40
+ beforeEach(() => {
41
+ trackEvent.mockClear();
42
+ });
43
+
44
+ describe('useWidgetLoadDiagnostics', () => {
45
+ it('emits nothing while disabled', () => {
46
+ renderHook(() => useWidgetLoadDiagnostics({ ...base, enabled: false, startLoading: true }));
47
+ expect(trackEvent).not.toHaveBeenCalled();
48
+ });
49
+
50
+ it('emits widget_mounted on mount regardless of visibility (the ungated baseline)', () => {
51
+ renderHook(() => useWidgetLoadDiagnostics({ ...base, startLoading: false }));
52
+ const mounted = propsOfType('widget_mounted');
53
+ expect(mounted).toBeDefined();
54
+ expect(mounted).toMatchObject({ load_id: 'load-1', widget_type: WidgetTypeV3.ProductCardV3 });
55
+ // No outcome/stall — it never became visible.
56
+ expect(propsOfType('widget_load_outcome')).toBeUndefined();
57
+ });
58
+
59
+ it('emits content_loaded when own requests settle with live api content', () => {
60
+ const { rerender } = renderHook(props => useWidgetLoadDiagnostics(props), {
61
+ initialProps: { ...base, startLoading: true },
62
+ });
63
+ rerender({
64
+ ...base,
65
+ startLoading: true,
66
+ ownRequestsSettled: true,
67
+ hardcopyContent: { responseId: 'r', language: 'en', values: {}, servedFrom: 'api' },
68
+ pendingRequests: [],
69
+ });
70
+ const outcome = propsOfType('widget_load_outcome');
71
+ expect(outcome).toMatchObject({ outcome: 'content_loaded', visibility_source: 'observer' });
72
+ });
73
+
74
+ it('emits static_fallback with the fallback reason', () => {
75
+ const { rerender } = renderHook(props => useWidgetLoadDiagnostics(props), {
76
+ initialProps: { ...base, startLoading: true },
77
+ });
78
+ rerender({
79
+ ...base,
80
+ startLoading: true,
81
+ ownRequestsSettled: true,
82
+ hardcopyContent: {
83
+ responseId: 'r',
84
+ language: 'en',
85
+ values: {},
86
+ servedFrom: 'static',
87
+ fallbackReason: 'timeout',
88
+ },
89
+ });
90
+ expect(propsOfType('widget_load_outcome')).toMatchObject({
91
+ outcome: 'static_fallback',
92
+ reason: 'timeout',
93
+ });
94
+ });
95
+
96
+ it('emits error when the load settles without content', () => {
97
+ const { rerender } = renderHook(props => useWidgetLoadDiagnostics(props), {
98
+ initialProps: { ...base, startLoading: true },
99
+ });
100
+ rerender({
101
+ ...base,
102
+ startLoading: true,
103
+ ownRequestsSettled: true,
104
+ hardcopyContent: undefined,
105
+ hardcopyErrorReason: 'hardcopy_error',
106
+ });
107
+ expect(propsOfType('widget_load_outcome')).toMatchObject({
108
+ outcome: 'error',
109
+ reason: 'hardcopy_error',
110
+ });
111
+ });
112
+
113
+ it('carries visibility_source=fallback through the outcome', () => {
114
+ const { rerender } = renderHook(props => useWidgetLoadDiagnostics(props), {
115
+ initialProps: { ...base, startLoading: true, visibilitySource: 'fallback' as const },
116
+ });
117
+ rerender({
118
+ ...base,
119
+ startLoading: true,
120
+ visibilitySource: 'fallback',
121
+ ownRequestsSettled: true,
122
+ hardcopyContent: { responseId: 'r', language: 'en', values: {}, servedFrom: 'api' },
123
+ });
124
+ expect(propsOfType('widget_load_outcome')).toMatchObject({ visibility_source: 'fallback' });
125
+ });
126
+
127
+ describe('stall (abandonment-safe)', () => {
128
+ beforeEach(() => vi.useFakeTimers());
129
+ afterEach(() => vi.useRealTimers());
130
+
131
+ it('emits widget_load_stalled if still loading at the threshold', () => {
132
+ renderHook(() => useWidgetLoadDiagnostics({ ...base, startLoading: true }));
133
+ act(() => {
134
+ vi.advanceTimersByTime(WIDGET_LOAD_STALL_MS);
135
+ });
136
+ expect(propsOfType('widget_load_stalled')).toMatchObject({ pending_requests: ['hardcopy'] });
137
+ });
138
+
139
+ it('does NOT emit a stall when the widget unmounts first (navigated away)', () => {
140
+ const { unmount } = renderHook(() =>
141
+ useWidgetLoadDiagnostics({ ...base, startLoading: true }),
142
+ );
143
+ unmount();
144
+ act(() => {
145
+ vi.advanceTimersByTime(WIDGET_LOAD_STALL_MS * 2);
146
+ });
147
+ expect(propsOfType('widget_load_stalled')).toBeUndefined();
148
+ });
149
+
150
+ it('does NOT emit a stall once the load has settled', () => {
151
+ const { rerender } = renderHook(props => useWidgetLoadDiagnostics(props), {
152
+ initialProps: { ...base, startLoading: true },
153
+ });
154
+ rerender({ ...base, startLoading: true, ownRequestsSettled: true });
155
+ act(() => {
156
+ vi.advanceTimersByTime(WIDGET_LOAD_STALL_MS * 2);
157
+ });
158
+ expect(propsOfType('widget_load_stalled')).toBeUndefined();
159
+ });
160
+ });
161
+ });
@@ -0,0 +1,5 @@
1
+ export { useWidgetLoadDiagnostics, WIDGET_LOAD_STALL_MS } from './useWidgetLoadDiagnostics';
2
+ export type {
3
+ WidgetLoadDiagnosticsInput,
4
+ WidgetVisibilitySource,
5
+ } from './useWidgetLoadDiagnostics';
@@ -0,0 +1,178 @@
1
+ import { useAtomValue } from 'jotai';
2
+ import { useEffect, useRef } from 'react';
3
+ import { pageVariantInfoAtom } from 'src/atoms/app';
4
+ import { useAmplitude } from 'src/contexts/amplitudeContext/amplitudeContext';
5
+ import { HardcopyResponse } from 'src/contexts/hardcopyContext';
6
+ import { PageVariantInfo } from 'src/contexts/pageContext/types';
7
+ import { WidgetTypeV3 } from 'src/contexts/typesV3';
8
+ import { extractPageContext } from 'src/hooks/WidgetInteraction/utils';
9
+ import { EnviveMetricsEventName } from 'src/services/amplitudeService/amplitudeService';
10
+
11
+ // Fired if a widget is still loading this long after it became visible. Kept at/just above
12
+ // the hardcopy timeout (HARDCOPY_TIMEOUT_MS, 4s) so a normal timeout → static-fallback settles
13
+ // first and does NOT trip a stall; a stall here is a genuine anomaly (e.g. a config hang).
14
+ // Mirrors WIDGET_LOADING_WATCHDOG_MS (the console-only watchdog in withBaseWidget).
15
+ export const WIDGET_LOAD_STALL_MS = 5000;
16
+
17
+ // 'pending' = visibility not confirmed by either path when the event fired (e.g. a
18
+ // deferLoading:false widget that loaded eagerly, before the observer reported).
19
+ export type WidgetVisibilitySource = 'observer' | 'fallback' | 'pending';
20
+
21
+ export interface WidgetLoadDiagnosticsInput {
22
+ /** Gate. No events are emitted unless true. On by default; a Statsig kill switch can disable it. */
23
+ enabled: boolean;
24
+ /** Correlates this widget's mount / rendered / outcome / stall events for a single load. */
25
+ loadId: string;
26
+ widgetType: WidgetTypeV3;
27
+ widgetConfigId?: string;
28
+ /** The widget became visible and started loading its own requests. */
29
+ startLoading: boolean;
30
+ /** This widget's own hardcopy / widgetConfig / uiConfig requests have all settled. */
31
+ ownRequestsSettled: boolean;
32
+ /** Present once hardcopy resolves; carries `servedFrom` (content_loaded vs static_fallback). */
33
+ hardcopyContent?: HardcopyResponse;
34
+ /** Whether visibility came from the observer or the reconcile fallback. */
35
+ visibilitySource: WidgetVisibilitySource;
36
+ /** Names of this widget's still-unsettled requests, reported on a stall. */
37
+ pendingRequests: string[];
38
+ /** Set when this widget's own hardcopy request rejected (no content — not a static fallback). */
39
+ hardcopyErrorReason?: string;
40
+ }
41
+
42
+ const connectionType = (): string | undefined => {
43
+ try {
44
+ // Network Information API — Chromium/Android only, undefined on iOS Safari (our key population).
45
+ return (navigator as Navigator & { connection?: { effectiveType?: string } }).connection
46
+ ?.effectiveType;
47
+ } catch {
48
+ return undefined;
49
+ }
50
+ };
51
+
52
+ const now = (): number => {
53
+ try {
54
+ return typeof window !== 'undefined' && window.performance?.now
55
+ ? window.performance.now()
56
+ : Date.now();
57
+ } catch {
58
+ return Date.now();
59
+ }
60
+ };
61
+
62
+ /**
63
+ * Emits the layered `[Envive] Diagnostics` widget-load net so eng can measure not just what
64
+ * loads, but the negative space between layers — the failures that fire nothing today:
65
+ *
66
+ * widget_mounted (ungated) → [Envive] Widget Rendered (visible) → widget_load_outcome
67
+ *
68
+ * - `widget_mounted` fires on mount regardless of visibility, so a widget that mounts but never
69
+ * becomes visible (the suspected infinite-load crux) shows up as a *mounted with no rendered*
70
+ * gap instead of being invisible to analytics entirely.
71
+ * - `widget_load_outcome` fires once the widget's own requests settle
72
+ * (content_loaded / static_fallback / error).
73
+ * - `widget_load_stalled` fires if it's still loading at WIDGET_LOAD_STALL_MS AND still mounted.
74
+ * The timer is cleared on unmount, so a shopper who navigates away first never trips it —
75
+ * "stalled with no later outcome" is therefore a clean stuck signal, undiluted by abandonment.
76
+ *
77
+ * `load_id` correlates all of the above (and is threaded onto `[Envive] Widget Rendered`) so
78
+ * the gaps are joinable per load.
79
+ */
80
+ export const useWidgetLoadDiagnostics = (input: WidgetLoadDiagnosticsInput): void => {
81
+ const {
82
+ enabled,
83
+ loadId,
84
+ widgetType,
85
+ widgetConfigId,
86
+ startLoading,
87
+ ownRequestsSettled,
88
+ hardcopyContent,
89
+ } = input;
90
+
91
+ const { trackEvent } = useAmplitude();
92
+ const variantInfo = useAtomValue(pageVariantInfoAtom);
93
+
94
+ // Latest input, read inside the deferred stall/outcome callbacks (pendingRequests and
95
+ // visibilitySource churn as requests settle / the fallback engages).
96
+ const inputRef = useRef(input);
97
+ inputRef.current = input;
98
+
99
+ const emit = (props: Record<string, unknown>) => {
100
+ trackEvent({
101
+ eventName: EnviveMetricsEventName.Diagnostics,
102
+ eventProps: {
103
+ load_id: loadId,
104
+ widget_type: widgetType,
105
+ widget_config_id: widgetConfigId,
106
+ connection_type: connectionType(),
107
+ context: variantInfo ? extractPageContext(variantInfo as PageVariantInfo) : null,
108
+ ...props,
109
+ },
110
+ });
111
+ };
112
+
113
+ // Layer 0 — mount. Ungated by visibility: the baseline that fires even when the widget never
114
+ // becomes visible, so never-visible failures are a measurable gap rather than silence.
115
+ const mountFiredRef = useRef(false);
116
+ useEffect(() => {
117
+ if (!enabled || mountFiredRef.current) return;
118
+ mountFiredRef.current = true;
119
+ emit({ type: 'widget_mounted' });
120
+ // eslint-disable-next-line react-hooks/exhaustive-deps
121
+ }, [enabled]);
122
+
123
+ // t0 — when the widget became visible / started loading.
124
+ const visibleAtRef = useRef<number | null>(null);
125
+ useEffect(() => {
126
+ if (startLoading && visibleAtRef.current === null) visibleAtRef.current = now();
127
+ }, [startLoading]);
128
+
129
+ // Layer 2 — terminal outcome, once, when this widget's own requests settle.
130
+ const outcomeFiredRef = useRef(false);
131
+ useEffect(() => {
132
+ if (!enabled || !startLoading || !ownRequestsSettled || outcomeFiredRef.current) return;
133
+ outcomeFiredRef.current = true;
134
+
135
+ const elapsed_ms =
136
+ visibleAtRef.current !== null ? Math.round(now() - visibleAtRef.current) : undefined;
137
+
138
+ let outcome: string;
139
+ let reason: string | undefined;
140
+ if (!hardcopyContent) {
141
+ outcome = 'error';
142
+ reason = inputRef.current.hardcopyErrorReason ?? 'no_content';
143
+ } else if (hardcopyContent.servedFrom === 'static') {
144
+ outcome = 'static_fallback';
145
+ reason = hardcopyContent.fallbackReason;
146
+ } else {
147
+ outcome = 'content_loaded';
148
+ }
149
+
150
+ emit({
151
+ type: 'widget_load_outcome',
152
+ outcome,
153
+ reason,
154
+ elapsed_ms,
155
+ visibility_source: inputRef.current.visibilitySource,
156
+ });
157
+ // eslint-disable-next-line react-hooks/exhaustive-deps
158
+ }, [enabled, startLoading, ownRequestsSettled, hardcopyContent]);
159
+
160
+ // Layer 1.5 — stall. Only arms while loading; cleared on unmount, so abandonment never trips it.
161
+ useEffect(() => {
162
+ if (!enabled || !startLoading || ownRequestsSettled) return undefined;
163
+ const timer = setTimeout(() => {
164
+ const elapsed_ms =
165
+ visibleAtRef.current !== null
166
+ ? Math.round(now() - visibleAtRef.current)
167
+ : WIDGET_LOAD_STALL_MS;
168
+ emit({
169
+ type: 'widget_load_stalled',
170
+ elapsed_ms,
171
+ pending_requests: inputRef.current.pendingRequests,
172
+ visibility_source: inputRef.current.visibilitySource,
173
+ });
174
+ }, WIDGET_LOAD_STALL_MS);
175
+ return () => clearTimeout(timer);
176
+ // eslint-disable-next-line react-hooks/exhaustive-deps
177
+ }, [enabled, startLoading, ownRequestsSettled]);
178
+ };