stream-chat-react-native-core
Version:
The official React Native and Expo components for Stream Chat, a service for building chat applications
574 lines (533 loc) • 17.4 kB
text/typescript
import { useMemo } from 'react';
import { Channel } from 'stream-chat';
import type { ActionHandler, ActionOptions } from './types';
import { useNotificationApi } from '../../components/Notifications/hooks';
import { useChatContext, useTranslationContext } from '../../contexts';
import { File, GlobalFileUploadRequest } from '../../types/types';
import { useStableCallback } from '../useStableCallback';
export type ChannelActionOptions = ActionOptions;
export type ChannelActionHandler = ActionHandler;
export type ChannelActions = {
addMembers: (memberIds: string[], options?: ChannelActionOptions) => Promise<void>;
removeMembers: (memberIds: string[], options?: ChannelActionOptions) => Promise<void>;
archive: ChannelActionHandler;
deleteChannel: ChannelActionHandler;
leave: ChannelActionHandler;
pin: ChannelActionHandler;
unarchive: ChannelActionHandler;
unpin: ChannelActionHandler;
updateImage: (
image: File | null,
options?: ChannelActionOptions,
doFileUploadRequest?: GlobalFileUploadRequest,
) => Promise<void>;
updateName: (name: string, options?: ChannelActionOptions) => Promise<void>;
muteUser: ChannelActionHandler;
unmuteUser: ChannelActionHandler;
muteChannel: ChannelActionHandler;
unmuteChannel: ChannelActionHandler;
blockUser: ChannelActionHandler;
unblockUser: ChannelActionHandler;
};
export const getOtherUserInDirectChannel = (channel: Channel) => {
const members = Object.values(channel.state.members);
return members.length === 2
? members.find((member) => member.user?.id !== channel.getClient().userID)
: undefined;
};
const getNotificationError = (error: unknown): Error | undefined => {
if (error instanceof Error) return error;
if (typeof error === 'string') return new Error(error);
if (error && typeof error === 'object' && 'message' in error) {
const message = error.message;
if (typeof message === 'string') return new Error(message);
}
return undefined;
};
export const getNotificationErrorOptions = (error: unknown) => {
const originalError = getNotificationError(error);
return originalError ? { originalError } : {};
};
export const useChannelActions = (channel: Channel) => {
const { client } = useChatContext();
const { addNotification } = useNotificationApi();
const { t } = useTranslationContext();
const ownUserId = client.userID;
const pin = useStableCallback(async (options?: ChannelActionOptions) => {
try {
if (!channel) {
return;
}
await channel.pin();
addNotification({
message: t('Channel pinned'),
options: { severity: 'success', type: 'api:channel:pin:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
} catch (error) {
addNotification({
message: t('Failed to update channel pinned status'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:channel:pin:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
});
const unpin = useStableCallback(async (options?: ChannelActionOptions) => {
try {
if (!channel) {
return;
}
await channel.unpin();
addNotification({
message: t('Channel unpinned'),
options: { severity: 'success', type: 'api:channel:unpin:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
} catch (error) {
addNotification({
message: t('Failed to update channel pinned status'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:channel:pin:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
});
const archive = useStableCallback(async (options?: ChannelActionOptions) => {
try {
if (!channel) {
return;
}
await channel.archive();
addNotification({
message: t('Channel archived'),
options: { severity: 'success', type: 'api:channel:archive:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
} catch (error) {
addNotification({
message: t('Failed to update channel archive status'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:channel:archive:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
});
const unarchive = useStableCallback(async (options?: ChannelActionOptions) => {
try {
if (!channel) {
return;
}
await channel.unarchive();
addNotification({
message: t('Channel unarchived'),
options: { severity: 'success', type: 'api:channel:unarchive:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
} catch (error) {
addNotification({
message: t('Failed to update channel archive status'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:channel:archive:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
});
const leave = useStableCallback(async (options?: ChannelActionOptions) => {
if (!channel) {
return;
}
if (ownUserId) {
try {
await channel.removeMembers([ownUserId]);
addNotification({
message: t('Left channel'),
options: { severity: 'success', type: 'api:channel:leave:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
} catch (error) {
addNotification({
message: t('Failed to leave channel'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:channel:leave:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
}
});
const removeMembers = useStableCallback(
async (memberIds: string[], options?: ChannelActionOptions) => {
if (!channel) {
return;
}
try {
await channel.removeMembers(memberIds);
addNotification({
message: t('{{count}} members removed', { count: memberIds.length }),
options: { severity: 'success', type: 'api:channel:remove-members:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
} catch (error) {
addNotification({
message: t('Failed to remove members'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:channel:remove-members:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
},
);
const addMembers = useStableCallback(
async (memberIds: string[], options?: ChannelActionOptions) => {
if (!channel) {
return;
}
try {
await channel.addMembers(memberIds);
addNotification({
message: t('{{count}} members added', { count: memberIds.length }),
options: { severity: 'success', type: 'api:channel:add-members:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
} catch (error) {
addNotification({
message: t('Failed to add members'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:channel:add-members:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
},
);
const deleteChannel = useStableCallback(async (options?: ChannelActionOptions) => {
if (!channel) {
return;
}
try {
await channel.delete();
addNotification({
message: t('Channel deleted'),
options: { severity: 'success', type: 'api:channel:delete:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
} catch (error) {
addNotification({
message: t('Failed to delete channel'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:channel:delete:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
});
const muteUser = useStableCallback(async (options?: ChannelActionOptions) => {
if (!channel) {
return;
}
const otherUser = getOtherUserInDirectChannel(channel);
try {
if (otherUser?.user?.id) {
await client.muteUser(otherUser.user.id);
addNotification({
message: t('{{ user }} has been muted', {
user: otherUser.user.name || otherUser.user.id,
}),
options: { severity: 'success', type: 'api:user:mute:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
}
} catch (error) {
addNotification({
message: t('Error muting a user ...'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:user:mute:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
});
const unmuteUser = useStableCallback(async (options?: ChannelActionOptions) => {
if (!channel) {
return;
}
const otherUser = getOtherUserInDirectChannel(channel);
try {
if (otherUser?.user?.id) {
await client.unmuteUser(otherUser.user.id);
addNotification({
message: t('{{ user }} has been unmuted', {
user: otherUser.user.name || otherUser.user.id,
}),
options: { severity: 'success', type: 'api:user:unmute:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
}
} catch (error) {
addNotification({
message: t('Error unmuting a user ...'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:user:unmute:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
});
const muteChannel = useStableCallback(async (options?: ChannelActionOptions) => {
if (!channel) {
return;
}
try {
await channel.mute();
addNotification({
message: t('Channel muted'),
options: { severity: 'success', type: 'api:channel:mute:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
} catch (error) {
addNotification({
message: t('Failed to update channel mute status'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:channel:mute:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
});
const unmuteChannel = useStableCallback(async (options?: ChannelActionOptions) => {
if (!channel) {
return;
}
try {
await channel.unmute();
addNotification({
message: t('Channel unmuted'),
options: { severity: 'success', type: 'api:channel:unmute:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
} catch (error) {
addNotification({
message: t('Failed to update channel mute status'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:channel:mute:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
});
const blockUser = useStableCallback(async (options?: ChannelActionOptions) => {
if (!channel) {
return;
}
const otherUser = getOtherUserInDirectChannel(channel);
try {
if (otherUser?.user?.id) {
await client.blockUser(otherUser.user.id);
addNotification({
message: t('User blocked'),
options: { severity: 'success', type: 'api:user:block:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
}
} catch (error) {
addNotification({
message: t('Failed to block user'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:user:block:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
});
const updateName = useStableCallback(async (name: string, options?: ChannelActionOptions) => {
if (!channel) {
return;
}
try {
if (name.trim() === '') {
await channel.updatePartial({ unset: ['name'] });
} else {
await channel.updatePartial({ set: { name } });
}
addNotification({
message: t('Channel name updated'),
options: { severity: 'success', type: 'api:channel:update-name:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
} catch (error) {
addNotification({
message: t('Failed to update channel name'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:channel:update-name:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
});
const updateImage = useStableCallback(
async (
image: File | null,
options?: ChannelActionOptions,
doFileUploadRequest?: GlobalFileUploadRequest,
) => {
if (!channel) {
return;
}
try {
if (image === null) {
await channel.updatePartial({ unset: ['image'] });
} else {
const { file } = doFileUploadRequest
? await doFileUploadRequest(image)
: await client.uploadImage(image.uri, image.name, image.type);
await channel.updatePartial({ set: { image: file } });
}
addNotification({
message: t('Channel image updated'),
options: { severity: 'success', type: 'api:channel:update-image:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
} catch (error) {
addNotification({
message: t('Failed to update channel image'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:channel:update-image:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
},
);
const unblockUser = useStableCallback(async (options?: ChannelActionOptions) => {
if (!channel) {
return;
}
const otherUser = getOtherUserInDirectChannel(channel);
try {
if (otherUser?.user?.id) {
await client.unBlockUser(otherUser.user.id);
addNotification({
message: t('User unblocked'),
options: { severity: 'success', type: 'api:user:unblock:success' },
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onSuccess?.();
}
} catch (error) {
addNotification({
message: t('Failed to block user'),
options: {
...getNotificationErrorOptions(error),
severity: 'error',
type: 'api:user:block:failed',
},
origin: { context: { channel }, emitter: 'ChannelActions' },
});
options?.onFailure?.(error);
}
});
return useMemo<ChannelActions>(
() => ({
addMembers,
removeMembers,
pin,
unpin,
archive,
unarchive,
leave,
deleteChannel,
updateName,
updateImage,
muteUser,
unmuteUser,
muteChannel,
unmuteChannel,
blockUser,
unblockUser,
}),
[
addMembers,
removeMembers,
pin,
unpin,
archive,
unarchive,
leave,
deleteChannel,
updateName,
updateImage,
muteUser,
unmuteUser,
muteChannel,
unmuteChannel,
blockUser,
unblockUser,
],
);
};