@jolibox/implement 1.2.5 → 1.2.6
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 +21 -16
- 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 +1 -0
- package/dist/common/rewards/registers/use-subscription.d.ts +7 -0
- 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/registers/utils/subscription/commands/index.d.ts +1 -0
- package/dist/common/rewards/registers/utils/subscription/commands/use-subscription.d.ts +4 -0
- package/dist/common/rewards/registers/utils/subscription/sub-handler.d.ts +13 -0
- package/dist/common/rewards/reward-emitter.d.ts +7 -0
- package/dist/common/rewards/reward-helper.d.ts +2 -1
- package/dist/common/utils/index.d.ts +18 -0
- package/dist/h5/api/platformAdsHandle/JoliboxAdsHandler.d.ts +1 -0
- package/dist/h5/bootstrap/auth/index.d.ts +2 -0
- package/dist/h5/bootstrap/auth/sub.d.ts +2 -0
- package/dist/index.js +9 -9
- package/dist/index.native.js +46 -46
- package/dist/native/payment/payment-service.d.ts +1 -1
- package/implement.build.log +2 -2
- package/package.json +5 -5
- package/src/common/rewards/cached-fetch-reward.ts +19 -2
- package/src/common/rewards/cached-reward-service.ts +3 -3
- package/src/common/rewards/index.ts +1 -0
- package/src/common/rewards/registers/use-subscription.ts +34 -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/registers/utils/subscription/commands/index.ts +1 -0
- package/src/common/rewards/registers/utils/subscription/commands/use-subscription.ts +29 -0
- package/src/common/rewards/registers/utils/subscription/sub-handler.ts +88 -0
- package/src/common/rewards/reward-emitter.ts +8 -0
- package/src/common/rewards/reward-helper.ts +8 -1
- package/src/common/utils/index.ts +23 -0
- package/src/h5/api/ads.ts +14 -2
- package/src/h5/api/platformAdsHandle/JoliboxAdsHandler.ts +25 -1
- package/src/h5/bootstrap/auth/__tests__/auth.test.ts +308 -0
- package/src/h5/bootstrap/auth/index.ts +20 -0
- package/src/h5/bootstrap/auth/sub.ts +56 -0
- package/src/h5/bootstrap/index.ts +4 -19
- package/src/h5/rewards/index.ts +18 -1
- package/src/native/payment/payment-service.ts +1 -1
- package/dist/common/cache/request-cache-service.d.ts +0 -111
- package/src/common/cache/__tests__/request-cache-service.test.ts +0 -686
- package/src/common/cache/request-cache-service.ts +0 -393
- /package/dist/{common/cache/__tests__/request-cache-service.test.d.ts → h5/bootstrap/auth/__tests__/auth.test.d.ts} +0 -0
|
@@ -1,393 +0,0 @@
|
|
|
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
|
-
}
|
|
File without changes
|