@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.
- package/.cursor/rules/apcheki_sdk.mdc +90 -0
- package/README.md +136 -0
- package/dist/api/abstract.d.ts +21 -0
- package/dist/api/abstract.js +125 -0
- package/dist/api/bookmarks.d.ts +28 -0
- package/dist/api/bookmarks.js +121 -0
- package/dist/api/chat.d.ts +30 -0
- package/dist/api/chat.js +118 -0
- package/dist/api/content.d.ts +154 -0
- package/dist/api/content.js +237 -0
- package/dist/api/geoIp.d.ts +8 -0
- package/dist/api/geoIp.js +14 -0
- package/dist/api/index.d.ts +27 -0
- package/dist/api/index.js +31 -0
- package/dist/api/payment.d.ts +54 -0
- package/dist/api/payment.js +90 -0
- package/dist/api/promocode.d.ts +9 -0
- package/dist/api/promocode.js +22 -0
- package/dist/api/push.d.ts +54 -0
- package/dist/api/push.js +99 -0
- package/dist/api/receipt.d.ts +34 -0
- package/dist/api/receipt.js +60 -0
- package/dist/api/stat.d.ts +21 -0
- package/dist/api/stat.js +75 -0
- package/dist/api/survey.d.ts +82 -0
- package/dist/api/survey.js +122 -0
- package/dist/api/user.d.ts +80 -0
- package/dist/api/user.js +167 -0
- package/dist/eventBus/abstract.d.ts +4 -0
- package/dist/eventBus/abstract.js +29 -0
- package/dist/eventBus/userLogin.d.ts +5 -0
- package/dist/eventBus/userLogin.js +11 -0
- package/dist/eventBus/userLogout.d.ts +4 -0
- package/dist/eventBus/userLogout.js +11 -0
- package/dist/http/apiError.d.ts +4 -0
- package/dist/http/apiError.js +10 -0
- package/dist/http/apiRoute.d.ts +98 -0
- package/dist/http/apiRoute.js +197 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/interfaces/BannerType.d.ts +5 -0
- package/dist/interfaces/BannerType.js +7 -0
- package/dist/interfaces/apiStatEvents.d.ts +1 -0
- package/dist/interfaces/apiStatEvents.js +18 -0
- package/dist/interfaces/diplomaStatus.d.ts +8 -0
- package/dist/interfaces/diplomaStatus.js +9 -0
- package/dist/interfaces/iAppStore.d.ts +1 -0
- package/dist/interfaces/iAppStore.js +2 -0
- package/dist/interfaces/iAppVersionCheck.d.ts +5 -0
- package/dist/interfaces/iAppVersionCheck.js +2 -0
- package/dist/interfaces/iArticle.d.ts +15 -0
- package/dist/interfaces/iArticle.js +2 -0
- package/dist/interfaces/iBalance.d.ts +6 -0
- package/dist/interfaces/iBalance.js +2 -0
- package/dist/interfaces/iBank.d.ts +4 -0
- package/dist/interfaces/iBank.js +2 -0
- package/dist/interfaces/iBanner.d.ts +6 -0
- package/dist/interfaces/iBanner.js +2 -0
- package/dist/interfaces/iBookmark.d.ts +3 -0
- package/dist/interfaces/iBookmark.js +2 -0
- package/dist/interfaces/iBrand.d.ts +6 -0
- package/dist/interfaces/iBrand.js +2 -0
- package/dist/interfaces/iCatalog.d.ts +50 -0
- package/dist/interfaces/iCatalog.js +2 -0
- package/dist/interfaces/iCatalogNode.d.ts +3 -0
- package/dist/interfaces/iCatalogNode.js +2 -0
- package/dist/interfaces/iCatalogPreview.d.ts +17 -0
- package/dist/interfaces/iCatalogPreview.js +2 -0
- package/dist/interfaces/iCity.d.ts +4 -0
- package/dist/interfaces/iCity.js +2 -0
- package/dist/interfaces/iContentItem.d.ts +7 -0
- package/dist/interfaces/iContentItem.js +2 -0
- package/dist/interfaces/iEnv.d.ts +6 -0
- package/dist/interfaces/iEnv.js +2 -0
- package/dist/interfaces/iFilter.d.ts +11 -0
- package/dist/interfaces/iFilter.js +2 -0
- package/dist/interfaces/iGetNodeListProps.d.ts +8 -0
- package/dist/interfaces/iGetNodeListProps.js +2 -0
- package/dist/interfaces/iInstruction.d.ts +6 -0
- package/dist/interfaces/iInstruction.js +2 -0
- package/dist/interfaces/iIntro.d.ts +6 -0
- package/dist/interfaces/iIntro.js +2 -0
- package/dist/interfaces/iMessage.d.ts +25 -0
- package/dist/interfaces/iMessage.js +2 -0
- package/dist/interfaces/iNode.d.ts +26 -0
- package/dist/interfaces/iNode.js +2 -0
- package/dist/interfaces/iNodeTree.d.ts +14 -0
- package/dist/interfaces/iNodeTree.js +2 -0
- package/dist/interfaces/iPagingItems.d.ts +9 -0
- package/dist/interfaces/iPagingItems.js +2 -0
- package/dist/interfaces/iPayment.d.ts +20 -0
- package/dist/interfaces/iPayment.js +2 -0
- package/dist/interfaces/iPush.d.ts +14 -0
- package/dist/interfaces/iPush.js +2 -0
- package/dist/interfaces/iQuestion.d.ts +7 -0
- package/dist/interfaces/iQuestion.js +2 -0
- package/dist/interfaces/iReceipt.d.ts +20 -0
- package/dist/interfaces/iReceipt.js +2 -0
- package/dist/interfaces/iReceiptAddProps.d.ts +8 -0
- package/dist/interfaces/iReceiptAddProps.js +2 -0
- package/dist/interfaces/iReceiptProduct.d.ts +7 -0
- package/dist/interfaces/iReceiptProduct.js +2 -0
- package/dist/interfaces/iSearchParams.d.ts +11 -0
- package/dist/interfaces/iSearchParams.js +2 -0
- package/dist/interfaces/iShop.d.ts +6 -0
- package/dist/interfaces/iShop.js +2 -0
- package/dist/interfaces/iShopAddress.d.ts +5 -0
- package/dist/interfaces/iShopAddress.js +2 -0
- package/dist/interfaces/iSorter.d.ts +7 -0
- package/dist/interfaces/iSorter.js +2 -0
- package/dist/interfaces/iSpecialization.d.ts +4 -0
- package/dist/interfaces/iSpecialization.js +2 -0
- package/dist/interfaces/iStatView.d.ts +4 -0
- package/dist/interfaces/iStatView.js +2 -0
- package/dist/interfaces/iStorage.d.ts +5 -0
- package/dist/interfaces/iStorage.js +2 -0
- package/dist/interfaces/iStories.d.ts +13 -0
- package/dist/interfaces/iStories.js +2 -0
- package/dist/interfaces/iSubstance.d.ts +4 -0
- package/dist/interfaces/iSubstance.js +2 -0
- package/dist/interfaces/iSurvey.d.ts +36 -0
- package/dist/interfaces/iSurvey.js +2 -0
- package/dist/interfaces/iSurveyQuestion.d.ts +11 -0
- package/dist/interfaces/iSurveyQuestion.js +2 -0
- package/dist/interfaces/iSymptoms.d.ts +4 -0
- package/dist/interfaces/iSymptoms.js +2 -0
- package/dist/interfaces/iTestQuestion.d.ts +20 -0
- package/dist/interfaces/iTestQuestion.js +2 -0
- package/dist/interfaces/iTestResult.d.ts +17 -0
- package/dist/interfaces/iTestResult.js +2 -0
- package/dist/interfaces/iText.d.ts +3 -0
- package/dist/interfaces/iText.js +2 -0
- package/dist/interfaces/iUser.d.ts +35 -0
- package/dist/interfaces/iUser.js +2 -0
- package/dist/interfaces/iUserData.d.ts +10 -0
- package/dist/interfaces/iUserData.js +2 -0
- package/dist/interfaces/iUserEditProps.d.ts +20 -0
- package/dist/interfaces/iUserEditProps.js +2 -0
- package/dist/interfaces/iUserStat.d.ts +7 -0
- package/dist/interfaces/iUserStat.js +2 -0
- package/dist/interfaces/iUserSurvey.d.ts +21 -0
- package/dist/interfaces/iUserSurvey.js +11 -0
- package/dist/interfaces/iUserTokens.d.ts +4 -0
- package/dist/interfaces/iUserTokens.js +2 -0
- package/dist/interfaces/intRange.d.ts +3 -0
- package/dist/interfaces/intRange.js +2 -0
- package/dist/interfaces/itemType.d.ts +9 -0
- package/dist/interfaces/itemType.js +11 -0
- package/dist/interfaces/paymentStatus.d.ts +7 -0
- package/dist/interfaces/paymentStatus.js +9 -0
- package/dist/interfaces/paymentType.d.ts +5 -0
- package/dist/interfaces/paymentType.js +7 -0
- package/dist/interfaces/payoutType.d.ts +5 -0
- package/dist/interfaces/payoutType.js +7 -0
- package/dist/interfaces/pushStatus.d.ts +6 -0
- package/dist/interfaces/pushStatus.js +8 -0
- package/dist/interfaces/receiptStatus.d.ts +7 -0
- package/dist/interfaces/receiptStatus.js +9 -0
- package/dist/interfaces/testStatus.d.ts +6 -0
- package/dist/interfaces/testStatus.js +7 -0
- package/dist/interfaces/userRole.d.ts +6 -0
- package/dist/interfaces/userRole.js +14 -0
- package/dist/interfaces/userStatEvent.d.ts +5 -0
- package/dist/interfaces/userStatEvent.js +7 -0
- package/dist/interfaces/viewEventType.d.ts +13 -0
- package/dist/interfaces/viewEventType.js +19 -0
- package/dist/service/webSocketClient.d.ts +21 -0
- package/dist/service/webSocketClient.js +87 -0
- package/dist/storage/default.d.ts +2 -0
- package/dist/storage/default.js +8 -0
- package/dist/storage/user.d.ts +21 -0
- package/dist/storage/user.js +107 -0
- package/dist/utils/queryString.d.ts +3 -0
- package/dist/utils/queryString.js +21 -0
- package/package.json +31 -0
- package/src/api/abstract.ts +136 -0
- package/src/api/bookmarks.ts +109 -0
- package/src/api/chat.ts +127 -0
- package/src/api/content.ts +258 -0
- package/src/api/geoIp.ts +12 -0
- package/src/api/index.ts +38 -0
- package/src/api/payment.ts +105 -0
- package/src/api/promocode.ts +18 -0
- package/src/api/push.ts +102 -0
- package/src/api/stat.ts +74 -0
- package/src/api/survey.ts +164 -0
- package/src/api/user.ts +176 -0
- package/src/eventBus/abstract.ts +33 -0
- package/src/eventBus/userLogin.ts +13 -0
- package/src/eventBus/userLogout.ts +12 -0
- package/src/http/apiError.ts +9 -0
- package/src/http/apiRoute.ts +234 -0
- package/src/index.ts +2 -0
- package/src/interfaces/BannerType.ts +6 -0
- package/src/interfaces/apiStatEvents.ts +18 -0
- package/src/interfaces/diplomaStatus.ts +10 -0
- package/src/interfaces/iAppStore.ts +1 -0
- package/src/interfaces/iArticle.ts +12 -0
- package/src/interfaces/iBalance.ts +6 -0
- package/src/interfaces/iBank.ts +4 -0
- package/src/interfaces/iBanner.ts +6 -0
- package/src/interfaces/iBookmark.ts +3 -0
- package/src/interfaces/iCity.ts +4 -0
- package/src/interfaces/iContentItem.ts +6 -0
- package/src/interfaces/iEnv.ts +6 -0
- package/src/interfaces/iFilter.ts +16 -0
- package/src/interfaces/iGetNodeListProps.ts +9 -0
- package/src/interfaces/iInstruction.ts +6 -0
- package/src/interfaces/iIntro.ts +6 -0
- package/src/interfaces/iMessage.ts +25 -0
- package/src/interfaces/iNode.ts +24 -0
- package/src/interfaces/iNodeTree.ts +14 -0
- package/src/interfaces/iPagingItems.ts +8 -0
- package/src/interfaces/iPayment.ts +28 -0
- package/src/interfaces/iPush.ts +15 -0
- package/src/interfaces/iQuestion.ts +4 -0
- package/src/interfaces/iSearchParams.ts +12 -0
- package/src/interfaces/iSorter.ts +10 -0
- package/src/interfaces/iSpecialization.ts +4 -0
- package/src/interfaces/iStatView.ts +4 -0
- package/src/interfaces/iStorage.ts +5 -0
- package/src/interfaces/iStories.ts +10 -0
- package/src/interfaces/iSubstance.ts +4 -0
- package/src/interfaces/iSurvey.ts +38 -0
- package/src/interfaces/iSurveyQuestion.ts +15 -0
- package/src/interfaces/iSymptoms.ts +4 -0
- package/src/interfaces/iTestQuestion.ts +21 -0
- package/src/interfaces/iTestResult.ts +20 -0
- package/src/interfaces/iText.ts +3 -0
- package/src/interfaces/iUser.ts +37 -0
- package/src/interfaces/iUserData.ts +10 -0
- package/src/interfaces/iUserEditProps.ts +16 -0
- package/src/interfaces/iUserStat.ts +8 -0
- package/src/interfaces/iUserSurvey.ts +23 -0
- package/src/interfaces/iUserTokens.ts +4 -0
- package/src/interfaces/intRange.ts +5 -0
- package/src/interfaces/itemType.ts +10 -0
- package/src/interfaces/paymentStatus.ts +8 -0
- package/src/interfaces/paymentType.ts +6 -0
- package/src/interfaces/payoutType.ts +6 -0
- package/src/interfaces/pushStatus.ts +7 -0
- package/src/interfaces/testStatus.ts +8 -0
- package/src/interfaces/userRole.ts +13 -0
- package/src/interfaces/userStatEvent.ts +6 -0
- package/src/interfaces/viewEventType.ts +17 -0
- package/src/service/webSocketClient.ts +94 -0
- package/src/storage/default.ts +7 -0
- package/src/storage/user.ts +109 -0
- package/src/utils/queryString.ts +22 -0
- 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,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
|
+
}
|
package/src/api/chat.ts
ADDED
|
@@ -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
|
+
}
|