@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.
@@ -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":"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","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":";;;;;;;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,+BAA+B;EACvDC,oCAAoC,EAAE,6CAA6C;EACnFC,oCAAoC,EAAE;AACxC,CAAC","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 };
@@ -774,7 +774,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
774
774
  }, _callee1);
775
775
  }))();
776
776
  },
777
- version: "3.12.0-next.54"
777
+ version: "3.12.0-next.56"
778
778
  });
779
779
  var _default = exports.default = Webinar;
780
780
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -94,5 +94,5 @@
94
94
  "//": [
95
95
  "TODO: upgrade jwt-decode when moving to node 18"
96
96
  ],
97
- "version": "3.12.0-next.54"
97
+ "version": "3.12.0-next.56"
98
98
  }
@@ -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} received message ${debugText || ''}:`,
1005
- message
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
- // todo: send a metric
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
- LoggerProxy.logger.info(
1436
- `HashTreeParser#runSyncAlgorithm --> ${this.debugId} Data set "${dataSet.name}" has no hash tree, skipping sync algorithm`
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`
@@ -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 {
@@ -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();