@amityco/ts-sdk 0.0.1-beta.30 → 0.0.1-beta.31
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/cache.d.ts +3 -0
- package/dist/@types/core/cache.d.ts.map +1 -1
- package/dist/@types/core/errors.d.ts +2 -1
- package/dist/@types/core/errors.d.ts.map +1 -1
- package/dist/@types/core/events.d.ts +2 -0
- package/dist/@types/core/events.d.ts.map +1 -1
- package/dist/@types/core/live.d.ts +8 -4
- package/dist/@types/core/live.d.ts.map +1 -1
- package/dist/@types/core/query.d.ts +1 -1
- package/dist/@types/core/query.d.ts.map +1 -1
- package/dist/@types/domains/channel.d.ts +1 -0
- package/dist/@types/domains/channel.d.ts.map +1 -1
- package/dist/@types/domains/message.d.ts +13 -6
- package/dist/@types/domains/message.d.ts.map +1 -1
- package/dist/@types/domains/post.d.ts +10 -3
- package/dist/@types/domains/post.d.ts.map +1 -1
- package/dist/@types/domains/user.d.ts +1 -0
- package/dist/@types/domains/user.d.ts.map +1 -1
- package/dist/@types/index.d.ts +2 -2
- package/dist/@types/index.d.ts.map +1 -1
- package/dist/cache/api/isInTombstone.d.ts +18 -0
- package/dist/cache/api/isInTombstone.d.ts.map +1 -0
- package/dist/cache/api/pushToTombstone.d.ts +17 -0
- package/dist/cache/api/pushToTombstone.d.ts.map +1 -0
- package/dist/cache/utils/index.d.ts +17 -0
- package/dist/cache/utils/index.d.ts.map +1 -1
- package/dist/channel/api/queryChannelMembers.d.ts +1 -1
- package/dist/channel/api/queryChannelMembers.d.ts.map +1 -1
- package/dist/channel/api/queryChannels.d.ts +1 -1
- package/dist/channel/api/queryChannels.d.ts.map +1 -1
- package/dist/channel/api/tests/queryChannelMembers.test.d.ts +2 -0
- package/dist/channel/api/tests/queryChannelMembers.test.d.ts.map +1 -0
- package/dist/channel/api/tests/queryChannels.test.d.ts +2 -0
- package/dist/channel/api/tests/queryChannels.test.d.ts.map +1 -0
- package/dist/channel/events/index.d.ts +2 -0
- package/dist/channel/events/index.d.ts.map +1 -1
- package/dist/channel/events/onChannelMemberBanned.d.ts +17 -0
- package/dist/channel/events/onChannelMemberBanned.d.ts.map +1 -0
- package/dist/channel/events/onChannelMemberUnbanned.d.ts +17 -0
- package/dist/channel/events/onChannelMemberUnbanned.d.ts.map +1 -0
- package/dist/channel/events/tests/onChannelMemberBanned.test.d.ts +2 -0
- package/dist/channel/events/tests/onChannelMemberBanned.test.d.ts.map +1 -0
- package/dist/channel/events/tests/onChannelMemberUnbanned.test.d.ts +2 -0
- package/dist/channel/events/tests/onChannelMemberUnbanned.test.d.ts.map +1 -0
- package/dist/channel/observers/liveChannelMembers.d.ts.map +1 -1
- package/dist/channel/observers/liveChannels.d.ts.map +1 -1
- package/dist/channel/{tests/utils → utils/tests}/prepareChannelPayload.test.d.ts +0 -0
- package/dist/channel/utils/tests/prepareChannelPayload.test.d.ts.map +1 -0
- package/dist/core/events.d.ts +3 -3
- package/dist/core/events.d.ts.map +1 -1
- package/dist/core/query/filtering.d.ts +11 -0
- package/dist/core/query/filtering.d.ts.map +1 -1
- package/dist/core/query/query.d.ts.map +1 -1
- package/dist/index.cjs.js +963 -507
- package/dist/index.esm.js +946 -499
- package/dist/index.umd.js +4 -4
- package/dist/message/api/createMessage.d.ts +4 -1
- package/dist/message/api/createMessage.d.ts.map +1 -1
- package/dist/message/api/deleteMessage.d.ts.map +1 -1
- package/dist/message/api/getMessage.d.ts +1 -1
- package/dist/message/api/getMessage.d.ts.map +1 -1
- package/dist/message/api/getMessages.d.ts.map +1 -1
- package/dist/message/api/queryMessages.d.ts.map +1 -1
- package/dist/message/api/tests/createMessage.test.d.ts +2 -0
- package/dist/message/api/tests/createMessage.test.d.ts.map +1 -0
- package/dist/message/api/updateMessage.d.ts.map +1 -1
- package/dist/message/events/index.d.ts +1 -0
- package/dist/message/events/index.d.ts.map +1 -1
- package/dist/message/events/onMessageFetched.d.ts +17 -0
- package/dist/message/events/onMessageFetched.d.ts.map +1 -0
- package/dist/message/observers/index.d.ts +2 -1
- package/dist/message/observers/index.d.ts.map +1 -1
- package/dist/message/observers/liveMessage.d.ts +18 -0
- package/dist/message/observers/liveMessage.d.ts.map +1 -0
- package/dist/message/observers/liveMessages.d.ts.map +1 -1
- package/dist/message/observers/observeMessage.d.ts.map +1 -1
- package/dist/message/tests/api/getMessage.test.d.ts +2 -0
- package/dist/message/tests/api/getMessage.test.d.ts.map +1 -0
- package/dist/message/tests/observers/liveMessage.test.d.ts +2 -0
- package/dist/message/tests/observers/liveMessage.test.d.ts.map +1 -0
- package/dist/message/{observers/tests → tests/observers}/liveMessages.test.d.ts +0 -0
- package/dist/message/tests/observers/liveMessages.test.d.ts.map +1 -0
- package/dist/post/api/deletePost.d.ts.map +1 -1
- package/dist/post/api/getPost.d.ts.map +1 -1
- package/dist/post/api/updatePost.d.ts +6 -1
- package/dist/post/api/updatePost.d.ts.map +1 -1
- package/dist/user/api/getUser.d.ts +1 -1
- package/dist/user/api/getUser.d.ts.map +1 -1
- package/dist/user/api/getUsers.d.ts.map +1 -1
- package/dist/user/api/queryUsers.d.ts.map +1 -1
- package/dist/user/api/updateUser.d.ts.map +1 -1
- package/dist/user/events/index.d.ts +1 -0
- package/dist/user/events/index.d.ts.map +1 -1
- package/dist/user/events/onUserFetched.d.ts +17 -0
- package/dist/user/events/onUserFetched.d.ts.map +1 -0
- package/dist/user/observers/index.d.ts +1 -0
- package/dist/user/observers/index.d.ts.map +1 -1
- package/dist/user/observers/liveUser.d.ts +18 -0
- package/dist/user/observers/liveUser.d.ts.map +1 -0
- package/dist/user/observers/observeUser.d.ts.map +1 -1
- package/dist/user/tests/api/getUser.test.d.ts +2 -0
- package/dist/user/tests/api/getUser.test.d.ts.map +1 -0
- package/dist/user/tests/observers/liveUser.test.d.ts +2 -0
- package/dist/user/tests/observers/liveUser.test.d.ts.map +1 -0
- package/dist/utils/constants.d.ts +3 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/tests/dummy/channel.d.ts +15 -0
- package/dist/utils/tests/dummy/channel.d.ts.map +1 -1
- package/dist/utils/tests/dummy/message.d.ts +12 -1
- package/dist/utils/tests/dummy/message.d.ts.map +1 -1
- package/dist/utils/tests/dummy/user.d.ts +10 -0
- package/dist/utils/tests/dummy/user.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/@types/core/cache.ts +4 -0
- package/src/@types/core/errors.ts +1 -0
- package/src/@types/core/events.ts +2 -0
- package/src/@types/core/live.ts +10 -4
- package/src/@types/core/query.ts +1 -1
- package/src/@types/domains/channel.ts +6 -0
- package/src/@types/domains/message.ts +13 -10
- package/src/@types/domains/post.ts +10 -3
- package/src/@types/domains/user.ts +8 -0
- package/src/@types/index.ts +2 -2
- package/src/cache/api/index.ts +2 -1
- package/src/cache/api/isInTombstone.ts +41 -0
- package/src/cache/api/pushToTombstone.ts +33 -0
- package/src/cache/utils/index.ts +22 -0
- package/src/channel/api/queryChannelMembers.ts +53 -5
- package/src/channel/api/queryChannels.ts +31 -4
- package/src/channel/api/tests/queryChannelMembers.test.ts +115 -0
- package/src/channel/api/tests/queryChannels.test.ts +101 -0
- package/src/channel/events/index.ts +5 -0
- package/src/channel/events/onChannelMemberBanned.ts +36 -0
- package/src/channel/events/onChannelMemberUnbanned.ts +36 -0
- package/src/channel/events/tests/onChannelMemberBanned.test.ts +58 -0
- package/src/channel/events/tests/onChannelMemberUnbanned.test.ts +58 -0
- package/src/channel/observers/liveChannelMembers.ts +28 -4
- package/src/channel/observers/liveChannels.ts +7 -1
- package/src/channel/observers/tests/liveChannelMembers.test.ts +53 -11
- package/src/channel/observers/tests/liveChannels.test.ts +50 -20
- package/src/channel/{tests/utils → utils/tests}/prepareChannelPayload.test.ts +1 -1
- package/src/core/events.ts +1 -0
- package/src/core/query/filtering.ts +49 -0
- package/src/core/query/query.ts +9 -6
- package/src/core/tests/query/query.test.ts +12 -2
- package/src/message/api/createMessage.ts +15 -9
- package/src/message/api/deleteMessage.ts +2 -1
- package/src/message/api/getMessage.ts +32 -4
- package/src/message/api/getMessages.ts +6 -1
- package/src/message/api/queryMessages.ts +3 -0
- package/src/message/api/tests/createMessage.test.ts +76 -0
- package/src/message/api/updateMessage.ts +2 -0
- package/src/message/events/index.ts +1 -0
- package/src/message/events/onMessageFetched.ts +30 -0
- package/src/message/observers/index.ts +2 -1
- package/src/message/observers/liveMessage.ts +100 -0
- package/src/message/observers/liveMessages.ts +3 -1
- package/src/message/observers/observeMessage.ts +4 -0
- package/src/message/tests/api/getMessage.test.ts +96 -0
- package/src/message/tests/observers/liveMessage.test.ts +248 -0
- package/src/message/{observers/tests → tests/observers}/liveMessages.test.ts +29 -3
- package/src/post/api/deletePost.ts +4 -3
- package/src/post/api/getPost.ts +8 -4
- package/src/post/api/updatePost.ts +6 -1
- package/src/user/api/getUser.ts +34 -6
- package/src/user/api/getUsers.ts +3 -0
- package/src/user/api/queryUsers.ts +3 -0
- package/src/user/api/updateUser.ts +3 -0
- package/src/user/events/index.ts +1 -0
- package/src/user/events/onUserFetched.ts +19 -0
- package/src/user/observers/index.ts +1 -0
- package/src/user/observers/liveUser.ts +89 -0
- package/src/user/observers/observeUser.ts +2 -0
- package/src/user/tests/api/getUser.test.ts +112 -0
- package/src/user/tests/observers/liveUser.test.ts +224 -0
- package/src/utils/constants.ts +5 -0
- package/src/utils/tests/dummy/channel.ts +43 -0
- package/src/utils/tests/dummy/message.ts +16 -2
- package/src/utils/tests/dummy/user.ts +11 -0
- package/dist/channel/tests/utils/prepareChannelPayload.test.d.ts.map +0 -1
- package/dist/message/observers/tests/liveMessages.test.d.ts.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getActiveClient } from '~/client/api';
|
|
2
2
|
|
|
3
|
+
import { fireEvent } from '~/core/events';
|
|
3
4
|
import { pullFromCache } from '~/cache/api';
|
|
4
5
|
import { ingestInCache } from '~/cache/api/ingestInCache';
|
|
5
6
|
|
|
@@ -31,8 +32,12 @@ export const getMessages = async (
|
|
|
31
32
|
const cachedAt = client.cache && Date.now();
|
|
32
33
|
if (client.cache) ingestInCache(data, { cachedAt });
|
|
33
34
|
|
|
35
|
+
const { messages } = data;
|
|
36
|
+
|
|
37
|
+
fireEvent('v3.message.didFetch', { messages });
|
|
38
|
+
|
|
34
39
|
return {
|
|
35
|
-
data:
|
|
40
|
+
data: messages,
|
|
36
41
|
cachedAt,
|
|
37
42
|
};
|
|
38
43
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getActiveClient } from '~/client/api';
|
|
2
2
|
|
|
3
|
+
import { fireEvent } from '~/core/events';
|
|
3
4
|
import { toPage, toToken } from '~/core/query';
|
|
4
5
|
import { pullFromCache, pushToCache } from '~/cache/api';
|
|
5
6
|
import { ingestInCache } from '~/cache/api/ingestInCache';
|
|
@@ -62,6 +63,8 @@ export const queryMessages = async (
|
|
|
62
63
|
pushToCache(cacheKey, { messages: messages.map(getResolver('message')), paging });
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
fireEvent('v3.message.didFetch', { messages });
|
|
67
|
+
|
|
65
68
|
const nextPage = toPage(paging.next);
|
|
66
69
|
const prevPage = toPage(paging.previous);
|
|
67
70
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { UNSYNCED_OBJECT_CACHED_AT_VALUE } from '~/utils/constants';
|
|
2
|
+
import { disableCache, enableCache, pullFromCache } from '~/cache/api';
|
|
3
|
+
|
|
4
|
+
import { client, message11 } from '~/utils/tests';
|
|
5
|
+
|
|
6
|
+
import { onMessageCreated } from '../../events';
|
|
7
|
+
import { createMessage } from '../createMessage';
|
|
8
|
+
|
|
9
|
+
describe('message/api', () => {
|
|
10
|
+
const message = message11;
|
|
11
|
+
const { messageId, ...messageBundle } = message11;
|
|
12
|
+
|
|
13
|
+
const getResolvedMessageValue = () => ({
|
|
14
|
+
data: {
|
|
15
|
+
messages: [message],
|
|
16
|
+
users: [],
|
|
17
|
+
files: [],
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('createMessage', () => {
|
|
22
|
+
test('should return message', async () => {
|
|
23
|
+
client.http.post = jest.fn().mockResolvedValue(getResolvedMessageValue());
|
|
24
|
+
const { data } = await createMessage(messageBundle);
|
|
25
|
+
expect(data).toStrictEqual(message);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('should throw an error if request fails', async () => {
|
|
29
|
+
client.http.post = jest.fn().mockRejectedValueOnce(new Error('error'));
|
|
30
|
+
await expect(createMessage(messageBundle)).rejects.toThrow('error');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('createMessage.optimistically', () => {
|
|
35
|
+
beforeEach(() => enableCache());
|
|
36
|
+
afterEach(() => disableCache());
|
|
37
|
+
|
|
38
|
+
test('should create cache after created message', () => {
|
|
39
|
+
const optimisticId = createMessage.optimistically(messageBundle)?.data?.messageId;
|
|
40
|
+
expect(pullFromCache(['message', 'get', optimisticId])?.data).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('should increase message count after created message', () => {
|
|
44
|
+
const { channelId } = messageBundle;
|
|
45
|
+
|
|
46
|
+
const currentCount =
|
|
47
|
+
pullFromCache<Amity.Channel>(['channel', 'get', channelId])?.data?.messageCount ?? 0;
|
|
48
|
+
|
|
49
|
+
createMessage.optimistically(messageBundle);
|
|
50
|
+
|
|
51
|
+
expect(
|
|
52
|
+
pullFromCache<Amity.Channel>(['channel', 'get', channelId])?.data?.messageCount,
|
|
53
|
+
).toEqual(currentCount + 1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('should fire event `onMessageCreated`', () => {
|
|
57
|
+
expect.assertions(1);
|
|
58
|
+
let dispose;
|
|
59
|
+
|
|
60
|
+
const callback = new Promise(resolve => {
|
|
61
|
+
dispose = onMessageCreated(resolve);
|
|
62
|
+
}).finally(dispose);
|
|
63
|
+
|
|
64
|
+
createMessage.optimistically(messageBundle);
|
|
65
|
+
return expect(callback).resolves.not.toThrow();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('should define object as unsynced object', async () => {
|
|
69
|
+
const data = { channelId: 'test', data: { text: 'text' } };
|
|
70
|
+
|
|
71
|
+
const message = createMessage.optimistically(data);
|
|
72
|
+
|
|
73
|
+
expect(message?.cachedAt).toEqual(UNSYNCED_OBJECT_CACHED_AT_VALUE);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getActiveClient } from '~/client/api';
|
|
2
|
+
import { createEventSubscriber } from '~/core/events';
|
|
3
|
+
|
|
4
|
+
import { ingestInCache } from '~/cache/api/ingestInCache';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ```js
|
|
8
|
+
* import { onMessageFetched } from '@amityco/ts-sdk'
|
|
9
|
+
* const dispose = onMessageFetched(message => {
|
|
10
|
+
* // ...
|
|
11
|
+
* })
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Fired when an {@link Amity.Message} has been fetched
|
|
15
|
+
*
|
|
16
|
+
* @param callback The function to call when the event was fired
|
|
17
|
+
* @returns an {@link Amity.Unsubscriber} function to stop listening
|
|
18
|
+
*
|
|
19
|
+
* @category Message Events
|
|
20
|
+
*/
|
|
21
|
+
export const onMessageFetched = (callback: Amity.Listener<Amity.Message>): Amity.Unsubscriber => {
|
|
22
|
+
const client = getActiveClient();
|
|
23
|
+
|
|
24
|
+
const filter = (payload: Amity.MessagePayload) => {
|
|
25
|
+
ingestInCache(payload);
|
|
26
|
+
callback(payload.messages[0]);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return createEventSubscriber(client, 'message/onMessageFetched', 'v3.message.didFetch', filter);
|
|
30
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ASCApiError } from '~/core/errors';
|
|
2
|
+
import { getActiveClient } from '~/client/api';
|
|
3
|
+
import { createQuery, runQuery } from '~/core/query';
|
|
4
|
+
import {
|
|
5
|
+
UNSYNCED_OBJECT_CACHED_AT_VALUE,
|
|
6
|
+
LIVE_OBJECT_ENABLE_CACHE_MESSAGE,
|
|
7
|
+
UNSYNCED_OBJECT_CACHED_AT_MESSAGE,
|
|
8
|
+
} from '~/utils/constants';
|
|
9
|
+
|
|
10
|
+
import { getMessage } from '../api';
|
|
11
|
+
import { onMessageUpdated, onMessageDeleted, onMessageFetched } from '../events';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ```js
|
|
15
|
+
* import { liveMessage } from '@amityco/ts-sdk'
|
|
16
|
+
*
|
|
17
|
+
* let messages = []
|
|
18
|
+
* const unsub = liveMessage(messageId, response => merge(messages, response.data))
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Observe all mutation on a given {@link Amity.Message}
|
|
22
|
+
*
|
|
23
|
+
* @param messageId the ID of the message to observe
|
|
24
|
+
* @param callback the function to call when new data are available
|
|
25
|
+
* @returns An {@link Amity.Unsubscriber} function to run when willing to stop observing the messages
|
|
26
|
+
*
|
|
27
|
+
* @category Message Live Object
|
|
28
|
+
*/
|
|
29
|
+
export const liveMessage = (
|
|
30
|
+
messageId: Amity.Message['messageId'],
|
|
31
|
+
callback: Amity.LiveObjectCallback<Amity.Message>,
|
|
32
|
+
): Amity.Unsubscriber => {
|
|
33
|
+
const { log, cache } = getActiveClient();
|
|
34
|
+
let message: Amity.Message;
|
|
35
|
+
let isUnsyncedObject = false;
|
|
36
|
+
|
|
37
|
+
if (!cache) {
|
|
38
|
+
console.log(LIVE_OBJECT_ENABLE_CACHE_MESSAGE);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const timestamp = Date.now();
|
|
42
|
+
log(`liveMessage(tmpid: ${timestamp}) > listen`);
|
|
43
|
+
|
|
44
|
+
const disposers: Amity.Unsubscriber[] = [];
|
|
45
|
+
|
|
46
|
+
const dispatcher = (data: Amity.LiveObject<Amity.Message>) => {
|
|
47
|
+
// we check updatedAt to see if the current snapshot is new so
|
|
48
|
+
// we won't send old data back, only fresh new data
|
|
49
|
+
if (message && data.data?.updatedAt === message?.updatedAt && data.origin !== 'server') return;
|
|
50
|
+
|
|
51
|
+
callback(data);
|
|
52
|
+
message = data.data;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const realtimeRouter = (message: Amity.Message, action: Amity.MessageActionType) => {
|
|
56
|
+
dispatcher({ loading: false, data: message, origin: 'event' });
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const onFetch = () => {
|
|
60
|
+
const query = createQuery(getMessage, messageId, true);
|
|
61
|
+
|
|
62
|
+
runQuery(query, ({ loading, data, error, origin, cachedAt }) => {
|
|
63
|
+
if (cachedAt === UNSYNCED_OBJECT_CACHED_AT_VALUE) {
|
|
64
|
+
dispatcher({
|
|
65
|
+
data,
|
|
66
|
+
origin,
|
|
67
|
+
loading: false,
|
|
68
|
+
error: new ASCApiError(
|
|
69
|
+
UNSYNCED_OBJECT_CACHED_AT_MESSAGE,
|
|
70
|
+
Amity.ClientError.DISALOOW_UNSYNCED_OBJECT,
|
|
71
|
+
Amity.ErrorLevel.ERROR,
|
|
72
|
+
),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
isUnsyncedObject = true;
|
|
76
|
+
disposers.forEach(fn => fn());
|
|
77
|
+
} else if (!isUnsyncedObject) {
|
|
78
|
+
dispatcher({ loading, data, origin, error });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (error) {
|
|
82
|
+
// Terminate observe functionality on error
|
|
83
|
+
disposers.forEach(fn => fn());
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
disposers.push(
|
|
89
|
+
onMessageFetched(message => realtimeRouter(message, 'onFetch')),
|
|
90
|
+
onMessageUpdated(message => realtimeRouter(message, 'onUpdate')),
|
|
91
|
+
onMessageDeleted(message => realtimeRouter(message, 'onDelete')),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
onFetch();
|
|
95
|
+
|
|
96
|
+
return () => {
|
|
97
|
+
log(`liveMessage(tmpid: ${timestamp}) > dispose`);
|
|
98
|
+
disposers.forEach(fn => fn());
|
|
99
|
+
};
|
|
100
|
+
};
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
ENABLE_CACHE_MESSAGE,
|
|
11
11
|
} from '~/utils/constants';
|
|
12
12
|
|
|
13
|
-
import { onMessageCreated, onMessageDeleted, onMessageUpdated } from '../events';
|
|
13
|
+
import { onMessageCreated, onMessageDeleted, onMessageUpdated, onMessageFetched } from '../events';
|
|
14
14
|
import { queryMessages } from '../api';
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -126,6 +126,8 @@ export const liveMessages = (
|
|
|
126
126
|
};
|
|
127
127
|
|
|
128
128
|
disposers.push(
|
|
129
|
+
onMessageFetched(message => realtimeRouter(message, 'onFetch')),
|
|
130
|
+
|
|
129
131
|
onMessageCreated(message => realtimeRouter(message, 'onCreate')),
|
|
130
132
|
onMessageDeleted(message => realtimeRouter(message, 'onDelete')),
|
|
131
133
|
|
|
@@ -34,6 +34,10 @@ export const observeMessage = <Events extends ['onFetch', 'onUpdate', 'onDelete'
|
|
|
34
34
|
const timestamp = Date.now();
|
|
35
35
|
log(`observeMessage(tmpid: ${timestamp}) > listen`);
|
|
36
36
|
|
|
37
|
+
console.log(
|
|
38
|
+
'observeMessage will be deprecated on 15th November 2022, please use liveMessage instead.',
|
|
39
|
+
);
|
|
40
|
+
|
|
37
41
|
// wrapper function to make sure
|
|
38
42
|
const router = (result: Amity.Snapshot<Amity.Message>, action: Events[number]) => {
|
|
39
43
|
// filter function
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ASCApiError } from '~/core/errors';
|
|
2
|
+
import { disableCache, enableCache, pushToCache } from '~/cache/api';
|
|
3
|
+
import { pushToTombstone } from '~/cache/api/pushToTombstone';
|
|
4
|
+
import { isInTombstone } from '~/cache/api/isInTombstone';
|
|
5
|
+
|
|
6
|
+
import { client, message11 } from '~/utils/tests';
|
|
7
|
+
|
|
8
|
+
import { getMessage } from '../../api/getMessage';
|
|
9
|
+
|
|
10
|
+
const message = message11;
|
|
11
|
+
const getResolvedMessageValue = () => ({
|
|
12
|
+
data: {
|
|
13
|
+
messages: [message],
|
|
14
|
+
users: [],
|
|
15
|
+
files: [],
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('getMessage', () => {
|
|
20
|
+
test('it should return message', async () => {
|
|
21
|
+
client.http.get = jest.fn().mockResolvedValue(getResolvedMessageValue());
|
|
22
|
+
|
|
23
|
+
const { data } = await getMessage(message.messageId);
|
|
24
|
+
|
|
25
|
+
expect(data).toStrictEqual(message);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('it should update cache after fetching message from server', async () => {
|
|
29
|
+
enableCache();
|
|
30
|
+
client.http.get = jest.fn().mockResolvedValue(getResolvedMessageValue());
|
|
31
|
+
await getMessage(message.messageId);
|
|
32
|
+
|
|
33
|
+
expect(getMessage.locally(message.messageId)).toBeDefined();
|
|
34
|
+
|
|
35
|
+
disableCache();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('it should throw an error if request fails', async () => {
|
|
39
|
+
client.http.get = jest.fn().mockRejectedValueOnce(new Error('error'));
|
|
40
|
+
|
|
41
|
+
await expect(getMessage(message.messageId)).rejects.toThrow('error');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('getMessage locally', () => {
|
|
46
|
+
test('it should return cached message', async () => {
|
|
47
|
+
enableCache();
|
|
48
|
+
pushToCache(['message', 'get', message.messageId], message, { cachedAt: Date.now() });
|
|
49
|
+
|
|
50
|
+
client.http.get = jest
|
|
51
|
+
.fn()
|
|
52
|
+
.mockRejectedValueOnce(
|
|
53
|
+
new ASCApiError('not found!', Amity.ServerError.ITEM_NOT_FOUND, Amity.ErrorLevel.ERROR),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
await expect(getMessage(message.messageId)).rejects.toThrow();
|
|
57
|
+
|
|
58
|
+
expect(getMessage.locally(message.messageId)).toBeUndefined();
|
|
59
|
+
disableCache();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('it should return undefined if message not in cache', () => {
|
|
63
|
+
expect(getMessage.locally('non-existent-message')).toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('it should return undefined if cache not enabled', () => {
|
|
67
|
+
expect(getMessage.locally(message.messageId)).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('getMessage tombostone', () => {
|
|
72
|
+
test('it should add message to tombostone if 404', async () => {
|
|
73
|
+
enableCache();
|
|
74
|
+
client.http.get = jest
|
|
75
|
+
.fn()
|
|
76
|
+
.mockRejectedValueOnce(
|
|
77
|
+
new ASCApiError('not found!', Amity.ServerError.ITEM_NOT_FOUND, Amity.ErrorLevel.ERROR),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
await expect(getMessage(message.messageId)).rejects.toThrow();
|
|
81
|
+
expect(() => {
|
|
82
|
+
isInTombstone('message', message.messageId);
|
|
83
|
+
}).toThrow();
|
|
84
|
+
|
|
85
|
+
disableCache();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('it should throw error if message not found in tombostone', async () => {
|
|
89
|
+
enableCache();
|
|
90
|
+
|
|
91
|
+
pushToTombstone('message', message.messageId);
|
|
92
|
+
await expect(getMessage(message.messageId)).rejects.toThrow();
|
|
93
|
+
|
|
94
|
+
disableCache();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { ASCApiError } from '~/core/errors';
|
|
2
|
+
import { disableCache, enableCache } from '~/cache/api';
|
|
3
|
+
import {
|
|
4
|
+
LIVE_OBJECT_ENABLE_CACHE_MESSAGE,
|
|
5
|
+
UNSYNCED_OBJECT_CACHED_AT_MESSAGE,
|
|
6
|
+
} from '~/utils/constants';
|
|
7
|
+
import { liveMessage } from '../../observers/liveMessage';
|
|
8
|
+
import { createMessage } from '../../api';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
pause,
|
|
12
|
+
client,
|
|
13
|
+
connectClient,
|
|
14
|
+
disconnectClient,
|
|
15
|
+
messageQueryResponse,
|
|
16
|
+
message11 as defaultMessage,
|
|
17
|
+
} from '~/utils/tests';
|
|
18
|
+
|
|
19
|
+
const getSnapshot = () => {
|
|
20
|
+
return {
|
|
21
|
+
loading: true,
|
|
22
|
+
error: undefined as any,
|
|
23
|
+
data: undefined as undefined | Amity.Message,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('liveMessage', () => {
|
|
28
|
+
beforeAll(async () => {
|
|
29
|
+
await connectClient();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterAll(async () => {
|
|
33
|
+
await disconnectClient();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
beforeEach(async () => {
|
|
37
|
+
enableCache();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterEach(disableCache);
|
|
41
|
+
|
|
42
|
+
test('it should show message if cache not enabled', () => {
|
|
43
|
+
/*
|
|
44
|
+
* NOTE: at the time of writing this test cache is diabled by default, but
|
|
45
|
+
* there is a proposal to enable cache by default. So I'm disabling cache
|
|
46
|
+
* here to ensure cache is disabled
|
|
47
|
+
*/
|
|
48
|
+
disableCache();
|
|
49
|
+
|
|
50
|
+
const callback = jest.fn();
|
|
51
|
+
jest.spyOn(global.console, 'log');
|
|
52
|
+
client.http.get = jest.fn();
|
|
53
|
+
|
|
54
|
+
// call liveMessage check if mocked console get's called with the correct
|
|
55
|
+
// message
|
|
56
|
+
client.use();
|
|
57
|
+
liveMessage(defaultMessage.messageId, callback);
|
|
58
|
+
|
|
59
|
+
expect(console.log).toBeCalledWith(LIVE_OBJECT_ENABLE_CACHE_MESSAGE);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('it should return message live object', async () => {
|
|
63
|
+
const snapshot = getSnapshot();
|
|
64
|
+
|
|
65
|
+
// mock response of queryMessages and validate
|
|
66
|
+
const callback = jest.fn();
|
|
67
|
+
client.http.get = jest.fn().mockResolvedValue(messageQueryResponse);
|
|
68
|
+
|
|
69
|
+
liveMessage(defaultMessage.messageId, callback);
|
|
70
|
+
|
|
71
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
72
|
+
|
|
73
|
+
// check if cache data returned (should be empty)
|
|
74
|
+
expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
|
|
75
|
+
|
|
76
|
+
await pause();
|
|
77
|
+
|
|
78
|
+
expect(callback).toHaveBeenCalledTimes(2);
|
|
79
|
+
|
|
80
|
+
snapshot.loading = false;
|
|
81
|
+
snapshot.data = defaultMessage;
|
|
82
|
+
|
|
83
|
+
expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('it should return error on api failure', async () => {
|
|
87
|
+
const snapshot = getSnapshot();
|
|
88
|
+
const error = new ASCApiError(
|
|
89
|
+
'server error!',
|
|
90
|
+
Amity.ServerError.ITEM_NOT_FOUND,
|
|
91
|
+
Amity.ErrorLevel.ERROR,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// mock response of queryMessages and validate
|
|
95
|
+
const callback = jest.fn();
|
|
96
|
+
client.http.get = jest.fn().mockRejectedValue(error);
|
|
97
|
+
|
|
98
|
+
liveMessage(defaultMessage.messageId, callback);
|
|
99
|
+
|
|
100
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
101
|
+
|
|
102
|
+
// check if cache data returned (should be empty)
|
|
103
|
+
expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
|
|
104
|
+
|
|
105
|
+
await pause();
|
|
106
|
+
|
|
107
|
+
expect(callback).toHaveBeenCalledTimes(2);
|
|
108
|
+
|
|
109
|
+
snapshot.error = error;
|
|
110
|
+
snapshot.loading = false;
|
|
111
|
+
expect(callback).toHaveBeenLastCalledWith(expect.objectContaining(snapshot));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('it should terminate updates on api failure', async () => {
|
|
115
|
+
const snapshot = getSnapshot();
|
|
116
|
+
const error = new ASCApiError(
|
|
117
|
+
'server error!',
|
|
118
|
+
Amity.ServerError.ITEM_NOT_FOUND,
|
|
119
|
+
Amity.ErrorLevel.ERROR,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// mock response of queryMessages and validate
|
|
123
|
+
const callback = jest.fn();
|
|
124
|
+
client.http.get = jest.fn().mockRejectedValue(error);
|
|
125
|
+
|
|
126
|
+
liveMessage(defaultMessage.messageId, callback);
|
|
127
|
+
|
|
128
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
129
|
+
|
|
130
|
+
// check if cache data returned (should be empty)
|
|
131
|
+
expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
|
|
132
|
+
|
|
133
|
+
await pause();
|
|
134
|
+
|
|
135
|
+
expect(callback).toHaveBeenCalledTimes(2);
|
|
136
|
+
|
|
137
|
+
snapshot.error = error;
|
|
138
|
+
snapshot.loading = false;
|
|
139
|
+
expect(callback).toHaveBeenLastCalledWith(expect.objectContaining(snapshot));
|
|
140
|
+
|
|
141
|
+
const updatedAt = new Date(defaultMessage?.updatedAt ?? new Date());
|
|
142
|
+
updatedAt.setDate(updatedAt.getDate() + 7);
|
|
143
|
+
|
|
144
|
+
const updatedMessage = {
|
|
145
|
+
...defaultMessage,
|
|
146
|
+
updatedAt: updatedAt.toISOString(),
|
|
147
|
+
};
|
|
148
|
+
client.emitter.emit('v3.message.didUpdate', { messages: [updatedMessage] });
|
|
149
|
+
|
|
150
|
+
delete snapshot.error;
|
|
151
|
+
snapshot.loading = false;
|
|
152
|
+
snapshot.data = updatedMessage;
|
|
153
|
+
expect(callback).not.toHaveBeenNthCalledWith(3, expect.objectContaining(snapshot));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('it should not return updates on obtain once!', async () => {
|
|
157
|
+
const snapshot = getSnapshot();
|
|
158
|
+
|
|
159
|
+
// mock response of queryMessages and validate
|
|
160
|
+
const callback = jest.fn();
|
|
161
|
+
client.http.get = jest.fn().mockResolvedValue(messageQueryResponse);
|
|
162
|
+
|
|
163
|
+
const unsub = liveMessage(defaultMessage.messageId, callback);
|
|
164
|
+
|
|
165
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
166
|
+
|
|
167
|
+
// check if cache data returned (should be empty)
|
|
168
|
+
expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
|
|
169
|
+
|
|
170
|
+
await pause();
|
|
171
|
+
|
|
172
|
+
expect(callback).toHaveBeenCalledTimes(2);
|
|
173
|
+
|
|
174
|
+
unsub();
|
|
175
|
+
|
|
176
|
+
const updatedAt = new Date(defaultMessage?.updatedAt ?? new Date());
|
|
177
|
+
updatedAt.setDate(updatedAt.getDate() + 7);
|
|
178
|
+
|
|
179
|
+
const updatedMessage = {
|
|
180
|
+
...defaultMessage,
|
|
181
|
+
updatedAt: updatedAt.toISOString(),
|
|
182
|
+
};
|
|
183
|
+
client.emitter.emit('v3.message.didUpdate', { messages: [updatedMessage] });
|
|
184
|
+
|
|
185
|
+
delete snapshot.error;
|
|
186
|
+
snapshot.loading = false;
|
|
187
|
+
snapshot.data = updatedMessage;
|
|
188
|
+
expect(callback).not.toHaveBeenNthCalledWith(3, expect.objectContaining(snapshot));
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const events: [string, keyof Amity.Events][] = [
|
|
192
|
+
['it should return updated message on onFetch', 'v3.message.didFetch'],
|
|
193
|
+
['it should return updated message on onUpdate', 'v3.message.didUpdate'],
|
|
194
|
+
['it should return updated message on onDelete', 'v3.message.didDelete'],
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
test.each(events)('%s', async (test, event) => {
|
|
198
|
+
const snapshot = getSnapshot();
|
|
199
|
+
|
|
200
|
+
// mock response of queryMessages and validate
|
|
201
|
+
const callback = jest.fn();
|
|
202
|
+
client.http.get = jest.fn().mockResolvedValue(messageQueryResponse);
|
|
203
|
+
|
|
204
|
+
liveMessage(defaultMessage.messageId, callback);
|
|
205
|
+
|
|
206
|
+
await pause();
|
|
207
|
+
|
|
208
|
+
expect(callback).toHaveBeenCalledTimes(2);
|
|
209
|
+
|
|
210
|
+
const updatedAt = new Date(defaultMessage?.updatedAt ?? new Date());
|
|
211
|
+
updatedAt.setDate(updatedAt.getDate() + 7);
|
|
212
|
+
|
|
213
|
+
const updatedMessage = {
|
|
214
|
+
...defaultMessage,
|
|
215
|
+
updatedAt: updatedAt.toISOString(),
|
|
216
|
+
};
|
|
217
|
+
client.emitter.emit(event, { messages: [updatedMessage] });
|
|
218
|
+
|
|
219
|
+
delete snapshot.error;
|
|
220
|
+
snapshot.loading = false;
|
|
221
|
+
snapshot.data = updatedMessage;
|
|
222
|
+
expect(callback).toHaveBeenNthCalledWith(3, expect.objectContaining(snapshot));
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('it should terminate updates for unsynced object', async () => {
|
|
226
|
+
const callback = jest.fn();
|
|
227
|
+
client.http.get = jest.fn();
|
|
228
|
+
const snapshot = getSnapshot();
|
|
229
|
+
const data = { channelId: 'test', data: { text: 'text' } };
|
|
230
|
+
|
|
231
|
+
const message = createMessage.optimistically(data);
|
|
232
|
+
|
|
233
|
+
const error = new ASCApiError(
|
|
234
|
+
UNSYNCED_OBJECT_CACHED_AT_MESSAGE,
|
|
235
|
+
Amity.ClientError.DISALOOW_UNSYNCED_OBJECT,
|
|
236
|
+
Amity.ErrorLevel.ERROR,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
liveMessage(message!.data.messageId, callback);
|
|
240
|
+
|
|
241
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
242
|
+
|
|
243
|
+
snapshot.error = error;
|
|
244
|
+
snapshot.loading = false;
|
|
245
|
+
snapshot.data = message!.data;
|
|
246
|
+
expect(callback).toHaveBeenLastCalledWith(expect.objectContaining(snapshot));
|
|
247
|
+
});
|
|
248
|
+
});
|
|
@@ -2,15 +2,15 @@ import { ASCApiError } from '~/core/errors';
|
|
|
2
2
|
import { disableCache, enableCache } from '~/cache/api';
|
|
3
3
|
import { ENABLE_CACHE_MESSAGE } from '~/utils/constants';
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { queryMessages } from '../../api';
|
|
6
|
+
import { liveMessages } from '../../observers';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
client,
|
|
10
10
|
connectClient,
|
|
11
11
|
disconnectClient,
|
|
12
12
|
messages,
|
|
13
|
-
|
|
13
|
+
message11 as defaultMessage,
|
|
14
14
|
} from '~/utils/tests';
|
|
15
15
|
|
|
16
16
|
describe('liveMessages', () => {
|
|
@@ -222,6 +222,32 @@ describe('liveMessages', () => {
|
|
|
222
222
|
);
|
|
223
223
|
});
|
|
224
224
|
|
|
225
|
+
test('it should add new message to collection onFetch', async () => {
|
|
226
|
+
const callback = jest.fn();
|
|
227
|
+
client.http.get = jest.fn().mockResolvedValue({
|
|
228
|
+
data: {
|
|
229
|
+
messages: returnValue,
|
|
230
|
+
paging: {},
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
liveMessages({ channelId }, callback);
|
|
235
|
+
|
|
236
|
+
await expect(queryMessages({ channelId })).resolves.toBeTruthy();
|
|
237
|
+
expect(callback).toHaveBeenCalledTimes(2);
|
|
238
|
+
|
|
239
|
+
client.emitter.emit('v3.message.didFetch', { messages: [message] });
|
|
240
|
+
|
|
241
|
+
expect(callback).toHaveBeenNthCalledWith(
|
|
242
|
+
3,
|
|
243
|
+
expect.objectContaining({
|
|
244
|
+
data: [message],
|
|
245
|
+
error: undefined,
|
|
246
|
+
loading: false,
|
|
247
|
+
}),
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
|
|
225
251
|
test('it should add new message to collection onCreate', async () => {
|
|
226
252
|
const newMessage = {
|
|
227
253
|
...message,
|