Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
199 changes: 100 additions & 99 deletions src/channel_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@ import type { ValueOrPatch } from './store';
import { isPatch, StateStore } from './store';
import type { Channel } from './channel';
import {
extractSortValue,
findLastPinnedChannelIndex,
getAndWatchChannel,
isChannelArchived,
isChannelPinned,
promoteChannel,
shouldConsiderArchivedChannels,
shouldConsiderPinnedChannels,
sleep,
sortChannels,
uniqBy,
} from './utils';
import { generateUUIDv4 } from './utils';
Expand Down Expand Up @@ -204,6 +202,20 @@ export class ChannelManager extends WithSubscriptions {
notificationRemovedFromChannelHandler: this.notificationRemovedFromChannelHandler,
}),
);

this.state.addPreprocessor((currentState, previousState) => {
if (
currentState.channels === previousState?.channels &&
currentState.pagination.sort === previousState.pagination.sort
) {
return;
}

currentState.channels = sortChannels(
currentState.channels,
currentState.pagination.sort,
);
});
}

public setChannels = (valueOrFactory: ChannelSetterParameterType) => {
Expand Down Expand Up @@ -438,76 +450,78 @@ export class ChannelManager extends WithSubscriptions {
};

private notificationAddedToChannelHandler = async (event: Event) => {
const { id, type, members } = event?.channel ?? {};
const channelId = event.channel_id ?? event.channel?.id;
const channelType = event.channel_type ?? event.channel?.type;
const members = event.channel?.members;

if (
!type ||
!channelType ||
!channelId ||
!this.options.allowNotLoadedChannelPromotionForEvent?.[
'notification.added_to_channel'
]
) {
return;
}

const channel = await getAndWatchChannel({
const targetChannel = await getAndWatchChannel({
client: this.client,
id,
members: members?.reduce<string[]>((acc, { user, user_id }) => {
const userId = user_id || user?.id;
id: channelId,
members: members?.reduce<string[]>((memberIds, { user, user_id }) => {
const userId = user_id ?? user?.id;
if (userId) {
acc.push(userId);
memberIds.push(userId);
}
return acc;
return memberIds;
}, []),
type,
type: channelType,
});

const { pagination, channels } = this.state.getLatestValue();
if (!channels) {
return;
}
this.setChannels((currentChannels) => {
const targetChannelExistsWithinList = currentChannels.indexOf(targetChannel) >= 0;

const { sort } = pagination ?? {};
if (targetChannelExistsWithinList) {
return currentChannels;
}

this.setChannels(
promoteChannel({
channels,
channelToMove: channel,
sort,
}),
);
return [...currentChannels, targetChannel];
});
};

private channelDeletedHandler = (event: Event) => {
const { channels } = this.state.getLatestValue();
if (!channels) {

const channelId = event.channel_id ?? event.channel?.id;
const channelType = event.channel_type ?? event.channel?.type;

if (!channelType || !channelId) {
return;
}

const newChannels = [...channels];
const channelIndex = newChannels.findIndex(
(channel) => channel.cid === (event.cid || event.channel?.cid),
const targetChannelIndex = channels.indexOf(
this.client.channel(channelType, channelId),
);

if (channelIndex < 0) {
return;
}
if (targetChannelIndex < 0) return;

newChannels.splice(channelIndex, 1);
this.setChannels(newChannels);
this.setChannels((currentChannels) => {
const newChannels = [...currentChannels];

newChannels.splice(targetChannelIndex, 1);

return newChannels;
});
};

private channelHiddenHandler = this.channelDeletedHandler;

private newMessageHandler = (event: Event) => {
const { pagination, channels } = this.state.getLatestValue();
if (!channels) {
return;
}

const { filters, sort } = pagination ?? {};

const channelType = event.channel_type;
const channelId = event.channel_id;
const channelId = event.channel_id ?? event.channel?.id;
const channelType = event.channel_type ?? event.channel?.type;

if (!channelType || !channelId) {
return;
Expand Down Expand Up @@ -539,112 +553,114 @@ export class ChannelManager extends WithSubscriptions {
return;
}

this.setChannels(
promoteChannel({
channels,
channelToMove: targetChannel,
channelToMoveIndexWithinChannels: targetChannelIndex,
sort,
}),
);
this.setChannels((currentChannels) => {
if (targetChannelExistsWithinList) {
return [...currentChannels];
}

return [...currentChannels, targetChannel];
});
};

private notificationNewMessageHandler = async (event: Event) => {
const { id, type } = event?.channel ?? {};
const channelId = event.channel_id ?? event.channel?.id;
const channelType = event.channel_type ?? event.channel?.type;

if (!id || !type) {
if (!channelType || !channelId) {
return;
}

const channel = await getAndWatchChannel({
const targetChannel = await getAndWatchChannel({
client: this.client,
id,
type,
id: channelId,
type: channelType,
});

const { channels, pagination } = this.state.getLatestValue();
const { filters, sort } = pagination ?? {};
const { pagination } = this.state.getLatestValue();
const { filters } = pagination ?? {};

const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
const isTargetChannelArchived = isChannelArchived(channel);
const isTargetChannelArchived = isChannelArchived(targetChannel);

if (
!channels ||
(considerArchivedChannels && isTargetChannelArchived && !filters.archived) ||
(considerArchivedChannels && !isTargetChannelArchived && filters.archived) ||
!this.options.allowNotLoadedChannelPromotionForEvent?.['notification.message_new']
) {
return;
}

this.setChannels(
promoteChannel({
channels,
channelToMove: channel,
sort,
}),
);
this.setChannels((currentChannels) => {
const targetChannelExistsWithinList = currentChannels.indexOf(targetChannel) >= 0;

if (targetChannelExistsWithinList) {
return currentChannels;
}

return [...currentChannels, targetChannel];
});
};

private channelVisibleHandler = async (event: Event) => {
const { channel_type: channelType, channel_id: channelId } = event;
const channelId = event.channel_id ?? event.channel?.id;
const channelType = event.channel_type ?? event.channel?.type;

if (!channelType || !channelId) {
return;
}

const channel = await getAndWatchChannel({
const targetChannel = await getAndWatchChannel({
client: this.client,
id: event.channel_id,
type: event.channel_type,
});

const { channels, pagination } = this.state.getLatestValue();
const { sort, filters } = pagination ?? {};
const { pagination } = this.state.getLatestValue();
const { filters } = pagination ?? {};

const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
const isTargetChannelArchived = isChannelArchived(channel);
const isTargetChannelArchived = isChannelArchived(targetChannel);

if (
!channels ||
(considerArchivedChannels && isTargetChannelArchived && !filters.archived) ||
(considerArchivedChannels && !isTargetChannelArchived && filters.archived) ||
!this.options.allowNotLoadedChannelPromotionForEvent?.['channel.visible']
) {
return;
}

this.setChannels(
promoteChannel({
channels,
channelToMove: channel,
sort,
}),
);
this.setChannels((currentChannels) => {
const targetChannelExistsWithinList = currentChannels.indexOf(targetChannel) >= 0;

if (targetChannelExistsWithinList) {
return currentChannels;
}

return [...currentChannels, targetChannel];
});
};

private notificationRemovedFromChannelHandler = this.channelDeletedHandler;

private memberUpdatedHandler = (event: Event) => {
const { pagination, channels } = this.state.getLatestValue();
const { filters, sort } = pagination;
const channelId = event.channel_id ?? event.channel?.id;
const channelType = event.channel_type ?? event.channel?.type;

if (
!event.member?.user ||
event.member.user.id !== this.client.userID ||
!event.channel_type ||
!event.channel_id
!channelType ||
!channelId
) {
return;
}
const channelType = event.channel_type;
const channelId = event.channel_id;

const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
const pinnedAtSort = extractSortValue({ atIndex: 0, sort, targetKey: 'pinned_at' });

if (
!channels ||
(!considerPinnedChannels && !considerArchivedChannels) ||
this.options.lockChannelOrder
) {
Expand All @@ -656,7 +672,6 @@ export class ChannelManager extends WithSubscriptions {
const targetChannelIndex = channels.indexOf(targetChannel);
const targetChannelExistsWithinList = targetChannelIndex >= 0;

const isTargetChannelPinned = isChannelPinned(targetChannel);
const isTargetChannelArchived = isChannelArchived(targetChannel);

const newChannels = [...channels];
Expand All @@ -672,25 +687,11 @@ export class ChannelManager extends WithSubscriptions {
// When archived filter false, and channel is archived
(considerArchivedChannels && isTargetChannelArchived && !filters?.archived)
) {
this.setChannels(newChannels);
return;
}

// handle pinning
let lastPinnedChannelIndex: number | null = null;

if (pinnedAtSort === 1 || (pinnedAtSort === -1 && !isTargetChannelPinned)) {
lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels: newChannels });
}
const newTargetChannelIndex =
typeof lastPinnedChannelIndex === 'number' ? lastPinnedChannelIndex + 1 : 0;

// skip state update if the position of the channel does not change
if (channels[newTargetChannelIndex] === targetChannel) {
return;
// do nothing
} else {
newChannels.push(targetChannel);
}

newChannels.splice(newTargetChannelIndex, 0, targetChannel);
this.setChannels(newChannels);
};

Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ export {
logChatPromiseExecution,
localMessageToNewMessagePayload,
formatMessage,
promoteChannel,
sortChannels,
} from './utils';
export { FixedSizeQueueCache } from './utils/FixedSizeQueueCache';
12 changes: 0 additions & 12 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { EVENT_MAP } from './events';
import type { Channel } from './channel';
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
import type { StableWSConnection } from './connection';
import type { Role } from './permissions';
Expand Down Expand Up @@ -3882,17 +3881,6 @@ export type VelocityFilterConfig = {
async?: boolean;
};

export type PromoteChannelParams = {
channels: Array<Channel>;
channelToMove: Channel;
sort: ChannelSort;
/**
* If the index of the channel within `channels` list which is being moved upwards
* (`channelToMove`) is known, you can supply it to skip extra calculation.
*/
channelToMoveIndexWithinChannels?: number;
};

/**
* An identifier containing information about the downstream SDK using stream-chat. It
* is used to resolve the user agent.
Expand Down
Loading
Loading