@jolibox/implement 1.1.19-beta.1 → 1.1.19

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 (44) hide show
  1. package/.rush/temp/package-deps_build.json +23 -16
  2. package/.rush/temp/shrinkwrap-deps.json +1 -1
  3. package/dist/common/context/index.d.ts +1 -0
  4. package/dist/common/rewards/index.d.ts +1 -10
  5. package/dist/common/rewards/registers/use-ads.d.ts +3 -2
  6. package/dist/common/rewards/registers/use-jolicoin-only.d.ts +10 -0
  7. package/dist/common/rewards/registers/utils/coins/index.d.ts +32 -0
  8. package/dist/common/rewards/registers/utils/event-listener.d.ts +15 -0
  9. package/dist/common/rewards/registers/utils/index.d.ts +1 -0
  10. package/dist/common/rewards/reward-emitter.d.ts +58 -0
  11. package/dist/common/rewards/reward-helper.d.ts +2 -1
  12. package/dist/common/utils/index.d.ts +49 -2
  13. package/dist/h5/api/ads.d.ts +1 -1
  14. package/dist/h5/http/index.d.ts +1 -1
  15. package/dist/h5/rewards/index.d.ts +4 -0
  16. package/dist/index.js +197 -3
  17. package/dist/index.native.js +402 -21
  18. package/dist/native/api/ads.d.ts +1 -1
  19. package/dist/native/api/login.d.ts +4 -1
  20. package/dist/native/rewards/index.d.ts +4 -0
  21. package/dist/native/ui/modal-iframe.d.ts +26 -0
  22. package/implement.build.log +2 -2
  23. package/package.json +5 -4
  24. package/src/common/context/index.ts +8 -4
  25. package/src/common/rewards/fetch-reward.ts +9 -3
  26. package/src/common/rewards/index.ts +1 -12
  27. package/src/common/rewards/registers/use-ads.ts +3 -2
  28. package/src/common/rewards/registers/use-jolicoin-only.ts +57 -0
  29. package/src/common/rewards/registers/use-jolicoin.ts +27 -77
  30. package/src/common/rewards/registers/utils/coins/index.ts +188 -0
  31. package/src/common/rewards/registers/utils/event-listener.ts +66 -0
  32. package/src/common/rewards/registers/utils/index.ts +8 -0
  33. package/src/common/rewards/reward-emitter.ts +79 -0
  34. package/src/common/rewards/reward-helper.ts +3 -2
  35. package/src/common/utils/index.ts +117 -4
  36. package/src/h5/api/ads.ts +65 -12
  37. package/src/h5/http/index.ts +2 -2
  38. package/src/h5/rewards/index.ts +69 -0
  39. package/src/native/api/ads.ts +95 -33
  40. package/src/native/api/login.ts +1 -1
  41. package/src/native/api/navigate.ts +2 -0
  42. package/src/native/bootstrap/index.ts +33 -0
  43. package/src/native/rewards/index.ts +138 -0
  44. package/src/native/ui/modal-iframe.ts +271 -0
@@ -1 +1 @@
1
- export {};
1
+ import '../rewards';
@@ -1 +1,4 @@
1
- export {};
1
+ export declare const login: (...inputs: unknown[]) => Promise<import("@jolibox/types").StandardResponse<{
2
+ isLogin: boolean;
3
+ token?: string;
4
+ }>>;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * rewards event handlers
3
+ */
4
+ export {};
@@ -0,0 +1,26 @@
1
+ interface IframeModalOptions {
2
+ url: string;
3
+ height?: string;
4
+ showCloseButton?: boolean;
5
+ enableDragClose?: boolean;
6
+ useAnimation?: boolean;
7
+ onClose?: () => void;
8
+ onLoad?: () => void;
9
+ }
10
+ /**
11
+ * create a half-screen iframe modal
12
+ */
13
+ export declare function createIframeModal(options: IframeModalOptions): {
14
+ close: () => void;
15
+ };
16
+ declare global {
17
+ interface Window {
18
+ joliboxUI?: {
19
+ createIframeModal: (options: IframeModalOptions) => {
20
+ close: () => void;
21
+ };
22
+ };
23
+ }
24
+ }
25
+ export declare function registerIframeModalToGlobal(): () => void;
26
+ export {};
@@ -1,9 +1,9 @@
1
1
  Invoking: npm run clean && npm run build:esm && tsc
2
2
 
3
- > @jolibox/implement@1.1.19-beta.1 clean
3
+ > @jolibox/implement@1.1.19 clean
4
4
  > rimraf ./dist
5
5
 
6
6
 
7
- > @jolibox/implement@1.1.19-beta.1 build:esm
7
+ > @jolibox/implement@1.1.19 build:esm
8
8
  > BUILD_VERSION=$(node -p "require('./package.json').version") node esbuild.config.js --format=esm
9
9
 
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@jolibox/implement",
3
3
  "description": "This project is Jolibox JS-SDk implement for Native && H5",
4
- "version": "1.1.19-beta.1",
4
+ "version": "1.1.19",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
7
7
  "license": "MIT",
8
8
  "dependencies": {
9
- "@jolibox/common": "1.1.19-beta.1",
10
- "@jolibox/types": "1.1.19-beta.1",
11
- "@jolibox/native-bridge": "1.1.19-beta.1",
9
+ "@jolibox/common": "1.1.19",
10
+ "@jolibox/types": "1.1.19",
11
+ "@jolibox/native-bridge": "1.1.19",
12
+ "@jolibox/ads": "1.1.19",
12
13
  "localforage": "1.10.0",
13
14
  "@jolibox/ui": "1.0.0",
14
15
  "web-vitals": "4.2.4"
@@ -1,9 +1,9 @@
1
- import { mergeArray, mergeWith } from '@jolibox/common';
1
+ import { getAdvertisingId, mergeArray, mergeWith } from '@jolibox/common';
2
2
  import { DeviceInfo, HostInfo, HostUserInfo, SdkInfo } from './types';
3
3
  import { Env } from '@jolibox/types';
4
4
  import { parseUrlQuery, encodeJoliSourceQuery, QueryParams } from './url-parse';
5
5
  import { getAppVersion } from '../http/xua';
6
- import { getDeviceId } from '@jolibox/common';
6
+ import { getDeviceId, platform } from '@jolibox/common';
7
7
 
8
8
  declare const globalThis: {
9
9
  joliboxJSCore?: {
@@ -16,8 +16,9 @@ const defaultEnv: Env = {
16
16
  brand: 'UnknownBrand',
17
17
  model: 'UnknownModel',
18
18
  did: getDeviceId(),
19
+ adId: getAdvertisingId(),
19
20
  pixelRatio: window.devicePixelRatio || 1,
20
- platform: 'h5',
21
+ platform: platform.isAndroid ? 'android' : platform.isiOS ? 'ios' : 'h5',
21
22
  system: 'UnknownSystemVersion',
22
23
  lang: 'zh'
23
24
  },
@@ -26,7 +27,7 @@ const defaultEnv: Env = {
26
27
  jssdkVersion: getAppVersion()
27
28
  },
28
29
  schema: window.location.href,
29
- platform: 'h5'
30
+ platform: platform.isAndroid ? 'android' : platform.isiOS ? 'ios' : 'h5'
30
31
  };
31
32
 
32
33
  const nativeEnv = globalThis.joliboxJSCore?.env;
@@ -108,6 +109,9 @@ const wrapContext = () => {
108
109
  get baseApiHost(): string {
109
110
  return testMode ? 'https://stg-api.jolibox.com' : 'https://api.jolibox.com';
110
111
  },
112
+ get adId(): string {
113
+ return env.deviceInfo.adId ?? env.deviceInfo.did;
114
+ },
111
115
  onEnvConfigChanged: (newConfig: Partial<Env>) => {
112
116
  mergeWith(env, newConfig, mergeArray);
113
117
  },
@@ -1,7 +1,7 @@
1
- import { UnlockOptionsEventName, unlockOptionsEmitter } from '.';
2
1
  import { IHttpClient } from '../http';
3
2
  import { RewardsHelper, RewardType } from './reward-helper';
4
3
  import { IJolicoinRewardOption } from './type';
4
+ import { UnlockOptionsEventName, rewardsEmitter } from './reward-emitter';
5
5
 
6
6
  const priority = () => {
7
7
  return (a: RewardType, b: RewardType) => {
@@ -11,6 +11,12 @@ const priority = () => {
11
11
  };
12
12
  };
13
13
 
14
+ const sortRewards = (rewardsTypes: RewardType[]): RewardType[] => {
15
+ if (!rewardsTypes.length) return ['ADS'];
16
+ if (rewardsTypes.includes('JOLI_COIN') && rewardsTypes.length <= 1) return ['JOLI_COIN_ONLY'];
17
+ return rewardsTypes.sort(priority());
18
+ };
19
+
14
20
  export const createRewardFetcher = (rewardsHelper: RewardsHelper) => {
15
21
  rewardsHelper.registerRewardsFetcher(async (httpClient: IHttpClient) => {
16
22
  const defaultRewards: RewardType[] = ['ADS'];
@@ -19,7 +25,7 @@ export const createRewardFetcher = (rewardsHelper: RewardsHelper) => {
19
25
  if (res.code !== 'SUCCESS') {
20
26
  return defaultRewards;
21
27
  }
22
- unlockOptionsEmitter.emit(UnlockOptionsEventName, {
28
+ rewardsEmitter.emit(UnlockOptionsEventName, {
23
29
  options: res.data?.unlockOptions || [],
24
30
  userJoliCoin: res.extra?.joliCoin || {
25
31
  balance: 0,
@@ -30,7 +36,7 @@ export const createRewardFetcher = (rewardsHelper: RewardsHelper) => {
30
36
  const rewardsTypes =
31
37
  res.data?.unlockOptions?.map((option) => option.type) || Array.from(defaultRewards);
32
38
  // Sort reward types with JOLI_COIN having higher priority than ADS
33
- return rewardsTypes.sort(priority());
39
+ return sortRewards(rewardsTypes);
34
40
  } catch (e) {
35
41
  console.error('getRewardOptions error:', e);
36
42
  return defaultRewards;
@@ -1,20 +1,9 @@
1
1
  import { createRewardsHelper } from './reward-helper';
2
2
  import { createRewardFetcher } from './fetch-reward';
3
- import { EventEmitter } from '@jolibox/common';
4
- import { IUnlockOption, IJoliCoin } from './type';
5
3
 
6
4
  export const rewardsHelper = createRewardsHelper();
7
5
  createRewardFetcher(rewardsHelper);
8
6
 
9
7
  export * from './registers/use-ads';
10
8
  export * from './registers/use-jolicoin';
11
-
12
- export interface IUnlockOptionsEvent {
13
- options: IUnlockOption[];
14
- userJoliCoin: IJoliCoin;
15
- }
16
-
17
- export const UnlockOptionsEventName = 'UNLOCK_OPTIONS_CHANGED' as const;
18
- export const unlockOptionsEmitter = new EventEmitter<{
19
- [UnlockOptionsEventName]: [IUnlockOptionsEvent];
20
- }>();
9
+ export * from './registers/use-jolicoin-only';
@@ -1,7 +1,8 @@
1
- import { JoliboxAdsImpl, IRewardParams } from '../../ads';
1
+ import { IRewardParams } from '../../ads';
2
+ import { JoliboxAdsForGame } from '@jolibox/ads';
2
3
 
3
4
  export type AdsRewardsHandler = (params: IRewardParams) => Promise<boolean>;
4
- export const createAdsRewardHandler = (ads: JoliboxAdsImpl): AdsRewardsHandler => {
5
+ export const createAdsRewardHandler = (ads: JoliboxAdsForGame): AdsRewardsHandler => {
5
6
  return async (params: IRewardParams) => {
6
7
  ads.adBreak(params);
7
8
  return true;
@@ -0,0 +1,57 @@
1
+ import { IHttpClient } from '@/common/http';
2
+ import { rewardsEmitter, UnlockOptionsEventName, IUnlockOptionsEvent } from '../reward-emitter';
3
+ import { IAdBreakParams } from '@/common/ads';
4
+ import {
5
+ createCommonJolicoinRewardHandler,
6
+ createShowUnlockWithJolicoinModal,
7
+ createInitiateAndAwaitPayment
8
+ } from './utils/coins';
9
+ import { createEventPromiseHandler } from './utils/event-listener';
10
+
11
+ export type JolicoinOnlyRewardsHandler = (params: IAdBreakParams) => Promise<boolean>;
12
+ export const createJolicoinOnlyRewardHandler = (
13
+ httpClient: IHttpClient,
14
+ {
15
+ onUnlockSuccess,
16
+ onUnlockFailed
17
+ }: {
18
+ onUnlockSuccess?: (data: { quantity: number; balance: number }) => void;
19
+ onUnlockFailed?: () => void;
20
+ }
21
+ ): JolicoinOnlyRewardsHandler => {
22
+ const unlockOptionsHandler = createEventPromiseHandler<IUnlockOptionsEvent, typeof UnlockOptionsEventName>(
23
+ rewardsEmitter,
24
+ UnlockOptionsEventName
25
+ );
26
+
27
+ const handleUnlockFailed = (() => (params: IAdBreakParams) => {
28
+ onUnlockFailed?.();
29
+ params.adBreakDone?.({
30
+ breakType: params.type,
31
+ breakFormat: params.type === 'reward' ? 'reward' : 'interstitial',
32
+ breakStatus: 'noAdPreloaded'
33
+ });
34
+ })();
35
+
36
+ const showUnlockWithJolicoinModal = createShowUnlockWithJolicoinModal('JOLI_COIN', {
37
+ confirmButtonText: 'Use Jolicoin',
38
+ cancelButtonText: 'No thanks'
39
+ });
40
+
41
+ const initiateAndAwaitPayment = createInitiateAndAwaitPayment('JOLI_COIN', {
42
+ confirmButtonText: 'Pay and unlock',
43
+ cancelButtonText: 'No thanks'
44
+ });
45
+
46
+ const jolicoinOnlyRewardHandler = createCommonJolicoinRewardHandler(httpClient, {
47
+ handlers: {
48
+ handleUnlockSuccess: onUnlockSuccess,
49
+ handleUnlockFailed,
50
+ unlockOptionsHandler,
51
+ initiateAndAwaitPayment,
52
+ showUnlockWithJolicoinModal
53
+ }
54
+ });
55
+
56
+ return jolicoinOnlyRewardHandler;
57
+ };
@@ -1,19 +1,9 @@
1
1
  import { IHttpClient } from '@/common/http';
2
- import { context } from '@/common/context';
3
- import { uuidv4 } from '@jolibox/common';
4
- import { unlockOptionsEmitter, UnlockOptionsEventName, IUnlockOptionsEvent } from '..';
2
+ import { rewardsEmitter, UnlockOptionsEventName, IUnlockOptionsEvent } from '../reward-emitter';
5
3
  import { IAdBreakParams } from '@/common/ads';
6
- import { canUseJolicoin } from './utils';
7
-
8
- interface IJolicoinUnlockRes {
9
- code: 'SUCCESS' | 'BALANCE_NOT_ENOUGH' | 'EPISODE_LOCK_JUMP' | 'EPISODE_UNLOCK_ALREADY';
10
- message: string;
11
- data: {
12
- transactionId: string;
13
- quantity: number;
14
- balance: number;
15
- };
16
- }
4
+ import { createEventPromiseHandler } from './utils/event-listener';
5
+ import { createCommonJolicoinRewardHandler } from './utils/coins';
6
+ import { createInitiateAndAwaitPayment, createShowUnlockWithJolicoinModal } from './utils/coins';
17
7
 
18
8
  export type JolicoinRewardsHandler = (params: IAdBreakParams) => Promise<boolean>;
19
9
  export const createJolicoinRewardHandler = (
@@ -26,74 +16,34 @@ export const createJolicoinRewardHandler = (
26
16
  onUnlockFailed?: () => void;
27
17
  }
28
18
  ): JolicoinRewardsHandler => {
29
- let unlockOptionsPromise: Promise<IUnlockOptionsEvent> | null = null;
30
- let resolveUnlockOptions: ((value: IUnlockOptionsEvent) => void) | null = null;
31
- let cachedUnlockOptions: IUnlockOptionsEvent | null = null;
19
+ const unlockOptionsHandler = createEventPromiseHandler<IUnlockOptionsEvent, typeof UnlockOptionsEventName>(
20
+ rewardsEmitter,
21
+ UnlockOptionsEventName
22
+ );
32
23
 
33
- const createUnlockOptionsPromise = () => {
34
- if (cachedUnlockOptions) {
35
- unlockOptionsPromise = Promise.resolve(cachedUnlockOptions);
36
- return;
37
- }
38
-
39
- unlockOptionsPromise = new Promise<IUnlockOptionsEvent>((resolve) => {
40
- resolveUnlockOptions = resolve;
41
- });
24
+ const handleUnlockFailed = () => {
25
+ onUnlockFailed?.();
42
26
  };
43
27
 
44
- createUnlockOptionsPromise();
45
-
46
- unlockOptionsEmitter.on(UnlockOptionsEventName, (options) => {
47
- cachedUnlockOptions = options;
48
- if (resolveUnlockOptions) {
49
- resolveUnlockOptions(options);
50
- resolveUnlockOptions = null;
51
- }
28
+ const showUnlockWithJolicoinModal = createShowUnlockWithJolicoinModal('ADS-JOLI_COIN', {
29
+ confirmButtonText: 'Use Jolicoin',
30
+ cancelButtonText: 'No watch ads'
52
31
  });
53
32
 
54
- return async (params: IAdBreakParams) => {
55
- try {
56
- if (!unlockOptionsPromise) {
57
- createUnlockOptionsPromise();
58
- }
59
- const unlockOptions = await unlockOptionsPromise!;
60
- if (!canUseJolicoin(unlockOptions?.options || [], unlockOptions?.userJoliCoin)) {
61
- onUnlockFailed?.();
62
- return false;
63
- }
33
+ const initiateAndAwaitPayment = createInitiateAndAwaitPayment('ADS-JOLI_COIN', {
34
+ confirmButtonText: 'Pay and unlock',
35
+ cancelButtonText: 'No watch ads'
36
+ });
64
37
 
65
- const unlockWithJolicoin = await httpClient.post<IJolicoinUnlockRes>('/api/joli-coin/unlock', {
66
- data: {
67
- // TODO: support drama
68
- type: 'GAME_REWARD',
69
- reqId: `${uuidv4()}-${context.mpType}-${Date.now()}`,
70
- gameInfo: {
71
- gameId: context.mpId
72
- }
73
- }
74
- });
75
- if (unlockWithJolicoin.code == 'SUCCESS') {
76
- params.adBreakDone?.({
77
- breakType: params.type,
78
- breakName: 'name' in params ? params.name ?? '' : '',
79
- breakFormat: 'reward',
80
- breakStatus: 'viewed'
81
- });
82
- if ('adViewed' in params) {
83
- params.adViewed?.();
84
- }
85
- onUnlockSuccess?.({
86
- quantity: unlockWithJolicoin.data.quantity,
87
- balance: unlockWithJolicoin.data.balance
88
- });
89
- return true;
90
- }
91
- onUnlockFailed?.();
92
- return false;
93
- } catch (e) {
94
- console.error(`JolicoinRewardHandler error:`, e);
95
- onUnlockFailed?.();
96
- return false;
38
+ const jolicoinOnlyRewardHandler = createCommonJolicoinRewardHandler(httpClient, {
39
+ handlers: {
40
+ handleUnlockSuccess: onUnlockSuccess,
41
+ handleUnlockFailed,
42
+ unlockOptionsHandler,
43
+ initiateAndAwaitPayment,
44
+ showUnlockWithJolicoinModal
97
45
  }
98
- };
46
+ });
47
+
48
+ return jolicoinOnlyRewardHandler;
99
49
  };
@@ -0,0 +1,188 @@
1
+ import { IHttpClient } from '@/common/http';
2
+ import { context } from '@/common/context';
3
+ import { uuidv4 } from '@jolibox/common';
4
+ import {
5
+ rewardsEmitter,
6
+ UnlockOptionsEventName,
7
+ IUnlockOptionsEvent,
8
+ InvokePaymentEventName,
9
+ PaymentResultEventName,
10
+ IPaymentEvent,
11
+ IUseModalResultEvent,
12
+ UseModalEventName,
13
+ UseModalResultEventName
14
+ } from '@/common/rewards/reward-emitter';
15
+ import { IAdBreakParams } from '@/common/ads';
16
+ import { joliCoinIsEnough } from '../index';
17
+ import { createEventPromiseHandler, EventPromiseHandler } from '../event-listener';
18
+ import { IJoliCoin } from '@/common/rewards/type';
19
+
20
+ type IPaymentResult = 'SUCCESS' | 'FAILED' | 'CANCEL';
21
+ type IUseModalResult = 'CONFIRM' | 'CANCEL' | 'FAILED';
22
+
23
+ interface IJolicoinUnlockRes {
24
+ code: 'SUCCESS' | 'BALANCE_NOT_ENOUGH' | 'EPISODE_LOCK_JUMP' | 'EPISODE_UNLOCK_ALREADY';
25
+ message: string;
26
+ data: {
27
+ transactionId: string;
28
+ quantity: number;
29
+ balance: number;
30
+ };
31
+ }
32
+
33
+ export const createInitiateAndAwaitPayment = (
34
+ type: 'JOLI_COIN' | 'ADS-JOLI_COIN',
35
+ buttons: {
36
+ confirmButtonText: string;
37
+ cancelButtonText: string;
38
+ }
39
+ ) => {
40
+ return async (params: { userJoliCoin: IJoliCoin; joliCoinQuantity: number }) => {
41
+ const paymentHandler = createEventPromiseHandler<IPaymentEvent, typeof PaymentResultEventName>(
42
+ rewardsEmitter,
43
+ PaymentResultEventName
44
+ );
45
+ rewardsEmitter.emit(InvokePaymentEventName, type, {
46
+ userJoliCoin: params.userJoliCoin,
47
+ joliCoinQuantity: params.joliCoinQuantity,
48
+ enableAutoDeduct: params.userJoliCoin.enableAutoDeduct,
49
+ confirmButtonText: buttons.confirmButtonText,
50
+ cancelButtonText: buttons.cancelButtonText
51
+ });
52
+
53
+ // Wait for payment result
54
+ const paymentResult = await paymentHandler.getFreshData();
55
+ return paymentResult.paymentResult;
56
+ };
57
+ };
58
+
59
+ export const createShowUnlockWithJolicoinModal = (
60
+ type: 'JOLI_COIN' | 'ADS-JOLI_COIN',
61
+ buttons: {
62
+ confirmButtonText: string;
63
+ cancelButtonText: string;
64
+ }
65
+ ) => {
66
+ return async (params: { enableAutoDeduct: boolean; userJoliCoin: IJoliCoin; joliCoinQuantity: number }) => {
67
+ const modalHandler = createEventPromiseHandler<IUseModalResultEvent, typeof UseModalResultEventName>(
68
+ rewardsEmitter,
69
+ UseModalResultEventName
70
+ );
71
+ rewardsEmitter.emit(UseModalEventName, type, {
72
+ enableAutoDeduct: params.enableAutoDeduct,
73
+ userJoliCoin: params.userJoliCoin,
74
+ joliCoinQuantity: params.joliCoinQuantity,
75
+ confirmButtonText: buttons.confirmButtonText,
76
+ cancelButtonText: buttons.cancelButtonText
77
+ });
78
+ const modalResult = await modalHandler.getFreshData();
79
+ return modalResult.useModalResult;
80
+ };
81
+ };
82
+
83
+ const checkIfCancel = (result: IPaymentResult | IUseModalResult) => {
84
+ if (result == 'CANCEL') {
85
+ throw new Error('CANCEL');
86
+ }
87
+ };
88
+
89
+ export const createCommonJolicoinRewardHandler = (
90
+ httpClient: IHttpClient,
91
+ {
92
+ handlers: {
93
+ handleUnlockSuccess,
94
+ handleUnlockFailed,
95
+ unlockOptionsHandler,
96
+ initiateAndAwaitPayment,
97
+ showUnlockWithJolicoinModal
98
+ }
99
+ }: {
100
+ handlers: {
101
+ handleUnlockSuccess?: (data: { quantity: number; balance: number }) => void;
102
+ handleUnlockFailed?: (params: IAdBreakParams) => void;
103
+ unlockOptionsHandler: EventPromiseHandler<IUnlockOptionsEvent, typeof UnlockOptionsEventName>;
104
+ initiateAndAwaitPayment: ReturnType<typeof createInitiateAndAwaitPayment>;
105
+ showUnlockWithJolicoinModal: ReturnType<typeof createShowUnlockWithJolicoinModal>;
106
+ };
107
+ }
108
+ ) => {
109
+ return async (params: IAdBreakParams) => {
110
+ try {
111
+ let shouldSkipConfirm = false;
112
+ const unlockOptions = await unlockOptionsHandler.getData();
113
+ const joliCoinQuantity =
114
+ unlockOptions?.options?.find((option) => option.type === 'JOLI_COIN')?.joliCoinChoices[0]
115
+ .joliCoinQuantity ?? 0;
116
+ if (!joliCoinIsEnough(unlockOptions?.options || [], unlockOptions?.userJoliCoin)) {
117
+ const paymentResult = await initiateAndAwaitPayment({
118
+ userJoliCoin: unlockOptions?.userJoliCoin,
119
+ joliCoinQuantity
120
+ });
121
+
122
+ checkIfCancel(paymentResult);
123
+
124
+ if (paymentResult !== 'SUCCESS') {
125
+ handleUnlockFailed?.(params);
126
+ return false;
127
+ }
128
+
129
+ shouldSkipConfirm = true;
130
+ }
131
+
132
+ const enableAutoDeduct = !!unlockOptions?.userJoliCoin?.enableAutoDeduct;
133
+
134
+ if (!shouldSkipConfirm) {
135
+ const shouldUnlock = await showUnlockWithJolicoinModal({
136
+ enableAutoDeduct,
137
+ userJoliCoin: unlockOptions?.userJoliCoin,
138
+ joliCoinQuantity
139
+ });
140
+
141
+ checkIfCancel(shouldUnlock);
142
+ if (shouldUnlock !== 'CONFIRM') {
143
+ handleUnlockFailed?.(params);
144
+ return false;
145
+ }
146
+ }
147
+
148
+ const unlockWithJolicoin = await httpClient.post<IJolicoinUnlockRes>('/api/joli-coin/unlock', {
149
+ data: {
150
+ // TODO: support drama
151
+ type: 'GAME_REWARD',
152
+ reqId: `${uuidv4()}-${context.mpType}-${Date.now()}`,
153
+ gameInfo: {
154
+ gameId: context.mpId
155
+ }
156
+ }
157
+ });
158
+ if (unlockWithJolicoin.code == 'SUCCESS') {
159
+ params.adBreakDone?.({
160
+ breakType: params.type,
161
+ breakName: 'name' in params ? params.name ?? '' : '',
162
+ breakFormat: 'reward',
163
+ breakStatus: 'viewed'
164
+ });
165
+ if ('adViewed' in params) {
166
+ params.adViewed?.();
167
+ }
168
+ handleUnlockSuccess?.({
169
+ quantity: unlockWithJolicoin.data.quantity,
170
+ balance: unlockWithJolicoin.data.balance
171
+ });
172
+ return true;
173
+ }
174
+ handleUnlockFailed?.(params);
175
+ return false;
176
+ } catch (e) {
177
+ console.info(`JolicoinRewardHandler error:`, e);
178
+ if (e instanceof Error && e.message == 'CANCEL') {
179
+ // Cancel should terminate the reward handler, invoke unlock failed
180
+ throw e;
181
+ }
182
+ handleUnlockFailed?.(params);
183
+ return false;
184
+ } finally {
185
+ unlockOptionsHandler.clearCache();
186
+ }
187
+ };
188
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Creates an event promise handler for EventEmitter with tuple-based events
3
+ * @param emitter The event emitter
4
+ * @param eventName The event name to listen for
5
+ * @returns An object with methods to get and manage the event data
6
+ */
7
+ export function createEventPromiseHandler<T, E extends string = string>(
8
+ // Type 'emitter.on' to accept any type of listener callback
9
+ emitter: { on: (eventName: E, listener: (...args: any[]) => void) => void },
10
+ eventName: E
11
+ ) {
12
+ let dataPromise: Promise<T> | null = null;
13
+ let resolveData: ((value: T) => void) | null = null;
14
+ let cachedData: T | null = null;
15
+
16
+ const createDataPromise = () => {
17
+ if (cachedData) {
18
+ dataPromise = Promise.resolve(cachedData);
19
+ return;
20
+ }
21
+
22
+ dataPromise = new Promise<T>((resolve) => {
23
+ resolveData = resolve;
24
+ });
25
+ };
26
+
27
+ createDataPromise();
28
+
29
+ // The listener receives variable arguments
30
+ emitter.on(eventName, (...args: any[]) => {
31
+ // Get the first argument as the data
32
+ const data = args[0] as T;
33
+ cachedData = data;
34
+ if (resolveData) {
35
+ resolveData(data);
36
+ resolveData = null;
37
+ }
38
+ });
39
+
40
+ return {
41
+ getData: () => {
42
+ if (!dataPromise) {
43
+ createDataPromise();
44
+ }
45
+ return dataPromise as Promise<T>;
46
+ },
47
+
48
+ getCachedData: () => cachedData,
49
+
50
+ clearCache: () => {
51
+ cachedData = null;
52
+ dataPromise = null;
53
+ },
54
+
55
+ getFreshData: () => {
56
+ cachedData = null;
57
+ dataPromise = null;
58
+ createDataPromise();
59
+ return dataPromise!;
60
+ }
61
+ };
62
+ }
63
+
64
+ export type EventPromiseHandler<T, E extends string = string> = ReturnType<
65
+ typeof createEventPromiseHandler<T, E>
66
+ >;
@@ -9,3 +9,11 @@ export const canUseJolicoin = (unlockOptions: IUnlockOption[], joliCoin?: IJoliC
9
9
  )
10
10
  );
11
11
  };
12
+
13
+ export const joliCoinIsEnough = (unlockOptions: IUnlockOption[], joliCoin?: IJoliCoin) => {
14
+ return unlockOptions.some(
15
+ (option) =>
16
+ option.type === 'JOLI_COIN' &&
17
+ option.joliCoinChoices.some((choice) => choice.joliCoinQuantity <= (joliCoin?.balance ?? 0))
18
+ );
19
+ };