@amityco/ts-sdk 7.5.4-db55aa2.0 → 7.6.0
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/.env +26 -26
- package/dist/@types/domains/channel.d.ts +1 -0
- package/dist/@types/domains/channel.d.ts.map +1 -1
- package/dist/@types/domains/group.d.ts +2 -0
- package/dist/@types/domains/group.d.ts.map +1 -1
- package/dist/channelRepository/channelMembership/observers/getMembers/ChannelMemberLiveCollectionController.d.ts.map +1 -1
- package/dist/channelRepository/channelMembership/observers/searchMembers/SearchChannelMemberLiveCollectionController.d.ts.map +1 -1
- package/dist/channelRepository/channelMembership/observers/searchMembers/SearchChannelMemberQueryStreamController.d.ts +2 -2
- package/dist/channelRepository/channelMembership/observers/searchMembers/SearchChannelMemberQueryStreamController.d.ts.map +1 -1
- package/dist/client/api/index.d.ts +1 -0
- package/dist/client/api/index.d.ts.map +1 -1
- package/dist/client/api/resumeSession.d.ts +32 -0
- package/dist/client/api/resumeSession.d.ts.map +1 -0
- package/dist/client/api/tests/resumeSession.test.d.ts +2 -0
- package/dist/client/api/tests/resumeSession.test.d.ts.map +1 -0
- package/dist/index.cjs.js +218 -17
- package/dist/index.esm.js +217 -17
- package/dist/index.umd.js +4 -4
- package/dist/utils/linkedObject/channelLinkedObject.d.ts.map +1 -1
- package/dist/utils/linkedObject/channelMemberLinkedObject.d.ts +2 -0
- package/dist/utils/linkedObject/channelMemberLinkedObject.d.ts.map +1 -0
- package/dist/utils/linkedObject/index.d.ts +1 -0
- package/dist/utils/linkedObject/index.d.ts.map +1 -1
- package/dist/utils/tests/dummy/community.d.ts +2 -0
- package/dist/utils/tests/dummy/community.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/@types/domains/channel.ts +1 -0
- package/src/@types/domains/group.ts +2 -0
- package/src/channelRepository/channelMembership/observers/getMembers/ChannelMemberLiveCollectionController.ts +3 -1
- package/src/channelRepository/channelMembership/observers/searchMembers/SearchChannelMemberLiveCollectionController.ts +2 -1
- package/src/channelRepository/channelMembership/observers/searchMembers/SearchChannelMemberQueryStreamController.ts +2 -2
- package/src/client/api/index.ts +1 -0
- package/src/client/api/resumeSession.ts +282 -0
- package/src/client/api/tests/resumeSession.test.ts +173 -0
- package/src/utils/linkedObject/channelLinkedObject.ts +19 -0
- package/src/utils/linkedObject/channelMemberLinkedObject.ts +20 -0
- package/src/utils/linkedObject/index.ts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channelLinkedObject.d.ts","sourceRoot":"","sources":["../../../src/utils/linkedObject/channelLinkedObject.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"channelLinkedObject.d.ts","sourceRoot":"","sources":["../../../src/utils/linkedObject/channelLinkedObject.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,mBAAmB,YAAa,MAAM,eAAe,KAAG,MAAM,OAqB1E,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channelMemberLinkedObject.d.ts","sourceRoot":"","sources":["../../../src/utils/linkedObject/channelMemberLinkedObject.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,yBAAyB,kBACrB,MAAM,UAAU,CAAC,SAAS,CAAC,KACzC,MAAM,UAAU,CAAC,SAAS,CAc5B,CAAC"}
|
|
@@ -15,5 +15,6 @@ export declare const LinkedObject: {
|
|
|
15
15
|
community: (community: Amity.InternalCommunity) => Amity.Community;
|
|
16
16
|
invitation: (invitation: Amity.InternalInvitation) => Amity.Invitation;
|
|
17
17
|
joinRequest: (joinRequest: Amity.InternalJoinRequest) => Amity.JoinRequest;
|
|
18
|
+
channelMember: (channelMember: Amity.Membership<"channel">) => Amity.Membership<"channel">;
|
|
18
19
|
};
|
|
19
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/linkedObject/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/linkedObject/index.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;CAkBxB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"community.d.ts","sourceRoot":"","sources":["../../../../src/utils/tests/dummy/community.ts"],"names":[],"mappings":"AAqBA,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,SAajC,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,SAoB/B,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,SAI/B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAa5D,CAAC;AAEF,eAAO,MAAM,uBAAuB,+BAAuD,CAAC;AAE5F,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAO5D,CAAC;AAEF,eAAO,MAAM,uBAAuB,+BAAuD,CAAC;AAE5F,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAO5D,CAAC;AAEF,eAAO,MAAM,uBAAuB,+BAAuD,CAAC;AAE5F,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAM5D,CAAC;AAEF,eAAO,MAAM,qBAAqB,+BAAuD,CAAC;AAE1F,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAK5D,CAAC;AAEF,eAAO,MAAM,mBAAmB,+BAAuD,CAAC;AAExF,eAAO,MAAM,qBAAqB;;;;;;;CAOjC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,gBAOpC,CAAC;AAEF,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;CAUtC,CAAC;AAEF,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;CAU3C,CAAC;AAEF,eAAO,MAAM,kBAAkB
|
|
1
|
+
{"version":3,"file":"community.d.ts","sourceRoot":"","sources":["../../../../src/utils/tests/dummy/community.ts"],"names":[],"mappings":"AAqBA,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,SAajC,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,SAoB/B,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,SAI/B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAa5D,CAAC;AAEF,eAAO,MAAM,uBAAuB,+BAAuD,CAAC;AAE5F,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAO5D,CAAC;AAEF,eAAO,MAAM,uBAAuB,+BAAuD,CAAC;AAE5F,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAO5D,CAAC;AAEF,eAAO,MAAM,uBAAuB,+BAAuD,CAAC;AAE5F,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAM5D,CAAC;AAEF,eAAO,MAAM,qBAAqB,+BAAuD,CAAC;AAE1F,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAK5D,CAAC;AAEF,eAAO,MAAM,mBAAmB,+BAAuD,CAAC;AAExF,eAAO,MAAM,qBAAqB;;;;;;;CAOjC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,gBAOpC,CAAC;AAEF,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;CAUtC,CAAC;AAEF,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;CAU3C,CAAC;AAEF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;GAG9B,CAAC;AAEF,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;CAuBzB,CAAC;AAEF,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;CAuBzB,CAAC;AAEF,eAAO,MAAM,sBAAsB;;CAqBlC,CAAC;AAEF,eAAO,MAAM,2BAA2B;;CAqBvC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amityco/ts-sdk",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.6.0",
|
|
4
4
|
"license": "CC-BY-ND-4.0",
|
|
5
5
|
"author": "amity.co <developers@amity.co> (https://amity.co)",
|
|
6
6
|
"description": "Amity Social Cloud Typescript SDK",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"debug": "^4.3.4",
|
|
64
64
|
"hls.js": "^1.4.10",
|
|
65
65
|
"js-base64": "^3.7.2",
|
|
66
|
+
"jwt-decode": "^3.1.2",
|
|
66
67
|
"mitt": "^3.0.0",
|
|
67
68
|
"mqtt": "^4.3.7",
|
|
68
69
|
"object-hash": "^3.0.0",
|
|
@@ -15,6 +15,8 @@ declare global {
|
|
|
15
15
|
|
|
16
16
|
type Member<T extends Amity.GroupType> = {
|
|
17
17
|
userId: Amity.InternalUser['userId'];
|
|
18
|
+
userInternalId: Amity.InternalUser['userInternalId'];
|
|
19
|
+
userPublicId: Amity.InternalUser['userPublicId'];
|
|
18
20
|
} & (T extends 'channel'
|
|
19
21
|
? {
|
|
20
22
|
channelId: Amity.Channel['channelId'];
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from '~/core/query';
|
|
24
24
|
import { prepareChannelPayload } from '~/channelRepository/utils';
|
|
25
25
|
import { onUserDeleted } from '~/channelRepository/events/onUserDeleted';
|
|
26
|
+
import { LinkedObject } from '~/utils/linkedObject';
|
|
26
27
|
|
|
27
28
|
export class ChannelMemberLiveCollectionController extends LiveCollectionController<
|
|
28
29
|
'channelUser',
|
|
@@ -100,7 +101,8 @@ export class ChannelMemberLiveCollectionController extends LiveCollectionControl
|
|
|
100
101
|
collection.data
|
|
101
102
|
.map(id => pullFromCache<Amity.Membership<'channel'>>(['channelUsers', 'get', id])!)
|
|
102
103
|
.filter(Boolean)
|
|
103
|
-
.map(({ data }) => data)
|
|
104
|
+
.map(({ data }) => data)
|
|
105
|
+
.map(LinkedObject.channelMember) ?? [],
|
|
104
106
|
);
|
|
105
107
|
|
|
106
108
|
if (!this.shouldNotify(data) && origin === 'event') return;
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { filterByPropIntersection, filterBySearchTerm } from '~/core/query';
|
|
18
18
|
import { prepareChannelPayload } from '~/channelRepository/utils';
|
|
19
19
|
import { onUserDeleted } from '~/channelRepository/events/onUserDeleted';
|
|
20
|
+
import { LinkedObject } from '~/utils/linkedObject';
|
|
20
21
|
|
|
21
22
|
export class SearchChannelMemberLiveCollectionController extends LiveCollectionController<
|
|
22
23
|
'channelUser',
|
|
@@ -94,7 +95,7 @@ export class SearchChannelMemberLiveCollectionController extends LiveCollectionC
|
|
|
94
95
|
collection.data
|
|
95
96
|
.map(id => pullFromCache<Amity.Membership<'channel'>>(['channelUsers', 'get', id])!)
|
|
96
97
|
.filter(Boolean)
|
|
97
|
-
.map(({ data }) => data) ?? [],
|
|
98
|
+
.map(({ data }) => LinkedObject.channelMember(data) ?? []),
|
|
98
99
|
);
|
|
99
100
|
|
|
100
101
|
if (!this.shouldNotify(data) && origin === 'event') return;
|
|
@@ -72,7 +72,7 @@ export class SearchChannelMemberQueryStreamController extends QueryStreamControl
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
reactor(action: string) {
|
|
75
|
-
return (channel: Amity.
|
|
75
|
+
return (channel: Amity.StaticInternalChannel, channelMember: Amity.Membership<'channel'>) => {
|
|
76
76
|
if (this.query.channelId !== channelMember.channelId) return;
|
|
77
77
|
|
|
78
78
|
const collection = pullFromCache<Amity.ChannelMembersLiveCollectionCache>(
|
|
@@ -100,7 +100,7 @@ export class SearchChannelMemberQueryStreamController extends QueryStreamControl
|
|
|
100
100
|
createSubscriber: {
|
|
101
101
|
fn: (
|
|
102
102
|
reactor: (
|
|
103
|
-
channel: Amity.
|
|
103
|
+
channel: Amity.StaticInternalChannel,
|
|
104
104
|
channelMember: Amity.Membership<'channel'>,
|
|
105
105
|
) => void,
|
|
106
106
|
) => Amity.Unsubscriber;
|
package/src/client/api/index.ts
CHANGED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import jwtDecode from 'jwt-decode';
|
|
2
|
+
/* eslint-disable no-param-reassign */
|
|
3
|
+
import { modifyMqttConnection } from '~/client/utils/modifyMqttConnection';
|
|
4
|
+
/* eslint-disable require-atomic-updates */
|
|
5
|
+
import { proxyWebsocketEvents } from '~/core/events';
|
|
6
|
+
import { onChannelDeleted } from '~/channelRepository/events/onChannelDeleted';
|
|
7
|
+
import { onChannelMemberBanned } from '~/channelRepository/events/onChannelMemberBanned';
|
|
8
|
+
|
|
9
|
+
import { markReadEngineOnLoginHandler } from '~/subChannelRepository/utils/markReadEngine';
|
|
10
|
+
import { onUserDeleted } from '~/userRepository/events/onUserDeleted';
|
|
11
|
+
|
|
12
|
+
import analyticsEngineOnLoginHandler from '~/analytic/utils/analyticsEngineOnLoginHandler';
|
|
13
|
+
import readReceiptSyncEngineOnLoginHandler from '~/client/utils/ReadReceiptSync/readReceiptSyncEngineOnLoginHandler';
|
|
14
|
+
import legacyReadReceiptSyncEngineOnLoginHandler from '~/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngineOnLoginHandler';
|
|
15
|
+
import objectResolverEngineOnLoginHandler from '~/client/utils/ObjectResolver/objectResolverEngineOnLoginHandler';
|
|
16
|
+
import { logout } from './logout';
|
|
17
|
+
|
|
18
|
+
import { getActiveClient } from './activeClient';
|
|
19
|
+
import { terminateClient } from './terminateClient';
|
|
20
|
+
import { setActiveUser } from './activeUser';
|
|
21
|
+
import { setSessionState } from './setSessionState';
|
|
22
|
+
|
|
23
|
+
import { onClientBanned } from '../events';
|
|
24
|
+
import { onTokenExpired } from '../events/onTokenExpired';
|
|
25
|
+
import { onTokenTerminated } from '../events/onTokenTerminated';
|
|
26
|
+
|
|
27
|
+
import { removeChannelMarkerCache } from '../utils/removeChannelMarkerCache';
|
|
28
|
+
import { initializeMessagePreviewSetting } from '../utils/messagePreviewEngine';
|
|
29
|
+
import { ASCError } from '~/core/errors';
|
|
30
|
+
import SessionWatcher from '../utils/SessionWatcher';
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
* declared earlier to accomodate case when logging in with a different user
|
|
34
|
+
* than the one already connected, in which case the existing subscriptions need
|
|
35
|
+
* to be cleared
|
|
36
|
+
*/
|
|
37
|
+
let subscriptions: Amity.Unsubscriber[] = [];
|
|
38
|
+
|
|
39
|
+
async function runMqtt() {
|
|
40
|
+
await modifyMqttConnection();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface Params {
|
|
44
|
+
userId: string;
|
|
45
|
+
token: {
|
|
46
|
+
issuedAt: string;
|
|
47
|
+
expiresAt: string;
|
|
48
|
+
accessToken: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const isSameUserId = (token: Params['token']['accessToken']) => {
|
|
53
|
+
const client = getActiveClient();
|
|
54
|
+
|
|
55
|
+
const decoded = jwtDecode<{
|
|
56
|
+
user: {
|
|
57
|
+
userId: string;
|
|
58
|
+
publicUserId: string;
|
|
59
|
+
deviceInfo: Amity.Device['deviceInfo'];
|
|
60
|
+
networkId: string;
|
|
61
|
+
displayName: string;
|
|
62
|
+
refreshToken: string;
|
|
63
|
+
};
|
|
64
|
+
}>(token);
|
|
65
|
+
|
|
66
|
+
return decoded?.user?.publicUserId === client.userId;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const validateAccessToken = async ({ token, userId }: Params) => {
|
|
70
|
+
const client = getActiveClient();
|
|
71
|
+
// begin establishing session
|
|
72
|
+
setSessionState(Amity.SessionStates.ESTABLISHING);
|
|
73
|
+
|
|
74
|
+
const {
|
|
75
|
+
data: { users },
|
|
76
|
+
} = await client.http.get(`/api/v3/users/${userId}`, {
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: `Bearer ${token.accessToken}`,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const user = users.find((u: Amity.User) => u.userId === userId);
|
|
83
|
+
|
|
84
|
+
client.http.defaults.headers.common.Authorization = `Bearer ${token.accessToken}`;
|
|
85
|
+
|
|
86
|
+
client.http.defaults.metadata = {
|
|
87
|
+
tokenExpiry: token.expiresAt,
|
|
88
|
+
isGlobalBanned: false,
|
|
89
|
+
isUserDeleted: false,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
client.upload.defaults.headers.common.Authorization = `Bearer ${token.accessToken}`;
|
|
93
|
+
|
|
94
|
+
client.upload.defaults.metadata = {
|
|
95
|
+
tokenExpiry: token.expiresAt,
|
|
96
|
+
isGlobalBanned: false,
|
|
97
|
+
isUserDeleted: false,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// manually setup the token for ws transport
|
|
101
|
+
if (client.ws) client.ws.io.opts.query = { token: token.accessToken };
|
|
102
|
+
|
|
103
|
+
client.token = token;
|
|
104
|
+
|
|
105
|
+
setSessionState(Amity.SessionStates.ESTABLISHED);
|
|
106
|
+
|
|
107
|
+
return user;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/* begin_public_function
|
|
111
|
+
id: client.resumeSession
|
|
112
|
+
*/
|
|
113
|
+
/**
|
|
114
|
+
* ```js
|
|
115
|
+
* import { resumeSession } from '@amityco/ts-sdk/client/api'
|
|
116
|
+
* const success = await resumeSession({
|
|
117
|
+
* userId: 'XYZ123456789',
|
|
118
|
+
* token: { accessToken: 'abc123', issuedAt: '2023-01-01T00:00:00Z', expiresAt: '2023-01-02T00:00:00Z' }
|
|
119
|
+
* })
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* Connects an {@link Amity.Client} instance to ASC servers using an existing access token
|
|
123
|
+
*
|
|
124
|
+
* @param params the connect parameters
|
|
125
|
+
* @param params.userId the user ID for the current session
|
|
126
|
+
* @param params.token the existing access token with its metadata
|
|
127
|
+
* @param sessionHandler the session handler for token renewal
|
|
128
|
+
* @param config optional configuration
|
|
129
|
+
* @returns a success boolean if connected
|
|
130
|
+
*
|
|
131
|
+
* @category Client API
|
|
132
|
+
* @async
|
|
133
|
+
*/
|
|
134
|
+
export const resumeSession = async (
|
|
135
|
+
params: Params,
|
|
136
|
+
sessionHandler: Amity.SessionHandler,
|
|
137
|
+
config?: Amity.ConnectClientConfig,
|
|
138
|
+
): Promise<boolean> => {
|
|
139
|
+
const client = getActiveClient();
|
|
140
|
+
let unsubWatcher: Amity.Unsubscriber;
|
|
141
|
+
|
|
142
|
+
client.log('client/api/resumeSession', {
|
|
143
|
+
apiKey: client.apiKey,
|
|
144
|
+
sessionState: client.sessionState,
|
|
145
|
+
...params,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Handle existing connected user
|
|
149
|
+
if (client.userId) {
|
|
150
|
+
if (client.userId === params.userId && isSameUserId(params.token.accessToken)) {
|
|
151
|
+
// Clear connections and listeners but preserve cache
|
|
152
|
+
if (client.mqtt && client.mqtt.connected) {
|
|
153
|
+
client.mqtt.disconnect();
|
|
154
|
+
}
|
|
155
|
+
if (client.ws && client.ws.connected) {
|
|
156
|
+
client.ws.disconnect();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Clear existing subscriptions
|
|
160
|
+
subscriptions.forEach(fn => fn());
|
|
161
|
+
subscriptions = [];
|
|
162
|
+
} else {
|
|
163
|
+
// Different user - do full logout
|
|
164
|
+
await logout();
|
|
165
|
+
|
|
166
|
+
// Remove subscription to ban and delete
|
|
167
|
+
subscriptions.forEach(fn => fn());
|
|
168
|
+
subscriptions = [];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const user = await validateAccessToken(params);
|
|
174
|
+
|
|
175
|
+
if (user == null) {
|
|
176
|
+
throw new ASCError(
|
|
177
|
+
`${params.userId} has not been found`,
|
|
178
|
+
Amity.ClientError.UNKNOWN_ERROR,
|
|
179
|
+
Amity.ErrorLevel.ERROR,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (user.isDeleted) {
|
|
184
|
+
terminateClient(Amity.TokenTerminationReason.USER_DELETED);
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (user.isGlobalBanned) {
|
|
189
|
+
terminateClient(Amity.TokenTerminationReason.GLOBAL_BAN);
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// FIXME: events are duplicated if connectClient is called few times without disconnectClient
|
|
194
|
+
// wire websocket events to our event emitter
|
|
195
|
+
proxyWebsocketEvents(client.ws, client.emitter);
|
|
196
|
+
|
|
197
|
+
client.ws?.open();
|
|
198
|
+
|
|
199
|
+
client.userId = user.userId;
|
|
200
|
+
|
|
201
|
+
client.sessionHandler = sessionHandler;
|
|
202
|
+
|
|
203
|
+
/*
|
|
204
|
+
* Cannot push to subscriptions as watcher needs to continue working even if
|
|
205
|
+
* token expires
|
|
206
|
+
*/
|
|
207
|
+
unsubWatcher = client.accessTokenExpiryWatcher(sessionHandler);
|
|
208
|
+
|
|
209
|
+
setActiveUser(user);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
/*
|
|
212
|
+
* if getting token failed session state reverts to initial state when app
|
|
213
|
+
* is first launched
|
|
214
|
+
*/
|
|
215
|
+
SessionWatcher.getInstance().setSessionState(Amity.SessionStates.NOT_LOGGED_IN);
|
|
216
|
+
|
|
217
|
+
// pass error down tree so the calling function handle it
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (config?.disableRTE !== true) {
|
|
222
|
+
runMqtt();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await initializeMessagePreviewSetting();
|
|
226
|
+
|
|
227
|
+
if (subscriptions.length === 0) {
|
|
228
|
+
subscriptions.push(
|
|
229
|
+
// GLOBAL_BAN
|
|
230
|
+
onClientBanned((_: Amity.UserPayload) => {
|
|
231
|
+
terminateClient(Amity.TokenTerminationReason.GLOBAL_BAN);
|
|
232
|
+
|
|
233
|
+
subscriptions.forEach(fn => fn());
|
|
234
|
+
|
|
235
|
+
unsubWatcher();
|
|
236
|
+
}),
|
|
237
|
+
|
|
238
|
+
onTokenTerminated(_ => {
|
|
239
|
+
terminateClient();
|
|
240
|
+
|
|
241
|
+
subscriptions.forEach(fn => fn());
|
|
242
|
+
|
|
243
|
+
unsubWatcher();
|
|
244
|
+
}),
|
|
245
|
+
|
|
246
|
+
onUserDeleted((user: Amity.InternalUser) => {
|
|
247
|
+
if (user.userId === client.userId) {
|
|
248
|
+
terminateClient(Amity.TokenTerminationReason.USER_DELETED);
|
|
249
|
+
|
|
250
|
+
subscriptions.forEach(fn => fn());
|
|
251
|
+
|
|
252
|
+
unsubWatcher();
|
|
253
|
+
}
|
|
254
|
+
}),
|
|
255
|
+
|
|
256
|
+
onTokenExpired(state => {
|
|
257
|
+
SessionWatcher.getInstance().setSessionState(state);
|
|
258
|
+
|
|
259
|
+
logout();
|
|
260
|
+
|
|
261
|
+
subscriptions.forEach(fn => fn());
|
|
262
|
+
}),
|
|
263
|
+
|
|
264
|
+
// NOTE: This is a temporary solution to handle the channel marker when the user is forced to leave
|
|
265
|
+
// the channel because currently backend can't handle this, so every time a user is banned from
|
|
266
|
+
// a channel or the channel is deleted the channel's unread count will not be reset to zero
|
|
267
|
+
onChannelDeleted(removeChannelMarkerCache),
|
|
268
|
+
onChannelMemberBanned(removeChannelMarkerCache),
|
|
269
|
+
|
|
270
|
+
markReadEngineOnLoginHandler(),
|
|
271
|
+
analyticsEngineOnLoginHandler(),
|
|
272
|
+
objectResolverEngineOnLoginHandler(),
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
if (client.useLegacyUnreadCount) {
|
|
276
|
+
subscriptions.push(readReceiptSyncEngineOnLoginHandler());
|
|
277
|
+
} else subscriptions.push(legacyReadReceiptSyncEngineOnLoginHandler());
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return true;
|
|
281
|
+
};
|
|
282
|
+
/* end_public_function */
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { ASCApiError } from '~/core/errors';
|
|
2
|
+
import { user11, disconnectClient } from '~/utils/tests';
|
|
3
|
+
import { resumeSession, createClient } from '..';
|
|
4
|
+
import { setActiveClient } from '../activeClient';
|
|
5
|
+
|
|
6
|
+
let client: Amity.Client;
|
|
7
|
+
|
|
8
|
+
const sessionHandler: Amity.SessionHandler = {
|
|
9
|
+
sessionWillRenewAccessToken(_) {
|
|
10
|
+
// do nothing
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const mockToken = {
|
|
15
|
+
accessToken: 'mock-access-token',
|
|
16
|
+
issuedAt: '2023-01-01T00:00:00Z',
|
|
17
|
+
expiresAt: '2025-01-01T00:00:00Z',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const mockUserResponse = {
|
|
21
|
+
data: {
|
|
22
|
+
users: [user11],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const onConnect = () =>
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
const CONNECT_PACKET = { type: 0, nsp: client.ws?.nsp };
|
|
29
|
+
|
|
30
|
+
client.ws?.emit('connect');
|
|
31
|
+
|
|
32
|
+
// simulate a connection ack packet from server
|
|
33
|
+
client.ws?.io.emit('packet', CONNECT_PACKET);
|
|
34
|
+
}, 50);
|
|
35
|
+
|
|
36
|
+
describe('resumeSession', () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
client = createClient('key', 'sg');
|
|
39
|
+
client.mqtt?.connect && (client.mqtt.connect = jest.fn());
|
|
40
|
+
client.mqtt?.subscribe && (client.mqtt.subscribe = jest.fn());
|
|
41
|
+
client.http.get = jest.fn().mockResolvedValueOnce(mockUserResponse);
|
|
42
|
+
|
|
43
|
+
setActiveClient(client);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(async () => {
|
|
47
|
+
if (client.sessionState === Amity.SessionStates.ESTABLISHED) await disconnectClient();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('it should connect client with access token', async () => {
|
|
51
|
+
onConnect().unref();
|
|
52
|
+
|
|
53
|
+
const received = await resumeSession(
|
|
54
|
+
{ userId: user11.userId, token: mockToken },
|
|
55
|
+
sessionHandler,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(received).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('it should establish connection', async () => {
|
|
62
|
+
onConnect().unref();
|
|
63
|
+
|
|
64
|
+
await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);
|
|
65
|
+
const { sessionState } = client;
|
|
66
|
+
|
|
67
|
+
expect(sessionState).toBe(Amity.SessionStates.ESTABLISHED);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('it should have session state establishing while connecting client', () => {
|
|
71
|
+
resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);
|
|
72
|
+
|
|
73
|
+
expect(client.sessionState).toBe(Amity.SessionStates.ESTABLISHING);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('it should have session state notLoggedIn on failure', async () => {
|
|
77
|
+
client.http.get = jest
|
|
78
|
+
.fn()
|
|
79
|
+
.mockRejectedValue(
|
|
80
|
+
new ASCApiError('unauthorized', Amity.ServerError.UNAUTHORIZED, Amity.ErrorLevel.FATAL),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
await expect(
|
|
84
|
+
resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler),
|
|
85
|
+
).rejects.toThrow('unauthorized');
|
|
86
|
+
expect(client.sessionState).toBe(Amity.SessionStates.NOT_LOGGED_IN);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('it should set authorization header with token', async () => {
|
|
90
|
+
onConnect().unref();
|
|
91
|
+
|
|
92
|
+
await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);
|
|
93
|
+
|
|
94
|
+
expect(client.http.defaults.headers.common.Authorization).toBe(
|
|
95
|
+
`Bearer ${mockToken.accessToken}`,
|
|
96
|
+
);
|
|
97
|
+
expect(client.upload.defaults.headers.common.Authorization).toBe(
|
|
98
|
+
`Bearer ${mockToken.accessToken}`,
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('it should set token metadata', async () => {
|
|
103
|
+
onConnect().unref();
|
|
104
|
+
|
|
105
|
+
await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);
|
|
106
|
+
|
|
107
|
+
expect(client.token).toEqual(mockToken);
|
|
108
|
+
expect(client.http.defaults.metadata).toEqual({
|
|
109
|
+
tokenExpiry: mockToken.expiresAt,
|
|
110
|
+
isGlobalBanned: false,
|
|
111
|
+
isUserDeleted: false,
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('it should call user endpoint to validate token', async () => {
|
|
116
|
+
onConnect().unref();
|
|
117
|
+
|
|
118
|
+
await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);
|
|
119
|
+
|
|
120
|
+
expect(client.http.get).toHaveBeenCalledWith(`/api/v3/users/${user11.userId}`);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('it should terminate session on ban', async () => {
|
|
124
|
+
onConnect().unref();
|
|
125
|
+
|
|
126
|
+
await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);
|
|
127
|
+
|
|
128
|
+
// ban user
|
|
129
|
+
client.emitter.emit('user.didGlobalBan', {} as Amity.UserPayload);
|
|
130
|
+
|
|
131
|
+
expect(client.sessionState).toBe(Amity.SessionStates.TERMINATED);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('it should reset client state when user is already connected with same userId', async () => {
|
|
135
|
+
onConnect().unref();
|
|
136
|
+
|
|
137
|
+
// First connection
|
|
138
|
+
await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);
|
|
139
|
+
expect(client.sessionState).toBe(Amity.SessionStates.ESTABLISHED);
|
|
140
|
+
expect(client.userId).toBe(user11.userId);
|
|
141
|
+
|
|
142
|
+
// Mock the HTTP call for second connection
|
|
143
|
+
client.http.get = jest.fn().mockResolvedValueOnce(mockUserResponse);
|
|
144
|
+
|
|
145
|
+
// Second connection with same userId should treat as token expiry (preserve cache)
|
|
146
|
+
onConnect().unref();
|
|
147
|
+
await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);
|
|
148
|
+
|
|
149
|
+
// Verify that the client is properly reset and reconnected
|
|
150
|
+
expect(client.sessionState).toBe(Amity.SessionStates.ESTABLISHED);
|
|
151
|
+
expect(client.userId).toBe(user11.userId);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('it should preserve cache when resuming session with same userId', async () => {
|
|
155
|
+
onConnect().unref();
|
|
156
|
+
|
|
157
|
+
// First connection - set some cache data
|
|
158
|
+
await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);
|
|
159
|
+
if (client.cache) {
|
|
160
|
+
client.cache.data = { test: 'data' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Mock the HTTP call for second connection
|
|
164
|
+
client.http.get = jest.fn().mockResolvedValueOnce(mockUserResponse);
|
|
165
|
+
|
|
166
|
+
// Second connection with same userId
|
|
167
|
+
onConnect().unref();
|
|
168
|
+
await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);
|
|
169
|
+
|
|
170
|
+
// Cache should be preserved when same userId is used
|
|
171
|
+
expect(client.cache?.data).toEqual({ test: 'data' });
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
import { markAsRead } from '~/channelRepository/internalApi/markAsRead';
|
|
2
2
|
import { shallowClone } from '../shallowClone';
|
|
3
|
+
import { queryCache } from '~/cache/api';
|
|
4
|
+
import { channelMemberLinkedObject } from './channelMemberLinkedObject';
|
|
3
5
|
|
|
4
6
|
export const channelLinkedObject = (channel: Amity.InternalChannel): Amity.Channel => {
|
|
7
|
+
let previewMembers: Amity.Membership<'channel'>[] = [];
|
|
8
|
+
|
|
9
|
+
if (channel.type === 'conversation') {
|
|
10
|
+
const channelUsers = queryCache<Amity.Membership<'channel'>>(['channelUsers', 'get']);
|
|
11
|
+
if (channelUsers && channelUsers?.length > 0) {
|
|
12
|
+
previewMembers = (
|
|
13
|
+
channelUsers?.filter(({ data }) => data.channelId === channel.channelId) ?? []
|
|
14
|
+
)
|
|
15
|
+
// sort in ascending order by userInternalId
|
|
16
|
+
.sort((a, b) => a.data.userInternalId.localeCompare(b.data.userInternalId))
|
|
17
|
+
// Select only first 4 members
|
|
18
|
+
.slice(0, 4)
|
|
19
|
+
.map(({ data }) => channelMemberLinkedObject(data));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
5
23
|
return shallowClone(channel, {
|
|
6
24
|
markAsRead: () => markAsRead(channel.channelInternalId),
|
|
25
|
+
previewMembers,
|
|
7
26
|
});
|
|
8
27
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { pullFromCache } from '~/cache/api/pullFromCache';
|
|
2
|
+
import { userLinkedObject } from './userLinkedObject';
|
|
3
|
+
|
|
4
|
+
export const channelMemberLinkedObject = (
|
|
5
|
+
channelMember: Amity.Membership<'channel'>,
|
|
6
|
+
): Amity.Membership<'channel'> => {
|
|
7
|
+
const getUser = (): Amity.User | undefined => {
|
|
8
|
+
const cacheKey = ['user', 'get', channelMember.userId];
|
|
9
|
+
const internalUser = pullFromCache<Amity.InternalUser>(cacheKey)?.data;
|
|
10
|
+
|
|
11
|
+
return internalUser ? userLinkedObject(internalUser) : undefined;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
...channelMember,
|
|
16
|
+
get user() {
|
|
17
|
+
return getUser();
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -14,6 +14,7 @@ import { notificationTrayLinkedObject } from './notificationTrayLinkedObject';
|
|
|
14
14
|
import { communityLinkedObject } from './communityLinkedObject';
|
|
15
15
|
import { invitationLinkedObject } from './invitationLinkedObject';
|
|
16
16
|
import { joinRequestLinkedObject } from './joinRequestLinkedObject';
|
|
17
|
+
import { channelMemberLinkedObject } from './channelMemberLinkedObject';
|
|
17
18
|
|
|
18
19
|
export const LinkedObject = {
|
|
19
20
|
ad: adLinkedObject,
|
|
@@ -32,4 +33,5 @@ export const LinkedObject = {
|
|
|
32
33
|
community: communityLinkedObject,
|
|
33
34
|
invitation: invitationLinkedObject,
|
|
34
35
|
joinRequest: joinRequestLinkedObject,
|
|
36
|
+
channelMember: channelMemberLinkedObject,
|
|
35
37
|
};
|