@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.
- package/.rush/temp/package-deps_build.json +36 -23
- package/CHANGELOG.json +11 -0
- package/CHANGELOG.md +9 -0
- package/dist/common/report/base-tracker.d.ts +2 -1
- package/dist/common/rewards/cached-fetch-reward.d.ts +46 -0
- package/dist/common/rewards/cached-reward-service.d.ts +24 -0
- package/dist/common/rewards/fetch-reward.d.ts +2 -3
- package/dist/common/rewards/index.d.ts +3 -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 +34 -0
- package/dist/common/rewards/registers/utils/coins/jolicoin/fetch-balance.d.ts +2 -1
- package/dist/common/rewards/registers/utils/coins/joligem/cached-fetch-gem-balance.d.ts +34 -0
- package/dist/common/rewards/registers/utils/coins/joligem/fetch-gem-balance.d.ts +2 -1
- 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/__tests__/auth.test.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 +49 -49
- package/dist/native/payment/payment-service.d.ts +36 -30
- package/implement.build.log +2 -2
- package/package.json +7 -7
- package/src/common/report/base-tracker.ts +2 -2
- package/src/common/rewards/cached-fetch-reward.ts +258 -0
- package/src/common/rewards/cached-reward-service.ts +255 -0
- package/src/common/rewards/fetch-reward.ts +17 -93
- package/src/common/rewards/index.ts +4 -0
- package/src/common/rewards/registers/use-subscription.ts +34 -0
- package/src/common/rewards/registers/utils/coins/jolicoin/cached-fetch-balance.ts +177 -0
- package/src/common/rewards/registers/utils/coins/jolicoin/fetch-balance.ts +13 -1
- package/src/common/rewards/registers/utils/coins/jolicoin/jolicoin-handler.ts +2 -0
- package/src/common/rewards/registers/utils/coins/joligem/cached-fetch-gem-balance.ts +181 -0
- package/src/common/rewards/registers/utils/coins/joligem/fetch-gem-balance.ts +13 -1
- package/src/common/rewards/registers/utils/coins/joligem/gem-handler.ts +2 -0
- 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 +18 -12
- package/src/h5/api/platformAdsHandle/JoliboxAdsHandler.ts +25 -1
- package/src/h5/api/storage.ts +2 -2
- 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/http/index.ts +2 -2
- package/src/h5/report/event-tracker.ts +2 -2
- package/src/h5/rewards/index.ts +18 -1
- package/src/native/api/ads.ts +7 -1
- package/src/native/api/payment.ts +4 -4
- package/src/native/payment/__tests__/payment-service-simple.test.ts +97 -31
- package/src/native/payment/payment-service.ts +224 -210
- package/src/native/rewards/ui/payment-modal.ts +14 -7
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { IHttpClient } from '@/common/http';
|
|
2
|
+
import { IAdBreakParams } from '@/common/ads';
|
|
3
|
+
import { EventPromiseHandler } from '../event-listener';
|
|
4
|
+
import { UnlockOptionsEventName, IUnlockOptionsEvent } from '@/common/rewards/reward-emitter';
|
|
5
|
+
import { rewardsCommands } from '../coins/rewards-command';
|
|
6
|
+
import { RewardsCommandType } from '@jolibox/types';
|
|
7
|
+
import { createShowSubscriptionModal } from './commands/use-subscription';
|
|
8
|
+
import { registerUseSubscriptionCommand } from './commands';
|
|
9
|
+
import { context } from '@/common/context';
|
|
10
|
+
|
|
11
|
+
export const createCommonSubscriptionHandler = (
|
|
12
|
+
type: 'SUBSCRIPTION',
|
|
13
|
+
httpClient: IHttpClient,
|
|
14
|
+
{
|
|
15
|
+
handlers: { handleSubSuccess, handleSubFailed, unlockOptionsHandler, showSubscriptionModal }
|
|
16
|
+
}: {
|
|
17
|
+
handlers: {
|
|
18
|
+
handleSubSuccess?: () => void;
|
|
19
|
+
handleSubFailed?: (params: IAdBreakParams) => void;
|
|
20
|
+
unlockOptionsHandler: EventPromiseHandler<IUnlockOptionsEvent, typeof UnlockOptionsEventName>;
|
|
21
|
+
showSubscriptionModal: ReturnType<typeof createShowSubscriptionModal>;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
) => {
|
|
25
|
+
// register commands
|
|
26
|
+
registerUseSubscriptionCommand({
|
|
27
|
+
showSubscriptionModal: showSubscriptionModal
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const commands = [`Rewards.subscription`];
|
|
31
|
+
|
|
32
|
+
return async (params: IAdBreakParams) => {
|
|
33
|
+
try {
|
|
34
|
+
let result = true;
|
|
35
|
+
for (const command of commands) {
|
|
36
|
+
const commandResult = await rewardsCommands.executeCommand(command as RewardsCommandType);
|
|
37
|
+
result = result && commandResult.result !== 'FAILED';
|
|
38
|
+
if (commandResult.result !== 'CONTINUE') {
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!result) {
|
|
44
|
+
handleSubFailed?.(params);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log('-----unlockWithSubscription result-----');
|
|
49
|
+
if (result) {
|
|
50
|
+
try {
|
|
51
|
+
params.adBreakDone?.({
|
|
52
|
+
breakType: params.type,
|
|
53
|
+
breakName: 'name' in params ? params.name ?? '' : '',
|
|
54
|
+
breakFormat: params.type === 'reward' ? 'reward' : 'interstitial',
|
|
55
|
+
breakStatus: 'viewed'
|
|
56
|
+
});
|
|
57
|
+
if ('adViewed' in params) {
|
|
58
|
+
params.adViewed?.();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
context.onEnvConfigChanged?.({
|
|
62
|
+
hostUserInfo: {
|
|
63
|
+
...(context.hostUserInfo ?? { isLogin: false }),
|
|
64
|
+
isSubUser: true
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.error('-----unlockWithSubscription adBreakDone error-----', e);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
handleSubSuccess?.();
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
handleSubFailed?.(params);
|
|
75
|
+
return false;
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.info(`SubscriptionRewardHandler error:`, e);
|
|
78
|
+
if (e instanceof Error && e.message == 'CANCEL') {
|
|
79
|
+
// Cancel should terminate the reward handler, invoke unlock failed
|
|
80
|
+
throw e;
|
|
81
|
+
}
|
|
82
|
+
handleSubFailed?.(params);
|
|
83
|
+
return false;
|
|
84
|
+
} finally {
|
|
85
|
+
unlockOptionsHandler.clearCache();
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
};
|
|
@@ -17,6 +17,8 @@ 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;
|
|
20
22
|
|
|
21
23
|
type IPaymentResult = 'SUCCESS' | 'FAILED' | 'CANCEL';
|
|
22
24
|
export interface IPaymentEvent {
|
|
@@ -90,6 +92,10 @@ export interface IUseModalFrequencyConfig {
|
|
|
90
92
|
};
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
export interface IUseSubscriptionResultEvent {
|
|
96
|
+
result: 'SUCCESS' | 'CANCEL' | 'FAILED';
|
|
97
|
+
}
|
|
98
|
+
|
|
93
99
|
export interface RewardsEventMap extends Record<string, unknown[]> {
|
|
94
100
|
[UnlockOptionsEventName]: [IUnlockOptionsEvent];
|
|
95
101
|
[InvokePaymentEventName]: [
|
|
@@ -105,6 +111,8 @@ export interface RewardsEventMap extends Record<string, unknown[]> {
|
|
|
105
111
|
IUseUnloginModalEvent
|
|
106
112
|
];
|
|
107
113
|
[UseUnloginModalResultEventName]: [IUseUnloginModalResultEvent];
|
|
114
|
+
[InvokeSubscriptionEventName]: ['SUBSCRIPTION'];
|
|
115
|
+
[UseSubscriptionResultEventName]: [IUseSubscriptionResultEvent];
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
export const originalRewardsEmitter = new EventEmitter<RewardsEventMap>();
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
export type RewardType =
|
|
1
|
+
export type RewardType =
|
|
2
|
+
| 'ADS'
|
|
3
|
+
| 'JOLI_COIN'
|
|
4
|
+
| 'JOLI_COIN_ONLY'
|
|
5
|
+
| 'JOLI_GEM'
|
|
6
|
+
| 'JOLI_GEM_ONLY'
|
|
7
|
+
| 'SUBSCRIPTION';
|
|
2
8
|
|
|
3
9
|
import { context } from '../context';
|
|
4
10
|
import type { AdsRewardsHandler } from './registers/use-ads';
|
|
@@ -10,6 +16,7 @@ export interface RewardHandlerMap {
|
|
|
10
16
|
JOLI_COIN_ONLY: (params?: unknown) => Promise<boolean>; // coins only
|
|
11
17
|
JOLI_GEM: (params?: unknown) => Promise<boolean>; // gem + ads
|
|
12
18
|
JOLI_GEM_ONLY: (params?: unknown) => Promise<boolean>; // gem only
|
|
19
|
+
SUBSCRIPTION: (params?: unknown) => Promise<boolean>; // subscription
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
export type RewardHandler<T extends RewardType> = RewardHandlerMap[T];
|
|
@@ -8,6 +8,11 @@ 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
|
+
|
|
11
16
|
interface JoliboxCustomEvent {
|
|
12
17
|
[JOLIBOX_CUSTOM_ADS_EVENT_TYPE]: {
|
|
13
18
|
isAdShowing: boolean;
|
|
@@ -41,6 +46,12 @@ interface JoliboxCustomEvent {
|
|
|
41
46
|
sequenceId: string;
|
|
42
47
|
type: 'ADS-JOLI_COIN' | 'JOLI_COIN';
|
|
43
48
|
};
|
|
49
|
+
[JOLIBOX_GET_USER_SUB_STATUS]: {
|
|
50
|
+
sequenceId: string;
|
|
51
|
+
};
|
|
52
|
+
[JOLIBOX_SUB_EVENT]: {
|
|
53
|
+
sequenceId: string;
|
|
54
|
+
};
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
const notifyCustomEvent = <T extends keyof JoliboxCustomEvent>(eventName: T, data: JoliboxCustomEvent[T]) => {
|
|
@@ -66,6 +77,10 @@ const ON_JOLIBOX_TOPUP_JOLI_COIN_RESULT = 'ON_JOLIBOX_TOPUP_JOLI_COIN_RESULT';
|
|
|
66
77
|
const ON_JOLIBOX_JOLI_COIN_USE_RESULT = 'ON_JOLIBOX_JOLI_COIN_USE_RESULT';
|
|
67
78
|
const ON_JOLIBOX_JOLI_UNLOGIN_MODAL_RESULT_EVENT = 'ON_JOLIBOX_JOLI_UNLOGIN_MODAL_RESULT_EVENT';
|
|
68
79
|
|
|
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
|
+
|
|
69
84
|
interface ReceivedJoliboxCustomEvent {
|
|
70
85
|
[ON_JOLIBOX_TOPUP_JOLI_COIN_RESULT]: {
|
|
71
86
|
sequenceId: string;
|
|
@@ -93,6 +108,14 @@ interface ReceivedJoliboxCustomEvent {
|
|
|
93
108
|
};
|
|
94
109
|
isFirstLogin?: boolean;
|
|
95
110
|
};
|
|
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
|
+
};
|
|
96
119
|
}
|
|
97
120
|
|
|
98
121
|
type ReceivedJoliboxCustomEventResult = keyof ReceivedJoliboxCustomEvent;
|
package/src/h5/api/ads.ts
CHANGED
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
createAdsRewardHandler,
|
|
7
7
|
rewardsHelper,
|
|
8
8
|
createJolicoinRewardHandler,
|
|
9
|
-
createJolicoinOnlyRewardHandler
|
|
9
|
+
createJolicoinOnlyRewardHandler,
|
|
10
|
+
createSubscriptionRewardHandler
|
|
10
11
|
} from '@/common/rewards';
|
|
11
12
|
import {
|
|
12
13
|
JoliboxAdsForGame,
|
|
@@ -23,6 +24,8 @@ import HuaweiQuickAdsHandler from './platformAdsHandle/HuaweiQuickAdsHandler';
|
|
|
23
24
|
import GamedistributionAdsHandler from './platformAdsHandle/GamedistributionAdsHandler';
|
|
24
25
|
import FunmaxAdsHandler from './platformAdsHandle/FunmaxAdsHandler';
|
|
25
26
|
import JoliboxAdsHandler from './platformAdsHandle/JoliboxAdsHandler';
|
|
27
|
+
import { warmupRewardCache } from '@/common/rewards/fetch-reward';
|
|
28
|
+
import { warmupBalanceCache } from '@/common/rewards';
|
|
26
29
|
|
|
27
30
|
declare global {
|
|
28
31
|
interface Window {
|
|
@@ -127,13 +130,9 @@ const adsContext: IAdsContext<'GAME'> = {
|
|
|
127
130
|
}
|
|
128
131
|
};
|
|
129
132
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
});
|
|
134
|
-
track('jolicoin_unlock_success', {
|
|
135
|
-
quantity: params.quantity
|
|
136
|
-
});
|
|
133
|
+
|
|
134
|
+
const handleUnlockWithSubscriptionSuccess = () => {
|
|
135
|
+
track('subscription_unlock_success', {});
|
|
137
136
|
};
|
|
138
137
|
|
|
139
138
|
const adsManager = new H5AdsManager(adsContext);
|
|
@@ -143,17 +142,24 @@ rewardsHelper.registerRewardHandler('ADS', createAdsRewardHandler(adsHandler.get
|
|
|
143
142
|
rewardsHelper.registerRewardHandler(
|
|
144
143
|
'JOLI_COIN',
|
|
145
144
|
createJolicoinRewardHandler(httpClient, {
|
|
146
|
-
onUnlockSuccess:
|
|
145
|
+
onUnlockSuccess: (params: { quantity: number; balance: number }) => {
|
|
146
|
+
notifyCustomEvent('JOLIBOX_CUSTOM_REWARDS_EVENT', {
|
|
147
|
+
JOLI_COIN: params
|
|
148
|
+
});
|
|
149
|
+
}
|
|
147
150
|
}) as unknown as (params?: unknown) => Promise<boolean>
|
|
148
151
|
);
|
|
149
152
|
|
|
150
153
|
rewardsHelper.registerRewardHandler(
|
|
151
|
-
'
|
|
152
|
-
|
|
153
|
-
|
|
154
|
+
'SUBSCRIPTION',
|
|
155
|
+
createSubscriptionRewardHandler(httpClient, {
|
|
156
|
+
onSubSuccess: handleUnlockWithSubscriptionSuccess.bind(this)
|
|
154
157
|
}) as unknown as (params?: unknown) => Promise<boolean>
|
|
155
158
|
);
|
|
156
159
|
|
|
160
|
+
warmupRewardCache(httpClient);
|
|
161
|
+
warmupBalanceCache(httpClient);
|
|
162
|
+
|
|
157
163
|
adEventEmitter.on('isAdShowing', (isAdShowing) => {
|
|
158
164
|
notifyCustomEvent('JOLIBOX_ADS_EVENT', { isAdShowing });
|
|
159
165
|
});
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from '@jolibox/ads';
|
|
9
9
|
import { IAdsHandler } from '../ads';
|
|
10
10
|
import { JoliboxHttpClient } from '@/h5/http';
|
|
11
|
+
import { context } from '@/common/context';
|
|
11
12
|
|
|
12
13
|
export default class JoliboxAdsHandler implements IAdsHandler {
|
|
13
14
|
constructor(private ads: JoliboxAdsForGame, private httpClient: JoliboxHttpClient) {}
|
|
@@ -25,10 +26,21 @@ export default class JoliboxAdsHandler implements IAdsHandler {
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
async adBreak(params: IAdBreakParams): Promise<void> {
|
|
29
|
+
const isSubUser = context.hostUserInfo?.isSubUser;
|
|
30
|
+
|
|
31
|
+
// if is sub user, just call adBreakDone with viewed status
|
|
32
|
+
if (isSubUser) {
|
|
33
|
+
this.wrapAdBreadDoneViewed(params);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
if (params.type === 'reward') {
|
|
29
38
|
try {
|
|
30
39
|
const rewardsTypes = await rewardsHelper.getRewardsTypes(this.httpClient);
|
|
31
|
-
await rewardsHelper.handleReward(rewardsTypes, params);
|
|
40
|
+
const rewardResult = await rewardsHelper.handleReward(rewardsTypes, params);
|
|
41
|
+
if (!rewardResult) {
|
|
42
|
+
throw new Error('handleReward failed: fallback to unlock failed');
|
|
43
|
+
}
|
|
32
44
|
} catch (e) {
|
|
33
45
|
console.info('handleReward failed', e);
|
|
34
46
|
params.adBreakDone?.({
|
|
@@ -45,4 +57,16 @@ export default class JoliboxAdsHandler implements IAdsHandler {
|
|
|
45
57
|
adUnit(params: IAdUnitParams): void {
|
|
46
58
|
this.ads.adUnit(params);
|
|
47
59
|
}
|
|
60
|
+
|
|
61
|
+
private wrapAdBreadDoneViewed(params: IAdBreakParams) {
|
|
62
|
+
params.adBreakDone?.({
|
|
63
|
+
breakType: params.type,
|
|
64
|
+
breakName: 'name' in params ? params.name ?? '' : '',
|
|
65
|
+
breakFormat: params.type === 'reward' ? 'reward' : 'interstitial',
|
|
66
|
+
breakStatus: 'viewed'
|
|
67
|
+
});
|
|
68
|
+
if ('adViewed' in params) {
|
|
69
|
+
params.adViewed?.();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
48
72
|
}
|
package/src/h5/api/storage.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createAPI, t, registerCanIUse } from './base';
|
|
2
|
-
import { createCommands,
|
|
2
|
+
import { createCommands, getH5ApiHost } from '@jolibox/common';
|
|
3
3
|
import { httpClientManager } from '@/h5/http';
|
|
4
4
|
import { context } from '@/common/context';
|
|
5
5
|
import { StorageResponse } from '@jolibox/types';
|
|
@@ -10,7 +10,7 @@ const commands = createCommands();
|
|
|
10
10
|
export class JoliboxCloudStorage {
|
|
11
11
|
private gameId: string;
|
|
12
12
|
private httpClient = httpClientManager.create({
|
|
13
|
-
baseUrl:
|
|
13
|
+
baseUrl: getH5ApiHost(context.testMode)
|
|
14
14
|
});
|
|
15
15
|
private gameStore: LocalForage;
|
|
16
16
|
constructor() {
|
|
@@ -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';
|