Skip to content
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

[Payment Due Oct 1][$250] [#fast-apis] Subscribe guides to a new presence pusher channel #47888

Closed
MonilBhavsar opened this issue Aug 22, 2024 · 87 comments
Assignees
Labels
Awaiting Payment Auto-added when associated PR is deployed to production Daily KSv2 External Added to denote the issue can be worked on by a contributor Improvement Item broken or needs improvement. NewFeature Something to build that is a new item.

Comments

@MonilBhavsar
Copy link
Contributor

MonilBhavsar commented Aug 22, 2024

Context:

Guides are agents who help new customers with onboarding.

We use Pusher to send real time updates.
We have a private user channel here

App/src/CONST.ts

Line 1154 in f7d8e48

PRIVATE_USER_CHANNEL_PREFIX: 'private-encrypted-user-accountID-',

private-encrypted-user-accountID-<> that is used to receive various events in real time like onyx updates, user typing and more.

Problem

Pusher private channel is dynamic, or unique for each user as it contains an accountID.
For example: A channel name could look like private-encrypted-user-accountID-5-b8645604fe014e82b765ca84b7df1f2e

At the server side, if we want to know how many guides are online at the current time, then we need to make an API call to each pusher channel and see if it is occupied or not.
If we have 20 guides, then we make 20 API calls to check guide's presence, which is not efficient.

Solution

Make guides subscribe to a presence channel - activeGuides
https://pusher.com/docs/channels/using_channels/presence-channels/

With this, a server only will have to make one API call to find all active guides or users subscribed to this channel.

If a user is a guide - i.e. have a team.expensify.com domain and belongs to a specific policyID, then we additionally subscribe guide to the the guides presence channel.

Like the private user channel, a subscription to the channel should indicate guide's presence in the app - They should be subscribed if they login and are active, and should be unsubscribed if they sign out.

We probably have most of the code and logic for the private user channel. We need to similarly subscribe guides to the new channel.

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~01fe75f988a6386f2e
  • Upwork Job ID: 1826754279960805798
  • Last Price Increase: 2024-08-29
  • Automatic offers:
    • situchan | Reviewer | 103739965
Issue OwnerCurrent Issue Owner: @trjExpensify
@MonilBhavsar MonilBhavsar self-assigned this Aug 22, 2024
@MonilBhavsar MonilBhavsar added the External Added to denote the issue can be worked on by a contributor label Aug 22, 2024
Copy link

melvin-bot bot commented Aug 22, 2024

Job added to Upwork: https://www.upwork.com/jobs/~01fe75f988a6386f2e

@melvin-bot melvin-bot bot changed the title [#fast-apis] Subscribe guides to a new presence pusher channel [$250] [#fast-apis] Subscribe guides to a new presence pusher channel Aug 22, 2024
@melvin-bot melvin-bot bot added the Help Wanted Apply this label when an issue is open to proposals by contributors label Aug 22, 2024
Copy link

melvin-bot bot commented Aug 22, 2024

Triggered auto assignment to Contributor-plus team member for initial proposal review - @situchan (External)

@melvin-bot melvin-bot bot added the Daily KSv2 label Aug 22, 2024
@MonilBhavsar MonilBhavsar added the Improvement Item broken or needs improvement. label Aug 22, 2024
@Krishna2323
Copy link
Contributor

Proposal

Please re-state the problem that we are trying to solve in this issue.

[#fast-apis] Subscribe guides to a new presence pusher channel

What is the root cause of that problem?

Improvement.

What changes do you think we should make in order to solve the problem?

  • We already have subscribeToPrivateUserChannelEvent function which subscribes to to private user channel events.
    /**
    * Abstraction around subscribing to private user channel events. Handles all logs and errors automatically.
    */
    function subscribeToPrivateUserChannelEvent(eventName: string, accountID: string, onEvent: (pushJSON: OnyxUpdatesFromServer) => void) {
    const pusherChannelName = `${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}${accountID}${CONFIG.PUSHER.SUFFIX}` as const;
    function logPusherEvent(pushJSON: OnyxUpdatesFromServer) {
    Log.info(`[Report] Handled ${eventName} event sent by Pusher`, false, pushJSON);
    }
    function onPusherResubscribeToPrivateUserChannel() {
    NetworkConnection.triggerReconnectionCallbacks('Pusher re-subscribed to private user channel');
    }
    function onEventPush(pushJSON: OnyxUpdatesFromServer) {
    logPusherEvent(pushJSON);
    onEvent(pushJSON);
    }
    function onSubscriptionFailed(error: Error) {
    Log.hmmm('Failed to subscribe to Pusher channel', {error, pusherChannelName, eventName});
    }
    Pusher.subscribe(pusherChannelName, eventName, onEventPush, onPusherResubscribeToPrivateUserChannel).catch(onSubscriptionFailed);
    }
  • We can created a similar function to subscribeToPrivateUserChannelEvent which will subscribe to the new presence pusher channel.
  • When calling User.subscribeToUserEvents(); we can check if the user is a guide or not and then after we can subscribe to the new presence pusher channel using the newly created function.
    Onyx.connect({
    key: ONYXKEYS.SESSION,
    callback: (value) => {
    // When signed out, val hasn't accountID
    if (!(value && 'accountID' in value)) {
    timezone = null;
    return;
    }
    currentAccountID = value.accountID ?? -1;
    if (Navigation.isActiveRoute(ROUTES.SIGN_IN_MODAL)) {
    // This means sign in in RHP was successful, so we can subscribe to user events
    User.subscribeToUserEvents();
    }
    },
    });
  • We should also create and call unsubscribe when user sign's out, for that we subscribe to ONYX as in the above code snipped or can unsubscribe in the above code.

What alternative solutions did you explore? (Optional)


Result

@daledah
Copy link
Contributor

daledah commented Aug 23, 2024

Edited by proposal-police: This proposal was edited at 2024-08-23 01:43:52 UTC.

Proposal

Please re-state the problem that we are trying to solve in this issue.

  • New feature: Subscribe guides to a new presence pusher channel

What is the root cause of that problem?

  • It is new feature, so we can skip this part.

What changes do you think we should make in order to solve the problem?

  1. Create a subscribe function, same as what we did in subscribeToReportTypingEvents, subscribeToReportLeavingEvents or subscribeToPrivateUserChannelEvent:
function subscribeToActiveGuides() {
    Pusher.subscribe(`activeGuides`).catch((error: ReportError) => {
        Log.hmmm('[Report] Failed to initially subscribe to Pusher channel');
    });
}

function unsubscribeToActiveGuides() {
    Pusher.unsubscribe(`activeGuides`);
}

the eventName can be provided later. And in this case, we can skip the eventCallback because BE just needs to know if the guide is online or not.

  1. Create a subscribe component.
  • This component is named ActiveGuidesEventListener, its responsibility is to subscribe to a active guides channel if the current user is a guide and belongs to a specific policyID. Same as what we did in component UserTypingEventListener.
function ActiveGuidesEventListener({session, allPoliciesEmployeeList}) {
    const flatternPoliciesEmployee = flatten(Object.values(allPoliciesEmployeeList));
    const didSubscribeToActiveGuides = useRef(false);

    useEffect(
        () => () => {
            const sessionEmail = session.email;
            const emailDomain = Str.extractEmailDomain(sessionEmail ?? '');
            if (didSubscribeToActiveGuides.current) {
                return;
            }
            if (emailDomain !== CONST.EMAIL.GUIDES_DOMAIN) {
                return;
            }
            if (some(flatternPoliciesEmployee, (user) => user.email === sessionEmail)) {
                didSubscribeToActiveGuides.current = true;
                subscribeToActiveGuides();
            }
        },

        [session, flatternPoliciesEmployee],
    );
    return null;
}

export default withOnyx({
    session: {
        key: ONYXKEYS.SESSION,
    },
    allPoliciesEmployeeList: {
        key: ONYXKEYS.COLLECTION.POLICY,
        selector: (policy) => policy.employeeList,
    },
})(ActiveGuidesEventListener);
  1. Use the above component in AuthScreens:
            </View>
           {didPusherInit && <ActiveGuidesEventListener />}

with didPusherInit is state:

    const [didPusherInit, setDidPusherInit] = useState(false);

and we set it to true in here.

  1. We don't need to cleanup function because this line:
    Session.cleanupSession();

    already did that.

What alternative solutions did you explore? (Optional)

  • Based on comment, we can modify the ActiveGuidesEventListener in main solution to:
function ActiveGuidesEventListener({user}) {
    const didSubscribeToActiveGuides = useRef(false);
    useEffect(
        () => () => {
            if (didSubscribeToActiveGuides.current) {
                return;
            }
            if (user.isGuide) {
                didSubscribeToActiveGuides.current = true;
                subscribeToActiveGuides();
            }
        },

        [user],
    );
    return null;
}

export default withOnyx({
    user: {
        key: ONYXKEYS.USER,
    },
})(ActiveGuidesEventListener);
  • We still need to keep using didPusherInit && <ActiveGuidesEventListener /> as in main solution to make sure the Pusher.subscribe is called after the Pusher is init.

@daledah

This comment was marked as duplicate.

@daledah
Copy link
Contributor

daledah commented Aug 23, 2024

@MonilBhavsar In the issue's description you said:

have a team.expensify.com domain and belong to a specific policyID

if user has team.expensify.com domain but does not belongs to specific workspace, should we subscribe to the presence pusher channel?

@daledah
Copy link
Contributor

daledah commented Aug 23, 2024

Proposal updated

@MonilBhavsar
Copy link
Contributor Author

if user has team.expensify.com domain but does not belongs to specific workspace, should we subscribe to the presence pusher channel?

No, both conditions are required

@MonilBhavsar
Copy link
Contributor Author

Thanks for the proposals! 🚀
@situchan could you please take a look.

@mariapeever
Copy link

Proposal

Please re-state the problem that we are trying to solve in this issue.

[#fast-apis] Subscribe guides to a new presence pusher channel #47888

What is the root cause of that problem?

Guide users are subscribed to a private encrypted user channel only and not subscribed to a presence channel ('presence-activeGuides').

What changes do you think we should make in order to solve the problem?

If a user is a guide, i.e. their email is an expensify team email, add a Pusher subscription to a presence channel request (PusherUtils.subscribeToPresenceChannelEvent) to the subscribeToUserEvents function in User (called in AuthScreens after Pusher init).

Add a subscribeToPresenceChannelEvent function to Pusher Utils with a Pusher subscribe request. All guides are subscribed to the same presence channel. Subscription to a presence channel adds a members property to the pusher socket, which includes a members count.

PusherConnectionManager.init();
Pusher.init({
appKey: CONFIG.PUSHER.APP_KEY,
cluster: CONFIG.PUSHER.CLUSTER,
authEndpoint: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/AuthenticatePusher?`,
}).then(() => {
User.subscribeToUserEvents();
});

Add a PusherUtils.subscribeToPresenceChannelEvent request just after PusherUtils.subscribeToPrivateUserChannelEvent.

function subscribeToUserEvents() {
// If we don't have the user's accountID yet (because the app isn't fully setup yet) we can't subscribe so return early
if (currentUserAccountID === -1) {
return;
}
// Handles the mega multipleEvents from Pusher which contains an array of single events.
// Each single event is passed to PusherUtils in order to trigger the callbacks for that event
PusherUtils.subscribeToPrivateUserChannelEvent(Pusher.TYPE.MULTIPLE_EVENTS, currentUserAccountID.toString(), (pushJSON) => {
// If this is not the main client, we shouldn't process any data received from pusher.
if (!ActiveClientManager.isClientTheLeader()) {
Log.info('[Pusher] Received updates, but ignoring it since this is not the active client');
return;
}
// The data for the update is an object, containing updateIDs from the server and an array of onyx updates (this array is the same format as the original format above)

Add a subscribeToPresenceChannelEvent function just after subscribeToPrivateUserChannelEvent.
function subscribeToPrivateUserChannelEvent(eventName: string, accountID: string, onEvent: (pushJSON: OnyxUpdatesFromServer) => void) {
const pusherChannelName = `${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}${accountID}${CONFIG.PUSHER.SUFFIX}` as const;

Include a Pusher.subscribe request in the subscribeToPresenceChannelEvent function with channel name 'presence-activeGuides'.
Pusher.subscribe(pusherChannelName, eventName, onEventPush, onPusherResubscribeToPrivateUserChannel).catch(onSubscriptionFailed);

What alternative solutions did you explore? (Optional)

N/A

Copy link

melvin-bot bot commented Aug 26, 2024

@MonilBhavsar, @situchan Whoops! This issue is 2 days overdue. Let's get this updated quick!

@melvin-bot melvin-bot bot added the Overdue label Aug 26, 2024
@situchan
Copy link
Contributor

Thanks for the proposals.

I think @Krishna2323's solution is the most simple, though the logic to check if user is guide or not is missing. And unsubscribe logic is also not clear.

@daledah introducing new component is fine but I doubt subscribeToActiveGuides() is called before pusher init is complete.

PusherConnectionManager.init();
Pusher.init({
appKey: CONFIG.PUSHER.APP_KEY,
cluster: CONFIG.PUSHER.CLUSTER,
authEndpoint: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/AuthenticatePusher?`,
}).then(() => {
User.subscribeToUserEvents();
});

@mariapeever I don't see any meaningful difference from @Krishna2323's proposal.

@melvin-bot melvin-bot bot removed the Overdue label Aug 26, 2024
@daledah
Copy link
Contributor

daledah commented Aug 26, 2024

@situchan

  • I updated the proposal to address your feedback:

I doubt subscribeToActiveGuides() is called before pusher init is complete

  • Also, pls note the condition to subscribe to a new presence pusher:

have a team.expensify.com domain and belongs to a specific policyID

@situchan
Copy link
Contributor

@daledah thanks. What is the benefit of introducing new ActiveGuidesEventListener? Can't we just subscribe after subscribeToUserEvents()?

@daledah
Copy link
Contributor

daledah commented Aug 26, 2024

Can't we just subscribe after subscribeToUserEvents()

As mentioned in the OP's description, we just want to subscribe to a new presence pusher channel if they have a team.expensify.com domain and belong to a specific policyID. If we subscribe after subscribeToUserEvents(), it will subscribe to presence channel even if user is not in any policy.

@situchan
Copy link
Contributor

it will subscribe to presence channel even if user is not in any policy.

Ofc we'll check if user belongs to specific policy

@daledah
Copy link
Contributor

daledah commented Aug 26, 2024

Ofc we'll check if user belongs to specific policy

Yeah. If we subscribe after subscribeToUserEvents(), we cannot check if user belongs to specific policy, right?

@Krishna2323
Copy link
Contributor

@situchan, I think we are already disconnecting from the socket when the AuthScreen is unmounted, If we want we can explicitly from the channel. For checking if the user is a guide or not, we can use the email and compare it with CONST.EMAIL.GUIDES_DOMAIN and we will also make sure that the user belongs to a policy by getting the policy using PolicyUtils.getPolicy(policyID) and finding if the member is in the participants list or not.

Session.cleanupSession();

function cleanupSession() {
Pusher.disconnect();
Timers.clearAll();
Welcome.resetAllChecks();

@situchan
Copy link
Contributor

Ofc we'll check if user belongs to specific policy

Yeah. If we subscribe after subscribeToUserEvents(), we cannot check if user belongs to specific policy, right?

Makes sense. We may need to connect ONYXKEYS.COLLECTION.POLICY in AuthScreens

@Krishna2323 what about this concern?

@Krishna2323
Copy link
Contributor

@Krishna2323 what about #47888 (comment)?

I'm not sure why we can't check if user belongs to specific policy, can you please help me understand? Can't we get the policy using useOnyx or usePolicy hook?

@situchan
Copy link
Contributor

@Krishna2323 pusher init is called on AuthScreens component mount

@melvin-bot melvin-bot bot added Reviewing Has a PR in review Weekly KSv2 and removed Daily KSv2 labels Sep 10, 2024
@daledah
Copy link
Contributor

daledah commented Sep 10, 2024

@MonilBhavsar PR is up.

@MonilBhavsar
Copy link
Contributor Author

Okay, this PR was deployed to production. I'll add a label to assign bug-zero team member to issue payment next week

@MonilBhavsar MonilBhavsar added the NewFeature Something to build that is a new item. label Sep 24, 2024
Copy link

melvin-bot bot commented Sep 24, 2024

Triggered auto assignment to @trjExpensify (NewFeature), see https://stackoverflowteams.com/c/expensify/questions/14418#:~:text=BugZero%20process%20steps%20for%20feature%20requests for more details. Please add this Feature request to a GH project, as outlined in the SO.

Copy link

melvin-bot bot commented Sep 24, 2024

⚠️ It looks like this issue is labelled as a New Feature but not tied to any GitHub Project. Keep in mind that all new features should be tied to GitHub Projects in order to properly track external CAP software time ⚠️

Copy link

melvin-bot bot commented Sep 24, 2024

Triggered auto assignment to Design team member for new feature review - @dannymcclain (NewFeature)

@MonilBhavsar
Copy link
Contributor Author

@dannymcclain sorry for the ping. This was just to issue the payment :D

@MonilBhavsar MonilBhavsar changed the title [$250] [#fast-apis] Subscribe guides to a new presence pusher channel [Payment Due Oct 1][$250] [#fast-apis] Subscribe guides to a new presence pusher channel Sep 24, 2024
@trjExpensify trjExpensify added Awaiting Payment Auto-added when associated PR is deployed to production and removed Reviewing Has a PR in review labels Sep 25, 2024
@trjExpensify
Copy link
Contributor

Wonderful!

@melvin-bot melvin-bot bot added Daily KSv2 and removed Weekly KSv2 labels Oct 1, 2024
Copy link

melvin-bot bot commented Oct 4, 2024

@trjExpensify, @MonilBhavsar, @situchan, @daledah Whoops! This issue is 2 days overdue. Let's get this updated quick!

@melvin-bot melvin-bot bot added the Overdue label Oct 4, 2024
@trjExpensify
Copy link
Contributor

Payment summary as follows:

@situchan I've paid you. @daledah can you send me your upwork profile, please?

@trjExpensify
Copy link
Contributor

(Great timing, Melv).

@daledah
Copy link
Contributor

daledah commented Oct 5, 2024

@situchan
Copy link
Contributor

situchan commented Oct 7, 2024

@trjExpensify looks like you forgot to release payment there's bug in upwork. Contract ended but I don't see any payment 😕

@trjExpensify
Copy link
Contributor

@situchan paid a bonus for the amount that should have been paid due to the Upwork bug.
@daledah sent you an offer.

@melvin-bot melvin-bot bot removed the Overdue label Oct 7, 2024
@daledah
Copy link
Contributor

daledah commented Oct 8, 2024

@trjExpensify Thank you I accepted the offer

@trjExpensify
Copy link
Contributor

@daledah paid, closing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting Payment Auto-added when associated PR is deployed to production Daily KSv2 External Added to denote the issue can be worked on by a contributor Improvement Item broken or needs improvement. NewFeature Something to build that is a new item.
Projects
None yet
Development

No branches or pull requests

7 participants