@jolibox/implement 1.1.4-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/.eslintrc.js +13 -0
- package/.rush/temp/package-deps_build.json +105 -0
- package/.rush/temp/shrinkwrap-deps.json +79 -0
- package/README.md +1 -0
- package/dist/common/api-factory/index.d.ts +21 -0
- package/dist/common/api-factory/validator/__tests__/validate/any.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/array.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/arraybuffer.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/boolean.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/enum.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/function.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/literal.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/nullish.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/number.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/object.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/or.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/record.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/string.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/symbol.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/tuple.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/type-asserts.test.d.ts +1 -0
- package/dist/common/api-factory/validator/__tests__/validate/utils.test.d.ts +1 -0
- package/dist/common/api-factory/validator/index.d.ts +29 -0
- package/dist/common/api-factory/validator/validate.d.ts +119 -0
- package/dist/common/can-i-use.d.ts +2 -0
- package/dist/common/context/index.d.ts +16 -0
- package/dist/common/context/types.d.ts +5 -0
- package/dist/common/context/url-parse.d.ts +22 -0
- package/dist/common/http/index.d.ts +13 -0
- package/dist/common/http/uuid.d.ts +2 -0
- package/dist/common/http/xua.d.ts +17 -0
- package/dist/common/report/base-tracker.d.ts +13 -0
- package/dist/common/report/create-trace.d.ts +7 -0
- package/dist/common/report/errors/error-types.d.ts +122 -0
- package/dist/common/report/errors/index.d.ts +13 -0
- package/dist/common/report/errors/report/index.d.ts +51 -0
- package/dist/common/report/errors/report/listeners.d.ts +1 -0
- package/dist/common/report/index.d.ts +3 -0
- package/dist/common/report/task-track/index.d.ts +25 -0
- package/dist/common/report/track.d.ts +3 -0
- package/dist/common/report/types.d.ts +75 -0
- package/dist/h5/ads/ads-action-detection.d.ts +6 -0
- package/dist/h5/ads/anti-cheating.d.ts +61 -0
- package/dist/h5/ads/index.d.ts +275 -0
- package/dist/h5/api/base.d.ts +13 -0
- package/dist/h5/api/get-system-info.d.ts +1 -0
- package/dist/h5/api/index.d.ts +4 -0
- package/dist/h5/api/lifecycle.d.ts +1 -0
- package/dist/h5/api/storage.d.ts +27 -0
- package/dist/h5/api/task.d.ts +1 -0
- package/dist/h5/bootstrap/index.d.ts +1 -0
- package/dist/h5/http/index.d.ts +33 -0
- package/dist/h5/http/utils/__tests__/uuid.test.d.ts +1 -0
- package/dist/h5/http/utils/__tests__/xua.test.d.ts +1 -0
- package/dist/h5/http/utils/index.d.ts +14 -0
- package/dist/h5/http/utils/session.d.ts +1 -0
- package/dist/h5/report/errors/index.d.ts +4 -0
- package/dist/h5/report/event-tracker.d.ts +8 -0
- package/dist/h5/report/index.d.ts +10 -0
- package/dist/h5/report/task-tracker.d.ts +18 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +13 -0
- package/dist/index.native.d.ts +2 -0
- package/dist/index.native.js +3 -0
- package/dist/native/api/ads.d.ts +1 -0
- package/dist/native/api/base.d.ts +13 -0
- package/dist/native/api/get-system-info.d.ts +1 -0
- package/dist/native/api/index.d.ts +8 -0
- package/dist/native/api/keyboard.d.ts +9 -0
- package/dist/native/api/lifecycle.d.ts +1 -0
- package/dist/native/api/login.d.ts +1 -0
- package/dist/native/api/request.d.ts +1 -0
- package/dist/native/api/storage.d.ts +25 -0
- package/dist/native/api/task.d.ts +1 -0
- package/dist/native/bootstrap/bridge.d.ts +4 -0
- package/dist/native/bootstrap/index.d.ts +1 -0
- package/dist/native/js-bridge/const.d.ts +5 -0
- package/dist/native/js-bridge/index.d.ts +2 -0
- package/dist/native/js-bridge/invoke.d.ts +21 -0
- package/dist/native/js-bridge/js-bridge.d.ts +6 -0
- package/dist/native/js-bridge/report.d.ts +63 -0
- package/dist/native/js-bridge/subscribe.d.ts +8 -0
- package/dist/native/js-bridge/types.d.ts +14 -0
- package/dist/native/js-bridge/utils.d.ts +17 -0
- package/dist/native/js-core/index.d.ts +3 -0
- package/dist/native/js-core/jolibox-js-core.d.ts +45 -0
- package/dist/native/js-core/message-port.d.ts +12 -0
- package/dist/native/js-core/utils.d.ts +7 -0
- package/dist/native/network/create-fetch.d.ts +27 -0
- package/dist/native/network/index.d.ts +11 -0
- package/dist/native/network/report.d.ts +15 -0
- package/dist/native/network/types.d.ts +61 -0
- package/dist/native/network/utils.d.ts +9 -0
- package/dist/native/report/errors/index.d.ts +4 -0
- package/dist/native/report/index.d.ts +10 -0
- package/dist/native/report/task-tracker.d.ts +24 -0
- package/dist/utils/index.d.ts +0 -0
- package/esbuild.config.js +66 -0
- package/implement.build.log +9 -0
- package/package.json +30 -0
- package/src/common/api-factory/index.ts +188 -0
- package/src/common/api-factory/validator/__tests__/validate/any.test.ts +68 -0
- package/src/common/api-factory/validator/__tests__/validate/array.test.ts +402 -0
- package/src/common/api-factory/validator/__tests__/validate/arraybuffer.test.ts +48 -0
- package/src/common/api-factory/validator/__tests__/validate/boolean.test.ts +27 -0
- package/src/common/api-factory/validator/__tests__/validate/enum.test.ts +106 -0
- package/src/common/api-factory/validator/__tests__/validate/function.test.ts +54 -0
- package/src/common/api-factory/validator/__tests__/validate/literal.test.ts +130 -0
- package/src/common/api-factory/validator/__tests__/validate/nullish.test.ts +41 -0
- package/src/common/api-factory/validator/__tests__/validate/number.test.ts +147 -0
- package/src/common/api-factory/validator/__tests__/validate/object.test.ts +131 -0
- package/src/common/api-factory/validator/__tests__/validate/or.test.ts +96 -0
- package/src/common/api-factory/validator/__tests__/validate/record.test.ts +274 -0
- package/src/common/api-factory/validator/__tests__/validate/string.test.ts +187 -0
- package/src/common/api-factory/validator/__tests__/validate/symbol.test.ts +23 -0
- package/src/common/api-factory/validator/__tests__/validate/tuple.test.ts +86 -0
- package/src/common/api-factory/validator/__tests__/validate/type-asserts.test.ts +13 -0
- package/src/common/api-factory/validator/__tests__/validate/utils.test.ts +44 -0
- package/src/common/api-factory/validator/index.ts +107 -0
- package/src/common/api-factory/validator/validate.ts +641 -0
- package/src/common/can-i-use.ts +19 -0
- package/src/common/context/index.ts +85 -0
- package/src/common/context/types.ts +5 -0
- package/src/common/context/url-parse.ts +63 -0
- package/src/common/http/index.ts +29 -0
- package/src/common/http/uuid.ts +11 -0
- package/src/common/http/xua.ts +79 -0
- package/src/common/report/base-tracker.ts +134 -0
- package/src/common/report/create-trace.ts +17 -0
- package/src/common/report/errors/error-types.ts +206 -0
- package/src/common/report/errors/index.ts +20 -0
- package/src/common/report/errors/report/index.ts +63 -0
- package/src/common/report/errors/report/listeners.ts +80 -0
- package/src/common/report/index.ts +3 -0
- package/src/common/report/task-track/index.ts +102 -0
- package/src/common/report/track.ts +49 -0
- package/src/common/report/types.ts +90 -0
- package/src/h5/ads/ads-action-detection.ts +31 -0
- package/src/h5/ads/anti-cheating.ts +244 -0
- package/src/h5/ads/index.ts +658 -0
- package/src/h5/api/base.ts +9 -0
- package/src/h5/api/get-system-info.ts +59 -0
- package/src/h5/api/index.ts +4 -0
- package/src/h5/api/lifecycle.ts +95 -0
- package/src/h5/api/storage.ts +173 -0
- package/src/h5/api/task.ts +190 -0
- package/src/h5/bootstrap/index.ts +16 -0
- package/src/h5/http/index.ts +189 -0
- package/src/h5/http/utils/__tests__/uuid.test.ts +16 -0
- package/src/h5/http/utils/__tests__/xua.test.ts +27 -0
- package/src/h5/http/utils/index.ts +19 -0
- package/src/h5/http/utils/session.ts +10 -0
- package/src/h5/report/errors/index.ts +40 -0
- package/src/h5/report/event-tracker.ts +40 -0
- package/src/h5/report/index.ts +56 -0
- package/src/h5/report/task-tracker.ts +42 -0
- package/src/index.native.ts +7 -0
- package/src/index.ts +9 -0
- package/src/native/api/ads.ts +52 -0
- package/src/native/api/base.ts +8 -0
- package/src/native/api/get-system-info.ts +44 -0
- package/src/native/api/index.ts +8 -0
- package/src/native/api/keyboard.ts +75 -0
- package/src/native/api/lifecycle.ts +76 -0
- package/src/native/api/login.ts +73 -0
- package/src/native/api/request.ts +154 -0
- package/src/native/api/storage.ts +287 -0
- package/src/native/api/task.ts +227 -0
- package/src/native/bootstrap/bridge.ts +59 -0
- package/src/native/bootstrap/index.ts +59 -0
- package/src/native/js-bridge/const.ts +11 -0
- package/src/native/js-bridge/index.ts +2 -0
- package/src/native/js-bridge/invoke.ts +210 -0
- package/src/native/js-bridge/js-bridge.ts +23 -0
- package/src/native/js-bridge/report.ts +311 -0
- package/src/native/js-bridge/subscribe.ts +50 -0
- package/src/native/js-bridge/types.ts +26 -0
- package/src/native/js-bridge/utils.ts +116 -0
- package/src/native/js-core/index.ts +4 -0
- package/src/native/js-core/jolibox-js-core.ts +188 -0
- package/src/native/js-core/message-port.ts +52 -0
- package/src/native/js-core/utils.ts +9 -0
- package/src/native/network/create-fetch.ts +237 -0
- package/src/native/network/index.ts +15 -0
- package/src/native/network/report.ts +58 -0
- package/src/native/network/types.ts +77 -0
- package/src/native/network/utils.ts +90 -0
- package/src/native/report/errors/index.ts +27 -0
- package/src/native/report/index.ts +51 -0
- package/src/native/report/task-tracker.ts +72 -0
- package/src/native/types/global.d.ts +26 -0
- package/src/native/types/native-method-map.d.ts +282 -0
- package/src/native/types/native-method.d.ts +30 -0
- package/src/utils/index.ts +0 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* report-error
|
|
3
|
+
*/
|
|
4
|
+
import { EventEmitter } from '@jolibox/common';
|
|
5
|
+
import { debounce } from '@jolibox/common';
|
|
6
|
+
import { BaseError } from '@jolibox/common';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* native/h5 实现 errorReportEmitter的监听和上报逻辑
|
|
10
|
+
*/
|
|
11
|
+
export const errorReportCollector = new EventEmitter<ErrorEventMap>();
|
|
12
|
+
export const errorReportEmitter = new EventEmitter<{
|
|
13
|
+
GLOBAL_ERROR: [Error | BaseError, ErrorData];
|
|
14
|
+
GLOBAL_USER_ERROR: [Error | BaseError, ErrorData];
|
|
15
|
+
}>();
|
|
16
|
+
export interface PromiseEvent {
|
|
17
|
+
reason: unknown;
|
|
18
|
+
promise: Promise<unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface EventMap {
|
|
22
|
+
[ERROR: string]: [
|
|
23
|
+
{
|
|
24
|
+
error: Error | PromiseEvent;
|
|
25
|
+
isFromUser: boolean;
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ErrorEventMap {
|
|
31
|
+
[ERROR_REPORT: string]: Array<{
|
|
32
|
+
error: Error | BaseError;
|
|
33
|
+
options: ErrorOptions | GlobalErrorOptions;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ErrorOptions {
|
|
38
|
+
isNativeCallback?: boolean;
|
|
39
|
+
promiseEvent?: PromiseEvent;
|
|
40
|
+
environment?: 'native' | 'h5';
|
|
41
|
+
}
|
|
42
|
+
export interface GlobalErrorOptions extends ErrorOptions {
|
|
43
|
+
globalType: 'global' | 'promise';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ErrorData {
|
|
47
|
+
user_id: string;
|
|
48
|
+
device_id: string;
|
|
49
|
+
timestamp: number;
|
|
50
|
+
isFromUser: boolean;
|
|
51
|
+
tag: string; // for report
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function reportError(error: BaseError, options?: ErrorOptions): void;
|
|
55
|
+
export function reportError(error: Error | BaseError, options: GlobalErrorOptions): void;
|
|
56
|
+
export function reportError(error: Error | BaseError, options: ErrorOptions | GlobalErrorOptions = {}): void {
|
|
57
|
+
errorReportCollector.emit('ERROR_REPORT', {
|
|
58
|
+
error,
|
|
59
|
+
options
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
reportError.debounce = debounce(reportError, 50, { leading: true });
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { BaseError, InternalReporterError, UserError } from '@jolibox/common';
|
|
2
|
+
import { errorReportCollector, errorReportEmitter } from './index';
|
|
3
|
+
import { context } from '@/common/context';
|
|
4
|
+
import type { ErrorOptions, GlobalErrorOptions } from './index';
|
|
5
|
+
import { logger } from '@jolibox/common';
|
|
6
|
+
|
|
7
|
+
let reportFlag = false;
|
|
8
|
+
|
|
9
|
+
const hasIn = (object: Record<string, unknown> | Error, path: string): boolean => {
|
|
10
|
+
if (object == null) return false;
|
|
11
|
+
return path in object;
|
|
12
|
+
};
|
|
13
|
+
const isBaseError = (error: Error): error is BaseError => {
|
|
14
|
+
return hasIn(error, 'kind');
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const toCamelCase = (snakeStr: string): string => {
|
|
18
|
+
const words = snakeStr.toLowerCase().split('_');
|
|
19
|
+
return (
|
|
20
|
+
words[0] +
|
|
21
|
+
words
|
|
22
|
+
.slice(1)
|
|
23
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
24
|
+
.join('')
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const innerErrorReport = (
|
|
29
|
+
error: BaseError | Error,
|
|
30
|
+
options: ErrorOptions | GlobalErrorOptions = {},
|
|
31
|
+
isFromUser: boolean
|
|
32
|
+
) => {
|
|
33
|
+
const priority = (error as BaseError).priority ?? 'P1';
|
|
34
|
+
|
|
35
|
+
const common = {
|
|
36
|
+
user_id: context.hostUserInfo?.uid ?? '',
|
|
37
|
+
device_id: context.deviceInfo.did ?? '',
|
|
38
|
+
timestamp: Date.now(),
|
|
39
|
+
tag: toCamelCase(error.name)
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const reports = { ...common, env: options.environment, isFromUser };
|
|
43
|
+
if (isFromUser) {
|
|
44
|
+
errorReportEmitter.emit('GLOBAL_USER_ERROR', error, reports);
|
|
45
|
+
} else {
|
|
46
|
+
errorReportEmitter.emit('GLOBAL_ERROR', error, reports);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const isUserError = (error: Error): error is UserError => {
|
|
51
|
+
//eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
52
|
+
//@ts-ignore
|
|
53
|
+
return hasIn(error, 'kind') && error['kind'] === 'USER_ERROR';
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
errorReportCollector.on('ERROR_REPORT', ({ error, options }) => {
|
|
57
|
+
// avoid cycle report
|
|
58
|
+
if (reportFlag) return;
|
|
59
|
+
reportFlag = true;
|
|
60
|
+
|
|
61
|
+
const raw = isBaseError(error) && error.raw ? error.raw : error;
|
|
62
|
+
try {
|
|
63
|
+
const isFromUser = isUserError(error);
|
|
64
|
+
|
|
65
|
+
innerErrorReport(error, options, isFromUser);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
68
|
+
const reportError = new InternalReporterError(`${message}, origin error: ${raw.message}`);
|
|
69
|
+
logger.error(reportError);
|
|
70
|
+
innerErrorReport(
|
|
71
|
+
new InternalReporterError(reportError.message),
|
|
72
|
+
{
|
|
73
|
+
environment: options.environment
|
|
74
|
+
},
|
|
75
|
+
false
|
|
76
|
+
);
|
|
77
|
+
} finally {
|
|
78
|
+
reportFlag = false;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { EventEmitter } from '@jolibox/common';
|
|
2
|
+
import { TrackEvent } from '@jolibox/types';
|
|
3
|
+
|
|
4
|
+
type TaskEvent =
|
|
5
|
+
| 'OPEN_GAME'
|
|
6
|
+
| 'PLAY_GAME'
|
|
7
|
+
| 'CLOSE_GAME'
|
|
8
|
+
| 'COMPLETE_GAME_LEVEL'
|
|
9
|
+
| 'USE_GAME_ITEM' // 使用道具
|
|
10
|
+
| 'IN_GAME_PURCHASES' // 游戏内购
|
|
11
|
+
| 'ADS_UNLOCK_GAME'; // 广告解锁
|
|
12
|
+
export type TaskPoint = {
|
|
13
|
+
event: TaskEvent;
|
|
14
|
+
params?: Record<string, unknown>;
|
|
15
|
+
};
|
|
16
|
+
export abstract class TaskTracker {
|
|
17
|
+
interval: number;
|
|
18
|
+
lastReportTime = 0;
|
|
19
|
+
visible = true;
|
|
20
|
+
|
|
21
|
+
timer = this.createRAFTimer((duration) => {
|
|
22
|
+
this.reporter({
|
|
23
|
+
event: 'PLAY_GAME',
|
|
24
|
+
params: {
|
|
25
|
+
duration
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
this.tracker('PlayGame', { duration });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
abstract reporter(point: TaskPoint): void;
|
|
32
|
+
abstract tracker(point: TrackEvent, info: Record<string, unknown> | null): void;
|
|
33
|
+
constructor(eventEmitter: EventEmitter<{ visible: [boolean] }>, interval?: number) {
|
|
34
|
+
this.interval = interval ?? 5 * 1000;
|
|
35
|
+
|
|
36
|
+
eventEmitter.on('visible', (visible) => {
|
|
37
|
+
this.visible = visible;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
start(duration?: number) {
|
|
42
|
+
this.reporter({
|
|
43
|
+
event: 'OPEN_GAME',
|
|
44
|
+
params: {
|
|
45
|
+
timestamp: Date.now(),
|
|
46
|
+
duration: duration ?? 0
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
this.tracker('OpenGame', { duration: duration ?? 0 });
|
|
50
|
+
this.timer.start();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
close(duration: number) {
|
|
54
|
+
this.reporter({
|
|
55
|
+
event: 'CLOSE_GAME',
|
|
56
|
+
params: {
|
|
57
|
+
timestamp: Date.now(),
|
|
58
|
+
duration
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
this.tracker('CloseGame', { duration });
|
|
62
|
+
this.timer.stop();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private createRAFTimer(callback: (duration: number) => void) {
|
|
66
|
+
let lastTime = performance.now();
|
|
67
|
+
let rafId: number;
|
|
68
|
+
let isRunning = false;
|
|
69
|
+
let totalTime = 0;
|
|
70
|
+
|
|
71
|
+
const tick = (currentTime: number) => {
|
|
72
|
+
if (!isRunning) return;
|
|
73
|
+
|
|
74
|
+
const deltaTime = currentTime - lastTime;
|
|
75
|
+
if (deltaTime >= this.interval) {
|
|
76
|
+
if (this.visible) {
|
|
77
|
+
totalTime += deltaTime;
|
|
78
|
+
callback(totalTime);
|
|
79
|
+
}
|
|
80
|
+
lastTime = currentTime;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
rafId = requestAnimationFrame(tick);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
start() {
|
|
88
|
+
if (!isRunning) {
|
|
89
|
+
isRunning = true;
|
|
90
|
+
lastTime = performance.now();
|
|
91
|
+
rafId = requestAnimationFrame(tick);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
stop() {
|
|
95
|
+
isRunning = false;
|
|
96
|
+
if (rafId) {
|
|
97
|
+
cancelAnimationFrame(rafId);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { debounce } from '@jolibox/common';
|
|
2
|
+
import { ReportHandler, Track, TrackPerformance, CommonReportConfig } from './types';
|
|
3
|
+
import { PerformanceType, TrackEvent } from '@jolibox/types';
|
|
4
|
+
import { InternalContextError } from '@jolibox/common';
|
|
5
|
+
import { reportError } from './errors/report';
|
|
6
|
+
|
|
7
|
+
// Track system event, wrap common config
|
|
8
|
+
export function createTrack(reportHandler: ReportHandler, common: CommonReportConfig): Track {
|
|
9
|
+
const track = (tag: TrackEvent, info: Record<string, unknown> | null, webviewId?: number): void => {
|
|
10
|
+
const data = {
|
|
11
|
+
tag,
|
|
12
|
+
...common,
|
|
13
|
+
extra: {
|
|
14
|
+
...info
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
if (tag == 'globalJsError') {
|
|
18
|
+
reportError(new InternalContextError(JSON.stringify(data), 'P0')); // window.onerror, 白屏
|
|
19
|
+
} else {
|
|
20
|
+
reportHandler('systemLog', data, webviewId);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
track.debounce = debounce(track, 500, { leading: true });
|
|
25
|
+
return track;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createTrackPerformance(track: Track): TrackPerformance {
|
|
29
|
+
const trackPerformance = (
|
|
30
|
+
type: PerformanceType,
|
|
31
|
+
duration: number,
|
|
32
|
+
data?: Record<string, unknown>
|
|
33
|
+
): void => {
|
|
34
|
+
const length = (data && JSON.stringify(data)) ?? 0;
|
|
35
|
+
const info = {
|
|
36
|
+
speed_value_type: type,
|
|
37
|
+
total_duration: duration
|
|
38
|
+
};
|
|
39
|
+
if (length) {
|
|
40
|
+
Object.assign(info, { extra: length });
|
|
41
|
+
}
|
|
42
|
+
track('joliboxSpeedAnalysis', info);
|
|
43
|
+
};
|
|
44
|
+
trackPerformance.debounce = debounce(trackPerformance, 500, {
|
|
45
|
+
leading: true
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return trackPerformance;
|
|
49
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { PerformanceType, TrackEvent } from '@jolibox/types';
|
|
2
|
+
|
|
3
|
+
export interface Track {
|
|
4
|
+
(tag: TrackEvent, info: Record<string, unknown> | null): void;
|
|
5
|
+
debounce: (tag: TrackEvent, info: Record<string, unknown>) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface TrackPerformance {
|
|
9
|
+
(type: PerformanceType, duration: number): void;
|
|
10
|
+
debounce: (type: PerformanceType, duration: number) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ReportHandler = (
|
|
14
|
+
event: string,
|
|
15
|
+
data: { tag: TrackEvent; data?: Record<string, string | number | boolean | null> },
|
|
16
|
+
webviewId?: number
|
|
17
|
+
) => void;
|
|
18
|
+
|
|
19
|
+
// common parameters
|
|
20
|
+
export interface CommonReportConfig {
|
|
21
|
+
type: EProject;
|
|
22
|
+
platform: 'native' | 'h5';
|
|
23
|
+
jssdk_version: string;
|
|
24
|
+
mp_id: string;
|
|
25
|
+
mp_version: string;
|
|
26
|
+
trace_id?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export enum EventType {
|
|
30
|
+
System = 1000, // used for web sdk
|
|
31
|
+
ErrorTrace = 1001, // used for web sdk
|
|
32
|
+
UserDefined = 1002 // used for web sdk
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface IPage {
|
|
36
|
+
name: string;
|
|
37
|
+
params: Record<string, string | boolean | number | null> | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface IEvent {
|
|
41
|
+
name: string;
|
|
42
|
+
type: EventType; // use a higher ts version to get static analysis
|
|
43
|
+
location: IPage | null;
|
|
44
|
+
target: IPage | null;
|
|
45
|
+
extra: Record<string, string | boolean | number | null> | null;
|
|
46
|
+
timestamp: number; // in unix timestamp
|
|
47
|
+
userId: string | null; // 没登陆时为空
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export enum EProject {
|
|
51
|
+
MiniGame = 'mini-game',
|
|
52
|
+
MiniDrama = 'mini-drama',
|
|
53
|
+
App = 'app',
|
|
54
|
+
WebSDK = 'web-sdk',
|
|
55
|
+
AppSDK = 'app-sdk'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export enum EPlatform {
|
|
59
|
+
App = 0,
|
|
60
|
+
H5 = 1,
|
|
61
|
+
Weapp = 2,
|
|
62
|
+
Alipay = 3,
|
|
63
|
+
Gcash = 4,
|
|
64
|
+
Dana = 5,
|
|
65
|
+
Umma = 6,
|
|
66
|
+
|
|
67
|
+
WebSDK = 1000,
|
|
68
|
+
AppSDK = 1001,
|
|
69
|
+
|
|
70
|
+
Other = 9999
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface IDevice {
|
|
74
|
+
platform: EPlatform;
|
|
75
|
+
os: string; // for app: Android、iOS
|
|
76
|
+
appVersion: string;
|
|
77
|
+
appId: string; // for external SDK, "1 - game h5", "2 - drama h5", "3 - game h5 ant", "4 - drama h5 ant"
|
|
78
|
+
model: string;
|
|
79
|
+
brand: string;
|
|
80
|
+
uuid: string; // 前端生成的唯一标识,用来串整个埋点
|
|
81
|
+
jsSdkVersion: string | null;
|
|
82
|
+
extra: Record<string, string | boolean | number | null> | null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface IEventPackage {
|
|
86
|
+
protocolVersion: string;
|
|
87
|
+
events: IEvent[];
|
|
88
|
+
device: IDevice;
|
|
89
|
+
project: EProject;
|
|
90
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { track } from '../report';
|
|
2
|
+
|
|
3
|
+
export class AdsActionDetection {
|
|
4
|
+
adBreakIsShowing = false;
|
|
5
|
+
|
|
6
|
+
private reportPageJumpOut = () => {
|
|
7
|
+
track('AdBreakJumpOut', {
|
|
8
|
+
context: 'AdsActionDetection'
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
private reportPageHide = () => {
|
|
13
|
+
track('AdBreakHide', {
|
|
14
|
+
context: 'AdsActionDetection'
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
window.addEventListener('visibilitychange', () => {
|
|
20
|
+
if (document.hidden && this.adBreakIsShowing) {
|
|
21
|
+
this.reportPageHide();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
window.addEventListener('beforeunload', (e) => {
|
|
26
|
+
if (this.adBreakIsShowing) {
|
|
27
|
+
this.reportPageJumpOut();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { tracker } from '../report/event-tracker';
|
|
2
|
+
|
|
3
|
+
export type AdsDisplayPermission =
|
|
4
|
+
| 'BLOCK_INITIAL'
|
|
5
|
+
| 'BANNED_FOR_SESSION'
|
|
6
|
+
| 'NETWORK_NOT_OK'
|
|
7
|
+
| 'WAITING_BANNED_RELEASE'
|
|
8
|
+
| 'BANNED_FOR_TIME'
|
|
9
|
+
| 'ALLOWED';
|
|
10
|
+
interface CallAdsHistory {
|
|
11
|
+
timestamp: number;
|
|
12
|
+
type: 'reward' | 'interstitial';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const TIMESTAMP_STORAGE_KEY = 'jolibox-sdk-ads-callbreak-timestamps';
|
|
16
|
+
|
|
17
|
+
interface IAdsAntiCheatingConfig {
|
|
18
|
+
maxAllowedAdsForTime?: number; // default 8
|
|
19
|
+
bannedForTimeThreshold?: number; // default 60000 (1 minute)
|
|
20
|
+
bannedReleaseTime?: number; // default 60000 (1 minute), must be >= checkingTimeThreashold
|
|
21
|
+
initialThreshold?: number; // default 2000
|
|
22
|
+
maxAllowedAdsForSession?: number; // default 200
|
|
23
|
+
bannedForSessionThreshold?: number; // default 600000 (10 minutes)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class AdsAntiCheating {
|
|
27
|
+
private maxAllowedAdsForTime = 8;
|
|
28
|
+
private bannedForTimeThreshold = 60000;
|
|
29
|
+
private bannedReleaseTime = 60000;
|
|
30
|
+
private isBanningForTime = false;
|
|
31
|
+
private releaseBannedForTimeTimeout: number | null = null;
|
|
32
|
+
|
|
33
|
+
private maxAllowedAdsForSession = 200;
|
|
34
|
+
private bannedForSessionThreshold = 600000;
|
|
35
|
+
private isBanningForSession = false;
|
|
36
|
+
|
|
37
|
+
private initialThreshold = 2000;
|
|
38
|
+
|
|
39
|
+
private _callAdsTimestampsForTime: CallAdsHistory[] = []; // max x timestamps for time
|
|
40
|
+
private callAdsTimestampsForSession: CallAdsHistory[] = []; // max x timestamps for session
|
|
41
|
+
|
|
42
|
+
private initTimestamp = 0;
|
|
43
|
+
// private context: JoliboxSDKPipeExecutor;
|
|
44
|
+
private report = tracker;
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
// context: JoliboxSDKPipeExecutor,
|
|
48
|
+
config?: IAdsAntiCheatingConfig
|
|
49
|
+
) {
|
|
50
|
+
this.maxAllowedAdsForTime = config?.maxAllowedAdsForTime ?? 8;
|
|
51
|
+
this.bannedForTimeThreshold = config?.bannedForTimeThreshold ?? 60000;
|
|
52
|
+
this.bannedReleaseTime = config?.bannedReleaseTime ?? 60000;
|
|
53
|
+
|
|
54
|
+
this.maxAllowedAdsForSession = config?.maxAllowedAdsForSession ?? 200;
|
|
55
|
+
this.bannedForSessionThreshold = config?.bannedForSessionThreshold ?? 600000;
|
|
56
|
+
|
|
57
|
+
this.initialThreshold = config?.initialThreshold ?? 2000;
|
|
58
|
+
|
|
59
|
+
if (this.maxAllowedAdsForTime <= 1) {
|
|
60
|
+
throw new Error('maxAllowedAdsForTime must be greater than 1');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (this.bannedForTimeThreshold < 0) {
|
|
64
|
+
throw new Error('bannedForTimeThreshold must be greater than or equal to 0');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (this.bannedReleaseTime < this.bannedForTimeThreshold) {
|
|
68
|
+
throw new Error('bannedReleaseTime must be greater than or equal to bannedForTimeThreshold');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (this.initialThreshold < 0) {
|
|
72
|
+
throw new Error('initialThreshold must be greater than or equal to 0');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.initCallAdsTimestampsForTime();
|
|
76
|
+
this.initTimestamp = Date.now();
|
|
77
|
+
// this.context = context;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Restore call ads timestamps for time from local storage
|
|
82
|
+
*/
|
|
83
|
+
private initCallAdsTimestampsForTime = () => {
|
|
84
|
+
try {
|
|
85
|
+
const fromStorage = JSON.parse(window.localStorage.getItem(TIMESTAMP_STORAGE_KEY) ?? '[]');
|
|
86
|
+
if (Array.isArray(fromStorage)) {
|
|
87
|
+
this._callAdsTimestampsForTime = fromStorage;
|
|
88
|
+
} else {
|
|
89
|
+
this._callAdsTimestampsForTime = [];
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
this._callAdsTimestampsForTime = [];
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get call ads timestamps for time
|
|
98
|
+
*/
|
|
99
|
+
private get callAdsTimestampsForTime() {
|
|
100
|
+
return this._callAdsTimestampsForTime;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Set call ads timestamps for time, this will also save to local storage
|
|
105
|
+
*/
|
|
106
|
+
private set callAdsTimestampsForTime(timestamps: CallAdsHistory[]) {
|
|
107
|
+
try {
|
|
108
|
+
window.localStorage.setItem(TIMESTAMP_STORAGE_KEY, JSON.stringify(timestamps));
|
|
109
|
+
} catch {
|
|
110
|
+
console.error('Failed to save timestamps');
|
|
111
|
+
}
|
|
112
|
+
this._callAdsTimestampsForTime = timestamps;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Set a timeout to release the banned for time status
|
|
117
|
+
*/
|
|
118
|
+
private setReleaseBannedForTime = () => {
|
|
119
|
+
if (this.releaseBannedForTimeTimeout) {
|
|
120
|
+
window.clearTimeout(this.releaseBannedForTimeTimeout);
|
|
121
|
+
this.releaseBannedForTimeTimeout = null;
|
|
122
|
+
}
|
|
123
|
+
this.releaseBannedForTimeTimeout = window.setTimeout(() => {
|
|
124
|
+
this.isBanningForTime = false;
|
|
125
|
+
this.releaseBannedForTimeTimeout = null;
|
|
126
|
+
}, this.bannedReleaseTime);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if we should ban for initial call. By default, if user makes a call less than 2 seconds after the first call, block.
|
|
131
|
+
* @returns
|
|
132
|
+
*/
|
|
133
|
+
private checkShouldBannedInitial = () => {
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
const diffFromInit = now - this.initTimestamp;
|
|
136
|
+
// if initial call less than x seconds, block
|
|
137
|
+
if (diffFromInit <= this.initialThreshold) {
|
|
138
|
+
return 'BLOCK_INITIAL';
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 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.
|
|
144
|
+
* @param type
|
|
145
|
+
* @returns
|
|
146
|
+
*/
|
|
147
|
+
private checkShouldBannedForSession = (type: 'reward' | 'interstitial') => {
|
|
148
|
+
if (this.isBanningForSession) {
|
|
149
|
+
return 'BANNED_FOR_SESSION';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
|
|
154
|
+
// keep track of call timestamps
|
|
155
|
+
this.callAdsTimestampsForSession = this.callAdsTimestampsForSession.concat({
|
|
156
|
+
timestamp: now,
|
|
157
|
+
type
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// if only 1 call, no need to check
|
|
161
|
+
if (this.callAdsTimestampsForSession.length === 1) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// keep last x timestamps
|
|
166
|
+
if (this.callAdsTimestampsForSession.length > this.maxAllowedAdsForSession) {
|
|
167
|
+
this.callAdsTimestampsForSession = this.callAdsTimestampsForSession.slice(
|
|
168
|
+
-this.maxAllowedAdsForSession
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// if less than x seconds, ban for session
|
|
173
|
+
if (this.callAdsTimestampsForSession.length === this.maxAllowedAdsForSession) {
|
|
174
|
+
const diffFromFirst = now - this.callAdsTimestampsForSession[0].timestamp;
|
|
175
|
+
if (diffFromFirst <= this.bannedForSessionThreshold) {
|
|
176
|
+
this.isBanningForSession = true;
|
|
177
|
+
return 'BANNED_FOR_SESSION';
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 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.
|
|
184
|
+
* After 1 minute, user can make calls again.
|
|
185
|
+
* @param type
|
|
186
|
+
* @returns
|
|
187
|
+
*/
|
|
188
|
+
private checkShouldBannedForTime = (type: 'reward' | 'interstitial') => {
|
|
189
|
+
if (this.isBanningForTime) {
|
|
190
|
+
return 'WAITING_BANNED_RELEASE';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const now = Date.now();
|
|
194
|
+
|
|
195
|
+
// keep track of call timestamps
|
|
196
|
+
this.callAdsTimestampsForTime = this.callAdsTimestampsForTime.concat({
|
|
197
|
+
timestamp: now,
|
|
198
|
+
type
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// if only 1 call, no need to check
|
|
202
|
+
if (this.callAdsTimestampsForTime.length === 1) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// keep last x timestamps
|
|
207
|
+
if (this.callAdsTimestampsForTime.length > this.maxAllowedAdsForTime) {
|
|
208
|
+
this.callAdsTimestampsForTime = this.callAdsTimestampsForTime.slice(-this.maxAllowedAdsForTime);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// if less than x seconds, ban for time
|
|
212
|
+
if (this.callAdsTimestampsForTime.length === this.maxAllowedAdsForTime) {
|
|
213
|
+
const diffFromFirst = now - this.callAdsTimestampsForTime[0].timestamp;
|
|
214
|
+
if (diffFromFirst <= this.bannedForTimeThreshold) {
|
|
215
|
+
this.isBanningForTime = true;
|
|
216
|
+
this.setReleaseBannedForTime();
|
|
217
|
+
return 'BANNED_FOR_TIME';
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
public checkAdsDisplayPermission: (type: 'reward' | 'interstitial') => AdsDisplayPermission = (type) => {
|
|
223
|
+
if (!this.report.networkIsOk) {
|
|
224
|
+
return 'NETWORK_NOT_OK';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const shouldBannedInitial = this.checkShouldBannedInitial();
|
|
228
|
+
if (shouldBannedInitial) {
|
|
229
|
+
return shouldBannedInitial;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const shouldBannedForSession = this.checkShouldBannedForSession(type);
|
|
233
|
+
if (shouldBannedForSession) {
|
|
234
|
+
return shouldBannedForSession;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const shouldBannedForTime = this.checkShouldBannedForTime(type);
|
|
238
|
+
if (shouldBannedForTime) {
|
|
239
|
+
return shouldBannedForTime;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return 'ALLOWED';
|
|
243
|
+
};
|
|
244
|
+
}
|