@jolibox/implement 1.1.10 → 1.1.11-beta.10
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 +22 -18
- package/.rush/temp/shrinkwrap-deps.json +1 -1
- package/dist/common/ads/anti-cheating.d.ts +24 -51
- package/dist/common/ads/anti-cheating.test.d.ts +1 -0
- package/dist/common/ads/index.d.ts +4 -9
- package/dist/common/context/index.d.ts +4 -0
- package/dist/common/context/url-parse.d.ts +8 -1
- package/dist/index.js +3 -3
- package/dist/index.native.js +110 -4
- package/dist/native/api/index.d.ts +1 -0
- package/dist/native/api/navigate.d.ts +1 -0
- package/dist/native/bootstrap/bridge.d.ts +1 -1
- package/dist/native/js-bridge/const.d.ts +1 -0
- package/dist/native/js-bridge/publish.d.ts +1 -0
- package/dist/native/js-bridge/subscribe.d.ts +2 -0
- package/dist/native/js-bridge/types.d.ts +4 -0
- package/dist/native/js-core/jolibox-js-core.d.ts +8 -3
- package/dist/native/ui/retention.d.ts +1 -0
- package/implement.build.log +2 -2
- package/package.json +4 -3
- package/src/common/ads/anti-cheating.test.ts +79 -0
- package/src/common/ads/anti-cheating.ts +228 -139
- package/src/common/ads/index.ts +33 -20
- package/src/common/context/index.ts +19 -3
- package/src/common/context/url-parse.ts +24 -1
- package/src/native/api/ads.ts +7 -0
- package/src/native/api/index.ts +1 -0
- package/src/native/api/lifecycle.ts +16 -4
- package/src/native/api/navigate.ts +61 -0
- package/src/native/bootstrap/bridge.ts +10 -1
- package/src/native/bootstrap/index.ts +32 -2
- package/src/native/js-bridge/const.ts +2 -0
- package/src/native/js-bridge/js-bridge.ts +7 -2
- package/src/native/js-bridge/publish.ts +44 -0
- package/src/native/js-bridge/subscribe.ts +25 -1
- package/src/native/js-bridge/types.ts +10 -0
- package/src/native/js-core/jolibox-js-core.ts +30 -26
- package/src/native/types/global.d.ts +1 -0
- package/src/native/types/native-method-map.d.ts +29 -0
- package/src/native/ui/retention.ts +152 -0
|
@@ -2,6 +2,7 @@ export type Listener = (...args: any[]) => any;
|
|
|
2
2
|
export type On = <T extends string>(event: T, handler: Listener) => void;
|
|
3
3
|
export type Off = <T extends string>(event: T, handler: Listener) => void;
|
|
4
4
|
export type InvokeHandler = (callbackId: string | number, data: string | Record<string, unknown>) => void;
|
|
5
|
+
export type Publish = (event: string, data: Record<string, unknown>, webviewId?: number, force?: boolean) => void;
|
|
5
6
|
export type SubscribeHandler = (event: string, data: string | Record<string, unknown>, webviewId?: number) => void;
|
|
6
7
|
export type AnyFunction = (...args: any[]) => any;
|
|
7
8
|
export interface JSBridge {
|
|
@@ -11,4 +12,7 @@ export interface JSBridge {
|
|
|
11
12
|
applyNative: jsb.service.ApplyNative;
|
|
12
13
|
onNative: jsb.service.OnNative;
|
|
13
14
|
offNative: jsb.service.OffNative;
|
|
15
|
+
publish: Publish;
|
|
16
|
+
subscribe: On;
|
|
17
|
+
unsubscribe: Off;
|
|
14
18
|
}
|
|
@@ -5,6 +5,11 @@ declare global {
|
|
|
5
5
|
invoke: (invokeString: string) => void;
|
|
6
6
|
onDocumentReady: (paramsstr: string) => void;
|
|
7
7
|
doExit: (uuidString: string) => void;
|
|
8
|
+
publish: (params: {
|
|
9
|
+
event: string;
|
|
10
|
+
webviewIds: number[] | '*';
|
|
11
|
+
paramsString: string;
|
|
12
|
+
}) => void;
|
|
8
13
|
};
|
|
9
14
|
webkit?: {
|
|
10
15
|
messageHandlers: {
|
|
@@ -18,7 +23,7 @@ declare global {
|
|
|
18
23
|
publish: {
|
|
19
24
|
postMessage: (params: {
|
|
20
25
|
event: string;
|
|
21
|
-
webviewIds:
|
|
26
|
+
webviewIds: number[] | '*';
|
|
22
27
|
paramsString: string;
|
|
23
28
|
}) => void;
|
|
24
29
|
};
|
|
@@ -39,7 +44,7 @@ declare global {
|
|
|
39
44
|
}
|
|
40
45
|
export declare const RuntimeLoader: {
|
|
41
46
|
trigger(): void;
|
|
42
|
-
exit(): boolean
|
|
47
|
+
exit(): Promise<boolean>;
|
|
43
48
|
onReady(fn: () => void): void;
|
|
44
|
-
doExit(fn: () => boolean): void;
|
|
49
|
+
doExit(fn: () => Promise<boolean>): void;
|
|
45
50
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function openRetentionSchema(): Promise<boolean>;
|
package/implement.build.log
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Invoking: npm run clean && npm run build:esm && tsc
|
|
2
2
|
|
|
3
|
-
> @jolibox/implement@1.1.10 clean
|
|
3
|
+
> @jolibox/implement@1.1.11-beta.10 clean
|
|
4
4
|
> rimraf ./dist
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
> @jolibox/implement@1.1.10 build:esm
|
|
7
|
+
> @jolibox/implement@1.1.11-beta.10 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.10",
|
|
4
|
+
"version": "1.1.11-beta.10",
|
|
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.10",
|
|
10
|
-
"@jolibox/types": "1.1.10",
|
|
9
|
+
"@jolibox/common": "1.1.11-beta.10",
|
|
10
|
+
"@jolibox/types": "1.1.11-beta.10",
|
|
11
11
|
"localforage": "1.10.0",
|
|
12
|
+
"@jolibox/ui": "1.0.0",
|
|
12
13
|
"web-vitals": "4.2.4"
|
|
13
14
|
},
|
|
14
15
|
"devDependencies": {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { AdsAntiCheating, IAdsAntiCheatingConfig } from './anti-cheating';
|
|
2
|
+
|
|
3
|
+
describe('createLeadingNotifiableDebounce', () => {
|
|
4
|
+
jest.setTimeout(3000); // increase timeout for tes
|
|
5
|
+
|
|
6
|
+
const track = jest.fn() as any;
|
|
7
|
+
const http = {
|
|
8
|
+
get: jest.fn(),
|
|
9
|
+
post: jest.fn()
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const config: IAdsAntiCheatingConfig = {
|
|
13
|
+
initialThreshold: 50,
|
|
14
|
+
highFreqThreshold: 50,
|
|
15
|
+
maxAllowedAdsForTime: 2,
|
|
16
|
+
banForTimeThreshold: 500, // 500ms
|
|
17
|
+
bannedReleaseTime: 500, // 500ms
|
|
18
|
+
banForSessionThreshold: 3000, // 4s
|
|
19
|
+
maxAllowedAdsForSession: 7 // 7 ads
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
it('should ban initial call', async () => {
|
|
23
|
+
localStorage.clear();
|
|
24
|
+
|
|
25
|
+
const adsAntiCheating = new AdsAntiCheating(track, http, () => true, config);
|
|
26
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
27
|
+
const a = adsAntiCheating.checkAdsDisplayPermission('reward');
|
|
28
|
+
expect(a.reason).toBe('BLOCK_INITIAL');
|
|
29
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
30
|
+
const b = adsAntiCheating.checkAdsDisplayPermission('reward');
|
|
31
|
+
expect(b.reason).toBe('ALLOWED');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should ban continous calling', async () => {
|
|
35
|
+
localStorage.clear();
|
|
36
|
+
|
|
37
|
+
const adsAntiCheating = new AdsAntiCheating(track, http, () => true, config);
|
|
38
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
39
|
+
const a = adsAntiCheating.checkAdsDisplayPermission('reward');
|
|
40
|
+
expect(a.reason).toBe('ALLOWED');
|
|
41
|
+
|
|
42
|
+
// wait 10 ms
|
|
43
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
44
|
+
const b = adsAntiCheating.checkAdsDisplayPermission('reward');
|
|
45
|
+
expect(b.reason).toBe('HOLDING_HIGH_FREQ');
|
|
46
|
+
expect(b.info?.count).toBe(1);
|
|
47
|
+
|
|
48
|
+
// wait another 10 ms
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
50
|
+
const d = adsAntiCheating.checkAdsDisplayPermission('reward');
|
|
51
|
+
expect(d.reason).toBe('HOLDING_HIGH_FREQ');
|
|
52
|
+
expect(d.info?.count).toBe(2);
|
|
53
|
+
|
|
54
|
+
// wait another 100ms to reset the high freq timer, but trigger banned for time
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, 60)); // wait 100ms
|
|
56
|
+
const c = adsAntiCheating.checkAdsDisplayPermission('reward');
|
|
57
|
+
expect(c.reason).toBe('BANNED_FOR_TIME');
|
|
58
|
+
|
|
59
|
+
// wait another 100ms
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, 100)); // wait 100ms
|
|
61
|
+
const e = adsAntiCheating.checkAdsDisplayPermission('reward');
|
|
62
|
+
expect(e.reason).toBe('WAITING_BANNED_RELEASE');
|
|
63
|
+
const isBannedForTime1 = window.localStorage.getItem('jolibox-sdk-ads-ban-for-time');
|
|
64
|
+
expect(isBannedForTime1).toBe('true');
|
|
65
|
+
|
|
66
|
+
// wait another 400ms to reset the banned for time timer and allow display
|
|
67
|
+
await new Promise((resolve) => setTimeout(resolve, 400)); // wait 400ms
|
|
68
|
+
const f = adsAntiCheating.checkAdsDisplayPermission('reward');
|
|
69
|
+
expect(f.reason).toBe('ALLOWED');
|
|
70
|
+
|
|
71
|
+
// 500ms
|
|
72
|
+
await new Promise((resolve) => setTimeout(resolve, 500)); // wait 500ms
|
|
73
|
+
const g = adsAntiCheating.checkAdsDisplayPermission('reward');
|
|
74
|
+
expect(g.reason).toBe('BANNED_FOR_SESSION');
|
|
75
|
+
|
|
76
|
+
const isBannedForTime = window.localStorage.getItem('jolibox-sdk-ads-ban-for-time');
|
|
77
|
+
expect(isBannedForTime).toBe('false'); // should be false after the timer resets
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import type { IHttpClient } from '../http';
|
|
2
|
+
import type { Track } from '../report';
|
|
3
|
+
|
|
1
4
|
export type AdsDisplayPermission =
|
|
2
5
|
| 'BLOCK_INITIAL'
|
|
3
6
|
| 'BANNED_FOR_SESSION'
|
|
7
|
+
| 'HOLDING_HIGH_FREQ'
|
|
4
8
|
| 'NETWORK_NOT_OK'
|
|
5
9
|
| 'WAITING_BANNED_RELEASE'
|
|
6
10
|
| 'BANNED_FOR_TIME'
|
|
@@ -10,233 +14,318 @@ interface CallAdsHistory {
|
|
|
10
14
|
type: 'reward' | 'interstitial';
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
interface IAdsAntiCheatingConfig {
|
|
17
|
+
export interface IAdsAntiCheatingConfig {
|
|
16
18
|
maxAllowedAdsForTime?: number; // default 8
|
|
17
|
-
|
|
18
|
-
bannedReleaseTime?: number; // default 60000 (1 minute), must be >=
|
|
19
|
+
banForTimeThreshold?: number; // default 60000 (1 minute)
|
|
20
|
+
bannedReleaseTime?: number; // default 60000 (1 minute), must be >= checkingTimeThreshold
|
|
19
21
|
initialThreshold?: number; // default 2000
|
|
20
22
|
maxAllowedAdsForSession?: number; // default 200
|
|
21
|
-
|
|
23
|
+
banForSessionThreshold?: number; // default 600000 (10 minutes)
|
|
24
|
+
highFreqThreshold?: number; // default 2000 (2s)
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export class AdsAntiCheating {
|
|
25
|
-
private
|
|
26
|
-
private
|
|
27
|
-
private
|
|
28
|
-
private
|
|
29
|
-
private releaseBannedForTimeTimeout: number | null = null;
|
|
30
|
-
|
|
31
|
-
private maxAllowedAdsForSession = 200;
|
|
32
|
-
private bannedForSessionThreshold = 600000;
|
|
33
|
-
private isBanningForSession = false;
|
|
34
|
-
|
|
35
|
-
private initialThreshold = 2000;
|
|
36
|
-
|
|
37
|
-
private _callAdsTimestampsForTime: CallAdsHistory[] = []; // max x timestamps for time
|
|
38
|
-
private callAdsTimestampsForSession: CallAdsHistory[] = []; // max x timestamps for session
|
|
39
|
-
|
|
40
|
-
private initTimestamp = 0;
|
|
41
|
-
// private context: JoliboxSDKPipeExecutor;
|
|
28
|
+
private checkShouldBanInitial: ReturnType<typeof createInitialStrategy>;
|
|
29
|
+
private checkShouldBanHighFreq: ReturnType<typeof createHighFreqStrategy>;
|
|
30
|
+
private checkShouldBanForTime: ReturnType<typeof createBanForTimeStrategy>;
|
|
31
|
+
private checkShouldBanForSession: ReturnType<typeof createBanForSessionStrategy>;
|
|
42
32
|
|
|
43
33
|
constructor(
|
|
44
|
-
|
|
34
|
+
readonly track: Track,
|
|
35
|
+
readonly httpClient: IHttpClient,
|
|
45
36
|
readonly checkNetwork: () => boolean,
|
|
46
37
|
config?: IAdsAntiCheatingConfig
|
|
47
38
|
) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
const maxAllowedAdsForTime = config?.maxAllowedAdsForTime ?? 8;
|
|
40
|
+
const banForTimeThreshold = config?.banForTimeThreshold ?? 60000;
|
|
41
|
+
const bannedReleaseTime = config?.bannedReleaseTime ?? 60000;
|
|
42
|
+
|
|
43
|
+
const maxAllowedAdsForSession = config?.maxAllowedAdsForSession ?? 200;
|
|
44
|
+
const banForSessionThreshold = config?.banForSessionThreshold ?? 600000;
|
|
45
|
+
|
|
46
|
+
const initialThreshold = config?.initialThreshold ?? 2000;
|
|
47
|
+
const highFreqThreshold = config?.highFreqThreshold ?? 2000;
|
|
48
|
+
|
|
49
|
+
this.checkShouldBanInitial = createInitialStrategy(initialThreshold);
|
|
50
|
+
this.checkShouldBanHighFreq = createHighFreqStrategy(highFreqThreshold);
|
|
51
|
+
this.checkShouldBanForTime = createBanForTimeStrategy(
|
|
52
|
+
maxAllowedAdsForTime,
|
|
53
|
+
banForTimeThreshold,
|
|
54
|
+
bannedReleaseTime
|
|
55
|
+
);
|
|
56
|
+
this.checkShouldBanForSession = createBanForSessionStrategy(
|
|
57
|
+
maxAllowedAdsForSession,
|
|
58
|
+
banForSessionThreshold
|
|
59
|
+
);
|
|
60
|
+
}
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
public checkAdsDisplayPermission: (type: 'reward' | 'interstitial') => {
|
|
63
|
+
reason: AdsDisplayPermission;
|
|
64
|
+
info?: { count: number };
|
|
65
|
+
} = (type) => {
|
|
66
|
+
if (!this.checkNetwork()) {
|
|
67
|
+
return { reason: 'NETWORK_NOT_OK' };
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
const shouldBanInitial = this.checkShouldBanInitial();
|
|
71
|
+
if (shouldBanInitial) {
|
|
72
|
+
return { reason: shouldBanInitial };
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
const shouldBanForSession = this.checkShouldBanForSession(type);
|
|
76
|
+
if (shouldBanForSession) {
|
|
77
|
+
return { reason: shouldBanForSession };
|
|
67
78
|
}
|
|
68
79
|
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
const shouldBanForHighFreq = this.checkShouldBanHighFreq(type);
|
|
81
|
+
if (shouldBanForHighFreq) {
|
|
82
|
+
return shouldBanForHighFreq;
|
|
71
83
|
}
|
|
72
84
|
|
|
73
|
-
this.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Restore call ads timestamps for time from local storage
|
|
80
|
-
*/
|
|
81
|
-
private initCallAdsTimestampsForTime = () => {
|
|
82
|
-
try {
|
|
83
|
-
const fromStorage = JSON.parse(window.localStorage.getItem(TIMESTAMP_STORAGE_KEY) ?? '[]');
|
|
84
|
-
if (Array.isArray(fromStorage)) {
|
|
85
|
-
this._callAdsTimestampsForTime = fromStorage;
|
|
86
|
-
} else {
|
|
87
|
-
this._callAdsTimestampsForTime = [];
|
|
88
|
-
}
|
|
89
|
-
} catch {
|
|
90
|
-
this._callAdsTimestampsForTime = [];
|
|
85
|
+
const shouldBanForTime = this.checkShouldBanForTime(type);
|
|
86
|
+
if (shouldBanForTime) {
|
|
87
|
+
return { reason: shouldBanForTime };
|
|
91
88
|
}
|
|
92
|
-
};
|
|
93
89
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
*/
|
|
97
|
-
private get callAdsTimestampsForTime() {
|
|
98
|
-
return this._callAdsTimestampsForTime;
|
|
99
|
-
}
|
|
90
|
+
return { reason: 'ALLOWED' };
|
|
91
|
+
};
|
|
100
92
|
|
|
101
93
|
/**
|
|
102
|
-
*
|
|
94
|
+
* Report an ad display permission issue.
|
|
95
|
+
* Report to both event tracking system and the business backend system.
|
|
96
|
+
* @param reason
|
|
103
97
|
*/
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
98
|
+
public report(reason: AdsDisplayPermission, count?: number) {
|
|
99
|
+
const info = count ? { reason, count } : { reason };
|
|
100
|
+
this.track('PreventAdsCheating', info);
|
|
101
|
+
this.httpClient.post('/api/base/app-event', {
|
|
102
|
+
data: {
|
|
103
|
+
eventType: 'PREVENT_ADS_CHEATING',
|
|
104
|
+
adsInfo: info
|
|
105
|
+
}
|
|
106
|
+
});
|
|
111
107
|
}
|
|
108
|
+
}
|
|
112
109
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
private setReleaseBannedForTime = () => {
|
|
117
|
-
if (this.releaseBannedForTimeTimeout) {
|
|
118
|
-
window.clearTimeout(this.releaseBannedForTimeTimeout);
|
|
119
|
-
this.releaseBannedForTimeTimeout = null;
|
|
120
|
-
}
|
|
121
|
-
this.releaseBannedForTimeTimeout = window.setTimeout(() => {
|
|
122
|
-
this.isBanningForTime = false;
|
|
123
|
-
this.releaseBannedForTimeTimeout = null;
|
|
124
|
-
}, this.bannedReleaseTime);
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Check if we should ban for initial call. By default, if user makes a call less than 2 seconds after the first call, block.
|
|
129
|
-
* @returns
|
|
130
|
-
*/
|
|
131
|
-
private checkShouldBannedInitial = () => {
|
|
132
|
-
const now = Date.now();
|
|
133
|
-
const diffFromInit = now - this.initTimestamp;
|
|
134
|
-
// if initial call less than x seconds, block
|
|
135
|
-
if (diffFromInit <= this.initialThreshold) {
|
|
136
|
-
return 'BLOCK_INITIAL';
|
|
137
|
-
}
|
|
138
|
-
};
|
|
110
|
+
const createBanForSessionStrategy = (maxAllowedAdsForSession: number, banForSessionThreshold: number) => {
|
|
111
|
+
let isBannedForSession = false;
|
|
112
|
+
let callAdsTimestampsForSession: CallAdsHistory[] = [];
|
|
139
113
|
|
|
140
114
|
/**
|
|
141
115
|
* Check if we should ban this user for the whole session. By default, if user makes more than 200 calls in 10 minutes, ban for the session.
|
|
142
116
|
* @param type
|
|
143
117
|
* @returns
|
|
144
118
|
*/
|
|
145
|
-
|
|
146
|
-
if (
|
|
119
|
+
const check = (type: 'reward' | 'interstitial') => {
|
|
120
|
+
if (isBannedForSession) {
|
|
147
121
|
return 'BANNED_FOR_SESSION';
|
|
148
122
|
}
|
|
149
123
|
|
|
150
124
|
const now = Date.now();
|
|
151
125
|
|
|
152
126
|
// keep track of call timestamps
|
|
153
|
-
|
|
127
|
+
callAdsTimestampsForSession = callAdsTimestampsForSession.concat({
|
|
154
128
|
timestamp: now,
|
|
155
129
|
type
|
|
156
130
|
});
|
|
157
131
|
|
|
158
132
|
// if only 1 call, no need to check
|
|
159
|
-
if (
|
|
133
|
+
if (callAdsTimestampsForSession.length === 1) {
|
|
160
134
|
return undefined;
|
|
161
135
|
}
|
|
162
136
|
|
|
163
137
|
// keep last x timestamps
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
-this.maxAllowedAdsForSession
|
|
167
|
-
);
|
|
138
|
+
if (callAdsTimestampsForSession.length > maxAllowedAdsForSession) {
|
|
139
|
+
callAdsTimestampsForSession = callAdsTimestampsForSession.slice(-maxAllowedAdsForSession);
|
|
168
140
|
}
|
|
169
141
|
|
|
170
142
|
// if less than x seconds, ban for session
|
|
171
|
-
if (
|
|
172
|
-
const diffFromFirst = now -
|
|
173
|
-
if (diffFromFirst <=
|
|
174
|
-
|
|
143
|
+
if (callAdsTimestampsForSession.length === maxAllowedAdsForSession) {
|
|
144
|
+
const diffFromFirst = now - callAdsTimestampsForSession[0].timestamp;
|
|
145
|
+
if (diffFromFirst <= banForSessionThreshold) {
|
|
146
|
+
isBannedForSession = true;
|
|
175
147
|
return 'BANNED_FOR_SESSION';
|
|
176
148
|
}
|
|
177
149
|
}
|
|
178
150
|
};
|
|
179
151
|
|
|
152
|
+
return check;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const createBanForTimeStrategy = (
|
|
156
|
+
maxAllowedAdsForTime: number,
|
|
157
|
+
banForTimeThreshold: number,
|
|
158
|
+
bannedReleaseTime: number
|
|
159
|
+
) => {
|
|
160
|
+
let releaseBannedForTimeTimeout: number | null = null;
|
|
161
|
+
|
|
162
|
+
const setReleaseBannedForTime = (timeout: number) => {
|
|
163
|
+
if (releaseBannedForTimeTimeout) {
|
|
164
|
+
window.clearTimeout(releaseBannedForTimeTimeout);
|
|
165
|
+
releaseBannedForTimeTimeout = null;
|
|
166
|
+
}
|
|
167
|
+
releaseBannedForTimeTimeout = window.setTimeout(() => {
|
|
168
|
+
isBannedForTime.value = false;
|
|
169
|
+
releaseBannedForTimeTimeout = null;
|
|
170
|
+
}, timeout);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const TIMESTAMP_STORAGE_KEY = 'jolibox-sdk-ads-callbreak-timestamps';
|
|
174
|
+
const BAN_FOR_TIME_KEY = 'jolibox-sdk-ads-ban-for-time';
|
|
175
|
+
|
|
176
|
+
const callAdsHistory = {
|
|
177
|
+
_records: [] as CallAdsHistory[],
|
|
178
|
+
init() {
|
|
179
|
+
try {
|
|
180
|
+
const fromStorage = JSON.parse(window.localStorage.getItem(TIMESTAMP_STORAGE_KEY) ?? '[]');
|
|
181
|
+
if (Array.isArray(fromStorage)) {
|
|
182
|
+
this._records = fromStorage;
|
|
183
|
+
} else {
|
|
184
|
+
this._records = [];
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
this._records = [];
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
get values() {
|
|
191
|
+
return this._records;
|
|
192
|
+
},
|
|
193
|
+
set values(value: CallAdsHistory[]) {
|
|
194
|
+
try {
|
|
195
|
+
window.localStorage.setItem(TIMESTAMP_STORAGE_KEY, JSON.stringify(value));
|
|
196
|
+
} catch {
|
|
197
|
+
// console.error('Failed to save timestamps');
|
|
198
|
+
// do nothing
|
|
199
|
+
}
|
|
200
|
+
this._records = value;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const isBannedForTime = {
|
|
205
|
+
_record: false,
|
|
206
|
+
init() {
|
|
207
|
+
try {
|
|
208
|
+
const fromStorage = JSON.parse(window.localStorage.getItem(BAN_FOR_TIME_KEY) ?? 'false');
|
|
209
|
+
if (typeof fromStorage === 'boolean') {
|
|
210
|
+
this._record = fromStorage;
|
|
211
|
+
}
|
|
212
|
+
} catch {
|
|
213
|
+
this._record = false; // default value
|
|
214
|
+
}
|
|
215
|
+
if (this._record) {
|
|
216
|
+
const lastCall: CallAdsHistory | undefined = callAdsHistory.values[callAdsHistory.values.length - 1];
|
|
217
|
+
const timeDiff = lastCall ? Date.now() - lastCall.timestamp : bannedReleaseTime;
|
|
218
|
+
if (timeDiff <= bannedReleaseTime && timeDiff > 0) {
|
|
219
|
+
// within the banned release time window
|
|
220
|
+
setReleaseBannedForTime(timeDiff);
|
|
221
|
+
} else {
|
|
222
|
+
this._record = false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
get value() {
|
|
227
|
+
return this._record;
|
|
228
|
+
},
|
|
229
|
+
set value(value: boolean) {
|
|
230
|
+
try {
|
|
231
|
+
window.localStorage.setItem(BAN_FOR_TIME_KEY, JSON.stringify(value));
|
|
232
|
+
} catch {
|
|
233
|
+
// do nothing
|
|
234
|
+
}
|
|
235
|
+
this._record = value;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
callAdsHistory.init();
|
|
240
|
+
isBannedForTime.init();
|
|
241
|
+
|
|
180
242
|
/**
|
|
181
243
|
* Check if we should ban this user for a period of time. By default, if user makes more than 8 calls in 1 minute, ban for 1 minute.
|
|
182
244
|
* After 1 minute, user can make calls again.
|
|
183
245
|
* @param type
|
|
184
246
|
* @returns
|
|
185
247
|
*/
|
|
186
|
-
|
|
187
|
-
if (
|
|
248
|
+
const check = (type: 'reward' | 'interstitial') => {
|
|
249
|
+
if (isBannedForTime.value) {
|
|
188
250
|
return 'WAITING_BANNED_RELEASE';
|
|
189
251
|
}
|
|
190
252
|
|
|
191
253
|
const now = Date.now();
|
|
192
254
|
|
|
193
255
|
// keep track of call timestamps
|
|
194
|
-
|
|
256
|
+
callAdsHistory.values = callAdsHistory.values.concat({
|
|
195
257
|
timestamp: now,
|
|
196
258
|
type
|
|
197
259
|
});
|
|
198
260
|
|
|
199
261
|
// if only 1 call, no need to check
|
|
200
|
-
if (
|
|
262
|
+
if (callAdsHistory.values.length === 1) {
|
|
201
263
|
return undefined;
|
|
202
264
|
}
|
|
203
265
|
|
|
204
266
|
// keep last x timestamps
|
|
205
|
-
if (
|
|
206
|
-
|
|
267
|
+
if (callAdsHistory.values.length > maxAllowedAdsForTime) {
|
|
268
|
+
callAdsHistory.values = callAdsHistory.values.slice(-maxAllowedAdsForTime);
|
|
207
269
|
}
|
|
208
270
|
|
|
209
271
|
// if less than x seconds, ban for time
|
|
210
|
-
if (
|
|
211
|
-
const diffFromFirst = now -
|
|
212
|
-
if (diffFromFirst <=
|
|
213
|
-
|
|
214
|
-
|
|
272
|
+
if (callAdsHistory.values.length === maxAllowedAdsForTime) {
|
|
273
|
+
const diffFromFirst = now - callAdsHistory.values[0].timestamp;
|
|
274
|
+
if (diffFromFirst <= banForTimeThreshold) {
|
|
275
|
+
isBannedForTime.value = true;
|
|
276
|
+
setReleaseBannedForTime(bannedReleaseTime);
|
|
215
277
|
return 'BANNED_FOR_TIME';
|
|
216
278
|
}
|
|
217
279
|
}
|
|
218
280
|
};
|
|
281
|
+
return check;
|
|
282
|
+
};
|
|
219
283
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
return 'NETWORK_NOT_OK';
|
|
223
|
-
}
|
|
284
|
+
const createInitialStrategy = (initialThreshold: number) => {
|
|
285
|
+
const initTimestamp = Date.now();
|
|
224
286
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
287
|
+
/**
|
|
288
|
+
* Check if we should ban for initial call. By default, if user makes a call less than 2 seconds after the first call, block.
|
|
289
|
+
* @returns
|
|
290
|
+
*/
|
|
291
|
+
const check = () => {
|
|
292
|
+
const now = Date.now();
|
|
293
|
+
const diffFromInit = now - initTimestamp;
|
|
294
|
+
// if initial call less than x seconds, block
|
|
295
|
+
if (diffFromInit <= initialThreshold) {
|
|
296
|
+
return 'BLOCK_INITIAL';
|
|
228
297
|
}
|
|
298
|
+
};
|
|
299
|
+
return check;
|
|
300
|
+
};
|
|
229
301
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
302
|
+
const createHighFreqStrategy = (highFreqThreshold = 2000) => {
|
|
303
|
+
let highFreqCounter = 0;
|
|
304
|
+
let highFreqTimer: number | null = null;
|
|
234
305
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Check if we should ban for high frequency calling. If user makes a call more than 1 times in x seconds, block.
|
|
308
|
+
* This only works for reward ads.
|
|
309
|
+
*
|
|
310
|
+
* @param type
|
|
311
|
+
* @returns
|
|
312
|
+
*/
|
|
313
|
+
const check = (type: 'reward' | 'interstitial') => {
|
|
314
|
+
if (type === 'reward') {
|
|
315
|
+
if (highFreqTimer) {
|
|
316
|
+
highFreqCounter += 1;
|
|
317
|
+
return { reason: 'HOLDING_HIGH_FREQ' as const, info: { count: highFreqCounter } };
|
|
318
|
+
} else {
|
|
319
|
+
highFreqTimer = window.setTimeout(() => {
|
|
320
|
+
if (highFreqTimer) {
|
|
321
|
+
window.clearTimeout(highFreqTimer);
|
|
322
|
+
highFreqTimer = 0;
|
|
323
|
+
highFreqCounter = 0;
|
|
324
|
+
}
|
|
325
|
+
}, highFreqThreshold);
|
|
326
|
+
}
|
|
238
327
|
}
|
|
239
|
-
|
|
240
|
-
return 'ALLOWED';
|
|
241
328
|
};
|
|
242
|
-
|
|
329
|
+
|
|
330
|
+
return check;
|
|
331
|
+
};
|