@sendbird/uikit-utils 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/lib/commonjs/hooks/index.js +88 -0
- package/lib/commonjs/hooks/index.js.map +1 -0
- package/lib/commonjs/index.js +223 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/sendbird/attrs.js +57 -0
- package/lib/commonjs/sendbird/attrs.js.map +1 -0
- package/lib/commonjs/sendbird/channel.js +53 -0
- package/lib/commonjs/sendbird/channel.js.map +1 -0
- package/lib/commonjs/sendbird/message.js +111 -0
- package/lib/commonjs/sendbird/message.js.map +1 -0
- package/lib/commonjs/shared/arrayToMap.js +54 -0
- package/lib/commonjs/shared/arrayToMap.js.map +1 -0
- package/lib/commonjs/shared/conditionChaining.js +15 -0
- package/lib/commonjs/shared/conditionChaining.js.map +1 -0
- package/lib/commonjs/shared/logger.js +98 -0
- package/lib/commonjs/shared/logger.js.map +1 -0
- package/lib/commonjs/shared/pubsub.js +54 -0
- package/lib/commonjs/shared/pubsub.js.map +1 -0
- package/lib/commonjs/shared/regex.js +102 -0
- package/lib/commonjs/shared/regex.js.map +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/ui-format/common.js +106 -0
- package/lib/commonjs/ui-format/common.js.map +1 -0
- package/lib/commonjs/ui-format/groupChannel.js +80 -0
- package/lib/commonjs/ui-format/groupChannel.js.map +1 -0
- package/lib/module/hooks/index.js +60 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/index.js +15 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/sendbird/attrs.js +49 -0
- package/lib/module/sendbird/attrs.js.map +1 -0
- package/lib/module/sendbird/channel.js +36 -0
- package/lib/module/sendbird/channel.js.map +1 -0
- package/lib/module/sendbird/message.js +85 -0
- package/lib/module/sendbird/message.js.map +1 -0
- package/lib/module/shared/arrayToMap.js +45 -0
- package/lib/module/shared/arrayToMap.js.map +1 -0
- package/lib/module/shared/conditionChaining.js +7 -0
- package/lib/module/shared/conditionChaining.js.map +1 -0
- package/lib/module/shared/logger.js +90 -0
- package/lib/module/shared/logger.js.map +1 -0
- package/lib/module/shared/pubsub.js +45 -0
- package/lib/module/shared/pubsub.js.map +1 -0
- package/lib/module/shared/regex.js +78 -0
- package/lib/module/shared/regex.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/ui-format/common.js +85 -0
- package/lib/module/ui-format/common.js.map +1 -0
- package/lib/module/ui-format/groupChannel.js +59 -0
- package/lib/module/ui-format/groupChannel.js.map +1 -0
- package/lib/typescript/src/hooks/index.d.ts +11 -0
- package/lib/typescript/src/index.d.ts +15 -0
- package/lib/typescript/src/sendbird/attrs.d.ts +43 -0
- package/lib/typescript/src/sendbird/channel.d.ts +18 -0
- package/lib/typescript/src/sendbird/message.d.ts +20 -0
- package/lib/typescript/src/shared/arrayToMap.d.ts +5 -0
- package/lib/typescript/src/shared/conditionChaining.d.ts +2 -0
- package/lib/typescript/src/shared/logger.d.ts +38 -0
- package/lib/typescript/src/shared/pubsub.d.ts +8 -0
- package/lib/typescript/src/shared/regex.d.ts +11 -0
- package/lib/typescript/src/types.d.ts +79 -0
- package/lib/typescript/src/ui-format/common.d.ts +43 -0
- package/lib/typescript/src/ui-format/groupChannel.d.ts +5 -0
- package/package.json +75 -0
- package/src/hooks/index.ts +65 -0
- package/src/index.ts +57 -0
- package/src/sendbird/attrs.ts +44 -0
- package/src/sendbird/channel.ts +37 -0
- package/src/sendbird/message.ts +101 -0
- package/src/shared/arrayToMap.ts +70 -0
- package/src/shared/conditionChaining.ts +6 -0
- package/src/shared/logger.ts +59 -0
- package/src/shared/pubsub.ts +42 -0
- package/src/shared/regex.ts +74 -0
- package/src/types.ts +101 -0
- package/src/ui-format/common.ts +73 -0
- package/src/ui-format/groupChannel.ts +47 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export { Logger } from './shared/logger';
|
|
2
|
+
export { default as arrayToMap, arrayToMapWithGetter } from './shared/arrayToMap';
|
|
3
|
+
export { createPubSub } from './shared/pubsub';
|
|
4
|
+
export { default as conditionChaining } from './shared/conditionChaining';
|
|
5
|
+
export {
|
|
6
|
+
urlRegexStrict,
|
|
7
|
+
urlRegexRough,
|
|
8
|
+
newLineRegex,
|
|
9
|
+
audioExtRegex,
|
|
10
|
+
videoExtRegex,
|
|
11
|
+
imageExtRegex,
|
|
12
|
+
getFileType,
|
|
13
|
+
getFileExtension,
|
|
14
|
+
replaceUrlAsComponents,
|
|
15
|
+
} from './shared/regex';
|
|
16
|
+
|
|
17
|
+
export * from './hooks';
|
|
18
|
+
export * from './ui-format/groupChannel';
|
|
19
|
+
export * from './ui-format/common';
|
|
20
|
+
export * from './sendbird/channel';
|
|
21
|
+
export * from './sendbird/message';
|
|
22
|
+
export * from './sendbird/attrs';
|
|
23
|
+
|
|
24
|
+
export const NOOP: () => void = () => void 0;
|
|
25
|
+
export const ASYNC_NOOP = async () => void 0;
|
|
26
|
+
export const PASS = <T>(val: T) => val;
|
|
27
|
+
export type {
|
|
28
|
+
FilterByValueType,
|
|
29
|
+
UnionToIntersection,
|
|
30
|
+
OmittedValues,
|
|
31
|
+
PartialDeep,
|
|
32
|
+
Optional,
|
|
33
|
+
ContextValue,
|
|
34
|
+
SendbirdMessage,
|
|
35
|
+
SendbirdChatSDK,
|
|
36
|
+
SendbirdChannel,
|
|
37
|
+
SendbirdBaseChannel,
|
|
38
|
+
SendbirdGroupChannel,
|
|
39
|
+
SendbirdOpenChannel,
|
|
40
|
+
SendbirdUser,
|
|
41
|
+
SendbirdMember,
|
|
42
|
+
SendbirdBaseMessage,
|
|
43
|
+
SendbirdUserMessage,
|
|
44
|
+
SendbirdFileMessage,
|
|
45
|
+
SendbirdAdminMessage,
|
|
46
|
+
SendbirdGroupChannelParams,
|
|
47
|
+
SendbirdFileMessageParams,
|
|
48
|
+
SendbirdUserMessageParams,
|
|
49
|
+
SendbirdError,
|
|
50
|
+
SendbirdMessageCollection,
|
|
51
|
+
SendbirdGroupChannelCollection,
|
|
52
|
+
NotificationFiles,
|
|
53
|
+
NotificationMentionedUsers,
|
|
54
|
+
NotificationTranslations,
|
|
55
|
+
PartialNullable,
|
|
56
|
+
SendbirdDataPayload,
|
|
57
|
+
} from './types';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export enum ApplicationAttributes {
|
|
2
|
+
reactions = 'reactions',
|
|
3
|
+
message_search_v3 = 'message_search_v3',
|
|
4
|
+
enable_og_tag = 'enable_og_tag',
|
|
5
|
+
use_last_seen_at = 'use_last_seen_at',
|
|
6
|
+
enable_message_threading = 'enable_message_threading',
|
|
7
|
+
allow_message_update_from_sdk = 'allow_message_update_from_sdk',
|
|
8
|
+
allow_group_channel_create_from_sdk = 'allow_group_channel_create_from_sdk',
|
|
9
|
+
allow_migration_api = 'allow_migration_api',
|
|
10
|
+
allow_only_operator_sdk_to_update_group_channel_image = 'allow_only_operator_sdk_to_update_group_channel_image',
|
|
11
|
+
allow_user_update_from_sdk = 'allow_user_update_from_sdk',
|
|
12
|
+
allow_broadcast_channel = 'allow_broadcast_channel',
|
|
13
|
+
allow_sdk_file_message_urls = 'allow_sdk_file_message_urls',
|
|
14
|
+
allow_group_channel_leave_from_sdk = 'allow_group_channel_leave_from_sdk',
|
|
15
|
+
allow_user_list_from_sdk = 'allow_user_list_from_sdk',
|
|
16
|
+
allow_group_channel_invite_from_sdk = 'allow_group_channel_invite_from_sdk',
|
|
17
|
+
allow_only_operator_sdk_to_update_group_channel_name = 'allow_only_operator_sdk_to_update_group_channel_name',
|
|
18
|
+
allow_group_channel_update_from_sdk = 'allow_group_channel_update_from_sdk',
|
|
19
|
+
allow_super_group_channel = 'allow_super_group_channel',
|
|
20
|
+
allow_non_operators_to_mention_channel = 'allow_non_operators_to_mention_channel',
|
|
21
|
+
allow_file_messages_from_sdk = 'allow_file_messages_from_sdk',
|
|
22
|
+
allow_open_channel_create_from_sdk = 'allow_open_channel_create_from_sdk',
|
|
23
|
+
allow_anonymous_message_update = 'allow_anonymous_message_update',
|
|
24
|
+
allow_group_channel_delete_from_sdk = 'allow_group_channel_delete_from_sdk',
|
|
25
|
+
allow_operators_to_ban_operators = 'allow_operators_to_ban_operators',
|
|
26
|
+
allow_operators_to_edit_operators = 'allow_operators_to_edit_operators',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export enum PremiumFeatures {
|
|
30
|
+
reactions = 'reactions',
|
|
31
|
+
moderation_group = 'moderation_group',
|
|
32
|
+
message_search_v3 = 'message_search_v3',
|
|
33
|
+
data_export = 'data_export',
|
|
34
|
+
migration = 'migration',
|
|
35
|
+
delivery_receipt = 'delivery_receipt',
|
|
36
|
+
auto_thumbnail = 'auto_thumbnail',
|
|
37
|
+
image_moderation = 'image_moderation',
|
|
38
|
+
super_group_channel = 'super_group_channel',
|
|
39
|
+
announcement = 'announcement',
|
|
40
|
+
moderation_open = 'moderation_open',
|
|
41
|
+
desk = 'desk',
|
|
42
|
+
bot_interface = 'bot_interface',
|
|
43
|
+
auto_trans = 'auto_trans',
|
|
44
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SendbirdBaseChannel, SendbirdGroupChannel } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default sort comparator for channel
|
|
5
|
+
* @param {SendbirdBaseChannel} a
|
|
6
|
+
* @param {SendbirdBaseChannel} b
|
|
7
|
+
* @returns {number}
|
|
8
|
+
* */
|
|
9
|
+
export function channelComparator<T extends SendbirdBaseChannel>(a: T, b: T): number {
|
|
10
|
+
if (a.isGroupChannel() && b.isGroupChannel()) {
|
|
11
|
+
return (b.lastMessage?.createdAt ?? b.createdAt) - (a.lastMessage?.createdAt ?? a.createdAt);
|
|
12
|
+
}
|
|
13
|
+
return b.createdAt - a.createdAt;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Diff utils for channel
|
|
18
|
+
* @param {SendbirdBaseChannel} [a]
|
|
19
|
+
* @param {SendbirdBaseChannel} [b]
|
|
20
|
+
* @returns {boolean}
|
|
21
|
+
* */
|
|
22
|
+
export function isDifferentChannel<T extends SendbirdBaseChannel>(a?: T, b?: T): boolean {
|
|
23
|
+
if (!a || !b) return true;
|
|
24
|
+
return a.url !== b.url;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const groupChannelChatUnavailable = (channel: SendbirdGroupChannel) => {
|
|
28
|
+
return channel.myMutedState === 'muted' || (channel.isFrozen && channel.myRole !== 'operator');
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function isDefaultCoverImage(coverUrl: string) {
|
|
32
|
+
return coverUrl === '' || coverUrl.startsWith('https://static.sendbird.com/sample/cover');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getMembersExcludeMe(channel: SendbirdGroupChannel, currentUserId?: string) {
|
|
36
|
+
return channel.members.filter((m) => m.userId !== currentUserId);
|
|
37
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { SendbirdDataPayload, SendbirdFileMessage, SendbirdMessage } from '../types';
|
|
2
|
+
import { messageTime } from '../ui-format/common';
|
|
3
|
+
|
|
4
|
+
export function isNewMessage(msg: SendbirdMessage, currentUserId?: string) {
|
|
5
|
+
const myMessage = isMyMessage(msg, currentUserId);
|
|
6
|
+
if (myMessage) return false;
|
|
7
|
+
if (msg.isAdminMessage()) return false;
|
|
8
|
+
return msg.updatedAt === 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isMyMessage(msg: SendbirdMessage, currentUserId = '##__USER_ID_IS_NOT_PROVIDED__##') {
|
|
12
|
+
return (
|
|
13
|
+
('sender' in msg && msg.sender?.userId === currentUserId) ||
|
|
14
|
+
msg.sendingStatus === 'pending' ||
|
|
15
|
+
msg.sendingStatus === 'failed' ||
|
|
16
|
+
msg.sendingStatus === 'canceled'
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function messageKeyExtractor(message: SendbirdMessage): string {
|
|
21
|
+
return getMessageUniqId(message);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// |-------------------|-------------------|-----------------|-------------------|
|
|
25
|
+
// | sending status | reqId | messageId | createdAt |
|
|
26
|
+
// |-------------------|-------------------|-----------------|-------------------|
|
|
27
|
+
// | pending | timestamp(A) | 0 | timestamp(B) |
|
|
28
|
+
// | canceled | timestamp(A) | 0 | ? |
|
|
29
|
+
// | failed | timestamp(A) | 0 | ? |
|
|
30
|
+
// | succeeded | timestamp(A) / '' | id from DB | timestamp(C) |
|
|
31
|
+
// |-------------------|-------------------|-----------------|-------------------|
|
|
32
|
+
export function messageComparator<T extends SendbirdMessage>(a: T, b: T) {
|
|
33
|
+
let aStatusOffset = 0;
|
|
34
|
+
let bStatusOffset = 0;
|
|
35
|
+
|
|
36
|
+
if (a.sendingStatus !== 'succeeded') aStatusOffset = 999999;
|
|
37
|
+
if (b.sendingStatus !== 'succeeded') bStatusOffset = 999999;
|
|
38
|
+
|
|
39
|
+
return b.createdAt + bStatusOffset - (a.createdAt + aStatusOffset);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function hasSameSender(a?: SendbirdMessage, b?: SendbirdMessage) {
|
|
43
|
+
if (!a || !b) return false;
|
|
44
|
+
if ('sender' in a && 'sender' in b) return a.sender?.userId === b.sender?.userId;
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function calcMessageGrouping(
|
|
49
|
+
groupEnabled: boolean,
|
|
50
|
+
curr: SendbirdMessage,
|
|
51
|
+
prev?: SendbirdMessage,
|
|
52
|
+
next?: SendbirdMessage,
|
|
53
|
+
) {
|
|
54
|
+
const getPrev = () => {
|
|
55
|
+
if (!groupEnabled) return false;
|
|
56
|
+
if (!prev) return false;
|
|
57
|
+
if (curr.isAdminMessage()) return false;
|
|
58
|
+
if (!hasSameSender(curr, prev)) return false;
|
|
59
|
+
if (messageTime(new Date(curr.createdAt)) !== messageTime(new Date(prev.createdAt))) return false;
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const getNext = () => {
|
|
64
|
+
if (!groupEnabled) return false;
|
|
65
|
+
if (!next) return false;
|
|
66
|
+
if (curr.isAdminMessage()) return false;
|
|
67
|
+
if (!hasSameSender(curr, next)) return false;
|
|
68
|
+
if (messageTime(new Date(curr.createdAt)) !== messageTime(new Date(next.createdAt))) return false;
|
|
69
|
+
return true;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return { groupWithPrev: getPrev(), groupWithNext: getNext() };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getMessageUniqId(msg: SendbirdMessage) {
|
|
76
|
+
if (msg.isUserMessage() || msg.isFileMessage()) {
|
|
77
|
+
if (msg.sendingStatus === 'succeeded') return String(msg.messageId);
|
|
78
|
+
return msg.reqId;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return String(msg.messageId);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getAvailableUriFromFileMessage(message: SendbirdFileMessage) {
|
|
85
|
+
if (!message.url && message.messageParams && 'uri' in message.messageParams.file) {
|
|
86
|
+
return message.messageParams.file.uri;
|
|
87
|
+
}
|
|
88
|
+
return message.url;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type RawSendbirdDataPayload = { sendbird: string | object };
|
|
92
|
+
export function isSendbirdNotification(dataPayload?: {
|
|
93
|
+
[key: string]: string | object;
|
|
94
|
+
}): dataPayload is RawSendbirdDataPayload {
|
|
95
|
+
if (!dataPayload) return false;
|
|
96
|
+
return Boolean(dataPayload['sendbird']);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function parseSendbirdNotification(dataPayload: RawSendbirdDataPayload): SendbirdDataPayload {
|
|
100
|
+
return typeof dataPayload.sendbird === 'string' ? JSON.parse(dataPayload.sendbird) : dataPayload.sendbird;
|
|
101
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { FilterByValueType } from '../types';
|
|
2
|
+
|
|
3
|
+
export default function arrayToMap<T extends Record<K, unknown>, K extends keyof T = keyof T>(
|
|
4
|
+
arr: T[],
|
|
5
|
+
selector: keyof FilterByValueType<T, string | number>,
|
|
6
|
+
): Record<string, T>;
|
|
7
|
+
|
|
8
|
+
export default function arrayToMap<T extends Record<K, unknown>, K extends keyof T = keyof T>(
|
|
9
|
+
arr: T[],
|
|
10
|
+
selector: string,
|
|
11
|
+
fallbackSelector: K,
|
|
12
|
+
): Record<string, T>;
|
|
13
|
+
|
|
14
|
+
export default function arrayToMap<T extends Record<K, unknown>, K extends keyof T = keyof T>(
|
|
15
|
+
arr: T[],
|
|
16
|
+
selector: string,
|
|
17
|
+
selector2: string,
|
|
18
|
+
combineKey: true,
|
|
19
|
+
): Record<string, T>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Convert Array of the object to Object
|
|
23
|
+
* @param {object[]} arr
|
|
24
|
+
* @param {keyof object} selector
|
|
25
|
+
* @param {keyof object} [fallbackSelector]
|
|
26
|
+
* @param {boolean} [combine]
|
|
27
|
+
* @returns {Record<keyof object, object>}
|
|
28
|
+
* @example
|
|
29
|
+
* ```
|
|
30
|
+
* const arr = [{name: 'john'}, {name: 'chris'}, {name: 'sravan'}, {name: 'hoon'}];
|
|
31
|
+
* const obj = arrayToMap(arr, 'name');
|
|
32
|
+
*
|
|
33
|
+
* console.log(obj);
|
|
34
|
+
* // {
|
|
35
|
+
* // 'john': {name: 'john'},
|
|
36
|
+
* // 'chris': {name: 'chris'},
|
|
37
|
+
* // 'sravan': {name: 'sravan'},
|
|
38
|
+
* // 'hoon': {name: 'hoon'}
|
|
39
|
+
* // }
|
|
40
|
+
* ```
|
|
41
|
+
* */
|
|
42
|
+
export default function arrayToMap<T extends Record<K, unknown>, K extends keyof T = keyof T>(
|
|
43
|
+
arr: T[],
|
|
44
|
+
selector: K,
|
|
45
|
+
fallbackSelector?: K,
|
|
46
|
+
combine?: boolean,
|
|
47
|
+
) {
|
|
48
|
+
return arr.reduce((accum, curr) => {
|
|
49
|
+
if (combine && fallbackSelector) {
|
|
50
|
+
const _key = curr[selector] + (curr[fallbackSelector] as string);
|
|
51
|
+
accum[_key] = curr;
|
|
52
|
+
} else {
|
|
53
|
+
const _key = (curr[selector] || curr[fallbackSelector as K]) as string;
|
|
54
|
+
accum[_key] = curr;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return accum;
|
|
58
|
+
}, {} as Record<string, T>);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function arrayToMapWithGetter<T extends Record<K, unknown>, K extends keyof T = keyof T>(
|
|
62
|
+
arr: T[],
|
|
63
|
+
getSelector: (item: T) => string,
|
|
64
|
+
) {
|
|
65
|
+
return arr.reduce((accum, curr) => {
|
|
66
|
+
const _key = getSelector(curr);
|
|
67
|
+
accum[_key] = curr;
|
|
68
|
+
return accum;
|
|
69
|
+
}, {} as Record<string, T>);
|
|
70
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
const LogLevelEnum = {
|
|
4
|
+
'none': 0,
|
|
5
|
+
'log': 1,
|
|
6
|
+
'error': 2,
|
|
7
|
+
'warn': 3,
|
|
8
|
+
'info': 4,
|
|
9
|
+
'debug': 5,
|
|
10
|
+
};
|
|
11
|
+
type LogLevel = keyof typeof LogLevelEnum;
|
|
12
|
+
|
|
13
|
+
const createLogger = (level: LogLevel = 'warn') => {
|
|
14
|
+
let _logLevel = level;
|
|
15
|
+
let _title = '[UIKit]';
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
setTitle(title: string) {
|
|
19
|
+
_title = title;
|
|
20
|
+
},
|
|
21
|
+
setLogLevel(lv: LogLevel) {
|
|
22
|
+
_logLevel = lv;
|
|
23
|
+
},
|
|
24
|
+
getLogLevel() {
|
|
25
|
+
return _logLevel;
|
|
26
|
+
},
|
|
27
|
+
log(...args: unknown[]) {
|
|
28
|
+
if (LogLevelEnum[_logLevel] < LogLevelEnum.log) return LogLevelEnum.none;
|
|
29
|
+
console.log(_title, ...args);
|
|
30
|
+
return LogLevelEnum[_logLevel];
|
|
31
|
+
},
|
|
32
|
+
error(...args: unknown[]) {
|
|
33
|
+
if (LogLevelEnum[_logLevel] < LogLevelEnum.error) return LogLevelEnum.none;
|
|
34
|
+
console.error(_title, ...args);
|
|
35
|
+
return LogLevelEnum[_logLevel];
|
|
36
|
+
},
|
|
37
|
+
warn(...args: unknown[]) {
|
|
38
|
+
if (LogLevelEnum[_logLevel] < LogLevelEnum.warn) return LogLevelEnum.none;
|
|
39
|
+
console.warn(_title, ...args);
|
|
40
|
+
return LogLevelEnum[_logLevel];
|
|
41
|
+
},
|
|
42
|
+
info(...args: unknown[]) {
|
|
43
|
+
if (LogLevelEnum[_logLevel] < LogLevelEnum.info) return LogLevelEnum.none;
|
|
44
|
+
console.info(_title, ...args);
|
|
45
|
+
return LogLevelEnum[_logLevel];
|
|
46
|
+
},
|
|
47
|
+
debug(...args: unknown[]) {
|
|
48
|
+
if (LogLevelEnum[_logLevel] < LogLevelEnum.debug) return LogLevelEnum.none;
|
|
49
|
+
console.log(_title, ...args);
|
|
50
|
+
return LogLevelEnum[_logLevel];
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const Logger = {
|
|
56
|
+
...createLogger(),
|
|
57
|
+
LogLevelEnum,
|
|
58
|
+
create: createLogger,
|
|
59
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type SubscribeListener = ((...args: unknown[]) => void) & { onError?: (error: unknown) => void };
|
|
2
|
+
export type SubscribeListenerPool = Record<symbol, SubscribeListener>;
|
|
3
|
+
|
|
4
|
+
export const createPubSub = (label = 'pubsub') => {
|
|
5
|
+
const eventPool: Record<string, SubscribeListenerPool> = {};
|
|
6
|
+
|
|
7
|
+
const getSubscribeListenerPool = (event: string) => {
|
|
8
|
+
if (!eventPool[event]) eventPool[event] = {};
|
|
9
|
+
return eventPool[event];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
subscribe: (event: string, listener: SubscribeListener, subscriber = '') => {
|
|
14
|
+
const name = `${label}_${event}_${subscriber}`;
|
|
15
|
+
const id = Symbol(name);
|
|
16
|
+
|
|
17
|
+
const subscribeListenerPool = getSubscribeListenerPool(event);
|
|
18
|
+
subscribeListenerPool[id] = listener;
|
|
19
|
+
|
|
20
|
+
return () => {
|
|
21
|
+
delete getSubscribeListenerPool(event)[id];
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
publish: (event: string, ...args: unknown[]) => {
|
|
25
|
+
const subscribeListenerPool = getSubscribeListenerPool(event);
|
|
26
|
+
const ids = Object.getOwnPropertySymbols(subscribeListenerPool);
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
ids.forEach((id) => {
|
|
29
|
+
const listener = subscribeListenerPool[id];
|
|
30
|
+
try {
|
|
31
|
+
listener(...args);
|
|
32
|
+
} catch (err: unknown) {
|
|
33
|
+
if (listener.onError) {
|
|
34
|
+
(err as Error).stack += `\npubsub error: ${id.description}`;
|
|
35
|
+
listener.onError(err);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}, 0);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// For iOS 12 backwards compatibility
|
|
2
|
+
require('string.prototype.matchall').shim();
|
|
3
|
+
|
|
4
|
+
export const urlRegexStrict =
|
|
5
|
+
/(https?:\/\/|www\.)[-a-zA-Z0-9@:%._+~#=]{1,256}\.(xn--)?[a-z0-9-]{2,20}\b([-a-zA-Z0-9@:%_+[\],.~#?&/=]*[-a-zA-Z0-9@:%_+\]~#?&/=])*/g;
|
|
6
|
+
export const urlRegexRough =
|
|
7
|
+
/(https?:\/\/|www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/g;
|
|
8
|
+
export const phoneRegex = /[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,7}/;
|
|
9
|
+
export const emailRegex = /\S+@\S+\.\S+/;
|
|
10
|
+
export const newLineRegex = /\r\n|\r|\n/g;
|
|
11
|
+
|
|
12
|
+
// const cases = [
|
|
13
|
+
// {
|
|
14
|
+
// type: 'urlStrict',
|
|
15
|
+
// regex: urlRegexStrict,
|
|
16
|
+
// },
|
|
17
|
+
// {
|
|
18
|
+
// type: 'urlRough',
|
|
19
|
+
// regex: urlRegexStrict,
|
|
20
|
+
// },
|
|
21
|
+
// {
|
|
22
|
+
// type: 'email',
|
|
23
|
+
// regex: emailRegex,
|
|
24
|
+
// },
|
|
25
|
+
// {
|
|
26
|
+
// type: 'phone',
|
|
27
|
+
// regex: urlRegexStrict,
|
|
28
|
+
// },
|
|
29
|
+
// ];
|
|
30
|
+
|
|
31
|
+
export const replaceUrlAsComponents = <T>(originText: string, replacer: (url: string) => T, strict?: boolean) => {
|
|
32
|
+
const matches = [...originText.matchAll(strict ? urlRegexStrict : urlRegexRough)];
|
|
33
|
+
const founds = matches.map((value) => {
|
|
34
|
+
const text = value[0];
|
|
35
|
+
const start = value.index ?? 0;
|
|
36
|
+
const end = start + text.length;
|
|
37
|
+
return { text, start, end };
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const items: Array<string | T> = [originText];
|
|
41
|
+
let cursor = 0;
|
|
42
|
+
founds.forEach(({ text, start, end }) => {
|
|
43
|
+
const restText = items.pop() as string;
|
|
44
|
+
const head = restText.slice(0, start - cursor);
|
|
45
|
+
const mid = replacer(text);
|
|
46
|
+
const tail = restText.slice(end - cursor);
|
|
47
|
+
items.push(head, mid, tail);
|
|
48
|
+
cursor = end;
|
|
49
|
+
});
|
|
50
|
+
return items;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const imageExtRegex = /jpeg|jpg|png|webp|gif/i;
|
|
54
|
+
export const audioExtRegex = /3gp|aac|aax|act|aiff|flac|gsm|m4a|m4b|m4p|tta|wma|mp3|webm|wav/i;
|
|
55
|
+
export const videoExtRegex = /mov|vod|mp4|avi/i;
|
|
56
|
+
export const getFileType = (extOrType: string) => {
|
|
57
|
+
if (extOrType.indexOf('/') > -1) {
|
|
58
|
+
const type = extOrType.split('/')[0];
|
|
59
|
+
if (type === 'video') return 'video';
|
|
60
|
+
if (type === 'audio') return 'audio';
|
|
61
|
+
if (type === 'image') return 'image';
|
|
62
|
+
return 'file';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (extOrType.match(imageExtRegex)) return 'image';
|
|
66
|
+
if (extOrType.match(audioExtRegex)) return 'audio';
|
|
67
|
+
if (extOrType.match(videoExtRegex)) return 'video';
|
|
68
|
+
return 'file';
|
|
69
|
+
};
|
|
70
|
+
export function getFileExtension(filePath: string) {
|
|
71
|
+
const idx = filePath.lastIndexOf('.');
|
|
72
|
+
if (idx === -1) return '';
|
|
73
|
+
return filePath.slice(idx - filePath.length).toLowerCase();
|
|
74
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-empty-interface */
|
|
4
|
+
import type React from 'react';
|
|
5
|
+
import type Sendbird from 'sendbird';
|
|
6
|
+
|
|
7
|
+
export type FilterByValueType<T extends object, Type> = {
|
|
8
|
+
[K in keyof T as T[K] extends Type ? K : never]: T[K];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void
|
|
12
|
+
? I
|
|
13
|
+
: never;
|
|
14
|
+
|
|
15
|
+
export type OmittedValues<T, K extends keyof T> = Omit<T, K>[keyof Omit<T, K>];
|
|
16
|
+
|
|
17
|
+
export type PartialDeep<T> = T extends object
|
|
18
|
+
? T extends Function
|
|
19
|
+
? T
|
|
20
|
+
: {
|
|
21
|
+
[P in keyof T]?: PartialDeep<T[P]>;
|
|
22
|
+
}
|
|
23
|
+
: T;
|
|
24
|
+
|
|
25
|
+
export type PartialNullable<T> = {
|
|
26
|
+
[P in keyof T]?: T[P] | null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type Optional<T> = T | undefined;
|
|
30
|
+
|
|
31
|
+
export type ContextValue<T extends React.Context<any>> = T extends React.Context<infer V> ? V : never;
|
|
32
|
+
|
|
33
|
+
// for v4 migration
|
|
34
|
+
export type SendbirdChatSDK = Sendbird.SendBirdInstance;
|
|
35
|
+
export type SendbirdMessage =
|
|
36
|
+
| Sendbird.BaseMessageInstance
|
|
37
|
+
| Sendbird.FileMessage
|
|
38
|
+
| Sendbird.UserMessage
|
|
39
|
+
| Sendbird.AdminMessage;
|
|
40
|
+
export type SendbirdChannel = Sendbird.BaseChannel | Sendbird.GroupChannel | Sendbird.OpenChannel;
|
|
41
|
+
export type SendbirdUserMessage = Sendbird.UserMessage;
|
|
42
|
+
export type SendbirdFileMessage = Sendbird.FileMessage;
|
|
43
|
+
export type SendbirdAdminMessage = Sendbird.AdminMessage;
|
|
44
|
+
export type SendbirdBaseMessage = Sendbird.BaseMessageInstance;
|
|
45
|
+
export type SendbirdFileMessageParams = Sendbird.FileMessageParams;
|
|
46
|
+
export type SendbirdUserMessageParams = Sendbird.UserMessageParams;
|
|
47
|
+
export type SendbirdError = Sendbird.SendBirdError;
|
|
48
|
+
export type SendbirdGroupChannelParams = Sendbird.GroupChannelParams;
|
|
49
|
+
export type SendbirdUser = Sendbird.User;
|
|
50
|
+
export type SendbirdMember = Sendbird.Member;
|
|
51
|
+
export type SendbirdGroupChannel = Sendbird.GroupChannel;
|
|
52
|
+
export type SendbirdBaseChannel = Sendbird.BaseChannel;
|
|
53
|
+
export type SendbirdOpenChannel = Sendbird.OpenChannel;
|
|
54
|
+
export type SendbirdGroupChannelCollection = Sendbird.GroupChannelCollection;
|
|
55
|
+
export type SendbirdMessageCollection = Sendbird.MessageCollection;
|
|
56
|
+
|
|
57
|
+
export interface SendbirdDataPayload {
|
|
58
|
+
app_id: string;
|
|
59
|
+
audience_type: string;
|
|
60
|
+
category: string;
|
|
61
|
+
channel: {
|
|
62
|
+
channel_unread_message_count: number;
|
|
63
|
+
channel_url: string;
|
|
64
|
+
custom_type: string;
|
|
65
|
+
name: string;
|
|
66
|
+
};
|
|
67
|
+
channel_type: string;
|
|
68
|
+
created_at: number;
|
|
69
|
+
custom_type: string;
|
|
70
|
+
files: NotificationFiles[];
|
|
71
|
+
mentioned_users: NotificationMentionedUsers[];
|
|
72
|
+
message: string;
|
|
73
|
+
message_id: number;
|
|
74
|
+
push_sound: string;
|
|
75
|
+
push_title: null;
|
|
76
|
+
recipient: {
|
|
77
|
+
id: string;
|
|
78
|
+
name: string;
|
|
79
|
+
push_template: string;
|
|
80
|
+
};
|
|
81
|
+
sender?: {
|
|
82
|
+
id: string;
|
|
83
|
+
name: string;
|
|
84
|
+
profile_url: string;
|
|
85
|
+
require_auth_for_profile_image: boolean;
|
|
86
|
+
};
|
|
87
|
+
sqs_ts: number;
|
|
88
|
+
translations: NotificationTranslations;
|
|
89
|
+
type: 'ADMM' | 'MESG' | 'FILE' | string;
|
|
90
|
+
unread_message_count: number;
|
|
91
|
+
}
|
|
92
|
+
export interface NotificationFiles {
|
|
93
|
+
type: string;
|
|
94
|
+
url: string;
|
|
95
|
+
name: string;
|
|
96
|
+
size: number;
|
|
97
|
+
require_auth: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface NotificationTranslations {}
|
|
101
|
+
export interface NotificationMentionedUsers {}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Locale } from 'date-fns';
|
|
2
|
+
import format from 'date-fns/format';
|
|
3
|
+
|
|
4
|
+
type TruncateMode = 'head' | 'mid' | 'tail';
|
|
5
|
+
type TruncateOption = { mode: TruncateMode; maxLen: number; separator: string };
|
|
6
|
+
const defaultOpts: TruncateOption = { mode: 'mid', maxLen: 40, separator: '...' };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* String truncate util
|
|
10
|
+
* @param {string} str
|
|
11
|
+
* @param {Object} opts Options for truncate
|
|
12
|
+
* @param {'head' | 'mid' | 'tail'} opts.mode default "mid"
|
|
13
|
+
* @param {number} opts.maxLen default 40
|
|
14
|
+
* @param {string} opts.separator default "..."
|
|
15
|
+
* @returns {string}
|
|
16
|
+
* */
|
|
17
|
+
export const truncate = (str: string, opts: Partial<TruncateOption> = defaultOpts): string => {
|
|
18
|
+
const options = { ...defaultOpts, ...opts };
|
|
19
|
+
const { maxLen, mode, separator } = options;
|
|
20
|
+
|
|
21
|
+
if (str.length <= maxLen) return str;
|
|
22
|
+
|
|
23
|
+
if (mode === 'head') {
|
|
24
|
+
return separator + str.slice(-maxLen);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (mode === 'mid') {
|
|
28
|
+
const lead = Math.ceil(maxLen / 2);
|
|
29
|
+
const trail = Math.floor(maxLen / 2);
|
|
30
|
+
return str.slice(0, lead) + separator + str.slice(-trail);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (mode === 'tail') {
|
|
34
|
+
return str.slice(0, maxLen) + separator;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
throw new Error('Invalid truncate mode: ' + mode);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Badge count truncate util
|
|
42
|
+
* If count exceed the limit, it comes in the form of "MAX+"
|
|
43
|
+
*
|
|
44
|
+
* @param {number} count
|
|
45
|
+
* @param {number} MAX default 99
|
|
46
|
+
* @returns {string}
|
|
47
|
+
* */
|
|
48
|
+
export const truncatedBadgeCount = (count: number, MAX = 99) => {
|
|
49
|
+
if (count >= MAX) return `${MAX}+`;
|
|
50
|
+
return `${count}`;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Messages date separator format
|
|
55
|
+
*
|
|
56
|
+
* @param {Date} date
|
|
57
|
+
* @param {Locale} [locale]
|
|
58
|
+
* @returns {string}
|
|
59
|
+
* */
|
|
60
|
+
export const dateSeparator = (date: Date, locale?: Locale): string => {
|
|
61
|
+
return format(date, 'E, LLL dd', { locale });
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Message time format
|
|
66
|
+
*
|
|
67
|
+
* @param {Date} date
|
|
68
|
+
* @param {Locale} [locale]
|
|
69
|
+
* @returns {string}
|
|
70
|
+
* */
|
|
71
|
+
export const messageTime = (date: Date, locale?: Locale): string => {
|
|
72
|
+
return format(date, 'p', { locale });
|
|
73
|
+
};
|