@jolibox/implement 1.2.5-beta.3 → 1.2.5
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/.rush/temp/package-deps_build.json +17 -24
- package/dist/common/cache/request-cache-service.d.ts +111 -0
- package/dist/common/rewards/cached-fetch-reward.d.ts +2 -2
- package/dist/common/rewards/cached-reward-service.d.ts +2 -2
- package/dist/common/rewards/index.d.ts +0 -1
- package/dist/common/rewards/registers/utils/coins/jolicoin/cached-fetch-balance.d.ts +2 -2
- package/dist/common/rewards/registers/utils/coins/joligem/cached-fetch-gem-balance.d.ts +2 -2
- package/dist/common/rewards/reward-emitter.d.ts +0 -7
- package/dist/common/rewards/reward-helper.d.ts +1 -2
- package/dist/common/utils/index.d.ts +0 -18
- package/dist/h5/api/platformAdsHandle/JoliboxAdsHandler.d.ts +0 -1
- package/dist/index.js +9 -9
- package/dist/index.native.js +47 -47
- package/dist/native/payment/payment-service.d.ts +1 -1
- package/implement.build.log +2 -2
- package/package.json +7 -7
- package/src/common/cache/__tests__/request-cache-service.test.ts +686 -0
- package/src/common/cache/request-cache-service.ts +393 -0
- package/src/common/rewards/cached-fetch-reward.ts +2 -19
- package/src/common/rewards/cached-reward-service.ts +3 -3
- package/src/common/rewards/index.ts +0 -1
- package/src/common/rewards/registers/utils/coins/commands/use-payment.ts +8 -0
- package/src/common/rewards/registers/utils/coins/jolicoin/cached-fetch-balance.ts +1 -1
- package/src/common/rewards/registers/utils/coins/joligem/cached-fetch-gem-balance.ts +1 -1
- package/src/common/rewards/reward-emitter.ts +0 -8
- package/src/common/rewards/reward-helper.ts +1 -8
- package/src/common/utils/index.ts +0 -23
- package/src/h5/api/ads.ts +13 -14
- package/src/h5/api/platformAdsHandle/JoliboxAdsHandler.ts +1 -25
- package/src/h5/bootstrap/index.ts +19 -4
- package/src/h5/rewards/index.ts +1 -18
- package/src/native/payment/payment-service.ts +1 -1
- package/CHANGELOG.json +0 -11
- package/CHANGELOG.md +0 -9
- package/dist/common/rewards/registers/use-subscription.d.ts +0 -7
- package/dist/common/rewards/registers/utils/subscription/commands/index.d.ts +0 -1
- package/dist/common/rewards/registers/utils/subscription/commands/use-subscription.d.ts +0 -4
- package/dist/common/rewards/registers/utils/subscription/sub-handler.d.ts +0 -13
- package/dist/h5/bootstrap/auth/index.d.ts +0 -2
- package/dist/h5/bootstrap/auth/sub.d.ts +0 -2
- package/src/common/rewards/registers/use-subscription.ts +0 -34
- package/src/common/rewards/registers/utils/subscription/commands/index.ts +0 -1
- package/src/common/rewards/registers/utils/subscription/commands/use-subscription.ts +0 -29
- package/src/common/rewards/registers/utils/subscription/sub-handler.ts +0 -88
- package/src/h5/bootstrap/auth/__tests__/auth.test.ts +0 -308
- package/src/h5/bootstrap/auth/index.ts +0 -20
- package/src/h5/bootstrap/auth/sub.ts +0 -56
- /package/dist/{h5/bootstrap/auth/__tests__/auth.test.d.ts → common/cache/__tests__/request-cache-service.test.d.ts} +0 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通用请求缓存服务基类
|
|
3
|
+
* 支持部分数据缓存,部分数据实时获取的混合模式
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface CacheConfig<TCacheData, TServerData> {
|
|
7
|
+
/** 缓存持续时间,默认10分钟 */
|
|
8
|
+
duration?: number;
|
|
9
|
+
/** 请求超时时间,默认3秒 */
|
|
10
|
+
timeout?: number;
|
|
11
|
+
/** 缓存键生成函数 */
|
|
12
|
+
keyGenerator?: <TRequest>(endpoint: string, params?: TRequest) => string;
|
|
13
|
+
/** 数据一致性校验函数 */
|
|
14
|
+
consistencyChecker?: (cached: TCacheData, serverData: TServerData) => boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CacheEntry<T> {
|
|
18
|
+
data: T;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
expiresAt: number;
|
|
21
|
+
key: string;
|
|
22
|
+
sequence: number; // 请求序列号
|
|
23
|
+
metadata?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CacheStats {
|
|
27
|
+
cacheCount: number;
|
|
28
|
+
validCount: number;
|
|
29
|
+
expiredCount: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RequestAdapter<TRequest, TResponse, TCacheData, TRealTimeData> {
|
|
33
|
+
/** 执行实际的网络请求 */
|
|
34
|
+
execute(endpoint: string, options?: TRequest): Promise<TResponse>;
|
|
35
|
+
/** 从响应中提取需要缓存的部分(不变数据) */
|
|
36
|
+
extractCacheableData?(response: TResponse): TCacheData;
|
|
37
|
+
/** 从响应中提取实时数据部分(变化数据) */
|
|
38
|
+
extractRealTimeData?(response: TResponse): TRealTimeData;
|
|
39
|
+
/** 合并缓存数据和实时数据 */
|
|
40
|
+
mergeData?(cached: TCacheData, realTime: TRealTimeData): TResponse;
|
|
41
|
+
/** 处理纯缓存场景的数据,当没有新的网络请求时 */
|
|
42
|
+
processCachedData?(cached: TCacheData): TResponse;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export abstract class RequestCacheService<TRequest, TResponse, TCacheData, TRealTimeData> {
|
|
46
|
+
private cache = new Map<string, CacheEntry<TCacheData>>();
|
|
47
|
+
private readonly config: {
|
|
48
|
+
duration: number;
|
|
49
|
+
timeout: number;
|
|
50
|
+
keyGenerator: <TReq>(endpoint: string, params?: TReq) => string;
|
|
51
|
+
consistencyChecker: (cached: TCacheData, serverData: TCacheData) => boolean;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// 请求去重:每个key只允许同时有一个正在进行的请求
|
|
55
|
+
private pendingRequests = new Map<string, Promise<TResponse>>();
|
|
56
|
+
|
|
57
|
+
// 请求序列号:防止晚到的请求覆盖早发出的请求结果
|
|
58
|
+
private requestSequence = 0;
|
|
59
|
+
private cacheSequences = new Map<string, number>();
|
|
60
|
+
|
|
61
|
+
constructor(
|
|
62
|
+
protected requestAdapter: RequestAdapter<TRequest, TResponse, TCacheData, TRealTimeData>,
|
|
63
|
+
config: Partial<CacheConfig<TCacheData, TCacheData>> = {}
|
|
64
|
+
) {
|
|
65
|
+
this.config = {
|
|
66
|
+
duration: 10 * 60 * 1000, // 10分钟
|
|
67
|
+
timeout: 3000, // 3秒
|
|
68
|
+
keyGenerator: <TReq>(endpoint: string, _params?: TReq) => endpoint,
|
|
69
|
+
consistencyChecker: () => true,
|
|
70
|
+
...config
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 带缓存的请求方法 - 缓存优先 + 实时数据混合模式 + 请求去重
|
|
76
|
+
*/
|
|
77
|
+
async request(endpoint: string, options?: TRequest): Promise<TResponse> {
|
|
78
|
+
const cacheKey = this.config.keyGenerator(endpoint, options);
|
|
79
|
+
|
|
80
|
+
// 检查是否有正在进行的请求,如果有则等待其完成
|
|
81
|
+
const pendingRequest = this.pendingRequests.get(cacheKey);
|
|
82
|
+
if (pendingRequest) {
|
|
83
|
+
console.log(`[RequestCacheService] Reusing pending request for ${endpoint}`);
|
|
84
|
+
return pendingRequest;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// internal request promise for background update
|
|
88
|
+
const internalRequestPromise = this.requestInternal(endpoint, options);
|
|
89
|
+
|
|
90
|
+
// createTimeoutPromise
|
|
91
|
+
const timeoutPromise = this.createTimeoutPromise();
|
|
92
|
+
|
|
93
|
+
// usePromiseRace
|
|
94
|
+
const racedPromise = Promise.race([internalRequestPromise, timeoutPromise]).catch(async (error) => {
|
|
95
|
+
// if timeout error, try to get data from cache as fallback
|
|
96
|
+
if (error.message.includes('timeout')) {
|
|
97
|
+
const cachedData = this.getValidCachedData(cacheKey);
|
|
98
|
+
if (cachedData) {
|
|
99
|
+
console.warn(`[RequestCacheService] Request timeout, falling back to cache for ${endpoint}`);
|
|
100
|
+
return this.requestAdapter.processCachedData
|
|
101
|
+
? this.requestAdapter.processCachedData(cachedData)
|
|
102
|
+
: (cachedData as TResponse);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
this.pendingRequests.set(cacheKey, racedPromise);
|
|
109
|
+
|
|
110
|
+
// ensure internal request can be completed in background for cache update
|
|
111
|
+
internalRequestPromise
|
|
112
|
+
.then((result) => {
|
|
113
|
+
// 成功时不需要特殊处理,requestInternal 已经更新了缓存
|
|
114
|
+
console.log(`[RequestCacheService] Background request completed successfully for ${endpoint}`);
|
|
115
|
+
})
|
|
116
|
+
.catch((error) => {
|
|
117
|
+
console.warn(`[RequestCacheService] Background request failed for ${endpoint}:`, error);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const result = await racedPromise;
|
|
122
|
+
return result;
|
|
123
|
+
} finally {
|
|
124
|
+
// 请求完成后清理 pending request
|
|
125
|
+
this.pendingRequests.delete(cacheKey);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async requestInternal(endpoint: string, options?: TRequest): Promise<TResponse> {
|
|
130
|
+
const cacheKey = this.config.keyGenerator(endpoint, options);
|
|
131
|
+
const currentSequence = ++this.requestSequence; // 生成请求序列号
|
|
132
|
+
|
|
133
|
+
const cachedData = this.getValidCachedData(cacheKey);
|
|
134
|
+
|
|
135
|
+
console.log('[RequestCacheService] requestInternal start', endpoint, 'cached:', cachedData);
|
|
136
|
+
if (cachedData) {
|
|
137
|
+
if (this.requestAdapter.extractRealTimeData && this.requestAdapter.mergeData) {
|
|
138
|
+
try {
|
|
139
|
+
const freshResponse = await this.requestAdapter.execute(endpoint, options);
|
|
140
|
+
const realTimeData = this.requestAdapter.extractRealTimeData(freshResponse);
|
|
141
|
+
|
|
142
|
+
const serverCacheableData = this.requestAdapter.extractCacheableData?.(freshResponse);
|
|
143
|
+
if (serverCacheableData && !this.config.consistencyChecker(cachedData, serverCacheableData)) {
|
|
144
|
+
console.log(`[RequestCacheService] Cache invalidated due to data change for ${endpoint}`);
|
|
145
|
+
this.setCachedDataWithSequence(cacheKey, serverCacheableData, currentSequence);
|
|
146
|
+
return freshResponse;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return this.requestAdapter.mergeData(cachedData, realTimeData);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.warn(
|
|
152
|
+
`[RequestCacheService] Failed to fetch real-time data, using cache only for ${endpoint}:`,
|
|
153
|
+
error
|
|
154
|
+
);
|
|
155
|
+
return this.requestAdapter.processCachedData
|
|
156
|
+
? this.requestAdapter.processCachedData(cachedData)
|
|
157
|
+
: (cachedData as TResponse);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(`[RequestCacheService] use cache for ${endpoint}`);
|
|
162
|
+
return this.requestAdapter.processCachedData
|
|
163
|
+
? this.requestAdapter.processCachedData(cachedData)
|
|
164
|
+
: (cachedData as TResponse);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log(
|
|
168
|
+
`[RequestCacheService] Cache miss, fetching fresh data for ${endpoint} (sequence: ${currentSequence})`
|
|
169
|
+
);
|
|
170
|
+
const freshResponse = await this.requestAdapter.execute(endpoint, options);
|
|
171
|
+
|
|
172
|
+
const serverCacheableData = this.requestAdapter.extractCacheableData?.(freshResponse);
|
|
173
|
+
if (serverCacheableData) {
|
|
174
|
+
this.setCachedDataWithSequence(cacheKey, serverCacheableData, currentSequence);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return freshResponse;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* timeout
|
|
182
|
+
*/
|
|
183
|
+
private createTimeoutPromise(): Promise<never> {
|
|
184
|
+
return new Promise((_, reject) => {
|
|
185
|
+
setTimeout(() => {
|
|
186
|
+
reject(
|
|
187
|
+
new Error(`[RequestCacheService] Request timeout after ${this.config.timeout / 1000} seconds`)
|
|
188
|
+
);
|
|
189
|
+
}, this.config.timeout);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* get valid cached data
|
|
195
|
+
*/
|
|
196
|
+
private getValidCachedData(key: string): TCacheData | null {
|
|
197
|
+
const cached = this.cache.get(key);
|
|
198
|
+
|
|
199
|
+
if (!cached) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 检查过期
|
|
204
|
+
if (Date.now() > cached.expiresAt) {
|
|
205
|
+
this.cache.delete(key);
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return cached.data;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 设置缓存数据 (带序列号检查)
|
|
214
|
+
*/
|
|
215
|
+
private setCachedDataWithSequence(
|
|
216
|
+
key: string,
|
|
217
|
+
data: TCacheData,
|
|
218
|
+
sequence: number,
|
|
219
|
+
metadata?: Record<string, unknown>
|
|
220
|
+
): void {
|
|
221
|
+
const existing = this.cache.get(key);
|
|
222
|
+
|
|
223
|
+
// 如果已有缓存且序列号更新(说明当前请求是过时的),则不更新缓存
|
|
224
|
+
if (existing && existing.sequence > sequence) {
|
|
225
|
+
console.log(
|
|
226
|
+
`[RequestCacheService] Ignoring stale cache update for ${key} (current: ${existing.sequence}, incoming: ${sequence})`
|
|
227
|
+
);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const now = Date.now();
|
|
232
|
+
this.cache.set(key, {
|
|
233
|
+
data: JSON.parse(JSON.stringify(data)), // 深拷贝
|
|
234
|
+
timestamp: now,
|
|
235
|
+
expiresAt: now + this.config.duration,
|
|
236
|
+
key,
|
|
237
|
+
sequence,
|
|
238
|
+
metadata
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
this.cacheSequences.set(key, sequence);
|
|
242
|
+
console.log(`[RequestCacheService] Updated cache for ${key} with sequence ${sequence}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* refresh cache
|
|
247
|
+
*/
|
|
248
|
+
async refreshCache(endpoint: string, options?: TRequest): Promise<TResponse> {
|
|
249
|
+
const cacheKey = this.config.keyGenerator(endpoint, options);
|
|
250
|
+
this.cache.delete(cacheKey);
|
|
251
|
+
|
|
252
|
+
return this.request(endpoint, options);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* force request and update cache
|
|
257
|
+
*/
|
|
258
|
+
async forceRequest(endpoint: string, options?: TRequest): Promise<TResponse> {
|
|
259
|
+
const cacheKey = this.config.keyGenerator(endpoint, options);
|
|
260
|
+
const currentSequence = ++this.requestSequence;
|
|
261
|
+
|
|
262
|
+
console.log(`[RequestCacheService] Force request for ${endpoint} (sequence: ${currentSequence})`);
|
|
263
|
+
|
|
264
|
+
const freshResponse = await this.requestAdapter.execute(endpoint, options);
|
|
265
|
+
|
|
266
|
+
const serverCacheableData = this.requestAdapter.extractCacheableData?.(freshResponse);
|
|
267
|
+
|
|
268
|
+
if (serverCacheableData) {
|
|
269
|
+
this.setCachedDataWithSequence(cacheKey, serverCacheableData, currentSequence);
|
|
270
|
+
console.log(`[RequestCacheService] Force updated cache for ${endpoint}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return freshResponse;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* warmup cache - 避免与正在进行的请求冲突
|
|
278
|
+
*/
|
|
279
|
+
async warmupCache(endpoint: string, options?: TRequest): Promise<void> {
|
|
280
|
+
const cacheKey = this.config.keyGenerator(endpoint, options);
|
|
281
|
+
|
|
282
|
+
// 如果有正在进行的请求,跳过 warmup
|
|
283
|
+
if (this.pendingRequests.has(cacheKey)) {
|
|
284
|
+
console.log(`[RequestCacheService] Skipping warmup for ${endpoint} - request in progress`);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 如果已有有效缓存,跳过 warmup
|
|
289
|
+
if (this.hasValidCache(cacheKey)) {
|
|
290
|
+
console.log(`[RequestCacheService] Skipping warmup for ${endpoint} - valid cache exists`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
console.log(`[RequestCacheService] Warming up cache for ${endpoint}`);
|
|
296
|
+
await this.forceRequest(endpoint, options);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.warn(`[RequestCacheService] Cache warmup failed for ${endpoint}:`, error);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* clear cache with target key
|
|
304
|
+
*/
|
|
305
|
+
clearCache(key?: string): void {
|
|
306
|
+
if (key) {
|
|
307
|
+
this.cache.delete(key);
|
|
308
|
+
this.cacheSequences.delete(key);
|
|
309
|
+
this.pendingRequests.delete(key);
|
|
310
|
+
console.log(`[RequestCacheService] Cleared cache for ${key}`);
|
|
311
|
+
} else {
|
|
312
|
+
this.cache.clear();
|
|
313
|
+
this.cacheSequences.clear();
|
|
314
|
+
this.pendingRequests.clear();
|
|
315
|
+
this.requestSequence = 0; // 重置序列号
|
|
316
|
+
console.log('[RequestCacheService] Cleared all cache');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* clear expired cache
|
|
322
|
+
*/
|
|
323
|
+
clearExpiredCache(): void {
|
|
324
|
+
const now = Date.now();
|
|
325
|
+
for (const [key, entry] of this.cache) {
|
|
326
|
+
if (now > entry.expiresAt) {
|
|
327
|
+
this.cache.delete(key);
|
|
328
|
+
this.cacheSequences.delete(key);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
console.log('[RequestCacheService] Cleared expired cache entries');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* get cache stats
|
|
336
|
+
*/
|
|
337
|
+
getCacheStats(): CacheStats & { pendingRequestCount: number } {
|
|
338
|
+
const now = Date.now();
|
|
339
|
+
let validCount = 0;
|
|
340
|
+
let expiredCount = 0;
|
|
341
|
+
|
|
342
|
+
for (const entry of this.cache.values()) {
|
|
343
|
+
if (now > entry.expiresAt) {
|
|
344
|
+
expiredCount++;
|
|
345
|
+
} else {
|
|
346
|
+
validCount++;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
cacheCount: this.cache.size,
|
|
352
|
+
validCount,
|
|
353
|
+
expiredCount,
|
|
354
|
+
pendingRequestCount: this.pendingRequests.size
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* get all cache keys
|
|
360
|
+
*/
|
|
361
|
+
getCacheKeys(): string[] {
|
|
362
|
+
return Array.from(this.cache.keys());
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* check if the target key has valid cache
|
|
367
|
+
*/
|
|
368
|
+
hasValidCache(key: string): boolean {
|
|
369
|
+
const cached = this.cache.get(key);
|
|
370
|
+
return cached ? Date.now() <= cached.expiresAt : false;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* get pending request keys for debugging
|
|
375
|
+
*/
|
|
376
|
+
getPendingRequestKeys(): string[] {
|
|
377
|
+
return Array.from(this.pendingRequests.keys());
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* check if a request is currently pending
|
|
382
|
+
*/
|
|
383
|
+
hasPendingRequest(key: string): boolean {
|
|
384
|
+
return this.pendingRequests.has(key);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* get cache sequence for a specific key
|
|
389
|
+
*/
|
|
390
|
+
getCacheSequence(key: string): number | undefined {
|
|
391
|
+
return this.cacheSequences.get(key);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
@@ -16,7 +16,6 @@ const priority = () => {
|
|
|
16
16
|
return (a: RewardType, b: RewardType) => {
|
|
17
17
|
// Priority order: GEM > JOLI_COIN > ADS
|
|
18
18
|
const priorityMap: Record<RewardType, number> = {
|
|
19
|
-
SUBSCRIPTION: 4,
|
|
20
19
|
JOLI_GEM: 3,
|
|
21
20
|
JOLI_GEM_ONLY: 3,
|
|
22
21
|
JOLI_COIN: 2,
|
|
@@ -30,24 +29,8 @@ const priority = () => {
|
|
|
30
29
|
|
|
31
30
|
const sortRewards = (rewardsTypes: RewardType[]): RewardType[] => {
|
|
32
31
|
if (!rewardsTypes.length) return ['ADS'];
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
}
|
|
32
|
+
if (rewardsTypes.includes('JOLI_GEM') && rewardsTypes.length <= 1) return ['JOLI_GEM_ONLY'];
|
|
33
|
+
if (rewardsTypes.includes('JOLI_COIN') && rewardsTypes.length <= 1) return ['JOLI_COIN_ONLY'];
|
|
51
34
|
|
|
52
35
|
return rewardsTypes.sort(priority());
|
|
53
36
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RequestCacheService, RequestAdapter } from '
|
|
1
|
+
import { RequestCacheService, RequestAdapter } from '../cache/request-cache-service';
|
|
2
2
|
import { IHttpClient } from '../http';
|
|
3
3
|
import { StandardResponse, GlobalConfig } from '@jolibox/types';
|
|
4
4
|
import { IJolicoinRewardOption, IUnlockOption, IJoliCoin, IGem } from './type';
|
|
@@ -50,8 +50,8 @@ class RewardRequestAdapter
|
|
|
50
50
|
extractCacheableData(response: IJolicoinRewardOption): UnlockOptionsCacheData {
|
|
51
51
|
return {
|
|
52
52
|
unlockOptions: response.data?.unlockOptions || [],
|
|
53
|
-
joliCoin: response.extra
|
|
54
|
-
joliGem: response.extra
|
|
53
|
+
joliCoin: response.extra.joliCoin,
|
|
54
|
+
joliGem: response.extra.joliGem || { balance: 0, enableAutoDeduct: false }
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -10,7 +10,6 @@ export * from './registers/use-jolicoin';
|
|
|
10
10
|
export * from './registers/use-jolicoin-only';
|
|
11
11
|
export * from './registers/use-gem';
|
|
12
12
|
export * from './registers/use-gem-only';
|
|
13
|
-
export * from './registers/use-subscription';
|
|
14
13
|
|
|
15
14
|
export { warmupBalanceCache } from './registers/utils/coins/jolicoin/cached-fetch-balance';
|
|
16
15
|
export { warmupGemBalanceCache } from './registers/utils/coins/joligem/cached-fetch-gem-balance';
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
InvokePaymentEventName
|
|
11
11
|
} from '@/common/rewards/reward-emitter';
|
|
12
12
|
import { CURRENCY_HANDLERS, CurrencyType } from './currency-handlers';
|
|
13
|
+
import { refreshGemBalanceCache } from '../joligem/cached-fetch-gem-balance';
|
|
14
|
+
import { refreshBalanceCache } from '../jolicoin/cached-fetch-balance';
|
|
13
15
|
|
|
14
16
|
export const registerUsePaymentCommand = <T extends IJoliCoin | IGem>(
|
|
15
17
|
prefix: 'JOLI_COIN' | 'ADS-JOLI_COIN' | 'JOLI_GEM' | 'ADS-JOLI_GEM',
|
|
@@ -48,6 +50,12 @@ export const registerUsePaymentCommand = <T extends IJoliCoin | IGem>(
|
|
|
48
50
|
currency: currency as T extends IJoliCoin ? 'JOLI_COIN' : 'JOLI_GEM'
|
|
49
51
|
});
|
|
50
52
|
|
|
53
|
+
if (currency === 'JOLI_GEM') {
|
|
54
|
+
refreshGemBalanceCache(httpClient);
|
|
55
|
+
} else {
|
|
56
|
+
refreshBalanceCache(httpClient);
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
return {
|
|
52
60
|
result: paymentResult === 'SUCCESS' ? 'SUCCESS' : 'FAILED'
|
|
53
61
|
};
|
|
@@ -17,8 +17,6 @@ export const UseModalResultEventName = 'ON_USE_MODAL_RESULT' as const;
|
|
|
17
17
|
export const UseModalFrequencyEventName = 'ON_USE_MODAL_FREQUENCY' as const;
|
|
18
18
|
export const UseUnloginModalResultEventName = 'ON_USE_UNLOGIN_MODAL_EVENT' as const;
|
|
19
19
|
export const InvokeUnloginModalEventName = 'INVOKE_UNLOGIN_MODAL_EVENT' as const;
|
|
20
|
-
export const InvokeSubscriptionEventName = 'INVOKE_SUBSCRIPTION_EVENT' as const;
|
|
21
|
-
export const UseSubscriptionResultEventName = 'ON_USE_SUBSCRIPTION_RESULT' as const;
|
|
22
20
|
|
|
23
21
|
type IPaymentResult = 'SUCCESS' | 'FAILED' | 'CANCEL';
|
|
24
22
|
export interface IPaymentEvent {
|
|
@@ -92,10 +90,6 @@ export interface IUseModalFrequencyConfig {
|
|
|
92
90
|
};
|
|
93
91
|
}
|
|
94
92
|
|
|
95
|
-
export interface IUseSubscriptionResultEvent {
|
|
96
|
-
result: 'SUCCESS' | 'CANCEL' | 'FAILED';
|
|
97
|
-
}
|
|
98
|
-
|
|
99
93
|
export interface RewardsEventMap extends Record<string, unknown[]> {
|
|
100
94
|
[UnlockOptionsEventName]: [IUnlockOptionsEvent];
|
|
101
95
|
[InvokePaymentEventName]: [
|
|
@@ -111,8 +105,6 @@ export interface RewardsEventMap extends Record<string, unknown[]> {
|
|
|
111
105
|
IUseUnloginModalEvent
|
|
112
106
|
];
|
|
113
107
|
[UseUnloginModalResultEventName]: [IUseUnloginModalResultEvent];
|
|
114
|
-
[InvokeSubscriptionEventName]: ['SUBSCRIPTION'];
|
|
115
|
-
[UseSubscriptionResultEventName]: [IUseSubscriptionResultEvent];
|
|
116
108
|
}
|
|
117
109
|
|
|
118
110
|
export const originalRewardsEmitter = new EventEmitter<RewardsEventMap>();
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
export type RewardType =
|
|
2
|
-
| 'ADS'
|
|
3
|
-
| 'JOLI_COIN'
|
|
4
|
-
| 'JOLI_COIN_ONLY'
|
|
5
|
-
| 'JOLI_GEM'
|
|
6
|
-
| 'JOLI_GEM_ONLY'
|
|
7
|
-
| 'SUBSCRIPTION';
|
|
1
|
+
export type RewardType = 'ADS' | 'JOLI_COIN' | 'JOLI_COIN_ONLY' | 'JOLI_GEM' | 'JOLI_GEM_ONLY';
|
|
8
2
|
|
|
9
3
|
import { context } from '../context';
|
|
10
4
|
import type { AdsRewardsHandler } from './registers/use-ads';
|
|
@@ -16,7 +10,6 @@ export interface RewardHandlerMap {
|
|
|
16
10
|
JOLI_COIN_ONLY: (params?: unknown) => Promise<boolean>; // coins only
|
|
17
11
|
JOLI_GEM: (params?: unknown) => Promise<boolean>; // gem + ads
|
|
18
12
|
JOLI_GEM_ONLY: (params?: unknown) => Promise<boolean>; // gem only
|
|
19
|
-
SUBSCRIPTION: (params?: unknown) => Promise<boolean>; // subscription
|
|
20
13
|
}
|
|
21
14
|
|
|
22
15
|
export type RewardHandler<T extends RewardType> = RewardHandlerMap[T];
|
|
@@ -8,11 +8,6 @@ const JOLIBOX_JOLI_COIN_USE_EVENT = 'JOLIBOX_JOLI_COIN_USE_EVENT';
|
|
|
8
8
|
const JOLIBOX_JOLI_UNLOGIN_MODAL_EVENT = 'JOLIBOX_JOLI_UNLOGIN_MODAL_EVENT'; // unlogin modal event, triggered when unlogin
|
|
9
9
|
// unlogin modal result event success.
|
|
10
10
|
|
|
11
|
-
// subscrition
|
|
12
|
-
|
|
13
|
-
const JOLIBOX_GET_USER_SUB_STATUS = 'JOLIBOX_GET_USER_SUB_STATUS';
|
|
14
|
-
const JOLIBOX_SUB_EVENT = 'JOLIBOX_SUB_EVENT';
|
|
15
|
-
|
|
16
11
|
interface JoliboxCustomEvent {
|
|
17
12
|
[JOLIBOX_CUSTOM_ADS_EVENT_TYPE]: {
|
|
18
13
|
isAdShowing: boolean;
|
|
@@ -46,12 +41,6 @@ interface JoliboxCustomEvent {
|
|
|
46
41
|
sequenceId: string;
|
|
47
42
|
type: 'ADS-JOLI_COIN' | 'JOLI_COIN';
|
|
48
43
|
};
|
|
49
|
-
[JOLIBOX_GET_USER_SUB_STATUS]: {
|
|
50
|
-
sequenceId: string;
|
|
51
|
-
};
|
|
52
|
-
[JOLIBOX_SUB_EVENT]: {
|
|
53
|
-
sequenceId: string;
|
|
54
|
-
};
|
|
55
44
|
}
|
|
56
45
|
|
|
57
46
|
const notifyCustomEvent = <T extends keyof JoliboxCustomEvent>(eventName: T, data: JoliboxCustomEvent[T]) => {
|
|
@@ -77,10 +66,6 @@ const ON_JOLIBOX_TOPUP_JOLI_COIN_RESULT = 'ON_JOLIBOX_TOPUP_JOLI_COIN_RESULT';
|
|
|
77
66
|
const ON_JOLIBOX_JOLI_COIN_USE_RESULT = 'ON_JOLIBOX_JOLI_COIN_USE_RESULT';
|
|
78
67
|
const ON_JOLIBOX_JOLI_UNLOGIN_MODAL_RESULT_EVENT = 'ON_JOLIBOX_JOLI_UNLOGIN_MODAL_RESULT_EVENT';
|
|
79
68
|
|
|
80
|
-
// subscription
|
|
81
|
-
const ON_GET_USER_SUB_STATUS = 'ON_GET_USER_SUB_STATUS';
|
|
82
|
-
const ON_JOLIBOX_SUB_RESULT_EVENT = 'ON_JOLIBOX_SUB_RESULT_EVENT';
|
|
83
|
-
|
|
84
69
|
interface ReceivedJoliboxCustomEvent {
|
|
85
70
|
[ON_JOLIBOX_TOPUP_JOLI_COIN_RESULT]: {
|
|
86
71
|
sequenceId: string;
|
|
@@ -108,14 +93,6 @@ interface ReceivedJoliboxCustomEvent {
|
|
|
108
93
|
};
|
|
109
94
|
isFirstLogin?: boolean;
|
|
110
95
|
};
|
|
111
|
-
[ON_GET_USER_SUB_STATUS]: {
|
|
112
|
-
sequenceId: string;
|
|
113
|
-
isSubUser: boolean;
|
|
114
|
-
};
|
|
115
|
-
[ON_JOLIBOX_SUB_RESULT_EVENT]: {
|
|
116
|
-
sequenceId: string;
|
|
117
|
-
result: 'SUCCESS' | 'CANCEL' | 'FAILED';
|
|
118
|
-
};
|
|
119
96
|
}
|
|
120
97
|
|
|
121
98
|
type ReceivedJoliboxCustomEventResult = keyof ReceivedJoliboxCustomEvent;
|
package/src/h5/api/ads.ts
CHANGED
|
@@ -6,8 +6,7 @@ import {
|
|
|
6
6
|
createAdsRewardHandler,
|
|
7
7
|
rewardsHelper,
|
|
8
8
|
createJolicoinRewardHandler,
|
|
9
|
-
createJolicoinOnlyRewardHandler
|
|
10
|
-
createSubscriptionRewardHandler
|
|
9
|
+
createJolicoinOnlyRewardHandler
|
|
11
10
|
} from '@/common/rewards';
|
|
12
11
|
import {
|
|
13
12
|
JoliboxAdsForGame,
|
|
@@ -25,7 +24,7 @@ import GamedistributionAdsHandler from './platformAdsHandle/GamedistributionAdsH
|
|
|
25
24
|
import FunmaxAdsHandler from './platformAdsHandle/FunmaxAdsHandler';
|
|
26
25
|
import JoliboxAdsHandler from './platformAdsHandle/JoliboxAdsHandler';
|
|
27
26
|
import { warmupRewardCache } from '@/common/rewards/fetch-reward';
|
|
28
|
-
import { warmupBalanceCache } from '@/common/rewards';
|
|
27
|
+
import { warmupBalanceCache, warmupGemBalanceCache } from '@/common/rewards';
|
|
29
28
|
|
|
30
29
|
declare global {
|
|
31
30
|
interface Window {
|
|
@@ -130,9 +129,13 @@ const adsContext: IAdsContext<'GAME'> = {
|
|
|
130
129
|
}
|
|
131
130
|
};
|
|
132
131
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
const handleUnlockSuccess = (params: { quantity: number; balance: number }) => {
|
|
133
|
+
notifyCustomEvent('JOLIBOX_CUSTOM_REWARDS_EVENT', {
|
|
134
|
+
JOLI_COIN: params
|
|
135
|
+
});
|
|
136
|
+
track('jolicoin_unlock_success', {
|
|
137
|
+
quantity: params.quantity
|
|
138
|
+
});
|
|
136
139
|
};
|
|
137
140
|
|
|
138
141
|
const adsManager = new H5AdsManager(adsContext);
|
|
@@ -142,18 +145,14 @@ rewardsHelper.registerRewardHandler('ADS', createAdsRewardHandler(adsHandler.get
|
|
|
142
145
|
rewardsHelper.registerRewardHandler(
|
|
143
146
|
'JOLI_COIN',
|
|
144
147
|
createJolicoinRewardHandler(httpClient, {
|
|
145
|
-
onUnlockSuccess: (
|
|
146
|
-
notifyCustomEvent('JOLIBOX_CUSTOM_REWARDS_EVENT', {
|
|
147
|
-
JOLI_COIN: params
|
|
148
|
-
});
|
|
149
|
-
}
|
|
148
|
+
onUnlockSuccess: handleUnlockSuccess.bind(this)
|
|
150
149
|
}) as unknown as (params?: unknown) => Promise<boolean>
|
|
151
150
|
);
|
|
152
151
|
|
|
153
152
|
rewardsHelper.registerRewardHandler(
|
|
154
|
-
'
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
'JOLI_COIN_ONLY',
|
|
154
|
+
createJolicoinOnlyRewardHandler(httpClient, {
|
|
155
|
+
onUnlockSuccess: handleUnlockSuccess.bind(this)
|
|
157
156
|
}) as unknown as (params?: unknown) => Promise<boolean>
|
|
158
157
|
);
|
|
159
158
|
|