@jolibox/implement 1.1.4-beta.4

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 (193) hide show
  1. package/.eslintrc.js +3 -0
  2. package/.rush/temp/package-deps_build.json +104 -0
  3. package/.rush/temp/shrinkwrap-deps.json +79 -0
  4. package/README.md +1 -0
  5. package/dist/common/api-factory/index.d.ts +21 -0
  6. package/dist/common/api-factory/validator/__tests__/validate/any.test.d.ts +1 -0
  7. package/dist/common/api-factory/validator/__tests__/validate/array.test.d.ts +1 -0
  8. package/dist/common/api-factory/validator/__tests__/validate/arraybuffer.test.d.ts +1 -0
  9. package/dist/common/api-factory/validator/__tests__/validate/boolean.test.d.ts +1 -0
  10. package/dist/common/api-factory/validator/__tests__/validate/enum.test.d.ts +1 -0
  11. package/dist/common/api-factory/validator/__tests__/validate/function.test.d.ts +1 -0
  12. package/dist/common/api-factory/validator/__tests__/validate/literal.test.d.ts +1 -0
  13. package/dist/common/api-factory/validator/__tests__/validate/nullish.test.d.ts +1 -0
  14. package/dist/common/api-factory/validator/__tests__/validate/number.test.d.ts +1 -0
  15. package/dist/common/api-factory/validator/__tests__/validate/object.test.d.ts +1 -0
  16. package/dist/common/api-factory/validator/__tests__/validate/or.test.d.ts +1 -0
  17. package/dist/common/api-factory/validator/__tests__/validate/record.test.d.ts +1 -0
  18. package/dist/common/api-factory/validator/__tests__/validate/string.test.d.ts +1 -0
  19. package/dist/common/api-factory/validator/__tests__/validate/symbol.test.d.ts +1 -0
  20. package/dist/common/api-factory/validator/__tests__/validate/tuple.test.d.ts +1 -0
  21. package/dist/common/api-factory/validator/__tests__/validate/type-asserts.test.d.ts +1 -0
  22. package/dist/common/api-factory/validator/__tests__/validate/utils.test.d.ts +1 -0
  23. package/dist/common/api-factory/validator/index.d.ts +29 -0
  24. package/dist/common/api-factory/validator/validate.d.ts +119 -0
  25. package/dist/common/can-i-use.d.ts +2 -0
  26. package/dist/common/context/index.d.ts +16 -0
  27. package/dist/common/context/types.d.ts +5 -0
  28. package/dist/common/context/url-parse.d.ts +22 -0
  29. package/dist/common/http/index.d.ts +13 -0
  30. package/dist/common/http/uuid.d.ts +2 -0
  31. package/dist/common/http/xua.d.ts +17 -0
  32. package/dist/common/report/base-tracker.d.ts +13 -0
  33. package/dist/common/report/create-trace.d.ts +7 -0
  34. package/dist/common/report/errors/error-types.d.ts +122 -0
  35. package/dist/common/report/errors/index.d.ts +13 -0
  36. package/dist/common/report/errors/report/index.d.ts +51 -0
  37. package/dist/common/report/errors/report/listeners.d.ts +1 -0
  38. package/dist/common/report/index.d.ts +3 -0
  39. package/dist/common/report/task-track/index.d.ts +23 -0
  40. package/dist/common/report/track.d.ts +3 -0
  41. package/dist/common/report/types.d.ts +75 -0
  42. package/dist/h5/ads/ads-action-detection.d.ts +6 -0
  43. package/dist/h5/ads/anti-cheating.d.ts +61 -0
  44. package/dist/h5/ads/index.d.ts +275 -0
  45. package/dist/h5/api/base.d.ts +13 -0
  46. package/dist/h5/api/get-system-info.d.ts +1 -0
  47. package/dist/h5/api/index.d.ts +4 -0
  48. package/dist/h5/api/lifecycle.d.ts +1 -0
  49. package/dist/h5/api/storage.d.ts +27 -0
  50. package/dist/h5/api/task.d.ts +1 -0
  51. package/dist/h5/bootstrap/index.d.ts +1 -0
  52. package/dist/h5/http/index.d.ts +33 -0
  53. package/dist/h5/http/utils/__tests__/uuid.test.d.ts +1 -0
  54. package/dist/h5/http/utils/__tests__/xua.test.d.ts +1 -0
  55. package/dist/h5/http/utils/index.d.ts +14 -0
  56. package/dist/h5/http/utils/session.d.ts +1 -0
  57. package/dist/h5/report/errors/index.d.ts +4 -0
  58. package/dist/h5/report/event-tracker.d.ts +8 -0
  59. package/dist/h5/report/index.d.ts +10 -0
  60. package/dist/h5/report/task-tracker.d.ts +14 -0
  61. package/dist/index.d.ts +3 -0
  62. package/dist/index.js +5026 -0
  63. package/dist/index.native.d.ts +2 -0
  64. package/dist/index.native.js +3743 -0
  65. package/dist/native/api/base.d.ts +13 -0
  66. package/dist/native/api/get-system-info.d.ts +1 -0
  67. package/dist/native/api/index.d.ts +7 -0
  68. package/dist/native/api/keyboard.d.ts +3 -0
  69. package/dist/native/api/lifecycle.d.ts +1 -0
  70. package/dist/native/api/login.d.ts +1 -0
  71. package/dist/native/api/request.d.ts +1 -0
  72. package/dist/native/api/storage.d.ts +25 -0
  73. package/dist/native/api/task.d.ts +1 -0
  74. package/dist/native/bootstrap/bridge.d.ts +4 -0
  75. package/dist/native/bootstrap/index.d.ts +1 -0
  76. package/dist/native/js-bridge/const.d.ts +5 -0
  77. package/dist/native/js-bridge/index.d.ts +2 -0
  78. package/dist/native/js-bridge/invoke.d.ts +21 -0
  79. package/dist/native/js-bridge/js-bridge.d.ts +6 -0
  80. package/dist/native/js-bridge/report.d.ts +63 -0
  81. package/dist/native/js-bridge/subscribe.d.ts +8 -0
  82. package/dist/native/js-bridge/types.d.ts +14 -0
  83. package/dist/native/js-bridge/utils.d.ts +17 -0
  84. package/dist/native/js-core/index.d.ts +3 -0
  85. package/dist/native/js-core/jolibox-js-core.d.ts +45 -0
  86. package/dist/native/js-core/message-port.d.ts +12 -0
  87. package/dist/native/js-core/utils.d.ts +7 -0
  88. package/dist/native/network/create-fetch.d.ts +27 -0
  89. package/dist/native/network/index.d.ts +11 -0
  90. package/dist/native/network/report.d.ts +15 -0
  91. package/dist/native/network/types.d.ts +61 -0
  92. package/dist/native/network/utils.d.ts +9 -0
  93. package/dist/native/report/errors/index.d.ts +4 -0
  94. package/dist/native/report/index.d.ts +10 -0
  95. package/dist/native/report/task-tracker.d.ts +20 -0
  96. package/dist/utils/index.d.ts +0 -0
  97. package/esbuild.config.js +66 -0
  98. package/implement.build.log +9 -0
  99. package/package.json +30 -0
  100. package/src/common/api-factory/index.ts +188 -0
  101. package/src/common/api-factory/validator/__tests__/validate/any.test.ts +68 -0
  102. package/src/common/api-factory/validator/__tests__/validate/array.test.ts +402 -0
  103. package/src/common/api-factory/validator/__tests__/validate/arraybuffer.test.ts +48 -0
  104. package/src/common/api-factory/validator/__tests__/validate/boolean.test.ts +27 -0
  105. package/src/common/api-factory/validator/__tests__/validate/enum.test.ts +106 -0
  106. package/src/common/api-factory/validator/__tests__/validate/function.test.ts +54 -0
  107. package/src/common/api-factory/validator/__tests__/validate/literal.test.ts +130 -0
  108. package/src/common/api-factory/validator/__tests__/validate/nullish.test.ts +41 -0
  109. package/src/common/api-factory/validator/__tests__/validate/number.test.ts +147 -0
  110. package/src/common/api-factory/validator/__tests__/validate/object.test.ts +131 -0
  111. package/src/common/api-factory/validator/__tests__/validate/or.test.ts +96 -0
  112. package/src/common/api-factory/validator/__tests__/validate/record.test.ts +274 -0
  113. package/src/common/api-factory/validator/__tests__/validate/string.test.ts +187 -0
  114. package/src/common/api-factory/validator/__tests__/validate/symbol.test.ts +23 -0
  115. package/src/common/api-factory/validator/__tests__/validate/tuple.test.ts +86 -0
  116. package/src/common/api-factory/validator/__tests__/validate/type-asserts.test.ts +13 -0
  117. package/src/common/api-factory/validator/__tests__/validate/utils.test.ts +44 -0
  118. package/src/common/api-factory/validator/index.ts +107 -0
  119. package/src/common/api-factory/validator/validate.ts +639 -0
  120. package/src/common/can-i-use.ts +19 -0
  121. package/src/common/context/index.ts +91 -0
  122. package/src/common/context/types.ts +5 -0
  123. package/src/common/context/url-parse.ts +63 -0
  124. package/src/common/http/index.ts +29 -0
  125. package/src/common/http/uuid.ts +11 -0
  126. package/src/common/http/xua.ts +79 -0
  127. package/src/common/report/base-tracker.ts +134 -0
  128. package/src/common/report/create-trace.ts +17 -0
  129. package/src/common/report/errors/error-types.ts +206 -0
  130. package/src/common/report/errors/index.ts +20 -0
  131. package/src/common/report/errors/report/index.ts +63 -0
  132. package/src/common/report/errors/report/listeners.ts +80 -0
  133. package/src/common/report/index.ts +3 -0
  134. package/src/common/report/task-track/index.ts +97 -0
  135. package/src/common/report/track.ts +49 -0
  136. package/src/common/report/types.ts +90 -0
  137. package/src/h5/ads/ads-action-detection.ts +31 -0
  138. package/src/h5/ads/anti-cheating.ts +244 -0
  139. package/src/h5/ads/index.ts +641 -0
  140. package/src/h5/api/base.ts +9 -0
  141. package/src/h5/api/get-system-info.ts +55 -0
  142. package/src/h5/api/index.ts +4 -0
  143. package/src/h5/api/lifecycle.ts +110 -0
  144. package/src/h5/api/storage.ts +173 -0
  145. package/src/h5/api/task.ts +197 -0
  146. package/src/h5/bootstrap/index.ts +16 -0
  147. package/src/h5/http/index.ts +189 -0
  148. package/src/h5/http/utils/__tests__/uuid.test.ts +16 -0
  149. package/src/h5/http/utils/__tests__/xua.test.ts +27 -0
  150. package/src/h5/http/utils/index.ts +19 -0
  151. package/src/h5/http/utils/session.ts +10 -0
  152. package/src/h5/report/errors/index.ts +40 -0
  153. package/src/h5/report/event-tracker.ts +40 -0
  154. package/src/h5/report/index.ts +56 -0
  155. package/src/h5/report/task-tracker.ts +35 -0
  156. package/src/index.native.ts +7 -0
  157. package/src/index.ts +9 -0
  158. package/src/native/api/base.ts +8 -0
  159. package/src/native/api/get-system-info.ts +41 -0
  160. package/src/native/api/index.ts +7 -0
  161. package/src/native/api/keyboard.ts +75 -0
  162. package/src/native/api/lifecycle.ts +76 -0
  163. package/src/native/api/login.ts +71 -0
  164. package/src/native/api/request.ts +154 -0
  165. package/src/native/api/storage.ts +287 -0
  166. package/src/native/api/task.ts +267 -0
  167. package/src/native/bootstrap/bridge.ts +59 -0
  168. package/src/native/bootstrap/index.ts +58 -0
  169. package/src/native/js-bridge/const.ts +11 -0
  170. package/src/native/js-bridge/index.ts +2 -0
  171. package/src/native/js-bridge/invoke.ts +210 -0
  172. package/src/native/js-bridge/js-bridge.ts +23 -0
  173. package/src/native/js-bridge/report.ts +311 -0
  174. package/src/native/js-bridge/subscribe.ts +50 -0
  175. package/src/native/js-bridge/types.ts +26 -0
  176. package/src/native/js-bridge/utils.ts +116 -0
  177. package/src/native/js-core/index.ts +4 -0
  178. package/src/native/js-core/jolibox-js-core.ts +188 -0
  179. package/src/native/js-core/message-port.ts +52 -0
  180. package/src/native/js-core/utils.ts +9 -0
  181. package/src/native/network/create-fetch.ts +237 -0
  182. package/src/native/network/index.ts +15 -0
  183. package/src/native/network/report.ts +58 -0
  184. package/src/native/network/types.ts +77 -0
  185. package/src/native/network/utils.ts +90 -0
  186. package/src/native/report/errors/index.ts +27 -0
  187. package/src/native/report/index.ts +51 -0
  188. package/src/native/report/task-tracker.ts +64 -0
  189. package/src/native/types/global.d.ts +26 -0
  190. package/src/native/types/native-method-map.d.ts +282 -0
  191. package/src/native/types/native-method.d.ts +30 -0
  192. package/src/utils/index.ts +0 -0
  193. 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,3 @@
1
+ export * from './create-trace';
2
+ export * from './types';
3
+ export * from './base-tracker';
@@ -0,0 +1,97 @@
1
+ import { EventEmitter } from '@jolibox/common';
2
+
3
+ type TaskEvent =
4
+ | 'OPEN_GAME'
5
+ | 'PLAY_GAME'
6
+ | 'CLOSE_GAME'
7
+ | 'COMPLETE_QUEST'
8
+ | 'USE_GAME_ITEM' // 使用道具
9
+ | 'IN_GAME_PURCHASES' // 游戏内购
10
+ | 'ADS_UNLOCK_GAME'; // 广告解锁
11
+ export type TaskPoint = {
12
+ event: TaskEvent;
13
+ params?: Record<string, unknown>;
14
+ };
15
+ export abstract class TaskTracker {
16
+ interval: number;
17
+ lastReportTime = 0;
18
+ visible = true;
19
+
20
+ timer = this.createRAFTimer((duration) => {
21
+ this.reporter({
22
+ event: 'PLAY_GAME',
23
+ params: {
24
+ duration: duration / 1000 // second
25
+ }
26
+ });
27
+ });
28
+
29
+ abstract reporter(point: TaskPoint): void;
30
+ constructor(eventEmitter: EventEmitter<{ visible: [boolean] }>, interval?: number) {
31
+ this.interval = interval ?? 10 * 1000;
32
+
33
+ eventEmitter.on('visible', (visible) => {
34
+ this.visible = visible;
35
+ });
36
+ }
37
+
38
+ start(duration?: number) {
39
+ this.reporter({
40
+ event: 'OPEN_GAME',
41
+ params: {
42
+ timestamp: Date.now(),
43
+ duration: duration ?? 0 / 1000 // second
44
+ }
45
+ });
46
+ this.timer.start();
47
+ }
48
+
49
+ close(duration: number) {
50
+ this.reporter({
51
+ event: 'CLOSE_GAME',
52
+ params: {
53
+ timestamp: Date.now(),
54
+ duration: duration / 1000 // second
55
+ }
56
+ });
57
+ this.timer.stop();
58
+ }
59
+
60
+ private createRAFTimer(callback: (duration: number) => void) {
61
+ let lastTime = performance.now();
62
+ let rafId: number;
63
+ let isRunning = false;
64
+ let totalTime = 0;
65
+
66
+ const tick = (currentTime: number) => {
67
+ if (!isRunning) return;
68
+
69
+ const deltaTime = currentTime - lastTime;
70
+ if (deltaTime >= this.interval) {
71
+ if (this.visible) {
72
+ totalTime += deltaTime;
73
+ callback(totalTime);
74
+ }
75
+ lastTime = currentTime;
76
+ }
77
+
78
+ rafId = requestAnimationFrame(tick);
79
+ };
80
+
81
+ return {
82
+ start() {
83
+ if (!isRunning) {
84
+ isRunning = true;
85
+ lastTime = performance.now();
86
+ rafId = requestAnimationFrame(tick);
87
+ }
88
+ },
89
+ stop() {
90
+ isRunning = false;
91
+ if (rafId) {
92
+ cancelAnimationFrame(rafId);
93
+ }
94
+ }
95
+ };
96
+ }
97
+ }
@@ -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
+ source_id: string;
25
+ source_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
+ }