@jolibox/implement 1.2.5 → 1.2.7
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 +5 -5
- 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
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// Mock dependencies first
|
|
2
|
+
const mockOnCustomEvent = jest.fn();
|
|
3
|
+
const mockNotifyCustomEvent = jest.fn();
|
|
4
|
+
const mockUuidv4 = jest.fn();
|
|
5
|
+
|
|
6
|
+
jest.mock('@jolibox/common', () => ({
|
|
7
|
+
uuidv4: mockUuidv4,
|
|
8
|
+
EventEmitter: jest.fn().mockImplementation(() => ({
|
|
9
|
+
emit: jest.fn(),
|
|
10
|
+
on: jest.fn(),
|
|
11
|
+
off: jest.fn(),
|
|
12
|
+
once: jest.fn(),
|
|
13
|
+
removeAllListeners: jest.fn()
|
|
14
|
+
})),
|
|
15
|
+
InternalGlobalJSError: jest.fn().mockImplementation((error) => error)
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock('@/common/utils', () => ({
|
|
19
|
+
onCustomEvent: mockOnCustomEvent,
|
|
20
|
+
notifyCustomEvent: mockNotifyCustomEvent
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
jest.mock('@/common/report/errors/report', () => ({
|
|
24
|
+
reportError: jest.fn()
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
import { getUserSubStatus, resetSubState } from '../sub';
|
|
28
|
+
|
|
29
|
+
// Define event data interface
|
|
30
|
+
interface EventData {
|
|
31
|
+
sequenceId: string;
|
|
32
|
+
isSubUser: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('getUserSubStatus', () => {
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
jest.clearAllMocks();
|
|
38
|
+
jest.clearAllTimers();
|
|
39
|
+
jest.useRealTimers(); // Use real timers to avoid timing issues
|
|
40
|
+
|
|
41
|
+
// Reset module state between tests
|
|
42
|
+
resetSubState();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
jest.restoreAllMocks();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should resolve with correct value when event is received', async () => {
|
|
50
|
+
let eventHandler: ((data: EventData) => void) | null = null;
|
|
51
|
+
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
52
|
+
if (eventName === 'ON_GET_USER_SUB_STATUS') {
|
|
53
|
+
eventHandler = handler;
|
|
54
|
+
}
|
|
55
|
+
return jest.fn();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
mockUuidv4.mockReturnValue('test-sequence-id');
|
|
59
|
+
|
|
60
|
+
const promise = getUserSubStatus();
|
|
61
|
+
|
|
62
|
+
// Verify notification was sent
|
|
63
|
+
expect(mockNotifyCustomEvent).toHaveBeenCalledWith('JOLIBOX_GET_USER_SUB_STATUS', {
|
|
64
|
+
sequenceId: 'test-sequence-id'
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Simulate response immediately
|
|
68
|
+
if (eventHandler) {
|
|
69
|
+
(eventHandler as any)({
|
|
70
|
+
sequenceId: 'test-sequence-id',
|
|
71
|
+
isSubUser: true
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const result = await promise;
|
|
76
|
+
expect(result).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should resolve with false when isSubUser is false', async () => {
|
|
80
|
+
let eventHandler: ((data: EventData) => void) | null = null;
|
|
81
|
+
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
82
|
+
if (eventName === 'ON_GET_USER_SUB_STATUS') {
|
|
83
|
+
eventHandler = handler;
|
|
84
|
+
}
|
|
85
|
+
return jest.fn();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
mockUuidv4.mockReturnValue('test-sequence-id');
|
|
89
|
+
|
|
90
|
+
const promise = getUserSubStatus();
|
|
91
|
+
|
|
92
|
+
// Simulate response with false
|
|
93
|
+
if (eventHandler) {
|
|
94
|
+
(eventHandler as any)({
|
|
95
|
+
sequenceId: 'test-sequence-id',
|
|
96
|
+
isSubUser: false
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const result = await promise;
|
|
101
|
+
expect(result).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should reject on timeout', async () => {
|
|
105
|
+
mockOnCustomEvent.mockImplementation(() => jest.fn());
|
|
106
|
+
mockUuidv4.mockReturnValue('timeout-test-id');
|
|
107
|
+
|
|
108
|
+
const promise = getUserSubStatus();
|
|
109
|
+
|
|
110
|
+
// Fast forward time to trigger timeout
|
|
111
|
+
jest.advanceTimersByTime(3000);
|
|
112
|
+
|
|
113
|
+
await expect(promise).rejects.toThrow('Timeout waiting for user sub status response');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should initialize event listener only once', async () => {
|
|
117
|
+
let eventHandler: ((data: EventData) => void) | null = null;
|
|
118
|
+
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
119
|
+
if (eventName === 'ON_GET_USER_SUB_STATUS') {
|
|
120
|
+
eventHandler = handler;
|
|
121
|
+
}
|
|
122
|
+
return jest.fn();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Mock different UUIDs for different calls
|
|
126
|
+
mockUuidv4.mockReturnValueOnce('uuid-1').mockReturnValueOnce('uuid-2');
|
|
127
|
+
|
|
128
|
+
// First call
|
|
129
|
+
const promise1 = getUserSubStatus();
|
|
130
|
+
|
|
131
|
+
// Second call
|
|
132
|
+
const promise2 = getUserSubStatus();
|
|
133
|
+
|
|
134
|
+
// Should only initialize listener once
|
|
135
|
+
expect(mockOnCustomEvent).toHaveBeenCalledTimes(1);
|
|
136
|
+
expect(mockOnCustomEvent).toHaveBeenCalledWith('ON_GET_USER_SUB_STATUS', expect.any(Function));
|
|
137
|
+
|
|
138
|
+
// Should send notification for both calls
|
|
139
|
+
expect(mockNotifyCustomEvent).toHaveBeenCalledTimes(2);
|
|
140
|
+
|
|
141
|
+
// Simulate responses with different sequence IDs
|
|
142
|
+
if (eventHandler) {
|
|
143
|
+
(eventHandler as any)({ sequenceId: 'uuid-1', isSubUser: true });
|
|
144
|
+
(eventHandler as any)({ sequenceId: 'uuid-2', isSubUser: false });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const [result1, result2] = await Promise.all([promise1, promise2]);
|
|
148
|
+
expect(result1).toBe(true);
|
|
149
|
+
expect(result2).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should ignore responses with different sequenceId', async () => {
|
|
153
|
+
let eventHandler: ((data: EventData) => void) | null = null;
|
|
154
|
+
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
155
|
+
if (eventName === 'ON_GET_USER_SUB_STATUS') {
|
|
156
|
+
eventHandler = handler;
|
|
157
|
+
}
|
|
158
|
+
return jest.fn();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
mockUuidv4.mockReturnValue('correct-sequence-id');
|
|
162
|
+
|
|
163
|
+
const promise = getUserSubStatus();
|
|
164
|
+
|
|
165
|
+
// Simulate response with wrong sequenceId first
|
|
166
|
+
if (eventHandler) {
|
|
167
|
+
(eventHandler as any)({
|
|
168
|
+
sequenceId: 'wrong-sequence-id',
|
|
169
|
+
isSubUser: true
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Promise should still be pending after wrong sequenceId
|
|
174
|
+
let isResolved = false;
|
|
175
|
+
let promiseResult: any;
|
|
176
|
+
promise
|
|
177
|
+
.then((result) => {
|
|
178
|
+
isResolved = true;
|
|
179
|
+
promiseResult = result;
|
|
180
|
+
})
|
|
181
|
+
.catch(() => {
|
|
182
|
+
isResolved = true;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Flush microtasks
|
|
186
|
+
await Promise.resolve();
|
|
187
|
+
expect(isResolved).toBe(false);
|
|
188
|
+
|
|
189
|
+
// Now send correct response
|
|
190
|
+
if (eventHandler) {
|
|
191
|
+
(eventHandler as any)({
|
|
192
|
+
sequenceId: 'correct-sequence-id',
|
|
193
|
+
isSubUser: true
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
await promise;
|
|
198
|
+
expect(promiseResult).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should handle multiple concurrent requests with different sequenceIds', async () => {
|
|
202
|
+
let eventHandler: ((data: EventData) => void) | null = null;
|
|
203
|
+
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
204
|
+
if (eventName === 'ON_GET_USER_SUB_STATUS') {
|
|
205
|
+
eventHandler = handler;
|
|
206
|
+
}
|
|
207
|
+
return jest.fn();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Mock different UUIDs for different calls
|
|
211
|
+
mockUuidv4
|
|
212
|
+
.mockReturnValueOnce('request-1')
|
|
213
|
+
.mockReturnValueOnce('request-2')
|
|
214
|
+
.mockReturnValueOnce('request-3');
|
|
215
|
+
|
|
216
|
+
const promise1 = getUserSubStatus();
|
|
217
|
+
const promise2 = getUserSubStatus();
|
|
218
|
+
const promise3 = getUserSubStatus();
|
|
219
|
+
|
|
220
|
+
// Should initialize listener only once
|
|
221
|
+
expect(mockOnCustomEvent).toHaveBeenCalledTimes(1);
|
|
222
|
+
|
|
223
|
+
// Should send three notifications
|
|
224
|
+
expect(mockNotifyCustomEvent).toHaveBeenCalledTimes(3);
|
|
225
|
+
expect(mockNotifyCustomEvent).toHaveBeenNthCalledWith(1, 'JOLIBOX_GET_USER_SUB_STATUS', {
|
|
226
|
+
sequenceId: 'request-1'
|
|
227
|
+
});
|
|
228
|
+
expect(mockNotifyCustomEvent).toHaveBeenNthCalledWith(2, 'JOLIBOX_GET_USER_SUB_STATUS', {
|
|
229
|
+
sequenceId: 'request-2'
|
|
230
|
+
});
|
|
231
|
+
expect(mockNotifyCustomEvent).toHaveBeenNthCalledWith(3, 'JOLIBOX_GET_USER_SUB_STATUS', {
|
|
232
|
+
sequenceId: 'request-3'
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Simulate responses in reverse order
|
|
236
|
+
if (eventHandler) {
|
|
237
|
+
(eventHandler as any)({ sequenceId: 'request-3', isSubUser: false });
|
|
238
|
+
(eventHandler as any)({ sequenceId: 'request-1', isSubUser: true });
|
|
239
|
+
(eventHandler as any)({ sequenceId: 'request-2', isSubUser: true });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const [result1, result2, result3] = await Promise.all([promise1, promise2, promise3]);
|
|
243
|
+
expect(result1).toBe(true);
|
|
244
|
+
expect(result2).toBe(true);
|
|
245
|
+
expect(result3).toBe(false);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should clean up timeout when response is received', async () => {
|
|
249
|
+
let eventHandler: ((data: EventData) => void) | null = null;
|
|
250
|
+
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
251
|
+
if (eventName === 'ON_GET_USER_SUB_STATUS') {
|
|
252
|
+
eventHandler = handler;
|
|
253
|
+
}
|
|
254
|
+
return jest.fn();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
mockUuidv4.mockReturnValue('cleanup-test-id');
|
|
258
|
+
|
|
259
|
+
// Spy on clearTimeout
|
|
260
|
+
const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout');
|
|
261
|
+
|
|
262
|
+
const promise = getUserSubStatus();
|
|
263
|
+
|
|
264
|
+
// Simulate response before timeout
|
|
265
|
+
if (eventHandler) {
|
|
266
|
+
(eventHandler as any)({
|
|
267
|
+
sequenceId: 'cleanup-test-id',
|
|
268
|
+
isSubUser: true
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
await promise;
|
|
273
|
+
|
|
274
|
+
// Verify timeout was cleared
|
|
275
|
+
expect(clearTimeoutSpy).toHaveBeenCalled();
|
|
276
|
+
|
|
277
|
+
clearTimeoutSpy.mockRestore();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should not resolve after timeout even if response comes later', async () => {
|
|
281
|
+
let eventHandler: ((data: EventData) => void) | null = null;
|
|
282
|
+
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
283
|
+
if (eventName === 'ON_GET_USER_SUB_STATUS') {
|
|
284
|
+
eventHandler = handler;
|
|
285
|
+
}
|
|
286
|
+
return jest.fn();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
mockUuidv4.mockReturnValue('late-response-id');
|
|
290
|
+
|
|
291
|
+
const promise = getUserSubStatus();
|
|
292
|
+
|
|
293
|
+
// Trigger timeout first
|
|
294
|
+
jest.advanceTimersByTime(3000);
|
|
295
|
+
|
|
296
|
+
await expect(promise).rejects.toThrow('Timeout waiting for user sub status response');
|
|
297
|
+
|
|
298
|
+
// Try to send response after timeout
|
|
299
|
+
if (eventHandler) {
|
|
300
|
+
(eventHandler as any)({
|
|
301
|
+
sequenceId: 'late-response-id',
|
|
302
|
+
isSubUser: true
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Promise should already be rejected, no additional effects
|
|
307
|
+
});
|
|
308
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { httpClientManager } from '@/h5/http';
|
|
2
|
+
import { StandardResponse } from '@jolibox/types';
|
|
3
|
+
import { reportError } from '@/common/report/errors/report';
|
|
4
|
+
import { InternalGlobalJSError } from '@jolibox/common';
|
|
5
|
+
|
|
6
|
+
export async function checkSession(): Promise<boolean> {
|
|
7
|
+
const httpClient = httpClientManager.create();
|
|
8
|
+
try {
|
|
9
|
+
const response = await httpClient.get<StandardResponse<void>>('/api/users/info');
|
|
10
|
+
if (response.code !== 'SUCCESS' || !response.data) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return true;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
reportError(new InternalGlobalJSError(error as Error));
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { getUserSubStatus } from './sub';
|
|
@@ -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
|
|
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 {
|
|
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
|
-
|
|
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
|
});
|
package/src/h5/rewards/index.ts
CHANGED
|
@@ -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
|
+
});
|
|
@@ -5,7 +5,7 @@ import { StandardResponse } from '@jolibox/types';
|
|
|
5
5
|
import { applyNative } from '@jolibox/native-bridge';
|
|
6
6
|
import { innerFetch as fetch } from '@/native/network';
|
|
7
7
|
import type { PaymentResult } from './payment-helper';
|
|
8
|
-
import { RequestCacheService, RequestAdapter } from '
|
|
8
|
+
import { RequestCacheService, RequestAdapter } from '@jolibox/common';
|
|
9
9
|
|
|
10
10
|
type PaymentPurchaseType = 'JOLI_COIN' | 'JOLI_GEM';
|
|
11
11
|
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 通用请求缓存服务基类
|
|
3
|
-
* 支持部分数据缓存,部分数据实时获取的混合模式
|
|
4
|
-
*/
|
|
5
|
-
export interface CacheConfig<TCacheData, TServerData> {
|
|
6
|
-
/** 缓存持续时间,默认10分钟 */
|
|
7
|
-
duration?: number;
|
|
8
|
-
/** 请求超时时间,默认3秒 */
|
|
9
|
-
timeout?: number;
|
|
10
|
-
/** 缓存键生成函数 */
|
|
11
|
-
keyGenerator?: <TRequest>(endpoint: string, params?: TRequest) => string;
|
|
12
|
-
/** 数据一致性校验函数 */
|
|
13
|
-
consistencyChecker?: (cached: TCacheData, serverData: TServerData) => boolean;
|
|
14
|
-
}
|
|
15
|
-
export interface CacheEntry<T> {
|
|
16
|
-
data: T;
|
|
17
|
-
timestamp: number;
|
|
18
|
-
expiresAt: number;
|
|
19
|
-
key: string;
|
|
20
|
-
sequence: number;
|
|
21
|
-
metadata?: Record<string, unknown>;
|
|
22
|
-
}
|
|
23
|
-
export interface CacheStats {
|
|
24
|
-
cacheCount: number;
|
|
25
|
-
validCount: number;
|
|
26
|
-
expiredCount: number;
|
|
27
|
-
}
|
|
28
|
-
export interface RequestAdapter<TRequest, TResponse, TCacheData, TRealTimeData> {
|
|
29
|
-
/** 执行实际的网络请求 */
|
|
30
|
-
execute(endpoint: string, options?: TRequest): Promise<TResponse>;
|
|
31
|
-
/** 从响应中提取需要缓存的部分(不变数据) */
|
|
32
|
-
extractCacheableData?(response: TResponse): TCacheData;
|
|
33
|
-
/** 从响应中提取实时数据部分(变化数据) */
|
|
34
|
-
extractRealTimeData?(response: TResponse): TRealTimeData;
|
|
35
|
-
/** 合并缓存数据和实时数据 */
|
|
36
|
-
mergeData?(cached: TCacheData, realTime: TRealTimeData): TResponse;
|
|
37
|
-
/** 处理纯缓存场景的数据,当没有新的网络请求时 */
|
|
38
|
-
processCachedData?(cached: TCacheData): TResponse;
|
|
39
|
-
}
|
|
40
|
-
export declare abstract class RequestCacheService<TRequest, TResponse, TCacheData, TRealTimeData> {
|
|
41
|
-
protected requestAdapter: RequestAdapter<TRequest, TResponse, TCacheData, TRealTimeData>;
|
|
42
|
-
private cache;
|
|
43
|
-
private readonly config;
|
|
44
|
-
private pendingRequests;
|
|
45
|
-
private requestSequence;
|
|
46
|
-
private cacheSequences;
|
|
47
|
-
constructor(requestAdapter: RequestAdapter<TRequest, TResponse, TCacheData, TRealTimeData>, config?: Partial<CacheConfig<TCacheData, TCacheData>>);
|
|
48
|
-
/**
|
|
49
|
-
* 带缓存的请求方法 - 缓存优先 + 实时数据混合模式 + 请求去重
|
|
50
|
-
*/
|
|
51
|
-
request(endpoint: string, options?: TRequest): Promise<TResponse>;
|
|
52
|
-
private requestInternal;
|
|
53
|
-
/**
|
|
54
|
-
* timeout
|
|
55
|
-
*/
|
|
56
|
-
private createTimeoutPromise;
|
|
57
|
-
/**
|
|
58
|
-
* get valid cached data
|
|
59
|
-
*/
|
|
60
|
-
private getValidCachedData;
|
|
61
|
-
/**
|
|
62
|
-
* 设置缓存数据 (带序列号检查)
|
|
63
|
-
*/
|
|
64
|
-
private setCachedDataWithSequence;
|
|
65
|
-
/**
|
|
66
|
-
* refresh cache
|
|
67
|
-
*/
|
|
68
|
-
refreshCache(endpoint: string, options?: TRequest): Promise<TResponse>;
|
|
69
|
-
/**
|
|
70
|
-
* force request and update cache
|
|
71
|
-
*/
|
|
72
|
-
forceRequest(endpoint: string, options?: TRequest): Promise<TResponse>;
|
|
73
|
-
/**
|
|
74
|
-
* warmup cache - 避免与正在进行的请求冲突
|
|
75
|
-
*/
|
|
76
|
-
warmupCache(endpoint: string, options?: TRequest): Promise<void>;
|
|
77
|
-
/**
|
|
78
|
-
* clear cache with target key
|
|
79
|
-
*/
|
|
80
|
-
clearCache(key?: string): void;
|
|
81
|
-
/**
|
|
82
|
-
* clear expired cache
|
|
83
|
-
*/
|
|
84
|
-
clearExpiredCache(): void;
|
|
85
|
-
/**
|
|
86
|
-
* get cache stats
|
|
87
|
-
*/
|
|
88
|
-
getCacheStats(): CacheStats & {
|
|
89
|
-
pendingRequestCount: number;
|
|
90
|
-
};
|
|
91
|
-
/**
|
|
92
|
-
* get all cache keys
|
|
93
|
-
*/
|
|
94
|
-
getCacheKeys(): string[];
|
|
95
|
-
/**
|
|
96
|
-
* check if the target key has valid cache
|
|
97
|
-
*/
|
|
98
|
-
hasValidCache(key: string): boolean;
|
|
99
|
-
/**
|
|
100
|
-
* get pending request keys for debugging
|
|
101
|
-
*/
|
|
102
|
-
getPendingRequestKeys(): string[];
|
|
103
|
-
/**
|
|
104
|
-
* check if a request is currently pending
|
|
105
|
-
*/
|
|
106
|
-
hasPendingRequest(key: string): boolean;
|
|
107
|
-
/**
|
|
108
|
-
* get cache sequence for a specific key
|
|
109
|
-
*/
|
|
110
|
-
getCacheSequence(key: string): number | undefined;
|
|
111
|
-
}
|