@amityco/ts-sdk 0.0.1-beta.36 → 0.0.1-beta.37

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.
Files changed (133) hide show
  1. package/dist/@types/core/errors.d.ts +1 -0
  2. package/dist/@types/core/errors.d.ts.map +1 -1
  3. package/dist/@types/core/events.d.ts +4 -0
  4. package/dist/@types/core/events.d.ts.map +1 -1
  5. package/dist/@types/domains/client.d.ts +21 -1
  6. package/dist/@types/domains/client.d.ts.map +1 -1
  7. package/dist/@types/global.d.ts +7 -0
  8. package/dist/@types/global.d.ts.map +1 -1
  9. package/dist/client/api/accessTokenExpiryWatcher.d.ts +4 -0
  10. package/dist/client/api/accessTokenExpiryWatcher.d.ts.map +1 -0
  11. package/dist/client/api/activeUser.d.ts +1 -1
  12. package/dist/client/api/activeUser.d.ts.map +1 -1
  13. package/dist/client/api/connectClient.d.ts +1 -1
  14. package/dist/client/api/connectClient.d.ts.map +1 -1
  15. package/dist/client/api/createClient.d.ts +1 -1
  16. package/dist/client/api/createClient.d.ts.map +1 -1
  17. package/dist/client/api/disconnectClient.d.ts +0 -1
  18. package/dist/client/api/disconnectClient.d.ts.map +1 -1
  19. package/dist/client/api/getToken.d.ts +4 -4
  20. package/dist/client/api/index.d.ts +1 -0
  21. package/dist/client/api/index.d.ts.map +1 -1
  22. package/dist/client/api/renewal.d.ts +2 -0
  23. package/dist/client/api/renewal.d.ts.map +1 -0
  24. package/dist/client/api/setSessionState.d.ts +12 -0
  25. package/dist/client/api/setSessionState.d.ts.map +1 -0
  26. package/dist/client/api/terminateClient.d.ts +9 -0
  27. package/dist/client/api/terminateClient.d.ts.map +1 -0
  28. package/dist/client/api/tests/accessTokenExpiryWatcher.test.d.ts +2 -0
  29. package/dist/client/api/tests/accessTokenExpiryWatcher.test.d.ts.map +1 -0
  30. package/dist/client/api/tests/connectClient.test.d.ts +2 -0
  31. package/dist/client/api/tests/connectClient.test.d.ts.map +1 -0
  32. package/dist/client/api/tests/disconnectClient.test.d.ts +2 -0
  33. package/dist/client/api/tests/disconnectClient.test.d.ts.map +1 -0
  34. package/dist/client/api/tests/getToken.test.d.ts +2 -0
  35. package/dist/client/api/tests/getToken.test.d.ts.map +1 -0
  36. package/dist/client/api/tests/renewal.test.d.ts +2 -0
  37. package/dist/client/api/tests/renewal.test.d.ts.map +1 -0
  38. package/dist/client/api/tests/setSessionState.test.d.ts +2 -0
  39. package/dist/client/api/tests/setSessionState.test.d.ts.map +1 -0
  40. package/dist/client/api/tests/terminateClient.test.d.ts +2 -0
  41. package/dist/client/api/tests/terminateClient.test.d.ts.map +1 -0
  42. package/dist/client/events/index.d.ts +1 -0
  43. package/dist/client/events/index.d.ts.map +1 -1
  44. package/dist/client/events/onClientBanned.d.ts +1 -1
  45. package/dist/client/events/onClientBanned.d.ts.map +1 -1
  46. package/dist/client/events/onSessionStateChange.d.ts +17 -0
  47. package/dist/client/events/onSessionStateChange.d.ts.map +1 -0
  48. package/dist/client/events/onTokenExpired.d.ts +17 -0
  49. package/dist/client/events/onTokenExpired.d.ts.map +1 -0
  50. package/dist/client/events/onTokenTerminated.d.ts +11 -0
  51. package/dist/client/events/onTokenTerminated.d.ts.map +1 -0
  52. package/dist/client/events/tests/onSessionStateChange.test.d.ts +2 -0
  53. package/dist/client/events/tests/onSessionStateChange.test.d.ts.map +1 -0
  54. package/dist/client/events/tests/onTokenTerminated.test.d.ts +2 -0
  55. package/dist/client/events/tests/onTokenTerminated.test.d.ts.map +1 -0
  56. package/dist/client/utils/setClientToken.d.ts +19 -0
  57. package/dist/client/utils/setClientToken.d.ts.map +1 -0
  58. package/dist/comment/events/tests/onCommentDeleted.test.d.ts +2 -0
  59. package/dist/comment/events/tests/onCommentDeleted.test.d.ts.map +1 -0
  60. package/dist/core/events.d.ts +3 -3
  61. package/dist/core/events.d.ts.map +1 -1
  62. package/dist/core/subscription.d.ts.map +1 -1
  63. package/dist/core/transports/http.d.ts.map +1 -1
  64. package/dist/index.cjs.js +503 -137
  65. package/dist/index.esm.js +502 -138
  66. package/dist/index.umd.js +4 -4
  67. package/dist/stream/api/tests/createStream.test.d.ts +2 -0
  68. package/dist/stream/api/tests/createStream.test.d.ts.map +1 -0
  69. package/dist/stream/api/tests/deleteStream.test.d.ts +2 -0
  70. package/dist/stream/api/tests/deleteStream.test.d.ts.map +1 -0
  71. package/dist/stream/api/tests/getStream.test.d.ts +2 -0
  72. package/dist/stream/api/tests/getStream.test.d.ts.map +1 -0
  73. package/dist/subChannel/api/tests/createSubChannel.test.d.ts +2 -0
  74. package/dist/subChannel/api/tests/createSubChannel.test.d.ts.map +1 -0
  75. package/dist/utils/constants.d.ts +2 -0
  76. package/dist/utils/constants.d.ts.map +1 -1
  77. package/dist/utils/tests/client.d.ts.map +1 -1
  78. package/dist/utils/tests/dummy/index.d.ts +2 -0
  79. package/dist/utils/tests/dummy/index.d.ts.map +1 -1
  80. package/dist/utils/tests/dummy/stream.d.ts +83 -0
  81. package/dist/utils/tests/dummy/stream.d.ts.map +1 -0
  82. package/dist/utils/tests/dummy/subChannel.d.ts +20 -0
  83. package/dist/utils/tests/dummy/subChannel.d.ts.map +1 -1
  84. package/dist/utils/tests/dummy/token.d.ts +11 -0
  85. package/dist/utils/tests/dummy/token.d.ts.map +1 -0
  86. package/package.json +1 -1
  87. package/src/@types/core/errors.ts +1 -0
  88. package/src/@types/core/events.ts +6 -0
  89. package/src/@types/domains/client.ts +45 -1
  90. package/src/@types/global.ts +14 -0
  91. package/src/client/api/accessTokenExpiryWatcher.ts +83 -0
  92. package/src/client/api/activeUser.ts +1 -1
  93. package/src/client/api/connectClient.ts +105 -24
  94. package/src/client/api/createClient.ts +9 -1
  95. package/src/client/api/disconnectClient.ts +26 -2
  96. package/src/client/api/getToken.ts +6 -6
  97. package/src/client/api/index.ts +2 -0
  98. package/src/client/api/renewal.ts +74 -0
  99. package/src/client/api/setSessionState.ts +75 -0
  100. package/src/client/api/terminateClient.ts +20 -0
  101. package/src/client/api/tests/accessTokenExpiryWatcher.test.ts +129 -0
  102. package/src/client/api/tests/connectClient.test.ts +84 -0
  103. package/src/client/api/tests/createClient.test.ts +8 -0
  104. package/src/client/api/tests/disconnectClient.test.ts +78 -0
  105. package/src/client/api/tests/getToken.test.ts +36 -0
  106. package/src/client/api/tests/renewal.test.ts +67 -0
  107. package/src/client/api/tests/setSessionState.test.ts +62 -0
  108. package/src/client/api/tests/terminateClient.test.ts +29 -0
  109. package/src/client/events/index.ts +2 -0
  110. package/src/client/events/onClientBanned.ts +12 -5
  111. package/src/client/events/onSessionStateChange.ts +31 -0
  112. package/src/client/events/onTokenExpired.ts +26 -0
  113. package/src/client/events/onTokenTerminated.ts +20 -0
  114. package/src/client/events/tests/onSessionStateChange.test.ts +37 -0
  115. package/src/client/events/tests/onTokenTerminated.test.ts +37 -0
  116. package/src/client/tests/connectClient.test.ts +6 -1
  117. package/src/client/utils/setClientToken.ts +39 -0
  118. package/src/comment/events/tests/onCommentDeleted.test.ts +27 -0
  119. package/src/core/events.ts +5 -3
  120. package/src/core/subscription.ts +3 -1
  121. package/src/core/transports/http.ts +36 -1
  122. package/src/core/transports/mqtt.ts +1 -1
  123. package/src/external/api/createUserToken.ts +2 -2
  124. package/src/stream/api/tests/createStream.test.ts +30 -0
  125. package/src/stream/api/tests/deleteStream.test.ts +48 -0
  126. package/src/stream/api/tests/getStream.test.ts +23 -0
  127. package/src/subChannel/api/tests/createSubChannel.test.ts +103 -0
  128. package/src/utils/constants.ts +2 -0
  129. package/src/utils/tests/client.ts +18 -2
  130. package/src/utils/tests/dummy/index.ts +3 -0
  131. package/src/utils/tests/dummy/stream.ts +86 -0
  132. package/src/utils/tests/dummy/subChannel.ts +22 -0
  133. package/src/utils/tests/dummy/token.ts +12 -0
@@ -1,4 +1,5 @@
1
1
  import { getActiveClient } from './activeClient';
2
+ import { setSessionState } from './setSessionState';
2
3
 
3
4
  /**
4
5
  * ```js
@@ -8,7 +9,6 @@ import { getActiveClient } from './activeClient';
8
9
  *
9
10
  * Disconnects an {@link Amity.Client} instance from ASC servers
10
11
  *
11
- * @param client The {@link Amity.Client} instance to disconnect
12
12
  * @returns a success boolean if disconnected
13
13
  *
14
14
  * @category Client API
@@ -16,6 +16,7 @@ import { getActiveClient } from './activeClient';
16
16
  */
17
17
  export const disconnectClient = async (): Promise<boolean> => {
18
18
  const client = getActiveClient();
19
+
19
20
  client.log('client/api/disconnectClient');
20
21
 
21
22
  await new Promise(resolve => {
@@ -29,7 +30,30 @@ export const disconnectClient = async (): Promise<boolean> => {
29
30
  client.ws.removeAllListeners();
30
31
  client.mqtt.removeAllListeners();
31
32
  client.userId = undefined;
32
- client.accessToken = undefined;
33
+ client.token = undefined;
34
+
35
+ client.http.defaults.headers.common.Authorization = '';
36
+ client.ws.io.opts.query = { token: '' };
37
+
38
+ /*
39
+ * for cases when session state is terminated (example on ban) or token expired,
40
+ * the terminating block will set session state to terminated or for the or
41
+ * in the case of expired token the same happens
42
+ *
43
+ * establishing state also ignored in cases where accessTokenExpiryWatcher
44
+ * calls renewal. There is a possibility that renewal will be called before
45
+ * disconnectClient finishes execution
46
+ */
47
+ if (client.sessionState === Amity.SessionStates.ESTABLISHED)
48
+ setSessionState(Amity.SessionStates.NOT_LOGGED_IN);
49
+
50
+ /*
51
+ * Cache should be usable if tokenExpired
52
+ * https://ekoapp.atlassian.net/wiki/spaces/UP/pages/2082537485/ASC+Core+-+Session+Management+3.0#SDK-usability-based-on-Session-State
53
+ */
54
+ if (client.sessionState !== Amity.SessionStates.TOKEN_EXPIRED && client.cache) {
55
+ client.cache = { data: {} };
56
+ }
33
57
 
34
58
  return !client.ws.connected;
35
59
  };
@@ -5,10 +5,10 @@ import { getDeviceInfo } from '~/core/device';
5
5
  /**
6
6
  * Retrieves a pair of {@link Amity.Tokens} necessary for connection
7
7
  *
8
- * @param userId The userId to use to issue a token
9
- * @param displayName The user's displayName
10
- * @param deviceId The user's device Id (can be manually set for native users)
11
- * @param authToken The authentication token - necessary when network option is set to secure
8
+ * @param params.userId The userId to use to issue a token
9
+ * @param params.displayName The user's displayName
10
+ * @param params.deviceId The user's device Id (can be manually set for native users)
11
+ * @param params.authToken The authentication token - necessary when network option is set to secure
12
12
  * @return The accessToken for the given userId
13
13
  *
14
14
  * @category Client API
@@ -23,10 +23,10 @@ export const getToken = async (params: {
23
23
  const client = getActiveClient();
24
24
  client.log('client/api/getToken', params);
25
25
 
26
- const deviceInfo = await getDeviceInfo();
26
+ const deviceInfo = getDeviceInfo();
27
27
 
28
28
  const { data } = await client.http.post<Amity.Tokens & { users: Amity.User[] }>(
29
- '/v1/device',
29
+ '/api/v3/sessions',
30
30
  {
31
31
  ...params,
32
32
  deviceInfo,
@@ -8,3 +8,5 @@ export * from './disconnectClient';
8
8
  export * from './isConnected';
9
9
 
10
10
  export * from './getFeedSettings';
11
+
12
+ export * from './renewal';
@@ -0,0 +1,74 @@
1
+ import { getDeviceId } from '~/core/device';
2
+ import { ACCESS_TOKEN_WATCHER_INTERVAL } from '~/utils/constants';
3
+
4
+ import { getActiveUser } from './activeUser';
5
+ import { setClientToken as refreshToken } from '../utils/setClientToken';
6
+ import { getActiveClient } from './activeClient';
7
+ import { connectClient } from './connectClient';
8
+
9
+ /*
10
+ * Renewal defintion accepted by SessionHandler interface
11
+ *
12
+ * Tech Spec:
13
+ * https://ekoapp.atlassian.net/wiki/spaces/UP/pages/2082537485/ASC+Core+-+Session+Management+3.0#Session-Handler
14
+ *
15
+ * @category private
16
+ */
17
+ export const renewal = (): Amity.AccessTokenRenewal => {
18
+ let tokenRenewed = false;
19
+ let renewTimeoutId: ReturnType<typeof setTimeout>;
20
+
21
+ const client = getActiveClient();
22
+
23
+ client.log('initiating access token renewal');
24
+ /*
25
+ * Renews a token if it is hasn't been renewed before. Also marks token as
26
+ * renewed once done
27
+ * Per instance of Renewal, only one renewal is allowed
28
+ */
29
+ const renewToken = async (authToken?: Amity.ConnectClientParams['authToken']): Promise<void> => {
30
+ const { userId, displayName } = getActiveUser();
31
+ const deviceId = getDeviceId();
32
+
33
+ const params = { userId, displayName, authToken, deviceId };
34
+
35
+ if (client.sessionState === Amity.SessionStates.TOKEN_EXPIRED) {
36
+ // sessionHandler will never be empty when renewal is instantiatied
37
+ await connectClient(params, client.sessionHandler!);
38
+ } else {
39
+ // about to expire
40
+
41
+ // @ts-ignore: in that particular case, typing can be
42
+ // ignored since we enforce default values just before.
43
+ await refreshToken(params);
44
+ }
45
+
46
+ tokenRenewed = true;
47
+
48
+ if (renewTimeoutId) clearTimeout(renewTimeoutId);
49
+ };
50
+
51
+ return {
52
+ renew: () => {
53
+ if (tokenRenewed) {
54
+ console.log("'renew' method can be called only once per renewal instance");
55
+ return;
56
+ }
57
+ renewToken();
58
+ },
59
+
60
+ renewWithAuthToken: (authToken: string) => {
61
+ if (tokenRenewed) {
62
+ console.log("'renewWithAuthToken' method can be called only once per renewal instance");
63
+ return;
64
+ }
65
+ renewToken(authToken);
66
+ },
67
+
68
+ unableToRetrieveAuthToken: () => {
69
+ renewTimeoutId = setTimeout(() => {
70
+ client.sessionHandler?.sessionWillRenewAccessToken(renewal());
71
+ }, ACCESS_TOKEN_WATCHER_INTERVAL);
72
+ },
73
+ };
74
+ };
@@ -0,0 +1,75 @@
1
+ import { fireEvent } from '~/core/events';
2
+ import { ASCError } from '~/core/errors';
3
+
4
+ import { getActiveClient } from './activeClient';
5
+
6
+ /*
7
+ * Session States cannot be set arbitarily.
8
+ * These are the possible new states based on the current state
9
+ * Check tech spec for details:
10
+ * https://ekoapp.atlassian.net/wiki/spaces/UP/pages/2082537485/ASC+Core+-+Session+Management+3.0#Session-State
11
+ */
12
+ const sessisonStateTransitions: Record<Amity.SessionStates, Amity.SessionStates[]> = {
13
+ // notLoggedIn -> establishing
14
+ [Amity.SessionStates.NOT_LOGGED_IN]: [Amity.SessionStates.ESTABLISHING],
15
+ // terminated -> establishing
16
+ [Amity.SessionStates.TERMINATED]: [Amity.SessionStates.ESTABLISHING],
17
+ // establishing -> notLoggedIn, established
18
+ [Amity.SessionStates.ESTABLISHING]: [
19
+ Amity.SessionStates.NOT_LOGGED_IN,
20
+ Amity.SessionStates.ESTABLISHED,
21
+ ],
22
+ /*
23
+ * established -> notLoggedIn, terminated, tokenExpired, establishing (for
24
+ * cases when logging in same user)
25
+ */
26
+ [Amity.SessionStates.ESTABLISHED]: [
27
+ Amity.SessionStates.NOT_LOGGED_IN,
28
+ Amity.SessionStates.TERMINATED,
29
+ Amity.SessionStates.TOKEN_EXPIRED,
30
+ Amity.SessionStates.ESTABLISHING,
31
+ ],
32
+ // tokenExpired -> establishing
33
+ [Amity.SessionStates.TOKEN_EXPIRED]: [Amity.SessionStates.ESTABLISHING],
34
+ };
35
+
36
+ export const isValidStateChange = (
37
+ prevState: Amity.SessionStates,
38
+ nextState: Amity.SessionStates,
39
+ ): boolean => {
40
+ return sessisonStateTransitions[prevState].includes(nextState);
41
+ };
42
+
43
+ /**
44
+ * Checks if a {@link Amity.Client} instance is connected to ASC servers
45
+ *
46
+ *
47
+ * @param state the new session state
48
+ * @returns a success boolean if connected
49
+ *
50
+ * @category private
51
+ */
52
+ export const setSessionState = (state: Amity.SessionStates): boolean => {
53
+ const client = getActiveClient();
54
+
55
+ client.log('client/api/setSessionState', state);
56
+
57
+ const { sessionState: prevState } = client;
58
+
59
+ if (prevState === state) return false;
60
+
61
+ // check if transition is valid
62
+ if (!isValidStateChange(prevState, state)) {
63
+ throw new ASCError(
64
+ `Session state cannot change from ${prevState} to ${state}`,
65
+ Amity.ClientError.UNKNOWN_ERROR,
66
+ Amity.ErrorLevel.ERROR,
67
+ );
68
+ }
69
+
70
+ client.sessionState = state;
71
+
72
+ fireEvent('sessionStateChange', state);
73
+
74
+ return true;
75
+ };
@@ -0,0 +1,20 @@
1
+ import { disconnectClient } from './disconnectClient';
2
+ import { setSessionState } from './setSessionState';
3
+ import { getActiveClient } from './activeClient';
4
+
5
+ /**
6
+ * Terminates {@link Amity.Client} instance
7
+ *
8
+ *
9
+ *
10
+ * @category private
11
+ */
12
+ export const terminateClient = () => {
13
+ const client = getActiveClient();
14
+
15
+ setSessionState(Amity.SessionStates.TERMINATED);
16
+
17
+ client.sessionHandler = undefined;
18
+
19
+ disconnectClient();
20
+ };
@@ -0,0 +1,129 @@
1
+ import { client, connectClient, disconnectClient } from '~/utils/tests';
2
+ import { ACCESS_TOKEN_WATCHER_INTERVAL } from '~/utils/constants';
3
+
4
+ import { accessTokenExpiryWatcher, isAboutToExpire, isExpired } from '../accessTokenExpiryWatcher';
5
+
6
+ import { onTokenExpired } from '~/client/events/onTokenExpired';
7
+
8
+ describe('accessTokenExpiryWatcher', () => {
9
+ beforeAll(async () => {
10
+ await connectClient();
11
+ });
12
+
13
+ afterAll(async () => {
14
+ await disconnectClient();
15
+ });
16
+
17
+ const expiresAt = new Date().toISOString();
18
+ const issuedAt = new Date().toISOString();
19
+
20
+ test('it should fire event when session expires', async () => {
21
+ const sessionHandler: Amity.SessionHandler = {
22
+ sessionWillRenewAccessToken(_) {
23
+ // do nothing
24
+ },
25
+ };
26
+
27
+ jest.useFakeTimers();
28
+ const callback = jest.fn();
29
+ const expected = Amity.SessionStates.TOKEN_EXPIRED;
30
+
31
+ const unsub = onTokenExpired(callback);
32
+ const stopWatcher = accessTokenExpiryWatcher(expiresAt, issuedAt, sessionHandler!);
33
+
34
+ jest.advanceTimersByTime(ACCESS_TOKEN_WATCHER_INTERVAL + 1);
35
+
36
+ expect(callback).toBeCalledWith(expected);
37
+
38
+ unsub();
39
+ stopWatcher();
40
+ });
41
+
42
+ test('it should renew token if session is about to expire', async () => {
43
+ jest.useFakeTimers();
44
+ const callback = jest.fn();
45
+
46
+ const sessionHandler = { sessionWillRenewAccessToken: callback };
47
+ client.sessionHandler = sessionHandler;
48
+
49
+ const stopWatcher = accessTokenExpiryWatcher(expiresAt, issuedAt, sessionHandler);
50
+
51
+ const oldIssuedAtTime = new Date();
52
+ const aboutToExpireTime = new Date();
53
+
54
+ aboutToExpireTime.setMinutes(aboutToExpireTime.getMinutes() + 30);
55
+ oldIssuedAtTime.setDate(oldIssuedAtTime.getDate() - 1);
56
+
57
+ aboutToExpireTime.setHours(aboutToExpireTime.getHours() + 1);
58
+
59
+ if (client.token) {
60
+ client.token.expiresAt = aboutToExpireTime.toISOString();
61
+ client.token.issuedAt = oldIssuedAtTime.toISOString();
62
+ }
63
+
64
+ jest.advanceTimersByTime(ACCESS_TOKEN_WATCHER_INTERVAL + 1);
65
+ stopWatcher();
66
+
67
+ expect(callback).toHaveBeenCalled();
68
+ });
69
+
70
+ test('it should renew token if session has expired', async () => {
71
+ jest.useFakeTimers();
72
+ const callback = jest.fn();
73
+
74
+ const sessionHandler = { sessionWillRenewAccessToken: callback };
75
+ client.sessionHandler = sessionHandler;
76
+
77
+ const stopWatcher = accessTokenExpiryWatcher(expiresAt, issuedAt, sessionHandler);
78
+ if (client.token) client.token.expiresAt = new Date().toISOString();
79
+
80
+ jest.advanceTimersByTime(ACCESS_TOKEN_WATCHER_INTERVAL + 1);
81
+
82
+ expect(callback).toHaveBeenCalled();
83
+
84
+ stopWatcher();
85
+ });
86
+ });
87
+
88
+ describe('accessTokenExpiryWatcher > aboutToExpire', () => {
89
+ const expiresAt = new Date().toISOString();
90
+ const issuedAt = new Date().toISOString();
91
+ const oldIssuedAtTime = new Date();
92
+ const aboutToExpireTime = new Date();
93
+
94
+ test('it should return true if about to expire', () => {
95
+ oldIssuedAtTime.setDate(oldIssuedAtTime.getDate() - 1);
96
+
97
+ aboutToExpireTime.setHours(aboutToExpireTime.getHours() + 1);
98
+
99
+ expect(isAboutToExpire(aboutToExpireTime.toISOString(), oldIssuedAtTime.toISOString())).toBe(
100
+ true,
101
+ );
102
+ });
103
+
104
+ test('it should return false if token expired', () => {
105
+ expect(isAboutToExpire(expiresAt, issuedAt)).toBe(false);
106
+ });
107
+
108
+ test('it should return false if not about to expire', () => {
109
+ oldIssuedAtTime.setDate(oldIssuedAtTime.getDate() - 10);
110
+
111
+ expect(isAboutToExpire(expiresAt, issuedAt)).toBe(false);
112
+ });
113
+ });
114
+
115
+ describe('accessTokenExpiryWatcher > isExpired', () => {
116
+ let expiresAt = new Date().toISOString();
117
+
118
+ test('it should return true if token expired', () => {
119
+ expect(isExpired(expiresAt)).toBe(true);
120
+ });
121
+
122
+ test('it should return false if token not about expired', () => {
123
+ const laterDate = new Date();
124
+ laterDate.setMonth(laterDate.getMonth() + 1);
125
+ expiresAt = laterDate.toISOString();
126
+
127
+ expect(isExpired(expiresAt)).toBe(false);
128
+ });
129
+ });
@@ -0,0 +1,84 @@
1
+ import { ASCApiError } from '~/core/errors';
2
+ import { sessionResponse, user11, disconnectClient } from '~/utils/tests';
3
+ import { connectClient, 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 onConnect = () =>
15
+ setTimeout(() => {
16
+ const CONNECT_PACKET = { type: 0, nsp: client.ws.nsp };
17
+
18
+ client.ws.emit('connect');
19
+
20
+ // simulate a connection ack packet from server
21
+ client.ws.io.emit('packet', CONNECT_PACKET);
22
+ }, 50);
23
+
24
+ describe('connectClient', () => {
25
+ beforeEach(() => {
26
+ client = createClient('key', 'sg');
27
+ client.mqtt.connect = jest.fn();
28
+ client.mqtt.subscribe = jest.fn();
29
+ client.http.post = jest.fn().mockResolvedValueOnce(sessionResponse);
30
+
31
+ setActiveClient(client);
32
+ });
33
+
34
+ afterEach(async () => {
35
+ if (client.sessionState === Amity.SessionStates.ESTABLISHED) await disconnectClient();
36
+ });
37
+
38
+ test('it should connect client', async () => {
39
+ onConnect().unref();
40
+
41
+ const recieved = await connectClient({ userId: user11.userId }, sessionHandler);
42
+
43
+ expect(recieved).toBe(true);
44
+ });
45
+
46
+ test('it should establish connection', async () => {
47
+ onConnect().unref();
48
+
49
+ await connectClient({ userId: user11.userId }, sessionHandler);
50
+ const { sessionState } = client;
51
+
52
+ expect(sessionState).toBe(Amity.SessionStates.ESTABLISHED);
53
+ });
54
+
55
+ test('it should have session state establishing while connecting client', () => {
56
+ connectClient({ userId: user11.userId }, sessionHandler);
57
+
58
+ expect(client.sessionState).toBe(Amity.SessionStates.ESTABLISHING);
59
+ });
60
+
61
+ test('it should have session state notLoggedIn on failure', async () => {
62
+ client.http.post = jest
63
+ .fn()
64
+ .mockRejectedValue(
65
+ new ASCApiError('unauthorzed', Amity.ServerError.UNAUTHORIZED, Amity.ErrorLevel.FATAL),
66
+ );
67
+
68
+ await expect(connectClient({ userId: user11.userId }, sessionHandler)).rejects.toThrow(
69
+ 'unauthorzed',
70
+ );
71
+ expect(client.sessionState).toBe(Amity.SessionStates.NOT_LOGGED_IN);
72
+ });
73
+
74
+ test('it should terminate session on ban', async () => {
75
+ onConnect().unref();
76
+
77
+ await connectClient({ userId: user11.userId }, sessionHandler);
78
+
79
+ // ban user
80
+ client.emitter.emit('user.didGlobalBan', {} as Amity.UserPayload);
81
+
82
+ expect(client.sessionState).toBe(Amity.SessionStates.TERMINATED);
83
+ });
84
+ });
@@ -37,4 +37,12 @@ describe('createClient', () => {
37
37
 
38
38
  expect(recieved).toStrictEqual(expected);
39
39
  });
40
+
41
+ test('it should set not logged in as the start up session state', () => {
42
+ const got = createClient('key', 'sg');
43
+ const { sessionState } = got;
44
+
45
+ expect(got).toBeDefined();
46
+ expect(sessionState).toBe(Amity.SessionStates.NOT_LOGGED_IN);
47
+ });
40
48
  });
@@ -0,0 +1,78 @@
1
+ import { pushToCache, pullFromCache, enableCache } from '~/cache/api';
2
+ import { client, connectClient, message11 as message } from '~/utils/tests';
3
+
4
+ import { disconnectClient } from '..';
5
+
6
+ const disconnect = () => setTimeout(() => client.ws.emit('disconnect'), 500);
7
+
8
+ describe('disconnectClient', () => {
9
+ beforeEach(async () => {
10
+ await connectClient();
11
+ });
12
+
13
+ test('it should disconnect client', async () => {
14
+ disconnect().unref();
15
+
16
+ const recieved = await disconnectClient();
17
+
18
+ expect(recieved).toBe(true);
19
+ });
20
+
21
+ test('it should set sesion state to notLoggedIn after disconnect', async () => {
22
+ disconnect().unref();
23
+ await disconnectClient();
24
+
25
+ expect(client.sessionState).toBe(Amity.SessionStates.NOT_LOGGED_IN);
26
+ });
27
+
28
+ test('it should not update sesion state if session state terminated', async () => {
29
+ client.sessionState = Amity.SessionStates.TERMINATED;
30
+ disconnect().unref();
31
+
32
+ await disconnectClient();
33
+
34
+ expect(client.sessionState).toBe(Amity.SessionStates.TERMINATED);
35
+ });
36
+
37
+ test('it should clear userId and accessToken on disconnect', async () => {
38
+ disconnect().unref();
39
+
40
+ await disconnectClient();
41
+
42
+ expect(client.userId).toBeUndefined();
43
+ expect(client.token).toBeUndefined();
44
+ });
45
+
46
+ test('it should clear auth header on disconnect', async () => {
47
+ disconnect().unref();
48
+
49
+ await disconnectClient();
50
+
51
+ expect(client.http.defaults.headers.common.Authorization).toBeFalsy();
52
+ });
53
+
54
+ test('it should clear cache on disconnect', async () => {
55
+ disconnect().unref();
56
+ enableCache();
57
+
58
+ const cacheKey = ['message', 'get', message.messageId];
59
+ pushToCache(cacheKey, message);
60
+
61
+ await disconnectClient();
62
+
63
+ expect(pullFromCache(cacheKey)).toBeUndefined();
64
+ });
65
+
66
+ test('it should not clear cache if tokenExpired', async () => {
67
+ client.sessionState = Amity.SessionStates.TOKEN_EXPIRED;
68
+ disconnect().unref();
69
+ enableCache();
70
+
71
+ const cacheKey = ['message', 'get', message.messageId];
72
+ pushToCache(cacheKey, message);
73
+
74
+ await disconnectClient();
75
+
76
+ expect(pullFromCache(cacheKey)?.data).toStrictEqual(message);
77
+ });
78
+ });
@@ -0,0 +1,36 @@
1
+ import { client, user11, sessionResponse } from '~/utils/tests';
2
+
3
+ import { getToken } from '../getToken';
4
+
5
+ describe('getToken', () => {
6
+ const { userId } = user11;
7
+ const displayName = 'test-displayName';
8
+ const deviceId = 'test-deviceId';
9
+ const params = { userId, displayName, deviceId };
10
+
11
+ test('it should call v3/sessions api', async () => {
12
+ const expected = '/api/v3/sessions';
13
+ const mockApi = jest.fn().mockResolvedValue(sessionResponse);
14
+ client.http.post = mockApi;
15
+
16
+ await getToken(params);
17
+ const [recieved] = mockApi.mock.lastCall;
18
+
19
+ expect(recieved).toBe(expected);
20
+ });
21
+
22
+ test('it should return token', async () => {
23
+ const expected = sessionResponse.data;
24
+ client.http.post = jest.fn().mockResolvedValue(sessionResponse);
25
+
26
+ const recieved = await getToken(params);
27
+
28
+ expect(recieved).toStrictEqual(expected);
29
+ });
30
+
31
+ test('it should throw error', async () => {
32
+ client.http.post = jest.fn().mockRejectedValue(new Error('error'));
33
+
34
+ await expect(getToken(params)).rejects.toThrow();
35
+ });
36
+ });
@@ -0,0 +1,67 @@
1
+ import { ACCESS_TOKEN_WATCHER_INTERVAL } from '~/utils/constants';
2
+ import { client, connectClient, disconnectClient, user11, pause } from '~/utils/tests';
3
+
4
+ import { renewal } from '../renewal';
5
+
6
+ const accessToken = 'renewed-access-token';
7
+ const sessionResponse = {
8
+ data: {
9
+ accessToken,
10
+ refreshToken: 'test-refresh-token',
11
+ issuedAt: new Date(),
12
+ expiresAt: new Date(),
13
+ users: [user11],
14
+ files: [],
15
+ },
16
+ };
17
+
18
+ describe('renewal', () => {
19
+ beforeAll(connectClient);
20
+
21
+ afterAll(disconnectClient);
22
+
23
+ test('it should renew access token', async () => {
24
+ const mock = jest.fn();
25
+ client.http.post = mock.mockResolvedValue(sessionResponse);
26
+
27
+ renewal().renew();
28
+ await pause();
29
+
30
+ expect(client.token?.accessToken).toBe(accessToken);
31
+ });
32
+
33
+ test('it should show warning message if renewal already called', async () => {
34
+ client.http.post = jest.fn().mockResolvedValue(sessionResponse);
35
+ jest.spyOn(global.console, 'log');
36
+
37
+ const expected = "'renew' method can be called only once per renewal instance";
38
+ const renewalInstance = renewal();
39
+
40
+ renewalInstance.renew();
41
+ await pause();
42
+
43
+ renewalInstance.renew();
44
+ await pause();
45
+
46
+ expect(console.log).toBeCalledWith(expected);
47
+ });
48
+
49
+ test('it should call renewal later if unable to renew', async () => {
50
+ jest.useFakeTimers();
51
+ const sessionHandler: Amity.SessionHandler = {
52
+ sessionWillRenewAccessToken(renewal) {
53
+ renewal.renew();
54
+ },
55
+ };
56
+
57
+ client.sessionHandler = sessionHandler;
58
+
59
+ const mock = jest.fn();
60
+ client.http.post = mock.mockResolvedValue(sessionResponse);
61
+
62
+ renewal().unableToRetrieveAuthToken();
63
+ jest.advanceTimersByTime(ACCESS_TOKEN_WATCHER_INTERVAL);
64
+
65
+ expect(mock).toBeCalled();
66
+ });
67
+ });