@jolibox/implement 1.2.4 → 1.2.5-beta.3

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 (61) hide show
  1. package/.rush/temp/package-deps_build.json +36 -23
  2. package/CHANGELOG.json +11 -0
  3. package/CHANGELOG.md +9 -0
  4. package/dist/common/report/base-tracker.d.ts +2 -1
  5. package/dist/common/rewards/cached-fetch-reward.d.ts +46 -0
  6. package/dist/common/rewards/cached-reward-service.d.ts +24 -0
  7. package/dist/common/rewards/fetch-reward.d.ts +2 -3
  8. package/dist/common/rewards/index.d.ts +3 -0
  9. package/dist/common/rewards/registers/use-subscription.d.ts +7 -0
  10. package/dist/common/rewards/registers/utils/coins/jolicoin/cached-fetch-balance.d.ts +34 -0
  11. package/dist/common/rewards/registers/utils/coins/jolicoin/fetch-balance.d.ts +2 -1
  12. package/dist/common/rewards/registers/utils/coins/joligem/cached-fetch-gem-balance.d.ts +34 -0
  13. package/dist/common/rewards/registers/utils/coins/joligem/fetch-gem-balance.d.ts +2 -1
  14. package/dist/common/rewards/registers/utils/subscription/commands/index.d.ts +1 -0
  15. package/dist/common/rewards/registers/utils/subscription/commands/use-subscription.d.ts +4 -0
  16. package/dist/common/rewards/registers/utils/subscription/sub-handler.d.ts +13 -0
  17. package/dist/common/rewards/reward-emitter.d.ts +7 -0
  18. package/dist/common/rewards/reward-helper.d.ts +2 -1
  19. package/dist/common/utils/index.d.ts +18 -0
  20. package/dist/h5/api/platformAdsHandle/JoliboxAdsHandler.d.ts +1 -0
  21. package/dist/h5/bootstrap/auth/__tests__/auth.test.d.ts +1 -0
  22. package/dist/h5/bootstrap/auth/index.d.ts +2 -0
  23. package/dist/h5/bootstrap/auth/sub.d.ts +2 -0
  24. package/dist/index.js +9 -9
  25. package/dist/index.native.js +49 -49
  26. package/dist/native/payment/payment-service.d.ts +36 -30
  27. package/implement.build.log +2 -2
  28. package/package.json +7 -7
  29. package/src/common/report/base-tracker.ts +2 -2
  30. package/src/common/rewards/cached-fetch-reward.ts +258 -0
  31. package/src/common/rewards/cached-reward-service.ts +255 -0
  32. package/src/common/rewards/fetch-reward.ts +17 -93
  33. package/src/common/rewards/index.ts +4 -0
  34. package/src/common/rewards/registers/use-subscription.ts +34 -0
  35. package/src/common/rewards/registers/utils/coins/jolicoin/cached-fetch-balance.ts +177 -0
  36. package/src/common/rewards/registers/utils/coins/jolicoin/fetch-balance.ts +13 -1
  37. package/src/common/rewards/registers/utils/coins/jolicoin/jolicoin-handler.ts +2 -0
  38. package/src/common/rewards/registers/utils/coins/joligem/cached-fetch-gem-balance.ts +181 -0
  39. package/src/common/rewards/registers/utils/coins/joligem/fetch-gem-balance.ts +13 -1
  40. package/src/common/rewards/registers/utils/coins/joligem/gem-handler.ts +2 -0
  41. package/src/common/rewards/registers/utils/subscription/commands/index.ts +1 -0
  42. package/src/common/rewards/registers/utils/subscription/commands/use-subscription.ts +29 -0
  43. package/src/common/rewards/registers/utils/subscription/sub-handler.ts +88 -0
  44. package/src/common/rewards/reward-emitter.ts +8 -0
  45. package/src/common/rewards/reward-helper.ts +8 -1
  46. package/src/common/utils/index.ts +23 -0
  47. package/src/h5/api/ads.ts +18 -12
  48. package/src/h5/api/platformAdsHandle/JoliboxAdsHandler.ts +25 -1
  49. package/src/h5/api/storage.ts +2 -2
  50. package/src/h5/bootstrap/auth/__tests__/auth.test.ts +308 -0
  51. package/src/h5/bootstrap/auth/index.ts +20 -0
  52. package/src/h5/bootstrap/auth/sub.ts +56 -0
  53. package/src/h5/bootstrap/index.ts +4 -19
  54. package/src/h5/http/index.ts +2 -2
  55. package/src/h5/report/event-tracker.ts +2 -2
  56. package/src/h5/rewards/index.ts +18 -1
  57. package/src/native/api/ads.ts +7 -1
  58. package/src/native/api/payment.ts +4 -4
  59. package/src/native/payment/__tests__/payment-service-simple.test.ts +97 -31
  60. package/src/native/payment/payment-service.ts +224 -210
  61. package/src/native/rewards/ui/payment-modal.ts +14 -7
@@ -1,38 +1,44 @@
1
1
  import { IPaymentChoice } from '@/common/rewards/reward-emitter';
2
2
  import type { PaymentResult } from './payment-helper';
3
+ import { RequestCacheService } from '@jolibox/common';
3
4
  type PaymentPurchaseType = 'JOLI_COIN' | 'JOLI_GEM';
4
- export interface CachedPaymentChoices {
5
- choices: IPaymentChoice[];
6
- timestamp: number;
7
- expiresAt: number;
8
- productIds: string[];
5
+ type PaymentRequest = Record<string, never>;
6
+ interface PaymentResponse {
7
+ balance: number;
8
+ enableAutoDeduct: boolean;
9
+ paymentChoices: IPaymentChoice[];
9
10
  }
10
- export declare class PaymentService {
11
- private productInfoCache;
12
- private static paymentChoicesCache;
13
- private static readonly PAYMENT_CHOICES_CACHE_DURATION;
14
- getJolicoinProductsInfo(type: PaymentPurchaseType): Promise<IPaymentChoice[]>;
15
- purchase(type: PaymentPurchaseType, productId: string): Promise<PaymentResult<{
11
+ interface PaymentCacheData {
12
+ paymentChoices: IPaymentChoice[];
13
+ }
14
+ interface PaymentRealTimeData {
15
+ balance: number;
16
+ enableAutoDeduct: boolean;
17
+ }
18
+ declare class BasePaymentService extends RequestCacheService<PaymentRequest, PaymentResponse, PaymentCacheData, PaymentRealTimeData> {
19
+ private apiEndpoint;
20
+ private paymentType;
21
+ private static failureCounters;
22
+ private static readonly MAX_FAILURE_COUNT;
23
+ constructor(apiEndpoint: string, paymentType: PaymentPurchaseType);
24
+ getProductsInfo(): Promise<PaymentResponse | undefined>;
25
+ purchase(productId: string): Promise<PaymentResult<{
16
26
  totalAmount: string;
17
27
  }>>;
18
- private static mergeResponseData;
19
- static getProductsInfo(apiEndpoint: string): Promise<{
20
- balance: number;
21
- enableAutoDeduct: boolean;
22
- paymentChoices: IPaymentChoice[];
23
- } | undefined>;
24
- private static getProductsInfoInternal;
25
- private static getPaymentChoicesFromCache;
26
- private static cachePaymentChoices;
27
- private static extractProductIds;
28
- private static arraysEqual;
29
- private static updateChoicesWithServerData;
30
- static clearPaymentChoicesCache(apiEndpoint?: string): void;
31
- static getPaymentChoicesCacheStats(): {
32
- cacheCount: number;
33
- validCount: number;
34
- expiredCount: number;
35
- };
36
- static clearExpiredPaymentChoicesCache(): void;
28
+ getProductsInfoWithBalance(): Promise<PaymentResponse | undefined>;
29
+ refreshProductsInfo(): Promise<PaymentResponse | undefined>;
30
+ static resetFailureCounters(): void;
31
+ static getFailureCount(endpoint: string): number;
32
+ }
33
+ export declare class JoliCoinPaymentService extends BasePaymentService {
34
+ constructor();
35
+ }
36
+ export declare class JoliGemPaymentService extends BasePaymentService {
37
+ constructor();
37
38
  }
39
+ export declare const createJoliCoinPaymentService: () => JoliCoinPaymentService;
40
+ export declare const createJoliGemPaymentService: () => JoliGemPaymentService;
41
+ export declare const clearNativePriceCache: () => void;
42
+ export declare const resetFailureCounters: () => void;
43
+ export declare const getFailureCount: (endpoint: string) => number;
38
44
  export {};
@@ -1,9 +1,9 @@
1
1
  Invoking: npm run clean && npm run build:esm && tsc
2
2
 
3
- > @jolibox/implement@1.2.4 clean
3
+ > @jolibox/implement@1.2.5-beta.3 clean
4
4
  > rimraf ./dist
5
5
 
6
6
 
7
- > @jolibox/implement@1.2.4 build:esm
7
+ > @jolibox/implement@1.2.5-beta.3 build:esm
8
8
  > BUILD_VERSION=$(node -p "require('./package.json').version") node esbuild.config.js --format=esm
9
9
 
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@jolibox/implement",
3
3
  "description": "This project is Jolibox JS-SDk implement for Native && H5",
4
- "version": "1.2.4",
4
+ "version": "1.2.5-beta.3",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
7
7
  "license": "MIT",
8
8
  "dependencies": {
9
- "@jolibox/common": "1.2.4",
10
- "@jolibox/types": "1.2.4",
11
- "@jolibox/native-bridge": "1.2.4",
12
- "@jolibox/ads": "1.2.4",
9
+ "@jolibox/common": "1.2.5-beta.3",
10
+ "@jolibox/types": "1.2.5-beta.3",
11
+ "@jolibox/native-bridge": "1.2.5-beta.3",
12
+ "@jolibox/ads": "1.2.5-beta.3",
13
13
  "localforage": "1.10.0",
14
- "@jolibox/ui": "1.0.0",
14
+ "@jolibox/ui": "1.2.5-beta.3",
15
15
  "web-vitals": "4.2.4"
16
16
  },
17
17
  "devDependencies": {
@@ -20,7 +20,7 @@
20
20
  "@types/node": "18.0.0",
21
21
  "rimraf": "6.0.1",
22
22
  "esbuild": "0.24.2",
23
- "@jolibox/eslint-config": "1.0.0"
23
+ "@jolibox/eslint-config": "1.0.1-beta.11"
24
24
  },
25
25
  "scripts": {
26
26
  "clean": "rimraf ./dist",
@@ -22,12 +22,12 @@ export abstract class EventTracker {
22
22
 
23
23
  private samplesConfig: ISamplesConfig | null = null;
24
24
 
25
- constructor() {
25
+ constructor(readonly apiHost: string) {
26
26
  this.fetchSamplesConfig();
27
27
  }
28
28
 
29
29
  private async fetchSamplesConfig() {
30
- const host = getApiHost(context.testMode);
30
+ const host = this.apiHost;
31
31
  const path = `${host}/api/fe-configs/js-sdk/samples-config`;
32
32
  const samplesConfig = (await (await fetch(path)).json()) as ISamplesConfig;
33
33
  this.samplesConfig = samplesConfig;
@@ -0,0 +1,258 @@
1
+ import { IHttpClient } from '../http';
2
+ import { RewardsHelper, RewardType } from './reward-helper';
3
+ import { CachedRewardService } from './cached-reward-service';
4
+ import { IUnlockOption } from './type';
5
+ import {
6
+ UnlockOptionsEventName,
7
+ UseModalFrequencyEventName,
8
+ rewardsEmitter,
9
+ DefaltJoliCoinUseAndCharge,
10
+ DefaltLoginGuide
11
+ } from './reward-emitter';
12
+ import { hostEmitter } from '@jolibox/common';
13
+ import { StandardResponse, GlobalConfig } from '@jolibox/types';
14
+
15
+ const priority = () => {
16
+ return (a: RewardType, b: RewardType) => {
17
+ // Priority order: GEM > JOLI_COIN > ADS
18
+ const priorityMap: Record<RewardType, number> = {
19
+ SUBSCRIPTION: 4,
20
+ JOLI_GEM: 3,
21
+ JOLI_GEM_ONLY: 3,
22
+ JOLI_COIN: 2,
23
+ JOLI_COIN_ONLY: 2,
24
+ ADS: 1
25
+ };
26
+
27
+ return priorityMap[b] - priorityMap[a];
28
+ };
29
+ };
30
+
31
+ const sortRewards = (rewardsTypes: RewardType[]): RewardType[] => {
32
+ if (!rewardsTypes.length) return ['ADS'];
33
+
34
+ // Handle JOLI_GEM cases
35
+ if (rewardsTypes.includes('JOLI_GEM')) {
36
+ if (rewardsTypes.length === 1) {
37
+ return ['JOLI_GEM_ONLY'];
38
+ } else if (rewardsTypes.includes('SUBSCRIPTION') && rewardsTypes.length === 2) {
39
+ return ['SUBSCRIPTION', 'JOLI_GEM_ONLY'];
40
+ }
41
+ }
42
+
43
+ // Handle JOLI_COIN cases
44
+ if (rewardsTypes.includes('JOLI_COIN')) {
45
+ if (rewardsTypes.length === 1) {
46
+ return ['JOLI_COIN_ONLY'];
47
+ } else if (rewardsTypes.includes('SUBSCRIPTION') && rewardsTypes.length === 2) {
48
+ return ['SUBSCRIPTION', 'JOLI_COIN_ONLY'];
49
+ }
50
+ }
51
+
52
+ return rewardsTypes.sort(priority());
53
+ };
54
+
55
+ // 创建缓存奖励服务的单例
56
+ let cachedRewardServiceInstance: CachedRewardService | null = null;
57
+
58
+ function getCachedRewardService(httpClient: IHttpClient): CachedRewardService {
59
+ if (!cachedRewardServiceInstance) {
60
+ cachedRewardServiceInstance = new CachedRewardService(httpClient);
61
+ }
62
+ return cachedRewardServiceInstance;
63
+ }
64
+
65
+ /**
66
+ * 使用缓存的奖励获取器
67
+ * 优化版本:unlockOptions 使用缓存,余额信息实时获取
68
+ */
69
+ export const createCachedRewardFetcher = (rewardsHelper: RewardsHelper) => {
70
+ rewardsHelper.registerRewardsFetcher(async (httpClient: IHttpClient) => {
71
+ const defaultRewards: RewardType[] = ['ADS'];
72
+ const cachedRewardService = getCachedRewardService(httpClient);
73
+
74
+ try {
75
+ const res = await cachedRewardService.getUnlockOptions({});
76
+
77
+ if (res.code !== 'SUCCESS') {
78
+ return defaultRewards;
79
+ }
80
+ // 发送事件通知
81
+ rewardsEmitter.emit(UnlockOptionsEventName, {
82
+ options: res.data?.unlockOptions || [],
83
+ userJoliCoin: res.extra?.joliCoin || {
84
+ balance: 0,
85
+ enableAutoDeduct: false
86
+ },
87
+ userGem: res.extra?.joliGem || {
88
+ balance: 0,
89
+ enableAutoDeduct: false
90
+ }
91
+ });
92
+
93
+ const rewardsTypes =
94
+ res.data?.unlockOptions?.map((option: IUnlockOption) => option.type) || Array.from(defaultRewards);
95
+
96
+ return sortRewards(rewardsTypes);
97
+ } catch (e) {
98
+ console.error('getRewardOptions error (cached):', e);
99
+ return defaultRewards;
100
+ }
101
+ });
102
+ };
103
+
104
+ /**
105
+ * 使用缓存的频率配置获取器
106
+ * 优化版本:全局配置使用纯缓存
107
+ */
108
+ export const createCachedRewardFrequencyConfigFetcher = (rewardsHelper: RewardsHelper) => {
109
+ rewardsHelper.registerRewardFrequencyConfigFetcher(async (httpClient: IHttpClient) => {
110
+ const cachedRewardService = getCachedRewardService(httpClient);
111
+
112
+ try {
113
+ const res = await cachedRewardService.getGlobalConfig();
114
+
115
+ // 发送事件通知
116
+ rewardsEmitter.emit(UseModalFrequencyEventName, {
117
+ joliCoinUseAndCharge: res.data?.joliCoinUseAndCharge || DefaltJoliCoinUseAndCharge,
118
+ loginGuide: res.data?.loginGuide || DefaltLoginGuide
119
+ });
120
+
121
+ res.data &&
122
+ hostEmitter.emit('onGlobalConfigChanged', {
123
+ globalConfig: res.data
124
+ });
125
+
126
+ return {
127
+ joliCoinUseAndCharge: res.data?.joliCoinUseAndCharge || DefaltJoliCoinUseAndCharge,
128
+ loginGuide: res.data?.loginGuide || DefaltLoginGuide
129
+ };
130
+ } catch (e) {
131
+ console.error('getGlobalConfig error (cached):', e);
132
+ // 发送兜底事件通知
133
+ rewardsEmitter.emit(UseModalFrequencyEventName, {
134
+ joliCoinUseAndCharge: DefaltJoliCoinUseAndCharge,
135
+ loginGuide: DefaltLoginGuide
136
+ });
137
+
138
+ hostEmitter.emit('onGlobalConfigChanged', {
139
+ globalConfig: {
140
+ joliCoinUseAndCharge: DefaltJoliCoinUseAndCharge,
141
+ loginGuide: DefaltLoginGuide
142
+ }
143
+ });
144
+ return {
145
+ joliCoinUseAndCharge: DefaltJoliCoinUseAndCharge,
146
+ loginGuide: DefaltLoginGuide
147
+ };
148
+ }
149
+ });
150
+ };
151
+
152
+ /**
153
+ * 获取缓存服务实例(用于手动管理缓存)
154
+ */
155
+ export const getRewardCacheService = (httpClient: IHttpClient): CachedRewardService => {
156
+ return getCachedRewardService(httpClient);
157
+ };
158
+
159
+ /**
160
+ * 强制刷新奖励数据缓存
161
+ */
162
+ export const refreshRewardCache = async (httpClient: IHttpClient) => {
163
+ const cachedRewardService = getCachedRewardService(httpClient);
164
+ await cachedRewardService.refreshUnlockOptions({});
165
+ console.log('Reward cache refreshed');
166
+ };
167
+
168
+ /**
169
+ * 强制刷新全局配置缓存
170
+ */
171
+ export const refreshGlobalConfigCache = async (httpClient: IHttpClient) => {
172
+ const cachedRewardService = getCachedRewardService(httpClient);
173
+ await cachedRewardService.refreshGlobalConfig();
174
+ console.log('Global config cache refreshed');
175
+ };
176
+
177
+ /**
178
+ * 预热所有缓存
179
+ */
180
+ export const warmupRewardCache = async (httpClient: IHttpClient) => {
181
+ const cachedRewardService = getCachedRewardService(httpClient);
182
+ await cachedRewardService.warmupCache();
183
+ console.log('Reward cache warmed up');
184
+ };
185
+
186
+ /**
187
+ * 清除所有奖励相关缓存
188
+ */
189
+ export const clearRewardCache = (httpClient: IHttpClient) => {
190
+ const cachedRewardService = getCachedRewardService(httpClient);
191
+ cachedRewardService.clearAllCache();
192
+ console.log('All reward cache cleared');
193
+ };
194
+
195
+ /**
196
+ * 获取缓存统计信息
197
+ */
198
+ export const getRewardCacheStats = (httpClient: IHttpClient) => {
199
+ const cachedRewardService = getCachedRewardService(httpClient);
200
+ return cachedRewardService.getCacheStats();
201
+ };
202
+
203
+ // 保持原有的创建器以便向后兼容
204
+ export const createRewardFetcher = (rewardsHelper: RewardsHelper) => {
205
+ rewardsHelper.registerRewardsFetcher(async (httpClient: IHttpClient) => {
206
+ const defaultRewards: RewardType[] = ['ADS'];
207
+ try {
208
+ const res = await httpClient.post('/api/games/unlock-options', {});
209
+ if (res.code !== 'SUCCESS') {
210
+ return defaultRewards;
211
+ }
212
+
213
+ console.log('-----res fetch reward-----', res);
214
+ rewardsEmitter.emit(UnlockOptionsEventName, {
215
+ options: res.data?.unlockOptions || [],
216
+ userJoliCoin: res.extra?.joliCoin || {
217
+ balance: 0,
218
+ enableAutoDeduct: false
219
+ },
220
+ userGem: res.extra?.joliGem || {
221
+ balance: 0,
222
+ enableAutoDeduct: false
223
+ }
224
+ });
225
+
226
+ const rewardsTypes =
227
+ res.data?.unlockOptions?.map((option: IUnlockOption) => option.type) || Array.from(defaultRewards);
228
+ return sortRewards(rewardsTypes);
229
+ } catch (e) {
230
+ console.error('getRewardOptions error:', e);
231
+ return defaultRewards;
232
+ }
233
+ });
234
+ };
235
+
236
+ export const createRewardFrequencyConfigFetcher = (rewardsHelper: RewardsHelper) => {
237
+ rewardsHelper.registerRewardFrequencyConfigFetcher(async (httpClient: IHttpClient) => {
238
+ const res: StandardResponse<GlobalConfig> = await httpClient.get(
239
+ '/api/fe-configs/web-common/global-config',
240
+ {}
241
+ );
242
+
243
+ rewardsEmitter.emit(UseModalFrequencyEventName, {
244
+ joliCoinUseAndCharge: res.data?.joliCoinUseAndCharge || DefaltJoliCoinUseAndCharge,
245
+ loginGuide: res.data?.loginGuide || DefaltLoginGuide
246
+ });
247
+
248
+ res.data &&
249
+ hostEmitter.emit('onGlobalConfigChanged', {
250
+ globalConfig: res.data
251
+ });
252
+
253
+ return {
254
+ joliCoinUseAndCharge: res.data?.joliCoinUseAndCharge || DefaltJoliCoinUseAndCharge,
255
+ loginGuide: res.data?.loginGuide || DefaltLoginGuide
256
+ };
257
+ });
258
+ };
@@ -0,0 +1,255 @@
1
+ import { RequestCacheService, RequestAdapter } from '@jolibox/common';
2
+ import { IHttpClient } from '../http';
3
+ import { StandardResponse, GlobalConfig } from '@jolibox/types';
4
+ import { IJolicoinRewardOption, IUnlockOption, IJoliCoin, IGem } from './type';
5
+ import { IUseModalFrequencyConfig } from './reward-emitter';
6
+
7
+ // 定义请求参数类型
8
+ interface RewardRequest {
9
+ endpoint: string;
10
+ body?: Record<string, unknown>;
11
+ method?: 'GET' | 'POST';
12
+ }
13
+
14
+ interface UnlockOptionsCacheData {
15
+ unlockOptions: IUnlockOption[];
16
+ joliCoin: IJoliCoin;
17
+ joliGem: IGem;
18
+ }
19
+
20
+ interface UnlockOptionsRealTimeData {
21
+ timestamp?: number;
22
+ }
23
+
24
+ // 定义全局配置的缓存数据
25
+ interface GlobalConfigCacheData {
26
+ joliCoinUseAndCharge: IUseModalFrequencyConfig['joliCoinUseAndCharge'];
27
+ loginGuide: IUseModalFrequencyConfig['loginGuide'];
28
+ // 其他全局配置字段
29
+ [key: string]: unknown;
30
+ }
31
+
32
+ // 奖励接口适配器
33
+ class RewardRequestAdapter
34
+ implements
35
+ RequestAdapter<RewardRequest, IJolicoinRewardOption, UnlockOptionsCacheData, UnlockOptionsRealTimeData>
36
+ {
37
+ constructor(private httpClient: IHttpClient) {}
38
+
39
+ async execute(endpoint: string, options?: RewardRequest): Promise<IJolicoinRewardOption> {
40
+ const method = options?.method || 'POST';
41
+ const body = options?.body || {};
42
+
43
+ if (method === 'GET') {
44
+ return await this.httpClient.get<IJolicoinRewardOption>(endpoint, body);
45
+ } else {
46
+ return await this.httpClient.post<IJolicoinRewardOption>(endpoint, body);
47
+ }
48
+ }
49
+
50
+ extractCacheableData(response: IJolicoinRewardOption): UnlockOptionsCacheData {
51
+ return {
52
+ unlockOptions: response.data?.unlockOptions || [],
53
+ joliCoin: response.extra?.joliCoin,
54
+ joliGem: response.extra?.joliGem || { balance: 0, enableAutoDeduct: false }
55
+ };
56
+ }
57
+
58
+ extractRealTimeData?: undefined;
59
+
60
+ mergeData(cached: UnlockOptionsCacheData, realTime: UnlockOptionsRealTimeData): IJolicoinRewardOption {
61
+ return {
62
+ code: 'SUCCESS',
63
+ message: 'success',
64
+ data: {
65
+ unlockOptions: cached.unlockOptions
66
+ },
67
+ extra: {
68
+ joliCoin: cached.joliCoin,
69
+ joliGem: cached.joliGem
70
+ }
71
+ };
72
+ }
73
+
74
+ processCachedData(cached: UnlockOptionsCacheData): IJolicoinRewardOption {
75
+ return {
76
+ code: 'SUCCESS',
77
+ message: 'success from cache',
78
+ data: {
79
+ unlockOptions: cached.unlockOptions
80
+ },
81
+ extra: {
82
+ joliCoin: cached.joliCoin,
83
+ joliGem: cached.joliGem
84
+ }
85
+ };
86
+ }
87
+ }
88
+
89
+ class GlobalConfigRequestAdapter
90
+ implements
91
+ RequestAdapter<
92
+ RewardRequest,
93
+ StandardResponse<GlobalConfig & IUseModalFrequencyConfig>,
94
+ GlobalConfigCacheData,
95
+ undefined
96
+ >
97
+ {
98
+ constructor(private httpClient: IHttpClient) {}
99
+
100
+ async execute(
101
+ endpoint: string,
102
+ options?: RewardRequest
103
+ ): Promise<StandardResponse<GlobalConfig & IUseModalFrequencyConfig>> {
104
+ const method = options?.method || 'GET';
105
+ const body = options?.body || {};
106
+
107
+ if (method === 'GET') {
108
+ return await this.httpClient.get<StandardResponse<GlobalConfig & IUseModalFrequencyConfig>>(
109
+ endpoint,
110
+ body
111
+ );
112
+ } else {
113
+ return await this.httpClient.post<StandardResponse<GlobalConfig & IUseModalFrequencyConfig>>(
114
+ endpoint,
115
+ body
116
+ );
117
+ }
118
+ }
119
+
120
+ extractCacheableData(
121
+ response: StandardResponse<GlobalConfig & IUseModalFrequencyConfig>
122
+ ): GlobalConfigCacheData {
123
+ const data = response.data;
124
+ return {
125
+ joliCoinUseAndCharge: data?.joliCoinUseAndCharge,
126
+ loginGuide: data?.loginGuide,
127
+ ...data // 包含其他所有配置字段
128
+ } as GlobalConfigCacheData;
129
+ }
130
+
131
+ extractRealTimeData?: undefined;
132
+ mergeData?: undefined;
133
+
134
+ processCachedData(
135
+ cached: GlobalConfigCacheData
136
+ ): StandardResponse<GlobalConfig & IUseModalFrequencyConfig> {
137
+ return {
138
+ code: 'SUCCESS',
139
+ message: 'success from cache',
140
+ data: cached as GlobalConfig & IUseModalFrequencyConfig
141
+ };
142
+ }
143
+ }
144
+
145
+ export class CachedRewardService {
146
+ private unlockOptionsService: RequestCacheService<
147
+ RewardRequest,
148
+ IJolicoinRewardOption,
149
+ UnlockOptionsCacheData,
150
+ UnlockOptionsRealTimeData
151
+ >;
152
+ private globalConfigService: RequestCacheService<
153
+ RewardRequest,
154
+ StandardResponse<GlobalConfig & IUseModalFrequencyConfig>,
155
+ GlobalConfigCacheData,
156
+ undefined
157
+ >;
158
+
159
+ constructor(httpClient: IHttpClient) {
160
+ // 创建 unlock-options 服务,缓存 unlockOptions,实时获取余额
161
+ this.unlockOptionsService = new (RequestCacheService as new (...args: unknown[]) => RequestCacheService<
162
+ RewardRequest,
163
+ IJolicoinRewardOption,
164
+ UnlockOptionsCacheData,
165
+ UnlockOptionsRealTimeData
166
+ >)(new RewardRequestAdapter(httpClient), {
167
+ duration: 30 * 60 * 1000, // 30分钟缓存
168
+ timeout: 1000, // 1s timeout for request
169
+ keyGenerator: (endpoint: string) => endpoint,
170
+ // unlockOptions 结构一致性检查
171
+ consistencyChecker: (cached: UnlockOptionsCacheData, serverData: UnlockOptionsCacheData) => {
172
+ if (cached.unlockOptions.length !== serverData.unlockOptions.length) {
173
+ return false;
174
+ }
175
+ return cached.unlockOptions.every((cachedOption: IUnlockOption, index: number) => {
176
+ const serverOption = serverData.unlockOptions[index];
177
+ return (
178
+ cachedOption.type === serverOption.type &&
179
+ cachedOption.joliGemChoice?.joliGemQuantity === serverOption.joliGemChoice?.joliGemQuantity
180
+ );
181
+ });
182
+ }
183
+ });
184
+
185
+ this.globalConfigService = new (RequestCacheService as new (...args: unknown[]) => RequestCacheService<
186
+ RewardRequest,
187
+ StandardResponse<GlobalConfig & IUseModalFrequencyConfig>,
188
+ GlobalConfigCacheData,
189
+ undefined
190
+ >)(new GlobalConfigRequestAdapter(httpClient), {
191
+ duration: 60 * 60 * 1000, // 60分钟缓存
192
+ timeout: 1000, // 1s timeout for request
193
+ keyGenerator: (endpoint: string) => endpoint
194
+ });
195
+ }
196
+
197
+ async getUnlockOptions(body: Record<string, unknown> = {}): Promise<IJolicoinRewardOption> {
198
+ return await this.unlockOptionsService.request('/api/games/unlock-options', {
199
+ endpoint: '/api/games/unlock-options',
200
+ body,
201
+ method: 'POST'
202
+ });
203
+ }
204
+
205
+ async getGlobalConfig(): Promise<StandardResponse<GlobalConfig & IUseModalFrequencyConfig>> {
206
+ return await this.globalConfigService.request('/api/fe-configs/web-common/global-config', {
207
+ endpoint: '/api/fe-configs/web-common/global-config',
208
+ method: 'GET'
209
+ });
210
+ }
211
+ async refreshUnlockOptions(body: Record<string, unknown> = {}): Promise<IJolicoinRewardOption> {
212
+ return await this.unlockOptionsService.forceRequest('/api/games/unlock-options', {
213
+ endpoint: '/api/games/unlock-options',
214
+ body,
215
+ method: 'POST'
216
+ });
217
+ }
218
+
219
+ async refreshGlobalConfig(): Promise<StandardResponse<GlobalConfig & IUseModalFrequencyConfig>> {
220
+ return await this.globalConfigService.forceRequest('/api/fe-configs/web-common/global-config', {
221
+ endpoint: '/api/fe-configs/web-common/global-config',
222
+ method: 'GET'
223
+ });
224
+ }
225
+
226
+ async warmupCache(): Promise<void> {
227
+ await Promise.all([
228
+ this.unlockOptionsService.warmupCache('/api/games/unlock-options', {
229
+ endpoint: '/api/games/unlock-options',
230
+ method: 'POST'
231
+ }),
232
+ this.globalConfigService.warmupCache('/api/fe-configs/web-common/global-config', {
233
+ endpoint: '/api/fe-configs/web-common/global-config',
234
+ method: 'GET'
235
+ })
236
+ ]);
237
+ }
238
+
239
+ clearAllCache(): void {
240
+ this.unlockOptionsService.clearCache();
241
+ this.globalConfigService.clearCache();
242
+ }
243
+
244
+ getCacheStats() {
245
+ return {
246
+ unlockOptions: this.unlockOptionsService.getCacheStats(),
247
+ globalConfig: this.globalConfigService.getCacheStats()
248
+ };
249
+ }
250
+
251
+ clearExpiredCache(): void {
252
+ this.unlockOptionsService.clearExpiredCache();
253
+ this.globalConfigService.clearExpiredCache();
254
+ }
255
+ }