# SyncHub

- File ID: file:47d4sb:synchub-cs
- Path: NexusReader.Infrastructure/RealTime/SyncHub.cs
- Role: endpoint
- Layer: infrastructure

## Purpose

Defines a SignalR Hub that accepts authenticated client connections and forwards reading-progress updates to the application layer via MediatR, and manages per-user SignalR groups on connect/disconnect.

## Key Details

Class is Authorize-protected; adds/removes connections to per-user groups named "User_{userId}" on connect/disconnect; forwards progress updates as UpdateReadingProgressCommand including Context.ConnectionId via IMediator.

## Symbols

- SyncHub: Defines a SignalR Hub that accepts authenticated client connections and forwards reading-progress updates to the application layer via MediatR, and manages per-user SignalR groups on connect/disconnect.
- SyncHub(): Constructs the hub and stores an IMediator reference.
- UpdateProgress(): If the connection is associated with an authenticated user, create and forward an UpdateReadingProgressCommand (including ConnectionId) to the application via MediatR.
- OnConnectedAsync(): When a client connects, add the connection to a per-user SignalR group if UserIdentifier is present, then call base.OnConnectedAsync.
- OnDisconnectedAsync(): When a client disconnects, remove the connection from the per-user SignalR group if UserIdentifier is present, then call base.OnDisconnectedAsync(exception).
- SyncHub: SignalR Hub that handles real-time reading progress updates and per-user group membership.

## Dependencies

- No resolved dependencies.

## Calls

- No resolved calls.

## Unresolved References

- dependency-target: NexusReader.Application.Commands.Sync
- call-target: NexusReader.Application.Commands.Sync
- interaction-target: var userId = Context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
- interaction-target: await _mediator.Send(new UpdateReadingProgressCommand(pageId, userId, ebookId, progress, chapterTitle, chapterIndex, Context.ConnectionId));
- interaction-target: var userId = Context.UserIdentifier;
- interaction-target: await Groups.AddToGroupAsync(Context.ConnectionId, $"User_{userId}");
- interaction-target: var userId = Context.UserIdentifier;
- interaction-target: await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"User_{userId}");
- interaction-target: Groups.AddToGroupAsync(Context.ConnectionId, $"User_{userId}") — SignalR group membership change
- interaction-target: await base.OnConnectedAsync()
- interaction-target: Groups.RemoveFromGroupAsync(Context.ConnectionId, $"User_{userId}") — SignalR group membership change
- interaction-target: await base.OnDisconnectedAsync(exception)

## Coverage

- Symbols: 6
- Executable symbols: 4
- Executable symbols with control flow: 4
- Grounded edges: 6
- Unresolved references: 12
- Control-flow fragments: 4
- Unresolved jumps: 1
- Unsupported constructs: 0
- Control-flow coverage: 100%
- Required diagrams: 2
- Generated diagrams: 5

## Control Flow Fragments

- SyncHub: 3 nodes, 2 edges, 0 jumps, 0 unsupported constructs
- UpdateProgress: 16 nodes, 17 edges, 1 jumps, 0 unsupported constructs
- OnConnectedAsync: 16 nodes, 17 edges, 0 jumps, 0 unsupported constructs
- OnDisconnectedAsync: 16 nodes, 17 edges, 0 jumps, 0 unsupported constructs

## Diagrams

### SyncHub Neighborhood

```mermaid
flowchart LR
    class_154519v_synchub("SyncHub")
    file_47d4sb_synchub_cs("SyncHub")
    method_1gkzz9r_updateprogress[["UpdateProgress()"]]
    method_1jy35qw_ondisconnectedasync[["OnDisconnectedAsync()"]]
    method_1whrhth_onconnectedasync[["OnConnectedAsync()"]]
    method_vlrx66_synchub[["SyncHub()"]]
    type_wpqby2_synchub[/"SyncHub"/]
```

### SyncHub Execution Overview

```mermaid
flowchart TD
    class_154519v_synchub("SyncHub")
    method_1gkzz9r_updateprogress[["UpdateProgress()"]]
    method_1jy35qw_ondisconnectedasync[["OnDisconnectedAsync()"]]
    method_1whrhth_onconnectedasync[["OnConnectedAsync()"]]
    method_vlrx66_synchub[["SyncHub()"]]
    unresolved_unresolved_103s891_interaction_target{"Unresolved: await Groups.RemoveFromGroupAsync(Context.ConnectionId, $\\"User_{userId}\\");"}
    unresolved_unresolved_12xuggn_interaction_target{"Unresolved: await base.OnDisconnectedAsync(exception)"}
    unresolved_unresolved_16crk4v_call_target{"Unresolved: UpdateReadingProgressCommand (ctor)"}
    unresolved_unresolved_1dpzx9c_interaction_target{"Unresolved: var userId = Context.UserIdentifier;"}
    unresolved_unresolved_1qw4zqv_interaction_target{"Unresolved: Groups.RemoveFromGroupAsync(Context.ConnectionId, $\\"User_{userId}\\") — SignalR group membership change"}
    unresolved_unresolved_1sfqks3_interaction_target{"Unresolved: var userId = Context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;"}
    unresolved_unresolved_1ts5omd_interaction_target{"Unresolved: Groups.AddToGroupAsync(Context.ConnectionId, $\\"User_{userId}\\") — SignalR group membership change"}
    unresolved_unresolved_lmelii_interaction_target{"Unresolved: await _mediator.Send(new UpdateReadingProgressCommand(pageId, userId, ebookId, progress, chapterTitle, chapterIndex, Context.ConnectionId));"}
    unresolved_unresolved_maydcv_interaction_target{"Unresolved: await Groups.AddToGroupAsync(Context.ConnectionId, $\\"User_{userId}\\");"}
    unresolved_unresolved_u0mqhe_interaction_target{"Unresolved: await base.OnConnectedAsync()"}
    class_154519v_synchub -->|contains| method_1gkzz9r_updateprogress
    class_154519v_synchub -->|contains| method_1jy35qw_ondisconnectedasync
    class_154519v_synchub -->|contains| method_1whrhth_onconnectedasync
    class_154519v_synchub -->|contains| method_vlrx66_synchub
    class_154519v_synchub -->|unresolved interaction| unresolved_unresolved_103s891_interaction_target
    class_154519v_synchub -->|unresolved interaction| unresolved_unresolved_12xuggn_interaction_target
    class_154519v_synchub -->|unresolved interaction| unresolved_unresolved_1dpzx9c_interaction_target
    class_154519v_synchub -->|unresolved interaction| unresolved_unresolved_1dpzx9c_interaction_target
    class_154519v_synchub -->|unresolved interaction| unresolved_unresolved_1qw4zqv_interaction_target
    class_154519v_synchub -->|unresolved interaction| unresolved_unresolved_1sfqks3_interaction_target
    class_154519v_synchub -->|unresolved interaction| unresolved_unresolved_1ts5omd_interaction_target
    class_154519v_synchub -->|unresolved interaction| unresolved_unresolved_lmelii_interaction_target
    class_154519v_synchub -->|unresolved interaction| unresolved_unresolved_maydcv_interaction_target
    class_154519v_synchub -->|unresolved interaction| unresolved_unresolved_u0mqhe_interaction_target
    method_1gkzz9r_updateprogress -->|unresolved call| unresolved_unresolved_16crk4v_call_target
```

### UpdateProgress() Control Flow

```mermaid
flowchart TD
    method_1gkzz9r_updateprogress__call_24_UpdateReadingProgressCommand_ctor_[["UpdateReadingProgressCommand (ctor)"]]
    method_1gkzz9r_updateprogress__action_21_guard_clause_21__condition{"Context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value != null"}
    method_1gkzz9r_updateprogress__action_21_guard_clause_21__false("continue (has userId)")
    method_1gkzz9r_updateprogress__action_21_guard_clause_21__merge("merge")
    method_1gkzz9r_updateprogress__action_21_guard_clause_21__true("skip (no userId)")
    method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__condition{"userId != null"}
    method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__false("false")
    method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__merge("merge")
    method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__true("true")
    method_1gkzz9r_updateprogress__action_24_construct_24__linear("Create UpdateReadingProgressCommand")
    method_1gkzz9r_updateprogress__action_24_external_orchestration_24__linear("Forward command via mediator")
    method_1gkzz9r_updateprogress__action_24_updateprogress_await_24_3__await[["Waits for async work"]]
    method_1gkzz9r_updateprogress__entry(["UpdateProgress entry"])
    method_1gkzz9r_updateprogress__action_21_updateprogress_repository_read_21_0__linear[("Reads repository or persistence state")]
    method_1gkzz9r_updateprogress__action_24_updateprogress_repository_read_24_2__linear[("Reads repository or persistence state")]
    method_1gkzz9r_updateprogress__implicit_return(["return"])
    method_1gkzz9r_updateprogress__action_21_guard_clause_21__condition -->|continue has userId| method_1gkzz9r_updateprogress__action_21_guard_clause_21__false
    method_1gkzz9r_updateprogress__action_21_guard_clause_21__condition -->|skip no userId| method_1gkzz9r_updateprogress__action_21_guard_clause_21__true
    method_1gkzz9r_updateprogress__action_21_guard_clause_21__false -->|next| method_1gkzz9r_updateprogress__action_21_guard_clause_21__merge
    method_1gkzz9r_updateprogress__action_21_guard_clause_21__merge -->|next| method_1gkzz9r_updateprogress__action_21_updateprogress_repository_read_21_0__linear
    method_1gkzz9r_updateprogress__action_21_guard_clause_21__true -->|next| method_1gkzz9r_updateprogress__action_21_guard_clause_21__merge
    method_1gkzz9r_updateprogress__action_21_updateprogress_repository_read_21_0__linear -->|next| method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__condition
    method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__condition -->|false| method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__false
    method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__condition -->|true| method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__true
    method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__false -->|next| method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__merge
    method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__merge -->|next| method_1gkzz9r_updateprogress__action_24_updateprogress_await_24_3__await
    method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__true -->|next| method_1gkzz9r_updateprogress__action_22_updateprogress_branch_22_1__merge
    method_1gkzz9r_updateprogress__action_24_construct_24__linear -->|pageId, userId, ebookId, progress, chapterTitle, chapterIndex, Context.ConnectionId| method_1gkzz9r_updateprogress__call_24_UpdateReadingProgressCommand_ctor_
    method_1gkzz9r_updateprogress__action_24_external_orchestration_24__linear -->|next| method_1gkzz9r_updateprogress__action_24_construct_24__linear
    method_1gkzz9r_updateprogress__action_24_updateprogress_await_24_3__await -->|next| method_1gkzz9r_updateprogress__action_24_updateprogress_repository_read_24_2__linear
    method_1gkzz9r_updateprogress__action_24_updateprogress_repository_read_24_2__linear -->|next| method_1gkzz9r_updateprogress__action_24_external_orchestration_24__linear
    method_1gkzz9r_updateprogress__call_24_UpdateReadingProgressCommand_ctor_ -->|return| method_1gkzz9r_updateprogress__implicit_return
    method_1gkzz9r_updateprogress__entry -->|next| method_1gkzz9r_updateprogress__action_21_guard_clause_21__condition
```

### OnConnectedAsync() Control Flow

```mermaid
flowchart TD
    method_1whrhth_onconnectedasync__action_31_guard_clause_31__condition{"!string.IsNullOrEmpty(Context.UserIdentifier)"}
    method_1whrhth_onconnectedasync__action_31_guard_clause_31__false("add-to-group")
    method_1whrhth_onconnectedasync__action_31_guard_clause_31__merge("merge")
    method_1whrhth_onconnectedasync__action_31_guard_clause_31__true("skip (no UserIdentifier)")
    method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__condition{"!string.IsNullOrEmpty(userId)"}
    method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__false("false")
    method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__merge("merge")
    method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__true("true")
    method_1whrhth_onconnectedasync__action_33_onconnectedasync_await_33_3__await[["Waits for async work"]]
    method_1whrhth_onconnectedasync__action_35_onconnectedasync_await_35_4__await[["Waits for async work"]]
    method_1whrhth_onconnectedasync__entry(["OnConnectedAsync entry"])
    method_1whrhth_onconnectedasync__action_30_onconnectedasync_repository_read_30_0__linear[("Reads repository or persistence state")]
    method_1whrhth_onconnectedasync__action_33_external_call_33__linear("Add connection to per-user group")
    method_1whrhth_onconnectedasync__action_33_onconnectedasync_repository_read_33_2__linear[("Reads repository or persistence state")]
    method_1whrhth_onconnectedasync__action_35_external_call_35__linear("Call base hub connect handler")
    method_1whrhth_onconnectedasync__implicit_return(["return"])
    method_1whrhth_onconnectedasync__action_30_onconnectedasync_repository_read_30_0__linear -->|next| method_1whrhth_onconnectedasync__action_31_guard_clause_31__condition
    method_1whrhth_onconnectedasync__action_31_guard_clause_31__condition -->|add-to-group| method_1whrhth_onconnectedasync__action_31_guard_clause_31__false
    method_1whrhth_onconnectedasync__action_31_guard_clause_31__condition -->|skip no UserIdentifier| method_1whrhth_onconnectedasync__action_31_guard_clause_31__true
    method_1whrhth_onconnectedasync__action_31_guard_clause_31__false -->|next| method_1whrhth_onconnectedasync__action_31_guard_clause_31__merge
    method_1whrhth_onconnectedasync__action_31_guard_clause_31__merge -->|next| method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__condition
    method_1whrhth_onconnectedasync__action_31_guard_clause_31__true -->|next| method_1whrhth_onconnectedasync__action_31_guard_clause_31__merge
    method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__condition -->|false| method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__false
    method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__condition -->|true| method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__true
    method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__false -->|next| method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__merge
    method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__merge -->|next| method_1whrhth_onconnectedasync__action_33_onconnectedasync_await_33_3__await
    method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__true -->|next| method_1whrhth_onconnectedasync__action_31_onconnectedasync_branch_31_1__merge
    method_1whrhth_onconnectedasync__action_33_external_call_33__linear -->|next| method_1whrhth_onconnectedasync__action_35_onconnectedasync_await_35_4__await
    method_1whrhth_onconnectedasync__action_33_onconnectedasync_await_33_3__await -->|next| method_1whrhth_onconnectedasync__action_33_onconnectedasync_repository_read_33_2__linear
    method_1whrhth_onconnectedasync__action_33_onconnectedasync_repository_read_33_2__linear -->|next| method_1whrhth_onconnectedasync__action_33_external_call_33__linear
    method_1whrhth_onconnectedasync__action_35_external_call_35__linear -->|return| method_1whrhth_onconnectedasync__implicit_return
    method_1whrhth_onconnectedasync__action_35_onconnectedasync_await_35_4__await -->|next| method_1whrhth_onconnectedasync__action_35_external_call_35__linear
    method_1whrhth_onconnectedasync__entry -->|next| method_1whrhth_onconnectedasync__action_30_onconnectedasync_repository_read_30_0__linear
```

### OnDisconnectedAsync() Control Flow

```mermaid
flowchart TD
    method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__condition{"!string.IsNullOrEmpty(Context.UserIdentifier)"}
    method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__false("remove-from-group")
    method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__merge("merge")
    method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__true("skip (no UserIdentifier)")
    method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__condition{"!string.IsNullOrEmpty(userId)"}
    method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__false("false")
    method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__merge("merge")
    method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__true("true")
    method_1jy35qw_ondisconnectedasync__action_43_ondisconnectedasync_await_43_3__await[["Waits for async work"]]
    method_1jy35qw_ondisconnectedasync__action_45_ondisconnectedasync_await_45_4__await[["Waits for async work"]]
    method_1jy35qw_ondisconnectedasync__entry(["OnDisconnectedAsync entry"])
    method_1jy35qw_ondisconnectedasync__action_40_ondisconnectedasync_repository_read_40_0__linear[("Reads repository or persistence state")]
    method_1jy35qw_ondisconnectedasync__action_43_external_call_43__linear("Remove connection from per-user group")
    method_1jy35qw_ondisconnectedasync__action_43_ondisconnectedasync_repository_read_43_2__linear[("Reads repository or persistence state")]
    method_1jy35qw_ondisconnectedasync__action_45_external_call_45__linear("Call base hub disconnect handler")
    method_1jy35qw_ondisconnectedasync__implicit_return(["return"])
    method_1jy35qw_ondisconnectedasync__action_40_ondisconnectedasync_repository_read_40_0__linear -->|next| method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__condition
    method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__condition -->|remove-from-group| method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__false
    method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__condition -->|skip no UserIdentifier| method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__true
    method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__false -->|next| method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__merge
    method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__merge -->|next| method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__condition
    method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__true -->|next| method_1jy35qw_ondisconnectedasync__action_41_guard_clause_41__merge
    method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__condition -->|false| method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__false
    method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__condition -->|true| method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__true
    method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__false -->|next| method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__merge
    method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__merge -->|next| method_1jy35qw_ondisconnectedasync__action_43_ondisconnectedasync_await_43_3__await
    method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__true -->|next| method_1jy35qw_ondisconnectedasync__action_41_ondisconnectedasync_branch_41_1__merge
    method_1jy35qw_ondisconnectedasync__action_43_external_call_43__linear -->|next| method_1jy35qw_ondisconnectedasync__action_43_ondisconnectedasync_repository_read_43_2__linear
    method_1jy35qw_ondisconnectedasync__action_43_ondisconnectedasync_await_43_3__await -->|next| method_1jy35qw_ondisconnectedasync__action_43_external_call_43__linear
    method_1jy35qw_ondisconnectedasync__action_43_ondisconnectedasync_repository_read_43_2__linear -->|next| method_1jy35qw_ondisconnectedasync__action_45_ondisconnectedasync_await_45_4__await
    method_1jy35qw_ondisconnectedasync__action_45_external_call_45__linear -->|return| method_1jy35qw_ondisconnectedasync__implicit_return
    method_1jy35qw_ondisconnectedasync__action_45_ondisconnectedasync_await_45_4__await -->|next| method_1jy35qw_ondisconnectedasync__action_45_external_call_45__linear
    method_1jy35qw_ondisconnectedasync__entry -->|next| method_1jy35qw_ondisconnectedasync__action_40_ondisconnectedasync_repository_read_40_0__linear
```

## Diagram Validation

- Status: warn
- Warning: Control-flow extraction left 1 unresolved jumps.
