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
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
32cc933
Merge branch 'master' of github.com:GetStream/stream-chat-js into vis…
vishalnarkhede Jan 4, 2021
5f16699
Ensure unique member based channel on client
vishalnarkhede Jan 15, 2021
38bfcb9
Merge branch 'master' into vishal/thread_participants_type
vishalnarkhede Jan 15, 2021
8852374
Adding test
vishalnarkhede Jan 15, 2021
30841cd
Merge branch 'vishal/thread_participants_type' of github.com:GetStrea…
vishalnarkhede Jan 15, 2021
506d6be
Updated the test
vishalnarkhede Jan 15, 2021
40bc02c
Merge branch 'master' into vishal/thread_participants_type
Jan 15, 2021
de2ce01
Cover the edge cases of channel creation
vishalnarkhede Jan 18, 2021
3491ee4
Merge branch 'vishal/thread_participants_type' of github.com:GetStrea…
vishalnarkhede Jan 18, 2021
39d3db1
Adding channelType to temporary cid of unique conversation
vishalnarkhede Jan 18, 2021
877bb57
Fixing members related error
vishalnarkhede Jan 18, 2021
3abf6c9
Update test title
Jan 18, 2021
9065826
Fixing comments
vishalnarkhede Jan 18, 2021
5b6d6b9
Merge branch 'vishal/thread_participants_type' of github.com:GetStrea…
vishalnarkhede Jan 18, 2021
f8e9e72
Fixing comment grammer
vishalnarkhede Jan 18, 2021
763c4ab
Merge branch 'master' into vishal/thread_participants_type
vishalnarkhede Jan 18, 2021
3ff1be9
Merge branch 'master' into vishal/thread_participants_type
vishalnarkhede Jan 18, 2021
fcba1a6
Updated temporary cid for member based channels, to keep it consisten…
vishalnarkhede Jan 19, 2021
ba0d5c3
Merge github.com:GetStream/stream-chat-js into vishal/thread_particip…
vishalnarkhede Jan 19, 2021
21c2664
Merge branch 'vishal/thread_participants_type' of github.com:GetStrea…
vishalnarkhede Jan 19, 2021
f4cb2aa
Fixing code comment
vishalnarkhede Jan 19, 2021
f24efb7
Fixing tempCid cleanup from activeChannels
vishalnarkhede Jan 20, 2021
4ef8f28
Fixing memberStr during channel.query
vishalnarkhede Jan 20, 2021
9c7abc8
Moving test to unit-test
vishalnarkhede Jan 20, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,12 @@ export class Channel<
this.id = state.channel.id;
this.cid = state.channel.cid;
// set the channel as active...
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 !!

}

if (!(this.cid in this.getClient().activeChannels)) {
this.getClient().activeChannels[this.cid] = this;
}
Expand Down
110 changes: 94 additions & 16 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1513,27 +1513,105 @@ export class StreamChat<
UserType
>(this, channelType, undefined, custom);
}

// support channel("messaging", {options})
if (typeof channelIDOrCustom === 'object') {
return new Channel<
AttachmentType,
ChannelType,
CommandType,
EventType,
MessageType,
ReactionType,
UserType
>(this, channelType, undefined, channelIDOrCustom);
return this.getChannelByMembers(channelType, channelIDOrCustom);
}

if (typeof channelIDOrCustom === 'string' && ~channelIDOrCustom.indexOf(':')) {
throw Error(
`Invalid channel id ${channelIDOrCustom}, can't contain the : character`,
);
return this.getChannelById(channelType, channelIDOrCustom, custom);
}

/**
* It's a helper method for `client.channel()` method, used to create unique conversation or
* channel based on member list instead of id.
*
* If the channel already exists in `activeChannels` list, then we simply return it, since that
* means the same channel was already requested or created.
*
* Otherwise we create a new instance of Channel class and return it.
*
* @private
*
* @param {string} channelType The channel type
* @param {object} [custom] Custom data to attach to the channel
*
* @return {channel} The channel object, initialize it using channel.watch()
*/
getChannelByMembers = (channelType: string, custom: ChannelData<ChannelType>) => {
// Check if the channel already exists.
// Only allow 1 channel object per cid
const membersStr = custom.members?.sort().join(',');
const tempCid = `${channelType}:!members-${membersStr}`;

if (!membersStr) {
throw Error('Please specify atleast one member when creating unique conversation');
}

// 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 is set when you create a channel, but haven't queried yet. After query,
// we will replace it with `cid`
for (const key in this.activeChannels) {
const channel = this.activeChannels[key];
if (key === tempCid) {
return channel;
}

if (key.indexOf(`${channelType}:!members-`) === 0) {
const membersStrInExistingChannel = Object.keys(channel.state.members)
.sort()
.join(',');
if (membersStrInExistingChannel === membersStr) {
return channel;
}
}
}

const channel = new Channel<
AttachmentType,
ChannelType,
CommandType,
EventType,
MessageType,
ReactionType,
UserType
>(this, channelType, undefined, custom);

// For the time being set the key as membersStr, since we don't know the cid yet.
// In channel.query, we will replace it with 'cid'.
this.activeChannels[tempCid] = channel;
return channel;
};

/**
* Its a helper method for `client.channel()` method, used to channel given the id of channel.
*
* If the channel already exists in `activeChannels` list, then we simply return it, since that
* means the same channel was already requested or created.
*
* Otherwise we create a new instance of Channel class and return it.
*
* @private
*
* @param {string} channelType The channel type
* @param {string} [channelID] The channel ID
* @param {object} [custom] Custom data to attach to the channel
*
* @return {channel} The channel object, initialize it using channel.watch()
*/
getChannelById = (
channelType: string,
channelID: string,
custom: ChannelData<ChannelType>,
) => {
if (typeof channelID === 'string' && ~channelID.indexOf(':')) {
throw Error(`Invalid channel id ${channelID}, can't contain the : character`);
}

// only allow 1 channel object per cid
const cid = `${channelType}:${channelIDOrCustom}`;
const cid = `${channelType}:${channelID}`;
if (cid in this.activeChannels) {
const channel = this.activeChannels[cid];
if (Object.keys(custom).length > 0) {
Expand All @@ -1550,11 +1628,11 @@ export class StreamChat<
MessageType,
ReactionType,
UserType
>(this, channelType, channelIDOrCustom, custom);
>(this, channelType, channelID, custom);
this.activeChannels[channel.cid] = channel;

return channel;
}
};

/**
* @deprecated Please use upsertUser() function instead.
Expand Down
71 changes: 71 additions & 0 deletions test/integration/channels.js
Original file line number Diff line number Diff line change
Expand Up @@ -2975,3 +2975,74 @@ describe('Channel - isUpToDate', async () => {
).to.be.equal(-1);
});
});

describe('Ensure single channel per cid on client activeChannels state', async () => {
it('channel created using id', async () => {
const userIdVish = 'vishal';
const userIdAmin = 'amin';
await createUsers([userIdVish, userIdAmin]);

const clientVish = await getTestClientForUser(userIdVish);

// Case 1
const channelVishId_case1 = uuidv4();
const channelVish_copy1_case1 = clientVish.channel(
'messaging',
channelVishId_case1,
);
await channelVish_copy1_case1.watch();
const channelVish_copy2_case1 = clientVish.channel(
'messaging',
channelVishId_case1,
);
channelVish_copy2_case1.watch();
expect(channelVish_copy1_case1).to.be.equal(channelVish_copy2_case1);

// Case 2
const channelVishId_case2 = uuidv4();
const channelVish_copy1_case2 = clientVish.channel(
'messaging',
channelVishId_case2,
);
const channelVish_copy2_case2 = clientVish.channel(
'messaging',
channelVishId_case2,
);
await channelVish_copy1_case2.watch();
await channelVish_copy2_case2.watch();

expect(channelVish_copy1_case2).to.be.equal(channelVish_copy2_case2);
});
it('channel created using member list', async () => {
const userIdVish = 'vishal';
const userIdAmin = 'amin';
const userIdJaap = 'jaap';
const userIdTom = 'tom';
await createUsers([userIdVish, userIdAmin, userIdJaap, userIdTom]);

const clientVish = await getTestClientForUser(userIdVish);

// Case 1
const channelVish_copy1_case1 = clientVish.channel('messaging', {
members: [userIdAmin, userIdVish],
});
await channelVish_copy1_case1.watch();
const channelVish_copy2_case1 = clientVish.channel('messaging', {
members: [userIdVish, userIdAmin],
});
await channelVish_copy2_case1.watch();
expect(channelVish_copy1_case1).to.be.equal(channelVish_copy2_case1);

// Case 2
const channelVish_copy1_case2 = clientVish.channel('messaging', {
members: [userIdJaap, userIdTom],
});
const channelVish_copy2_case2 = clientVish.channel('messaging', {
members: [userIdTom, userIdJaap],
});
await channelVish_copy1_case2.watch();
await channelVish_copy2_case2.watch();

expect(channelVish_copy1_case2).to.be.equal(channelVish_copy2_case2);
});
});