@jolibox/implement 1.2.3 → 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 (74) hide show
  1. package/.rush/temp/package-deps_build.json +44 -28
  2. package/.rush/temp/shrinkwrap-deps.json +2 -1
  3. package/CHANGELOG.json +11 -0
  4. package/CHANGELOG.md +9 -0
  5. package/dist/common/context/index.d.ts +5 -0
  6. package/dist/common/report/base-tracker.d.ts +2 -1
  7. package/dist/common/rewards/cached-fetch-reward.d.ts +46 -0
  8. package/dist/common/rewards/cached-reward-service.d.ts +24 -0
  9. package/dist/common/rewards/fetch-reward.d.ts +2 -3
  10. package/dist/common/rewards/index.d.ts +3 -0
  11. package/dist/common/rewards/registers/use-subscription.d.ts +7 -0
  12. package/dist/common/rewards/registers/utils/coins/jolicoin/cached-fetch-balance.d.ts +34 -0
  13. package/dist/common/rewards/registers/utils/coins/jolicoin/fetch-balance.d.ts +2 -1
  14. package/dist/common/rewards/registers/utils/coins/joligem/cached-fetch-gem-balance.d.ts +34 -0
  15. package/dist/common/rewards/registers/utils/coins/joligem/fetch-gem-balance.d.ts +2 -1
  16. package/dist/common/rewards/registers/utils/subscription/commands/index.d.ts +1 -0
  17. package/dist/common/rewards/registers/utils/subscription/commands/use-subscription.d.ts +4 -0
  18. package/dist/common/rewards/registers/utils/subscription/sub-handler.d.ts +13 -0
  19. package/dist/common/rewards/reward-emitter.d.ts +7 -0
  20. package/dist/common/rewards/reward-helper.d.ts +2 -1
  21. package/dist/common/utils/index.d.ts +18 -0
  22. package/dist/h5/api/platformAdsHandle/JoliboxAdsHandler.d.ts +1 -0
  23. package/dist/h5/bootstrap/auth/__tests__/auth.test.d.ts +1 -0
  24. package/dist/h5/bootstrap/auth/index.d.ts +2 -0
  25. package/dist/h5/bootstrap/auth/sub.d.ts +2 -0
  26. package/dist/index.js +9 -13
  27. package/dist/index.native.js +49 -53
  28. package/dist/native/api/index.d.ts +1 -0
  29. package/dist/native/api/payment.d.ts +1 -0
  30. package/dist/native/payment/__tests__/payment-service-simple.test.d.ts +1 -0
  31. package/dist/native/payment/payment-helper.d.ts +8 -5
  32. package/dist/native/payment/payment-service.d.ts +44 -0
  33. package/implement.build.log +2 -2
  34. package/package.json +8 -7
  35. package/src/common/context/index.ts +12 -0
  36. package/src/common/report/base-tracker.ts +2 -2
  37. package/src/common/rewards/cached-fetch-reward.ts +258 -0
  38. package/src/common/rewards/cached-reward-service.ts +255 -0
  39. package/src/common/rewards/fetch-reward.ts +17 -93
  40. package/src/common/rewards/index.ts +4 -0
  41. package/src/common/rewards/registers/use-subscription.ts +34 -0
  42. package/src/common/rewards/registers/utils/coins/jolicoin/cached-fetch-balance.ts +177 -0
  43. package/src/common/rewards/registers/utils/coins/jolicoin/fetch-balance.ts +13 -1
  44. package/src/common/rewards/registers/utils/coins/jolicoin/jolicoin-handler.ts +2 -0
  45. package/src/common/rewards/registers/utils/coins/joligem/cached-fetch-gem-balance.ts +181 -0
  46. package/src/common/rewards/registers/utils/coins/joligem/fetch-gem-balance.ts +13 -1
  47. package/src/common/rewards/registers/utils/coins/joligem/gem-handler.ts +2 -0
  48. package/src/common/rewards/registers/utils/subscription/commands/index.ts +1 -0
  49. package/src/common/rewards/registers/utils/subscription/commands/use-subscription.ts +29 -0
  50. package/src/common/rewards/registers/utils/subscription/sub-handler.ts +88 -0
  51. package/src/common/rewards/reward-emitter.ts +8 -0
  52. package/src/common/rewards/reward-helper.ts +8 -1
  53. package/src/common/utils/index.ts +23 -0
  54. package/src/h5/api/ads.ts +18 -12
  55. package/src/h5/api/platformAdsHandle/JoliboxAdsHandler.ts +25 -1
  56. package/src/h5/api/storage.ts +2 -2
  57. package/src/h5/bootstrap/auth/__tests__/auth.test.ts +308 -0
  58. package/src/h5/bootstrap/auth/index.ts +20 -0
  59. package/src/h5/bootstrap/auth/sub.ts +56 -0
  60. package/src/h5/bootstrap/index.ts +4 -19
  61. package/src/h5/http/index.ts +2 -2
  62. package/src/h5/report/event-tracker.ts +2 -2
  63. package/src/h5/rewards/index.ts +18 -1
  64. package/src/native/api/ads.ts +7 -1
  65. package/src/native/api/call-host-method.ts +1 -1
  66. package/src/native/api/index.ts +1 -0
  67. package/src/native/api/navigate.ts +10 -1
  68. package/src/native/api/payment.ts +56 -0
  69. package/src/native/payment/__tests__/payment-service-simple.test.ts +274 -0
  70. package/src/native/payment/payment-helper.ts +10 -4
  71. package/src/native/payment/payment-service.ts +293 -0
  72. package/src/native/payment/registers/jolicoin-iap.ts +4 -4
  73. package/src/native/report/index.ts +4 -1
  74. package/src/native/rewards/ui/payment-modal.ts +20 -60
@@ -10,3 +10,4 @@ import './navigate';
10
10
  import './runtime';
11
11
  import './is-native-support';
12
12
  import './call-host-method';
13
+ import './payment';
@@ -0,0 +1 @@
1
+ export {};
@@ -1,16 +1,20 @@
1
1
  export type PaymentType = 'JOLI_COIN' | 'JOLI_COIN_IAP' | 'JOLI_GEM_IAP';
2
2
  import { StandardResponse } from '@jolibox/types';
3
- type PaymentResult = StandardResponse<void>;
3
+ export type PaymentResult<T> = StandardResponse<T>;
4
4
  export interface PaymentHandlerMap {
5
- JOLI_COIN: (productId: string) => Promise<PaymentResult>;
5
+ JOLI_COIN: (productId: string) => Promise<PaymentResult<void>>;
6
6
  JOLI_COIN_IAP: (params: {
7
7
  productId: string;
8
8
  appStoreProductId: string;
9
- }) => Promise<PaymentResult>;
9
+ }) => Promise<PaymentResult<{
10
+ totalAmount: string;
11
+ }>>;
10
12
  JOLI_GEM_IAP: (params: {
11
13
  productId: string;
12
14
  appStoreProductId: string;
13
- }) => Promise<PaymentResult>;
15
+ }) => Promise<PaymentResult<{
16
+ totalAmount: string;
17
+ }>>;
14
18
  }
15
19
  export type PaymentHandler<T extends PaymentType> = PaymentHandlerMap[T];
16
20
  export declare function createPaymentHelper(): {
@@ -18,4 +22,3 @@ export declare function createPaymentHelper(): {
18
22
  invokePayment<T extends PaymentType>(type: T, ...args: Parameters<PaymentHandler<T>>): Promise<any>;
19
23
  };
20
24
  export type PaymentHelper = ReturnType<typeof createPaymentHelper>;
21
- export {};
@@ -0,0 +1,44 @@
1
+ import { IPaymentChoice } from '@/common/rewards/reward-emitter';
2
+ import type { PaymentResult } from './payment-helper';
3
+ import { RequestCacheService } from '@jolibox/common';
4
+ type PaymentPurchaseType = 'JOLI_COIN' | 'JOLI_GEM';
5
+ type PaymentRequest = Record<string, never>;
6
+ interface PaymentResponse {
7
+ balance: number;
8
+ enableAutoDeduct: boolean;
9
+ paymentChoices: IPaymentChoice[];
10
+ }
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<{
26
+ totalAmount: string;
27
+ }>>;
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();
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;
44
+ export {};
@@ -1,9 +1,9 @@
1
1
  Invoking: npm run clean && npm run build:esm && tsc
2
2
 
3
- > @jolibox/implement@1.2.3 clean
3
+ > @jolibox/implement@1.2.5-beta.3 clean
4
4
  > rimraf ./dist
5
5
 
6
6
 
7
- > @jolibox/implement@1.2.3 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,25 +1,26 @@
1
1
  {
2
2
  "name": "@jolibox/implement",
3
3
  "description": "This project is Jolibox JS-SDk implement for Native && H5",
4
- "version": "1.2.3",
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.3",
10
- "@jolibox/types": "1.2.3",
11
- "@jolibox/native-bridge": "1.2.3",
12
- "@jolibox/ads": "1.2.3",
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": {
18
18
  "typescript": "5.7.3",
19
19
  "@types/jest": "28.1.1",
20
+ "@types/node": "18.0.0",
20
21
  "rimraf": "6.0.1",
21
22
  "esbuild": "0.24.2",
22
- "@jolibox/eslint-config": "1.0.0"
23
+ "@jolibox/eslint-config": "1.0.1-beta.11"
23
24
  },
24
25
  "scripts": {
25
26
  "clean": "rimraf ./dist",
@@ -49,6 +49,11 @@ type Viewport = {
49
49
  navigationBarHeight: number;
50
50
  };
51
51
 
52
+ type MPInfo = {
53
+ mpName: string;
54
+ mpVersion?: string;
55
+ };
56
+
52
57
  function hasMetaTag(name: string, content: string): boolean {
53
58
  return document?.head.querySelector(`meta[name="${name}"][content="${content}"]`) !== null;
54
59
  }
@@ -165,6 +170,13 @@ const wrapContext = () => {
165
170
  get abTests(): string[] {
166
171
  return env.abValues?.split(',') ?? [];
167
172
  },
173
+ get mpInfo(): MPInfo {
174
+ return (
175
+ env.mpInfo ?? {
176
+ mpName: 'unknown'
177
+ }
178
+ );
179
+ },
168
180
  onEnvConfigChanged: (newConfig: Partial<Env>) => {
169
181
  mergeWith(env, newConfig, mergeArray);
170
182
  },
@@ -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
+ }