@amityco/ts-sdk 0.0.1-beta.41.1 → 0.0.1-beta.42.2
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/dist/@types/core/model.d.ts +3 -1
- package/dist/@types/core/model.d.ts.map +1 -1
- package/dist/@types/domains/category.d.ts +1 -1
- package/dist/@types/domains/category.d.ts.map +1 -1
- package/dist/@types/domains/comment.d.ts +1 -1
- package/dist/@types/domains/comment.d.ts.map +1 -1
- package/dist/@types/domains/community.d.ts +1 -1
- package/dist/@types/domains/community.d.ts.map +1 -1
- package/dist/@types/domains/message.d.ts +1 -1
- package/dist/@types/domains/message.d.ts.map +1 -1
- package/dist/@types/domains/post.d.ts +1 -1
- package/dist/@types/domains/post.d.ts.map +1 -1
- package/dist/@types/domains/subChannel.d.ts +1 -1
- package/dist/@types/domains/subChannel.d.ts.map +1 -1
- package/dist/cache/utils/index.d.ts +1 -0
- package/dist/cache/utils/index.d.ts.map +1 -1
- package/dist/category/api/queryCategories.d.ts.map +1 -1
- package/dist/category/observers/liveCategories.d.ts.map +1 -1
- package/dist/channel/observers/liveChannel.d.ts.map +1 -1
- package/dist/channel/observers/liveChannels.d.ts.map +1 -1
- package/dist/comment/api/queryComments.d.ts.map +1 -1
- package/dist/comment/api/tests/queryComments.test.d.ts +2 -0
- package/dist/comment/api/tests/queryComments.test.d.ts.map +1 -0
- package/dist/comment/observers/liveComments.d.ts.map +1 -1
- package/dist/community/api/queryCommunities.d.ts.map +1 -1
- package/dist/community/observers/liveCommunities.d.ts.map +1 -1
- package/dist/core/model/idResolvers.d.ts.map +1 -1
- package/dist/core/model/identifyModel.d.ts.map +1 -1
- package/dist/core/model/index.d.ts +1 -1
- package/dist/core/model/index.d.ts.map +1 -1
- package/dist/follow/api/getFollowInfo.d.ts.map +1 -1
- package/dist/follow/observers/index.d.ts +1 -0
- package/dist/follow/observers/index.d.ts.map +1 -1
- package/dist/follow/observers/liveFollowInfo.d.ts +21 -0
- package/dist/follow/observers/liveFollowInfo.d.ts.map +1 -0
- package/dist/follow/observers/tests/liveFollowInfo.test.d.ts +2 -0
- package/dist/follow/observers/tests/liveFollowInfo.test.d.ts.map +1 -0
- package/dist/index.cjs.js +112 -63
- package/dist/index.esm.js +112 -64
- package/dist/index.umd.js +4 -4
- package/dist/message/api/createMessage.d.ts +2 -1
- package/dist/message/api/createMessage.d.ts.map +1 -1
- package/dist/message/observers/liveMessages.d.ts.map +1 -1
- package/dist/message/utils/index.d.ts +3 -2
- package/dist/message/utils/index.d.ts.map +1 -1
- package/dist/post/api/queryPosts.d.ts.map +1 -1
- package/dist/post/observers/livePosts.d.ts.map +1 -1
- package/dist/subChannel/api/querySubChannels.d.ts.map +1 -1
- package/dist/subChannel/observers/liveSubChannels.d.ts.map +1 -1
- package/dist/utils/constants.d.ts +1 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/liveObject.d.ts +1 -1
- package/dist/utils/liveObject.d.ts.map +1 -1
- package/dist/utils/tests/dummy/comment.d.ts +77 -0
- package/dist/utils/tests/dummy/comment.d.ts.map +1 -0
- package/dist/utils/tests/dummy/community.d.ts +1 -1
- package/dist/utils/tests/dummy/index.d.ts +1 -0
- package/dist/utils/tests/dummy/index.d.ts.map +1 -1
- package/dist/utils/tests/dummy/post.d.ts +0 -12
- package/dist/utils/tests/dummy/post.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/@types/core/model.ts +3 -1
- package/src/@types/domains/category.ts +1 -1
- package/src/@types/domains/comment.ts +1 -1
- package/src/@types/domains/community.ts +1 -1
- package/src/@types/domains/message.ts +1 -1
- package/src/@types/domains/post.ts +1 -1
- package/src/@types/domains/subChannel.ts +1 -1
- package/src/cache/utils/index.ts +5 -2
- package/src/category/api/queryCategories.ts +2 -1
- package/src/category/observers/liveCategories.ts +3 -1
- package/src/category/observers/tests/liveCategories.test.ts +1 -3
- package/src/channel/api/tests/addChannelMembers.test.ts +13 -0
- package/src/channel/api/tests/queryChannelMembers.test.ts +1 -0
- package/src/channel/api/tests/removeChannelMembers.test.ts +9 -2
- package/src/channel/observers/liveChannel.ts +16 -1
- package/src/channel/observers/liveChannels.ts +11 -9
- package/src/comment/api/queryComments.ts +2 -1
- package/src/comment/api/tests/createComment.test.ts +17 -112
- package/src/comment/api/tests/queryComments.test.ts +147 -0
- package/src/comment/api/tests/updateComment.test.ts +14 -85
- package/src/comment/observers/liveComments.ts +12 -7
- package/src/community/api/queryCommunities.ts +2 -1
- package/src/community/api/test/queryCommunity.test.ts +30 -0
- package/src/community/observers/liveCommunities.ts +13 -7
- package/src/community/observers/tests/liveCommunities.test.ts +2 -2
- package/src/core/model/idResolvers.ts +1 -0
- package/src/core/model/identifyModel.ts +2 -1
- package/src/core/model/index.ts +11 -2
- package/src/follow/api/getFollowInfo.ts +4 -8
- package/src/follow/observers/index.ts +1 -0
- package/src/follow/observers/liveFollowInfo.ts +29 -0
- package/src/follow/observers/tests/liveFollowInfo.test.ts +198 -0
- package/src/message/api/createMessage.ts +17 -5
- package/src/message/api/tests/createMessage.test.ts +36 -5
- package/src/message/api/tests/deleteMessage.test.ts +10 -3
- package/src/message/api/tests/getMessage.test.ts +9 -3
- package/src/message/api/tests/queryMessages.test.ts +30 -0
- package/src/message/api/tests/updateMessage.test.ts +9 -2
- package/src/message/observers/liveMessages.ts +15 -15
- package/src/message/tests/observers/liveMessages.test.ts +2 -2
- package/src/message/utils/index.ts +10 -1
- package/src/post/api/queryPosts.ts +2 -1
- package/src/post/api/tests/queryPosts.test.ts +30 -0
- package/src/post/observers/livePosts.ts +16 -11
- package/src/post/observers/tests/livePosts.test.ts +4 -5
- package/src/reaction/api/tests/addReaction.test.ts +13 -0
- package/src/reaction/api/tests/removeReaction.test.ts +13 -0
- package/src/subChannel/api/querySubChannels.ts +2 -1
- package/src/subChannel/observers/liveSubChannels.ts +6 -5
- package/src/subChannel/observers/tests/liveSubChannels.test.ts +5 -2
- package/src/utils/constants.ts +2 -1
- package/src/utils/liveObject.ts +1 -4
- package/src/utils/tests/dummy/comment.ts +268 -0
- package/src/utils/tests/dummy/index.ts +1 -0
- package/src/utils/tests/dummy/post.ts +0 -114
- package/dist/post/tests/api/getPost.test.d.ts +0 -2
- package/dist/post/tests/api/getPost.test.d.ts.map +0 -1
- package/dist/post/tests/api/queryPosts.test.d.ts +0 -2
- package/dist/post/tests/api/queryPosts.test.d.ts.map +0 -1
- package/src/post/tests/api/getPost.test.ts +0 -98
- package/src/post/tests/api/queryPosts.test.ts +0 -123
package/src/core/model/index.ts
CHANGED
|
@@ -33,8 +33,17 @@ export const PAYLOAD2MODEL: Record<string, Amity.Domain> = {
|
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
/** hidden */
|
|
36
|
-
export const hasUpdates = (prevData:
|
|
37
|
-
|
|
36
|
+
export const hasUpdates = <T extends Amity.Model>(prevData: T, nextData: T): boolean => {
|
|
37
|
+
if ('updatedAt' in prevData && 'updatedAt' in nextData) {
|
|
38
|
+
return new Date(prevData.updatedAt!) < new Date(nextData.updatedAt!);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
* hasUpdates is used to identify the latest object among many. If the object
|
|
43
|
+
* does not extend ${Amity.UpdatedAt}, then there is no way to differentiate.
|
|
44
|
+
* So it is considered by default that the object hasUpdates
|
|
45
|
+
*/
|
|
46
|
+
return true;
|
|
38
47
|
};
|
|
39
48
|
|
|
40
49
|
/** hidden */
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { getActiveClient } from '~/client/api';
|
|
2
2
|
import { pullFromCache, pushToCache } from '~/cache/api';
|
|
3
3
|
|
|
4
|
-
const getCacheKey = (userId: Amity.User['userId']) => [
|
|
5
|
-
'followInfo',
|
|
6
|
-
'get',
|
|
7
|
-
`${getActiveClient().userId}#${userId}`,
|
|
8
|
-
];
|
|
9
|
-
|
|
10
4
|
/* begin_public_function
|
|
11
5
|
id: user.relationship.get_follow_info, user.relationship.get_my_follow_info
|
|
12
6
|
*/
|
|
@@ -44,7 +38,9 @@ export const getFollowInfo = async (
|
|
|
44
38
|
: data.followCounts[0];
|
|
45
39
|
|
|
46
40
|
if (client.cache) {
|
|
47
|
-
pushToCache(
|
|
41
|
+
pushToCache(['followInfo', 'get', userId], followInfo, {
|
|
42
|
+
cachedAt,
|
|
43
|
+
});
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
return {
|
|
@@ -77,7 +73,7 @@ getFollowInfo.locally = (
|
|
|
77
73
|
return;
|
|
78
74
|
}
|
|
79
75
|
|
|
80
|
-
const cached = pullFromCache<Amity.FollowInfo>(
|
|
76
|
+
const cached = pullFromCache<Amity.FollowInfo>(['followInfo', 'get', userId]);
|
|
81
77
|
|
|
82
78
|
if (!cached) {
|
|
83
79
|
return;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { liveObject } from '~/utils/liveObject';
|
|
2
|
+
import { getFollowInfo } from '../api';
|
|
3
|
+
import { onFollowInfoUpdated } from '../events';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ```js
|
|
7
|
+
* import { liveFollowInfo } from '@amityco/ts-sdk';
|
|
8
|
+
*
|
|
9
|
+
* let followInfo;
|
|
10
|
+
*
|
|
11
|
+
* const unsubscribe = liveFollowInfo(userId, response => {
|
|
12
|
+
* followInfo = response.data;
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Observe all mutation on a given {@link Amity.FollowCount} object
|
|
17
|
+
*
|
|
18
|
+
* @param userId the ID of the current user
|
|
19
|
+
* @param callback the function to call when new data are available
|
|
20
|
+
* @returns An {@link Amity.Unsubscriber} function to run when willing to stop observing
|
|
21
|
+
*
|
|
22
|
+
* @category FollowInfo Live Object
|
|
23
|
+
*/
|
|
24
|
+
export const liveFollowInfo = (
|
|
25
|
+
userId: Amity.FollowInfo['userId'],
|
|
26
|
+
callback: Amity.LiveObjectCallback<Amity.FollowInfo>,
|
|
27
|
+
): Amity.Unsubscriber => {
|
|
28
|
+
return liveObject(userId, callback, 'userId', getFollowInfo, [onFollowInfoUpdated]);
|
|
29
|
+
};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { disableCache, enableCache } from '~/cache/api';
|
|
2
|
+
import {
|
|
3
|
+
client,
|
|
4
|
+
connectClient,
|
|
5
|
+
disconnectClient,
|
|
6
|
+
follow21,
|
|
7
|
+
follow11,
|
|
8
|
+
user11,
|
|
9
|
+
pause,
|
|
10
|
+
date,
|
|
11
|
+
} from '~/utils/tests';
|
|
12
|
+
|
|
13
|
+
import { liveFollowInfo } from '../liveFollowInfo';
|
|
14
|
+
|
|
15
|
+
const FOLLOWING_COUNT = 2;
|
|
16
|
+
const getFollowInfoMePayload = ({
|
|
17
|
+
userId = user11.userId,
|
|
18
|
+
followerCount = 0,
|
|
19
|
+
pendingCount = 0,
|
|
20
|
+
followingCount = FOLLOWING_COUNT,
|
|
21
|
+
} = {}): Amity.FollowInfoMePayload => {
|
|
22
|
+
return {
|
|
23
|
+
followCounts: [
|
|
24
|
+
{
|
|
25
|
+
userId,
|
|
26
|
+
followerCount,
|
|
27
|
+
pendingCount,
|
|
28
|
+
followingCount,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const getFollowMeStatusPayload = (
|
|
35
|
+
status: Amity.FollowStatus['status'] = follow11.status,
|
|
36
|
+
): Amity.FollowStatusPayload => {
|
|
37
|
+
return {
|
|
38
|
+
follows: [
|
|
39
|
+
{
|
|
40
|
+
from: follow11.from,
|
|
41
|
+
to: follow11.to,
|
|
42
|
+
status,
|
|
43
|
+
createdAt: date,
|
|
44
|
+
updatedAt: date,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const getFollowStatusPayload = (
|
|
51
|
+
status: Amity.FollowStatus['status'] = follow21.status,
|
|
52
|
+
): Amity.FollowStatusPayload => {
|
|
53
|
+
return {
|
|
54
|
+
follows: [
|
|
55
|
+
{
|
|
56
|
+
from: follow21.from,
|
|
57
|
+
to: follow21.to,
|
|
58
|
+
status,
|
|
59
|
+
createdAt: date,
|
|
60
|
+
updatedAt: date,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
describe('liveFollowInfo > me', () => {
|
|
67
|
+
beforeAll(connectClient);
|
|
68
|
+
afterAll(disconnectClient);
|
|
69
|
+
|
|
70
|
+
beforeEach(enableCache);
|
|
71
|
+
afterEach(disableCache);
|
|
72
|
+
|
|
73
|
+
const tests: [
|
|
74
|
+
string,
|
|
75
|
+
keyof Amity.MqttFollowEvents,
|
|
76
|
+
Amity.FollowStatusPayload,
|
|
77
|
+
Amity.FollowInfoMePayload,
|
|
78
|
+
][] = [
|
|
79
|
+
[
|
|
80
|
+
'it should update follow info collection on accepted',
|
|
81
|
+
'follow.accepted',
|
|
82
|
+
getFollowMeStatusPayload('accepted'),
|
|
83
|
+
getFollowInfoMePayload({ userId: user11.userId, followingCount: FOLLOWING_COUNT + 1 }),
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
'it should update follow info collection on unfollowed',
|
|
87
|
+
'follow.unfollowed',
|
|
88
|
+
getFollowMeStatusPayload('none'),
|
|
89
|
+
getFollowInfoMePayload({ userId: user11.userId, followingCount: FOLLOWING_COUNT - 1 }),
|
|
90
|
+
],
|
|
91
|
+
[
|
|
92
|
+
'it should update follow info collection on requestDeclined',
|
|
93
|
+
'follow.requestDeclined',
|
|
94
|
+
getFollowMeStatusPayload('none'),
|
|
95
|
+
getFollowInfoMePayload({ userId: user11.userId, followingCount: FOLLOWING_COUNT }),
|
|
96
|
+
],
|
|
97
|
+
[
|
|
98
|
+
'it should update follow info collection on followerDeleted',
|
|
99
|
+
'follow.followerDeleted',
|
|
100
|
+
getFollowMeStatusPayload('none'),
|
|
101
|
+
getFollowInfoMePayload({ userId: user11.userId, followingCount: FOLLOWING_COUNT - 1 }),
|
|
102
|
+
],
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
test.each(tests)('%s', async (description, event, payload, payloadFrom) => {
|
|
106
|
+
const callback = jest.fn();
|
|
107
|
+
client.http.get = jest
|
|
108
|
+
.fn()
|
|
109
|
+
.mockResolvedValue({ data: getFollowInfoMePayload() })
|
|
110
|
+
.mockResolvedValueOnce({ data: payloadFrom })
|
|
111
|
+
.mockResolvedValueOnce({ data: payloadFrom });
|
|
112
|
+
|
|
113
|
+
const unsub = liveFollowInfo(user11.userId, callback);
|
|
114
|
+
await pause();
|
|
115
|
+
|
|
116
|
+
client.emitter.emit(event, payload);
|
|
117
|
+
await pause();
|
|
118
|
+
|
|
119
|
+
expect(callback).toHaveBeenCalled();
|
|
120
|
+
|
|
121
|
+
expect(callback).toHaveBeenNthCalledWith(
|
|
122
|
+
3,
|
|
123
|
+
expect.objectContaining({
|
|
124
|
+
data: payloadFrom.followCounts[0],
|
|
125
|
+
loading: false,
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
unsub();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('liveFollowInfo > other', () => {
|
|
134
|
+
beforeAll(connectClient);
|
|
135
|
+
afterAll(disconnectClient);
|
|
136
|
+
|
|
137
|
+
beforeEach(enableCache);
|
|
138
|
+
afterEach(disableCache);
|
|
139
|
+
|
|
140
|
+
const tests: [
|
|
141
|
+
string,
|
|
142
|
+
keyof Amity.MqttFollowEvents,
|
|
143
|
+
Amity.FollowStatusPayload,
|
|
144
|
+
Amity.FollowInfoMePayload,
|
|
145
|
+
][] = [
|
|
146
|
+
[
|
|
147
|
+
'it should update follow info collection on accepted',
|
|
148
|
+
'follow.accepted',
|
|
149
|
+
getFollowStatusPayload('accepted'),
|
|
150
|
+
getFollowInfoMePayload({ userId: follow21.from, followingCount: FOLLOWING_COUNT + 1 }),
|
|
151
|
+
],
|
|
152
|
+
[
|
|
153
|
+
'it should update follow info collection on unfollowed',
|
|
154
|
+
'follow.unfollowed',
|
|
155
|
+
getFollowStatusPayload('none'),
|
|
156
|
+
getFollowInfoMePayload({ userId: follow21.from, followingCount: FOLLOWING_COUNT - 1 }),
|
|
157
|
+
],
|
|
158
|
+
[
|
|
159
|
+
'it should update follow info collection on requestDeclined',
|
|
160
|
+
'follow.requestDeclined',
|
|
161
|
+
getFollowStatusPayload('none'),
|
|
162
|
+
getFollowInfoMePayload({ userId: follow21.from, followingCount: FOLLOWING_COUNT }),
|
|
163
|
+
],
|
|
164
|
+
[
|
|
165
|
+
'it should update follow info collection on followerDeleted',
|
|
166
|
+
'follow.followerDeleted',
|
|
167
|
+
getFollowStatusPayload('none'),
|
|
168
|
+
getFollowInfoMePayload({ userId: follow21.from, followingCount: FOLLOWING_COUNT - 1 }),
|
|
169
|
+
],
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
test.each(tests)('%s', async (description, event, payload, payloadFrom) => {
|
|
173
|
+
const callback = jest.fn();
|
|
174
|
+
client.http.get = jest
|
|
175
|
+
.fn()
|
|
176
|
+
.mockResolvedValue({ data: getFollowInfoMePayload({ userId: follow21.from }) })
|
|
177
|
+
.mockResolvedValueOnce({ data: payloadFrom })
|
|
178
|
+
.mockResolvedValueOnce({ data: payloadFrom });
|
|
179
|
+
|
|
180
|
+
const unsub = liveFollowInfo(follow21.from, callback);
|
|
181
|
+
await pause();
|
|
182
|
+
|
|
183
|
+
client.emitter.emit(event, payload);
|
|
184
|
+
await pause();
|
|
185
|
+
|
|
186
|
+
expect(callback).toHaveBeenCalled();
|
|
187
|
+
|
|
188
|
+
expect(callback).toHaveBeenNthCalledWith(
|
|
189
|
+
3,
|
|
190
|
+
expect.objectContaining({
|
|
191
|
+
data: payloadFrom.followCounts[0],
|
|
192
|
+
loading: false,
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
unsub();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
@@ -44,12 +44,23 @@ export const createMessage = async <T extends Amity.MessageContentType>(
|
|
|
44
44
|
> & {
|
|
45
45
|
data?: Amity.Message<T>['data'];
|
|
46
46
|
fileId?: Amity.File['fileId'];
|
|
47
|
+
// Custom referenceId is a workaround for the case:
|
|
48
|
+
// User wants to create an image message. He should
|
|
49
|
+
// - create and show dummy message in ui
|
|
50
|
+
// - upload image to get fileId, while uploading show a spinner in dummy message in ui
|
|
51
|
+
// - call createMessage and pass fileId
|
|
52
|
+
// - replace dummy message with the actual message received from backend
|
|
53
|
+
// Custom referenceId is needed to make relation between dummy message and actual message.
|
|
54
|
+
// TODO: passing file src into createMessage so that sdk uploads files itself,
|
|
55
|
+
// returns the dummy message bound with the file to eliminate the need to create
|
|
56
|
+
// the dummy message on the user side,
|
|
57
|
+
referenceId?: string;
|
|
47
58
|
},
|
|
48
59
|
): Promise<Amity.Cached<Amity.Message>> => {
|
|
49
60
|
const client = getActiveClient();
|
|
50
61
|
client.log('message/createMessage', bundle);
|
|
51
62
|
|
|
52
|
-
const referenceId = uniqueId || getLocalId();
|
|
63
|
+
const referenceId = bundle.referenceId || uniqueId || getLocalId();
|
|
53
64
|
uniqueId = undefined;
|
|
54
65
|
|
|
55
66
|
const { data: payload } = await client.http.post<Amity.MessagePayload>('/api/v5/messages', {
|
|
@@ -91,9 +102,10 @@ export const createMessage = async <T extends Amity.MessageContentType>(
|
|
|
91
102
|
*
|
|
92
103
|
* @category Message API
|
|
93
104
|
*/
|
|
94
|
-
createMessage.optimistically = (
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
createMessage.optimistically = ({
|
|
106
|
+
referenceId,
|
|
107
|
+
...bundle
|
|
108
|
+
}: Parameters<typeof createMessage>[0]): Amity.Cached<Amity.Message> | undefined => {
|
|
97
109
|
const client = getActiveClient();
|
|
98
110
|
|
|
99
111
|
if (!client.cache) return;
|
|
@@ -105,7 +117,7 @@ createMessage.optimistically = (
|
|
|
105
117
|
* Updated to handle client requirement to add messageId while uploading
|
|
106
118
|
* message with image. Temporary!
|
|
107
119
|
*/
|
|
108
|
-
uniqueId = getLocalId();
|
|
120
|
+
uniqueId = referenceId || getLocalId();
|
|
109
121
|
const bundleWithMessageId = { messageId: uniqueId, uniqueId, ...bundle };
|
|
110
122
|
|
|
111
123
|
client.log('message/createMessage.optimistically', bundleWithMessageId);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ASCApiError } from '~/core/errors';
|
|
1
2
|
import { UNSYNCED_OBJECT_CACHED_AT_VALUE } from '~/utils/constants';
|
|
2
3
|
import { disableCache, enableCache, pullFromCache, pushToCache } from '~/cache/api';
|
|
3
4
|
|
|
@@ -28,6 +29,7 @@ describe('createMessage', () => {
|
|
|
28
29
|
|
|
29
30
|
afterEach(disableCache);
|
|
30
31
|
|
|
32
|
+
// integration_test_id: 9691969d-6ba8-4af6-93d6-dfe9134e9796
|
|
31
33
|
test('should return created message', async () => {
|
|
32
34
|
client.http.post = jest.fn().mockResolvedValue(response);
|
|
33
35
|
|
|
@@ -37,10 +39,15 @@ describe('createMessage', () => {
|
|
|
37
39
|
expect(created.uniqueId).toBeDefined();
|
|
38
40
|
});
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
// integration_test_id: acc3be84-9234-4af1-88a1-cbb517fa247f
|
|
43
|
+
test('should throw an 404300 if user has no permission', async () => {
|
|
44
|
+
client.http.post = jest
|
|
45
|
+
.fn()
|
|
46
|
+
.mockRejectedValueOnce(
|
|
47
|
+
new ASCApiError('forbidden', Amity.ServerError.FORBIDDEN, Amity.ErrorLevel.ERROR),
|
|
48
|
+
);
|
|
42
49
|
|
|
43
|
-
await expect(createMessage(messageWithoutId)).rejects.toThrow('
|
|
50
|
+
await expect(createMessage(messageWithoutId)).rejects.toThrow('400300');
|
|
44
51
|
});
|
|
45
52
|
|
|
46
53
|
test('should put message into cache', async () => {
|
|
@@ -141,9 +148,9 @@ describe('uniqueId / referenceId', () => {
|
|
|
141
148
|
subChannelId: messageToCreate.subChannelId,
|
|
142
149
|
};
|
|
143
150
|
|
|
144
|
-
|
|
151
|
+
beforeEach(enableCache);
|
|
145
152
|
|
|
146
|
-
|
|
153
|
+
afterEach(disableCache);
|
|
147
154
|
|
|
148
155
|
it('should optimistically create message with uniqueId', () => {
|
|
149
156
|
const { data: message } = createMessage.optimistically(bundle)!;
|
|
@@ -208,4 +215,28 @@ describe('uniqueId / referenceId', () => {
|
|
|
208
215
|
}),
|
|
209
216
|
);
|
|
210
217
|
});
|
|
218
|
+
|
|
219
|
+
describe('custom referenceId', () => {
|
|
220
|
+
it('should handle custom referenceId for optimistic message', () => {
|
|
221
|
+
const referenceId = 'custom-reference-id';
|
|
222
|
+
|
|
223
|
+
const { data: message } = createMessage.optimistically({ ...bundle, referenceId })!;
|
|
224
|
+
|
|
225
|
+
expect(message.uniqueId).toEqual(referenceId);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should handle custom referenceId if cache is turned off', async () => {
|
|
229
|
+
disableCache();
|
|
230
|
+
const referenceId = 'custom-reference-id';
|
|
231
|
+
client.http.post = jest.fn().mockResolvedValue({ data: { messages: [rawMessageToCreate] } });
|
|
232
|
+
|
|
233
|
+
runQuery(createQuery(createMessage, { ...bundle, referenceId }));
|
|
234
|
+
await pause();
|
|
235
|
+
|
|
236
|
+
expect(client.http.post).toHaveBeenCalledWith(
|
|
237
|
+
expect.anything(),
|
|
238
|
+
expect.objectContaining({ referenceId }),
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
211
242
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ASCApiError } from '~/core/errors';
|
|
1
2
|
import { disableCache, enableCache, pullFromCache, pushToCache } from '~/cache/api';
|
|
2
3
|
import { client, generateRawMessage } from '~/utils/tests';
|
|
3
4
|
|
|
@@ -20,6 +21,7 @@ const getResolvedMessageValue = () => ({
|
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
describe('deleteMessage', () => {
|
|
24
|
+
// integration_test_id: 16106137-0e4a-4d2f-b8cf-1a6c571bf0c7
|
|
23
25
|
test('should return deleted message', async () => {
|
|
24
26
|
client.http.delete = jest.fn();
|
|
25
27
|
client.http.get = jest.fn().mockResolvedValueOnce(getResolvedMessageValue());
|
|
@@ -27,10 +29,15 @@ describe('deleteMessage', () => {
|
|
|
27
29
|
await expect(deleteMessage(deleteId)).resolves.toEqual(deletedMessage);
|
|
28
30
|
});
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
// integration_test_id: 27d393d9-480e-4eec-bfdb-f04738801b04
|
|
33
|
+
test('should return 400400 if message not found', async () => {
|
|
34
|
+
client.http.delete = jest
|
|
35
|
+
.fn()
|
|
36
|
+
.mockRejectedValueOnce(
|
|
37
|
+
new ASCApiError('not found', Amity.ServerError.ITEM_NOT_FOUND, Amity.ErrorLevel.ERROR),
|
|
38
|
+
);
|
|
32
39
|
|
|
33
|
-
await expect(deleteMessage(deleteId)).rejects.toThrow('
|
|
40
|
+
await expect(deleteMessage(deleteId)).rejects.toThrow('400400');
|
|
34
41
|
});
|
|
35
42
|
|
|
36
43
|
test('should update cache after deleted messages', async () => {
|
|
@@ -21,6 +21,7 @@ const getResolvedMessageValue = () => ({
|
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
describe('getMessage', () => {
|
|
24
|
+
// integration_test_id: bc048747-17ea-43fe-9f97-7b2bf008c476
|
|
24
25
|
test('should return fetched message', async () => {
|
|
25
26
|
client.http.get = jest.fn().mockResolvedValueOnce(getResolvedMessageValue());
|
|
26
27
|
|
|
@@ -41,10 +42,15 @@ describe('getMessage', () => {
|
|
|
41
42
|
disableCache();
|
|
42
43
|
});
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
// integration_test_id: 81375153-2886-46e0-a164-7f3902e77b55
|
|
46
|
+
test('should return 400300 if unauthorized', async () => {
|
|
47
|
+
client.http.get = jest
|
|
48
|
+
.fn()
|
|
49
|
+
.mockRejectedValueOnce(
|
|
50
|
+
new ASCApiError('forbidden', Amity.ServerError.FORBIDDEN, Amity.ErrorLevel.ERROR),
|
|
51
|
+
);
|
|
46
52
|
|
|
47
|
-
await expect(getMessage(messageId)).rejects.toThrow('
|
|
53
|
+
await expect(getMessage(messageId)).rejects.toThrow('400300');
|
|
48
54
|
});
|
|
49
55
|
|
|
50
56
|
test('should fire event `onMessageFetched`', async () => {
|
|
@@ -74,6 +74,36 @@ describe('queryMessages', () => {
|
|
|
74
74
|
await queryMessages(queryCriteria);
|
|
75
75
|
await expect(callbackPromise).resolves.toEqual(messagesToQuery[0]);
|
|
76
76
|
});
|
|
77
|
+
|
|
78
|
+
test('it should set isDeleted to undefined if includeDeleted is true', async () => {
|
|
79
|
+
const mockFn = jest.fn();
|
|
80
|
+
client.http.get = mockFn.mockResolvedValue(getResolvedMessageValue());
|
|
81
|
+
|
|
82
|
+
await queryMessages({ ...queryCriteriaWithoutPage, includeDeleted: true });
|
|
83
|
+
|
|
84
|
+
expect(mockFn.mock.lastCall).toHaveLength(2);
|
|
85
|
+
expect(mockFn.mock.lastCall[1].params.isDeleted).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('it should set isDeleted to false if includeDeleted is false', async () => {
|
|
89
|
+
const mockFn = jest.fn();
|
|
90
|
+
client.http.get = mockFn.mockResolvedValue(getResolvedMessageValue());
|
|
91
|
+
|
|
92
|
+
await queryMessages({ ...queryCriteriaWithoutPage, includeDeleted: false });
|
|
93
|
+
|
|
94
|
+
expect(mockFn.mock.lastCall).toHaveLength(2);
|
|
95
|
+
expect(mockFn.mock.lastCall[1].params.isDeleted).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('it should set isDeleted to undefined if includeDeleted is undefined', async () => {
|
|
99
|
+
const mockFn = jest.fn();
|
|
100
|
+
client.http.get = mockFn.mockResolvedValue(getResolvedMessageValue());
|
|
101
|
+
|
|
102
|
+
await queryMessages(queryCriteriaWithoutPage);
|
|
103
|
+
|
|
104
|
+
expect(mockFn.mock.lastCall).toHaveLength(2);
|
|
105
|
+
expect(mockFn.mock.lastCall[1].params.isDeleted).toBeUndefined();
|
|
106
|
+
});
|
|
77
107
|
});
|
|
78
108
|
|
|
79
109
|
describe('queryMessages.locally', () => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ASCApiError } from '~/core/errors';
|
|
1
2
|
import { disableCache, enableCache, pullFromCache, pushToCache } from '~/cache/api';
|
|
2
3
|
import { client, generateRawMessage } from '~/utils/tests';
|
|
3
4
|
|
|
@@ -21,6 +22,7 @@ const getResolvedMessageValue = () => ({
|
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
describe('updateMessage', () => {
|
|
25
|
+
// integration_test_id: 3ae17ea2-dee5-4418-88af-fe809ee48a52
|
|
24
26
|
test('should return updated message', async () => {
|
|
25
27
|
client.http.put = jest.fn().mockResolvedValueOnce(getResolvedMessageValue());
|
|
26
28
|
|
|
@@ -29,10 +31,15 @@ describe('updateMessage', () => {
|
|
|
29
31
|
await expect(recieved).resolves.toEqual(expect.objectContaining({ data: updatedMessage }));
|
|
30
32
|
});
|
|
31
33
|
|
|
34
|
+
// integration_test_id: 3ae17ea2-dee5-4418-88af-fe809ee48a52
|
|
32
35
|
test('should throw an error if request fails', async () => {
|
|
33
|
-
client.http.put = jest
|
|
36
|
+
client.http.put = jest
|
|
37
|
+
.fn()
|
|
38
|
+
.mockRejectedValueOnce(
|
|
39
|
+
new ASCApiError('not found', Amity.ServerError.ITEM_NOT_FOUND, Amity.ErrorLevel.ERROR),
|
|
40
|
+
);
|
|
34
41
|
|
|
35
|
-
await expect(updateMessage(messageId, patch)).rejects.toThrow('
|
|
42
|
+
await expect(updateMessage(messageId, patch)).rejects.toThrow('400400');
|
|
36
43
|
});
|
|
37
44
|
|
|
38
45
|
test('should update cache after updated messages', async () => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/* eslint-disable no-use-before-define */
|
|
2
2
|
import { getResolver } from '~/core/model';
|
|
3
3
|
import { getActiveClient } from '~/client/api';
|
|
4
|
-
import {
|
|
5
|
-
import { createQuery,
|
|
4
|
+
import { dropFromCache, pullFromCache, pushToCache } from '~/cache/api';
|
|
5
|
+
import { createQuery, filterByPropEquality, queryOptions, runQuery } from '~/core/query';
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
COLLECTION_DEFAULT_CACHING_POLICY,
|
|
@@ -10,15 +10,16 @@ import {
|
|
|
10
10
|
ENABLE_CACHE_MESSAGE,
|
|
11
11
|
} from '~/utils/constants';
|
|
12
12
|
|
|
13
|
+
import { CACHE_SHORTEN_LIFESPAN } from '~/cache/utils';
|
|
13
14
|
import {
|
|
14
15
|
onMessageCreated,
|
|
15
16
|
onMessageDeleted,
|
|
16
|
-
onMessageUpdated,
|
|
17
|
-
onMessageFlagged,
|
|
18
|
-
onMessageUnflagged,
|
|
19
17
|
onMessageFlagCleared,
|
|
18
|
+
onMessageFlagged,
|
|
20
19
|
onMessageReactionAdded,
|
|
21
20
|
onMessageReactionRemoved,
|
|
21
|
+
onMessageUnflagged,
|
|
22
|
+
onMessageUpdated,
|
|
22
23
|
} from '../events';
|
|
23
24
|
import { onMessageFetched } from '../events/onMessageFetched';
|
|
24
25
|
import { queryMessages } from '../api';
|
|
@@ -73,9 +74,11 @@ export const liveMessages = (
|
|
|
73
74
|
|
|
74
75
|
/*
|
|
75
76
|
* for cases when message is deleted via RTE, this flag is used to get
|
|
76
|
-
* items
|
|
77
|
+
* items from cache that are !deleted
|
|
77
78
|
*/
|
|
78
|
-
|
|
79
|
+
if (params.includeDeleted === false) {
|
|
80
|
+
messages = filterByPropEquality(messages, 'isDeleted', false);
|
|
81
|
+
}
|
|
79
82
|
|
|
80
83
|
callback({
|
|
81
84
|
onNextPage: onFetch,
|
|
@@ -86,16 +89,12 @@ export const liveMessages = (
|
|
|
86
89
|
});
|
|
87
90
|
};
|
|
88
91
|
|
|
89
|
-
const realtimeRouter = (
|
|
92
|
+
const realtimeRouter = (_: Amity.MessageActionType) => (message: Amity.Message) => {
|
|
90
93
|
const collection = pullFromCache<Amity.MessageLiveCollectionCache>(cacheKey)?.data;
|
|
91
94
|
|
|
92
|
-
if (params.subChannelId !== message
|
|
95
|
+
if (params.subChannelId !== message?.subChannelId || !collection) return;
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
collection.data = [...new Set([message.messageId, ...collection.data])];
|
|
96
|
-
} else if (['onDelete'].includes(action)) {
|
|
97
|
-
collection.data = collection.data.filter(m => m !== message.messageId);
|
|
98
|
-
}
|
|
97
|
+
collection.data = [...new Set([message.messageId, ...collection.data])];
|
|
99
98
|
|
|
100
99
|
pushToCache(cacheKey, collection);
|
|
101
100
|
|
|
@@ -132,7 +131,7 @@ export const liveMessages = (
|
|
|
132
131
|
|
|
133
132
|
responder(data);
|
|
134
133
|
},
|
|
135
|
-
queryOptions(policy),
|
|
134
|
+
queryOptions(policy, CACHE_SHORTEN_LIFESPAN),
|
|
136
135
|
);
|
|
137
136
|
};
|
|
138
137
|
|
|
@@ -153,5 +152,6 @@ export const liveMessages = (
|
|
|
153
152
|
return () => {
|
|
154
153
|
log(`liveMessages(tmpid: ${timestamp}) > dispose`);
|
|
155
154
|
disposers.forEach(fn => fn());
|
|
155
|
+
dropFromCache(cacheKey);
|
|
156
156
|
};
|
|
157
157
|
};
|
|
@@ -146,7 +146,7 @@ describe('liveMessages', () => {
|
|
|
146
146
|
},
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
-
liveMessages({ subChannelId,
|
|
149
|
+
liveMessages({ subChannelId, includeDeleted: false }, callback);
|
|
150
150
|
|
|
151
151
|
expect(callback).toHaveBeenCalledTimes(1);
|
|
152
152
|
|
|
@@ -351,7 +351,7 @@ describe('liveMessages', () => {
|
|
|
351
351
|
expect(callback).toHaveBeenNthCalledWith(
|
|
352
352
|
3,
|
|
353
353
|
expect.objectContaining({
|
|
354
|
-
data: [],
|
|
354
|
+
data: [message],
|
|
355
355
|
error: undefined,
|
|
356
356
|
loading: false,
|
|
357
357
|
}),
|
|
@@ -89,8 +89,15 @@ export const convertResponseFromRaw = <T extends Amity.MessagePayload>(
|
|
|
89
89
|
};
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
type RawQueryMessages = Omit<
|
|
92
|
+
type RawQueryMessages = Omit<
|
|
93
|
+
Amity.QueryMessages,
|
|
94
|
+
/*
|
|
95
|
+
* include deleted is for internal sdk use only. The server expects isDeleted
|
|
96
|
+
*/
|
|
97
|
+
'page' | 'sortBy' | 'subChannelId' | 'tags' | 'includeDeleted'
|
|
98
|
+
> & {
|
|
93
99
|
includeTags?: Amity.QueryMessages['tags'];
|
|
100
|
+
isDeleted?: Amity.Message['isDeleted'];
|
|
94
101
|
messageFeedId: Amity.QueryMessages['subChannelId'];
|
|
95
102
|
options: {
|
|
96
103
|
sortBy?: Amity.QueryMessages['sortBy'];
|
|
@@ -111,11 +118,13 @@ export function convertQueryParams({
|
|
|
111
118
|
sortBy,
|
|
112
119
|
subChannelId,
|
|
113
120
|
tags,
|
|
121
|
+
includeDeleted,
|
|
114
122
|
...rest
|
|
115
123
|
}: Amity.QueryMessages): RawQueryMessages {
|
|
116
124
|
const out: RawQueryMessages = {
|
|
117
125
|
...rest,
|
|
118
126
|
messageFeedId: subChannelId,
|
|
127
|
+
isDeleted: includeDeleted === false ? false : undefined,
|
|
119
128
|
options: { token: toToken(page || { limit: 10 }, 'afterbeforeraw')! },
|
|
120
129
|
};
|
|
121
130
|
|
|
@@ -30,7 +30,7 @@ export const queryPosts = async (
|
|
|
30
30
|
const client = getActiveClient();
|
|
31
31
|
client.log('post/queryPosts', query);
|
|
32
32
|
|
|
33
|
-
const { page = { limit: 10 }, ...params } = query;
|
|
33
|
+
const { page = { limit: 10 }, includeDeleted, ...params } = query;
|
|
34
34
|
const { dataTypes, matchingOnlyParentPost } = params;
|
|
35
35
|
|
|
36
36
|
// API-FIX: parameters should be querystring. (1)
|
|
@@ -39,6 +39,7 @@ export const queryPosts = async (
|
|
|
39
39
|
const { data } = await client.http.get<Amity.PostPayload & Amity.Pagination>(`/api/v4/posts`, {
|
|
40
40
|
params: {
|
|
41
41
|
...params,
|
|
42
|
+
isDeleted: includeDeleted === false ? false : undefined,
|
|
42
43
|
/*
|
|
43
44
|
* when creating post like image, file, video BE will create 2 posts
|
|
44
45
|
* 1. parent post to store text with dataType=text
|