Skip to content

refactor: general code cleanup (Threads/Polls/MessageComposer) #1522

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

Merged
merged 6 commits into from
May 22, 2025
Merged
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
15 changes: 6 additions & 9 deletions src/channel_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
shouldConsiderPinnedChannels,
uniqBy,
} from './utils';
import { WithSubscriptions } from './utils/WithSubscriptions';

export type ChannelManagerPagination = {
filters: ChannelFilters;
Expand Down Expand Up @@ -142,10 +143,9 @@ export const DEFAULT_CHANNEL_MANAGER_PAGINATION_OPTIONS = {
*
* @internal
*/
export class ChannelManager {
export class ChannelManager extends WithSubscriptions {
public readonly state: StateStore<ChannelManagerState>;
private client: StreamChat;
private unsubscribeFunctions: Set<() => void> = new Set();
private eventHandlers: Map<string, EventHandlerType> = new Map();
private eventHandlerOverrides: Map<string, EventHandlerOverrideType> = new Map();
private options: ChannelManagerOptions = {};
Expand All @@ -160,6 +160,8 @@ export class ChannelManager {
eventHandlerOverrides?: ChannelManagerEventHandlerOverrides;
options?: ChannelManagerOptions;
}) {
super();

this.client = client;
this.state = new StateStore<ChannelManagerState>({
channels: [],
Expand Down Expand Up @@ -606,20 +608,15 @@ export class ChannelManager {
};

public registerSubscriptions = () => {
if (this.unsubscribeFunctions.size) {
if (this.hasSubscriptions) {
// Already listening for events and changes
return;
}

for (const eventType of Object.keys(channelManagerEventToHandlerMapping)) {
this.unsubscribeFunctions.add(
this.addUnsubscribeFunction(
this.client.on(eventType, this.subscriptionOrOverride).unsubscribe,
);
}
};

public unregisterSubscriptions = () => {
this.unsubscribeFunctions.forEach((cleanupFunction) => cleanupFunction());
this.unsubscribeFunctions.clear();
};
}
66 changes: 47 additions & 19 deletions src/messageComposer/attachmentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,35 @@ const initState = ({
export class AttachmentManager {
readonly state: StateStore<AttachmentManagerState>;
readonly composer: MessageComposer;
private attachmentsByIdGetterCache: {
attachmentsById: Record<string, LocalAttachment>;
attachments: LocalAttachment[];
};

constructor({ composer, message }: AttachmentManagerOptions) {
this.composer = composer;
this.state = new StateStore<AttachmentManagerState>(initState({ message }));
this.attachmentsByIdGetterCache = { attachmentsById: {}, attachments: [] };
}

get attachmentsById() {
const { attachments } = this.state.getLatestValue();

if (attachments !== this.attachmentsByIdGetterCache.attachments) {
this.attachmentsByIdGetterCache.attachments = attachments;
this.attachmentsByIdGetterCache.attachmentsById = attachments.reduce<
Record<string, LocalAttachment>
>((newAttachmentsById, attachment) => {
// should never happen but does not hurt to check
if (!attachment.localMetadata.id) return newAttachmentsById;

newAttachmentsById[attachment.localMetadata.id] ??= attachment;

return newAttachmentsById;
}, {});
}

return this.attachmentsByIdGetterCache.attachmentsById;
}

get client() {
Expand Down Expand Up @@ -176,44 +201,47 @@ export class AttachmentManager {
this.state.next(initState({ message }));
};

getAttachmentIndex = (localId: string) =>
this.attachments.findIndex(
(attachment) =>
attachment.localMetadata.id && localId === attachment.localMetadata?.id,
);
getAttachmentIndex = (localId: string) => {
const attachmentsById = this.attachmentsById;

return this.attachments.indexOf(attachmentsById[localId]);
};

upsertAttachments = (attachmentsToUpsert: LocalAttachment[]) => {
if (!attachmentsToUpsert.length) return;
const stateAttachments = this.attachments;
const attachments = [...this.attachments];
attachmentsToUpsert.forEach((upsertedAttachment) => {
const attachmentIndex = this.getAttachmentIndex(
upsertedAttachment.localMetadata.id,
);

if (attachmentIndex === -1) {
const localAttachment = ensureIsLocalAttachment(upsertedAttachment);
if (localAttachment) attachments.push(localAttachment);
const currentAttachments = this.attachments;
const newAttachments = [...currentAttachments];

attachmentsToUpsert.forEach((attachment) => {
const targetAttachmentIndex = this.getAttachmentIndex(attachment.localMetadata?.id);

if (targetAttachmentIndex < 0) {
const localAttachment = ensureIsLocalAttachment(attachment);
if (localAttachment) newAttachments.push(localAttachment);
} else {
// do not re-organize newAttachments array otherwise indexing would no longer work
// replace in place only with the attachments with the same id's
const merged = mergeWithDiff<LocalAttachment>(
stateAttachments[attachmentIndex] ?? {},
upsertedAttachment,
currentAttachments[targetAttachmentIndex],
attachment,
);
const updatesOnMerge = merged.diff && Object.keys(merged.diff.children).length;
if (updatesOnMerge) {
const localAttachment = ensureIsLocalAttachment(merged.result);
if (localAttachment) attachments.splice(attachmentIndex, 1, localAttachment);
if (localAttachment)
newAttachments.splice(targetAttachmentIndex, 1, localAttachment);
}
}
});

this.state.partialNext({ attachments });
this.state.partialNext({ attachments: newAttachments });
};

removeAttachments = (localAttachmentIds: string[]) => {
this.state.partialNext({
attachments: this.attachments.filter(
(att) => !localAttachmentIds.includes(att.localMetadata?.id),
(attachment) => !localAttachmentIds.includes(attachment.localMetadata?.id),
),
});
};
Expand Down
9 changes: 4 additions & 5 deletions src/messageComposer/configuration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,16 @@ export type AttachmentManagerConfig = {
/** Function that allows to customize the upload request. */
doUploadRequest?: UploadRequestFn;
};
export type LinkPreviewConfig = {
/** Custom function to react to link preview dismissal */
onLinkPreviewDismissed?: (linkPreview: LinkPreview) => void;
};
export type LinkPreviewsManagerConfig = LinkPreviewConfig & {

export type LinkPreviewsManagerConfig = {
/** Number of milliseconds to debounce firing the URL enrichment queries when typing. The default value is 1500(ms). */
debounceURLEnrichmentMs: number;
/** Allows for toggling the URL enrichment and link previews in `MessageInput`. By default, the feature is disabled. */
enabled: boolean;
/** Custom function to identify URLs in a string and request OG data */
findURLFn: (text: string) => string[];
/** Custom function to react to link preview dismissal */
onLinkPreviewDismissed?: (linkPreview: LinkPreview) => void;
};

export type MessageComposerConfig = {
Expand Down
Loading
Loading