Skip to content

Commit

Permalink
Merge branch 'master' into custom-check-api
Browse files Browse the repository at this point in the history
  • Loading branch information
varbhat authored Dec 19, 2024
2 parents 377ab3e + 745c607 commit 420340c
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 24 deletions.
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [8.47.1](https://github.com/GetStream/stream-chat-js/compare/v8.47.0...v8.47.1) (2024-12-18)


### Bug Fixes

* message duplication when some messages have the same creation timestamp ([#1421](https://github.com/GetStream/stream-chat-js/issues/1421)) ([b7b019a](https://github.com/GetStream/stream-chat-js/commit/b7b019afa9bbe4d25c40ef6874f9219172991b7d))
* **search:** missing thread_participants in message ([#1412](https://github.com/GetStream/stream-chat-js/issues/1412)) ([af5cb81](https://github.com/GetStream/stream-chat-js/commit/af5cb81051cc7f1964fb17072fd07a2dd9f0b74b))

## [8.47.0](https://github.com/GetStream/stream-chat-js/compare/v8.46.1...v8.47.0) (2024-12-13)


### Features

* add team to channel template ([#1416](https://github.com/GetStream/stream-chat-js/issues/1416)) ([56bc83e](https://github.com/GetStream/stream-chat-js/commit/56bc83ee94d5f9859ebb24edd5f20f9a3b86aaca))


### Bug Fixes

* revert membership initialization behavior ([#1417](https://github.com/GetStream/stream-chat-js/issues/1417)) ([12aa4af](https://github.com/GetStream/stream-chat-js/commit/12aa4af73cade951dec21ec9b94b343eb36ec296))

### [8.46.1](https://github.com/GetStream/stream-chat-js/compare/v8.46.0...v8.46.1) (2024-12-11)


### Bug Fixes

* update membership object on member events ([#1409](https://github.com/GetStream/stream-chat-js/issues/1409)) ([5d1e4c4](https://github.com/GetStream/stream-chat-js/commit/5d1e4c4ffaf68bb372cefb6fde769858498a143e))

## [8.46.0](https://github.com/GetStream/stream-chat-js/compare/v8.45.3...v8.46.0) (2024-12-03)


Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "stream-chat",
"version": "8.46.0",
"version": "8.47.1",
"description": "JS SDK for the Stream Chat API",
"author": "GetStream",
"homepage": "https://getstream.io/chat/",
Expand Down
16 changes: 14 additions & 2 deletions src/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import {
AscDesc,
PartialUpdateMemberAPIResponse,
AIState,
MessageOptions,
} from './types';
import { Role } from './permissions';
import { DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE } from './constants';
Expand Down Expand Up @@ -237,6 +238,7 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
client_id?: string;
connection_id?: string;
message_filter_conditions?: MessageFilters<StreamChatGenerics>;
message_options?: MessageOptions;
query?: string;
} = {},
) {
Expand Down Expand Up @@ -1553,12 +1555,20 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
break;
case 'member.added':
case 'member.updated':
if (event.member?.user_id) {
if (event.member?.user) {
channelState.members = {
...channelState.members,
[event.member.user_id]: event.member,
[event.member.user.id]: event.member,
};
}

if (
typeof channelState.membership.user?.id === 'string' &&
typeof event.member?.user?.id === 'string' &&
event.member.user.id === channelState.membership.user.id
) {
channelState.membership = event.member;
}
break;
case 'member.removed':
if (event.user?.id) {
Expand All @@ -1569,6 +1579,8 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
delete newMembers[event.user.id];

channelState.members = newMembers;

// TODO?: unset membership
}
break;
case 'notification.mark_unread': {
Expand Down
8 changes: 3 additions & 5 deletions src/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,17 +354,15 @@ export class Thread<SCG extends ExtendableGenerics = DefaultGenerics> {
sortedArray: replies,
sortDirection: 'ascending',
selectValueToCompare: (reply) => reply.created_at.getTime(),
selectKey: (reply) => reply.id,
});

const actualIndex =
replies[index]?.id === message.id ? index : replies[index - 1]?.id === message.id ? index - 1 : null;

if (actualIndex === null) {
if (replies[index]?.id !== message.id) {
return;
}

const updatedReplies = [...replies];
updatedReplies.splice(actualIndex, 1);
updatedReplies.splice(index, 1);

this.state.partialNext({
replies: updatedReplies,
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1713,6 +1713,10 @@ export type MessageFilters<StreamChatGenerics extends ExtendableGenerics = Defau
}
>;

export type MessageOptions = {
include_thread_participants?: boolean;
};

export type PrimitiveFilter<ObjectType> = ObjectType | null;

export type QueryFilter<ObjectType = string> = NonNullable<ObjectType> extends string | number | boolean
Expand Down Expand Up @@ -2738,6 +2742,7 @@ export type SearchPayload<StreamChatGenerics extends ExtendableGenerics = Defaul
connection_id?: string;
filter_conditions?: ChannelFilters<StreamChatGenerics>;
message_filter_conditions?: MessageFilters<StreamChatGenerics>;
message_options?: MessageOptions;
query?: string;
sort?: Array<{
direction: AscDesc;
Expand Down Expand Up @@ -2970,6 +2975,7 @@ export type CampaignData = {
custom?: {};
id?: string;
members?: string[];
team?: string;
};
create_channels?: boolean;
deleted_at?: string;
Expand Down
56 changes: 41 additions & 15 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,26 @@ export function formatMessage<StreamChatGenerics extends ExtendableGenerics = De
export const findIndexInSortedArray = <T, L>({
needle,
sortedArray,
selectKey,
selectValueToCompare = (e) => e,
sortDirection = 'ascending',
}: {
needle: T;
sortedArray: readonly T[];
/**
* In array of objects (like messages), pick a specific
* property to compare needle value to.
* In an array of objects (like messages), pick a unique property identifying
* an element. It will be used to find a direct match for the needle element
* in case compare values are not unique.
*
* @example
* ```ts
* selectKey: (message) => message.id
* ```
*/
selectKey?: (arrayElement: T) => string;
/**
* In an array of objects (like messages), pick a specific
* property to compare the needle value to.
*
* @example
* ```ts
Expand Down Expand Up @@ -353,18 +365,33 @@ export const findIndexInSortedArray = <T, L>({

const comparableMiddle = selectValueToCompare(sortedArray[middle]);

// if (comparableNeedle === comparableMiddle) return middle;

if (
(sortDirection === 'ascending' && comparableNeedle < comparableMiddle) ||
(sortDirection === 'descending' && comparableNeedle > comparableMiddle)
(sortDirection === 'descending' && comparableNeedle >= comparableMiddle)
) {
right = middle - 1;
} else {
left = middle + 1;
}
}

// In case there are several array elements with the same comparable value, search around the insertion
// point to possibly find an element with the same key. If found, prefer it.
// This, for example, prevents duplication of messages with the same creation date.
if (selectKey) {
const needleKey = selectKey(needle);
const step = sortDirection === 'ascending' ? -1 : +1;
for (
let i = left + step;
0 <= i && i < sortedArray.length && selectValueToCompare(sortedArray[i]) === comparableNeedle;
i += step
) {
if (selectKey(sortedArray[i]) === needleKey) {
return i;
}
}
}

return left;
};

Expand Down Expand Up @@ -410,19 +437,18 @@ export function addToMessageList<T extends FormatMessageResponse>(
sortDirection: 'ascending',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
selectValueToCompare: (m) => m[sortBy]!.getTime(),
selectKey: (m) => m.id,
});

// message already exists and not filtered with timestampChanged, update and return
if (!timestampChanged && newMessage.id) {
if (newMessages[insertionIndex] && newMessage.id === newMessages[insertionIndex].id) {
newMessages[insertionIndex] = newMessage;
return newMessages;
}

if (newMessages[insertionIndex - 1] && newMessage.id === newMessages[insertionIndex - 1].id) {
newMessages[insertionIndex - 1] = newMessage;
return newMessages;
}
if (
!timestampChanged &&
newMessage.id &&
newMessages[insertionIndex] &&
newMessage.id === newMessages[insertionIndex].id
) {
newMessages[insertionIndex] = newMessage;
return newMessages;
}

// do not add updated or deleted messages to the list if they already exist or come with a timestamp change
Expand Down
146 changes: 145 additions & 1 deletion test/unit/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { v4 as uuidv4 } from 'uuid';

import { generateMsg } from './test-utils/generateMessage';

import { addToMessageList, formatMessage } from '../../src/utils';
import { addToMessageList, findIndexInSortedArray, formatMessage } from '../../src/utils';

import type { FormatMessageResponse, MessageResponse } from '../../src';

Expand Down Expand Up @@ -105,3 +105,147 @@ describe('addToMessageList', () => {
expect(messagesAfter[4]).to.equal(newMessage);
});
});

describe('findIndexInSortedArray', () => {
it('finds index in the middle of haystack (asc)', () => {
const needle = 5;
const haystack = [1, 2, 3, 4, 6, 7, 8, 9];
const index = findIndexInSortedArray({ needle, sortedArray: haystack, sortDirection: 'ascending' });
expect(index).to.eq(4);
});

it('finds index at the top of haystack (asc)', () => {
const needle = 0;
const haystack = [1, 2, 3, 4, 6, 7, 8, 9];
const index = findIndexInSortedArray({ needle, sortedArray: haystack, sortDirection: 'ascending' });
expect(index).to.eq(0);
});

it('finds index at the bottom of haystack (asc)', () => {
const needle = 10;
const haystack = [1, 2, 3, 4, 6, 7, 8, 9];
const index = findIndexInSortedArray({ needle, sortedArray: haystack, sortDirection: 'ascending' });
expect(index).to.eq(8);
});

it('in a haystack with duplicates, prefers index closer to the bottom (asc)', () => {
const needle = 5;
const haystack = [1, 5, 5, 5, 5, 5, 8, 9];
const index = findIndexInSortedArray({ needle, sortedArray: haystack, sortDirection: 'ascending' });
expect(index).to.eq(6);
});

it('in a haystack with duplicates, look up an item by key (asc)', () => {
const haystack: [key: string, value: number][] = [
['one', 1],
['five-1', 5],
['five-2', 5],
['five-3', 5],
['nine', 9],
];

const selectKey = (tuple: [key: string, value: number]) => tuple[0];
const selectValue = (tuple: [key: string, value: number]) => tuple[1];

expect(
findIndexInSortedArray({
needle: ['five-1', 5],
sortedArray: haystack,
sortDirection: 'ascending',
selectKey,
selectValueToCompare: selectValue,
}),
).to.eq(1);

expect(
findIndexInSortedArray({
needle: ['five-2', 5],
sortedArray: haystack,
sortDirection: 'ascending',
selectKey,
selectValueToCompare: selectValue,
}),
).to.eq(2);

expect(
findIndexInSortedArray({
needle: ['five-3', 5],
sortedArray: haystack,
sortDirection: 'ascending',
selectKey,
selectValueToCompare: selectValue,
}),
).to.eq(3);
});

it('finds index in the middle of haystack (desc)', () => {
const needle = 5;
const haystack = [9, 8, 7, 6, 4, 3, 2, 1];
const index = findIndexInSortedArray({ needle, sortedArray: haystack, sortDirection: 'descending' });
expect(index).to.eq(4);
});

it('finds index at the top of haystack (desc)', () => {
const needle = 10;
const haystack = [9, 8, 7, 6, 4, 3, 2, 1];
const index = findIndexInSortedArray({ needle, sortedArray: haystack, sortDirection: 'descending' });
expect(index).to.eq(0);
});

it('finds index at the bottom of haystack (desc)', () => {
const needle = 0;
const haystack = [9, 8, 7, 6, 4, 3, 2, 1];
const index = findIndexInSortedArray({ needle, sortedArray: haystack, sortDirection: 'descending' });
expect(index).to.eq(8);
});

it('in a haystack with duplicates, prefers index closer to the top (desc)', () => {
const needle = 5;
const haystack = [9, 8, 5, 5, 5, 5, 5, 1];
const index = findIndexInSortedArray({ needle, sortedArray: haystack, sortDirection: 'descending' });
expect(index).to.eq(2);
});

it('in a haystack with duplicates, look up an item by key (desc)', () => {
const haystack: [key: string, value: number][] = [
['nine', 9],
['five-1', 5],
['five-2', 5],
['five-3', 5],
['one', 1],
];

const selectKey = (tuple: [key: string, value: number]) => tuple[0];
const selectValue = (tuple: [key: string, value: number]) => tuple[1];

expect(
findIndexInSortedArray({
needle: ['five-1', 5],
sortedArray: haystack,
sortDirection: 'descending',
selectKey,
selectValueToCompare: selectValue,
}),
).to.eq(1);

expect(
findIndexInSortedArray({
needle: ['five-2', 5],
sortedArray: haystack,
sortDirection: 'descending',
selectKey,
selectValueToCompare: selectValue,
}),
).to.eq(2);

expect(
findIndexInSortedArray({
needle: ['five-3', 5],
sortedArray: haystack,
sortDirection: 'descending',
selectKey,
selectValueToCompare: selectValue,
}),
).to.eq(3);
});
});

0 comments on commit 420340c

Please sign in to comment.