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

Fix for member based channel #586

Merged
merged 24 commits into from
Jan 20, 2021
Merged

Conversation

vishalnarkhede
Copy link
Contributor

@vishalnarkhede vishalnarkhede commented Jan 15, 2021

Submit a pull request

CLA

  • I have signed the Stream CLA (required).
  • The code changes follow best practices
  • Code changes are tested (add some information if not applicable)

Description of the pull request

Existing logic:
We maintain a list of active channels on client - client.activeChannel.

So every-time, user requests a channel using client.channel('messaging', 'random_channel_id'), we first check if the cid already exist in client.activeChannels
|--> if yes, then return the channel available on client.activeChannels
|--> if not, then create new instance of Channel class and return that.

This way we ensure that user doesn't end up creating multiple copies of same channel. And this is important, because when client receives ws event, it delegates it to all channels in activeChannel map. So if your channel isn't part of activeChannels, it won't receive any of the events.

Issue

This logic only worked for id based channels. If user requests a member based channel (e.g., client.channel('messaging', { members: ['vishal', 'amin'] })), then we skipped that check, because the logic for generating id from members list is on backend side, and its not a good idea to expose it (checked with @thesyncim ) on frontend. So we couldn't really check if the channel already exists in client.activeChannels

Fix

We are adding this missing check for member based channels.

For member based channel, we don't get cid until channel is watched/queried. So until we get the key from backend, we are going to store it in activeChannels with the key ${channelType}:!members-${members.sort().join(',')}. E.g. messaging:!members-amin,vishal or messaging:!members-amin,jaap,tom. So that we can ensure uniqueness even if channel is not queries/watched yet.

We assert that a particular channel in activeChannels is same as requested channel by user:

  • key === ${channelType}:!members-${members.sort().join(',')}

OR

  • If key starts with ${channelType}:!members- AND
  • channel has same members as the channel which is requested.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 15, 2021

Size Change: +4.03 kB (1%)

Total Size: 214 kB

Filename Size Change
dist/browser.es.js 45.9 kB +944 B (2%)
dist/browser.full-bundle.min.js 29.6 kB +227 B (0%)
dist/browser.js 46.5 kB +954 B (2%)
dist/index.es.js 45.9 kB +945 B (2%)
dist/index.js 46.5 kB +957 B (2%)

compressed-size-action

test/integration/channels.js Outdated Show resolved Hide resolved
Comment on lines 2999 to 3006
const clientVish = await getTestClientForUser(userIdVish);
const channelVish_copy1 = clientVish.channel('messaging', {
members: ['amin', 'vishal'],
});
await channelVish_copy1.watch();
const channelVish_copy2 = clientVish.channel('messaging', {
members: ['vishal', 'amin'],
});
Copy link
Contributor

@mahboubii mahboubii Jan 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kinda an edge case to this fix which itself is an edge case but if you call channel.watch or channel.create after instantiating the second channel you still end up with a duplicate channel in the state. (this can actually happen if different parts of the app instantiate channels at the same time)

const channelVish_copy1 = clientVish.channel('messaging', { members: ['amin', 'vishal'] });
const channelVish_copy2 = clientVish.channel('messaging', { members: ['amin', 'vishal'] });
// channelVish_copy1 !== channelVish_copy2

await channelVish_copy1.watch();
await channelVish_copy2.watch();
// channelVish_copy1 !== channelVish_copy2

Copy link
Contributor Author

@vishalnarkhede vishalnarkhede Jan 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm probably we should early return from watch() function if its already being watched

async watch(options) {
  if (this.initialized) {
    return;
  }

  ... // rest of the logic
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea it's a potential fix but there could be changes in the option param which cause other issues. To cover this we need to make some changes to the new code you implemented to also check for channels that don't have their cid yet but have the same member list

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense!! Have updated the PR.

So when channel doesn't have cid yet, I am storing it in activeChannels as following:

{
  "messaging:amin,vishal": uniqueChannelAminVishal,
  "messaging:k4j2n3k4j2n3kn42k3": channelWithId
}

When cid gets set on channel, I replace messaging:amin,vishal with actual cid generated from backend.

{
  "messaging:!members:kajshdkjsakjsdkjas": uniqueChannelAminVishal,
  "messaging:k4j2n3k4j2n3kn42k3": channelWithId
}

Also updated tests to reflect this case :)

Copy link
Contributor

@ferhatelmas ferhatelmas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

src/client.ts Outdated
* Its a submethod for `client.channel()` method, used to create unique conversation or
* channel based on member list instead of id.
*
* If the channel already exist in `activeChannels` list, then we simply return it, since that
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exists

src/client.ts Outdated
* If the channel already exist in `activeChannels` list, then we simply return it, since that
* means the same channel was already requested or created.
*
* Otherwise we create new instance of Channel class and return it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a new

src/client.ts Outdated
}

/**
* Its a submethod for `client.channel()` method, used to create unique conversation or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's

src/client.ts Outdated
// channel could exist in `activeChannels` list with either one of the following two keys:
// 1. cid - Which gets set on channel only after calling channel.query or channel.watch or channel.create
// 2. Sorted membersStr - E.g., "messaging:amin,vishal" OR "messaging:amin,jaap,tom"
// This gets set when you create a channel, but haven't queried yet. After query,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/gets/is/

src/client.ts Outdated
};

/**
* Its a submethod for `client.channel()` method, used to channel given the id of channel.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's

Copy link
Contributor

@ferhatelmas ferhatelmas Jan 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

submethod doesn't sound intuitive, a helper maybe

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, let's add a @private to these two functions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done :)

src/client.ts Outdated
/**
* Its a submethod for `client.channel()` method, used to channel given the id of channel.
*
* If the channel already exist in `activeChannels` list, then we simply return it, since that
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exists

@vishalnarkhede
Copy link
Contributor Author

@mahboubii added !members- to membersStr !!

src/channel.ts Outdated
const membersStr = Object.keys(this.state.members)?.sort().join(',');

if (`${this.type}:!members-${membersStr}` in this.getClient().activeChannels) {
delete this.getClient().activeChannels[membersStr];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we delete the first instance from the activeChannels?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? Its a map, so there will be only one instance.

But just noticed that we are not deleting the correct key from activeChannels. It should be something like:

delete this.getClient().activeChannels[`${this.type}:!members-${membersStr}`];

Gonna add to test as well :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done !!

@vishalnarkhede
Copy link
Contributor Author

Moved tests to unit-test :)

@vishalnarkhede vishalnarkhede merged commit 1d9daa6 into master Jan 20, 2021
@delete-merged-branch delete-merged-branch bot deleted the vishal/thread_participants_type branch January 20, 2021 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants