@up-im/medotvet_sdk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/.cursor/rules/apcheki_sdk.mdc +90 -0
  2. package/README.md +136 -0
  3. package/dist/api/abstract.d.ts +21 -0
  4. package/dist/api/abstract.js +125 -0
  5. package/dist/api/bookmarks.d.ts +28 -0
  6. package/dist/api/bookmarks.js +121 -0
  7. package/dist/api/chat.d.ts +30 -0
  8. package/dist/api/chat.js +118 -0
  9. package/dist/api/content.d.ts +154 -0
  10. package/dist/api/content.js +237 -0
  11. package/dist/api/geoIp.d.ts +8 -0
  12. package/dist/api/geoIp.js +14 -0
  13. package/dist/api/index.d.ts +27 -0
  14. package/dist/api/index.js +31 -0
  15. package/dist/api/payment.d.ts +54 -0
  16. package/dist/api/payment.js +90 -0
  17. package/dist/api/promocode.d.ts +9 -0
  18. package/dist/api/promocode.js +22 -0
  19. package/dist/api/push.d.ts +54 -0
  20. package/dist/api/push.js +99 -0
  21. package/dist/api/receipt.d.ts +34 -0
  22. package/dist/api/receipt.js +60 -0
  23. package/dist/api/stat.d.ts +21 -0
  24. package/dist/api/stat.js +75 -0
  25. package/dist/api/survey.d.ts +82 -0
  26. package/dist/api/survey.js +122 -0
  27. package/dist/api/user.d.ts +80 -0
  28. package/dist/api/user.js +167 -0
  29. package/dist/eventBus/abstract.d.ts +4 -0
  30. package/dist/eventBus/abstract.js +29 -0
  31. package/dist/eventBus/userLogin.d.ts +5 -0
  32. package/dist/eventBus/userLogin.js +11 -0
  33. package/dist/eventBus/userLogout.d.ts +4 -0
  34. package/dist/eventBus/userLogout.js +11 -0
  35. package/dist/http/apiError.d.ts +4 -0
  36. package/dist/http/apiError.js +10 -0
  37. package/dist/http/apiRoute.d.ts +98 -0
  38. package/dist/http/apiRoute.js +197 -0
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.js +7 -0
  41. package/dist/interfaces/BannerType.d.ts +5 -0
  42. package/dist/interfaces/BannerType.js +7 -0
  43. package/dist/interfaces/apiStatEvents.d.ts +1 -0
  44. package/dist/interfaces/apiStatEvents.js +18 -0
  45. package/dist/interfaces/diplomaStatus.d.ts +8 -0
  46. package/dist/interfaces/diplomaStatus.js +9 -0
  47. package/dist/interfaces/iAppStore.d.ts +1 -0
  48. package/dist/interfaces/iAppStore.js +2 -0
  49. package/dist/interfaces/iAppVersionCheck.d.ts +5 -0
  50. package/dist/interfaces/iAppVersionCheck.js +2 -0
  51. package/dist/interfaces/iArticle.d.ts +15 -0
  52. package/dist/interfaces/iArticle.js +2 -0
  53. package/dist/interfaces/iBalance.d.ts +6 -0
  54. package/dist/interfaces/iBalance.js +2 -0
  55. package/dist/interfaces/iBank.d.ts +4 -0
  56. package/dist/interfaces/iBank.js +2 -0
  57. package/dist/interfaces/iBanner.d.ts +6 -0
  58. package/dist/interfaces/iBanner.js +2 -0
  59. package/dist/interfaces/iBookmark.d.ts +3 -0
  60. package/dist/interfaces/iBookmark.js +2 -0
  61. package/dist/interfaces/iBrand.d.ts +6 -0
  62. package/dist/interfaces/iBrand.js +2 -0
  63. package/dist/interfaces/iCatalog.d.ts +50 -0
  64. package/dist/interfaces/iCatalog.js +2 -0
  65. package/dist/interfaces/iCatalogNode.d.ts +3 -0
  66. package/dist/interfaces/iCatalogNode.js +2 -0
  67. package/dist/interfaces/iCatalogPreview.d.ts +17 -0
  68. package/dist/interfaces/iCatalogPreview.js +2 -0
  69. package/dist/interfaces/iCity.d.ts +4 -0
  70. package/dist/interfaces/iCity.js +2 -0
  71. package/dist/interfaces/iContentItem.d.ts +7 -0
  72. package/dist/interfaces/iContentItem.js +2 -0
  73. package/dist/interfaces/iEnv.d.ts +6 -0
  74. package/dist/interfaces/iEnv.js +2 -0
  75. package/dist/interfaces/iFilter.d.ts +11 -0
  76. package/dist/interfaces/iFilter.js +2 -0
  77. package/dist/interfaces/iGetNodeListProps.d.ts +8 -0
  78. package/dist/interfaces/iGetNodeListProps.js +2 -0
  79. package/dist/interfaces/iInstruction.d.ts +6 -0
  80. package/dist/interfaces/iInstruction.js +2 -0
  81. package/dist/interfaces/iIntro.d.ts +6 -0
  82. package/dist/interfaces/iIntro.js +2 -0
  83. package/dist/interfaces/iMessage.d.ts +25 -0
  84. package/dist/interfaces/iMessage.js +2 -0
  85. package/dist/interfaces/iNode.d.ts +26 -0
  86. package/dist/interfaces/iNode.js +2 -0
  87. package/dist/interfaces/iNodeTree.d.ts +14 -0
  88. package/dist/interfaces/iNodeTree.js +2 -0
  89. package/dist/interfaces/iPagingItems.d.ts +9 -0
  90. package/dist/interfaces/iPagingItems.js +2 -0
  91. package/dist/interfaces/iPayment.d.ts +20 -0
  92. package/dist/interfaces/iPayment.js +2 -0
  93. package/dist/interfaces/iPush.d.ts +14 -0
  94. package/dist/interfaces/iPush.js +2 -0
  95. package/dist/interfaces/iQuestion.d.ts +7 -0
  96. package/dist/interfaces/iQuestion.js +2 -0
  97. package/dist/interfaces/iReceipt.d.ts +20 -0
  98. package/dist/interfaces/iReceipt.js +2 -0
  99. package/dist/interfaces/iReceiptAddProps.d.ts +8 -0
  100. package/dist/interfaces/iReceiptAddProps.js +2 -0
  101. package/dist/interfaces/iReceiptProduct.d.ts +7 -0
  102. package/dist/interfaces/iReceiptProduct.js +2 -0
  103. package/dist/interfaces/iSearchParams.d.ts +11 -0
  104. package/dist/interfaces/iSearchParams.js +2 -0
  105. package/dist/interfaces/iShop.d.ts +6 -0
  106. package/dist/interfaces/iShop.js +2 -0
  107. package/dist/interfaces/iShopAddress.d.ts +5 -0
  108. package/dist/interfaces/iShopAddress.js +2 -0
  109. package/dist/interfaces/iSorter.d.ts +7 -0
  110. package/dist/interfaces/iSorter.js +2 -0
  111. package/dist/interfaces/iSpecialization.d.ts +4 -0
  112. package/dist/interfaces/iSpecialization.js +2 -0
  113. package/dist/interfaces/iStatView.d.ts +4 -0
  114. package/dist/interfaces/iStatView.js +2 -0
  115. package/dist/interfaces/iStorage.d.ts +5 -0
  116. package/dist/interfaces/iStorage.js +2 -0
  117. package/dist/interfaces/iStories.d.ts +13 -0
  118. package/dist/interfaces/iStories.js +2 -0
  119. package/dist/interfaces/iSubstance.d.ts +4 -0
  120. package/dist/interfaces/iSubstance.js +2 -0
  121. package/dist/interfaces/iSurvey.d.ts +36 -0
  122. package/dist/interfaces/iSurvey.js +2 -0
  123. package/dist/interfaces/iSurveyQuestion.d.ts +11 -0
  124. package/dist/interfaces/iSurveyQuestion.js +2 -0
  125. package/dist/interfaces/iSymptoms.d.ts +4 -0
  126. package/dist/interfaces/iSymptoms.js +2 -0
  127. package/dist/interfaces/iTestQuestion.d.ts +20 -0
  128. package/dist/interfaces/iTestQuestion.js +2 -0
  129. package/dist/interfaces/iTestResult.d.ts +17 -0
  130. package/dist/interfaces/iTestResult.js +2 -0
  131. package/dist/interfaces/iText.d.ts +3 -0
  132. package/dist/interfaces/iText.js +2 -0
  133. package/dist/interfaces/iUser.d.ts +35 -0
  134. package/dist/interfaces/iUser.js +2 -0
  135. package/dist/interfaces/iUserData.d.ts +10 -0
  136. package/dist/interfaces/iUserData.js +2 -0
  137. package/dist/interfaces/iUserEditProps.d.ts +20 -0
  138. package/dist/interfaces/iUserEditProps.js +2 -0
  139. package/dist/interfaces/iUserStat.d.ts +7 -0
  140. package/dist/interfaces/iUserStat.js +2 -0
  141. package/dist/interfaces/iUserSurvey.d.ts +21 -0
  142. package/dist/interfaces/iUserSurvey.js +11 -0
  143. package/dist/interfaces/iUserTokens.d.ts +4 -0
  144. package/dist/interfaces/iUserTokens.js +2 -0
  145. package/dist/interfaces/intRange.d.ts +3 -0
  146. package/dist/interfaces/intRange.js +2 -0
  147. package/dist/interfaces/itemType.d.ts +9 -0
  148. package/dist/interfaces/itemType.js +11 -0
  149. package/dist/interfaces/paymentStatus.d.ts +7 -0
  150. package/dist/interfaces/paymentStatus.js +9 -0
  151. package/dist/interfaces/paymentType.d.ts +5 -0
  152. package/dist/interfaces/paymentType.js +7 -0
  153. package/dist/interfaces/payoutType.d.ts +5 -0
  154. package/dist/interfaces/payoutType.js +7 -0
  155. package/dist/interfaces/pushStatus.d.ts +6 -0
  156. package/dist/interfaces/pushStatus.js +8 -0
  157. package/dist/interfaces/receiptStatus.d.ts +7 -0
  158. package/dist/interfaces/receiptStatus.js +9 -0
  159. package/dist/interfaces/testStatus.d.ts +6 -0
  160. package/dist/interfaces/testStatus.js +7 -0
  161. package/dist/interfaces/userRole.d.ts +6 -0
  162. package/dist/interfaces/userRole.js +14 -0
  163. package/dist/interfaces/userStatEvent.d.ts +5 -0
  164. package/dist/interfaces/userStatEvent.js +7 -0
  165. package/dist/interfaces/viewEventType.d.ts +13 -0
  166. package/dist/interfaces/viewEventType.js +19 -0
  167. package/dist/service/webSocketClient.d.ts +21 -0
  168. package/dist/service/webSocketClient.js +87 -0
  169. package/dist/storage/default.d.ts +2 -0
  170. package/dist/storage/default.js +8 -0
  171. package/dist/storage/user.d.ts +21 -0
  172. package/dist/storage/user.js +107 -0
  173. package/dist/utils/queryString.d.ts +3 -0
  174. package/dist/utils/queryString.js +21 -0
  175. package/package.json +31 -0
  176. package/src/api/abstract.ts +136 -0
  177. package/src/api/bookmarks.ts +109 -0
  178. package/src/api/chat.ts +127 -0
  179. package/src/api/content.ts +258 -0
  180. package/src/api/geoIp.ts +12 -0
  181. package/src/api/index.ts +38 -0
  182. package/src/api/payment.ts +105 -0
  183. package/src/api/promocode.ts +18 -0
  184. package/src/api/push.ts +102 -0
  185. package/src/api/stat.ts +74 -0
  186. package/src/api/survey.ts +164 -0
  187. package/src/api/user.ts +176 -0
  188. package/src/eventBus/abstract.ts +33 -0
  189. package/src/eventBus/userLogin.ts +13 -0
  190. package/src/eventBus/userLogout.ts +12 -0
  191. package/src/http/apiError.ts +9 -0
  192. package/src/http/apiRoute.ts +234 -0
  193. package/src/index.ts +2 -0
  194. package/src/interfaces/BannerType.ts +6 -0
  195. package/src/interfaces/apiStatEvents.ts +18 -0
  196. package/src/interfaces/diplomaStatus.ts +10 -0
  197. package/src/interfaces/iAppStore.ts +1 -0
  198. package/src/interfaces/iArticle.ts +12 -0
  199. package/src/interfaces/iBalance.ts +6 -0
  200. package/src/interfaces/iBank.ts +4 -0
  201. package/src/interfaces/iBanner.ts +6 -0
  202. package/src/interfaces/iBookmark.ts +3 -0
  203. package/src/interfaces/iCity.ts +4 -0
  204. package/src/interfaces/iContentItem.ts +6 -0
  205. package/src/interfaces/iEnv.ts +6 -0
  206. package/src/interfaces/iFilter.ts +16 -0
  207. package/src/interfaces/iGetNodeListProps.ts +9 -0
  208. package/src/interfaces/iInstruction.ts +6 -0
  209. package/src/interfaces/iIntro.ts +6 -0
  210. package/src/interfaces/iMessage.ts +25 -0
  211. package/src/interfaces/iNode.ts +24 -0
  212. package/src/interfaces/iNodeTree.ts +14 -0
  213. package/src/interfaces/iPagingItems.ts +8 -0
  214. package/src/interfaces/iPayment.ts +28 -0
  215. package/src/interfaces/iPush.ts +15 -0
  216. package/src/interfaces/iQuestion.ts +4 -0
  217. package/src/interfaces/iSearchParams.ts +12 -0
  218. package/src/interfaces/iSorter.ts +10 -0
  219. package/src/interfaces/iSpecialization.ts +4 -0
  220. package/src/interfaces/iStatView.ts +4 -0
  221. package/src/interfaces/iStorage.ts +5 -0
  222. package/src/interfaces/iStories.ts +10 -0
  223. package/src/interfaces/iSubstance.ts +4 -0
  224. package/src/interfaces/iSurvey.ts +38 -0
  225. package/src/interfaces/iSurveyQuestion.ts +15 -0
  226. package/src/interfaces/iSymptoms.ts +4 -0
  227. package/src/interfaces/iTestQuestion.ts +21 -0
  228. package/src/interfaces/iTestResult.ts +20 -0
  229. package/src/interfaces/iText.ts +3 -0
  230. package/src/interfaces/iUser.ts +37 -0
  231. package/src/interfaces/iUserData.ts +10 -0
  232. package/src/interfaces/iUserEditProps.ts +16 -0
  233. package/src/interfaces/iUserStat.ts +8 -0
  234. package/src/interfaces/iUserSurvey.ts +23 -0
  235. package/src/interfaces/iUserTokens.ts +4 -0
  236. package/src/interfaces/intRange.ts +5 -0
  237. package/src/interfaces/itemType.ts +10 -0
  238. package/src/interfaces/paymentStatus.ts +8 -0
  239. package/src/interfaces/paymentType.ts +6 -0
  240. package/src/interfaces/payoutType.ts +6 -0
  241. package/src/interfaces/pushStatus.ts +7 -0
  242. package/src/interfaces/testStatus.ts +8 -0
  243. package/src/interfaces/userRole.ts +13 -0
  244. package/src/interfaces/userStatEvent.ts +6 -0
  245. package/src/interfaces/viewEventType.ts +17 -0
  246. package/src/service/webSocketClient.ts +94 -0
  247. package/src/storage/default.ts +7 -0
  248. package/src/storage/user.ts +109 -0
  249. package/src/utils/queryString.ts +22 -0
  250. package/tsconfig.json +27 -0
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UserStorage = void 0;
4
+ const jwt_decode_1 = require("jwt-decode");
5
+ const default_1 = require("./default");
6
+ const userLogout_1 = require("../eventBus/userLogout");
7
+ class UserStorage {
8
+ constructor(storage) {
9
+ this.user = null; // = user
10
+ this.accessToken = null;
11
+ this.accessTokenExp = null; //строк действия accessToken
12
+ this.localTimeDiff = 0; //смещение локального времени в браузере
13
+ this.storage = storage || default_1.storageDefault;
14
+ }
15
+ async getUser() {
16
+ if (this.user)
17
+ return this.user;
18
+ if (typeof window === 'undefined')
19
+ return null; //SSR
20
+ const userStorage = await this.storage.get('user');
21
+ if (typeof userStorage != "string" && userStorage !== null && userStorage !== undefined) {
22
+ throw new Error("Из storage получено некорректное значение пользователя. Должно быть строкой или null");
23
+ }
24
+ if (userStorage) {
25
+ const userStorageDecoded = JSON.parse(userStorage);
26
+ // check exp
27
+ if (this.getCurrentTime() < userStorageDecoded.exp * 1000) { //тут срок refreshToken
28
+ this.user = userStorageDecoded;
29
+ return this.user;
30
+ }
31
+ else { //закончился срок refreshToken
32
+ await this.clearUser();
33
+ await userLogout_1.eventBusUserLogout.broadcast();
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+ async setUser(accessToken, refreshToken) {
39
+ this.user = (0, jwt_decode_1.jwtDecode)(refreshToken);
40
+ await this.storage.set('accessToken', accessToken);
41
+ await this.storage.set('refreshToken', refreshToken);
42
+ await this.storage.set('user', JSON.stringify(this.user));
43
+ return this.user;
44
+ }
45
+ async saveUser({ firstname, lastname }) {
46
+ if (this.user) {
47
+ if (firstname)
48
+ this.user.firstname = firstname;
49
+ if (lastname)
50
+ this.user.lastname = lastname;
51
+ await this.storage.set('user', JSON.stringify(this.user));
52
+ }
53
+ return this.user;
54
+ }
55
+ async clearUser() {
56
+ await this.storage.remove('accessToken');
57
+ await this.storage.remove('refreshToken');
58
+ await this.storage.remove('user');
59
+ this.user = null;
60
+ this.accessToken = null;
61
+ this.accessTokenExp = null;
62
+ this.localTimeDiff = 0;
63
+ }
64
+ getCurrentTime() {
65
+ return Date.now() + this.localTimeDiff;
66
+ }
67
+ async getAccessToken() {
68
+ if (typeof window === 'undefined')
69
+ return null; //SSR
70
+ //кэш с проверкой срока действия
71
+ if (this.accessToken && this.accessTokenExp && this.localTimeDiff && this.getCurrentTime() < this.accessTokenExp * 1000) {
72
+ return this.accessToken;
73
+ }
74
+ //создаем кэш
75
+ this.accessToken = await this.storage.get('accessToken');
76
+ if (typeof this.accessToken != "string" && this.accessToken !== null && this.accessToken !== undefined) {
77
+ throw new Error("Из storage получено некорректное значение accessToken. Должно быть строкой или null");
78
+ }
79
+ if (!this.accessToken)
80
+ return null; //токена нет
81
+ const accessTokenDecoded = (0, jwt_decode_1.jwtDecode)(this.accessToken);
82
+ this.localTimeDiff = accessTokenDecoded?.localTimeDiff ?? 0;
83
+ this.accessTokenExp = accessTokenDecoded?.exp ?? null;
84
+ if (accessTokenDecoded?.exp && this.getCurrentTime() < accessTokenDecoded.exp * 1000) {
85
+ return this.accessToken;
86
+ }
87
+ return null;
88
+ }
89
+ async getRefreshToken() {
90
+ if (typeof window === 'undefined')
91
+ return null; //SSR
92
+ const refreshTokenData = await this.getUser(); //возвращает раскодированный refreshToken и проверяет его дату
93
+ if (refreshTokenData) {
94
+ const refreshToken = await this.storage.get('refreshToken');
95
+ if (typeof refreshToken != "string" && refreshToken !== null && refreshToken !== undefined) {
96
+ throw new Error("Из storage получено некорректное значение refreshToken. Должно быть строкой или null");
97
+ }
98
+ if (refreshToken)
99
+ return refreshToken;
100
+ }
101
+ return null;
102
+ }
103
+ async isAuth() {
104
+ return !!(await this.getUser());
105
+ }
106
+ }
107
+ exports.UserStorage = UserStorage;
@@ -0,0 +1,3 @@
1
+ export declare function queryString(initialObj: {
2
+ [key: string]: any;
3
+ }): any;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.queryString = queryString;
4
+ function queryString(initialObj) {
5
+ const reducer = (obj, parentPrefix = null) => (prev, key) => {
6
+ const val = obj[key];
7
+ key = encodeURIComponent(key);
8
+ const prefix = parentPrefix ? `${parentPrefix}[${key}]` : key;
9
+ if (val == null || typeof val === 'function') {
10
+ prev.push(`${prefix}=`);
11
+ return prev;
12
+ }
13
+ if (['number', 'boolean', 'string'].includes(typeof val)) {
14
+ prev.push(`${prefix}=${encodeURIComponent(val)}`);
15
+ return prev;
16
+ }
17
+ prev.push(Object.keys(val).reduce(reducer(val, prefix), []).join('&'));
18
+ return prev;
19
+ };
20
+ return Object.keys(initialObj).reduce(reducer(initialObj), []).join('&');
21
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@up-im/medotvet_sdk",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1",
8
+ "build": "tsc"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/Oreen/apcheki_sdk.git"
13
+ },
14
+ "author": "Maximum-MK, LLC",
15
+ "license": "MIT",
16
+ "bugs": {
17
+ "url": "https://github.com/Oreen/apcheki_sdk/issues"
18
+ },
19
+ "homepage": "https://www.up-im.ru",
20
+ "dependencies": {
21
+ "jwt-decode": "^4.0.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^20.14.10",
25
+ "typescript": "^5.5.3"
26
+ },
27
+ "exports": {
28
+ ".": "./dist/index.js",
29
+ "./interfaces/*": "./dist/interfaces/*.js"
30
+ }
31
+ }
@@ -0,0 +1,136 @@
1
+ import { eventBusUserLogout } from "../eventBus/userLogout"
2
+ import { ApiError } from "../http/apiError"
3
+ import { apiRouteUserRefresh } from "../http/apiRoute"
4
+ import { iUserTokens } from "../interfaces/iUserTokens"
5
+ import { UserStorage } from "../storage/user"
6
+
7
+ export class ApiAbstract {
8
+ protected host: string
9
+ protected userStorage: UserStorage
10
+ private static isTokenUpdating = false
11
+
12
+ constructor({
13
+ host,
14
+ userStorage
15
+ }: {
16
+ host: string,
17
+ userStorage: UserStorage
18
+ }) {
19
+ this.host = host
20
+ this.userStorage = userStorage
21
+ }
22
+
23
+ protected async fetcher<TResponse>({
24
+ route,
25
+ postBody = null,
26
+ options
27
+ }: {
28
+ route: string,
29
+ postBody?: { [key: string]: any } | null,
30
+ options?: RequestInit & {
31
+ noConsoleError?: boolean
32
+ addToken?: boolean
33
+ }
34
+ }): Promise<TResponse> {
35
+ const formData = new FormData();
36
+ if (postBody) {
37
+ for (let key in postBody) {
38
+ if (postBody[key] instanceof FileList) {
39
+ for (const value of postBody[key]) {
40
+ formData.append(key + '[]', value);
41
+ }
42
+ } else if (Array.isArray(postBody[key])) {
43
+ for (const value of postBody[key]) {
44
+ if (value !== null && typeof value == "object") {
45
+ formData.append(key + '[]', JSON.stringify(value));
46
+ } else {
47
+ formData.append(key + '[]', value);
48
+ }
49
+ }
50
+ } else if (typeof postBody[key] == "object") {
51
+ formData.append(key, JSON.stringify(postBody[key]));
52
+ } else {
53
+ formData.append(key, postBody[key]);
54
+ }
55
+ }
56
+ }
57
+ const headers: { [key: string]: string } = {}
58
+ if (options?.addToken) {
59
+ const token = await this.getToken()
60
+ headers["Authorization"] = `Bearer ${token}`
61
+ } else if (route.startsWith('/user') && !!postBody) {
62
+ headers["timestamp"] = Date.now().toString()
63
+ }
64
+
65
+ try {
66
+ const res = await fetch(this.host + "/api" + route, {
67
+ headers: headers,
68
+ method: postBody ? "POST" : "GET",
69
+ body: postBody ? formData : null,
70
+ credentials: "include",
71
+ ...options
72
+ })
73
+
74
+ if (!res.ok) {
75
+ if (res.status == 413) throw new ApiError(res.status, "Слишком большой размер файла")
76
+ if (res.status == 401) {
77
+ await this.userStorage.clearUser()
78
+ await eventBusUserLogout.broadcast()
79
+ }
80
+ if (res.status == 404 && !postBody) {
81
+ if (!options?.noConsoleError) console.warn(`not_found: ${this.host + "/api" + route}`)
82
+ }
83
+ try {
84
+ //пробуем расшифровать json с ошибкой
85
+ const answer = await res.json()
86
+ const error = new ApiError(res.status, answer.message)
87
+ throw error
88
+ } catch (error: unknown) {
89
+ //ошибка расшифровки json
90
+ if (!options?.noConsoleError) console.warn({ url: this.host + "/api" + route })
91
+ if (!(error instanceof ApiError)) throw new ApiError(res.status, res.statusText)
92
+ throw error
93
+ }
94
+ }
95
+
96
+ return res.json()
97
+ } catch (error) {
98
+ if (error instanceof Error && error.message.toLowerCase().includes("fail")) throw new ApiError(0, "Нет соединения с интернетом")
99
+ throw error
100
+ }
101
+ }
102
+
103
+ protected async getToken(): Promise<string | null> {
104
+ const accessToken = await this.userStorage.getAccessToken()
105
+ if (accessToken) return accessToken
106
+
107
+ const refreshToken = await this.userStorage.getRefreshToken()
108
+ if (refreshToken) {
109
+ //перевыпуск пары токенов
110
+ if (ApiAbstract.isTokenUpdating) {//дедубликация запросов на обновление токена
111
+ await new Promise(r => setTimeout(r, 100));//sleep
112
+ return await this.getToken()
113
+ }
114
+ ApiAbstract.isTokenUpdating = true
115
+ try {
116
+ const data: iUserTokens = await this.fetcher({
117
+ route: apiRouteUserRefresh,
118
+ postBody: { refreshToken: refreshToken }
119
+ })
120
+ await this.userStorage.setUser(data.accessToken, data.refreshToken)
121
+ ApiAbstract.isTokenUpdating = false
122
+ return data.accessToken
123
+ } catch (error: unknown) {
124
+ console.error(error)
125
+ if (error instanceof ApiError) {
126
+ if (error.status === 404) {//токен не найден
127
+ await this.userStorage.clearUser()
128
+ await eventBusUserLogout.broadcast()
129
+ }
130
+ }
131
+ }
132
+ ApiAbstract.isTokenUpdating = false
133
+ }
134
+ return null
135
+ }
136
+ }
@@ -0,0 +1,109 @@
1
+ import { ApiAbstract } from "./abstract";
2
+ import { apiRouteBookmarksAdd, apiRouteBookmarksCount, apiRouteBookmarksGet, apiRouteBookmarksRemove } from "../http/apiRoute";
3
+
4
+ import { ItemType } from "../interfaces/itemType";
5
+ import { iBookmark } from "../interfaces/iBookmark";
6
+ import { ApiError } from "../http/apiError";
7
+
8
+ export class ApiBookmarks extends ApiAbstract {
9
+ private cache = new Map<string, string | false>()//false - если данные не получены с сервера по ключу
10
+ private timeout: ReturnType<typeof setTimeout> | undefined = undefined
11
+ private requestKeys = new Set<string>()
12
+ private total: number | null = null
13
+
14
+ /**
15
+ * принимает id акции (не товара) который находится в поле id метода возвращаемых товаров getCatalogList. В методе есть дебаунсер, что позволяет его вызвать для каждого товара на странице сделав только 1 запрос на сервер
16
+ * @param {number} props.sku - id акции (не товара)
17
+ */
18
+ async inBookmarks({ skuId }: { skuId: number }) {
19
+ if (!skuId || skuId <= 0) return false
20
+ if (!await this.userStorage.isAuth()) return false
21
+ const key = `${ItemType.CATALOG_SKU}-${skuId}`
22
+ return await this.collectKeys(key)
23
+ }
24
+
25
+ private async collectKeys(key: string): Promise<boolean> {
26
+ //has cache
27
+ if (this.cache.has(key)) {
28
+ const val = this.cache.get(key)
29
+ if (val !== false) return true
30
+ return false
31
+ }
32
+ //no cache
33
+ //собираем включи для 1 запроса со всех вызовов
34
+ if (!this.requestKeys.has(key)) {
35
+ if (this.timeout) clearTimeout(this.timeout)
36
+ this.requestKeys.add(key)
37
+ this.timeout = setTimeout(async () => {
38
+ if (!this.requestKeys.size) return
39
+ //все компоненты отрендерены, ключи все запрошены, можно выполнять запрос
40
+ try {
41
+ const arrFromRequestKeys = Array.from(this.requestKeys)
42
+ const data = await this.fetcher<iBookmark[]>({
43
+ route: apiRouteBookmarksGet,
44
+ postBody: { itemKeys: arrFromRequestKeys },
45
+ options: { addToken: true }
46
+ })
47
+ for (const requestKey of arrFromRequestKeys) {
48
+ const find = data.find(it => it.item_key == requestKey)
49
+ this.cache.set(requestKey, find?.item_key || false)
50
+ }
51
+ } catch (error) {
52
+ this.cache.set(key, false)
53
+ console.error(error)
54
+ }
55
+ this.requestKeys.clear()//очистка очереди запросов
56
+ }, 200)
57
+ }
58
+ //ждем последнего запроса
59
+ await new Promise(r => setTimeout(r, 300));//sleep
60
+ return await this.collectKeys(key)//на следующем крузе вернется результат из кэша
61
+ }
62
+
63
+ /**
64
+ * добавление \ удаление из избранного
65
+ * @param {number} props.sku - id акции (не товара)
66
+ * @param {UserStatEvent} props.isAdd - признак добавление или удаление
67
+ */
68
+ async mutate({ skuId, isAdd }: { skuId: number, isAdd: boolean }) {
69
+ if (!skuId || skuId <= 0) return
70
+ if (!await this.userStorage.isAuth()) throw new ApiError(401, "Пользователь не авторизован")
71
+ try {
72
+ await this.fetcher({
73
+ route: isAdd
74
+ ? apiRouteBookmarksAdd
75
+ : apiRouteBookmarksRemove,
76
+ postBody: {
77
+ itemType: ItemType.CATALOG_SKU,
78
+ itemId: skuId
79
+ },
80
+ options: {
81
+ addToken: true
82
+ }
83
+ })
84
+ } catch (error: unknown) {
85
+ console.error(error)
86
+ }
87
+ const key = `${ItemType.CATALOG_SKU}-${skuId}`
88
+ this.cache.set(key, isAdd ? key : false)
89
+ this.total = (this.total ?? 0) + (isAdd ? 1 : -1)
90
+ }
91
+
92
+ /**
93
+ * возвращает кол-во товаров в избранном или null в случае ошибки
94
+ */
95
+ async getTotal() {
96
+ if (this.total !== null) return this.total
97
+ if (!await this.userStorage.isAuth()) return null
98
+ try {
99
+ const total = await this.fetcher<number>({
100
+ route: apiRouteBookmarksCount,
101
+ options: { addToken: true }
102
+ })
103
+ this.total = total
104
+ } catch (error) {
105
+ console.error(error)
106
+ }
107
+ return this.total
108
+ }
109
+ }
@@ -0,0 +1,127 @@
1
+ import { iMessage, iMessageOutgoing, iMessageView } from "../interfaces/iMessage"
2
+ import { ApiAbstract } from "./abstract"
3
+ import { WebSocketClient } from "../service/webSocketClient"
4
+ import { UserStorage } from "../storage/user"
5
+ import { ApiError } from "../http/apiError"
6
+ import { eventBusUserLogout } from "../eventBus/userLogout"
7
+ import { apiRouteChatUnread } from "../http/apiRoute"
8
+ //import { eventBusUserLogin } from "../eventBus/userLogin"
9
+
10
+ export class ApiChat extends ApiAbstract {
11
+ private client: WebSocketClient
12
+ private messageHistory: iMessage[][] = []
13
+ private messageSubscriptions = new Set<(messages: iMessage[]) => void>()
14
+
15
+ constructor({
16
+ host,
17
+ wsHost,
18
+ userStorage
19
+ }: {
20
+ host: string,
21
+ wsHost: string,
22
+ userStorage: UserStorage
23
+ }) {
24
+ super({ host, userStorage });
25
+ this.client = new WebSocketClient(wsHost)
26
+ this.client.onOpen(async () => await this.auth())
27
+ this.client.onMessage(message => {
28
+ //prepare
29
+ const data: iMessage[] = JSON.parse(message)
30
+ if (!Array.isArray(data)) {
31
+ console.error('Входящее сообщения должны быть массивом', data)
32
+ return
33
+ }
34
+ for (const item of data) {
35
+ if ("date" in item) item.date = new Date(item.date)
36
+ }
37
+ //broadcast
38
+ for (const callback of Array.from(this.messageSubscriptions)) {
39
+ callback(data)
40
+ }
41
+ //Последние 10 групп сообщений с сервера для новых подписчиков
42
+ this.messageHistory.push(data)
43
+ if (this.messageHistory.length > 10) {
44
+ this.messageHistory.splice(0, this.messageHistory.length - 10);
45
+ }
46
+ })
47
+ // eventBusUserLogin.subscribe(async () => this.client.connect())
48
+ // this.userStorage.isAuth().then(isAuth => {
49
+ // if (isAuth) this.client.connect()
50
+ // })
51
+ eventBusUserLogout.subscribe(async () => this.client.close())
52
+ }
53
+
54
+ private async auth() {
55
+ const token = await this.getToken()
56
+ if (!token) {
57
+ console.error("User token for WebSocket empty")
58
+ return
59
+ }
60
+ this.client.send(JSON.stringify({ token }), true)
61
+ }
62
+
63
+ messageSubscribe(callback: (messages: iMessage[]) => void) {
64
+ this.messageSubscriptions.add(callback)
65
+ //Последние 10 групп сообщений с сервера для новых подписчиков
66
+ for (const history of this.messageHistory) callback(history)
67
+ return () => { this.messageSubscriptions.delete(callback) }
68
+ }
69
+
70
+ async sendMessage({ text, file }: { text: string, file?: FileList | File | null }) {
71
+ const user = await this.userStorage?.getUser()
72
+ if (!user) throw new ApiError(401, "Пользователь не авторизован")
73
+ if (user.role !== 0) throw new ApiError(400, "Для тестирования чата авторизуйтесь под обычного пользователя")
74
+
75
+ const convertToBase64 = (file: File): Promise<string> => {
76
+ return new Promise((resolve, reject) => {
77
+ const reader = new FileReader();
78
+ reader.onload = () => resolve(reader.result as string);
79
+ reader.onerror = reject;
80
+ reader.readAsDataURL(file);
81
+ })
82
+ }
83
+ const firstFile = file instanceof FileList
84
+ ? Array.from(file)?.pop()
85
+ : file
86
+ const message: iMessageOutgoing = {
87
+ route: "/message",
88
+ text,
89
+ file: firstFile ? await convertToBase64(firstFile) : undefined
90
+ }
91
+ this.client.send(JSON.stringify(message))
92
+ }
93
+ sendView({ id }: { id: number }) {
94
+ const message: iMessageView = {
95
+ route: "/view",
96
+ message_id: id
97
+ }
98
+ this.client.send(JSON.stringify(message))
99
+ }
100
+ /**
101
+ * Получить количество непрочитанных сообщений через обычное API
102
+ */
103
+ async getUnread() {
104
+ if (!await this.userStorage.isAuth()) throw new ApiError(401, "Пользователь не авторизован")
105
+ const data = await this.fetcher<{ unread: number }>({
106
+ route: apiRouteChatUnread,
107
+ options: { addToken: true }
108
+ })
109
+ return data.unread
110
+ }
111
+ // getDialogs({page}:{page: number}) {//оставим для оптимизации если диалогов будет много
112
+ // this.client.send(JSON.stringify({
113
+ // route: "/get/dialogs",
114
+ // page,
115
+ // }))
116
+ // }
117
+ getMessages({ page }: { page: number }) {
118
+ this.client.send(JSON.stringify({
119
+ route: "/get/messages",
120
+ page,
121
+ }))
122
+ }
123
+
124
+ disconnect() {
125
+ this.client.close()
126
+ }
127
+ }