@jolibox/implement 1.1.4-beta.9 → 1.1.5

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 (58) hide show
  1. package/.rush/temp/package-deps_build.json +33 -33
  2. package/dist/{h5 → common}/ads/ads-action-detection.d.ts +3 -1
  3. package/dist/{h5 → common}/ads/anti-cheating.d.ts +2 -2
  4. package/dist/common/ads/channel-policy.d.ts +26 -0
  5. package/dist/{h5 → common}/ads/index.d.ts +12 -3
  6. package/dist/common/context/index.d.ts +2 -0
  7. package/dist/common/http/uuid.d.ts +2 -2
  8. package/dist/common/http/xua.d.ts +0 -15
  9. package/dist/common/report/base-tracker.d.ts +10 -1
  10. package/dist/common/report/errors/index.d.ts +2 -2
  11. package/dist/common/report/task-track/index.d.ts +3 -0
  12. package/dist/common/report/types.d.ts +2 -55
  13. package/dist/h5/api/index.d.ts +1 -0
  14. package/dist/h5/http/index.d.ts +3 -3
  15. package/dist/h5/report/index.d.ts +1 -1
  16. package/dist/index.d.ts +0 -1
  17. package/dist/index.js +3 -5033
  18. package/dist/index.native.js +3 -3750
  19. package/dist/native/api/keyboard.d.ts +9 -3
  20. package/dist/native/network/index.d.ts +0 -3
  21. package/esbuild.config.js +1 -1
  22. package/implement.build.log +2 -2
  23. package/package.json +3 -3
  24. package/src/{h5 → common}/ads/ads-action-detection.ts +4 -4
  25. package/src/{h5 → common}/ads/anti-cheating.ts +2 -4
  26. package/src/common/ads/channel-policy.ts +52 -0
  27. package/src/{h5 → common}/ads/index.ts +39 -45
  28. package/src/common/api-factory/index.ts +3 -3
  29. package/src/common/context/index.ts +20 -11
  30. package/src/common/http/uuid.ts +2 -10
  31. package/src/common/http/xua.ts +2 -69
  32. package/src/common/report/base-tracker.ts +40 -44
  33. package/src/common/report/errors/index.ts +3 -3
  34. package/src/common/report/errors/report/listeners.ts +0 -2
  35. package/src/common/report/task-track/index.ts +63 -14
  36. package/src/common/report/track.ts +2 -2
  37. package/src/common/report/types.ts +2 -64
  38. package/src/h5/api/ads.ts +24 -0
  39. package/src/h5/api/get-system-info.ts +5 -1
  40. package/src/h5/api/index.ts +1 -0
  41. package/src/h5/http/index.ts +24 -28
  42. package/src/h5/http/utils/__tests__/xua.test.ts +1 -1
  43. package/src/h5/http/utils/session.ts +1 -1
  44. package/src/h5/report/errors/index.ts +1 -1
  45. package/src/h5/report/event-tracker.ts +1 -1
  46. package/src/h5/report/index.ts +5 -4
  47. package/src/index.ts +0 -1
  48. package/src/native/api/ads.ts +32 -8
  49. package/src/native/api/get-system-info.ts +5 -2
  50. package/src/native/api/keyboard.ts +13 -13
  51. package/src/native/bootstrap/index.ts +0 -1
  52. package/src/native/network/index.ts +11 -4
  53. package/src/native/report/index.ts +4 -4
  54. package/src/native/types/native-method-map.d.ts +11 -0
  55. package/dist/common/report/errors/error-types.d.ts +0 -122
  56. package/src/common/report/errors/error-types.ts +0 -206
  57. package/src/h5/http/utils/__tests__/uuid.test.ts +0 -16
  58. /package/dist/h5/{http/utils/__tests__/uuid.test.d.ts → api/ads.d.ts} +0 -0
@@ -1,3 +1,9 @@
1
- export declare const showKeyboard: (...inputs: unknown[]) => Promise<import("@jolibox/types").StandardResponse<void>>;
2
- export declare const updateKeyboard: (...inputs: unknown[]) => Promise<import("@jolibox/types").StandardResponse<void>>;
3
- export declare const hideKeyboard: (...inputs: unknown[]) => Promise<import("@jolibox/types").StandardResponse<void>>;
1
+ export declare const showKeyboard: (params: {
2
+ defaultValue?: string | undefined;
3
+ maxLength?: unknown;
4
+ multiple?: boolean | undefined;
5
+ }) => import("@jolibox/types").StandardResponse<void>;
6
+ export declare const updateKeyboard: (params: {
7
+ value: string;
8
+ }) => import("@jolibox/types").StandardResponse<void>;
9
+ export declare const hideKeyboard: () => import("@jolibox/types").StandardResponse<void>;
@@ -1,6 +1,3 @@
1
- /**
2
- * inner fetch
3
- */
4
1
  export declare const innerFetch: <T>(url: string, innerOptions?: import("./types").FetchOptions & {
5
2
  baseUrl?: string;
6
3
  }) => Promise<{
package/esbuild.config.js CHANGED
@@ -45,7 +45,7 @@ function build(format) {
45
45
  bundle: true,
46
46
  outdir: 'dist',
47
47
  format: format,
48
- // minify: true,
48
+ minify: true,
49
49
  loader: {
50
50
  '.ts': 'ts',
51
51
  '.tsx': 'tsx',
@@ -1,9 +1,9 @@
1
1
  Invoking: npm run clean && npm run build:esm && tsc
2
2
 
3
- > @jolibox/implement@1.1.4-beta.9 clean
3
+ > @jolibox/implement@1.1.5 clean
4
4
  > rimraf ./dist
5
5
 
6
6
 
7
- > @jolibox/implement@1.1.4-beta.9 build:esm
7
+ > @jolibox/implement@1.1.5 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,13 +1,13 @@
1
1
  {
2
2
  "name": "@jolibox/implement",
3
3
  "description": "This project is Jolibox JS-SDk implement for Native && H5",
4
- "version": "1.1.4-beta.9",
4
+ "version": "1.1.5",
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.4-beta.9",
10
- "@jolibox/types": "1.1.4-beta.9",
9
+ "@jolibox/common": "1.1.5",
10
+ "@jolibox/types": "1.1.5",
11
11
  "localforage": "1.10.0"
12
12
  },
13
13
  "devDependencies": {
@@ -1,21 +1,21 @@
1
- import { track } from '../report';
1
+ import { Track } from '../report';
2
2
 
3
3
  export class AdsActionDetection {
4
4
  adBreakIsShowing = false;
5
5
 
6
6
  private reportPageJumpOut = () => {
7
- track('AdBreakJumpOut', {
7
+ this.track('AdBreakJumpOut', {
8
8
  context: 'AdsActionDetection'
9
9
  });
10
10
  };
11
11
 
12
12
  private reportPageHide = () => {
13
- track('AdBreakHide', {
13
+ this.track('AdBreakHide', {
14
14
  context: 'AdsActionDetection'
15
15
  });
16
16
  };
17
17
 
18
- constructor() {
18
+ constructor(readonly track: Track) {
19
19
  window.addEventListener('visibilitychange', () => {
20
20
  if (document.hidden && this.adBreakIsShowing) {
21
21
  this.reportPageHide();
@@ -1,5 +1,3 @@
1
- import { tracker } from '../report/event-tracker';
2
-
3
1
  export type AdsDisplayPermission =
4
2
  | 'BLOCK_INITIAL'
5
3
  | 'BANNED_FOR_SESSION'
@@ -41,10 +39,10 @@ export class AdsAntiCheating {
41
39
 
42
40
  private initTimestamp = 0;
43
41
  // private context: JoliboxSDKPipeExecutor;
44
- private report = tracker;
45
42
 
46
43
  constructor(
47
44
  // context: JoliboxSDKPipeExecutor,
45
+ readonly checkNetwork: () => boolean,
48
46
  config?: IAdsAntiCheatingConfig
49
47
  ) {
50
48
  this.maxAllowedAdsForTime = config?.maxAllowedAdsForTime ?? 8;
@@ -220,7 +218,7 @@ export class AdsAntiCheating {
220
218
  };
221
219
 
222
220
  public checkAdsDisplayPermission: (type: 'reward' | 'interstitial') => AdsDisplayPermission = (type) => {
223
- if (!this.report.networkIsOk) {
221
+ if (!this.checkNetwork()) {
224
222
  return 'NETWORK_NOT_OK';
225
223
  }
226
224
 
@@ -0,0 +1,52 @@
1
+ import { context } from '../context';
2
+
3
+ export interface IChannelPolicy {
4
+ /**
5
+ * The rate at which interstitial ads should be shown.
6
+ * A value of 1 means always bypass interstitials call, while a value of 0 means always forbid calling interstitials.
7
+ * A value between 0 and 1 means the interstitial call will be bypassed with a probability of `rate`.
8
+ */
9
+ interstitialRate?: number;
10
+ }
11
+
12
+ interface HttpClient {
13
+ get<T>(url: string, options?: { query?: Record<string, string> }): Promise<T>;
14
+ }
15
+
16
+ export class ChannelPolicy {
17
+ private configs: Record<string, IChannelPolicy> | null = null;
18
+ private firstRun = true;
19
+
20
+ constructor(readonly httpClient: HttpClient) {
21
+ this.init();
22
+ }
23
+
24
+ private async init() {
25
+ try {
26
+ this.configs = await this.httpClient.get<Record<string, IChannelPolicy>>(
27
+ '/api/fe-configs/js-sdk/ads-channel-config'
28
+ );
29
+ } catch (error) {
30
+ // no-op
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Should bypass calling interstitial based on the channel policy.
36
+ * @returns
37
+ */
38
+ shouldBypassCallingInterstitial() {
39
+ let result;
40
+ if (!this.configs) {
41
+ this.init();
42
+ result = !this.firstRun; // If configs are not loaded yet, should forbid calling interstitial on first run.
43
+ } else {
44
+ const channel = context.channel ?? '';
45
+ const policy: IChannelPolicy | undefined = this.configs[channel];
46
+ const rate = policy?.interstitialRate ?? 1;
47
+ result = Math.random() < rate; // Bypass interstitial calling with the given rate.
48
+ }
49
+ this.firstRun = false;
50
+ return result;
51
+ }
52
+ }
@@ -1,10 +1,8 @@
1
- import { createCommands } from '@jolibox/common';
2
1
  import { AdsActionDetection } from './ads-action-detection';
3
2
  import { AdsAntiCheating } from './anti-cheating';
4
- import JoliboxHttpClient from '../http';
5
- import { track } from '../report';
6
-
7
- const commands = createCommands();
3
+ import { Track } from '../report';
4
+ import { context } from '../context';
5
+ import { ChannelPolicy } from './channel-policy';
8
6
 
9
7
  declare global {
10
8
  interface Window {
@@ -255,6 +253,10 @@ interface IJoliboxAdsResponse {
255
253
  };
256
254
  }
257
255
 
256
+ interface HttpClient {
257
+ get<T>(url: string, options?: { query?: Record<string, string> }): Promise<T>;
258
+ }
259
+
258
260
  /**
259
261
  * Jolibox Ads SDK
260
262
  */
@@ -264,18 +266,18 @@ export class JoliboxAdsImpl {
264
266
  public clientId?: string;
265
267
  public channelId?: string;
266
268
  public unitId?: string;
267
- private httpClient = new JoliboxHttpClient();
268
- // private context: JoliboxSDKPipeExecutor;
269
+
269
270
  private antiCheating: AdsAntiCheating;
270
271
  private adsActionDetection: AdsActionDetection;
272
+ private channelPolicy: ChannelPolicy;
271
273
 
272
274
  /**
273
275
  * Internal constructor, should not be called directly
274
276
  */
275
- constructor(/*context: JoliboxSDKPipeExecutor*/) {
276
- // this.context = context;
277
- this.antiCheating = new AdsAntiCheating(/*context*/);
278
- this.adsActionDetection = new AdsActionDetection(/*context*/);
277
+ constructor(readonly track: Track, readonly httpClient: HttpClient, readonly checkNetwork: () => boolean) {
278
+ this.antiCheating = new AdsAntiCheating(checkNetwork);
279
+ this.adsActionDetection = new AdsActionDetection(this.track);
280
+ this.channelPolicy = new ChannelPolicy(httpClient);
279
281
  }
280
282
 
281
283
  /**
@@ -284,7 +286,7 @@ export class JoliboxAdsImpl {
284
286
  * @returns
285
287
  */
286
288
  public init(config?: IAdsInitParams) {
287
- track('CallAdsInit', null);
289
+ this.track('CallAdsInit', null);
288
290
  if (typeof window === 'undefined') {
289
291
  // skip if not in browser
290
292
  return;
@@ -296,13 +298,11 @@ export class JoliboxAdsImpl {
296
298
  }
297
299
 
298
300
  private getGameId = () => {
299
- const urlParams = new URLSearchParams(window.location.search);
300
- return urlParams.get('gameId') ?? this.config.gameId;
301
+ return context.mpId;
301
302
  };
302
303
 
303
304
  private getTestAdsMode = () => {
304
- const urlParams = new URLSearchParams(window.location.search);
305
- return urlParams.get('testAdsMode') === 'true';
305
+ return context.testAdsMode;
306
306
  };
307
307
 
308
308
  private asyncLoad = async () => {
@@ -358,7 +358,7 @@ export class JoliboxAdsImpl {
358
358
  script.setAttribute('data-ad-channel', this.channelId);
359
359
  }
360
360
  document.head.appendChild(script);
361
- track('LoadAdsenseCompleted', null);
361
+ this.track('LoadAdsenseCompleted', null);
362
362
  }
363
363
  };
364
364
 
@@ -376,7 +376,7 @@ export class JoliboxAdsImpl {
376
376
  */
377
377
  public adConfig = (params: IAdConfigParams) => {
378
378
  const { onReady, ...paramsToTrack } = params;
379
- track('CallAdConfig', paramsToTrack);
379
+ this.track('CallAdConfig', paramsToTrack);
380
380
  if (!this.configured) {
381
381
  this.configured = true;
382
382
  this.push(params);
@@ -417,6 +417,20 @@ export class JoliboxAdsImpl {
417
417
  console.warn('Game ID is not set, skip calling adBreak');
418
418
  return;
419
419
  }
420
+
421
+ if (params.type !== 'reward') {
422
+ const shouldBypassCallingInterstitial = this.channelPolicy.shouldBypassCallingInterstitial();
423
+ if (!shouldBypassCallingInterstitial) {
424
+ params.adBreakDone?.({
425
+ breakType: params.type,
426
+ breakName: 'skipInterstitials',
427
+ breakFormat: 'interstitial',
428
+ breakStatus: 'viewed'
429
+ });
430
+ return;
431
+ }
432
+ }
433
+
420
434
  const adsDisplayPermission = this.antiCheating.checkAdsDisplayPermission(
421
435
  params.type === 'reward' ? 'reward' : 'interstitial'
422
436
  );
@@ -431,7 +445,7 @@ export class JoliboxAdsImpl {
431
445
  breakFormat: params.type === 'reward' ? 'reward' : 'interstitial',
432
446
  breakStatus: 'noAdPreloaded'
433
447
  });
434
- track('PreventAdsCheating', {
448
+ this.track('PreventAdsCheating', {
435
449
  reason: adsDisplayPermission
436
450
  });
437
451
  return;
@@ -444,7 +458,7 @@ export class JoliboxAdsImpl {
444
458
  breakFormat: params.type === 'reward' ? 'reward' : 'interstitial',
445
459
  breakStatus: 'viewed' // TODO: need to confirm
446
460
  });
447
- track('PreventAdsCheating', {
461
+ this.track('PreventAdsCheating', {
448
462
  reason: adsDisplayPermission
449
463
  });
450
464
  return;
@@ -459,7 +473,7 @@ export class JoliboxAdsImpl {
459
473
  const originAdBreakDone = params.adBreakDone;
460
474
  const adBreakDone = (placementInfo: IPlacementInfo) => {
461
475
  this.adsActionDetection.adBreakIsShowing = false;
462
- track('CallAdBreakDone', {
476
+ this.track('CallAdBreakDone', {
463
477
  breakType: placementInfo.breakType,
464
478
  breakName: placementInfo.breakName ?? '',
465
479
  breakFormat: placementInfo.breakFormat,
@@ -476,7 +490,7 @@ export class JoliboxAdsImpl {
476
490
  if (params.type === 'reward') {
477
491
  const originBeforeReward = params.beforeReward;
478
492
  const wrapShowAdFn = (originShowAdFn: () => void) => () => {
479
- track('CallShowAdFn', null);
493
+ this.track('CallShowAdFn', null);
480
494
  originShowAdFn();
481
495
  };
482
496
  const beforeReward = (originShowAdFn: () => void) => {
@@ -502,7 +516,7 @@ export class JoliboxAdsImpl {
502
516
  break;
503
517
  }
504
518
  this.adsActionDetection.adBreakIsShowing = true;
505
- track('CallAdBreak', paramsToTrack);
519
+ this.track('CallAdBreak', paramsToTrack);
506
520
  this.push(params);
507
521
 
508
522
  // https://rsgce94md216.sg.larksuite.com/wiki/JGrAwRk7BiRJyekzJmKlbOawg7e?from=from_copylink
@@ -524,7 +538,7 @@ export class JoliboxAdsImpl {
524
538
  * @returns
525
539
  */
526
540
  public adUnit = async (params: IAdUnitParams) => {
527
- track('CallAdUnit', {
541
+ this.track('CallAdUnit', {
528
542
  adFormat: params.adFormat?.toString() ?? null,
529
543
  fullWidthResponsive: params.fullWidthResponsive ?? null
530
544
  });
@@ -621,7 +635,7 @@ export class JoliboxAdsImpl {
621
635
  mutations.forEach((mutation) => {
622
636
  if (mutation.type === 'attributes' && mutation.attributeName === 'data-ad-status') {
623
637
  const status = ins.getAttribute('data-ad-status');
624
- track('AdSenseUnitStatusChanged', {
638
+ this.track('AdSenseUnitStatusChanged', {
625
639
  status: status ?? 'null'
626
640
  });
627
641
  }
@@ -636,23 +650,3 @@ export class JoliboxAdsImpl {
636
650
  }
637
651
  };
638
652
  }
639
-
640
- export default JoliboxAdsImpl;
641
-
642
- const ads = new JoliboxAdsImpl();
643
-
644
- commands.registerCommand('AdsSDK.init', (config) => {
645
- ads.init(config);
646
- });
647
-
648
- commands.registerCommand('AdsSDK.adConfig', (params) => {
649
- ads.adConfig(params);
650
- });
651
-
652
- commands.registerCommand('AdsSDK.adBreak', (params) => {
653
- ads.adBreak(params);
654
- });
655
-
656
- commands.registerCommand('AdsSDK.adUnit', (params) => {
657
- ads.adUnit(params);
658
- });
@@ -4,7 +4,7 @@ import { ResponseType, StandardResponse } from '@jolibox/types';
4
4
  import { Track } from '../report';
5
5
  import { reportError } from '../report/errors/report';
6
6
  import { Type, typeAsserts, Validator } from './validator';
7
- import { createAPIError, createUserAPIError } from '../report/errors';
7
+ import { createUserValidateAPIError } from '../report/errors';
8
8
 
9
9
  let traceId = 1;
10
10
 
@@ -65,7 +65,7 @@ export const createAPIFactory = (track: Track) => {
65
65
  typeAsserts(config.paramsSchema, inputs, 'params');
66
66
  } catch (err: unknown) {
67
67
  const error = err as { message: string };
68
- throw createUserAPIError(`${method}:fail ${error.message}`);
68
+ throw createUserValidateAPIError(`${method}:fail ${error.message}`);
69
69
  }
70
70
  }
71
71
  const res = config.implement(...inputs);
@@ -142,7 +142,7 @@ export const createAPIFactory = (track: Track) => {
142
142
  try {
143
143
  typeAsserts(config.paramsSchema, inputs, 'params');
144
144
  } catch (error) {
145
- throw createUserAPIError((error as Error).message);
145
+ throw createUserValidateAPIError((error as Error).message);
146
146
  }
147
147
  }
148
148
 
@@ -2,7 +2,8 @@ import { mergeArray, mergeWith } from '@jolibox/common';
2
2
  import { DeviceInfo, HostInfo, HostUserInfo, SdkInfo } from './types';
3
3
  import { Env } from '@jolibox/types';
4
4
  import { parseUrlQuery } from './url-parse';
5
- import { getAppVersion, getDeviceId } from '../http/xua';
5
+ import { getAppVersion } from '../http/xua';
6
+ import { getDeviceId } from '@jolibox/common';
6
7
 
7
8
  declare const globalThis: {
8
9
  joliboxJSCore?: {
@@ -24,7 +25,7 @@ const defaultEnv: Env = {
24
25
  nativeSDKVersion: '',
25
26
  jssdkVersion: getAppVersion()
26
27
  },
27
- schema: '',
28
+ schema: window.location.href,
28
29
  platform: 'h5'
29
30
  };
30
31
 
@@ -33,14 +34,16 @@ const nativeEnv = globalThis.joliboxJSCore?.env;
33
34
  const env = Object.assign({}, nativeEnv?.() ?? defaultEnv);
34
35
 
35
36
  const wrapContext = () => {
36
- const { payloadJson, headerJson } = parseUrlQuery(env.schema);
37
+ const { payloadJson, headerJson } = env.schema.length ? parseUrlQuery(env.schema) : {};
37
38
  const defaultSessionId = `${env.deviceInfo.did}-${new Date().getTime()}`;
38
- const urlParams = new URLSearchParams(window.location.search);
39
- const defaultGameID = urlParams.get('appId') ?? urlParams.get('gameId') ?? '';
40
- const sessionId = payloadJson.sessionId ?? urlParams.get('sessionId') ?? defaultSessionId;
41
- const testAdsMode = !!(payloadJson.testAdsMode ?? urlParams.get('testAdsMode') === 'true');
42
- const joliboxEnv = payloadJson.joliboxEnv ?? urlParams.get('joliboxEnv') ?? 'production';
39
+ const url = new URL(env.schema.length ? env.schema : window.location.href);
40
+ const urlParams = url.searchParams;
41
+ const defaultGameID = urlParams.get('mpId') ?? urlParams.get('appId') ?? urlParams.get('gameId') ?? '';
42
+ const sessionId = payloadJson?.sessionId ?? urlParams.get('sessionId') ?? defaultSessionId;
43
+ const testAdsMode = !!(payloadJson?.testAdsMode ?? urlParams.get('testAdsMode') === 'true');
44
+ const joliboxEnv = payloadJson?.joliboxEnv ?? urlParams.get('joliboxEnv') ?? 'production';
43
45
  const testMode = joliboxEnv === 'staging';
46
+ const channel = headerJson?.channel;
44
47
 
45
48
  return {
46
49
  get testMode(): boolean {
@@ -52,14 +55,17 @@ const wrapContext = () => {
52
55
  get joliboxEnv() {
53
56
  return joliboxEnv;
54
57
  },
58
+ get joliSource(): string | null {
59
+ return urlParams.get('joliSource') ?? null;
60
+ },
55
61
  get mpId(): string {
56
- return defaultGameID ?? payloadJson['id'];
62
+ return defaultGameID ?? payloadJson?.id ?? '';
57
63
  },
58
64
  get mpVersion(): string {
59
- return headerJson.ver ?? getAppVersion();
65
+ return headerJson?.ver ?? getAppVersion();
60
66
  },
61
67
  get platform(): Env['platform'] {
62
- return env.platform;
68
+ return env.platform ?? env.deviceInfo.platform;
63
69
  },
64
70
  get deviceInfo(): DeviceInfo {
65
71
  return env.deviceInfo;
@@ -76,6 +82,9 @@ const wrapContext = () => {
76
82
  get sessionId(): string {
77
83
  return env.clientSessionId ?? sessionId ?? defaultSessionId;
78
84
  },
85
+ get channel(): string | undefined {
86
+ return channel;
87
+ },
79
88
  onEnvConfigChanged: (newConfig: Partial<Env>) => {
80
89
  mergeWith(env, newConfig, mergeArray);
81
90
  }
@@ -1,11 +1,3 @@
1
- export const uuidv4 = () => {
2
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
3
- const r = (Math.random() * 16) | 0;
4
- const v = c === 'x' ? r : (r & 0x3) | 0x8;
5
- return v.toString(16);
6
- });
7
- };
1
+ import { uuidv4, isValidUUIDV4 } from '@jolibox/common';
8
2
 
9
- export const isValidUUIDV4 = (uuid: string) => {
10
- return /^[\da-f]{8}-[\da-f]{4}-4[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i.test(uuid);
11
- };
3
+ export { uuidv4, isValidUUIDV4 };
@@ -1,34 +1,4 @@
1
- import { uuidv4 } from './uuid';
2
-
3
- export const platform = {
4
- isiOS:
5
- navigator.userAgent.includes('iPhone') ||
6
- navigator.userAgent.includes('iPod') ||
7
- navigator.userAgent.includes('iPad') ||
8
- navigator.userAgent.includes('iPhone OS'),
9
- iosVersion: () => {
10
- const v = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/) as string[];
11
- return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || '0', 10)];
12
- },
13
- isAndroid: navigator.userAgent.includes('Android'),
14
- isMac: navigator.userAgent.includes('Mac'),
15
- isFacebook: navigator.userAgent.includes('FB_IAB'),
16
- isPC: !navigator.userAgent.includes('iPhone') && !navigator.userAgent.includes('Android')
17
- };
18
-
19
- export const getPlatform = () => {
20
- if (platform.isiOS) {
21
- return 'iOS';
22
- } else if (platform.isAndroid) {
23
- return 'Android';
24
- } else if (platform.isMac) {
25
- return 'Mac';
26
- } else if (platform.isFacebook) {
27
- return 'Facebook';
28
- } else {
29
- return 'PC';
30
- }
31
- };
1
+ import { xUserAgent as xua } from '@jolibox/common';
32
2
 
33
3
  const jssdkVersion = '__JOLIBOX_LOCAL_SDK_VERSION__';
34
4
 
@@ -36,44 +6,7 @@ export const getAppVersion = () => {
36
6
  return jssdkVersion;
37
7
  };
38
8
 
39
- export const DEVICE_ID = 'device_id';
40
- export const ADVERTISING_ID = 'advertising_id';
41
-
42
- export const getStorage = (cookieKey: string) => {
43
- if (!localStorage.getItem(cookieKey)) {
44
- localStorage.setItem(cookieKey, uuidv4());
45
- }
46
- return localStorage.getItem(cookieKey)!;
47
- };
48
-
49
- export const getDeviceId = () => {
50
- return getStorage(DEVICE_ID);
51
- };
52
-
53
- export const getAdvertisingId = () => {
54
- return getStorage(ADVERTISING_ID);
55
- };
56
-
57
- const firstCharUpperCase = (chars: string) => chars.charAt(0).toUpperCase() + chars.slice(1);
58
-
59
- export const getUtmSource = () => {
60
- const urlParams = new URLSearchParams(window.location.search);
61
- return firstCharUpperCase(urlParams.get('utm_source') ?? '') || 'JoliboxSDK';
62
- };
63
-
64
9
  export const xUserAgent = () => {
65
- const applicationName = 'JoliboxWebSDK';
66
- const thePlatform = getPlatform();
67
- const locale = navigator.language;
68
- const deviceId = getDeviceId();
69
- const adid = getAdvertisingId();
70
10
  const appVersion = getAppVersion();
71
- // const parserResult = getUAParser();
72
- // const deviceModel = parserResult.device.model;
73
- const deviceModel = 'UnknownModel';
74
- // const systemVersion = parserResult.os.version;
75
- const systemVersion = 'UnknownSystemVersion';
76
- return `${applicationName} (${getUtmSource()}${thePlatform}; ${deviceModel}; ${systemVersion}; ${locale}) uuid/${deviceId} adid/${adid} version/${
77
- appVersion || ''
78
- }`;
11
+ return `${xua(appVersion)}`;
79
12
  };
@@ -1,51 +1,18 @@
1
1
  import { context } from '@/common/context';
2
2
  import { logger } from '@jolibox/common';
3
- import { IDevice, IPage, IEventPackage, IEvent, EProject } from '@/common/report/types';
3
+ import { IDevice, IPage, IEventPackage, IEvent, EProject, serializeEventPackage } from '@jolibox/common';
4
4
 
5
- const keysOfPage: (keyof IPage)[] = ['name', 'params'];
6
- const keysOfEvent: (keyof IEvent)[] = ['name', 'type', 'location', 'target', 'extra', 'timestamp', 'userId'];
7
- const keysOfDevice: (keyof IDevice)[] = [
8
- 'platform',
9
- 'os',
10
- 'appVersion',
11
- 'appId',
12
- 'model',
13
- 'brand',
14
- 'uuid',
15
- 'jsSdkVersion',
16
- 'extra'
17
- ];
18
- function serializeObject<T>(obj: T, order: (keyof T)[]): any[] {
19
- return order.map((key) => {
20
- if (key === 'params' && obj[key]) {
21
- const params = obj[key] as Record<string, any>;
22
- return Object.keys(params).reduce((acc, k) => {
23
- acc[k] = String(params[k]);
24
- return acc;
25
- }, {} as Record<string, any>);
26
- }
27
- return obj[key];
28
- });
29
- }
30
-
31
- function serializeEvent(event: IEvent): any[] {
32
- const location = event.location ? serializeObject(event.location, keysOfPage) : null;
33
- const target = event.target ? serializeObject(event.target, keysOfPage) : null;
34
-
35
- // 定义IEvent的序列化顺序
36
- return serializeObject({ ...event, location, target }, keysOfEvent);
37
- }
38
-
39
- function serializeEventPackage(eventPackage: IEventPackage): any[] {
40
- // 序列化事件数组
41
- const events = eventPackage.events.map((event) => serializeEvent(event));
42
-
43
- // 定义IDevice的序列化顺序
44
- const device = serializeObject(eventPackage.device, keysOfDevice);
45
-
46
- return [eventPackage.protocolVersion, events, device, eventPackage.project];
5
+ interface ISamplesConfig {
6
+ samples: {
7
+ [sampleRate: number]: string[];
8
+ };
9
+ cluster: number;
47
10
  }
48
-
11
+ /**
12
+ * Base class for event tracker, all the event tracker should inherit from this class
13
+ * Send event to oss reporter
14
+ * @abstract
15
+ */
49
16
  export abstract class EventTracker {
50
17
  deviceInfo: IDevice | null = null;
51
18
 
@@ -54,6 +21,7 @@ export abstract class EventTracker {
54
21
  abstract doReport<T extends Record<string, unknown>>(event: unknown[], extra?: T): void;
55
22
 
56
23
  private pastTrackings: boolean[] = [];
24
+ private samplesConfig: ISamplesConfig | null = null;
57
25
 
58
26
  private pushPastTracking = (tracking: boolean) => {
59
27
  this.pastTrackings.push(tracking);
@@ -62,6 +30,10 @@ export abstract class EventTracker {
62
30
  }
63
31
  };
64
32
 
33
+ constructor() {
34
+ this.fetchSamplesConfig();
35
+ }
36
+
65
37
  public get networkIsOk() {
66
38
  // in the past 10 trackings, at least 60% of them are successful
67
39
  return (
@@ -70,6 +42,26 @@ export abstract class EventTracker {
70
42
  );
71
43
  }
72
44
 
45
+ private async fetchSamplesConfig() {
46
+ const host = context.testMode ? 'https://stg-api.jolibox.com' : 'https://api.jolibox.com';
47
+ const path = `${host}/api/fe-configs/js-sdk/samples-config`;
48
+ const samplesConfig = (await (await fetch(path)).json()) as ISamplesConfig;
49
+ this.samplesConfig = samplesConfig;
50
+ }
51
+
52
+ private checkIsSampled(event: IEvent) {
53
+ if (!this.samplesConfig) {
54
+ return false;
55
+ }
56
+ const { samples } = this.samplesConfig;
57
+ for (const [sampleRate, events] of Object.entries(samples)) {
58
+ if (events.includes(event.name)) {
59
+ return Math.floor(Math.random() * 101) < parseFloat(sampleRate);
60
+ }
61
+ }
62
+ return false;
63
+ }
64
+
73
65
  getDevice(): IDevice {
74
66
  const { nativeSDKVersion, jssdkVersion } = context.sdkInfo;
75
67
  return {
@@ -98,6 +90,10 @@ export abstract class EventTracker {
98
90
  }
99
91
 
100
92
  async trackEvent(event: IEvent, project: EProject, extraInfo?: Record<string, unknown>) {
93
+ // check if the event is sampled
94
+ if (this.checkIsSampled(event)) {
95
+ return;
96
+ }
101
97
  const location = event.location ? event.location : await this.getLocation();
102
98
  const target = event.target || null;
103
99
  const extra = event.extra || null;