@webex/plugin-meetings 3.12.0-next.54 → 3.12.0-next.56
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/aiEnableRequest/index.js +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/hashTree/hashTreeParser.js +131 -51
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +0 -1
- package/dist/locus-info/index.js.map +1 -1
- package/dist/metrics/constants.js +3 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/types/hashTree/hashTreeParser.d.ts +9 -0
- package/dist/types/metrics/constants.d.ts +2 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +1 -1
- package/src/hashTree/hashTreeParser.ts +84 -18
- package/src/locus-info/index.ts +0 -4
- package/src/metrics/constants.ts +2 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +268 -0
|
@@ -101,6 +101,8 @@ var BEHAVIORAL_METRICS = exports.default = {
|
|
|
101
101
|
DEPRECATED_DELETE_CODEC_PARAMETERS_USED: 'js_sdk_deprecated_delete_codec_parameters_used',
|
|
102
102
|
SET_CUSTOM_CODEC_PARAMETERS_USED: 'js_sdk_set_custom_codec_parameters_used',
|
|
103
103
|
MARK_CUSTOM_CODEC_PARAMETERS_FOR_DELETION_USED: 'js_sdk_mark_custom_codec_parameters_for_deletion_used',
|
|
104
|
-
HASH_TREE_SYNC_FAILURE: 'js_sdk_hash_tree_sync_failure'
|
|
104
|
+
HASH_TREE_SYNC_FAILURE: 'js_sdk_hash_tree_sync_failure',
|
|
105
|
+
HASH_TREE_HEARTBEAT_WATCHDOG_EXPIRED: 'js_sdk_hash_tree_heartbeat_watchdog_expired',
|
|
106
|
+
HASH_TREE_EMPTY_LOCUS_STATE_ELEMENTS: 'js_sdk_hash_tree_empty_locus_state_elements'
|
|
105
107
|
};
|
|
106
108
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["BEHAVIORAL_METRICS","exports","default","MEETINGS_REGISTRATION_FAILED","MEETINGS_REGISTRATION_SUCCESS","MEETINGS_REGISTRATION_STEP","MERCURY_CONNECTION_FAILURE","MERCURY_CONNECTION_RESTORED","JOIN_SUCCESS","JOIN_FAILURE","ADD_MEDIA_SUCCESS","ADD_MEDIA_FAILURE","ADD_MEDIA_RETRY","ROAP_MERCURY_EVENT_RECEIVED","CONNECTION_SUCCESS","CONNECTION_FAILURE","MEETING_LEAVE_FAILURE","MEETING_END_ALL_FAILURE","MEETING_END_ALL_INITIATED","GET_USER_MEDIA_FAILURE","GET_DISPLAY_MEDIA_FAILURE","JOIN_WITH_MEDIA_FAILURE","LLM_CONNECTION_AFTER_JOIN_FAILURE","LLM_HEALTHCHECK_FAILURE","RECEIVE_TRANSCRIPTION_AFTER_JOIN_FAILURE","DISCONNECT_DUE_TO_INACTIVITY","MEETING_MEDIA_INACTIVE","MEETING_RECONNECT_FAILURE","MEETING_MAX_REJOIN_FAILURE","MEETING_SHARE_SUCCESS","MEETING_SHARE_FAILURE","MEETING_START_WHITEBOARD_SHARE_FAILURE","MEETING_STOP_WHITEBOARD_SHARE_FAILURE","MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE","MUTE_AUDIO_FAILURE","MUTE_VIDEO_FAILURE","SET_MEETING_QUALITY_FAILURE","STOP_FLOOR_REQUEST_FAILURE","ADD_DIAL_IN_FAILURE","ADD_DIAL_OUT_FAILURE","UPDATE_MEDIA_FAILURE","UNMUTE_AUDIO_FAILURE","UNMUTE_VIDEO_FAILURE","ROAP_ANSWER_FAILURE","ROAP_GLARE_CONDITION","PEERCONNECTION_FAILURE","INVALID_ICE_CANDIDATE","UPLOAD_LOGS_FAILURE","UPLOAD_LOGS_SUCCESS","RECEIVE_TRANSCRIPTION_FAILURE","MEETING_IS_IN_PROGRESS_ERROR","STATIC_MEETING_LINK_ALREADY_EXISTS_ERROR","FETCH_MEETING_INFO_V1_SUCCESS","FETCH_MEETING_INFO_V1_FAILURE","ENABLE_STATIC_METTING_LINK_SUCCESS","ENABLE_STATIC_METTING_LINK_FAILURE","DISABLE_STATIC_MEETING_LINK_SUCCESS","DISABLE_STATIC_MEETING_LINK_FAILURE","ADHOC_MEETING_SUCCESS","ADHOC_MEETING_FAILURE","FETCH_STATIC_MEETING_LINK_SUCCESS","FETCH_STATIC_MEETING_LINK_FAILURE","MEETING_LINK_DOES_NOT_EXIST_ERROR","VERIFY_PASSWORD_SUCCESS","VERIFY_PASSWORD_ERROR","VERIFY_CAPTCHA_ERROR","MOVE_TO_SUCCESS","MOVE_TO_FAILURE","MOVE_FROM_SUCCESS","MOVE_FROM_FAILURE","TURN_DISCOVERY_FAILURE","MEETING_INFO_POLICY_ERROR","LOCUS_DELTA_SYNC_FAILED","LOCUS_DELTA_OUT_OF_ORDER","LOCUS_SYNC_HANDLING_FAILED","PERMISSION_TOKEN_REFRESH","PERMISSION_TOKEN_REFRESH_ERROR","TURN_DISCOVERY_LATENCY","ROAP_OFFER_TO_ANSWER_LATENCY","ROAP_HTTP_RESPONSE_MISSING","TURN_DISCOVERY_REQUIRES_OK","REACHABILITY_COMPLETED","JOIN_WEBINAR_ERROR","GUEST_ENTERED_LOBBY","GUEST_EXITED_LOBBY","VERIFY_REGISTRATION_ID_SUCCESS","VERIFY_REGISTRATION_ID_ERROR","JOIN_FORBIDDEN_ERROR","MEDIA_ISSUE_DETECTED","LOCUS_CLASSIC_VS_HASH_TREE_MISMATCH","LOCUS_HASH_TREE_UNSUPPORTED_OPERATION","MEDIA_STILL_NOT_CONNECTED","DEPRECATED_SET_CODEC_PARAMETERS_USED","DEPRECATED_DELETE_CODEC_PARAMETERS_USED","SET_CUSTOM_CODEC_PARAMETERS_USED","MARK_CUSTOM_CODEC_PARAMETERS_FOR_DELETION_USED","HASH_TREE_SYNC_FAILURE"],"sources":["constants.ts"],"sourcesContent":["// Metrics constants ----------------------------------------------------------\n\nconst BEHAVIORAL_METRICS = {\n MEETINGS_REGISTRATION_FAILED: 'js_sdk_meetings_registration_failed',\n MEETINGS_REGISTRATION_SUCCESS: 'js_sdk_meetings_registration_success',\n MEETINGS_REGISTRATION_STEP: 'meetings_registration_step',\n MERCURY_CONNECTION_FAILURE: 'js_sdk_mercury_connection_failure',\n MERCURY_CONNECTION_RESTORED: 'js_sdk_mercury_connection_restored',\n JOIN_SUCCESS: 'js_sdk_join_success',\n JOIN_FAILURE: 'js_sdk_join_failures',\n ADD_MEDIA_SUCCESS: 'js_sdk_add_media_success',\n ADD_MEDIA_FAILURE: 'js_sdk_add_media_failures',\n ADD_MEDIA_RETRY: 'js_sdk_add_media_retry',\n ROAP_MERCURY_EVENT_RECEIVED: 'js_sdk_roap_mercury_received',\n CONNECTION_SUCCESS: 'js_sdk_connection_success',\n CONNECTION_FAILURE: 'js_sdk_connection_failures',\n MEETING_LEAVE_FAILURE: 'js_sdk_meeting_leave_failure',\n MEETING_END_ALL_FAILURE: 'js_sdk_meeting_end_for_all_failure',\n MEETING_END_ALL_INITIATED: 'js_sdk_meeting_end_for_all_initiated',\n GET_USER_MEDIA_FAILURE: 'js_sdk_get_user_media_failures',\n GET_DISPLAY_MEDIA_FAILURE: 'js_sdk_get_display_media_failures',\n JOIN_WITH_MEDIA_FAILURE: 'js_sdk_join_with_media_failures',\n LLM_CONNECTION_AFTER_JOIN_FAILURE: 'js_sdk_llm_connection_after_join_failure',\n LLM_HEALTHCHECK_FAILURE: 'js_sdk_llm_healthcheck_failure',\n RECEIVE_TRANSCRIPTION_AFTER_JOIN_FAILURE: 'js_sdk_receive_transcription_after_join_failure',\n\n DISCONNECT_DUE_TO_INACTIVITY: 'js_sdk_disconnect_due_to_inactivity',\n MEETING_MEDIA_INACTIVE: 'js_sdk_meeting_media_inactive',\n MEETING_RECONNECT_FAILURE: 'js_sdk_meeting_reconnect_failures',\n MEETING_MAX_REJOIN_FAILURE: 'js_sdk_meeting_max_rejoin_failure',\n MEETING_SHARE_SUCCESS: 'js_sdk_meeting_share_success',\n MEETING_SHARE_FAILURE: 'js_sdk_meeting_share_failures',\n MEETING_START_WHITEBOARD_SHARE_FAILURE: 'js_sdk_meeting_start_whiteboard_share_failures',\n MEETING_STOP_WHITEBOARD_SHARE_FAILURE: 'js_sdk_meeting_stop_whiteboard_share_failures',\n MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE: 'js_sdk_meeting_share_video_mute_state_change',\n MUTE_AUDIO_FAILURE: 'js_sdk_mute_audio_failures',\n MUTE_VIDEO_FAILURE: 'js_sdk_mute_video_failures',\n SET_MEETING_QUALITY_FAILURE: 'js_sdk_set_meeting_quality_failures',\n STOP_FLOOR_REQUEST_FAILURE: 'js_sdk_stop_floor_request_failures',\n ADD_DIAL_IN_FAILURE: 'js_sdk_add_dial_in_failure',\n ADD_DIAL_OUT_FAILURE: 'js_sdk_add_dial_out_failure',\n UPDATE_MEDIA_FAILURE: 'js_sdk_update_media_failures',\n UNMUTE_AUDIO_FAILURE: 'js_sdk_unmute_audio_failures',\n UNMUTE_VIDEO_FAILURE: 'js_sdk_unmute_video_failures',\n ROAP_ANSWER_FAILURE: 'js_sdk_roap_answer_failures',\n ROAP_GLARE_CONDITION: 'js_sdk_roap_glar_condition',\n PEERCONNECTION_FAILURE: 'js_sdk_peerConnection_failures',\n INVALID_ICE_CANDIDATE: 'js_sdk_invalid_ice_candidate',\n UPLOAD_LOGS_FAILURE: 'js_sdk_upload_logs_failure',\n UPLOAD_LOGS_SUCCESS: 'js_sdk_upload_logs_success',\n RECEIVE_TRANSCRIPTION_FAILURE: 'js_sdk_receive_transcription_failure',\n MEETING_IS_IN_PROGRESS_ERROR: 'js_sdk_meeting_is_in_progress_error',\n STATIC_MEETING_LINK_ALREADY_EXISTS_ERROR: 'js_sdk_static_meeting_link_already_exists_error',\n FETCH_MEETING_INFO_V1_SUCCESS: 'js_sdk_fetch_meeting_info_v1_success',\n FETCH_MEETING_INFO_V1_FAILURE: 'js_sdk_fetch_meeting_info_v1_failure',\n ENABLE_STATIC_METTING_LINK_SUCCESS: 'js_sdk_enable_static_meeting_link_success',\n ENABLE_STATIC_METTING_LINK_FAILURE: 'js_sdk_enable_static_meeting_link_failure',\n DISABLE_STATIC_MEETING_LINK_SUCCESS: 'js_sdk_disable_static_meeting_link_success',\n DISABLE_STATIC_MEETING_LINK_FAILURE: 'js_sdk_disable_static_meeting_link_failure',\n ADHOC_MEETING_SUCCESS: 'js_sdk_adhoc_meeting_success',\n ADHOC_MEETING_FAILURE: 'js_sdk_adhoc_meeting_failure',\n FETCH_STATIC_MEETING_LINK_SUCCESS: 'js_sdk_fetch_static_meeting_link_success',\n FETCH_STATIC_MEETING_LINK_FAILURE: 'js_sdk_fetch_static_meeting_link_failure',\n MEETING_LINK_DOES_NOT_EXIST_ERROR: 'js_sdk_meeting_link_does_not_exist_error',\n VERIFY_PASSWORD_SUCCESS: 'js_sdk_verify_password_success',\n VERIFY_PASSWORD_ERROR: 'js_sdk_verify_password_error',\n VERIFY_CAPTCHA_ERROR: 'js_sdk_verify_captcha_error',\n MOVE_TO_SUCCESS: 'js_sdk_move_to_success',\n MOVE_TO_FAILURE: 'js_sdk_move_to_failure',\n MOVE_FROM_SUCCESS: 'js_sdk_move_from_success',\n MOVE_FROM_FAILURE: 'js_sdk_move_from_failure',\n TURN_DISCOVERY_FAILURE: 'js_sdk_turn_discovery_failure',\n MEETING_INFO_POLICY_ERROR: 'js_sdk_meeting_info_policy_error',\n LOCUS_DELTA_SYNC_FAILED: 'js_sdk_locus_delta_sync_failed',\n LOCUS_DELTA_OUT_OF_ORDER: 'js_sdk_locus_delta_ooo',\n LOCUS_SYNC_HANDLING_FAILED: 'js_sdk_locus_sync_handling_failed',\n PERMISSION_TOKEN_REFRESH: 'js_sdk_permission_token_refresh',\n PERMISSION_TOKEN_REFRESH_ERROR: 'js_sdk_permission_token_refresh_error',\n TURN_DISCOVERY_LATENCY: 'js_sdk_turn_discovery_latency',\n ROAP_OFFER_TO_ANSWER_LATENCY: 'js_sdk_roap_offer_to_answer_latency',\n ROAP_HTTP_RESPONSE_MISSING: 'js_sdk_roap_http_response_missing',\n TURN_DISCOVERY_REQUIRES_OK: 'js_sdk_turn_discovery_requires_ok',\n REACHABILITY_COMPLETED: 'js_sdk_reachability_completed',\n JOIN_WEBINAR_ERROR: 'js_sdk_join_webinar_error',\n GUEST_ENTERED_LOBBY: 'js_sdk_guest_entered_lobby',\n GUEST_EXITED_LOBBY: 'js_sdk_guest_exited_lobby',\n VERIFY_REGISTRATION_ID_SUCCESS: 'js_sdk_verify_registrationId_success',\n VERIFY_REGISTRATION_ID_ERROR: 'js_sdk_verify_registrationId_error',\n JOIN_FORBIDDEN_ERROR: 'js_sdk_join_forbidden_error',\n MEDIA_ISSUE_DETECTED: 'js_sdk_media_issue_detected',\n LOCUS_CLASSIC_VS_HASH_TREE_MISMATCH: 'js_sdk_locus_classic_vs_hash_tree_mismatch',\n LOCUS_HASH_TREE_UNSUPPORTED_OPERATION: 'js_sdk_locus_hash_tree_unsupported_operation',\n MEDIA_STILL_NOT_CONNECTED: 'js_sdk_media_still_not_connected',\n DEPRECATED_SET_CODEC_PARAMETERS_USED: 'js_sdk_deprecated_set_codec_parameters_used',\n DEPRECATED_DELETE_CODEC_PARAMETERS_USED: 'js_sdk_deprecated_delete_codec_parameters_used',\n SET_CUSTOM_CODEC_PARAMETERS_USED: 'js_sdk_set_custom_codec_parameters_used',\n MARK_CUSTOM_CODEC_PARAMETERS_FOR_DELETION_USED:\n 'js_sdk_mark_custom_codec_parameters_for_deletion_used',\n HASH_TREE_SYNC_FAILURE: 'js_sdk_hash_tree_sync_failure',\n};\n\nexport {BEHAVIORAL_METRICS as default};\n"],"mappings":";;;;;;;AAAA;;AAEA,IAAMA,kBAAkB,GAAAC,OAAA,CAAAC,OAAA,GAAG;EACzBC,4BAA4B,EAAE,qCAAqC;EACnEC,6BAA6B,EAAE,sCAAsC;EACrEC,0BAA0B,EAAE,4BAA4B;EACxDC,0BAA0B,EAAE,mCAAmC;EAC/DC,2BAA2B,EAAE,oCAAoC;EACjEC,YAAY,EAAE,qBAAqB;EACnCC,YAAY,EAAE,sBAAsB;EACpCC,iBAAiB,EAAE,0BAA0B;EAC7CC,iBAAiB,EAAE,2BAA2B;EAC9CC,eAAe,EAAE,wBAAwB;EACzCC,2BAA2B,EAAE,8BAA8B;EAC3DC,kBAAkB,EAAE,2BAA2B;EAC/CC,kBAAkB,EAAE,4BAA4B;EAChDC,qBAAqB,EAAE,8BAA8B;EACrDC,uBAAuB,EAAE,oCAAoC;EAC7DC,yBAAyB,EAAE,sCAAsC;EACjEC,sBAAsB,EAAE,gCAAgC;EACxDC,yBAAyB,EAAE,mCAAmC;EAC9DC,uBAAuB,EAAE,iCAAiC;EAC1DC,iCAAiC,EAAE,0CAA0C;EAC7EC,uBAAuB,EAAE,gCAAgC;EACzDC,wCAAwC,EAAE,iDAAiD;EAE3FC,4BAA4B,EAAE,qCAAqC;EACnEC,sBAAsB,EAAE,+BAA+B;EACvDC,yBAAyB,EAAE,mCAAmC;EAC9DC,0BAA0B,EAAE,mCAAmC;EAC/DC,qBAAqB,EAAE,8BAA8B;EACrDC,qBAAqB,EAAE,+BAA+B;EACtDC,sCAAsC,EAAE,gDAAgD;EACxFC,qCAAqC,EAAE,+CAA+C;EACtFC,qCAAqC,EAAE,8CAA8C;EACrFC,kBAAkB,EAAE,4BAA4B;EAChDC,kBAAkB,EAAE,4BAA4B;EAChDC,2BAA2B,EAAE,qCAAqC;EAClEC,0BAA0B,EAAE,oCAAoC;EAChEC,mBAAmB,EAAE,4BAA4B;EACjDC,oBAAoB,EAAE,6BAA6B;EACnDC,oBAAoB,EAAE,8BAA8B;EACpDC,oBAAoB,EAAE,8BAA8B;EACpDC,oBAAoB,EAAE,8BAA8B;EACpDC,mBAAmB,EAAE,6BAA6B;EAClDC,oBAAoB,EAAE,4BAA4B;EAClDC,sBAAsB,EAAE,gCAAgC;EACxDC,qBAAqB,EAAE,8BAA8B;EACrDC,mBAAmB,EAAE,4BAA4B;EACjDC,mBAAmB,EAAE,4BAA4B;EACjDC,6BAA6B,EAAE,sCAAsC;EACrEC,4BAA4B,EAAE,qCAAqC;EACnEC,wCAAwC,EAAE,iDAAiD;EAC3FC,6BAA6B,EAAE,sCAAsC;EACrEC,6BAA6B,EAAE,sCAAsC;EACrEC,kCAAkC,EAAE,2CAA2C;EAC/EC,kCAAkC,EAAE,2CAA2C;EAC/EC,mCAAmC,EAAE,4CAA4C;EACjFC,mCAAmC,EAAE,4CAA4C;EACjFC,qBAAqB,EAAE,8BAA8B;EACrDC,qBAAqB,EAAE,8BAA8B;EACrDC,iCAAiC,EAAE,0CAA0C;EAC7EC,iCAAiC,EAAE,0CAA0C;EAC7EC,iCAAiC,EAAE,0CAA0C;EAC7EC,uBAAuB,EAAE,gCAAgC;EACzDC,qBAAqB,EAAE,8BAA8B;EACrDC,oBAAoB,EAAE,6BAA6B;EACnDC,eAAe,EAAE,wBAAwB;EACzCC,eAAe,EAAE,wBAAwB;EACzCC,iBAAiB,EAAE,0BAA0B;EAC7CC,iBAAiB,EAAE,0BAA0B;EAC7CC,sBAAsB,EAAE,+BAA+B;EACvDC,yBAAyB,EAAE,kCAAkC;EAC7DC,uBAAuB,EAAE,gCAAgC;EACzDC,wBAAwB,EAAE,wBAAwB;EAClDC,0BAA0B,EAAE,mCAAmC;EAC/DC,wBAAwB,EAAE,iCAAiC;EAC3DC,8BAA8B,EAAE,uCAAuC;EACvEC,sBAAsB,EAAE,+BAA+B;EACvDC,4BAA4B,EAAE,qCAAqC;EACnEC,0BAA0B,EAAE,mCAAmC;EAC/DC,0BAA0B,EAAE,mCAAmC;EAC/DC,sBAAsB,EAAE,+BAA+B;EACvDC,kBAAkB,EAAE,2BAA2B;EAC/CC,mBAAmB,EAAE,4BAA4B;EACjDC,kBAAkB,EAAE,2BAA2B;EAC/CC,8BAA8B,EAAE,sCAAsC;EACtEC,4BAA4B,EAAE,oCAAoC;EAClEC,oBAAoB,EAAE,6BAA6B;EACnDC,oBAAoB,EAAE,6BAA6B;EACnDC,mCAAmC,EAAE,4CAA4C;EACjFC,qCAAqC,EAAE,8CAA8C;EACrFC,yBAAyB,EAAE,kCAAkC;EAC7DC,oCAAoC,EAAE,6CAA6C;EACnFC,uCAAuC,EAAE,gDAAgD;EACzFC,gCAAgC,EAAE,yCAAyC;EAC3EC,8CAA8C,EAC5C,uDAAuD;EACzDC,sBAAsB,EAAE;AAC1B,CAAC","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["BEHAVIORAL_METRICS","exports","default","MEETINGS_REGISTRATION_FAILED","MEETINGS_REGISTRATION_SUCCESS","MEETINGS_REGISTRATION_STEP","MERCURY_CONNECTION_FAILURE","MERCURY_CONNECTION_RESTORED","JOIN_SUCCESS","JOIN_FAILURE","ADD_MEDIA_SUCCESS","ADD_MEDIA_FAILURE","ADD_MEDIA_RETRY","ROAP_MERCURY_EVENT_RECEIVED","CONNECTION_SUCCESS","CONNECTION_FAILURE","MEETING_LEAVE_FAILURE","MEETING_END_ALL_FAILURE","MEETING_END_ALL_INITIATED","GET_USER_MEDIA_FAILURE","GET_DISPLAY_MEDIA_FAILURE","JOIN_WITH_MEDIA_FAILURE","LLM_CONNECTION_AFTER_JOIN_FAILURE","LLM_HEALTHCHECK_FAILURE","RECEIVE_TRANSCRIPTION_AFTER_JOIN_FAILURE","DISCONNECT_DUE_TO_INACTIVITY","MEETING_MEDIA_INACTIVE","MEETING_RECONNECT_FAILURE","MEETING_MAX_REJOIN_FAILURE","MEETING_SHARE_SUCCESS","MEETING_SHARE_FAILURE","MEETING_START_WHITEBOARD_SHARE_FAILURE","MEETING_STOP_WHITEBOARD_SHARE_FAILURE","MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE","MUTE_AUDIO_FAILURE","MUTE_VIDEO_FAILURE","SET_MEETING_QUALITY_FAILURE","STOP_FLOOR_REQUEST_FAILURE","ADD_DIAL_IN_FAILURE","ADD_DIAL_OUT_FAILURE","UPDATE_MEDIA_FAILURE","UNMUTE_AUDIO_FAILURE","UNMUTE_VIDEO_FAILURE","ROAP_ANSWER_FAILURE","ROAP_GLARE_CONDITION","PEERCONNECTION_FAILURE","INVALID_ICE_CANDIDATE","UPLOAD_LOGS_FAILURE","UPLOAD_LOGS_SUCCESS","RECEIVE_TRANSCRIPTION_FAILURE","MEETING_IS_IN_PROGRESS_ERROR","STATIC_MEETING_LINK_ALREADY_EXISTS_ERROR","FETCH_MEETING_INFO_V1_SUCCESS","FETCH_MEETING_INFO_V1_FAILURE","ENABLE_STATIC_METTING_LINK_SUCCESS","ENABLE_STATIC_METTING_LINK_FAILURE","DISABLE_STATIC_MEETING_LINK_SUCCESS","DISABLE_STATIC_MEETING_LINK_FAILURE","ADHOC_MEETING_SUCCESS","ADHOC_MEETING_FAILURE","FETCH_STATIC_MEETING_LINK_SUCCESS","FETCH_STATIC_MEETING_LINK_FAILURE","MEETING_LINK_DOES_NOT_EXIST_ERROR","VERIFY_PASSWORD_SUCCESS","VERIFY_PASSWORD_ERROR","VERIFY_CAPTCHA_ERROR","MOVE_TO_SUCCESS","MOVE_TO_FAILURE","MOVE_FROM_SUCCESS","MOVE_FROM_FAILURE","TURN_DISCOVERY_FAILURE","MEETING_INFO_POLICY_ERROR","LOCUS_DELTA_SYNC_FAILED","LOCUS_DELTA_OUT_OF_ORDER","LOCUS_SYNC_HANDLING_FAILED","PERMISSION_TOKEN_REFRESH","PERMISSION_TOKEN_REFRESH_ERROR","TURN_DISCOVERY_LATENCY","ROAP_OFFER_TO_ANSWER_LATENCY","ROAP_HTTP_RESPONSE_MISSING","TURN_DISCOVERY_REQUIRES_OK","REACHABILITY_COMPLETED","JOIN_WEBINAR_ERROR","GUEST_ENTERED_LOBBY","GUEST_EXITED_LOBBY","VERIFY_REGISTRATION_ID_SUCCESS","VERIFY_REGISTRATION_ID_ERROR","JOIN_FORBIDDEN_ERROR","MEDIA_ISSUE_DETECTED","LOCUS_CLASSIC_VS_HASH_TREE_MISMATCH","LOCUS_HASH_TREE_UNSUPPORTED_OPERATION","MEDIA_STILL_NOT_CONNECTED","DEPRECATED_SET_CODEC_PARAMETERS_USED","DEPRECATED_DELETE_CODEC_PARAMETERS_USED","SET_CUSTOM_CODEC_PARAMETERS_USED","MARK_CUSTOM_CODEC_PARAMETERS_FOR_DELETION_USED","HASH_TREE_SYNC_FAILURE","HASH_TREE_HEARTBEAT_WATCHDOG_EXPIRED","HASH_TREE_EMPTY_LOCUS_STATE_ELEMENTS"],"sources":["constants.ts"],"sourcesContent":["// Metrics constants ----------------------------------------------------------\n\nconst BEHAVIORAL_METRICS = {\n MEETINGS_REGISTRATION_FAILED: 'js_sdk_meetings_registration_failed',\n MEETINGS_REGISTRATION_SUCCESS: 'js_sdk_meetings_registration_success',\n MEETINGS_REGISTRATION_STEP: 'meetings_registration_step',\n MERCURY_CONNECTION_FAILURE: 'js_sdk_mercury_connection_failure',\n MERCURY_CONNECTION_RESTORED: 'js_sdk_mercury_connection_restored',\n JOIN_SUCCESS: 'js_sdk_join_success',\n JOIN_FAILURE: 'js_sdk_join_failures',\n ADD_MEDIA_SUCCESS: 'js_sdk_add_media_success',\n ADD_MEDIA_FAILURE: 'js_sdk_add_media_failures',\n ADD_MEDIA_RETRY: 'js_sdk_add_media_retry',\n ROAP_MERCURY_EVENT_RECEIVED: 'js_sdk_roap_mercury_received',\n CONNECTION_SUCCESS: 'js_sdk_connection_success',\n CONNECTION_FAILURE: 'js_sdk_connection_failures',\n MEETING_LEAVE_FAILURE: 'js_sdk_meeting_leave_failure',\n MEETING_END_ALL_FAILURE: 'js_sdk_meeting_end_for_all_failure',\n MEETING_END_ALL_INITIATED: 'js_sdk_meeting_end_for_all_initiated',\n GET_USER_MEDIA_FAILURE: 'js_sdk_get_user_media_failures',\n GET_DISPLAY_MEDIA_FAILURE: 'js_sdk_get_display_media_failures',\n JOIN_WITH_MEDIA_FAILURE: 'js_sdk_join_with_media_failures',\n LLM_CONNECTION_AFTER_JOIN_FAILURE: 'js_sdk_llm_connection_after_join_failure',\n LLM_HEALTHCHECK_FAILURE: 'js_sdk_llm_healthcheck_failure',\n RECEIVE_TRANSCRIPTION_AFTER_JOIN_FAILURE: 'js_sdk_receive_transcription_after_join_failure',\n\n DISCONNECT_DUE_TO_INACTIVITY: 'js_sdk_disconnect_due_to_inactivity',\n MEETING_MEDIA_INACTIVE: 'js_sdk_meeting_media_inactive',\n MEETING_RECONNECT_FAILURE: 'js_sdk_meeting_reconnect_failures',\n MEETING_MAX_REJOIN_FAILURE: 'js_sdk_meeting_max_rejoin_failure',\n MEETING_SHARE_SUCCESS: 'js_sdk_meeting_share_success',\n MEETING_SHARE_FAILURE: 'js_sdk_meeting_share_failures',\n MEETING_START_WHITEBOARD_SHARE_FAILURE: 'js_sdk_meeting_start_whiteboard_share_failures',\n MEETING_STOP_WHITEBOARD_SHARE_FAILURE: 'js_sdk_meeting_stop_whiteboard_share_failures',\n MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE: 'js_sdk_meeting_share_video_mute_state_change',\n MUTE_AUDIO_FAILURE: 'js_sdk_mute_audio_failures',\n MUTE_VIDEO_FAILURE: 'js_sdk_mute_video_failures',\n SET_MEETING_QUALITY_FAILURE: 'js_sdk_set_meeting_quality_failures',\n STOP_FLOOR_REQUEST_FAILURE: 'js_sdk_stop_floor_request_failures',\n ADD_DIAL_IN_FAILURE: 'js_sdk_add_dial_in_failure',\n ADD_DIAL_OUT_FAILURE: 'js_sdk_add_dial_out_failure',\n UPDATE_MEDIA_FAILURE: 'js_sdk_update_media_failures',\n UNMUTE_AUDIO_FAILURE: 'js_sdk_unmute_audio_failures',\n UNMUTE_VIDEO_FAILURE: 'js_sdk_unmute_video_failures',\n ROAP_ANSWER_FAILURE: 'js_sdk_roap_answer_failures',\n ROAP_GLARE_CONDITION: 'js_sdk_roap_glar_condition',\n PEERCONNECTION_FAILURE: 'js_sdk_peerConnection_failures',\n INVALID_ICE_CANDIDATE: 'js_sdk_invalid_ice_candidate',\n UPLOAD_LOGS_FAILURE: 'js_sdk_upload_logs_failure',\n UPLOAD_LOGS_SUCCESS: 'js_sdk_upload_logs_success',\n RECEIVE_TRANSCRIPTION_FAILURE: 'js_sdk_receive_transcription_failure',\n MEETING_IS_IN_PROGRESS_ERROR: 'js_sdk_meeting_is_in_progress_error',\n STATIC_MEETING_LINK_ALREADY_EXISTS_ERROR: 'js_sdk_static_meeting_link_already_exists_error',\n FETCH_MEETING_INFO_V1_SUCCESS: 'js_sdk_fetch_meeting_info_v1_success',\n FETCH_MEETING_INFO_V1_FAILURE: 'js_sdk_fetch_meeting_info_v1_failure',\n ENABLE_STATIC_METTING_LINK_SUCCESS: 'js_sdk_enable_static_meeting_link_success',\n ENABLE_STATIC_METTING_LINK_FAILURE: 'js_sdk_enable_static_meeting_link_failure',\n DISABLE_STATIC_MEETING_LINK_SUCCESS: 'js_sdk_disable_static_meeting_link_success',\n DISABLE_STATIC_MEETING_LINK_FAILURE: 'js_sdk_disable_static_meeting_link_failure',\n ADHOC_MEETING_SUCCESS: 'js_sdk_adhoc_meeting_success',\n ADHOC_MEETING_FAILURE: 'js_sdk_adhoc_meeting_failure',\n FETCH_STATIC_MEETING_LINK_SUCCESS: 'js_sdk_fetch_static_meeting_link_success',\n FETCH_STATIC_MEETING_LINK_FAILURE: 'js_sdk_fetch_static_meeting_link_failure',\n MEETING_LINK_DOES_NOT_EXIST_ERROR: 'js_sdk_meeting_link_does_not_exist_error',\n VERIFY_PASSWORD_SUCCESS: 'js_sdk_verify_password_success',\n VERIFY_PASSWORD_ERROR: 'js_sdk_verify_password_error',\n VERIFY_CAPTCHA_ERROR: 'js_sdk_verify_captcha_error',\n MOVE_TO_SUCCESS: 'js_sdk_move_to_success',\n MOVE_TO_FAILURE: 'js_sdk_move_to_failure',\n MOVE_FROM_SUCCESS: 'js_sdk_move_from_success',\n MOVE_FROM_FAILURE: 'js_sdk_move_from_failure',\n TURN_DISCOVERY_FAILURE: 'js_sdk_turn_discovery_failure',\n MEETING_INFO_POLICY_ERROR: 'js_sdk_meeting_info_policy_error',\n LOCUS_DELTA_SYNC_FAILED: 'js_sdk_locus_delta_sync_failed',\n LOCUS_DELTA_OUT_OF_ORDER: 'js_sdk_locus_delta_ooo',\n LOCUS_SYNC_HANDLING_FAILED: 'js_sdk_locus_sync_handling_failed',\n PERMISSION_TOKEN_REFRESH: 'js_sdk_permission_token_refresh',\n PERMISSION_TOKEN_REFRESH_ERROR: 'js_sdk_permission_token_refresh_error',\n TURN_DISCOVERY_LATENCY: 'js_sdk_turn_discovery_latency',\n ROAP_OFFER_TO_ANSWER_LATENCY: 'js_sdk_roap_offer_to_answer_latency',\n ROAP_HTTP_RESPONSE_MISSING: 'js_sdk_roap_http_response_missing',\n TURN_DISCOVERY_REQUIRES_OK: 'js_sdk_turn_discovery_requires_ok',\n REACHABILITY_COMPLETED: 'js_sdk_reachability_completed',\n JOIN_WEBINAR_ERROR: 'js_sdk_join_webinar_error',\n GUEST_ENTERED_LOBBY: 'js_sdk_guest_entered_lobby',\n GUEST_EXITED_LOBBY: 'js_sdk_guest_exited_lobby',\n VERIFY_REGISTRATION_ID_SUCCESS: 'js_sdk_verify_registrationId_success',\n VERIFY_REGISTRATION_ID_ERROR: 'js_sdk_verify_registrationId_error',\n JOIN_FORBIDDEN_ERROR: 'js_sdk_join_forbidden_error',\n MEDIA_ISSUE_DETECTED: 'js_sdk_media_issue_detected',\n LOCUS_CLASSIC_VS_HASH_TREE_MISMATCH: 'js_sdk_locus_classic_vs_hash_tree_mismatch',\n LOCUS_HASH_TREE_UNSUPPORTED_OPERATION: 'js_sdk_locus_hash_tree_unsupported_operation',\n MEDIA_STILL_NOT_CONNECTED: 'js_sdk_media_still_not_connected',\n DEPRECATED_SET_CODEC_PARAMETERS_USED: 'js_sdk_deprecated_set_codec_parameters_used',\n DEPRECATED_DELETE_CODEC_PARAMETERS_USED: 'js_sdk_deprecated_delete_codec_parameters_used',\n SET_CUSTOM_CODEC_PARAMETERS_USED: 'js_sdk_set_custom_codec_parameters_used',\n MARK_CUSTOM_CODEC_PARAMETERS_FOR_DELETION_USED:\n 'js_sdk_mark_custom_codec_parameters_for_deletion_used',\n HASH_TREE_SYNC_FAILURE: 'js_sdk_hash_tree_sync_failure',\n HASH_TREE_HEARTBEAT_WATCHDOG_EXPIRED: 'js_sdk_hash_tree_heartbeat_watchdog_expired',\n HASH_TREE_EMPTY_LOCUS_STATE_ELEMENTS: 'js_sdk_hash_tree_empty_locus_state_elements',\n};\n\nexport {BEHAVIORAL_METRICS as default};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -38,6 +38,7 @@ interface InternalDataSet extends DataSet {
|
|
|
38
38
|
hashTree?: HashTree;
|
|
39
39
|
timer?: ReturnType<typeof setTimeout>;
|
|
40
40
|
heartbeatWatchdogTimer?: ReturnType<typeof setTimeout>;
|
|
41
|
+
syncAbortController?: AbortController;
|
|
41
42
|
}
|
|
42
43
|
type WebexRequestMethod = (options: Record<string, any>) => Promise<any>;
|
|
43
44
|
export declare const LocusInfoUpdateType: {
|
|
@@ -332,6 +333,14 @@ declare class HashTreeParser {
|
|
|
332
333
|
* @returns {Promise<void>}
|
|
333
334
|
*/
|
|
334
335
|
private performSync;
|
|
336
|
+
/**
|
|
337
|
+
* Cancels any pending or in-flight syncs for the specified data sets.
|
|
338
|
+
* This removes matching entries from the sync queue and aborts any in-flight sync HTTP requests.
|
|
339
|
+
*
|
|
340
|
+
* @param {string[]} dataSetNames - The names of the data sets to cancel syncs for
|
|
341
|
+
* @returns {void}
|
|
342
|
+
*/
|
|
343
|
+
private cancelPendingSyncsForDataSets;
|
|
335
344
|
/**
|
|
336
345
|
* Enqueues a sync for the given data set. If the data set is already in the queue, the request is ignored.
|
|
337
346
|
* This ensures that all syncs are executed sequentially and no more than 1 sync runs at a time.
|
|
@@ -93,5 +93,7 @@ declare const BEHAVIORAL_METRICS: {
|
|
|
93
93
|
SET_CUSTOM_CODEC_PARAMETERS_USED: string;
|
|
94
94
|
MARK_CUSTOM_CODEC_PARAMETERS_FOR_DELETION_USED: string;
|
|
95
95
|
HASH_TREE_SYNC_FAILURE: string;
|
|
96
|
+
HASH_TREE_HEARTBEAT_WATCHDOG_EXPIRED: string;
|
|
97
|
+
HASH_TREE_EMPTY_LOCUS_STATE_ELEMENTS: string;
|
|
96
98
|
};
|
|
97
99
|
export { BEHAVIORAL_METRICS as default };
|
package/dist/webinar/index.js
CHANGED
package/package.json
CHANGED
|
@@ -49,6 +49,7 @@ interface InternalDataSet extends DataSet {
|
|
|
49
49
|
hashTree?: HashTree; // set only for visible data sets
|
|
50
50
|
timer?: ReturnType<typeof setTimeout>;
|
|
51
51
|
heartbeatWatchdogTimer?: ReturnType<typeof setTimeout>;
|
|
52
|
+
syncAbortController?: AbortController;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
type WebexRequestMethod = (options: Record<string, any>) => Promise<any>;
|
|
@@ -559,6 +560,8 @@ class HashTreeParser {
|
|
|
559
560
|
)}`
|
|
560
561
|
);
|
|
561
562
|
|
|
563
|
+
this.cancelPendingSyncsForDataSets(dataSets.map((ds) => ds.name));
|
|
564
|
+
|
|
562
565
|
dataSets.forEach((dataSet) => {
|
|
563
566
|
this.updateDataSetInfo(dataSet);
|
|
564
567
|
this.runSyncAlgorithm(dataSet);
|
|
@@ -685,6 +688,12 @@ class HashTreeParser {
|
|
|
685
688
|
|
|
686
689
|
const {dataSets, locus, metadata} = update;
|
|
687
690
|
|
|
691
|
+
LoggerProxy.logger.info(
|
|
692
|
+
`HashTreeParser#handleLocusUpdate --> ${this.debugId} received update with dataSets=${dataSets
|
|
693
|
+
?.map((ds) => ds.name)
|
|
694
|
+
.join(',')} metadata=${metadata ? 'yes' : 'no'}`
|
|
695
|
+
);
|
|
696
|
+
|
|
688
697
|
if (!dataSets) {
|
|
689
698
|
// this happens for example when we handle GET /loci response
|
|
690
699
|
LoggerProxy.logger.info(
|
|
@@ -852,6 +861,8 @@ class HashTreeParser {
|
|
|
852
861
|
*/
|
|
853
862
|
private deleteHashTree(dataSetName: string) {
|
|
854
863
|
this.dataSets[dataSetName].hashTree = undefined;
|
|
864
|
+
this.dataSets[dataSetName].syncAbortController?.abort();
|
|
865
|
+
this.dataSets[dataSetName].syncAbortController = undefined;
|
|
855
866
|
|
|
856
867
|
// we also need to stop the timers as there is no hash tree anymore to sync
|
|
857
868
|
if (this.dataSets[dataSetName].timer) {
|
|
@@ -1001,19 +1012,33 @@ class HashTreeParser {
|
|
|
1001
1012
|
const {dataSets, visibleDataSetsUrl} = message;
|
|
1002
1013
|
|
|
1003
1014
|
LoggerProxy.logger.info(
|
|
1004
|
-
`HashTreeParser#parseMessage --> ${this.debugId}
|
|
1005
|
-
|
|
1015
|
+
`HashTreeParser#parseMessage --> ${this.debugId} ${
|
|
1016
|
+
debugText || ''
|
|
1017
|
+
} dataSets: ${message.dataSets
|
|
1018
|
+
?.map(({name, version}) => `${name}:${version}`)
|
|
1019
|
+
.join(',')}, elements: ${message.locusStateElements
|
|
1020
|
+
?.map(
|
|
1021
|
+
(el) =>
|
|
1022
|
+
`${el.htMeta.elementId.type}:${el.htMeta.elementId.id}:${el.htMeta.elementId.version}${
|
|
1023
|
+
el.data ? '+' : '-'
|
|
1024
|
+
}`
|
|
1025
|
+
)
|
|
1026
|
+
.join(',')}`
|
|
1006
1027
|
);
|
|
1028
|
+
|
|
1007
1029
|
if (message.locusStateElements?.length === 0) {
|
|
1008
1030
|
LoggerProxy.logger.warn(
|
|
1009
1031
|
`HashTreeParser#parseMessage --> ${this.debugId} got empty locusStateElements!!!`
|
|
1010
1032
|
);
|
|
1011
|
-
|
|
1033
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.HASH_TREE_EMPTY_LOCUS_STATE_ELEMENTS, {
|
|
1034
|
+
debugId: this.debugId,
|
|
1035
|
+
});
|
|
1012
1036
|
}
|
|
1013
1037
|
|
|
1014
1038
|
// first, update our metadata about the datasets with info from the message
|
|
1015
1039
|
this.visibleDataSetsUrl = visibleDataSetsUrl;
|
|
1016
1040
|
dataSets.forEach((dataSet) => this.updateDataSetInfo(dataSet));
|
|
1041
|
+
this.cancelPendingSyncsForDataSets(dataSets.map((ds) => ds.name));
|
|
1017
1042
|
|
|
1018
1043
|
const updatedObjects: HashTreeObject[] = [];
|
|
1019
1044
|
|
|
@@ -1237,6 +1262,9 @@ class HashTreeParser {
|
|
|
1237
1262
|
return;
|
|
1238
1263
|
}
|
|
1239
1264
|
|
|
1265
|
+
const abortController = dataSet.syncAbortController ?? new AbortController();
|
|
1266
|
+
dataSet.syncAbortController = abortController;
|
|
1267
|
+
|
|
1240
1268
|
const {hashTree} = dataSet;
|
|
1241
1269
|
const rootHash = hashTree.getRootHash();
|
|
1242
1270
|
|
|
@@ -1285,6 +1313,14 @@ class HashTreeParser {
|
|
|
1285
1313
|
leavesData = {0: hashTree.getLeafData(0)};
|
|
1286
1314
|
}
|
|
1287
1315
|
}
|
|
1316
|
+
|
|
1317
|
+
if (abortController.signal.aborted) {
|
|
1318
|
+
LoggerProxy.logger.info(
|
|
1319
|
+
`HashTreeParser#performSync --> ${this.debugId} abandoning sync for "${dataSet.name}" before /sync - message received during sync`
|
|
1320
|
+
);
|
|
1321
|
+
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1288
1324
|
// request sync for mismatched leaves
|
|
1289
1325
|
let syncResponse: HashTreeMessage | null = null;
|
|
1290
1326
|
|
|
@@ -1302,6 +1338,10 @@ class HashTreeParser {
|
|
|
1302
1338
|
this.runSyncAlgorithm(dataSet);
|
|
1303
1339
|
|
|
1304
1340
|
if (syncResponse) {
|
|
1341
|
+
// clear the abort controller before processing the response so that
|
|
1342
|
+
// parseMessage() -> cancelPendingSyncsForDataSets() doesn't log a
|
|
1343
|
+
// misleading "aborting sync" message for this already-completed sync
|
|
1344
|
+
dataSet.syncAbortController = undefined;
|
|
1305
1345
|
// the format of sync response is the same as messages, so we can reuse the same handler
|
|
1306
1346
|
this.handleMessage(syncResponse, 'via sync API');
|
|
1307
1347
|
}
|
|
@@ -1312,6 +1352,38 @@ class HashTreeParser {
|
|
|
1312
1352
|
error
|
|
1313
1353
|
);
|
|
1314
1354
|
}
|
|
1355
|
+
} finally {
|
|
1356
|
+
dataSet.syncAbortController = undefined;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* Cancels any pending or in-flight syncs for the specified data sets.
|
|
1362
|
+
* This removes matching entries from the sync queue and aborts any in-flight sync HTTP requests.
|
|
1363
|
+
*
|
|
1364
|
+
* @param {string[]} dataSetNames - The names of the data sets to cancel syncs for
|
|
1365
|
+
* @returns {void}
|
|
1366
|
+
*/
|
|
1367
|
+
private cancelPendingSyncsForDataSets(dataSetNames: string[]): void {
|
|
1368
|
+
const previousLength = this.syncQueue.length;
|
|
1369
|
+
|
|
1370
|
+
this.syncQueue = this.syncQueue.filter((entry) => !dataSetNames.includes(entry.dataSetName));
|
|
1371
|
+
|
|
1372
|
+
if (previousLength !== this.syncQueue.length) {
|
|
1373
|
+
LoggerProxy.logger.info(
|
|
1374
|
+
`HashTreeParser#cancelPendingSyncsForDataSets --> ${this.debugId} removed ${
|
|
1375
|
+
previousLength - this.syncQueue.length
|
|
1376
|
+
} entries from sync queue for data sets: ${dataSetNames.join(', ')}`
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
for (const name of dataSetNames) {
|
|
1381
|
+
if (this.dataSets[name]?.syncAbortController) {
|
|
1382
|
+
LoggerProxy.logger.info(
|
|
1383
|
+
`HashTreeParser#cancelPendingSyncsForDataSets --> ${this.debugId} aborting in-flight sync for data set "${name}"`
|
|
1384
|
+
);
|
|
1385
|
+
this.dataSets[name].syncAbortController.abort();
|
|
1386
|
+
}
|
|
1315
1387
|
}
|
|
1316
1388
|
}
|
|
1317
1389
|
|
|
@@ -1432,9 +1504,8 @@ class HashTreeParser {
|
|
|
1432
1504
|
}
|
|
1433
1505
|
|
|
1434
1506
|
if (!dataSet.hashTree) {
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
);
|
|
1507
|
+
// no hash tree, so no need to do any syncing
|
|
1508
|
+
// we fall into this branch often, because Locus sends dataSets in messages that are not visible to us
|
|
1438
1509
|
|
|
1439
1510
|
return;
|
|
1440
1511
|
}
|
|
@@ -1448,10 +1519,6 @@ class HashTreeParser {
|
|
|
1448
1519
|
clearTimeout(dataSet.timer);
|
|
1449
1520
|
}
|
|
1450
1521
|
|
|
1451
|
-
LoggerProxy.logger.info(
|
|
1452
|
-
`HashTreeParser#runSyncAlgorithm --> ${this.debugId} setting "${dataSet.name}" sync timer for ${delay}`
|
|
1453
|
-
);
|
|
1454
|
-
|
|
1455
1522
|
dataSet.timer = setTimeout(() => {
|
|
1456
1523
|
dataSet.timer = undefined;
|
|
1457
1524
|
|
|
@@ -1470,10 +1537,6 @@ class HashTreeParser {
|
|
|
1470
1537
|
dataSet.name,
|
|
1471
1538
|
`Root hash mismatch: received=${dataSet.root}, ours=${rootHash}`
|
|
1472
1539
|
);
|
|
1473
|
-
} else {
|
|
1474
|
-
LoggerProxy.logger.info(
|
|
1475
|
-
`HashTreeParser#runSyncAlgorithm --> ${this.debugId} "${dataSet.name}" root hash matching: ${rootHash}, version=${dataSet.version}`
|
|
1476
|
-
);
|
|
1477
1540
|
}
|
|
1478
1541
|
}, delay);
|
|
1479
1542
|
} else {
|
|
@@ -1520,6 +1583,11 @@ class HashTreeParser {
|
|
|
1520
1583
|
`HashTreeParser#resetHeartbeatWatchdogs --> ${this.debugId} Heartbeat watchdog fired for data set "${dataSet.name}" - no heartbeat received within expected interval, initiating sync`
|
|
1521
1584
|
);
|
|
1522
1585
|
|
|
1586
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.HASH_TREE_HEARTBEAT_WATCHDOG_EXPIRED, {
|
|
1587
|
+
debugId: this.debugId,
|
|
1588
|
+
dataSetName: dataSet.name,
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1523
1591
|
this.enqueueSyncForDataset(dataSet.name, `heartbeat watchdog expired`);
|
|
1524
1592
|
this.resetHeartbeatWatchdogs([dataSet]);
|
|
1525
1593
|
}, delay);
|
|
@@ -1556,6 +1624,8 @@ class HashTreeParser {
|
|
|
1556
1624
|
this.stopAllTimers();
|
|
1557
1625
|
this.syncQueue = [];
|
|
1558
1626
|
Object.values(this.dataSets).forEach((dataSet) => {
|
|
1627
|
+
dataSet.syncAbortController?.abort();
|
|
1628
|
+
dataSet.syncAbortController = undefined;
|
|
1559
1629
|
dataSet.hashTree = undefined;
|
|
1560
1630
|
});
|
|
1561
1631
|
this.visibleDataSets = [];
|
|
@@ -1788,10 +1858,6 @@ class HashTreeParser {
|
|
|
1788
1858
|
body,
|
|
1789
1859
|
})
|
|
1790
1860
|
.then((resp) => {
|
|
1791
|
-
LoggerProxy.logger.info(
|
|
1792
|
-
`HashTreeParser#sendSyncRequestToLocus --> ${this.debugId} Sync request succeeded for "${dataSet.name}"`
|
|
1793
|
-
);
|
|
1794
|
-
|
|
1795
1861
|
if (!resp.body || isEmpty(resp.body)) {
|
|
1796
1862
|
LoggerProxy.logger.info(
|
|
1797
1863
|
`HashTreeParser#sendSyncRequestToLocus --> ${this.debugId} Got ${resp.statusCode} with empty body for sync request for data set "${dataSet.name}", data should arrive via messages`
|
package/src/locus-info/index.ts
CHANGED
|
@@ -749,10 +749,6 @@ export default class LocusInfo extends EventsScope {
|
|
|
749
749
|
|
|
750
750
|
// Active parser found - pass the API response to it
|
|
751
751
|
if (isWrapped) {
|
|
752
|
-
LoggerProxy.logger.info(
|
|
753
|
-
'Locus-info:index#handleLocusAPIResponse --> passing Locus API response to HashTreeParser: ',
|
|
754
|
-
responseBody
|
|
755
|
-
);
|
|
756
752
|
// update the data in our hash trees
|
|
757
753
|
hashTreeParserEntry.parser.handleLocusUpdate(responseBody);
|
|
758
754
|
} else {
|
package/src/metrics/constants.ts
CHANGED
|
@@ -97,6 +97,8 @@ const BEHAVIORAL_METRICS = {
|
|
|
97
97
|
MARK_CUSTOM_CODEC_PARAMETERS_FOR_DELETION_USED:
|
|
98
98
|
'js_sdk_mark_custom_codec_parameters_for_deletion_used',
|
|
99
99
|
HASH_TREE_SYNC_FAILURE: 'js_sdk_hash_tree_sync_failure',
|
|
100
|
+
HASH_TREE_HEARTBEAT_WATCHDOG_EXPIRED: 'js_sdk_hash_tree_heartbeat_watchdog_expired',
|
|
101
|
+
HASH_TREE_EMPTY_LOCUS_STATE_ELEMENTS: 'js_sdk_hash_tree_empty_locus_state_elements',
|
|
100
102
|
};
|
|
101
103
|
|
|
102
104
|
export {BEHAVIORAL_METRICS as default};
|
|
@@ -8,6 +8,7 @@ import {expect} from '@webex/test-helper-chai';
|
|
|
8
8
|
import sinon from 'sinon';
|
|
9
9
|
import {assert} from '@webex/test-helper-chai';
|
|
10
10
|
import {EMPTY_HASH} from '@webex/plugin-meetings/src/hashTree/constants';
|
|
11
|
+
import testUtils from '@webex/plugin-meetings/test/utils/testUtils';
|
|
11
12
|
import { some } from 'lodash';
|
|
12
13
|
import Metrics from '@webex/plugin-meetings/src/metrics';
|
|
13
14
|
import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
|
|
@@ -3072,6 +3073,12 @@ describe('HashTreeParser', () => {
|
|
|
3072
3073
|
})
|
|
3073
3074
|
);
|
|
3074
3075
|
|
|
3076
|
+
// Verify behavioral metric was sent for the watchdog expiration
|
|
3077
|
+
assert.calledWith(metricsStub, BEHAVIORAL_METRICS.HASH_TREE_HEARTBEAT_WATCHDOG_EXPIRED, {
|
|
3078
|
+
debugId: 'test',
|
|
3079
|
+
dataSetName: 'main',
|
|
3080
|
+
});
|
|
3081
|
+
|
|
3075
3082
|
// Verify no sync requests were sent for other datasets
|
|
3076
3083
|
assert.neverCalledWith(
|
|
3077
3084
|
webexRequest,
|
|
@@ -3115,6 +3122,12 @@ describe('HashTreeParser', () => {
|
|
|
3115
3122
|
// Advance time past watchdog delay
|
|
3116
3123
|
await clock.tickAsync(heartbeatIntervalMs);
|
|
3117
3124
|
|
|
3125
|
+
// Verify behavioral metric was sent for the watchdog expiration
|
|
3126
|
+
assert.calledWith(metricsStub, BEHAVIORAL_METRICS.HASH_TREE_HEARTBEAT_WATCHDOG_EXPIRED, {
|
|
3127
|
+
debugId: 'test',
|
|
3128
|
+
dataSetName: 'self',
|
|
3129
|
+
});
|
|
3130
|
+
|
|
3118
3131
|
// For leafCount === 1, performSync skips GET hashtree and goes straight to POST sync
|
|
3119
3132
|
assert.neverCalledWith(
|
|
3120
3133
|
webexRequest,
|
|
@@ -3975,6 +3988,9 @@ describe('HashTreeParser', () => {
|
|
|
3975
3988
|
parser.handleMessage(emptyMessage, 'empty elements');
|
|
3976
3989
|
|
|
3977
3990
|
assert.notCalled(callback);
|
|
3991
|
+
assert.calledWith(metricsStub, BEHAVIORAL_METRICS.HASH_TREE_EMPTY_LOCUS_STATE_ELEMENTS, {
|
|
3992
|
+
debugId: 'test',
|
|
3993
|
+
});
|
|
3978
3994
|
});
|
|
3979
3995
|
|
|
3980
3996
|
it('always calls callback for MEETING_ENDED regardless of filtering', () => {
|
|
@@ -4719,6 +4735,258 @@ describe('HashTreeParser', () => {
|
|
|
4719
4735
|
});
|
|
4720
4736
|
});
|
|
4721
4737
|
|
|
4738
|
+
describe('#performSync abort controller', () => {
|
|
4739
|
+
it('should reuse an existing syncAbortController if one is already set on the dataset', async () => {
|
|
4740
|
+
const parser = createHashTreeParser();
|
|
4741
|
+
const mainUrl = parser.dataSets.main.url;
|
|
4742
|
+
|
|
4743
|
+
// Pre-set an AbortController on the dataset before sync starts
|
|
4744
|
+
const existingController = new AbortController();
|
|
4745
|
+
parser.dataSets.main.syncAbortController = existingController;
|
|
4746
|
+
|
|
4747
|
+
// Use a deferred promise for GET hashtree so we can inspect the controller mid-sync
|
|
4748
|
+
let resolveGetHashtree;
|
|
4749
|
+
webexRequest.withArgs(sinon.match({method: 'GET', uri: `${mainUrl}/hashtree`})).callsFake(
|
|
4750
|
+
() =>
|
|
4751
|
+
new Promise((resolve) => {
|
|
4752
|
+
resolveGetHashtree = resolve;
|
|
4753
|
+
})
|
|
4754
|
+
);
|
|
4755
|
+
|
|
4756
|
+
// Trigger sync for main
|
|
4757
|
+
parser.handleMessage(
|
|
4758
|
+
createHeartbeatMessage('main', 16, 1100, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1'),
|
|
4759
|
+
'trigger main sync'
|
|
4760
|
+
);
|
|
4761
|
+
|
|
4762
|
+
await clock.tickAsync(1000);
|
|
4763
|
+
|
|
4764
|
+
// While sync is in-flight, verify the controller is the same one we pre-set
|
|
4765
|
+
expect(parser.dataSets.main.syncAbortController).to.equal(existingController);
|
|
4766
|
+
|
|
4767
|
+
// Resolve GET hashtree with matching hashes (no sync needed)
|
|
4768
|
+
resolveGetHashtree({body: {}});
|
|
4769
|
+
await testUtils.flushPromises();
|
|
4770
|
+
|
|
4771
|
+
// After sync completes, syncAbortController is cleared in finally
|
|
4772
|
+
expect(parser.dataSets.main.syncAbortController).to.be.undefined;
|
|
4773
|
+
});
|
|
4774
|
+
|
|
4775
|
+
it('should abort the sync before /sync request when the controller is aborted during getHashesFromLocus', async () => {
|
|
4776
|
+
const parser = createHashTreeParser();
|
|
4777
|
+
const mainUrl = parser.dataSets.main.url;
|
|
4778
|
+
|
|
4779
|
+
// Use a deferred promise for GET hashtree so we can abort while it's pending
|
|
4780
|
+
let resolveGetHashtree;
|
|
4781
|
+
webexRequest.withArgs(sinon.match({method: 'GET', uri: `${mainUrl}/hashtree`})).callsFake(
|
|
4782
|
+
() =>
|
|
4783
|
+
new Promise((resolve) => {
|
|
4784
|
+
resolveGetHashtree = resolve;
|
|
4785
|
+
})
|
|
4786
|
+
);
|
|
4787
|
+
|
|
4788
|
+
// Mock POST sync - should NOT be called if abort works
|
|
4789
|
+
mockSendSyncRequestResponse(mainUrl, null);
|
|
4790
|
+
|
|
4791
|
+
// Trigger sync for main via heartbeat with mismatched root hash
|
|
4792
|
+
parser.handleMessage(
|
|
4793
|
+
createHeartbeatMessage('main', 16, 1100, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1'),
|
|
4794
|
+
'trigger main sync'
|
|
4795
|
+
);
|
|
4796
|
+
|
|
4797
|
+
// Fire the timer to start the sync
|
|
4798
|
+
await clock.tickAsync(1000);
|
|
4799
|
+
|
|
4800
|
+
// Now abort the controller while getHashesFromLocus is pending
|
|
4801
|
+
expect(parser.dataSets.main.syncAbortController).to.not.be.undefined;
|
|
4802
|
+
parser.dataSets.main.syncAbortController.abort();
|
|
4803
|
+
|
|
4804
|
+
// Resolve GET hashtree with mismatched hashes so the code would normally proceed to /sync
|
|
4805
|
+
resolveGetHashtree({
|
|
4806
|
+
body: {
|
|
4807
|
+
hashes: new Array(16).fill('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1'),
|
|
4808
|
+
dataSet: createDataSet('main', 16, 1100),
|
|
4809
|
+
},
|
|
4810
|
+
});
|
|
4811
|
+
|
|
4812
|
+
await testUtils.flushPromises();
|
|
4813
|
+
|
|
4814
|
+
// POST sync should NOT have been called because the controller was aborted
|
|
4815
|
+
assert.neverCalledWith(webexRequest, sinon.match({method: 'POST', uri: `${mainUrl}/sync`}));
|
|
4816
|
+
});
|
|
4817
|
+
|
|
4818
|
+
it('should abort the sync before /sync request when the controller is aborted for leafCount === 1 datasets', async () => {
|
|
4819
|
+
const parser = createHashTreeParser();
|
|
4820
|
+
const selfUrl = parser.dataSets.self.url;
|
|
4821
|
+
|
|
4822
|
+
// Pre-set an already-aborted controller so performSync picks it up via ??
|
|
4823
|
+
const abortedController = new AbortController();
|
|
4824
|
+
abortedController.abort();
|
|
4825
|
+
parser.dataSets.self.syncAbortController = abortedController;
|
|
4826
|
+
|
|
4827
|
+
// Mock POST sync - should NOT be called
|
|
4828
|
+
mockSendSyncRequestResponse(selfUrl, null);
|
|
4829
|
+
|
|
4830
|
+
// Trigger sync for self via heartbeat with mismatched root hash
|
|
4831
|
+
parser.handleMessage(
|
|
4832
|
+
{
|
|
4833
|
+
dataSets: [
|
|
4834
|
+
{
|
|
4835
|
+
...createDataSet('self', 1, 2100),
|
|
4836
|
+
url: parser.dataSets.self.url,
|
|
4837
|
+
root: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb1',
|
|
4838
|
+
},
|
|
4839
|
+
],
|
|
4840
|
+
visibleDataSetsUrl,
|
|
4841
|
+
locusUrl,
|
|
4842
|
+
},
|
|
4843
|
+
'trigger self sync'
|
|
4844
|
+
);
|
|
4845
|
+
|
|
4846
|
+
// Fire the timer to start the sync
|
|
4847
|
+
await clock.tickAsync(1000);
|
|
4848
|
+
|
|
4849
|
+
// GET hashtree should NOT have been called (leafCount === 1 skips it)
|
|
4850
|
+
assert.neverCalledWith(webexRequest, sinon.match({method: 'GET', uri: `${selfUrl}/hashtree`}));
|
|
4851
|
+
|
|
4852
|
+
// POST sync should NOT have been called because the controller was already aborted
|
|
4853
|
+
assert.neverCalledWith(webexRequest, sinon.match({method: 'POST', uri: `${selfUrl}/sync`}));
|
|
4854
|
+
});
|
|
4855
|
+
|
|
4856
|
+
it('should unconditionally clear syncAbortController in the finally block', async () => {
|
|
4857
|
+
const parser = createHashTreeParser();
|
|
4858
|
+
const mainUrl = parser.dataSets.main.url;
|
|
4859
|
+
|
|
4860
|
+
// Mock GET hashtree to return matching hashes (early return, no sync needed)
|
|
4861
|
+
webexRequest
|
|
4862
|
+
.withArgs(sinon.match({method: 'GET', uri: `${mainUrl}/hashtree`}))
|
|
4863
|
+
.resolves({body: {}});
|
|
4864
|
+
|
|
4865
|
+
// Trigger sync for main
|
|
4866
|
+
parser.handleMessage(
|
|
4867
|
+
createHeartbeatMessage('main', 16, 1100, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1'),
|
|
4868
|
+
'trigger main sync'
|
|
4869
|
+
);
|
|
4870
|
+
|
|
4871
|
+
await clock.tickAsync(1000);
|
|
4872
|
+
|
|
4873
|
+
// After sync completes (even via early return), syncAbortController should be cleared
|
|
4874
|
+
expect(parser.dataSets.main.syncAbortController).to.be.undefined;
|
|
4875
|
+
});
|
|
4876
|
+
|
|
4877
|
+
it('should unconditionally clear syncAbortController even when sync throws an error', async () => {
|
|
4878
|
+
const parser = createHashTreeParser();
|
|
4879
|
+
const mainUrl = parser.dataSets.main.url;
|
|
4880
|
+
|
|
4881
|
+
// Mock GET hashtree to reject with a non-409 error
|
|
4882
|
+
webexRequest
|
|
4883
|
+
.withArgs(sinon.match({method: 'GET', uri: `${mainUrl}/hashtree`}))
|
|
4884
|
+
.rejects({statusCode: 500, message: 'Internal Server Error'});
|
|
4885
|
+
|
|
4886
|
+
// Trigger sync for main
|
|
4887
|
+
parser.handleMessage(
|
|
4888
|
+
createHeartbeatMessage('main', 16, 1100, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1'),
|
|
4889
|
+
'trigger main sync'
|
|
4890
|
+
);
|
|
4891
|
+
|
|
4892
|
+
await clock.tickAsync(1000);
|
|
4893
|
+
|
|
4894
|
+
// After sync completes with error, syncAbortController should still be cleared
|
|
4895
|
+
expect(parser.dataSets.main.syncAbortController).to.be.undefined;
|
|
4896
|
+
});
|
|
4897
|
+
|
|
4898
|
+
it('should reuse a pre-existing abort controller and respect its aborted state', async () => {
|
|
4899
|
+
const parser = createHashTreeParser();
|
|
4900
|
+
const mainUrl = parser.dataSets.main.url;
|
|
4901
|
+
|
|
4902
|
+
// Pre-set an AbortController and abort it before sync starts
|
|
4903
|
+
const preAbortedController = new AbortController();
|
|
4904
|
+
preAbortedController.abort();
|
|
4905
|
+
parser.dataSets.main.syncAbortController = preAbortedController;
|
|
4906
|
+
|
|
4907
|
+
// Mock GET hashtree to return mismatched hashes
|
|
4908
|
+
mockGetHashesFromLocusResponse(
|
|
4909
|
+
mainUrl,
|
|
4910
|
+
new Array(16).fill('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1'),
|
|
4911
|
+
createDataSet('main', 16, 1100)
|
|
4912
|
+
);
|
|
4913
|
+
|
|
4914
|
+
// Mock POST sync - should NOT be called
|
|
4915
|
+
mockSendSyncRequestResponse(mainUrl, null);
|
|
4916
|
+
|
|
4917
|
+
// Trigger sync for main
|
|
4918
|
+
parser.handleMessage(
|
|
4919
|
+
createHeartbeatMessage('main', 16, 1100, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1'),
|
|
4920
|
+
'trigger main sync'
|
|
4921
|
+
);
|
|
4922
|
+
|
|
4923
|
+
await clock.tickAsync(1000);
|
|
4924
|
+
|
|
4925
|
+
// POST sync should NOT have been called because the reused controller was already aborted
|
|
4926
|
+
assert.neverCalledWith(webexRequest, sinon.match({method: 'POST', uri: `${mainUrl}/sync`}));
|
|
4927
|
+
|
|
4928
|
+
// syncAbortController should be cleaned up
|
|
4929
|
+
expect(parser.dataSets.main.syncAbortController).to.be.undefined;
|
|
4930
|
+
});
|
|
4931
|
+
|
|
4932
|
+
it('should allow cancelPendingSyncsForDataSets to abort an in-flight sync via the shared controller', async () => {
|
|
4933
|
+
const parser = createHashTreeParser();
|
|
4934
|
+
const mainUrl = parser.dataSets.main.url;
|
|
4935
|
+
|
|
4936
|
+
// Use a deferred promise for GET hashtree
|
|
4937
|
+
let resolveGetHashtree;
|
|
4938
|
+
webexRequest.withArgs(sinon.match({method: 'GET', uri: `${mainUrl}/hashtree`})).callsFake(
|
|
4939
|
+
() =>
|
|
4940
|
+
new Promise((resolve) => {
|
|
4941
|
+
resolveGetHashtree = resolve;
|
|
4942
|
+
})
|
|
4943
|
+
);
|
|
4944
|
+
|
|
4945
|
+
mockSendSyncRequestResponse(mainUrl, null);
|
|
4946
|
+
|
|
4947
|
+
// Trigger sync for main
|
|
4948
|
+
parser.handleMessage(
|
|
4949
|
+
createHeartbeatMessage('main', 16, 1100, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1'),
|
|
4950
|
+
'trigger main sync'
|
|
4951
|
+
);
|
|
4952
|
+
|
|
4953
|
+
// Fire the timer to start sync
|
|
4954
|
+
await clock.tickAsync(1000);
|
|
4955
|
+
|
|
4956
|
+
// Verify controller is set
|
|
4957
|
+
expect(parser.dataSets.main.syncAbortController).to.not.be.undefined;
|
|
4958
|
+
|
|
4959
|
+
// Simulate a new heartbeat arriving that cancels the in-flight sync
|
|
4960
|
+
// (this is what happens in production via parseMessage -> cancelPendingSyncsForDataSets)
|
|
4961
|
+
parser.handleMessage(
|
|
4962
|
+
{
|
|
4963
|
+
dataSets: [
|
|
4964
|
+
{
|
|
4965
|
+
...createDataSet('main', 16, 1101),
|
|
4966
|
+
root: parser.dataSets.main.hashTree.getRootHash(), // matching hash so no new sync
|
|
4967
|
+
},
|
|
4968
|
+
],
|
|
4969
|
+
visibleDataSetsUrl,
|
|
4970
|
+
locusUrl,
|
|
4971
|
+
},
|
|
4972
|
+
'new heartbeat cancels sync'
|
|
4973
|
+
);
|
|
4974
|
+
|
|
4975
|
+
// Resolve the pending GET hashtree with mismatched hashes
|
|
4976
|
+
resolveGetHashtree({
|
|
4977
|
+
body: {
|
|
4978
|
+
hashes: new Array(16).fill('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1'),
|
|
4979
|
+
dataSet: createDataSet('main', 16, 1101),
|
|
4980
|
+
},
|
|
4981
|
+
});
|
|
4982
|
+
|
|
4983
|
+
await testUtils.flushPromises();
|
|
4984
|
+
|
|
4985
|
+
// POST sync should NOT have been called because cancelPendingSyncsForDataSets aborted the controller
|
|
4986
|
+
assert.neverCalledWith(webexRequest, sinon.match({method: 'POST', uri: `${mainUrl}/sync`}));
|
|
4987
|
+
});
|
|
4988
|
+
});
|
|
4989
|
+
|
|
4722
4990
|
describe('#cleanUp', () => {
|
|
4723
4991
|
it('should stop the parser, clear all timers and clear all dataSets', () => {
|
|
4724
4992
|
const parser = createHashTreeParser();
|