Skip to content

Commit

Permalink
Merge branch 'master' into fix/add-cache-disabling-option
Browse files Browse the repository at this point in the history
  • Loading branch information
isekovanic committed Jan 21, 2025
2 parents 7f4a3b1 + 8daedeb commit 471190f
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 17 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

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.52.0](https://github.com/GetStream/stream-chat-js/compare/v8.51.0...v8.52.0) (2025-01-20)


### Features

* add translate API support ([#1415](https://github.com/GetStream/stream-chat-js/issues/1415)) ([7c1c58d](https://github.com/GetStream/stream-chat-js/commit/7c1c58df4d0c7cffdadf7062189053d9b7a5fb80))

## [8.51.0](https://github.com/GetStream/stream-chat-js/compare/v8.50.0...v8.51.0) (2025-01-17)


### Features

* **threads:** handle custom data ([#1428](https://github.com/GetStream/stream-chat-js/issues/1428)) ([964f008](https://github.com/GetStream/stream-chat-js/commit/964f008dfca0c7740995d708dec12cdfbeb1b8b8))

## [8.50.0](https://github.com/GetStream/stream-chat-js/compare/v8.49.0...v8.50.0) (2025-01-16)


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.50.0",
"version": "8.52.0",
"description": "JS SDK for the Stream Chat API",
"author": "GetStream",
"homepage": "https://getstream.io/chat/",
Expand Down
48 changes: 36 additions & 12 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ import {
TestSNSDataInput,
TestSQSDataInput,
TokenOrProvider,
TranslateResponse,
UnBanUserOptions,
UpdateChannelOptions,
UpdateChannelResponse,
Expand Down Expand Up @@ -2524,6 +2525,23 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
);
}

/**
* translate - translates the given text to provided language
*
* @param {string} text
* @param {string} destination_language
* @param {string} source_language
*
* @return {TranslateResponse} Response that includes the message
*/
async translate(text: string, destination_language: string, source_language: string) {
return await this.post<APIResponse & TranslateResponse>(this.baseURL + `/translate`, {
text,
source_language,
destination_language,
});
}

/**
* _normalizeExpiration - transforms expiration value into ISO string
* @param {undefined|null|number|string|Date} timeoutOrExpirationDate expiration date or timeout. Use number type to set timeout in seconds, string or Date to set exact expiration date
Expand Down Expand Up @@ -2762,20 +2780,25 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
*
* @returns {{ threads: Thread<StreamChatGenerics>[], next: string }} Returns the list of threads and the next cursor.
*/
async queryThreads(options?: QueryThreadsOptions) {
const opts = {
async queryThreads(options: QueryThreadsOptions = {}) {
const optionsWithDefaults = {
limit: 10,
participant_limit: 10,
reply_limit: 3,
watch: true,
...options,
};

const res = await this.post<QueryThreadsAPIResponse<StreamChatGenerics>>(this.baseURL + `/threads`, opts);
const response = await this.post<QueryThreadsAPIResponse<StreamChatGenerics>>(
`${this.baseURL}/threads`,
optionsWithDefaults,
);

return {
threads: res.threads.map((thread) => new Thread({ client: this, threadData: thread })),
next: res.next,
threads: response.threads.map(
(thread) => new Thread<StreamChatGenerics>({ client: this, threadData: thread }),
),
next: response.next,
};
}

Expand All @@ -2792,22 +2815,22 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
*/
async getThread(messageId: string, options: GetThreadOptions = {}) {
if (!messageId) {
throw Error('Please specify the message id when calling partialUpdateThread');
throw Error('Please specify the messageId when calling getThread');
}

const opts = {
const optionsWithDefaults = {
participant_limit: 100,
reply_limit: 3,
watch: true,
...options,
};

const res = await this.get<GetThreadAPIResponse<StreamChatGenerics>>(
this.baseURL + `/threads/${encodeURIComponent(messageId)}`,
opts,
const response = await this.get<GetThreadAPIResponse<StreamChatGenerics>>(
`${this.baseURL}/threads/${encodeURIComponent(messageId)}`,
optionsWithDefaults,
);

return new Thread<StreamChatGenerics>({ client: this, threadData: res.thread });
return new Thread<StreamChatGenerics>({ client: this, threadData: response.thread });
}

/**
Expand Down Expand Up @@ -2835,6 +2858,7 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
'reply_count',
'participants',
'channel',
'custom',
];

for (const key in { ...partialThreadObject.set, ...partialThreadObject.unset }) {
Expand All @@ -2846,7 +2870,7 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
}

return await this.patch<GetThreadAPIResponse<StreamChatGenerics>>(
this.baseURL + `/threads/${encodeURIComponent(messageId)}`,
`${this.baseURL}/threads/${encodeURIComponent(messageId)}`,
partialThreadObject,
);
}
Expand Down
1 change: 1 addition & 0 deletions src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const EVENT_MAP = {
'reaction.deleted': true,
'reaction.new': true,
'reaction.updated': true,
'thread.updated': true,
'typing.start': true,
'typing.stop': true,
'user.banned': true,
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export * from './poll_manager';
export * from './segment';
export * from './signing';
export * from './store';
export * from './thread';
export { Thread } from './thread';
export type { ThreadState, ThreadReadState, ThreadRepliesPagination, ThreadUserReadState } from './thread';
export * from './thread_manager';
export * from './token_manager';
export * from './types';
Expand Down
68 changes: 66 additions & 2 deletions src/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
MessageResponse,
ReadResponse,
ThreadResponse,
ThreadResponseCustomData,
UserResponse,
} from './types';
import { addToMessageList, findIndexInSortedArray, formatMessage, throttle } from './utils';
Expand All @@ -27,6 +28,7 @@ export type ThreadState<SCG extends ExtendableGenerics = DefaultGenerics> = {
active: boolean;
channel: Channel<SCG>;
createdAt: Date;
custom: ThreadResponseCustomData;
deletedAt: Date | null;
isLoading: boolean;
isStateStale: boolean;
Expand All @@ -40,6 +42,7 @@ export type ThreadState<SCG extends ExtendableGenerics = DefaultGenerics> = {
read: ThreadReadState;
replies: Array<FormatMessageResponse<SCG>>;
replyCount: number;
title: string;
updatedAt: Date | null;
};

Expand All @@ -65,6 +68,43 @@ export type ThreadReadState<SCG extends ExtendableGenerics = DefaultGenerics> =
const DEFAULT_PAGE_LIMIT = 50;
const DEFAULT_SORT: { created_at: AscDesc }[] = [{ created_at: -1 }];
const MARK_AS_READ_THROTTLE_TIMEOUT = 1000;
// TODO: remove this once we move to API v2
export const THREAD_RESPONSE_RESERVED_KEYS: Record<keyof ThreadResponse, true> = {
channel: true,
channel_cid: true,
created_at: true,
created_by_user_id: true,
parent_message_id: true,
title: true,
updated_at: true,
latest_replies: true,
active_participant_count: true,
deleted_at: true,
last_message_at: true,
participant_count: true,
reply_count: true,
read: true,
thread_participants: true,
created_by: true,
parent_message: true,
};

// TODO: remove this once we move to API v2
const constructCustomDataObject = <T extends ThreadResponse>(threadData: T) => {
const custom: ThreadResponseCustomData = {};

for (const key in threadData) {
if (THREAD_RESPONSE_RESERVED_KEYS[key as keyof ThreadResponse]) {
continue;
}

const customKey = key as keyof ThreadResponseCustomData;

custom[customKey] = threadData[customKey];
}

return custom;
};

export class Thread<SCG extends ExtendableGenerics = DefaultGenerics> {
public readonly state: StateStore<ThreadState<SCG>>;
Expand All @@ -87,12 +127,15 @@ export class Thread<SCG extends ExtendableGenerics = DefaultGenerics> {
: [];

this.state = new StateStore<ThreadState<SCG>>({
// local only
active: false,
isLoading: false,
isStateStale: false,
// 99.9% should never change
channel,
createdAt: new Date(threadData.created_at),
// rest
deletedAt: threadData.deleted_at ? new Date(threadData.deleted_at) : null,
isLoading: false,
isStateStale: false,
pagination: repliesPaginationFromInitialThread(threadData),
parentMessage: formatMessage(threadData.parent_message),
participants: threadData.thread_participants,
Expand All @@ -102,6 +145,8 @@ export class Thread<SCG extends ExtendableGenerics = DefaultGenerics> {
replies: threadData.latest_replies.map(formatMessage),
replyCount: threadData.reply_count ?? 0,
updatedAt: threadData.updated_at ? new Date(threadData.updated_at) : null,
title: threadData.title,
custom: constructCustomDataObject(threadData),
});

this.id = threadData.parent_message_id;
Expand Down Expand Up @@ -186,6 +231,7 @@ export class Thread<SCG extends ExtendableGenerics = DefaultGenerics> {
return;
}

this.unsubscribeFunctions.add(this.subscribeThreadUpdated());
this.unsubscribeFunctions.add(this.subscribeMarkActiveThreadRead());
this.unsubscribeFunctions.add(this.subscribeReloadActiveStaleThread());
this.unsubscribeFunctions.add(this.subscribeMarkThreadStale());
Expand All @@ -195,6 +241,24 @@ export class Thread<SCG extends ExtendableGenerics = DefaultGenerics> {
this.unsubscribeFunctions.add(this.subscribeMessageUpdated());
};

private subscribeThreadUpdated = () => {
return this.client.on('thread.updated', (event) => {
if (!event.thread || event.thread.parent_message_id !== this.id) {
return;
}

const threadData = event.thread;

this.state.partialNext({
title: threadData.title,
updatedAt: new Date(threadData.updated_at),
deletedAt: threadData.deleted_at ? new Date(threadData.deleted_at) : null,
// TODO: use threadData.custom once we move to API v2
custom: constructCustomDataObject(threadData),
});
}).unsubscribe;
};

private subscribeMarkActiveThreadRead = () => {
return this.state.subscribeWithSelector(
(nextValue) => ({
Expand Down
13 changes: 12 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ export type APIResponse = {
duration: string;
};

export type TranslateResponse = {
language: string;
translated_text: string;
};

export type AppSettingsAPIResponse<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = APIResponse & {
app?: {
// TODO
Expand Down Expand Up @@ -520,7 +525,10 @@ export type GetMessageAPIResponse<
StreamChatGenerics extends ExtendableGenerics = DefaultGenerics
> = SendMessageAPIResponse<StreamChatGenerics>;

export interface ThreadResponse<SCG extends ExtendableGenerics = DefaultGenerics> {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ThreadResponseCustomData {}

export interface ThreadResponse<SCG extends ExtendableGenerics = DefaultGenerics> extends ThreadResponseCustomData {
// FIXME: according to OpenAPI, `channel` could be undefined but since cid is provided I'll asume that it's wrong
channel: ChannelResponse<SCG>;
channel_cid: string;
Expand All @@ -531,6 +539,7 @@ export interface ThreadResponse<SCG extends ExtendableGenerics = DefaultGenerics
parent_message_id: string;
title: string;
updated_at: string;
active_participant_count?: number;
created_by?: UserResponse<SCG>;
deleted_at?: string;
last_message_at?: string;
Expand All @@ -547,6 +556,8 @@ export interface ThreadResponse<SCG extends ExtendableGenerics = DefaultGenerics
user?: UserResponse<SCG>;
user_id?: string;
}>;
// TODO: when moving to API v2 we should do this instead
// custom: ThreadResponseCustomData;
}

// TODO: Figure out a way to strongly type set and unset.
Expand Down
Loading

0 comments on commit 471190f

Please sign in to comment.