-
Notifications
You must be signed in to change notification settings - Fork 4
Accept friend requests reactively via social RTA subscription #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 10 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
2be2ab1
Accept friend requests reactively via social RTA subscription
HashimTheArab 3ffa5e6
Undo social RTA subscription on broadcaster close
HashimTheArab 0c66b54
Guard against a closed friend-sync Trigger channel
HashimTheArab 87b1b9e
Release the social subscription per-handler instead of client-wide
HashimTheArab d222bc5
Use xsapi-owned social and presence clients
HashimTheArab c135828
Update owned Xbox clients against main and fix lifecycle coverage
HashimTheArab f77cc0f
Merge current main into reactive friend requests
HashimTheArab 610614c
Update reactive friend sync with shared-client lifecycle and current …
HashimTheArab 5d5e73c
Use registration cleanup and sync with merged client ownership
HashimTheArab c907b9e
Pin merged xsapi social subscription cleanup
HashimTheArab 7473e24
Bound reactive friend scans and honor read backoff
HashimTheArab 5b3e079
Bound social RTA subscription setup
HashimTheArab File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| package broadcaster | ||
|
|
||
| import ( | ||
| "context" | ||
| "log/slog" | ||
| "time" | ||
|
|
||
| "github.com/df-mc/go-xsapi/v2" | ||
| xblsocial "github.com/df-mc/go-xsapi/v2/social" | ||
| ) | ||
|
|
||
| // socialSubscriber is the part of go-xsapi's social client the broadcaster uses | ||
| // to receive and release RTA relationship events. It is satisfied by | ||
| // [*xblsocial.Client]. Subscribe returns a cleanup function for only that | ||
| // registration, leaving other subscribers on a shared client untouched. | ||
| type socialSubscriber interface { | ||
| Subscribe(context.Context, xblsocial.SubscriptionHandler) (func(context.Context) error, error) | ||
| } | ||
|
|
||
| // reactiveFriendSyncApplicable reports whether an account with the given friend | ||
| // sync configuration benefits from a reactive social subscription. Only | ||
| // auto-follow and auto-unfollow act on social changes, so a subscription is | ||
| // pointless without at least one of them. | ||
| func reactiveFriendSyncApplicable(conf *FriendSyncConfig) bool { | ||
| return conf != nil && (conf.AutoFollow || conf.AutoUnfollow) | ||
| } | ||
|
|
||
| // startSocialSubscription subscribes to the account's RTA social feed so friend | ||
| // requests are accepted (and relationship changes reconciled) reactively rather | ||
| // than only on the sync interval. It returns a channel the account's friend | ||
| // syncer should select on to run an immediate pass, or nil when a reactive | ||
| // subscription is not applicable. | ||
| func (b *Broadcaster) startSocialSubscription(client *xsapi.Client, conf *FriendSyncConfig, log *slog.Logger) <-chan struct{} { | ||
| if !hasSocialClient(client) || !reactiveFriendSyncApplicable(conf) { | ||
| return nil | ||
| } | ||
| return b.subscribeSocial(client.Social(), log) | ||
| } | ||
|
|
||
| // subscribeSocial subscribes sub to the social RTA feed and returns a trigger | ||
| // channel that fires on each event. The subscription's lifetime is bound to the | ||
| // broadcaster: a dedicated goroutine unsubscribes once the broadcaster's context | ||
| // is canceled, and [Broadcaster.Close] waits for it via socialWg. This keeps a | ||
| // caller-provided client (which the broadcaster does not close) from | ||
| // accumulating stale handlers across broadcaster restarts. | ||
| // | ||
| // The subscription is best-effort: on a subscribe failure or a lost subscription | ||
| // the periodic syncer remains the backstop, and the RTA connection re-subscribes | ||
| // automatically after a transient drop. | ||
| func (b *Broadcaster) subscribeSocial(sub socialSubscriber, log *slog.Logger) <-chan struct{} { | ||
| // Buffered by one so bursts of events collapse into a single pending pass. | ||
| trigger := make(chan struct{}, 1) | ||
| handler := friendRequestSubscriptionHandler{trigger: trigger, log: log} | ||
| b.socialWg.Add(1) | ||
| go func() { | ||
| defer b.socialWg.Done() | ||
| // Subscribe dials RTA lazily; running it here keeps a slow or failing | ||
| // dial off the start path. | ||
| unsubscribe, err := sub.Subscribe(b.ctx, handler) | ||
| if err != nil { | ||
| log.Warn("subscribe to social rta feed; friend requests will be accepted on the sync interval", "err", err) | ||
| return | ||
| } | ||
| log.Debug("subscribed to social rta feed for reactive friend sync") | ||
|
|
||
| <-b.ctx.Done() | ||
| // b.ctx is done, so use a fresh context to release the subscription. | ||
| // The cleanup removes only this registration, so a shared client's other | ||
| // subscribers keep working. | ||
| ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) | ||
| defer cancel() | ||
| if err := unsubscribe(ctx); err != nil { | ||
| log.Debug("unsubscribe social rta feed", "err", err) | ||
| } | ||
| }() | ||
| return trigger | ||
| } | ||
|
|
||
| // friendRequestSubscriptionHandler adapts go-xsapi's social RTA subscription to | ||
| // the friend syncer: any relationship change or incoming-friend-request event | ||
| // requests an immediate sync pass through trigger. | ||
| type friendRequestSubscriptionHandler struct { | ||
| trigger chan<- struct{} | ||
| log *slog.Logger | ||
| } | ||
|
|
||
| // HandleIncomingFriendRequestCountChange requests a sync pass so newly received | ||
| // friend requests are accepted promptly, mirroring MCXboxBroadcast's reaction to | ||
| // the IncomingFriendRequestCountChanged notification. | ||
| func (h friendRequestSubscriptionHandler) HandleIncomingFriendRequestCountChange(count int) { | ||
| h.log.Debug("incoming friend request count changed", "count", count) | ||
| h.signal() | ||
| } | ||
|
|
||
| // HandleSocialNotification requests a sync pass when the caller's relationships | ||
| // change (a user added, removed, or updated the caller). | ||
| func (h friendRequestSubscriptionHandler) HandleSocialNotification(typ string, xuids []string) { | ||
| h.log.Debug("social notification", "type", typ, "xuids", len(xuids)) | ||
| h.signal() | ||
| } | ||
|
|
||
| // HandleSubscriptionLost logs the loss. The periodic syncer continues to accept | ||
| // requests, and the RTA connection re-subscribes automatically after transient | ||
| // drops, so no action is taken here. | ||
| func (h friendRequestSubscriptionHandler) HandleSubscriptionLost() { | ||
| h.log.Warn("social subscription lost; friend requests will be accepted on the sync interval until it is restored") | ||
| } | ||
|
|
||
| // signal requests a sync pass without blocking. A full buffer means a pass is | ||
| // already pending, so the event is coalesced into it. | ||
| func (h friendRequestSubscriptionHandler) signal() { | ||
| select { | ||
| case h.trigger <- struct{}{}: | ||
| default: | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.