Skip to content

Commit

Permalink
feat: add channel messages pagination indicators (#1332)
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinCupela authored Aug 22, 2024
1 parent 47fe71c commit 2ce6059
Show file tree
Hide file tree
Showing 10 changed files with 2,960 additions and 19 deletions.
15 changes: 13 additions & 2 deletions src/channel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ChannelState } from './channel_state';
import { logChatPromiseExecution, normalizeQuerySort } from './utils';
import { logChatPromiseExecution, messageSetPagination, normalizeQuerySort } from './utils';
import { StreamChat } from './client';
import {
APIResponse,
Expand Down Expand Up @@ -58,6 +58,7 @@ import {
AscDesc,
} from './types';
import { Role } from './permissions';
import { DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE } from './constants';

/**
* Channel - The Channel class manages it's own state.
Expand Down Expand Up @@ -1002,7 +1003,7 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
* @return {Promise<QueryChannelAPIResponse<StreamChatGenerics>>} Returns a query response
*/
async query(
options: ChannelQueryOptions<StreamChatGenerics>,
options?: ChannelQueryOptions<StreamChatGenerics>,
messageSetToAddToIfDoesNotExist: MessageSetType = 'current',
) {
// Make sure we wait for the connect promise if there is a pending one
Expand Down Expand Up @@ -1046,6 +1047,16 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene

// add any messages to our channel state
const { messageSet } = this._initializeState(state, messageSetToAddToIfDoesNotExist);
messageSet.pagination = {
...messageSet.pagination,
...messageSetPagination({
parentSet: messageSet,
messagePaginationOptions: options?.messages,
requestedPageSize: options?.messages?.limit ?? DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE,
returnedPage: state.messages,
logger: this.getClient().logger,
}),
};

const areCapabilitiesChanged =
[...(state.channel.own_capabilities || [])].sort().join() !==
Expand Down
30 changes: 23 additions & 7 deletions src/channel_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ExtendableGenerics,
FormatMessageResponse,
MessageResponse,
MessageSet,
MessageSetType,
PendingMessageResponse,
PollResponse,
Expand All @@ -14,6 +15,7 @@ import {
UserResponse,
} from './types';
import { addToMessageList } from './utils';
import { DEFAULT_MESSAGE_SET_PAGINATION } from './constants';

type ChannelReadStatus<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = Record<
string,
Expand Down Expand Up @@ -56,11 +58,7 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
* The state manages these lists and merges them when lists overlap
* The messages array contains the currently active set
*/
messageSets: {
isCurrent: boolean;
isLatest: boolean;
messages: Array<ReturnType<ChannelState<StreamChatGenerics>['formatMessage']>>;
}[] = [];
messageSets: MessageSet[] = [];
constructor(channel: Channel<StreamChatGenerics>) {
this._channel = channel;
this.watcher_count = 0;
Expand Down Expand Up @@ -108,6 +106,10 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
this.messageSets[index].messages = messages;
}

get messagePagination() {
return this.messageSets.find((s) => s.isCurrent)?.pagination || DEFAULT_MESSAGE_SET_PAGINATION;
}

/**
* addMessageSorted - Add a message to the state
*
Expand Down Expand Up @@ -717,14 +719,15 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
}

initMessages() {
this.messageSets = [{ messages: [], isLatest: true, isCurrent: true }];
this.messageSets = [{ messages: [], isLatest: true, isCurrent: true, pagination: DEFAULT_MESSAGE_SET_PAGINATION }];
}

/**
* loadMessageIntoState - Loads a given message (and messages around it) into the state
*
* @param {string} messageId The id of the message, or 'latest' to indicate switching to the latest messages
* @param {string} parentMessageId The id of the parent message, if we want load a thread reply
* @param {number} limit The page size if the message has to be queried from the server
*/
async loadMessageIntoState(messageId: string | 'latest', parentMessageId?: string, limit = 25) {
let messageSetIndex: number;
Expand Down Expand Up @@ -820,7 +823,12 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
targetMessageSetIndex = overlappingMessageSetIndices[0];
// No new message set is created if newMessages only contains thread replies
} else if (newMessages.some((m) => !m.parent_id)) {
this.messageSets.push({ messages: [], isCurrent: false, isLatest: false });
this.messageSets.push({
messages: [],
isCurrent: false,
isLatest: false,
pagination: DEFAULT_MESSAGE_SET_PAGINATION,
});
targetMessageSetIndex = this.messageSets.length - 1;
}
break;
Expand All @@ -846,6 +854,14 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
sources.forEach((messageSet) => {
target.isLatest = target.isLatest || messageSet.isLatest;
target.isCurrent = target.isCurrent || messageSet.isCurrent;
target.pagination.hasPrev =
messageSet.messages[0].created_at < target.messages[0].created_at
? messageSet.pagination.hasPrev
: target.pagination.hasPrev;
target.pagination.hasNext =
target.messages.slice(-1)[0].created_at < messageSet.messages.slice(-1)[0].created_at
? messageSet.pagination.hasNext
: target.pagination.hasNext;
messagesToAdd = [...messagesToAdd, ...messageSet.messages];
});
sources.forEach((s) => this.messageSets.splice(this.messageSets.indexOf(s), 1));
Expand Down
30 changes: 22 additions & 8 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
isFunction,
isOnline,
isOwnUserBaseProperty,
messageSetPagination,
normalizeQuerySort,
randomId,
retryInterval,
Expand Down Expand Up @@ -207,6 +208,7 @@ import {
import { InsightMetrics, postInsights } from './insights';
import { Thread } from './thread';
import { Moderation } from './moderation';
import { DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE } from './constants';

function isString(x: unknown): x is string {
return typeof x === 'string' || x instanceof String;
Expand Down Expand Up @@ -1601,7 +1603,7 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
},
});

return this.hydrateActiveChannels(data.channels, stateOptions);
return this.hydrateActiveChannels(data.channels, stateOptions, options);
}

/**
Expand Down Expand Up @@ -1638,26 +1640,38 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
hydrateActiveChannels(
channelsFromApi: ChannelAPIResponse<StreamChatGenerics>[] = [],
stateOptions: ChannelStateOptions = {},
queryChannelsOptions?: ChannelOptions,
) {
const { skipInitialization, offlineMode = false } = stateOptions;

for (const channelState of channelsFromApi) {
this._addChannelConfig(channelState.channel);
}

const channels: Channel<StreamChatGenerics>[] = [];

for (const channelState of channelsFromApi) {
this._addChannelConfig(channelState.channel);
const c = this.channel(channelState.channel.type, channelState.channel.id);
c.data = channelState.channel;
c.offlineMode = offlineMode;
c.initialized = !offlineMode;

let updatedMessagesSet;
if (skipInitialization === undefined) {
c._initializeState(channelState, 'latest');
const { messageSet } = c._initializeState(channelState, 'latest');
updatedMessagesSet = messageSet;
} else if (!skipInitialization.includes(channelState.channel.id)) {
c.state.clearMessages();
c._initializeState(channelState, 'latest');
const { messageSet } = c._initializeState(channelState, 'latest');
updatedMessagesSet = messageSet;
}

if (updatedMessagesSet) {
updatedMessagesSet.pagination = {
...updatedMessagesSet.pagination,
...messageSetPagination({
parentSet: updatedMessagesSet,
requestedPageSize: queryChannelsOptions?.message_limit || DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE,
returnedPage: channelState.messages,
logger: this.logger,
}),
};
}

channels.push(c);
Expand Down
4 changes: 4 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE = 25;
export const DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE = 100;

export const DEFAULT_MESSAGE_SET_PAGINATION = { hasNext: true, hasPrev: true };
8 changes: 7 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ export type PaginationOptions = {
id_lt?: string;
id_lte?: string;
limit?: number;
offset?: number;
offset?: number; // should be avoided with channel.query()
};

export type MessagePaginationOptions = PaginationOptions & {
Expand Down Expand Up @@ -2900,6 +2900,12 @@ export type ImportTask = {
};

export type MessageSetType = 'latest' | 'current' | 'new';
export type MessageSet<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = {
isCurrent: boolean;
isLatest: boolean;
messages: FormatMessageResponse<StreamChatGenerics>[];
pagination: { hasNext: boolean; hasPrev: boolean };
};

export type PushProviderUpsertResponse = {
push_provider: PushProvider;
Expand Down
Loading

0 comments on commit 2ce6059

Please sign in to comment.