@jolibox/implement 1.1.20 → 1.1.21

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.
@@ -23,3 +23,8 @@ export declare const getStorageInfoSync: () => StandardResponse<{
23
23
  keys: string[];
24
24
  };
25
25
  }>;
26
+ export declare const getGlobalStorage: (...inputs: unknown[]) => Promise<StandardResponse<any>>;
27
+ export declare const setGlobalStorage: (...inputs: unknown[]) => Promise<StandardResponse<{
28
+ code: string;
29
+ message: string;
30
+ }>>;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * check can show useModal
3
+ * @param config
4
+ * @returns
5
+ */
6
+ export declare const checkUseModalFrequency: (config: {
7
+ dailyMaxPopUps: number;
8
+ minInterval: number;
9
+ }) => Promise<boolean>;
10
+ export declare const updateUseModalFrequency: () => Promise<void>;
11
+ /**
12
+ * check can show paymentModal
13
+ */
14
+ export declare const checkPaymentFrequency: (config: {
15
+ dailyMaxPopUps: number;
16
+ minInterval: number;
17
+ }) => Promise<boolean>;
18
+ /**
19
+ * update paymentFrequency
20
+ */
21
+ export declare const updatePaymentFrequency: () => Promise<void>;
@@ -0,0 +1 @@
1
+ export declare const filterTodayTimestamps: (timestamps: number[]) => number[];
@@ -1,9 +1,9 @@
1
1
  Invoking: npm run clean && npm run build:esm && tsc
2
2
 
3
- > @jolibox/implement@1.1.20 clean
3
+ > @jolibox/implement@1.1.21 clean
4
4
  > rimraf ./dist
5
5
 
6
6
 
7
- > @jolibox/implement@1.1.20 build:esm
7
+ > @jolibox/implement@1.1.21 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,15 +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.20",
4
+ "version": "1.1.21",
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.20",
10
- "@jolibox/types": "1.1.20",
11
- "@jolibox/native-bridge": "1.1.20",
12
- "@jolibox/ads": "1.1.20",
9
+ "@jolibox/common": "1.1.21",
10
+ "@jolibox/types": "1.1.21",
11
+ "@jolibox/native-bridge": "1.1.21",
12
+ "@jolibox/ads": "1.1.21",
13
13
  "localforage": "1.10.0",
14
14
  "@jolibox/ui": "1.0.0",
15
15
  "web-vitals": "4.2.4"
@@ -1,7 +1,14 @@
1
1
  import { IHttpClient } from '../http';
2
2
  import { RewardsHelper, RewardType } from './reward-helper';
3
3
  import { IJolicoinRewardOption } from './type';
4
- import { UnlockOptionsEventName, rewardsEmitter } from './reward-emitter';
4
+ import {
5
+ IUseModalFrequencyConfig,
6
+ UnlockOptionsEventName,
7
+ UseModalFrequencyEventName,
8
+ rewardsEmitter,
9
+ DefaltJoliCoinUseAndCharge
10
+ } from './reward-emitter';
11
+ import { StandardResponse } from '@jolibox/types';
5
12
 
6
13
  const priority = () => {
7
14
  return (a: RewardType, b: RewardType) => {
@@ -43,3 +50,22 @@ export const createRewardFetcher = (rewardsHelper: RewardsHelper) => {
43
50
  }
44
51
  });
45
52
  };
53
+
54
+ export const createRewardFrequencyConfigFetcher = (rewardsHelper: RewardsHelper) => {
55
+ rewardsHelper.registerRewardFrequencyConfigFetcher(async (httpClient: IHttpClient) => {
56
+ const res = await httpClient.get<StandardResponse<IUseModalFrequencyConfig>>(
57
+ '/api/fe-configs/web-common/global-config',
58
+ {}
59
+ );
60
+ if (res.code !== 'SUCCESS') {
61
+ return { joliCoinUseAndCharge: DefaltJoliCoinUseAndCharge };
62
+ }
63
+ rewardsEmitter.emit(UseModalFrequencyEventName, {
64
+ joliCoinUseAndCharge: res.data?.joliCoinUseAndCharge || DefaltJoliCoinUseAndCharge
65
+ });
66
+
67
+ return {
68
+ joliCoinUseAndCharge: res.data?.joliCoinUseAndCharge || DefaltJoliCoinUseAndCharge
69
+ };
70
+ });
71
+ };
@@ -1,8 +1,9 @@
1
1
  import { createRewardsHelper } from './reward-helper';
2
- import { createRewardFetcher } from './fetch-reward';
2
+ import { createRewardFetcher, createRewardFrequencyConfigFetcher } from './fetch-reward';
3
3
 
4
4
  export const rewardsHelper = createRewardsHelper();
5
5
  createRewardFetcher(rewardsHelper);
6
+ createRewardFrequencyConfigFetcher(rewardsHelper);
6
7
 
7
8
  export * from './registers/use-ads';
8
9
  export * from './registers/use-jolicoin';
@@ -14,6 +14,7 @@ export const InvokePaymentEventName = 'INVOKE_PAYMENT' as const;
14
14
  export const PaymentResultEventName = 'ON_PAYMENT_RESULT' as const;
15
15
  export const UseModalEventName = 'ON_USE_MODAL_EVENT' as const;
16
16
  export const UseModalResultEventName = 'ON_USE_MODAL_RESULT' as const;
17
+ export const UseModalFrequencyEventName = 'ON_USE_MODAL_FREQUENCY' as const;
17
18
 
18
19
  type IPaymentResult = 'SUCCESS' | 'FAILED' | 'CANCEL';
19
20
  export interface IPaymentEvent {
@@ -49,12 +50,26 @@ export interface IUseModalResultEvent {
49
50
  useModalResult: 'CONFIRM' | 'CANCEL' | 'FAILED';
50
51
  }
51
52
 
53
+ export interface IUseModalFrequencyConfig {
54
+ joliCoinUseAndCharge: {
55
+ useJolicoin: {
56
+ dailyMaxPopUps: number;
57
+ minInterval: number;
58
+ };
59
+ charge: {
60
+ dailyMaxPopUps: number;
61
+ minInterval: number;
62
+ };
63
+ };
64
+ }
65
+
52
66
  export interface RewardsEventMap extends Record<string, unknown[]> {
53
67
  [UnlockOptionsEventName]: [IUnlockOptionsEvent];
54
68
  [InvokePaymentEventName]: ['JOLI_COIN' | 'ADS-JOLI_COIN', IInvokePaymentEvent];
55
69
  [PaymentResultEventName]: [IPaymentEvent];
56
70
  [UseModalResultEventName]: [IUseModalResultEvent];
57
71
  [UseModalEventName]: ['JOLI_COIN' | 'ADS-JOLI_COIN', IUseModalEvent];
72
+ [UseModalFrequencyEventName]: [IUseModalFrequencyConfig];
58
73
  }
59
74
 
60
75
  export const originalRewardsEmitter = new EventEmitter<RewardsEventMap>();
@@ -77,3 +92,16 @@ export const createRewardsEmitterWrapper = (): RewardsEmitter => {
77
92
  };
78
93
 
79
94
  export const rewardsEmitter = createRewardsEmitterWrapper();
95
+
96
+ export const DefaltJoliCoinUseAndCharge = {
97
+ // 充值弹窗:
98
+ charge: {
99
+ dailyMaxPopUps: 2, // 一天内最多2次弹窗
100
+ minInterval: 8 * 60 * 60 * 1000 // 同一用户若在8小时内刚点过弹窗,则不再弹出
101
+ },
102
+ // 使用弹窗:
103
+ useJolicoin: {
104
+ dailyMaxPopUps: 2,
105
+ minInterval: 8 * 60 * 60 * 1000
106
+ }
107
+ };
@@ -2,6 +2,7 @@ export type RewardType = 'ADS' | 'JOLI_COIN' | 'JOLI_COIN_ONLY';
2
2
 
3
3
  import { context } from '../context';
4
4
  import type { AdsRewardsHandler } from './registers/use-ads';
5
+ import { IUseModalFrequencyConfig, DefaltJoliCoinUseAndCharge } from './reward-emitter';
5
6
 
6
7
  export interface RewardHandlerMap {
7
8
  ADS: AdsRewardsHandler;
@@ -14,7 +15,9 @@ export type RewardHandler<T extends RewardType> = RewardHandlerMap[T];
14
15
  const isTestMode = context.testMode;
15
16
  export function createRewardsHelper() {
16
17
  const rewardsHandlers = new Map<RewardType, RewardHandler<any>>();
18
+
17
19
  let rewardFetcher: ((...args: any[]) => Promise<RewardType[]>) | undefined;
20
+ let rewardFrequencyConfigFetcher: ((...args: any[]) => Promise<IUseModalFrequencyConfig>) | undefined;
18
21
 
19
22
  return {
20
23
  registerRewardHandler<T extends RewardType>(type: T, handler: RewardHandler<T>) {
@@ -45,11 +48,31 @@ export function createRewardsHelper() {
45
48
  }
46
49
  };
47
50
  },
51
+ async registerRewardFrequencyConfigFetcher(
52
+ fetcher: (...args: any[]) => Promise<IUseModalFrequencyConfig>
53
+ ) {
54
+ rewardFrequencyConfigFetcher = async (...args: unknown[]) => {
55
+ try {
56
+ const config = await fetcher(...args);
57
+ return config;
58
+ } catch (e) {
59
+ console.error(`getRewardOptions error:`, e);
60
+ return { joliCoinUseAndCharge: DefaltJoliCoinUseAndCharge };
61
+ }
62
+ };
63
+ },
64
+
48
65
  async getRewardsTypes(...args: unknown[]): Promise<RewardType[]> {
49
66
  if (!rewardFetcher) {
50
67
  return ['ADS'];
51
68
  }
52
69
  return await rewardFetcher(...args);
70
+ },
71
+ async getRewardFrequencyConfig(...args: unknown[]): Promise<IUseModalFrequencyConfig> {
72
+ if (!rewardFrequencyConfigFetcher) {
73
+ return { joliCoinUseAndCharge: DefaltJoliCoinUseAndCharge };
74
+ }
75
+ return await rewardFrequencyConfigFetcher(...args);
53
76
  }
54
77
  };
55
78
  }
@@ -76,6 +76,10 @@ const adsContext: IAdsContext<'GAME'> = {
76
76
 
77
77
  const ads = new JoliboxAdsForGame(adsContext);
78
78
 
79
+ rewardsHelper.getRewardFrequencyConfig(httpClient).then((config) => {
80
+ console.log('getRewardFrequencyConfig', config);
81
+ });
82
+
79
83
  rewardsHelper.registerRewardHandler('ADS', createAdsRewardHandler(ads));
80
84
  rewardsHelper.registerRewardHandler(
81
85
  'JOLI_COIN',
@@ -2,6 +2,7 @@ import { createCommands, hostEmitter, UserCustomError } from '@jolibox/common';
2
2
  import { invokeNative } from '@jolibox/native-bridge';
3
3
  import { createSyncAPI, t, registerCanIUse } from './base';
4
4
  import { context } from '@/common/context';
5
+ import { reportError } from '@/common/report/errors/report';
5
6
 
6
7
  const commands = createCommands();
7
8
 
@@ -53,10 +54,26 @@ const interceptSystemExitSync = createSyncAPI('interceptSystemExitSync', {
53
54
  }
54
55
  });
55
56
 
57
+ const navigateToNativePage = createSyncAPI('navigateToNativePage', {
58
+ paramsSchema: t.tuple(t.enum('openHistory'), t.object()),
59
+ implement: (path, params) => {
60
+ const { errNo, errMsg } = invokeNative('callHostMethodAsync', {
61
+ method: path,
62
+ params: {
63
+ ...params
64
+ }
65
+ });
66
+ if (errNo !== 0) {
67
+ throw new UserCustomError(errMsg, errNo);
68
+ }
69
+ }
70
+ });
71
+
56
72
  commands.registerCommand('RouterSDK.openSchema', openSchemaSync);
57
73
  commands.registerCommand('RouterSDK.openPage', openPageSync);
58
74
  commands.registerCommand('RouterSDK.closePage', closePageSync);
59
75
  commands.registerCommand('RouterSDK.interceptSystemExit', interceptSystemExitSync);
76
+ commands.registerCommand('RouterSDK.navigateToNativePage', navigateToNativePage);
60
77
 
61
78
  registerCanIUse('router.openSchema', {
62
79
  version: '1.1.10'
@@ -73,3 +90,7 @@ registerCanIUse('router.closePage', {
73
90
  registerCanIUse('router.interceptSystemExit', {
74
91
  version: '1.1.18'
75
92
  });
93
+
94
+ registerCanIUse('router.navigateToNativePage', {
95
+ version: '1.1.19'
96
+ });
@@ -285,3 +285,48 @@ function stringify(data: unknown, type: DataType) {
285
285
  return '';
286
286
  }
287
287
  }
288
+
289
+ export const getGlobalStorage = createAPI('getGlobalStorage', {
290
+ paramsSchema: t.tuple(t.string()),
291
+ async implement(key) {
292
+ try {
293
+ const { data, dataType } = await applyNative('getGlobalStorageAsync', { key });
294
+ const parsedData = parse(data, dataType);
295
+ return {
296
+ code: 'SUCCESS',
297
+ message: 'success',
298
+ data: parsedData
299
+ };
300
+ } catch (error) {
301
+ return {
302
+ code: 'INTERNAL_ERROR',
303
+ message: '[Jolibox SDK] get global storage failed',
304
+ data: null
305
+ };
306
+ }
307
+ }
308
+ });
309
+
310
+ export const setGlobalStorage = createAPI('setGlobalStorage', {
311
+ paramsSchema: t.tuple(t.string(), t.or(t.string(), t.boolean(), t.number())),
312
+ async implement(key, value) {
313
+ try {
314
+ const dataType = getType(value);
315
+ const data = stringify(value, dataType);
316
+ await applyNative('setGlobalStorageAsync', {
317
+ key,
318
+ data,
319
+ dataType
320
+ });
321
+ return {
322
+ code: 'SUCCESS',
323
+ message: 'success'
324
+ };
325
+ } catch (error) {
326
+ return {
327
+ code: 'INTERNAL_ERROR',
328
+ message: '[Jolibox SDK] set global storage failed'
329
+ };
330
+ }
331
+ }
332
+ });
@@ -1,7 +1,17 @@
1
1
  export function initializeNativeEnv() {
2
2
  let styleElement: HTMLStyleElement | null = null;
3
+ let viewportMeta: HTMLMetaElement | null = null;
3
4
  const originalFetch = window.fetch;
4
5
  const createStyles = () => {
6
+ // 创建并插入viewport meta标签
7
+ viewportMeta = document.createElement('meta');
8
+ viewportMeta.setAttribute('name', 'viewport');
9
+ viewportMeta.setAttribute(
10
+ 'content',
11
+ 'viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no'
12
+ );
13
+ viewportMeta.setAttribute('data-jolibox-sdk', 'viewport-meta');
14
+
5
15
  styleElement = document.createElement('style');
6
16
  styleElement.setAttribute('data-jolibox-sdk', 'default-styles');
7
17
 
@@ -28,6 +38,8 @@ export function initializeNativeEnv() {
28
38
  styleElement.textContent = styles;
29
39
 
30
40
  const head = document.head || document.getElementsByTagName('head')[0];
41
+ // 插入viewport meta标签
42
+ head.insertBefore(viewportMeta, head.firstChild);
31
43
  head.insertBefore(styleElement, head.firstChild);
32
44
  document.documentElement.setAttribute('data-jolibox-root', '');
33
45
  };
@@ -72,6 +84,11 @@ export function initializeNativeEnv() {
72
84
  styleElement.parentNode.removeChild(styleElement);
73
85
  styleElement = null;
74
86
  }
87
+ // 清理viewport meta标签
88
+ if (viewportMeta?.parentNode) {
89
+ viewportMeta.parentNode.removeChild(viewportMeta);
90
+ viewportMeta = null;
91
+ }
75
92
  document.documentElement.removeAttribute('data-jolibox-root');
76
93
 
77
94
  window.fetch = originalFetch;
@@ -0,0 +1,70 @@
1
+ import { StandardResponse } from '@jolibox/types';
2
+ import { getGlobalStorage, setGlobalStorage } from '../api/storage';
3
+ import { filterTodayTimestamps } from './utils';
4
+
5
+ /**
6
+ * check can show useModal
7
+ * @param config
8
+ * @returns
9
+ */
10
+ export const checkUseModalFrequency = async (config: { dailyMaxPopUps: number; minInterval: number }) => {
11
+ const { dailyMaxPopUps, minInterval } = config;
12
+ const res = (await getGlobalStorage('joli_coin_use_modal_frequency')) as StandardResponse<number[]>;
13
+ if (!res.data) {
14
+ return true;
15
+ }
16
+ const fequeycies = res.data;
17
+ const todayFequencies = filterTodayTimestamps(fequeycies);
18
+ if (todayFequencies.length >= dailyMaxPopUps) {
19
+ return false;
20
+ }
21
+ const now = Date.now();
22
+ if (now - todayFequencies[todayFequencies.length - 1] < minInterval) {
23
+ return false;
24
+ }
25
+ return true;
26
+ };
27
+
28
+ export const updateUseModalFrequency = async () => {
29
+ const now = Date.now();
30
+ const res = (await getGlobalStorage('joli_coin_use_modal_frequency')) as StandardResponse<number[]>;
31
+
32
+ const fequeycies = res.data ?? [];
33
+ fequeycies.push(now);
34
+ fequeycies.sort((a, b) => b - a).splice(8);
35
+ await setGlobalStorage('joli_coin_use_modal_frequency', fequeycies);
36
+ };
37
+
38
+ /**
39
+ * check can show paymentModal
40
+ */
41
+ export const checkPaymentFrequency = async (config: { dailyMaxPopUps: number; minInterval: number }) => {
42
+ const { dailyMaxPopUps, minInterval } = config;
43
+ const res = (await getGlobalStorage('joli_coin_payment_frequency')) as StandardResponse<number[]>;
44
+
45
+ if (!res.data) {
46
+ return true;
47
+ }
48
+ const fequeycies = res.data;
49
+ const todayFequencies = filterTodayTimestamps(fequeycies);
50
+ if (todayFequencies.length >= dailyMaxPopUps) {
51
+ return false;
52
+ }
53
+ const now = Date.now();
54
+ if (now - todayFequencies[todayFequencies.length - 1] < minInterval) {
55
+ return false;
56
+ }
57
+ return true;
58
+ };
59
+
60
+ /**
61
+ * update paymentFrequency
62
+ */
63
+ export const updatePaymentFrequency = async () => {
64
+ const now = Date.now();
65
+ const res = (await getGlobalStorage('joli_coin_payment_frequency')) as StandardResponse<number[]>;
66
+ const frequencies = res.data ?? [];
67
+ frequencies.push(now);
68
+ frequencies.sort((a, b) => b - a).splice(8);
69
+ await setGlobalStorage('joli_coin_payment_frequency', frequencies);
70
+ };
@@ -10,7 +10,9 @@ import {
10
10
  UseModalResultEventName,
11
11
  InvokePaymentEventName,
12
12
  IInvokePaymentEvent,
13
- IPaymentChoice
13
+ IPaymentChoice,
14
+ UseModalFrequencyEventName,
15
+ IUseModalFrequencyConfig
14
16
  } from '@/common/rewards/reward-emitter';
15
17
  import { createConfirmJolicoinModal, createPaymentJolicoinModal } from '@jolibox/ui';
16
18
  import { innerFetch as fetch } from '../network';
@@ -18,13 +20,34 @@ import { StandardResponse } from '@jolibox/types';
18
20
  import { context } from '@/common/context';
19
21
  import { login } from '../api/login';
20
22
 
23
+ import { createEventPromiseHandler } from '@/common/rewards/registers/utils/event-listener';
24
+ import {
25
+ checkUseModalFrequency,
26
+ updateUseModalFrequency,
27
+ checkPaymentFrequency,
28
+ updatePaymentFrequency
29
+ } from './check-frequency';
30
+ const modalUseFrequencyConfig = createEventPromiseHandler<
31
+ IUseModalFrequencyConfig,
32
+ typeof UseModalFrequencyEventName
33
+ >(rewardsEmitter, UseModalFrequencyEventName);
34
+
35
+ modalUseFrequencyConfig.getData();
36
+
21
37
  /**
22
38
  * confirm jolicoin modal
23
39
  */
24
- rewardsEmitter.on(UseModalEventName, (type: 'JOLI_COIN' | 'ADS-JOLI_COIN', params: IUseModalEvent) => {
40
+ rewardsEmitter.on(UseModalEventName, async (type: 'JOLI_COIN' | 'ADS-JOLI_COIN', params: IUseModalEvent) => {
25
41
  if (type === 'ADS-JOLI_COIN') {
26
42
  //TODO
27
43
  console.log('use modal show by frequency');
44
+ const config = await modalUseFrequencyConfig.getData();
45
+ const canShowUseModal = await checkUseModalFrequency(config.joliCoinUseAndCharge.useJolicoin);
46
+ if (!canShowUseModal) {
47
+ // return by frequency control
48
+ rewardsEmitter.emit(UseModalResultEventName, { useModalResult: 'CONFIRM' });
49
+ return;
50
+ }
28
51
  }
29
52
  const modal = createConfirmJolicoinModal({
30
53
  data: {
@@ -51,6 +74,8 @@ rewardsEmitter.on(UseModalEventName, (type: 'JOLI_COIN' | 'ADS-JOLI_COIN', param
51
74
  }
52
75
  }
53
76
  });
77
+
78
+ await updateUseModalFrequency();
54
79
  });
55
80
 
56
81
  /**
@@ -63,6 +88,13 @@ rewardsEmitter.on(
63
88
  // handle showup frequecy
64
89
  if (type === 'ADS-JOLI_COIN') {
65
90
  //
91
+ const config = await modalUseFrequencyConfig.getData();
92
+ const canShowPaymentModal = await checkPaymentFrequency(config.joliCoinUseAndCharge.charge);
93
+ if (!canShowPaymentModal) {
94
+ // return by frequency control
95
+ rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'SUCCESS' });
96
+ return;
97
+ }
66
98
  console.log('show by frequency');
67
99
  }
68
100
 
@@ -107,6 +139,8 @@ rewardsEmitter.on(
107
139
  }
108
140
  }
109
141
  });
142
+
143
+ await updatePaymentFrequency();
110
144
  } catch (error) {
111
145
  console.info('payment failed', error);
112
146
  rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'FAILED' });
@@ -0,0 +1,7 @@
1
+ export const filterTodayTimestamps = (timestamps: number[]): number[] => {
2
+ const now = new Date();
3
+ const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
4
+ const todayEnd = todayStart + 24 * 60 * 60 * 1000;
5
+
6
+ return timestamps.filter((timestamp) => timestamp >= todayStart && timestamp < todayEnd);
7
+ };