diff --git a/eslint.config.mjs b/eslint.config.mjs index 8ac2b61478..c9c1450df3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -91,6 +91,7 @@ export default tseslint.config( { ignoreRestSiblings: false, caughtErrors: 'none' }, ], '@typescript-eslint/no-unsafe-function-type': 'error', + '@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/no-non-null-assertion': 'error', 'no-empty-function': 'off', diff --git a/examples/capacitor/src/App.tsx b/examples/capacitor/src/App.tsx index 70efddf311..002f43e4d8 100644 --- a/examples/capacitor/src/App.tsx +++ b/examples/capacitor/src/App.tsx @@ -21,25 +21,7 @@ const filters: ChannelFilters = { type: 'messaging', members: { $in: [userId] } const options: ChannelOptions = { state: true, presence: true, limit: 10 }; const sort: ChannelSort = { last_message_at: -1, updated_at: -1 }; -type LocalAttachmentType = Record; -type LocalChannelType = Record; -type LocalCommandType = string; -type LocalEventType = Record; -type LocalMessageType = Record; -type LocalReactionType = Record; -type LocalUserType = Record; - -type StreamChatGenerics = { - attachmentType: LocalAttachmentType; - channelType: LocalChannelType; - commandType: LocalCommandType; - eventType: LocalEventType; - messageType: LocalMessageType; - reactionType: LocalReactionType; - userType: LocalUserType; -}; - -const chatClient = StreamChat.getInstance(apiKey); +const chatClient = StreamChat.getInstance(apiKey); if (process.env.REACT_APP_CHAT_SERVER_ENDPOINT) { chatClient.setBaseURL(process.env.REACT_APP_CHAT_SERVER_ENDPOINT); diff --git a/examples/typescript/src/App.tsx b/examples/typescript/src/App.tsx index 77dec5c641..3dfe26eef8 100644 --- a/examples/typescript/src/App.tsx +++ b/examples/typescript/src/App.tsx @@ -23,31 +23,7 @@ const filters: ChannelFilters = { type: 'messaging', members: { $in: [userId] } const options: ChannelOptions = { state: true, presence: true, limit: 10 }; const sort: ChannelSort = { last_message_at: -1, updated_at: -1 }; -type LocalAttachmentType = Record; -type LocalChannelType = Record; -type LocalCommandType = string; -type LocalEventType = Record; -type LocalMemberType = Record; -type LocalMessageType = Record; -type LocalPollOptionType = Record; -type LocalPollType = Record; -type LocalReactionType = Record; -type LocalUserType = Record; - -type StreamChatGenerics = { - attachmentType: LocalAttachmentType; - channelType: LocalChannelType; - commandType: LocalCommandType; - eventType: LocalEventType; - memberType: LocalMemberType; - messageType: LocalMessageType; - pollOptionType: LocalPollOptionType; - pollType: LocalPollType; - reactionType: LocalReactionType; - userType: LocalUserType; -}; - -const chatClient = StreamChat.getInstance(apiKey); +const chatClient = StreamChat.getInstance(apiKey); if (process.env.REACT_APP_CHAT_SERVER_ENDPOINT) { chatClient.setBaseURL(process.env.REACT_APP_CHAT_SERVER_ENDPOINT); diff --git a/examples/vite/src/App.tsx b/examples/vite/src/App.tsx index 4780d324ad..0786c53ee4 100644 --- a/examples/vite/src/App.tsx +++ b/examples/vite/src/App.tsx @@ -40,35 +40,10 @@ const filters: ChannelFilters = { const options: ChannelOptions = { limit: 5, presence: true, state: true }; const sort: ChannelSort = { pinned_at: 1, last_message_at: -1, updated_at: -1 }; -type LocalAttachmentType = Record; -type LocalChannelType = Record; -type LocalCommandType = string; -type LocalEventType = Record; -type LocalMemberType = Record; -type LocalMessageType = Record; -type LocalPollOptionType = Record; -type LocalPollType = Record; -type LocalReactionType = Record; -type LocalUserType = Record; - -type StreamChatGenerics = { - attachmentType: LocalAttachmentType; - channelType: LocalChannelType; - commandType: LocalCommandType; - eventType: LocalEventType; - memberType: LocalMemberType; - messageType: LocalMessageType; - pollOptionType: LocalPollOptionType; - pollType: LocalPollType; - reactionType: LocalReactionType; - userType: LocalUserType; -}; - -const isMessageAIGenerated = (message: StreamMessage) => - !!message?.ai_generated; +const isMessageAIGenerated = (message: StreamMessage) => !!message?.ai_generated; const App = () => { - const chatClient = useCreateChatClient({ + const chatClient = useCreateChatClient({ apiKey, tokenOrProvider: userToken, userData: { id: userId }, diff --git a/package.json b/package.json index 585043ec6d..85b227bd3f 100644 --- a/package.json +++ b/package.json @@ -239,7 +239,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "semantic-release": "^24.2.3", - "stream-chat": "^8.55.0", + "stream-chat": "^9.0.0-rc.8", "ts-jest": "^29.2.5", "typescript": "^5.4.5", "typescript-eslint": "^8.17.0" @@ -260,7 +260,7 @@ "prepare": "husky install", "preversion": "yarn install", "test": "jest", - "types": "tsc --noEmit --skipLibCheck false", + "types": "tsc --noEmit", "validate-translations": "node scripts/validate-translations.js", "validate-cjs": "concurrently 'node scripts/validate-cjs-node-bundle.cjs' 'node scripts/validate-cjs-browser-bundle.cjs'", "semantic-release": "semantic-release", diff --git a/src/@types/image-files.d.ts b/src/@types/image-files.d.ts deleted file mode 100644 index 20d903de0c..0000000000 --- a/src/@types/image-files.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare module '*.png'; -declare module '*.svg'; -declare module '*.jpg'; diff --git a/src/@types/stream-chat-common-custom-data.d.ts b/src/@types/stream-chat-common-custom-data.d.ts new file mode 100644 index 0000000000..2755dd9c7e --- /dev/null +++ b/src/@types/stream-chat-common-custom-data.d.ts @@ -0,0 +1,9 @@ +import 'stream-chat'; + +declare module 'stream-chat' { + interface CustomChannelData { + image?: string; + name?: string; + subtitle?: string; + } +} diff --git a/src/@types/vfile-message.d.ts b/src/@types/vfile-message.d.ts deleted file mode 100644 index bb7d411b6b..0000000000 --- a/src/@types/vfile-message.d.ts +++ /dev/null @@ -1,98 +0,0 @@ -// TypeScript Version: 3.0 - -import * as Unist from 'unist'; - -declare namespace vfileMessage { - /** - * Create a virtual message. - */ - interface VFileMessage extends Error { - /** - * Constructor of a message for `reason` at `position` from `origin`. - * When an error is passed in as `reason`, copies the `stack`. - * - * @param reason Reason for message (`string` or `Error`). Uses the stack and message of the error if given. - * @param position Place at which the message occurred in a file (`Node`, `Position`, or `Point`, optional). - * @param origin Place in code the message originates from (`string`, optional). - */ - ( - reason: string | Error, - position?: Unist.Node | Unist.Position | Unist.Point, - origin?: string, - ): VFileMessage; - - /** - * Constructor of a message for `reason` at `position` from `origin`. - * When an error is passed in as `reason`, copies the `stack`. - * - * @param reason Reason for message (`string` or `Error`). Uses the stack and message of the error if given. - * @param position Place at which the message occurred in a file (`Node`, `Position`, or `Point`, optional). - * @param origin Place in code the message originates from (`string`, optional). - */ - new ( - reason: string | Error, - position?: Unist.Node | Unist.Position | Unist.Point, - origin?: string, - ): VFileMessage; - - /** - * It’s OK to store custom data directly on the VMessage, some of those are handled by utilities. - */ - [key: string]: unknown; - - /** - * Starting column of error. - */ - column: number | null; - - /** - * Starting line of error. - */ - line: number | null; - - /** - * Full range information, when available. - * Has start and end properties, both set to an object with line and column, set to number?. - */ - location: Unist.Position; - - /** - * Reason for message. - */ - reason: string; - - /** - * Category of message. - */ - ruleId: string | null; - - /** - * Namespace of warning. - */ - source: string | null; - - /** - * If true, marks associated file as no longer processable. - */ - fatal?: boolean | null; - - /** - * You may add a file property with a path of a file (used throughout the VFile ecosystem). - */ - file?: string; - - /** - * You may add a note property with a long form description of the message (supported by vfile-reporter). - */ - note?: string; - - /** - * You may add a url property with a link to documentation for the message. - */ - url?: string; - } -} - -declare const vfileMessage: vfileMessage.VFileMessage; - -export = vfileMessage; diff --git a/src/components/AIStateIndicator/AIStateIndicator.tsx b/src/components/AIStateIndicator/AIStateIndicator.tsx index 977bd240a0..711f4fe36a 100644 --- a/src/components/AIStateIndicator/AIStateIndicator.tsx +++ b/src/components/AIStateIndicator/AIStateIndicator.tsx @@ -1,26 +1,19 @@ import React from 'react'; - -import { Channel } from 'stream-chat'; +import type { Channel } from 'stream-chat'; import { AIStates, useAIState } from './hooks/useAIState'; import { useChannelStateContext, useTranslationContext } from '../../context'; -import type { DefaultStreamChatGenerics } from '../../types/types'; -export type AIStateIndicatorProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - channel?: Channel; +export type AIStateIndicatorProps = { + channel?: Channel; }; -export const AIStateIndicator = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const AIStateIndicator = ({ channel: channelFromProps, -}: AIStateIndicatorProps) => { +}: AIStateIndicatorProps) => { const { t } = useTranslationContext(); - const { channel: channelFromContext } = - useChannelStateContext('AIStateIndicator'); + const { channel: channelFromContext } = useChannelStateContext('AIStateIndicator'); const channel = channelFromProps || channelFromContext; const { aiState } = useAIState(channel); const allowedStates = { diff --git a/src/components/AIStateIndicator/hooks/useAIState.ts b/src/components/AIStateIndicator/hooks/useAIState.ts index 33d744fda8..2934c25cfc 100644 --- a/src/components/AIStateIndicator/hooks/useAIState.ts +++ b/src/components/AIStateIndicator/hooks/useAIState.ts @@ -1,8 +1,5 @@ import { useEffect, useState } from 'react'; - -import { AIState, Channel, Event } from 'stream-chat'; - -import type { DefaultStreamChatGenerics } from '../../../types/types'; +import type { AIState, Channel, Event } from 'stream-chat'; export const AIStates = { Error: 'AI_STATE_ERROR', @@ -17,11 +14,7 @@ export const AIStates = { * @param {Channel} channel - The channel for which we want to know the AI state. * @returns {{ aiState: AIState }} The current AI state for the given channel. */ -export const useAIState = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel?: Channel, -): { aiState: AIState } => { +export const useAIState = (channel?: Channel): { aiState: AIState } => { const [aiState, setAiState] = useState(AIStates.Idle); useEffect(() => { @@ -29,16 +22,13 @@ export const useAIState = < return; } - const indicatorChangedListener = channel.on( - 'ai_indicator.update', - (event: Event) => { - const { cid } = event; - const state = event.ai_state as AIState; - if (channel.cid === cid) { - setAiState(state); - } - }, - ); + const indicatorChangedListener = channel.on('ai_indicator.update', (event: Event) => { + const { cid } = event; + const state = event.ai_state as AIState; + if (channel.cid === cid) { + setAiState(state); + } + }); const indicatorClearedListener = channel.on('ai_indicator.clear', (event) => { const { cid } = event; diff --git a/src/components/Attachment/Attachment.tsx b/src/components/Attachment/Attachment.tsx index 93e70adcc7..93f8b76425 100644 --- a/src/components/Attachment/Attachment.tsx +++ b/src/components/Attachment/Attachment.tsx @@ -3,7 +3,6 @@ import type { ReactPlayerProps } from 'react-player'; import type { Attachment as StreamAttachment } from 'stream-chat'; import { - GroupedRenderedAttachment, isAudioAttachment, isFileAttachment, isMediaAttachment, @@ -11,7 +10,6 @@ import { isUploadedImage, isVoiceRecordingAttachment, } from './utils'; - import { AudioContainer, CardContainer, @@ -22,7 +20,6 @@ import { UnsupportedAttachmentContainer, VoiceRecordingContainer, } from './AttachmentContainer'; - import type { AttachmentActionsProps } from './AttachmentActions'; import type { AudioProps } from './Audio'; import type { VoiceRecordingProps } from './VoiceRecording'; @@ -31,8 +28,7 @@ import type { FileAttachmentProps } from './FileAttachment'; import type { GalleryProps, ImageProps } from '../Gallery'; import type { UnsupportedAttachmentProps } from './UnsupportedAttachment'; import type { ActionHandlerReturnType } from '../Message/hooks/useActionHandler'; - -import type { DefaultStreamChatGenerics } from '../../types/types'; +import type { GroupedRenderedAttachment } from './utils'; const CONTAINER_MAP = { audio: AudioContainer, @@ -54,23 +50,21 @@ export const ATTACHMENT_GROUPS_ORDER = [ 'unsupported', ] as const; -export type AttachmentProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type AttachmentProps = { /** The message attachments to render, see [attachment structure](https://getstream.io/chat/docs/javascript/message_format/?language=javascript) **/ - attachments: StreamAttachment[]; + attachments: StreamAttachment[]; /** The handler function to call when an action is performed on an attachment, examples include canceling a \/giphy command or shuffling the results. */ actionHandler?: ActionHandlerReturnType; /** Custom UI component for displaying attachment actions, defaults to and accepts same props as: [AttachmentActions](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Attachment/AttachmentActions.tsx) */ - AttachmentActions?: React.ComponentType>; + AttachmentActions?: React.ComponentType; /** Custom UI component for displaying an audio type attachment, defaults to and accepts same props as: [Audio](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Attachment/Audio.tsx) */ - Audio?: React.ComponentType>; + Audio?: React.ComponentType; /** Custom UI component for displaying a card type attachment, defaults to and accepts same props as: [Card](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Attachment/Card.tsx) */ Card?: React.ComponentType; /** Custom UI component for displaying a file type attachment, defaults to and accepts same props as: [File](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Attachment/FileAttachment.tsx) */ - File?: React.ComponentType>; + File?: React.ComponentType; /** Custom UI component for displaying a gallery of image type attachments, defaults to and accepts same props as: [Gallery](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Gallery/Gallery.tsx) */ - Gallery?: React.ComponentType>; + Gallery?: React.ComponentType; /** Custom UI component for displaying an image type attachment, defaults to and accepts same props as: [Image](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Gallery/Image.tsx) */ Image?: React.ComponentType; /** Optional flag to signal that an attachment is a displayed as a part of a quoted message */ @@ -80,17 +74,13 @@ export type AttachmentProps< /** Custom UI component for displaying unsupported attachment types, defaults to NullComponent */ UnsupportedAttachment?: React.ComponentType; /** Custom UI component for displaying an audio recording attachment, defaults to and accepts same props as: [VoiceRecording](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Attachment/VoiceRecording.tsx) */ - VoiceRecording?: React.ComponentType>; + VoiceRecording?: React.ComponentType; }; /** * A component used for rendering message attachments. By default, the component supports: AttachmentActions, Audio, Card, File, Gallery, Image, and Video */ -export const Attachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AttachmentProps, -) => { +export const Attachment = (props: AttachmentProps) => { const { attachments } = props; const groupedAttachments = useMemo( @@ -109,14 +99,12 @@ export const Attachment = < ); }; -const renderGroupedAttachments = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +const renderGroupedAttachments = ({ attachments, ...rest -}: AttachmentProps): GroupedRenderedAttachment => { - const uploadedImages: StreamAttachment[] = attachments.filter( - (attachment) => isUploadedImage(attachment), +}: AttachmentProps): GroupedRenderedAttachment => { + const uploadedImages: StreamAttachment[] = attachments.filter((attachment) => + isUploadedImage(attachment), ); const containers = attachments @@ -171,10 +159,8 @@ const renderGroupedAttachments = < return containers; }; -const getAttachmentType = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - attachment: AttachmentProps['attachments'][number], +const getAttachmentType = ( + attachment: AttachmentProps['attachments'][number], ): keyof typeof CONTAINER_MAP => { if (isScrapedContent(attachment)) { return 'card'; diff --git a/src/components/Attachment/AttachmentActions.tsx b/src/components/Attachment/AttachmentActions.tsx index ec323edfb0..52e3148e1a 100644 --- a/src/components/Attachment/AttachmentActions.tsx +++ b/src/components/Attachment/AttachmentActions.tsx @@ -4,11 +4,8 @@ import type { Action, Attachment } from 'stream-chat'; import { useTranslationContext } from '../../context'; import type { ActionHandlerReturnType } from '../Message/hooks/useActionHandler'; -import type { DefaultStreamChatGenerics } from '../../types/types'; -export type AttachmentActionsProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Attachment & { +export type AttachmentActionsProps = Attachment & { /** A list of actions */ actions: Action[]; /** Unique id for action button key. Key is generated by concatenating this id with action value - {`${id}-${action.value}`} */ @@ -19,11 +16,7 @@ export type AttachmentActionsProps< actionHandler?: ActionHandlerReturnType; }; -const UnMemoizedAttachmentActions = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AttachmentActionsProps, -) => { +const UnMemoizedAttachmentActions = (props: AttachmentActionsProps) => { const { actionHandler, actions, id, text } = props; const { t } = useTranslationContext('UnMemoizedAttachmentActions'); diff --git a/src/components/Attachment/AttachmentContainer.tsx b/src/components/Attachment/AttachmentContainer.tsx index cb003a4236..3d1611d0e0 100644 --- a/src/components/Attachment/AttachmentContainer.tsx +++ b/src/components/Attachment/AttachmentContainer.tsx @@ -1,8 +1,9 @@ -import React, { PropsWithChildren, useLayoutEffect, useRef, useState } from 'react'; +import type { PropsWithChildren } from 'react'; +import React, { useLayoutEffect, useRef, useState } from 'react'; import ReactPlayer from 'react-player'; import clsx from 'clsx'; - import * as linkify from 'linkifyjs'; +import type { Attachment } from 'stream-chat'; import { AttachmentActions as DefaultAttachmentActions } from './AttachmentActions'; import { Audio as DefaultAudio } from './Audio'; @@ -11,37 +12,29 @@ import { Gallery as DefaultGallery, ImageComponent as DefaultImage } from '../Ga import { Card as DefaultCard } from './Card'; import { FileAttachment as DefaultFile } from './FileAttachment'; import { UnsupportedAttachment as DefaultUnsupportedAttachment } from './UnsupportedAttachment'; -import { +import type { AttachmentComponentType, GalleryAttachment, - isGalleryAttachmentType, - isSvgAttachment, RenderAttachmentProps, RenderGalleryProps, } from './utils'; - +import { isGalleryAttachmentType, isSvgAttachment } from './utils'; import { useChannelStateContext } from '../../context/ChannelStateContext'; - +import type { LocalAttachment } from '../MessageInput'; import type { - DefaultStreamChatGenerics, ImageAttachmentConfiguration, VideoAttachmentConfiguration, } from '../../types/types'; -import type { Attachment } from 'stream-chat'; -export type AttachmentContainerProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - attachment: Attachment | GalleryAttachment; +export type AttachmentContainerProps = { + attachment: Attachment | GalleryAttachment; componentType: AttachmentComponentType; }; -export const AttachmentWithinContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const AttachmentWithinContainer = ({ attachment, children, componentType, -}: PropsWithChildren>) => { +}: PropsWithChildren) => { const isGAT = isGalleryAttachmentType(attachment); let extra = ''; @@ -69,13 +62,11 @@ export const AttachmentWithinContainer = < return
{children}
; }; -export const AttachmentActionsContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const AttachmentActionsContainer = ({ actionHandler, attachment, AttachmentActions = DefaultAttachmentActions, -}: RenderAttachmentProps) => { +}: RenderAttachmentProps) => { if (!attachment.actions?.length) return null; return ( @@ -83,7 +74,7 @@ export const AttachmentActionsContainer = < {...attachment} actionHandler={actionHandler} actions={attachment.actions} - id={attachment.id || ''} + id={(attachment as LocalAttachment).localMetadata?.id || ''} text={attachment.text || ''} /> ); @@ -108,12 +99,10 @@ function getCssDimensionsVariables(url: string) { return cssVars; } -export const GalleryContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const GalleryContainer = ({ attachment, Gallery = DefaultGallery, -}: RenderGalleryProps) => { +}: RenderGalleryProps) => { const imageElements = useRef([]); const { imageAttachmentSizeHandler } = useChannelStateContext(); const [attachmentConfigurations, setAttachmentConfigurations] = useState< @@ -150,11 +139,7 @@ export const GalleryContainer = < ); }; -export const ImageContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: RenderAttachmentProps, -) => { +export const ImageContainer = (props: RenderAttachmentProps) => { const { attachment, Image = DefaultImage } = props; const componentType = 'image'; const imageElement = useRef(null); @@ -194,11 +179,7 @@ export const ImageContainer = < ); }; -export const CardContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: RenderAttachmentProps, -) => { +export const CardContainer = (props: RenderAttachmentProps) => { const { attachment, Card = DefaultCard } = props; const componentType = 'card'; @@ -220,12 +201,10 @@ export const CardContainer = < ); }; -export const FileContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const FileContainer = ({ attachment, File = DefaultFile, -}: RenderAttachmentProps) => { +}: RenderAttachmentProps) => { if (!attachment.asset_url) return null; return ( @@ -234,12 +213,10 @@ export const FileContainer = < ); }; -export const AudioContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const AudioContainer = ({ attachment, Audio = DefaultAudio, -}: RenderAttachmentProps) => ( +}: RenderAttachmentProps) => (