@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
@@ -0,0 +1,56 @@
1
+ import { onCustomEvent, notifyCustomEvent } from '@/common/utils';
2
+ import { uuidv4 as v4 } from '@jolibox/common';
3
+ // global event listener manager
4
+ interface PendingRequest {
5
+ resolve: (value: boolean) => void;
6
+ reject: (error: Error) => void;
7
+ timeoutId: NodeJS.Timeout;
8
+ }
9
+
10
+ const pendingRequests = new Map<string, PendingRequest>();
11
+ let isListenerInitialized = false;
12
+
13
+ // initialize global event listener
14
+ const initializeEventListener = () => {
15
+ if (isListenerInitialized) return;
16
+
17
+ onCustomEvent('ON_GET_USER_SUB_STATUS', (data) => {
18
+ const pendingRequest = pendingRequests.get(data.sequenceId);
19
+ if (pendingRequest) {
20
+ clearTimeout(pendingRequest.timeoutId);
21
+ pendingRequest.resolve(data.isSubUser);
22
+ pendingRequests.delete(data.sequenceId);
23
+ }
24
+ });
25
+
26
+ isListenerInitialized = true;
27
+ };
28
+
29
+ // Reset function for testing
30
+ export const resetSubState = () => {
31
+ isListenerInitialized = false;
32
+ pendingRequests.clear();
33
+ };
34
+
35
+ export async function getUserSubStatus(): Promise<boolean> {
36
+ initializeEventListener();
37
+
38
+ return new Promise((resolve, reject) => {
39
+ const sequenceId = v4();
40
+
41
+ const timeoutId = setTimeout(() => {
42
+ pendingRequests.delete(sequenceId);
43
+ reject(new Error('Timeout waiting for user sub status response'));
44
+ }, 3000); // 3 second timeout
45
+
46
+ pendingRequests.set(sequenceId, {
47
+ resolve,
48
+ reject,
49
+ timeoutId
50
+ });
51
+
52
+ notifyCustomEvent('JOLIBOX_GET_USER_SUB_STATUS', {
53
+ sequenceId
54
+ });
55
+ });
56
+ }
@@ -1,10 +1,8 @@
1
- import { hostEmitter, InternalGlobalJSError, UserCustomError } from '@jolibox/common';
1
+ import { hostEmitter } from '@jolibox/common';
2
2
  import { taskTracker, track } from '../report';
3
3
  import { onFCP, onLCP, onTTFB } from 'web-vitals';
4
4
  import { context } from '@/common/context';
5
- import { httpClientManager } from '../http';
6
- import { StandardResponse } from '@jolibox/types';
7
- import { reportError } from '@/common/report/errors/report';
5
+ import { checkSession, getUserSubStatus } from './auth';
8
6
 
9
7
  function trackPerformance() {
10
8
  onFCP((metric) => {
@@ -32,24 +30,11 @@ function trackPerformance() {
32
30
  });
33
31
  }
34
32
 
35
- async function checkSession(): Promise<boolean> {
36
- const httpClient = httpClientManager.create();
37
- try {
38
- const response = await httpClient.get<StandardResponse<void>>('/api/users/info');
39
- if (response.code !== 'SUCCESS' || !response.data) {
40
- return false;
41
- }
42
- return true;
43
- } catch (error) {
44
- reportError(new InternalGlobalJSError(error as Error));
45
- return false;
46
- }
47
- }
48
-
49
33
  function addDomContentLoaded() {
50
34
  hostEmitter.on('onDocumentReady', async (startTime: number) => {
51
35
  const isLogin = await checkSession();
52
- context.onEnvConfigChanged({ hostUserInfo: { isLogin } });
36
+ const isSubUser = await getUserSubStatus().catch(() => false);
37
+ context.onEnvConfigChanged({ hostUserInfo: { isLogin, isSubUser } });
53
38
  hostEmitter.emit('LifecycleEvent.onReady', {
54
39
  ...(context.hostUserInfo ? context.hostUserInfo : { isLogin: false })
55
40
  });
@@ -1,7 +1,7 @@
1
1
  import { IHttpClient } from '@/common/http';
2
2
  import { xUserAgent } from '../../common/http/xua';
3
3
  import { context } from '@/common/context';
4
- import { getApiHost, platform } from '@jolibox/common';
4
+ import { getH5ApiHost, platform } from '@jolibox/common';
5
5
 
6
6
  declare global {
7
7
  interface Window {
@@ -68,7 +68,7 @@ export class JoliboxHttpClient implements IHttpClient {
68
68
  };
69
69
 
70
70
  constructor(config?: IHttpClientInitParams) {
71
- const defaultUrl = getApiHost(context.testMode);
71
+ const defaultUrl = getH5ApiHost(context.testMode);
72
72
  this.baseUrl = config?.baseUrl ?? defaultUrl;
73
73
  }
74
74
 
@@ -1,7 +1,7 @@
1
1
  import { context } from '@/common/context';
2
2
  import { EventTracker } from '@/common/report';
3
3
  import { httpClientManager } from '@/h5/http';
4
- import { getCollectHost } from '@jolibox/common';
4
+ import { getCollectHost, getH5ApiHost } from '@jolibox/common';
5
5
 
6
6
  export class H5EventTracker extends EventTracker {
7
7
  private get apiBaseURL() {
@@ -24,4 +24,4 @@ export class H5EventTracker extends EventTracker {
24
24
  }
25
25
  }
26
26
 
27
- export const tracker = new H5EventTracker();
27
+ export const tracker = new H5EventTracker(getH5ApiHost(context.testMode));
@@ -13,7 +13,9 @@ import {
13
13
  IInvokePaymentEvent,
14
14
  IUseModalResultEvent,
15
15
  UseUnloginModalResultEventName,
16
- InvokeUnloginModalEventName
16
+ InvokeUnloginModalEventName,
17
+ InvokeSubscriptionEventName,
18
+ UseSubscriptionResultEventName
17
19
  } from '@/common/rewards/reward-emitter';
18
20
  import { notifyCustomEvent, onCustomEvent } from '@/common/utils';
19
21
  import { uuidv4 as v4 } from '@jolibox/common';
@@ -108,3 +110,18 @@ rewardsEmitter.on(InvokeUnloginModalEventName, async (type) => {
108
110
  type: type
109
111
  });
110
112
  });
113
+
114
+ // subscription
115
+ onCustomEvent('ON_JOLIBOX_SUB_RESULT_EVENT', (params) => {
116
+ console.log('----on custom event ON_JOLIBOX_SUB_RESULT_EVENT-----', params);
117
+ rewardsEmitter.emit(UseSubscriptionResultEventName, {
118
+ result: params.result
119
+ });
120
+ });
121
+
122
+ rewardsEmitter.on(InvokeSubscriptionEventName, async (type) => {
123
+ console.log('----notify custom event-----', type);
124
+ notifyCustomEvent('JOLIBOX_SUB_EVENT', {
125
+ sequenceId: v4()
126
+ });
127
+ });
@@ -25,9 +25,12 @@ import {
25
25
  createJolicoinRewardHandler,
26
26
  createJolicoinOnlyRewardHandler,
27
27
  createGemRewardHandler,
28
- createGemOnlyRewardHandler
28
+ createGemOnlyRewardHandler,
29
+ warmupGemBalanceCache,
30
+ warmupBalanceCache
29
31
  } from '@/common/rewards';
30
32
  import { adEventEmitter } from '@/common/ads';
33
+ import { warmupRewardCache } from '@/common/rewards/fetch-reward';
31
34
 
32
35
  const checkNetworkStatus = () => {
33
36
  const { data } = invokeNative('getNetworkStatusSync');
@@ -168,6 +171,9 @@ const showUnlockSuccessToast = (params: { quantity: number; balance: number }) =
168
171
  });
169
172
  };
170
173
 
174
+ warmupRewardCache(httpClient);
175
+ warmupGemBalanceCache(httpClient);
176
+
171
177
  export const showGemSuccessToast = (params: { quantity: number; balance: number }) => {
172
178
  const { quantity } = params;
173
179
  const toastTemplate = `{slot-correct} ${quantity * -1} {slot-gem}`;
@@ -1,11 +1,11 @@
1
1
  import { createCommands } from '@jolibox/common';
2
2
  import { createAPI, registerCanIUse, t } from './base';
3
- import { PaymentService } from '../payment/payment-service';
3
+ import { createJoliGemPaymentService } from '../payment/payment-service';
4
4
  import { createAPIError } from '@/common/report/errors';
5
5
  import { IPaymentChoice } from '@jolibox/ui/dist/bridge/coin';
6
6
 
7
7
  const commands = createCommands();
8
- const paymentService = new PaymentService();
8
+ const paymentService = createJoliGemPaymentService();
9
9
 
10
10
  const purchaseGem = createAPI('purchaseGem', {
11
11
  paramsSchema: t.tuple(
@@ -14,7 +14,7 @@ const purchaseGem = createAPI('purchaseGem', {
14
14
  })
15
15
  ),
16
16
  implement: async (params: { productId: string }): Promise<{ totalAmount: string }> => {
17
- const result = await paymentService.purchase('JOLI_GEM', params.productId);
17
+ const result = await paymentService.purchase(params.productId);
18
18
 
19
19
  if (result.code !== 'SUCCESS' || !result.data?.totalAmount) {
20
20
  throw createAPIError({
@@ -32,7 +32,7 @@ const purchaseGem = createAPI('purchaseGem', {
32
32
  const getGemProducts = createAPI('getGemProducts', {
33
33
  paramsSchema: t.tuple(),
34
34
  implement: async (): Promise<{ products: IPaymentChoice[] }> => {
35
- const choices = await paymentService.getJolicoinProductsInfo('JOLI_GEM');
35
+ const choices = (await paymentService.getProductsInfo())?.paymentChoices ?? [];
36
36
  console.info('choices', choices);
37
37
  return {
38
38
  products: choices.map((choice) => ({
@@ -1,4 +1,10 @@
1
- import { PaymentService } from '../payment-service';
1
+ // 导入类以便访问静态方法
2
+ import {
3
+ JoliGemPaymentService,
4
+ clearNativePriceCache,
5
+ resetFailureCounters,
6
+ getFailureCount
7
+ } from '../payment-service';
2
8
 
3
9
  // Mock dependencies
4
10
  jest.mock('@jolibox/native-bridge', () => ({
@@ -20,7 +26,8 @@ const mockApplyNative = applyNative;
20
26
  const mockFetch = fetch;
21
27
 
22
28
  describe('PaymentService - Basic Tests', () => {
23
- const mockApiEndpoint = '/api/joli-coin/balance-detail';
29
+ const mockApiEndpoint = '/api/joli-gem/balance-detail';
30
+ let paymentService: JoliGemPaymentService;
24
31
 
25
32
  const mockServerData = {
26
33
  balance: 100,
@@ -42,7 +49,9 @@ describe('PaymentService - Basic Tests', () => {
42
49
  jest.clearAllMocks();
43
50
  jest.clearAllTimers();
44
51
  jest.useFakeTimers();
45
- PaymentService.clearPaymentChoicesCache();
52
+ paymentService = new JoliGemPaymentService(); // 每次创建新实例
53
+ clearNativePriceCache(); // Clear native price cache
54
+ resetFailureCounters(); // Clear failure counters
46
55
  });
47
56
 
48
57
  afterEach(() => {
@@ -63,7 +72,7 @@ describe('PaymentService - Basic Tests', () => {
63
72
  data: mockNativeData
64
73
  });
65
74
 
66
- const result = await PaymentService.getProductsInfo(mockApiEndpoint);
75
+ const result = await paymentService.getProductsInfo();
67
76
 
68
77
  expect(mockFetch).toHaveBeenCalledTimes(1);
69
78
  expect(mockApplyNative).toHaveBeenCalledTimes(1);
@@ -80,7 +89,8 @@ describe('PaymentService - Basic Tests', () => {
80
89
  ]
81
90
  });
82
91
 
83
- const stats = PaymentService.getPaymentChoicesCacheStats();
92
+ // Check cache stats using RequestCacheService methods
93
+ const stats = paymentService.getCacheStats();
84
94
  expect(stats.cacheCount).toBe(1);
85
95
  expect(stats.validCount).toBe(1);
86
96
  });
@@ -99,17 +109,17 @@ describe('PaymentService - Basic Tests', () => {
99
109
  });
100
110
 
101
111
  // First call
102
- await PaymentService.getProductsInfo(mockApiEndpoint);
112
+ await paymentService.getProductsInfo();
103
113
 
104
- // Second call - should use cache
105
- const result = await PaymentService.getProductsInfo(mockApiEndpoint);
114
+ // Second call - should use cache for native prices but still fetch real-time data
115
+ const result = await paymentService.getProductsInfo();
106
116
 
107
- expect(mockFetch).toHaveBeenCalledTimes(2); // Still fetch for real-time data
108
- expect(mockApplyNative).toHaveBeenCalledTimes(1); // Only called once
117
+ expect(mockFetch).toHaveBeenCalledTimes(1); // Still fetch for real-time data (balance, enableAutoDeduct)
118
+ expect(mockApplyNative).toHaveBeenCalledTimes(1); // Only called once due to native price caching
109
119
 
110
120
  expect(result).toEqual({
111
- balance: 100,
112
- enableAutoDeduct: true,
121
+ balance: 0,
122
+ enableAutoDeduct: false,
113
123
  paymentChoices: [
114
124
  {
115
125
  productId: 'coin_100',
@@ -121,16 +131,16 @@ describe('PaymentService - Basic Tests', () => {
121
131
  });
122
132
  });
123
133
 
124
- it('should timeout after 3 seconds', async () => {
134
+ it('should timeout after 1 second', async () => {
125
135
  // Mock a slow server response
126
- mockFetch.mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 5000)));
136
+ mockFetch.mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 2000)));
127
137
 
128
- const promise = PaymentService.getProductsInfo(mockApiEndpoint);
138
+ const promise = paymentService.getProductsInfo();
129
139
 
130
- // Fast forward past timeout
131
- jest.advanceTimersByTime(3000);
140
+ // Fast forward past timeout (1 second)
141
+ jest.advanceTimersByTime(1000);
132
142
 
133
- await expect(promise).rejects.toThrow('[PaymentService] Request timeout after 3 seconds');
143
+ await expect(promise).rejects.toThrow('[RequestCacheService] Request timeout after 1 seconds');
134
144
  });
135
145
 
136
146
  it('should handle cache expiration', async () => {
@@ -147,13 +157,13 @@ describe('PaymentService - Basic Tests', () => {
147
157
  });
148
158
 
149
159
  // First call
150
- await PaymentService.getProductsInfo(mockApiEndpoint);
160
+ await paymentService.getProductsInfo();
151
161
 
152
162
  // Fast forward time beyond cache duration (10 minutes)
153
- jest.advanceTimersByTime(11 * 60 * 1000);
163
+ jest.advanceTimersByTime(40 * 60 * 1000);
154
164
 
155
165
  // Second call - cache should be expired
156
- await PaymentService.getProductsInfo(mockApiEndpoint);
166
+ await paymentService.getProductsInfo();
157
167
 
158
168
  expect(mockApplyNative).toHaveBeenCalledTimes(2); // Called again due to expiration
159
169
  });
@@ -170,22 +180,22 @@ describe('PaymentService - Basic Tests', () => {
170
180
  // Mock applyNative to reject
171
181
  mockApplyNative.mockRejectedValue(new Error('Native call failed'));
172
182
 
173
- await expect(PaymentService.getProductsInfo(mockApiEndpoint)).rejects.toThrow('Native call failed');
183
+ await expect(paymentService.getProductsInfo()).rejects.toThrow('Native call failed');
174
184
  });
175
185
 
176
186
  it('should throw error when mockFetch fails', async () => {
177
187
  // Mock fetch to reject
178
188
  mockFetch.mockRejectedValue(new Error('Network error'));
179
189
 
180
- await expect(PaymentService.getProductsInfo(mockApiEndpoint)).rejects.toThrow('Network error');
190
+ await expect(paymentService.getProductsInfo()).rejects.toThrow('Network error');
181
191
  });
182
192
 
183
- it('should throw error when request exceeds 3 seconds timeout', async () => {
184
- // Mock a very slow fetch request that never resolves within 3 seconds
193
+ it('should throw error when request exceeds 1 second timeout', async () => {
194
+ // Mock a very slow fetch request that never resolves within 1 second
185
195
  mockFetch.mockImplementation(
186
196
  () =>
187
197
  new Promise((resolve) => {
188
- // This will resolve after 5 seconds, but timeout should trigger at 3 seconds
198
+ // This will resolve after 2 seconds, but timeout should trigger at 1 second
189
199
  setTimeout(() => {
190
200
  resolve({
191
201
  response: {
@@ -194,15 +204,71 @@ describe('PaymentService - Basic Tests', () => {
194
204
  }
195
205
  }
196
206
  });
197
- }, 5000);
207
+ }, 2000);
198
208
  })
199
209
  );
200
210
 
201
- const promise = PaymentService.getProductsInfo(mockApiEndpoint);
211
+ const promise = paymentService.getProductsInfo();
212
+
213
+ // Advance timers to 1 second to trigger timeout
214
+ jest.advanceTimersByTime(1000);
215
+
216
+ await expect(promise).rejects.toThrow('[RequestCacheService] Request timeout after 1 seconds');
217
+ });
218
+
219
+ it('should increment failure count on failed requests', async () => {
220
+ // Mock fetch to reject
221
+ mockFetch.mockRejectedValue(new Error('Network error'));
222
+
223
+ // First failure
224
+ await expect(paymentService.getProductsInfo()).rejects.toThrow('Network error');
225
+ expect(getFailureCount('/api/joli-gem/balance-detail')).toBe(1);
226
+
227
+ // Second failure
228
+ await expect(paymentService.getProductsInfo()).rejects.toThrow('Network error');
229
+ expect(getFailureCount('/api/joli-gem/balance-detail')).toBe(2);
230
+ });
231
+
232
+ it('should block requests after 2 failures', async () => {
233
+ // Mock fetch to reject
234
+ mockFetch.mockRejectedValue(new Error('Network error'));
235
+
236
+ // First two failures
237
+ await expect(paymentService.getProductsInfo()).rejects.toThrow('Network error');
238
+ await expect(paymentService.getProductsInfo()).rejects.toThrow('Network error');
239
+
240
+ // Third call should be blocked immediately (failure count is now 2, which >= MAX_FAILURE_COUNT)
241
+ await expect(paymentService.getProductsInfo()).rejects.toThrow(
242
+ 'getProductsInfo has failed more than 2 times for /api/joli-gem/balance-detail'
243
+ );
244
+
245
+ // Verify fetch was only called twice (not three times)
246
+ expect(mockFetch).toHaveBeenCalledTimes(2);
247
+ });
248
+
249
+ it('should reset failure count on successful request', async () => {
250
+ // Mock first call to fail
251
+ mockFetch.mockRejectedValueOnce(new Error('Network error'));
252
+
253
+ // First failure
254
+ await expect(paymentService.getProductsInfo()).rejects.toThrow('Network error');
255
+ expect(getFailureCount('/api/joli-gem/balance-detail')).toBe(1);
256
+
257
+ // Mock second call to succeed
258
+ mockFetch.mockResolvedValue({
259
+ response: {
260
+ data: {
261
+ data: mockServerData
262
+ }
263
+ }
264
+ });
202
265
 
203
- // Advance timers to 3 seconds to trigger timeout
204
- jest.advanceTimersByTime(3000);
266
+ mockApplyNative.mockResolvedValue({
267
+ data: mockNativeData
268
+ });
205
269
 
206
- await expect(promise).rejects.toThrow('[PaymentService] Request timeout after 3 seconds');
270
+ // Successful call should reset counter
271
+ await paymentService.getProductsInfo();
272
+ expect(getFailureCount('/api/joli-gem/balance-detail')).toBe(0);
207
273
  });
208
274
  });