@jolibox/implement 1.2.8 → 1.2.9-beta.9
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 +37 -27
- package/dist/common/rewards/registers/use-gem-only.d.ts +2 -1
- package/dist/common/rewards/registers/use-gem.d.ts +2 -1
- package/dist/common/rewards/registers/use-jolicoin-only.d.ts +2 -1
- package/dist/common/rewards/registers/use-jolicoin.d.ts +2 -1
- package/dist/common/rewards/registers/use-subscription.d.ts +2 -1
- package/dist/common/rewards/registers/utils/coins/jolicoin/jolicoin-handler.d.ts +1 -0
- package/dist/common/rewards/registers/utils/coins/joligem/gem-handler.d.ts +2 -1
- package/dist/common/rewards/registers/utils/common.d.ts +1 -0
- package/dist/common/rewards/registers/utils/subscription/sub-handler.d.ts +2 -1
- package/dist/common/rewards/reward-emitter.d.ts +8 -0
- package/dist/common/rewards/type.d.ts +1 -1
- package/dist/common/utils/index.d.ts +1 -1
- package/dist/index.js +25 -25
- package/dist/index.native.js +54 -54
- package/dist/native/payment/utils/__tests__/cache-with-storage.test.d.ts +1 -0
- package/dist/native/payment/utils/cache-with-storage.d.ts +7 -0
- package/dist/native/rewards/check-frequency.d.ts +8 -0
- package/dist/native/rewards/index.d.ts +1 -0
- package/dist/native/rewards/ui/subscription-modal.d.ts +1 -0
- package/dist/native/subscription/index.d.ts +3 -0
- package/dist/native/subscription/registers/base.d.ts +22 -0
- package/dist/native/subscription/registers/sub-app.d.ts +21 -0
- package/dist/native/subscription/registers/type.d.ts +10 -0
- package/dist/native/subscription/subscription-helper.d.ts +22 -0
- package/dist/native/subscription/subscription-service.d.ts +43 -0
- package/dist/native/subscription/type.d.ts +18 -0
- package/implement.build.log +2 -2
- package/package.json +5 -5
- package/src/common/context/index.ts +4 -1
- package/src/common/rewards/registers/use-gem-only.ts +5 -2
- package/src/common/rewards/registers/use-gem.ts +5 -2
- package/src/common/rewards/registers/use-jolicoin-only.ts +5 -2
- package/src/common/rewards/registers/use-jolicoin.ts +5 -2
- package/src/common/rewards/registers/use-subscription.ts +5 -2
- package/src/common/rewards/registers/utils/coins/jolicoin/jolicoin-handler.ts +33 -11
- package/src/common/rewards/registers/utils/coins/joligem/gem-handler.ts +34 -13
- package/src/common/rewards/registers/utils/common.ts +9 -0
- package/src/common/rewards/registers/utils/subscription/commands/use-subscription.ts +16 -0
- package/src/common/rewards/registers/utils/subscription/sub-handler.ts +22 -6
- package/src/common/rewards/reward-emitter.ts +8 -0
- package/src/common/rewards/type.ts +1 -1
- package/src/common/utils/index.ts +1 -1
- package/src/h5/api/ads.ts +6 -3
- package/src/h5/bootstrap/auth/__tests__/auth.test.ts +15 -9
- package/src/h5/bootstrap/auth/sub.ts +1 -1
- package/src/native/api/ads.ts +43 -6
- package/src/native/api/call-host-method.ts +5 -61
- package/src/native/api/login.ts +22 -7
- package/src/native/api/payment.ts +78 -3
- package/src/native/payment/__tests__/payment-service-simple.test.ts +14 -1
- package/src/native/payment/payment-service.ts +26 -27
- package/src/native/payment/utils/__tests__/cache-with-storage.test.ts +414 -0
- package/src/native/payment/utils/cache-with-storage.ts +112 -0
- package/src/native/rewards/check-frequency.ts +6 -0
- package/src/native/rewards/index.ts +1 -0
- package/src/native/rewards/ui/payment-modal.ts +2 -2
- package/src/native/rewards/ui/subscription-modal.ts +81 -0
- package/src/native/subscription/index.ts +12 -0
- package/src/native/subscription/registers/base.ts +88 -0
- package/src/native/subscription/registers/sub-app.ts +258 -0
- package/src/native/subscription/registers/type.ts +13 -0
- package/src/native/subscription/subscription-helper.ts +53 -0
- package/src/native/subscription/subscription-service.ts +339 -0
- package/src/native/subscription/type.ts +18 -0
|
@@ -8,7 +8,7 @@ export interface IGem {
|
|
|
8
8
|
enableAutoDeduct: boolean;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export type IUnlockOptionType = 'JOLI_COIN' | 'ADS' | 'JOLI_GEM';
|
|
11
|
+
export type IUnlockOptionType = 'JOLI_COIN' | 'ADS' | 'JOLI_GEM' | 'SUBSCRIPTION';
|
|
12
12
|
|
|
13
13
|
interface IJoliCoinChoice {
|
|
14
14
|
joliCoinQuantity: number;
|
|
@@ -78,7 +78,7 @@ const ON_JOLIBOX_JOLI_COIN_USE_RESULT = 'ON_JOLIBOX_JOLI_COIN_USE_RESULT';
|
|
|
78
78
|
const ON_JOLIBOX_JOLI_UNLOGIN_MODAL_RESULT_EVENT = 'ON_JOLIBOX_JOLI_UNLOGIN_MODAL_RESULT_EVENT';
|
|
79
79
|
|
|
80
80
|
// subscription
|
|
81
|
-
const ON_GET_USER_SUB_STATUS = '
|
|
81
|
+
const ON_GET_USER_SUB_STATUS = 'ON_JOLIBOX_GET_USER_SUB_STATUS';
|
|
82
82
|
const ON_JOLIBOX_SUB_RESULT_EVENT = 'ON_JOLIBOX_SUB_RESULT_EVENT';
|
|
83
83
|
|
|
84
84
|
interface ReceivedJoliboxCustomEvent {
|
package/src/h5/api/ads.ts
CHANGED
|
@@ -150,21 +150,24 @@ rewardsHelper.registerRewardHandler('ADS', createAdsRewardHandler(adsHandler.get
|
|
|
150
150
|
rewardsHelper.registerRewardHandler(
|
|
151
151
|
'JOLI_COIN',
|
|
152
152
|
createJolicoinRewardHandler(httpClient, {
|
|
153
|
-
onUnlockSuccess: handleUnlockSuccess.bind(this)
|
|
153
|
+
onUnlockSuccess: handleUnlockSuccess.bind(this),
|
|
154
|
+
hasSubscription: () => true
|
|
154
155
|
}) as unknown as (params?: unknown) => Promise<boolean>
|
|
155
156
|
);
|
|
156
157
|
|
|
157
158
|
rewardsHelper.registerRewardHandler(
|
|
158
159
|
'JOLI_COIN_ONLY',
|
|
159
160
|
createJolicoinOnlyRewardHandler(httpClient, {
|
|
160
|
-
onUnlockSuccess: handleUnlockSuccess.bind(this)
|
|
161
|
+
onUnlockSuccess: handleUnlockSuccess.bind(this),
|
|
162
|
+
hasSubscription: () => true
|
|
161
163
|
}) as unknown as (params?: unknown) => Promise<boolean>
|
|
162
164
|
);
|
|
163
165
|
|
|
164
166
|
rewardsHelper.registerRewardHandler(
|
|
165
167
|
'SUBSCRIPTION',
|
|
166
168
|
createSubscriptionRewardHandler(httpClient, {
|
|
167
|
-
onSubSuccess: handleUnlockWithSubscriptionSuccess.bind(this)
|
|
169
|
+
onSubSuccess: handleUnlockWithSubscriptionSuccess.bind(this),
|
|
170
|
+
hasSubscription: () => true
|
|
168
171
|
}) as unknown as (params?: unknown) => Promise<boolean>
|
|
169
172
|
);
|
|
170
173
|
|
|
@@ -49,7 +49,7 @@ describe('getUserSubStatus', () => {
|
|
|
49
49
|
it('should resolve with correct value when event is received', async () => {
|
|
50
50
|
let eventHandler: ((data: EventData) => void) | null = null;
|
|
51
51
|
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
52
|
-
if (eventName === '
|
|
52
|
+
if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
|
|
53
53
|
eventHandler = handler;
|
|
54
54
|
}
|
|
55
55
|
return jest.fn();
|
|
@@ -79,7 +79,7 @@ describe('getUserSubStatus', () => {
|
|
|
79
79
|
it('should resolve with false when isSubUser is false', async () => {
|
|
80
80
|
let eventHandler: ((data: EventData) => void) | null = null;
|
|
81
81
|
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
82
|
-
if (eventName === '
|
|
82
|
+
if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
|
|
83
83
|
eventHandler = handler;
|
|
84
84
|
}
|
|
85
85
|
return jest.fn();
|
|
@@ -102,6 +102,8 @@ describe('getUserSubStatus', () => {
|
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
it('should reject on timeout', async () => {
|
|
105
|
+
jest.useFakeTimers();
|
|
106
|
+
|
|
105
107
|
mockOnCustomEvent.mockImplementation(() => jest.fn());
|
|
106
108
|
mockUuidv4.mockReturnValue('timeout-test-id');
|
|
107
109
|
|
|
@@ -111,12 +113,14 @@ describe('getUserSubStatus', () => {
|
|
|
111
113
|
jest.advanceTimersByTime(3000);
|
|
112
114
|
|
|
113
115
|
await expect(promise).rejects.toThrow('Timeout waiting for user sub status response');
|
|
116
|
+
|
|
117
|
+
jest.useRealTimers();
|
|
114
118
|
});
|
|
115
119
|
|
|
116
120
|
it('should initialize event listener only once', async () => {
|
|
117
121
|
let eventHandler: ((data: EventData) => void) | null = null;
|
|
118
122
|
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
119
|
-
if (eventName === '
|
|
123
|
+
if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
|
|
120
124
|
eventHandler = handler;
|
|
121
125
|
}
|
|
122
126
|
return jest.fn();
|
|
@@ -133,7 +137,7 @@ describe('getUserSubStatus', () => {
|
|
|
133
137
|
|
|
134
138
|
// Should only initialize listener once
|
|
135
139
|
expect(mockOnCustomEvent).toHaveBeenCalledTimes(1);
|
|
136
|
-
expect(mockOnCustomEvent).toHaveBeenCalledWith('
|
|
140
|
+
expect(mockOnCustomEvent).toHaveBeenCalledWith('ON_JOLIBOX_GET_USER_SUB_STATUS', expect.any(Function));
|
|
137
141
|
|
|
138
142
|
// Should send notification for both calls
|
|
139
143
|
expect(mockNotifyCustomEvent).toHaveBeenCalledTimes(2);
|
|
@@ -152,7 +156,7 @@ describe('getUserSubStatus', () => {
|
|
|
152
156
|
it('should ignore responses with different sequenceId', async () => {
|
|
153
157
|
let eventHandler: ((data: EventData) => void) | null = null;
|
|
154
158
|
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
155
|
-
if (eventName === '
|
|
159
|
+
if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
|
|
156
160
|
eventHandler = handler;
|
|
157
161
|
}
|
|
158
162
|
return jest.fn();
|
|
@@ -201,7 +205,7 @@ describe('getUserSubStatus', () => {
|
|
|
201
205
|
it('should handle multiple concurrent requests with different sequenceIds', async () => {
|
|
202
206
|
let eventHandler: ((data: EventData) => void) | null = null;
|
|
203
207
|
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
204
|
-
if (eventName === '
|
|
208
|
+
if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
|
|
205
209
|
eventHandler = handler;
|
|
206
210
|
}
|
|
207
211
|
return jest.fn();
|
|
@@ -248,7 +252,7 @@ describe('getUserSubStatus', () => {
|
|
|
248
252
|
it('should clean up timeout when response is received', async () => {
|
|
249
253
|
let eventHandler: ((data: EventData) => void) | null = null;
|
|
250
254
|
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
251
|
-
if (eventName === '
|
|
255
|
+
if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
|
|
252
256
|
eventHandler = handler;
|
|
253
257
|
}
|
|
254
258
|
return jest.fn();
|
|
@@ -278,9 +282,11 @@ describe('getUserSubStatus', () => {
|
|
|
278
282
|
});
|
|
279
283
|
|
|
280
284
|
it('should not resolve after timeout even if response comes later', async () => {
|
|
285
|
+
jest.useFakeTimers();
|
|
286
|
+
|
|
281
287
|
let eventHandler: ((data: EventData) => void) | null = null;
|
|
282
288
|
mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
|
|
283
|
-
if (eventName === '
|
|
289
|
+
if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
|
|
284
290
|
eventHandler = handler;
|
|
285
291
|
}
|
|
286
292
|
return jest.fn();
|
|
@@ -303,6 +309,6 @@ describe('getUserSubStatus', () => {
|
|
|
303
309
|
});
|
|
304
310
|
}
|
|
305
311
|
|
|
306
|
-
|
|
312
|
+
jest.useRealTimers();
|
|
307
313
|
});
|
|
308
314
|
});
|
|
@@ -14,7 +14,7 @@ let isListenerInitialized = false;
|
|
|
14
14
|
const initializeEventListener = () => {
|
|
15
15
|
if (isListenerInitialized) return;
|
|
16
16
|
|
|
17
|
-
onCustomEvent('
|
|
17
|
+
onCustomEvent('ON_JOLIBOX_GET_USER_SUB_STATUS', (data) => {
|
|
18
18
|
const pendingRequest = pendingRequests.get(data.sequenceId);
|
|
19
19
|
if (pendingRequest) {
|
|
20
20
|
clearTimeout(pendingRequest.timeoutId);
|
package/src/native/api/ads.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createCommands } from '@jolibox/common';
|
|
2
|
-
import { createSyncAPI, registerCanIUse } from './base';
|
|
2
|
+
import { canIUseNative, createSyncAPI, registerCanIUse } from './base';
|
|
3
3
|
import { createToast } from '@jolibox/ui';
|
|
4
4
|
import '../rewards';
|
|
5
5
|
|
|
@@ -27,7 +27,8 @@ import {
|
|
|
27
27
|
createGemRewardHandler,
|
|
28
28
|
createGemOnlyRewardHandler,
|
|
29
29
|
warmupGemBalanceCache,
|
|
30
|
-
warmupBalanceCache
|
|
30
|
+
warmupBalanceCache,
|
|
31
|
+
createSubscriptionRewardHandler
|
|
31
32
|
} from '@/common/rewards';
|
|
32
33
|
import { adEventEmitter } from '@/common/ads';
|
|
33
34
|
import { warmupRewardCache } from '@/common/rewards/fetch-reward';
|
|
@@ -107,7 +108,8 @@ rewardsHelper.registerRewardHandler(
|
|
|
107
108
|
},
|
|
108
109
|
onUnlockFailed: () => {
|
|
109
110
|
console.log('onUnlockFailed');
|
|
110
|
-
}
|
|
111
|
+
},
|
|
112
|
+
hasSubscription: () => canIUseNative('invokeSubscriptionPanelSync')
|
|
111
113
|
}) as unknown as (params?: unknown) => Promise<boolean>
|
|
112
114
|
);
|
|
113
115
|
|
|
@@ -123,7 +125,8 @@ rewardsHelper.registerRewardHandler(
|
|
|
123
125
|
},
|
|
124
126
|
onUnlockFailed: () => {
|
|
125
127
|
console.log('onUnlockFailed');
|
|
126
|
-
}
|
|
128
|
+
},
|
|
129
|
+
hasSubscription: () => canIUseNative('invokeSubscriptionPanelSync')
|
|
127
130
|
}) as unknown as (params?: unknown) => Promise<boolean>
|
|
128
131
|
);
|
|
129
132
|
|
|
@@ -136,7 +139,8 @@ rewardsHelper.registerRewardHandler(
|
|
|
136
139
|
track('gem_unlock_success', {
|
|
137
140
|
quantity: params.quantity
|
|
138
141
|
});
|
|
139
|
-
}
|
|
142
|
+
},
|
|
143
|
+
hasSubscription: () => canIUseNative('invokeSubscriptionPanelSync')
|
|
140
144
|
}) as unknown as (params?: unknown) => Promise<boolean>
|
|
141
145
|
);
|
|
142
146
|
|
|
@@ -149,7 +153,20 @@ rewardsHelper.registerRewardHandler(
|
|
|
149
153
|
quantity: params.quantity,
|
|
150
154
|
isOnly: true
|
|
151
155
|
});
|
|
152
|
-
}
|
|
156
|
+
},
|
|
157
|
+
hasSubscription: () => canIUseNative('invokeSubscriptionPanelSync')
|
|
158
|
+
}) as unknown as (params?: unknown) => Promise<boolean>
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const handleUnlockWithSubscriptionSuccess = () => {
|
|
162
|
+
track('subscription_unlock_success', {});
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
rewardsHelper.registerRewardHandler(
|
|
166
|
+
'SUBSCRIPTION',
|
|
167
|
+
createSubscriptionRewardHandler(httpClient, {
|
|
168
|
+
onSubSuccess: handleUnlockWithSubscriptionSuccess.bind(this),
|
|
169
|
+
hasSubscription: () => canIUseNative('invokeSubscriptionPanelSync')
|
|
153
170
|
}) as unknown as (params?: unknown) => Promise<boolean>
|
|
154
171
|
);
|
|
155
172
|
|
|
@@ -217,8 +234,28 @@ const adConfig = createSyncAPI('adConfig', {
|
|
|
217
234
|
});
|
|
218
235
|
|
|
219
236
|
let rewardLocked = false;
|
|
237
|
+
|
|
238
|
+
const wrapAdBreadDoneViewed = (params: IAdBreakParams) => {
|
|
239
|
+
params.adBreakDone?.({
|
|
240
|
+
breakType: params.type,
|
|
241
|
+
breakName: 'name' in params ? params.name ?? '' : '',
|
|
242
|
+
breakFormat: params.type === 'reward' ? 'reward' : 'interstitial',
|
|
243
|
+
breakStatus: 'viewed'
|
|
244
|
+
});
|
|
245
|
+
if ('adViewed' in params) {
|
|
246
|
+
params.adViewed?.();
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
220
250
|
const adBreak = createSyncAPI('adBreak', {
|
|
221
251
|
implement: (params: IAdBreakParams) => {
|
|
252
|
+
// check is sub user
|
|
253
|
+
const isSubUser = context.hostUserInfo?.isSubUser;
|
|
254
|
+
if (isSubUser) {
|
|
255
|
+
wrapAdBreadDoneViewed(params);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
222
259
|
if (params.type === 'reward') {
|
|
223
260
|
if (rewardLocked) {
|
|
224
261
|
return;
|
|
@@ -1,65 +1,10 @@
|
|
|
1
|
-
import { createAPI, registerCanIUse, t } from './base';
|
|
1
|
+
import { canIUseNative, createAPI, registerCanIUse, t } from './base';
|
|
2
2
|
import { invokeNative } from '@jolibox/native-bridge';
|
|
3
|
-
import {
|
|
3
|
+
import { UserCustomError } from '@jolibox/common';
|
|
4
4
|
import { createCommands } from '@jolibox/common';
|
|
5
5
|
import { context } from '@/common/context';
|
|
6
6
|
|
|
7
7
|
const commands = createCommands();
|
|
8
|
-
let supportedMethods: string[] = [];
|
|
9
|
-
let isInitialized = false;
|
|
10
|
-
|
|
11
|
-
const formatSupportedMethods = (
|
|
12
|
-
methods: (
|
|
13
|
-
| string
|
|
14
|
-
| {
|
|
15
|
-
method: string;
|
|
16
|
-
platforms?: string[];
|
|
17
|
-
nativeVersionCode?: number;
|
|
18
|
-
}
|
|
19
|
-
)[]
|
|
20
|
-
) => {
|
|
21
|
-
const platform = context.platform;
|
|
22
|
-
const nativeVersionCode = context.sdkInfo.nativeSDKVersionCode ?? 0;
|
|
23
|
-
return methods.reduce((acc, method) => {
|
|
24
|
-
if (typeof method === 'string') {
|
|
25
|
-
acc.push(method);
|
|
26
|
-
} else {
|
|
27
|
-
const { method: platformMethod, platforms, nativeVersionCode: methodNativeVersionCode } = method;
|
|
28
|
-
let canUse = true;
|
|
29
|
-
if (platforms && !platforms.includes(platform)) {
|
|
30
|
-
canUse = false;
|
|
31
|
-
}
|
|
32
|
-
if (methodNativeVersionCode && methodNativeVersionCode > nativeVersionCode) {
|
|
33
|
-
canUse = false;
|
|
34
|
-
}
|
|
35
|
-
if (canUse) {
|
|
36
|
-
acc.push(platformMethod);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return acc;
|
|
40
|
-
}, [] as string[]);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
hostEmitter.on('onGlobalConfigChanged', (params) => {
|
|
44
|
-
supportedMethods = formatSupportedMethods(params.globalConfig?.supportedHostMethods ?? []);
|
|
45
|
-
isInitialized = true;
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const getSupportedMethods = (): Promise<string[]> => {
|
|
49
|
-
return new Promise((resolve) => {
|
|
50
|
-
if (isInitialized) {
|
|
51
|
-
resolve(supportedMethods);
|
|
52
|
-
} else {
|
|
53
|
-
const handler = (params: any) => {
|
|
54
|
-
supportedMethods = formatSupportedMethods(params.globalConfig.supportedHostMethods ?? []);
|
|
55
|
-
isInitialized = true;
|
|
56
|
-
hostEmitter.off('onGlobalConfigChanged', handler);
|
|
57
|
-
resolve(supportedMethods);
|
|
58
|
-
};
|
|
59
|
-
hostEmitter.on('onGlobalConfigChanged', handler);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
};
|
|
63
8
|
|
|
64
9
|
const callHostMethodAsync = createAPI('callHostMethod', {
|
|
65
10
|
paramsSchema: t.tuple(
|
|
@@ -69,10 +14,10 @@ const callHostMethodAsync = createAPI('callHostMethod', {
|
|
|
69
14
|
})
|
|
70
15
|
),
|
|
71
16
|
implement: async ({ method, params }) => {
|
|
72
|
-
|
|
73
|
-
if (!supportedMethods.includes(method)) {
|
|
17
|
+
if (!canIUseNative(method)) {
|
|
74
18
|
throw new UserCustomError('Method not supported', 10001);
|
|
75
19
|
}
|
|
20
|
+
|
|
76
21
|
const { errNo, errMsg, data } = await invokeNative('callHostMethodAsync', {
|
|
77
22
|
method,
|
|
78
23
|
params: {
|
|
@@ -106,8 +51,7 @@ const userTrackAsync = createAPI('userTrackAsync', {
|
|
|
106
51
|
})
|
|
107
52
|
),
|
|
108
53
|
implement: async ({ event, eventType, params }) => {
|
|
109
|
-
|
|
110
|
-
if (!supportedMethods.includes('userTrackAsync')) {
|
|
54
|
+
if (!canIUseNative('userTrackAsync')) {
|
|
111
55
|
throw new UserCustomError('Method not supported', 10001);
|
|
112
56
|
}
|
|
113
57
|
const { errNo, errMsg } = await invokeNative('trackAsync', {
|
package/src/native/api/login.ts
CHANGED
|
@@ -19,19 +19,27 @@ registerCanIUse('login', {
|
|
|
19
19
|
registerCanIUse('checkSession', {
|
|
20
20
|
version: '1.0.0',
|
|
21
21
|
success: {
|
|
22
|
-
errMsg: '1.0.0'
|
|
22
|
+
errMsg: '1.0.0',
|
|
23
|
+
isLogin: '1.0.0',
|
|
24
|
+
isSubUser: '1.2.9'
|
|
23
25
|
}
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
const loginDeferredMap = new Map<
|
|
27
29
|
string,
|
|
28
|
-
Deferred<{
|
|
30
|
+
Deferred<{
|
|
31
|
+
isLogin: boolean;
|
|
32
|
+
token?: string;
|
|
33
|
+
isFirstLogin?: boolean;
|
|
34
|
+
extra?: LoginExtra;
|
|
35
|
+
appAccountToken?: string;
|
|
36
|
+
}>
|
|
29
37
|
>();
|
|
30
38
|
|
|
31
|
-
onNative('onLoginStateChange', ({ isLogin, token, uuid, isFirstLogin, extra }) => {
|
|
39
|
+
onNative('onLoginStateChange', ({ isLogin, token, uuid, isFirstLogin, extra, appAccountToken }) => {
|
|
32
40
|
const deferred = loginDeferredMap.get(uuid);
|
|
33
41
|
if (deferred) {
|
|
34
|
-
deferred.resolve({ isLogin, token, isFirstLogin, extra });
|
|
42
|
+
deferred.resolve({ isLogin, token, isFirstLogin, extra, appAccountToken });
|
|
35
43
|
loginDeferredMap.delete(uuid);
|
|
36
44
|
}
|
|
37
45
|
});
|
|
@@ -81,12 +89,16 @@ export const loginImplement = async (
|
|
|
81
89
|
isLogin: boolean;
|
|
82
90
|
token?: string;
|
|
83
91
|
isFirstLogin?: boolean;
|
|
92
|
+
appAccountToken?: string;
|
|
84
93
|
extra?: LoginExtra;
|
|
85
94
|
}>();
|
|
86
95
|
loginDeferredMap.set(loginUUID, deferred);
|
|
87
96
|
const loginRes = await deferred.promise;
|
|
88
|
-
|
|
89
|
-
const
|
|
97
|
+
// update sub user status
|
|
98
|
+
const env = invokeNative('envSync');
|
|
99
|
+
const isSubUser = env?.data?.hostUserInfo?.isSubUser;
|
|
100
|
+
context.onEnvConfigChanged({ hostUserInfo: { ...loginRes, isSubUser } });
|
|
101
|
+
const { extra, appAccountToken, ...rest } = loginRes;
|
|
90
102
|
hostEmitter.emit('onLoginComplete', rest);
|
|
91
103
|
return needExtra ? loginRes : rest;
|
|
92
104
|
};
|
|
@@ -110,8 +122,11 @@ const checkSession = createAPI('checkSession', {
|
|
|
110
122
|
const {
|
|
111
123
|
data: { isLogin }
|
|
112
124
|
} = await applyNative('checkLoginAsync');
|
|
125
|
+
|
|
126
|
+
const isSubUser = context.hostUserInfo?.isSubUser;
|
|
113
127
|
return {
|
|
114
|
-
isLogin
|
|
128
|
+
isLogin,
|
|
129
|
+
isSubUser
|
|
115
130
|
};
|
|
116
131
|
}
|
|
117
132
|
});
|
|
@@ -3,6 +3,8 @@ import { createAPI, registerCanIUse, t } from './base';
|
|
|
3
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
|
+
import { ISubscriptionTierData } from '@jolibox/types';
|
|
7
|
+
import { subscriptionService } from '@/native/subscription';
|
|
6
8
|
|
|
7
9
|
const commands = createCommands();
|
|
8
10
|
const paymentService = createJoliGemPaymentService();
|
|
@@ -13,10 +15,10 @@ const purchaseGem = createAPI('purchaseGem', {
|
|
|
13
15
|
productId: t.string()
|
|
14
16
|
})
|
|
15
17
|
),
|
|
16
|
-
implement: async (params: { productId: string }): Promise<{ totalAmount
|
|
18
|
+
implement: async (params: { productId: string }): Promise<{ totalAmount?: string }> => {
|
|
17
19
|
const result = await paymentService.purchase(params.productId);
|
|
18
20
|
|
|
19
|
-
if (result.code !== 'SUCCESS'
|
|
21
|
+
if (result.code !== 'SUCCESS') {
|
|
20
22
|
throw createAPIError({
|
|
21
23
|
code: -1,
|
|
22
24
|
msg: '[JoliboxSDK]: purchase gem failed: ' + result.message
|
|
@@ -24,7 +26,7 @@ const purchaseGem = createAPI('purchaseGem', {
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
return {
|
|
27
|
-
totalAmount: result.data?.totalAmount
|
|
29
|
+
totalAmount: result.data?.totalAmount ?? ''
|
|
28
30
|
};
|
|
29
31
|
}
|
|
30
32
|
});
|
|
@@ -44,9 +46,69 @@ const getGemProducts = createAPI('getGemProducts', {
|
|
|
44
46
|
}
|
|
45
47
|
});
|
|
46
48
|
|
|
49
|
+
const getSubscriptionPlans = createAPI('getSubscriptionPlans', {
|
|
50
|
+
paramsSchema: t.tuple(),
|
|
51
|
+
implement: async (): Promise<{ plans: ISubscriptionTierData[] } | undefined> => {
|
|
52
|
+
const subPlans = await subscriptionService.getSubInfo();
|
|
53
|
+
|
|
54
|
+
console.log('[PaymentAPI] getSubscriptionPlans result', subPlans);
|
|
55
|
+
if (!subPlans) {
|
|
56
|
+
throw createAPIError({
|
|
57
|
+
code: -1,
|
|
58
|
+
msg: '[JoliboxSDK]: get subscription plans failed'
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
plans: subPlans
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const flushSubInfoCache = createAPI('flushSubInfoCache', {
|
|
68
|
+
paramsSchema: t.tuple(),
|
|
69
|
+
implement: async (): Promise<void> => {
|
|
70
|
+
await subscriptionService.updateAllNativePriceCache();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const subscribe = createAPI('subscribe', {
|
|
75
|
+
paramsSchema: t.tuple(
|
|
76
|
+
t.object({
|
|
77
|
+
productId: t.string()
|
|
78
|
+
})
|
|
79
|
+
),
|
|
80
|
+
implement: async (params: {
|
|
81
|
+
productId: string;
|
|
82
|
+
}): Promise<{
|
|
83
|
+
subPlanId: string;
|
|
84
|
+
errMsg: string;
|
|
85
|
+
result: 'SUCCESS' | 'FAILED' | 'FAILURE_SUBSCRIPTED';
|
|
86
|
+
}> => {
|
|
87
|
+
const tier = await subscriptionService.getTierByProductId(params.productId);
|
|
88
|
+
if (!tier) {
|
|
89
|
+
throw createAPIError({
|
|
90
|
+
code: -1,
|
|
91
|
+
msg: '[JoliboxSDK]: product not found'
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const { basePlanId, appStoreProductId, nativeProductId } = tier;
|
|
95
|
+
const result = await subscriptionService.subscribe({ basePlanId, nativeProductId, appStoreProductId });
|
|
96
|
+
return {
|
|
97
|
+
result: result.result,
|
|
98
|
+
subPlanId: result.subPlanId,
|
|
99
|
+
errMsg: result.message ?? ''
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
47
104
|
commands.registerCommand('PaymentSDK.purchaseGem', purchaseGem);
|
|
48
105
|
commands.registerCommand('PaymentSDK.getGemProducts', getGemProducts);
|
|
49
106
|
|
|
107
|
+
commands.registerCommand('PaymentSDK.getSubscriptionPlans', getSubscriptionPlans);
|
|
108
|
+
commands.registerCommand('PaymentSDK.subscribe', subscribe);
|
|
109
|
+
|
|
110
|
+
commands.registerCommand('PaymentSDK.flushSubInfoCache', flushSubInfoCache);
|
|
111
|
+
|
|
50
112
|
registerCanIUse('payment.purchaseGem', {
|
|
51
113
|
version: '1.2.3'
|
|
52
114
|
});
|
|
@@ -54,3 +116,16 @@ registerCanIUse('payment.purchaseGem', {
|
|
|
54
116
|
registerCanIUse('payment.getGemProducts', {
|
|
55
117
|
version: '1.2.3'
|
|
56
118
|
});
|
|
119
|
+
|
|
120
|
+
// for test, correct version is 1.3.0
|
|
121
|
+
registerCanIUse('payment.getSubscriptionPlans', {
|
|
122
|
+
version: '1.2.5'
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
registerCanIUse('payment.subscribe', {
|
|
126
|
+
version: '1.2.5'
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
registerCanIUse('payment.flushSubInfoCache', {
|
|
130
|
+
version: '1.2.5'
|
|
131
|
+
});
|
|
@@ -9,13 +9,26 @@ import {
|
|
|
9
9
|
// Mock dependencies
|
|
10
10
|
jest.mock('@jolibox/native-bridge', () => ({
|
|
11
11
|
applyNative: jest.fn(),
|
|
12
|
-
onNative: jest.fn()
|
|
12
|
+
onNative: jest.fn(),
|
|
13
|
+
invokeNative: jest.fn()
|
|
13
14
|
}));
|
|
14
15
|
|
|
15
16
|
jest.mock('@/native/network', () => ({
|
|
16
17
|
innerFetch: jest.fn()
|
|
17
18
|
}));
|
|
18
19
|
|
|
20
|
+
jest.mock('@/native/api/storage', () => ({
|
|
21
|
+
setGlobalStorage: jest.fn().mockResolvedValue({
|
|
22
|
+
code: 'SUCCESS',
|
|
23
|
+
message: 'success'
|
|
24
|
+
}),
|
|
25
|
+
getGlobalStorage: jest.fn().mockResolvedValue({
|
|
26
|
+
code: 'SUCCESS',
|
|
27
|
+
message: 'success',
|
|
28
|
+
data: null
|
|
29
|
+
})
|
|
30
|
+
}));
|
|
31
|
+
|
|
19
32
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
20
33
|
const { applyNative } = require('@jolibox/native-bridge');
|
|
21
34
|
|
|
@@ -6,7 +6,7 @@ import { applyNative } from '@jolibox/native-bridge';
|
|
|
6
6
|
import { innerFetch as fetch } from '@/native/network';
|
|
7
7
|
import type { PaymentResult } from './payment-helper';
|
|
8
8
|
import { RequestCacheService, RequestAdapter } from '@jolibox/common';
|
|
9
|
-
|
|
9
|
+
import { executeWithPersistentCache } from './utils/cache-with-storage';
|
|
10
10
|
type PaymentPurchaseType = 'JOLI_COIN' | 'JOLI_GEM';
|
|
11
11
|
|
|
12
12
|
// Request/Response interfaces for RequestCacheService
|
|
@@ -51,7 +51,7 @@ class PaymentRequestAdapter
|
|
|
51
51
|
{
|
|
52
52
|
// applyNative 的缓存
|
|
53
53
|
private static nativePriceCache = new Map<string, { [appStoreProductId: string]: { price: string } }>();
|
|
54
|
-
private static readonly NATIVE_PRICE_CACHE_DURATION =
|
|
54
|
+
private static readonly NATIVE_PRICE_CACHE_DURATION = 1 * 24 * 60 * 60 * 1000; // 1 day
|
|
55
55
|
private static nativePriceCacheTimestamp = new Map<string, number>();
|
|
56
56
|
|
|
57
57
|
async execute(endpoint: string, _options?: PaymentRequest): Promise<PaymentResponse> {
|
|
@@ -92,34 +92,33 @@ class PaymentRequestAdapter
|
|
|
92
92
|
private async getNativePriceData(
|
|
93
93
|
appStoreProductIds: string[]
|
|
94
94
|
): Promise<{ [appStoreProductId: string]: { price: string } } | null> {
|
|
95
|
-
// 检查缓存
|
|
96
95
|
const cacheKey = appStoreProductIds.sort().join(',');
|
|
97
|
-
const cached = PaymentRequestAdapter.nativePriceCache.get(cacheKey);
|
|
98
|
-
const cacheTime = PaymentRequestAdapter.nativePriceCacheTimestamp.get(cacheKey) || 0;
|
|
99
|
-
|
|
100
|
-
if (cached && Date.now() - cacheTime < PaymentRequestAdapter.NATIVE_PRICE_CACHE_DURATION) {
|
|
101
|
-
console.log('[PaymentService] Using cached native price data');
|
|
102
|
-
return cached;
|
|
103
|
-
}
|
|
104
96
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
97
|
+
return executeWithPersistentCache(
|
|
98
|
+
cacheKey,
|
|
99
|
+
async () => {
|
|
100
|
+
try {
|
|
101
|
+
const { data } = await applyNative('requestProductDetailsAsync', {
|
|
102
|
+
appStoreProductIds
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (data) {
|
|
106
|
+
// cache
|
|
107
|
+
PaymentRequestAdapter.nativePriceCache.set(cacheKey, data);
|
|
108
|
+
PaymentRequestAdapter.nativePriceCacheTimestamp.set(cacheKey, Date.now());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return data;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.warn('[PaymentService] Failed to fetch native product details:', error);
|
|
114
|
+
throw error; // rethrow error, let upper layer handle it
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
cacheDuration: PaymentRequestAdapter.NATIVE_PRICE_CACHE_DURATION,
|
|
119
|
+
storageKeyPrefix: 'joli_native_price_'
|
|
116
120
|
}
|
|
117
|
-
|
|
118
|
-
return data;
|
|
119
|
-
} catch (error) {
|
|
120
|
-
console.warn('[PaymentService] Failed to fetch native product details:', error);
|
|
121
|
-
throw error; // rethrow error, let upper layer handle it
|
|
122
|
-
}
|
|
121
|
+
);
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
extractCacheableData(response: PaymentResponse): PaymentCacheData {
|