@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.
- package/dist/application/models/featureGates.cjs +2 -1
- package/dist/application/models/featureGates.d.cts +2 -1
- package/dist/application/models/featureGates.d.ts +2 -1
- package/dist/application/models/featureGates.js +2 -1
- package/dist/application/utils/elementObserver.d.cts +2 -2
- package/dist/atoms/app/index.d.cts +7 -7
- package/dist/atoms/app/variant.d.cts +6 -6
- package/dist/atoms/chat/chatState.d.cts +19 -19
- package/dist/atoms/chat/chatState.d.ts +19 -19
- package/dist/atoms/chat/form.d.cts +2 -2
- package/dist/atoms/chat/form.d.ts +2 -2
- package/dist/atoms/chat/index.d.cts +2 -2
- package/dist/atoms/chat/index.d.ts +3 -3
- package/dist/atoms/chat/lastMessage.d.cts +2 -2
- package/dist/atoms/chat/lastMessage.d.ts +2 -2
- package/dist/atoms/chat/messageQueue.d.cts +7 -7
- package/dist/atoms/chat/messageQueue.d.ts +6 -6
- package/dist/atoms/chat/performanceMetrics.d.cts +6 -6
- package/dist/atoms/chat/performanceMetrics.d.ts +6 -6
- package/dist/atoms/chat/renderedWidgetRefs.d.cts +3 -3
- package/dist/atoms/chat/renderedWidgetRefs.d.ts +2 -2
- package/dist/atoms/chat/replies.d.cts +3 -3
- package/dist/atoms/chat/replies.d.ts +3 -3
- package/dist/atoms/chat/suggestions.d.cts +3 -3
- package/dist/atoms/chat/suggestions.d.ts +2 -2
- package/dist/atoms/envive/enviveConfig.d.cts +14 -14
- package/dist/atoms/envive/enviveConfig.d.ts +14 -14
- package/dist/atoms/globalSearch/globalSearch.d.cts +5 -5
- package/dist/atoms/globalSearch/globalSearch.d.ts +5 -5
- package/dist/atoms/org/customerService.d.cts +6 -6
- package/dist/atoms/org/customerService.d.ts +6 -6
- package/dist/atoms/org/graphqlConfig.d.cts +4 -4
- package/dist/atoms/org/graphqlConfig.d.ts +4 -4
- package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
- package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
- package/dist/atoms/org/orgAnalyticsConfig.d.cts +4 -4
- package/dist/atoms/org/orgAnalyticsConfig.d.ts +4 -4
- package/dist/atoms/search/chatSearch.d.cts +17 -17
- package/dist/atoms/search/chatSearch.d.ts +17 -17
- package/dist/atoms/search/searchAPI.d.cts +13 -13
- package/dist/atoms/search/searchAPI.d.ts +13 -13
- package/dist/atoms/search/types.d.cts +1 -1
- package/dist/atoms/search/utils.d.cts +1 -1
- package/dist/atoms/widget/chatPreviewLoading.d.cts +2 -2
- package/dist/atoms/widget/chatPreviewLoading.d.ts +2 -2
- package/dist/contexts/systemSettingsContext/systemSettingsContext.d.cts +2 -2
- package/dist/contexts/types.d.cts +1 -1
- package/dist/contexts/types.d.ts +1 -1
- package/dist/contexts/typesV3.d.cts +1 -1
- package/dist/contexts/typesV3.d.ts +1 -1
- package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.cts +2 -2
- package/dist/hooks/Intersection/useIntersection.cjs +9 -2
- package/dist/hooks/Intersection/useIntersection.d.cts +2 -2
- package/dist/hooks/Intersection/useIntersection.d.ts +2 -2
- package/dist/hooks/Intersection/useIntersection.js +9 -2
- package/dist/hooks/SystemSettingsContext/useSystemSettingsContext.d.cts +2 -2
- package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.cjs +12 -3
- package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.cts +2 -1
- package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.ts +2 -1
- package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.js +13 -4
- package/dist/hooks/WidgetLoadDiagnostics/index.cjs +4 -0
- package/dist/hooks/WidgetLoadDiagnostics/index.d.cts +2 -0
- package/dist/hooks/WidgetLoadDiagnostics/index.d.ts +2 -0
- package/dist/hooks/WidgetLoadDiagnostics/index.js +3 -0
- package/dist/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.cjs +122 -0
- package/dist/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.d.cts +48 -0
- package/dist/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.d.ts +48 -0
- package/dist/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.js +120 -0
- package/dist/hooks/utils.d.cts +1 -1
- package/dist/hooks/utils.d.ts +1 -1
- package/dist/services/amplitudeService/eventNames.cjs +2 -1
- package/dist/services/amplitudeService/eventNames.d.cts +2 -1
- package/dist/services/amplitudeService/eventNames.d.ts +2 -1
- package/dist/services/amplitudeService/eventNames.js +2 -1
- package/dist/services/ga4ProjectionService/ga4EventSchema.cjs +3 -2
- package/dist/services/ga4ProjectionService/ga4EventSchema.js +3 -2
- package/dist/services/hardcopyService/hardcopyService.cjs +12 -3
- package/dist/services/hardcopyService/hardcopyService.d.cts +3 -1
- package/dist/services/hardcopyService/hardcopyService.d.ts +3 -1
- package/dist/services/hardcopyService/hardcopyService.js +12 -3
- package/package.json +5 -1
- package/src/application/models/featureGates.ts +6 -0
- package/src/hooks/Intersection/useIntersection.ts +11 -0
- package/src/hooks/TrackComponentVisibleEvent/__tests__/useTrackComponentVisibleEvent.test.tsx +64 -0
- package/src/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.ts +23 -4
- package/src/hooks/WidgetLoadDiagnostics/__tests__/useWidgetLoadDiagnostics.test.ts +161 -0
- package/src/hooks/WidgetLoadDiagnostics/index.ts +5 -0
- package/src/hooks/WidgetLoadDiagnostics/useWidgetLoadDiagnostics.ts +178 -0
- package/src/services/amplitudeService/eventNames.ts +4 -0
- package/src/services/ga4ProjectionService/ga4EventSchema.ts +5 -0
- package/src/services/hardcopyService/__tests__/hardcopyService.test.ts +16 -5
- 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
|
|
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
|
|
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.
|
|
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,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
|
+
};
|