@jolibox/implement 1.1.12 → 1.1.13-beta.1

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 (37) hide show
  1. package/.rush/temp/package-deps_build.json +21 -18
  2. package/.rush/temp/shrinkwrap-deps.json +1 -1
  3. package/dist/common/context/index.d.ts +5 -0
  4. package/dist/common/context/url-parse.d.ts +8 -1
  5. package/dist/index.js +3 -3
  6. package/dist/index.native.js +110 -4
  7. package/dist/native/api/index.d.ts +1 -0
  8. package/dist/native/api/navigate.d.ts +1 -0
  9. package/dist/native/bootstrap/bridge.d.ts +1 -1
  10. package/dist/native/js-bridge/const.d.ts +1 -0
  11. package/dist/native/js-bridge/publish.d.ts +1 -0
  12. package/dist/native/js-bridge/subscribe.d.ts +2 -0
  13. package/dist/native/js-bridge/types.d.ts +4 -0
  14. package/dist/native/js-core/jolibox-js-core.d.ts +8 -3
  15. package/dist/native/network/create-fetch.d.ts +1 -0
  16. package/dist/native/ui/retention.d.ts +1 -0
  17. package/implement.build.log +2 -2
  18. package/package.json +4 -3
  19. package/src/common/context/index.ts +22 -3
  20. package/src/common/context/url-parse.ts +24 -1
  21. package/src/native/api/index.ts +1 -0
  22. package/src/native/api/lifecycle.ts +16 -4
  23. package/src/native/api/navigate.ts +61 -0
  24. package/src/native/api/request.ts +19 -10
  25. package/src/native/bootstrap/bridge.ts +10 -1
  26. package/src/native/bootstrap/index.ts +100 -23
  27. package/src/native/js-bridge/const.ts +2 -0
  28. package/src/native/js-bridge/js-bridge.ts +7 -2
  29. package/src/native/js-bridge/publish.ts +44 -0
  30. package/src/native/js-bridge/subscribe.ts +25 -1
  31. package/src/native/js-bridge/types.ts +10 -0
  32. package/src/native/js-core/jolibox-js-core.ts +30 -26
  33. package/src/native/network/create-fetch.ts +1 -0
  34. package/src/native/network/utils.ts +13 -6
  35. package/src/native/types/global.d.ts +1 -0
  36. package/src/native/types/native-method-map.d.ts +29 -0
  37. package/src/native/ui/retention.ts +152 -0
@@ -2,6 +2,7 @@ export type Listener = (...args: any[]) => any;
2
2
  export type On = <T extends string>(event: T, handler: Listener) => void;
3
3
  export type Off = <T extends string>(event: T, handler: Listener) => void;
4
4
  export type InvokeHandler = (callbackId: string | number, data: string | Record<string, unknown>) => void;
5
+ export type Publish = (event: string, data: Record<string, unknown>, webviewId?: number, force?: boolean) => void;
5
6
  export type SubscribeHandler = (event: string, data: string | Record<string, unknown>, webviewId?: number) => void;
6
7
  export type AnyFunction = (...args: any[]) => any;
7
8
  export interface JSBridge {
@@ -11,4 +12,7 @@ export interface JSBridge {
11
12
  applyNative: jsb.service.ApplyNative;
12
13
  onNative: jsb.service.OnNative;
13
14
  offNative: jsb.service.OffNative;
15
+ publish: Publish;
16
+ subscribe: On;
17
+ unsubscribe: Off;
14
18
  }
@@ -5,6 +5,11 @@ declare global {
5
5
  invoke: (invokeString: string) => void;
6
6
  onDocumentReady: (paramsstr: string) => void;
7
7
  doExit: (uuidString: string) => void;
8
+ publish: (params: {
9
+ event: string;
10
+ webviewIds: number[] | '*';
11
+ paramsString: string;
12
+ }) => void;
8
13
  };
9
14
  webkit?: {
10
15
  messageHandlers: {
@@ -18,7 +23,7 @@ declare global {
18
23
  publish: {
19
24
  postMessage: (params: {
20
25
  event: string;
21
- webviewIds: string;
26
+ webviewIds: number[] | '*';
22
27
  paramsString: string;
23
28
  }) => void;
24
29
  };
@@ -39,7 +44,7 @@ declare global {
39
44
  }
40
45
  export declare const RuntimeLoader: {
41
46
  trigger(): void;
42
- exit(): boolean;
47
+ exit(): Promise<boolean>;
43
48
  onReady(fn: () => void): void;
44
- doExit(fn: () => boolean): void;
49
+ doExit(fn: () => Promise<boolean>): void;
45
50
  };
@@ -15,6 +15,7 @@ type PublicFetch = <T>(url: string, options?: FetchOptions) => Promise<{
15
15
  }>;
16
16
  export declare function createFetch(createMethod: 'createRequestTaskSync', operateMethod: 'operateRequestTaskSync', options?: {
17
17
  baseUrl?: string;
18
+ defaultHeaders?: Record<string, string>;
18
19
  type: 'public';
19
20
  }): PublicFetch;
20
21
  export declare function createFetch(createMethod: 'createRequestTaskSync', operateMethod: 'operateRequestTaskSync', options?: {
@@ -0,0 +1 @@
1
+ export declare function openRetentionSchema(): Promise<boolean>;
@@ -1,9 +1,9 @@
1
1
  Invoking: npm run clean && npm run build:esm && tsc
2
2
 
3
- > @jolibox/implement@1.1.12 clean
3
+ > @jolibox/implement@1.1.13-beta.1 clean
4
4
  > rimraf ./dist
5
5
 
6
6
 
7
- > @jolibox/implement@1.1.12 build:esm
7
+ > @jolibox/implement@1.1.13-beta.1 build:esm
8
8
  > BUILD_VERSION=$(node -p "require('./package.json').version") node esbuild.config.js --format=esm
9
9
 
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@jolibox/implement",
3
3
  "description": "This project is Jolibox JS-SDk implement for Native && H5",
4
- "version": "1.1.12",
4
+ "version": "1.1.13-beta.1",
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.12",
10
- "@jolibox/types": "1.1.12",
9
+ "@jolibox/common": "1.1.13-beta.1",
10
+ "@jolibox/types": "1.1.13-beta.1",
11
11
  "localforage": "1.10.0",
12
+ "@jolibox/ui": "1.0.0",
12
13
  "web-vitals": "4.2.4"
13
14
  },
14
15
  "devDependencies": {
@@ -1,7 +1,7 @@
1
1
  import { mergeArray, mergeWith } from '@jolibox/common';
2
2
  import { DeviceInfo, HostInfo, HostUserInfo, SdkInfo } from './types';
3
3
  import { Env } from '@jolibox/types';
4
- import { parseUrlQuery } from './url-parse';
4
+ import { parseUrlQuery, encodeJoliSourceQuery, QueryParams } from './url-parse';
5
5
  import { getAppVersion } from '../http/xua';
6
6
  import { getDeviceId } from '@jolibox/common';
7
7
 
@@ -36,12 +36,13 @@ const env = Object.assign({}, nativeEnv?.() ?? defaultEnv);
36
36
  type MPType = 'game' | 'miniApp';
37
37
 
38
38
  const wrapContext = () => {
39
- const { payloadJson, headerJson } = env.schema.length ? parseUrlQuery(env.schema) : {};
39
+ const { payloadJson, headerJson, signature } = env.schema.length ? parseUrlQuery(env.schema) : {};
40
40
  const defaultSessionId = `${env.deviceInfo.did}-${new Date().getTime()}`;
41
41
  const url = new URL(env.schema.length ? env.schema : window.location.href);
42
42
  const urlParams = url.searchParams;
43
43
  const defaultGameID = urlParams.get('mpId') ?? urlParams.get('appId') ?? urlParams.get('gameId') ?? '';
44
- const sessionId = payloadJson?.sessionId ?? urlParams.get('sessionId') ?? defaultSessionId;
44
+ const sessionId =
45
+ env.clientSessionId ?? payloadJson?.sessionId ?? urlParams.get('sessionId') ?? defaultSessionId;
45
46
  const testAdsMode = !!(payloadJson?.testAdsMode ?? urlParams.get('testAdsMode') === 'true');
46
47
  const joliboxEnv = payloadJson?.joliboxEnv ?? urlParams.get('joliboxEnv') ?? 'production';
47
48
  const testMode = joliboxEnv === 'staging';
@@ -93,8 +94,26 @@ const wrapContext = () => {
93
94
  get webviewId(): number {
94
95
  return env.webviewId ?? -1;
95
96
  },
97
+ get shouldInterupt(): boolean | undefined {
98
+ return payloadJson?.__shouldInterupt;
99
+ },
100
+ get from(): number | undefined {
101
+ return payloadJson?.__from;
102
+ },
103
+ get baseApiHost(): string {
104
+ return testMode ? 'https://stg-api.jolibox.com' : 'https://api.jolibox.com';
105
+ },
96
106
  onEnvConfigChanged: (newConfig: Partial<Env>) => {
97
107
  mergeWith(env, newConfig, mergeArray);
108
+ },
109
+ encodeJoliSourceQuery: (newPayloadJson: QueryParams['payloadJson']) => {
110
+ if (headerJson && signature) {
111
+ return encodeJoliSourceQuery(
112
+ { ...payloadJson, ...newPayloadJson },
113
+ urlParams.get('joliSource') ?? ''
114
+ );
115
+ }
116
+ return urlParams.get('joliSource') ?? '';
98
117
  }
99
118
  };
100
119
  };
@@ -14,6 +14,12 @@ interface PayloadJson {
14
14
  joliboxEnv?: 'staging' | 'production';
15
15
  sessionId?: string;
16
16
  __mpType?: 'game' | 'miniApp';
17
+ __orientation?: 'HORIZONTAL' | 'VERTICAL';
18
+ __transparent?: boolean;
19
+ __entryPath?: string;
20
+ __showStatusBar?: boolean;
21
+ __shouldInterupt?: boolean;
22
+ __from?: number; // 从哪个小程序打开的
17
23
  }
18
24
 
19
25
  interface Signature {
@@ -21,7 +27,7 @@ interface Signature {
21
27
  payloadSig?: string;
22
28
  }
23
29
 
24
- interface QueryParams {
30
+ export interface QueryParams {
25
31
  headerJson: HeaderJson;
26
32
  payloadJson: PayloadJson;
27
33
  signature: Signature;
@@ -38,6 +44,12 @@ const base64UrlDecode = <T>(input: string): T => {
38
44
  }
39
45
  };
40
46
 
47
+ const base64UrlEncode = <T>(input: T): string => {
48
+ const jsonStr = JSON.stringify(input);
49
+ const base64 = btoa(jsonStr);
50
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
51
+ };
52
+
41
53
  export const parseUrlQuery = (url: string): QueryParams => {
42
54
  try {
43
55
  const urlObj = new URL(url);
@@ -63,3 +75,14 @@ export const parseUrlQuery = (url: string): QueryParams => {
63
75
  };
64
76
  }
65
77
  };
78
+
79
+ export const encodeJoliSourceQuery = (payloadJson: PayloadJson, originJoliSource: string): string => {
80
+ const joli_source = originJoliSource.split('.');
81
+ if (joli_source && joli_source.length === 3) {
82
+ const headerJsonStr = joli_source[0];
83
+ const payloadJsonStr = base64UrlEncode(payloadJson);
84
+ const signatureJsonStr = joli_source[2];
85
+ return `${headerJsonStr}.${payloadJsonStr}.${signatureJsonStr}`;
86
+ }
87
+ return originJoliSource;
88
+ };
@@ -6,3 +6,4 @@ import './keyboard';
6
6
  import './task';
7
7
  import './login';
8
8
  import './ads';
9
+ import './navigate';
@@ -1,8 +1,9 @@
1
- import { BaseError, createCommands, wrapUserFunction, hostEmitter } from '@jolibox/common';
1
+ import { BaseError, createCommands, wrapUserFunction, hostEmitter, isBoolean } from '@jolibox/common';
2
2
  import { createAPI, createSyncAPI, registerCanIUse, t } from './base';
3
3
  import { reportError } from '@/common/report/errors/report';
4
- import { applyNative, onNative } from '../bootstrap/bridge';
4
+ import { applyNative, onNative, publish } from '../bootstrap/bridge';
5
5
  import { nativeTaskEmitter } from '../report';
6
+ import { context } from '@/common/context';
6
7
 
7
8
  const EXIT_GAME = 'exitGame';
8
9
  const ON_READY = 'onReady';
@@ -13,11 +14,22 @@ const commands = createCommands();
13
14
 
14
15
  const safeCallbackWrapper = wrapUserFunction(reportError as (err: Error | BaseError) => void);
15
16
  const exitGame = createAPI(EXIT_GAME, {
16
- paramsSchema: t.tuple(t.function()),
17
- implement: async (onBeforeExit: () => void) => {
17
+ paramsSchema: t.tuple(t.function(), t.boolean().optional().default(false)),
18
+ implement: async (onBeforeExit: () => void, shouldStay = false) => {
18
19
  const safeCallback = safeCallbackWrapper(onBeforeExit);
19
20
  // 集中上报
20
21
  safeCallback.call(this);
22
+ // 透传context.shouldInterupt 且值为false时,为内部小程序。广播onRetentionResult
23
+ if (isBoolean(context.shouldInterupt) && !context.shouldInterupt && context.from) {
24
+ publish(
25
+ 'onRetentionResult',
26
+ {
27
+ shouldStay
28
+ },
29
+ context.from,
30
+ true
31
+ );
32
+ }
21
33
  await applyNative('exitAppAsync');
22
34
  }
23
35
  });
@@ -0,0 +1,61 @@
1
+ import { createCommands, UserCustomError } from '@jolibox/common';
2
+ import { invokeNative } from '../bootstrap/bridge';
3
+ import { createSyncAPI, t, registerCanIUse } from './base';
4
+ import { context } from '@/common/context';
5
+
6
+ const commands = createCommands();
7
+
8
+ const openSchemaSync = createSyncAPI('openSchemaSync', {
9
+ paramsSchema: t.tuple(t.string()),
10
+ implement: (schema) => {
11
+ // Add compatibility logic for incomplete URLs
12
+ let finalSchema = schema;
13
+ if (!schema.match(/^https?:\/\//)) {
14
+ finalSchema = `https://${context.mpId}.app.jolibox.com${schema.startsWith('/') ? '' : '/'}${schema}`;
15
+ }
16
+
17
+ const res = invokeNative('openSchemaSync', {
18
+ schema: finalSchema
19
+ });
20
+ if (res.errNo !== 0) {
21
+ throw new UserCustomError(res.errMsg, res.errNo);
22
+ }
23
+ }
24
+ });
25
+
26
+ const openPageSync = createSyncAPI('openPageSync', {
27
+ paramsSchema: t.tuple(t.string()),
28
+ implement: (page) => {
29
+ const { errNo, errMsg, data } = invokeNative('openPageSync', { url: page });
30
+ if (errNo !== 0 || !data) {
31
+ throw new UserCustomError(errMsg, errNo);
32
+ }
33
+ return { webviewId: data.webviewId };
34
+ }
35
+ });
36
+
37
+ const closePageSync = createSyncAPI('closePageSync', {
38
+ paramsSchema: t.tuple(t.number()),
39
+ implement: (webviewId) => {
40
+ const { errNo, errMsg } = invokeNative('closePageSync', { webviewId });
41
+ if (errNo !== 0) {
42
+ throw new UserCustomError(errMsg, errNo);
43
+ }
44
+ }
45
+ });
46
+
47
+ commands.registerCommand('RouterSDK.openSchema', openSchemaSync);
48
+ commands.registerCommand('RouterSDK.openPage', openPageSync);
49
+ commands.registerCommand('RouterSDK.closePage', closePageSync);
50
+
51
+ registerCanIUse('router.openSchema', {
52
+ version: '1.1.10'
53
+ });
54
+
55
+ registerCanIUse('router.openPage', {
56
+ version: '1.1.10'
57
+ });
58
+
59
+ registerCanIUse('router.closePage', {
60
+ version: '1.1.10'
61
+ });
@@ -1,16 +1,18 @@
1
1
  import { createAPIError } from '@/common/report/errors';
2
2
  import { createFetch } from '../network/create-fetch';
3
- import { createAPI, registerCanIUse, t } from './base';
3
+ import { createSyncAPI, registerCanIUse, t } from './base';
4
4
  import { dirtyURL, normalizeHeader, normalizeMethod, normalizeTimeout } from '../network/utils';
5
5
  import { BaseError, createCommands, Deferred, wrapUserFunction } from '@jolibox/common';
6
6
  import { AnyFunction, ResponseType } from '@jolibox/types';
7
7
  import { reportError } from '@/common/report/errors/report';
8
8
  import { FetchOptions } from '../network/types';
9
+ import { context } from '@/common/context';
9
10
 
10
11
  const LIMIT = 15;
11
12
 
12
13
  const publicFetch = createFetch('createRequestTaskSync', 'operateRequestTaskSync', {
13
- type: 'public'
14
+ type: 'public',
15
+ baseUrl: context.baseApiHost
14
16
  });
15
17
 
16
18
  /** current active count */
@@ -41,12 +43,12 @@ const request = (_params: unknown) => {
41
43
  // noop
42
44
  };
43
45
 
44
- const _request = createAPI('request', {
46
+ const _request = createSyncAPI('request', {
45
47
  paramsSchema: t.tuple(
46
48
  t.object({
47
49
  url: t.string(),
48
50
  method: t.string(),
49
- header: t.object(),
51
+ headers: t.object(),
50
52
  data: t.object().optional(),
51
53
  query: t.object().optional(),
52
54
  dataType: t.string().default('json'),
@@ -68,9 +70,10 @@ const request = (_params: unknown) => {
68
70
  if (deferred.state !== 'pending') {
69
71
  return;
70
72
  }
73
+
71
74
  const { data, dataType, responseType, enableCache, appendHostCookie } = params;
72
75
 
73
- const header = normalizeHeader(params.header);
76
+ const header = normalizeHeader(params.headers);
74
77
  const method = normalizeMethod(params.method);
75
78
  const timeout = normalizeTimeout(params.timeout);
76
79
  const url = dirtyURL(params.url, method, data);
@@ -89,15 +92,21 @@ const request = (_params: unknown) => {
89
92
 
90
93
  if (timeout) _params['timeout'] = timeout;
91
94
 
92
- const task = publicFetch<string | ArrayBuffer | JSON>(url, _params);
93
-
95
+ const task = publicFetch<
96
+ string | ArrayBuffer | JSON | { status: number; data: string | ArrayBuffer | JSON }
97
+ >(url, _params);
94
98
  {
95
99
  const { response } = await task;
96
100
  const { code, data, message } = response;
97
-
101
+ if (data && typeof data === 'object' && 'status' in data && data.status !== 200) {
102
+ throw createAPIError({
103
+ code: data.status || -1,
104
+ msg: `request:fail status ${data.status}`
105
+ });
106
+ }
98
107
  deferred.resolve({
99
108
  code: (code ?? 'SUCCESS') as ResponseType,
100
- data,
109
+ data: data as string | ArrayBuffer | JSON,
101
110
  message: message ?? 'request:ok'
102
111
  });
103
112
 
@@ -132,7 +141,7 @@ const request = (_params: unknown) => {
132
141
  }
133
142
  });
134
143
 
135
- _request(_params);
144
+ _request(_params as Parameters<typeof _request>[0]);
136
145
 
137
146
  return {
138
147
  /**
@@ -38,7 +38,16 @@ const bridge = createBridge(core);
38
38
 
39
39
  const { invokeHandler } = bridge;
40
40
 
41
- export const { applyNative, invokeNative, onNative, offNative, subscribeHandler } = bridge;
41
+ export const {
42
+ applyNative,
43
+ invokeNative,
44
+ onNative,
45
+ offNative,
46
+ subscribeHandler,
47
+ publish,
48
+ subscribe,
49
+ unsubscribe
50
+ } = bridge;
42
51
 
43
52
  export const onNativeWithError: On = (event, handler) => {
44
53
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -5,35 +5,52 @@ import { hostEmitter, isBoolean } from '@jolibox/common';
5
5
  import { taskTracker, track } from '../report';
6
6
  import { initializeNativeEnv } from './init-env';
7
7
  import { adEventEmitter } from '@/common/ads';
8
+ import { openRetentionSchema } from '../ui/retention';
9
+ import { innerFetch } from '../network';
8
10
 
11
+ interface IBasicMetaConfig {
12
+ canShowRecommended: boolean;
13
+ }
14
+
15
+ /**
16
+ * 全局变量
17
+ */
18
+
19
+ /**
20
+ * 清除safari jolibox样式
21
+ */
9
22
  let cleanStyles: () => void;
23
+
24
+ /**
25
+ * 启动时间戳
26
+ */
27
+ let start_timestamp: number;
28
+
29
+ /**
30
+ * 是否展示广告
31
+ */
32
+ let isAdShowing = false;
33
+
34
+ let baskcMeta: IBasicMetaConfig | null = null;
35
+
10
36
  RuntimeLoader.onReady(() => {
11
37
  // TODO: merge some env config
12
38
  });
13
39
 
14
- RuntimeLoader.doExit(() => {
15
- //埋点上报
16
- track('onBeforeExit', {
17
- timestamp: Date.now()
40
+ /**
41
+ * 添加广告展示监听
42
+ */
43
+ function addShowAdListener() {
44
+ adEventEmitter.on('isAdShowing', (isShowing) => {
45
+ isAdShowing = isShowing;
46
+ if (isBoolean(isAdShowing)) {
47
+ invokeNative('updateContainerConfigSync', {
48
+ displayCapsuleButton: !isAdShowing,
49
+ webviewId: context.webviewId
50
+ });
51
+ }
18
52
  });
19
- cleanStyles?.();
20
- taskTracker.close(Date.now() - start_timestamp);
21
- if (isAdShowing) {
22
- return true; // Forbid exit on watching ads
23
- }
24
- return false;
25
- });
26
-
27
- let isAdShowing = false;
28
- adEventEmitter.on('isAdShowing', (isShowing) => {
29
- isAdShowing = isShowing;
30
- if (isBoolean(isAdShowing)) {
31
- invokeNative('updateContainerConfigSync', {
32
- displayCapsuleButton: !isAdShowing,
33
- webviewId: context.webviewId
34
- });
35
- }
36
- });
53
+ }
37
54
 
38
55
  /**
39
56
  * The DOMContentLoaded event might be triggered very early,
@@ -75,11 +92,71 @@ function addGameServiceReadyListener() {
75
92
  joliboxJSCore?.doExit(uuid);
76
93
  });
77
94
  }
78
- let start_timestamp: number;
95
+
96
+ function addDoExitLoader() {
97
+ /**
98
+ * 退出逻辑
99
+ * 1. 如果正在展示广告,则禁止退出
100
+ * 2. 如果指定退出挽留逻辑,则按照指定逻辑运行
101
+ * 3. 如果退出挽留逻辑返回 true,则按照指定逻辑运行
102
+ * 4. 否则,按照默认逻辑运行
103
+ */
104
+
105
+ const doActualExit = () => {
106
+ //埋点上报
107
+ track('onBeforeExit', {
108
+ timestamp: Date.now()
109
+ });
110
+ cleanStyles?.();
111
+ taskTracker.close(Date.now() - start_timestamp);
112
+ };
113
+
114
+ RuntimeLoader.doExit(async () => {
115
+ if (isAdShowing) {
116
+ return true; // Forbid exit on watching ads
117
+ }
118
+
119
+ // 指定退出挽留逻辑,则按照指定逻辑运行
120
+ if (isBoolean(context.shouldInterupt)) {
121
+ // 不需要打断退出,上报埋点
122
+ if (!context.shouldInterupt) {
123
+ doActualExit();
124
+ }
125
+ return context.shouldInterupt;
126
+ }
127
+
128
+ if (baskcMeta?.canShowRecommended) {
129
+ const stay = await openRetentionSchema();
130
+ if (stay) {
131
+ // 挽留成功,打断退出
132
+ return true;
133
+ }
134
+ }
135
+
136
+ // 退出,对应上报
137
+ //埋点上报
138
+ doActualExit();
139
+ return false;
140
+ });
141
+ }
142
+
143
+ async function fetchMetaConfig() {
144
+ const url = `${context.baseApiHost}/api/fe-configs/js-sdk/basic-meta`;
145
+ const {
146
+ response: { data }
147
+ } = await innerFetch<IBasicMetaConfig>(url);
148
+
149
+ if (data) {
150
+ baskcMeta = data;
151
+ }
152
+ }
79
153
 
80
154
  export function config(): void {
81
155
  start_timestamp = Date.now();
82
156
  addGameServiceReadyListener();
157
+ addShowAdListener();
158
+ addDoExitLoader();
83
159
  addWebviewReadyListener();
160
+ fetchMetaConfig();
84
161
  cleanStyles = initializeNativeEnv();
85
162
  }
@@ -9,3 +9,5 @@ export const BUFFER_METHODS: string[] = [];
9
9
  export const BACKGROUND_FORBIDDEN_METHODS: string[] = [];
10
10
 
11
11
  export const SYNC_METHODS: string[] = ['env', 'createRequestTask', 'login'];
12
+
13
+ export const BATCH_EVENT = '__batch_event__';
@@ -1,15 +1,17 @@
1
1
  import { createInvoke } from './invoke';
2
2
  import { createSubscribe } from './subscribe';
3
3
  import { JSBridge } from './types';
4
+ import { createPublish } from './publish';
4
5
 
5
6
  /**
6
7
  * build js-bridge
7
8
  * @param jsCore jsCore function inject by native
8
9
  */
9
10
  export function createBridge(jsCore: jsb.JSCore): JSBridge {
10
- const { subscribeHandler, onNative, offNative } = createSubscribe(jsCore);
11
+ const { subscribeHandler, onNative, offNative, subscribe, unsubscribe } = createSubscribe(jsCore);
11
12
 
12
13
  const { invokeNative, invokeHandler, applyNative } = createInvoke(jsCore, onNative);
14
+ const publish = createPublish(jsCore);
13
15
 
14
16
  return {
15
17
  // 宿主调用
@@ -18,6 +20,9 @@ export function createBridge(jsCore: jsb.JSCore): JSBridge {
18
20
  applyNative,
19
21
  invokeNative,
20
22
  onNative,
21
- offNative
23
+ offNative,
24
+ publish,
25
+ subscribe,
26
+ unsubscribe
22
27
  };
23
28
  }
@@ -0,0 +1,44 @@
1
+ import { CUSTOM_EVENT_PREFIX, BATCH_EVENT } from './const';
2
+
3
+ type BatchEvent = [event: string, data: unknown];
4
+ type BatchEvents = BatchEvent[];
5
+
6
+ export function createPublish(jsCore: jsb.JSCore) {
7
+ const eventsMap = new Map<number | undefined, BatchEvents>();
8
+ let batchTask: Promise<void> | undefined;
9
+ const publish = (event: string, data: Record<string, unknown>, webviewId?: number, force?: boolean) => {
10
+ if (force) {
11
+ const ids = webviewId ? [webviewId] : '*';
12
+ jsCore.publish(`${CUSTOM_EVENT_PREFIX}${event}`, data, ids);
13
+ return;
14
+ }
15
+
16
+ if (!batchTask) {
17
+ batchTask = Promise.resolve().then(() => {
18
+ eventsMap.forEach((data, webviewId) => {
19
+ try {
20
+ const ids = webviewId ? [webviewId] : '*';
21
+ jsCore.publish(BATCH_EVENT, data, ids);
22
+ } catch {
23
+ // 避免一组 webview publish 的报错影响其他 webview publish
24
+ }
25
+ });
26
+
27
+ // reset
28
+ batchTask = undefined;
29
+ eventsMap.clear();
30
+ });
31
+ }
32
+
33
+ let events = eventsMap.get(webviewId);
34
+
35
+ if (!events) {
36
+ events = [];
37
+ eventsMap.set(webviewId, events);
38
+ }
39
+
40
+ events.push([`${CUSTOM_EVENT_PREFIX}${event}`, data]);
41
+ };
42
+
43
+ return publish;
44
+ }