@jolibox/implement 1.1.10 → 1.1.11-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.rush/temp/package-deps_build.json +20 -17
- package/dist/common/ads/anti-cheating.d.ts +11 -1
- package/dist/common/ads/index.d.ts +4 -9
- package/dist/common/context/index.d.ts +4 -0
- package/dist/common/context/url-parse.d.ts +8 -1
- package/dist/index.js +3 -3
- package/dist/index.native.js +4 -4
- package/dist/native/api/index.d.ts +1 -0
- package/dist/native/api/navigate.d.ts +1 -0
- package/dist/native/bootstrap/bridge.d.ts +1 -1
- package/dist/native/bootstrap/retention.d.ts +1 -0
- package/dist/native/js-bridge/const.d.ts +1 -0
- package/dist/native/js-bridge/publish.d.ts +1 -0
- package/dist/native/js-bridge/subscribe.d.ts +2 -0
- package/dist/native/js-bridge/types.d.ts +4 -0
- package/dist/native/js-core/jolibox-js-core.d.ts +8 -3
- package/implement.build.log +2 -2
- package/package.json +3 -3
- package/src/common/ads/anti-cheating.ts +24 -1
- package/src/common/ads/index.ts +15 -16
- package/src/common/context/index.ts +20 -3
- package/src/common/context/url-parse.ts +21 -1
- package/src/native/api/ads.ts +7 -0
- package/src/native/api/index.ts +1 -0
- package/src/native/api/lifecycle.ts +16 -4
- package/src/native/api/navigate.ts +23 -0
- package/src/native/bootstrap/bridge.ts +10 -1
- package/src/native/bootstrap/index.ts +32 -2
- package/src/native/bootstrap/retention.ts +40 -0
- package/src/native/js-bridge/const.ts +2 -0
- package/src/native/js-bridge/js-bridge.ts +7 -2
- package/src/native/js-bridge/publish.ts +44 -0
- package/src/native/js-bridge/subscribe.ts +25 -1
- package/src/native/js-bridge/types.ts +10 -0
- package/src/native/js-core/jolibox-js-core.ts +29 -25
- package/src/native/types/global.d.ts +1 -0
- package/src/native/types/native-method-map.d.ts +12 -0
package/src/common/ads/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { AdsActionDetection } from './ads-action-detection';
|
|
2
2
|
import { AdsAntiCheating } from './anti-cheating';
|
|
3
|
-
import { Track } from '../report';
|
|
4
3
|
import { context } from '../context';
|
|
5
4
|
import { ChannelPolicy } from './channel-policy';
|
|
6
5
|
import { EventEmitter } from '@jolibox/common';
|
|
6
|
+
import type { Track } from '../report';
|
|
7
|
+
import type { IHttpClient } from '../http';
|
|
7
8
|
|
|
8
9
|
declare global {
|
|
9
10
|
interface Window {
|
|
@@ -254,10 +255,6 @@ interface IJoliboxAdsResponse {
|
|
|
254
255
|
};
|
|
255
256
|
}
|
|
256
257
|
|
|
257
|
-
interface HttpClient {
|
|
258
|
-
get<T>(url: string, options?: { query?: Record<string, string> }): Promise<T>;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
258
|
export const adEventEmitter = new EventEmitter<{
|
|
262
259
|
isAdShowing: [boolean];
|
|
263
260
|
}>();
|
|
@@ -278,8 +275,8 @@ export class JoliboxAdsImpl {
|
|
|
278
275
|
/**
|
|
279
276
|
* Internal constructor, should not be called directly
|
|
280
277
|
*/
|
|
281
|
-
constructor(readonly track: Track, readonly httpClient:
|
|
282
|
-
this.antiCheating = new AdsAntiCheating(checkNetwork);
|
|
278
|
+
constructor(readonly track: Track, readonly httpClient: IHttpClient, readonly checkNetwork: () => boolean) {
|
|
279
|
+
this.antiCheating = new AdsAntiCheating(track, httpClient, checkNetwork);
|
|
283
280
|
this.adsActionDetection = new AdsActionDetection(this.track);
|
|
284
281
|
this.channelPolicy = new ChannelPolicy(httpClient);
|
|
285
282
|
}
|
|
@@ -400,7 +397,10 @@ export class JoliboxAdsImpl {
|
|
|
400
397
|
|
|
401
398
|
const beforeAd = () => {
|
|
402
399
|
adEventEmitter.emit('isAdShowing', true);
|
|
403
|
-
this.track('CallBeforeAd', {
|
|
400
|
+
this.track('CallBeforeAd', {
|
|
401
|
+
type: params.type,
|
|
402
|
+
name: params.name ?? ''
|
|
403
|
+
});
|
|
404
404
|
if (originBeforeAd) {
|
|
405
405
|
originBeforeAd();
|
|
406
406
|
}
|
|
@@ -408,7 +408,10 @@ export class JoliboxAdsImpl {
|
|
|
408
408
|
|
|
409
409
|
const afterAd = () => {
|
|
410
410
|
adEventEmitter.emit('isAdShowing', false);
|
|
411
|
-
this.track('CallAfterAd', {
|
|
411
|
+
this.track('CallAfterAd', {
|
|
412
|
+
type: params.type,
|
|
413
|
+
name: params.name ?? ''
|
|
414
|
+
});
|
|
412
415
|
if (originAfterAd) {
|
|
413
416
|
originAfterAd();
|
|
414
417
|
}
|
|
@@ -492,9 +495,7 @@ export class JoliboxAdsImpl {
|
|
|
492
495
|
breakFormat: params.type === 'reward' ? 'reward' : 'interstitial',
|
|
493
496
|
breakStatus: 'noAdPreloaded'
|
|
494
497
|
});
|
|
495
|
-
this.
|
|
496
|
-
reason: adsDisplayPermission
|
|
497
|
-
});
|
|
498
|
+
this.antiCheating.report(adsDisplayPermission);
|
|
498
499
|
return;
|
|
499
500
|
case 'BLOCK_INITIAL':
|
|
500
501
|
case 'BANNED_FOR_TIME':
|
|
@@ -503,11 +504,9 @@ export class JoliboxAdsImpl {
|
|
|
503
504
|
params.adBreakDone?.({
|
|
504
505
|
breakType: params.type,
|
|
505
506
|
breakFormat: params.type === 'reward' ? 'reward' : 'interstitial',
|
|
506
|
-
breakStatus: '
|
|
507
|
-
});
|
|
508
|
-
this.track('PreventAdsCheating', {
|
|
509
|
-
reason: adsDisplayPermission
|
|
507
|
+
breakStatus: 'frequencyCapped'
|
|
510
508
|
});
|
|
509
|
+
this.antiCheating.report(adsDisplayPermission);
|
|
511
510
|
return;
|
|
512
511
|
default:
|
|
513
512
|
// allowed
|
|
@@ -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 =
|
|
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,24 @@ 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
|
+
},
|
|
96
103
|
onEnvConfigChanged: (newConfig: Partial<Env>) => {
|
|
97
104
|
mergeWith(env, newConfig, mergeArray);
|
|
105
|
+
},
|
|
106
|
+
encodeJoliSourceQuery: (newPayloadJson: QueryParams['payloadJson']) => {
|
|
107
|
+
if (headerJson && signature) {
|
|
108
|
+
return encodeJoliSourceQuery({
|
|
109
|
+
headerJson,
|
|
110
|
+
payloadJson: { ...payloadJson, ...newPayloadJson },
|
|
111
|
+
signature
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return urlParams.get('joliSource') ?? '';
|
|
98
115
|
}
|
|
99
116
|
};
|
|
100
117
|
};
|
|
@@ -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,11 @@ export const parseUrlQuery = (url: string): QueryParams => {
|
|
|
63
75
|
};
|
|
64
76
|
}
|
|
65
77
|
};
|
|
78
|
+
|
|
79
|
+
export const encodeJoliSourceQuery = (queryParams: QueryParams): string => {
|
|
80
|
+
const { headerJson, payloadJson, signature } = queryParams;
|
|
81
|
+
const headerJsonStr = base64UrlEncode(headerJson);
|
|
82
|
+
const payloadJsonStr = base64UrlEncode(payloadJson);
|
|
83
|
+
const signatureJsonStr = base64UrlEncode(signature);
|
|
84
|
+
return `${headerJsonStr}.${payloadJsonStr}.${signatureJsonStr}`;
|
|
85
|
+
};
|
package/src/native/api/ads.ts
CHANGED
|
@@ -22,6 +22,13 @@ const ads = new JoliboxAdsImpl(
|
|
|
22
22
|
responseType: 'json',
|
|
23
23
|
appendHostCookie: true,
|
|
24
24
|
...options
|
|
25
|
+
}).then((res) => res.response.data as T),
|
|
26
|
+
post: <T>(url: string, data: any, options?: any) =>
|
|
27
|
+
fetch<T>(url, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
responseType: 'json',
|
|
30
|
+
appendHostCookie: true,
|
|
31
|
+
...options
|
|
25
32
|
}).then((res) => res.response.data as T)
|
|
26
33
|
},
|
|
27
34
|
checkNetworkStatus
|
package/src/native/api/index.ts
CHANGED
|
@@ -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,23 @@
|
|
|
1
|
+
import { createCommands, UserCustomError } from '@jolibox/common';
|
|
2
|
+
import { invokeNative } from '../bootstrap/bridge';
|
|
3
|
+
import { createSyncAPI, t, registerCanIUse } from './base';
|
|
4
|
+
|
|
5
|
+
const commands = createCommands();
|
|
6
|
+
|
|
7
|
+
const openSchemaSync = createSyncAPI('openSchemaSync', {
|
|
8
|
+
paramsSchema: t.tuple(t.string()),
|
|
9
|
+
implement: (schema) => {
|
|
10
|
+
const res = invokeNative('openSchemaSync', {
|
|
11
|
+
schema
|
|
12
|
+
});
|
|
13
|
+
if (res.errNo !== 0) {
|
|
14
|
+
throw new UserCustomError(res.errMsg, res.errNo);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
commands.registerCommand('API.openSchemaSync', openSchemaSync);
|
|
20
|
+
|
|
21
|
+
registerCanIUse('openSchemaSync', {
|
|
22
|
+
version: '1.1.9'
|
|
23
|
+
});
|
|
@@ -38,7 +38,16 @@ const bridge = createBridge(core);
|
|
|
38
38
|
|
|
39
39
|
const { invokeHandler } = bridge;
|
|
40
40
|
|
|
41
|
-
export const {
|
|
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
|
|
@@ -1,26 +1,56 @@
|
|
|
1
|
-
import { invokeNative, onNative, RuntimeLoader } from './bridge';
|
|
1
|
+
import { invokeNative, onNative, RuntimeLoader, subscribe } from './bridge';
|
|
2
2
|
import { joliboxJSCore } from '../js-core';
|
|
3
3
|
import { context } from '@/common/context';
|
|
4
4
|
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 './retention';
|
|
8
9
|
|
|
9
10
|
let cleanStyles: () => void;
|
|
10
11
|
RuntimeLoader.onReady(() => {
|
|
11
12
|
// TODO: merge some env config
|
|
12
13
|
});
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
const doActualExit = () => {
|
|
15
16
|
//埋点上报
|
|
16
17
|
track('onBeforeExit', {
|
|
17
18
|
timestamp: Date.now()
|
|
18
19
|
});
|
|
19
20
|
cleanStyles?.();
|
|
20
21
|
taskTracker.close(Date.now() - start_timestamp);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 退出逻辑
|
|
26
|
+
* 1. 如果正在展示广告,则禁止退出
|
|
27
|
+
* 2. 如果指定退出挽留逻辑,则按照指定逻辑运行
|
|
28
|
+
* 3. 如果退出挽留逻辑返回 true,则按照指定逻辑运行
|
|
29
|
+
* 4. 否则,按照默认逻辑运行
|
|
30
|
+
*/
|
|
31
|
+
RuntimeLoader.doExit(async () => {
|
|
21
32
|
if (isAdShowing) {
|
|
22
33
|
return true; // Forbid exit on watching ads
|
|
23
34
|
}
|
|
35
|
+
|
|
36
|
+
// 指定退出挽留逻辑,则按照指定逻辑运行
|
|
37
|
+
if (isBoolean(context.shouldInterupt)) {
|
|
38
|
+
// 不需要打断退出,上报埋点
|
|
39
|
+
if (!context.shouldInterupt) {
|
|
40
|
+
doActualExit();
|
|
41
|
+
}
|
|
42
|
+
return context.shouldInterupt;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// const stay = await openRetentionSchema();
|
|
46
|
+
// if (stay) {
|
|
47
|
+
// // 挽留成功,打断退出
|
|
48
|
+
// return true;
|
|
49
|
+
// }
|
|
50
|
+
|
|
51
|
+
// 退出,对应上报
|
|
52
|
+
//埋点上报
|
|
53
|
+
doActualExit();
|
|
24
54
|
return false;
|
|
25
55
|
});
|
|
26
56
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { context } from '@/common/context';
|
|
2
|
+
import { invokeNative, subscribe } from './bridge';
|
|
3
|
+
import { Deferred } from '@jolibox/common';
|
|
4
|
+
|
|
5
|
+
export async function openRetentionSchema() {
|
|
6
|
+
const { data } = invokeNative('envSync');
|
|
7
|
+
const { orientation, webviewId } = data;
|
|
8
|
+
let joliPayload: Record<string, unknown> = {
|
|
9
|
+
__mpType: 'miniApp',
|
|
10
|
+
__transparent: true,
|
|
11
|
+
// set entryPath
|
|
12
|
+
__orientation: orientation ?? 'VERTICAL', // 默认竖屏
|
|
13
|
+
__showStatusBar: false,
|
|
14
|
+
__shouldInterupt: false
|
|
15
|
+
};
|
|
16
|
+
if (webviewId) {
|
|
17
|
+
joliPayload = {
|
|
18
|
+
...joliPayload,
|
|
19
|
+
__from: webviewId
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const joliSource = context.encodeJoliSourceQuery(joliPayload);
|
|
24
|
+
|
|
25
|
+
const host = context.testMode ? 'stg-game.jolibox.com' : 'game.jolibox.com';
|
|
26
|
+
const retentionSchema = `https://${host}/game/G32115508989327465281365749294/?appId=G32115508989327465281365749294&joliSource=${joliSource}`;
|
|
27
|
+
|
|
28
|
+
const quitResultDeffer = new Deferred<boolean>();
|
|
29
|
+
|
|
30
|
+
subscribe('onRetentionResult', ({ shouldStay }) => {
|
|
31
|
+
quitResultDeffer.resolve(shouldStay);
|
|
32
|
+
});
|
|
33
|
+
// 异步
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
invokeNative('openSchemaSync', {
|
|
36
|
+
schema: retentionSchema
|
|
37
|
+
});
|
|
38
|
+
}, 0);
|
|
39
|
+
return quitResultDeffer.promise;
|
|
40
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import { EventEmitter } from '@jolibox/common';
|
|
1
|
+
import { EventEmitter, logger } from '@jolibox/common';
|
|
2
2
|
import { DataObj, unpack } from './utils';
|
|
3
3
|
import { Off, On } from './types';
|
|
4
4
|
import { AnyFunction } from '@jolibox/types';
|
|
5
5
|
import { MetricsMonitor } from './report';
|
|
6
|
+
import { BATCH_EVENT, CUSTOM_EVENT_PREFIX } from './const';
|
|
6
7
|
|
|
7
8
|
export interface Subscribes {
|
|
8
9
|
onNative: On;
|
|
9
10
|
offNative: Off;
|
|
10
11
|
subscribeHandler: AnyFunction;
|
|
12
|
+
subscribe: On;
|
|
13
|
+
unsubscribe: Off;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
export function createSubscribe(jsCore: jsb.JSCore): Subscribes {
|
|
14
17
|
const nativeEmitter = new EventEmitter();
|
|
18
|
+
const customEmitter = new EventEmitter();
|
|
15
19
|
const publishMonitor = new MetricsMonitor({
|
|
16
20
|
eventName: 'jolibox_publish',
|
|
17
21
|
tagNameOrder: ['type', 'event'],
|
|
@@ -28,6 +32,8 @@ export function createSubscribe(jsCore: jsb.JSCore): Subscribes {
|
|
|
28
32
|
return {
|
|
29
33
|
onNative: nativeEmitter.on.bind(nativeEmitter),
|
|
30
34
|
offNative: nativeEmitter.off.bind(nativeEmitter),
|
|
35
|
+
subscribe: customEmitter.on.bind(customEmitter),
|
|
36
|
+
unsubscribe: customEmitter.off.bind(customEmitter),
|
|
31
37
|
subscribeHandler(event, data, webviewId) {
|
|
32
38
|
// ios: jsc 端基于系统方法,前端接受到的 data 总是 string 类型, webview 基于 evaluateJavascript,前端接受到的 data 总是 object 类型
|
|
33
39
|
// android: 传入为 string 则接收到 string,传入为可序列化成功的 object,则接收到 object
|
|
@@ -44,6 +50,24 @@ export function createSubscribe(jsCore: jsb.JSCore): Subscribes {
|
|
|
44
50
|
} else {
|
|
45
51
|
originalParams = unpackedData;
|
|
46
52
|
}
|
|
53
|
+
|
|
54
|
+
if (event === BATCH_EVENT) {
|
|
55
|
+
const list = originalParams as [event: string, data: unknown][];
|
|
56
|
+
list.forEach((item) => {
|
|
57
|
+
const [_event, _data] = item;
|
|
58
|
+
try {
|
|
59
|
+
customEmitter.emit(_event.slice(CUSTOM_EVENT_PREFIX.length), _data, webviewId);
|
|
60
|
+
} catch {
|
|
61
|
+
// 忽略
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (event.startsWith(CUSTOM_EVENT_PREFIX)) {
|
|
68
|
+
customEmitter.emit(event.slice(CUSTOM_EVENT_PREFIX.length), originalParams, webviewId);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
47
71
|
nativeEmitter.emit(event, originalParams, webviewId);
|
|
48
72
|
}
|
|
49
73
|
};
|
|
@@ -7,6 +7,13 @@ export type Off = <T extends string>(event: T, handler: Listener) => void;
|
|
|
7
7
|
|
|
8
8
|
export type InvokeHandler = (callbackId: string | number, data: string | Record<string, unknown>) => void;
|
|
9
9
|
|
|
10
|
+
export type Publish = (
|
|
11
|
+
event: string,
|
|
12
|
+
data: Record<string, unknown>,
|
|
13
|
+
webviewId?: number,
|
|
14
|
+
force?: boolean
|
|
15
|
+
) => void;
|
|
16
|
+
|
|
10
17
|
export type SubscribeHandler = (
|
|
11
18
|
event: string,
|
|
12
19
|
data: string | Record<string, unknown>,
|
|
@@ -23,4 +30,7 @@ export interface JSBridge {
|
|
|
23
30
|
applyNative: jsb.service.ApplyNative;
|
|
24
31
|
onNative: jsb.service.OnNative;
|
|
25
32
|
offNative: jsb.service.OffNative;
|
|
33
|
+
publish: Publish;
|
|
34
|
+
subscribe: On;
|
|
35
|
+
unsubscribe: Off;
|
|
26
36
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { InternalInvokeNativeError } from '@jolibox/common';
|
|
2
|
-
|
|
2
|
+
import { normalizeParams } from './utils';
|
|
3
3
|
import { Env } from '@jolibox/types';
|
|
4
4
|
import { reportError } from '@/common/report/errors/report';
|
|
5
5
|
|
|
@@ -23,6 +23,7 @@ declare global {
|
|
|
23
23
|
invoke: (invokeString: string) => void;
|
|
24
24
|
onDocumentReady: (paramsstr: string) => void;
|
|
25
25
|
doExit: (uuidString: string) => void;
|
|
26
|
+
publish: (params: { event: string; webviewIds: number[] | '*'; paramsString: string }) => void;
|
|
26
27
|
};
|
|
27
28
|
webkit?: {
|
|
28
29
|
messageHandlers: {
|
|
@@ -30,7 +31,7 @@ declare global {
|
|
|
30
31
|
postMessage: (params: { event: string; callbackId: number; paramsString: string }) => void;
|
|
31
32
|
};
|
|
32
33
|
publish: {
|
|
33
|
-
postMessage: (params: { event: string; webviewIds:
|
|
34
|
+
postMessage: (params: { event: string; webviewIds: number[] | '*'; paramsString: string }) => void;
|
|
34
35
|
};
|
|
35
36
|
onDocumentReady: {
|
|
36
37
|
postMessage: (params: { path: string }) => void;
|
|
@@ -47,14 +48,14 @@ export const RuntimeLoader = {
|
|
|
47
48
|
trigger() {
|
|
48
49
|
// noop
|
|
49
50
|
},
|
|
50
|
-
exit(): boolean {
|
|
51
|
+
exit(): Promise<boolean> {
|
|
51
52
|
//noop
|
|
52
|
-
return false; // 默认不打断退出
|
|
53
|
+
return Promise.resolve(false); // 默认不打断退出
|
|
53
54
|
},
|
|
54
55
|
onReady(fn: () => void) {
|
|
55
56
|
RuntimeLoader.trigger = fn;
|
|
56
57
|
},
|
|
57
|
-
doExit(fn: () => boolean) {
|
|
58
|
+
doExit(fn: () => Promise<boolean>) {
|
|
58
59
|
RuntimeLoader.exit = fn;
|
|
59
60
|
}
|
|
60
61
|
};
|
|
@@ -81,8 +82,8 @@ if (window.webkit) {
|
|
|
81
82
|
RuntimeLoader.trigger();
|
|
82
83
|
_joliboxJSCore.onDocumentReady.postMessage({ path });
|
|
83
84
|
};
|
|
84
|
-
const doExit = (uuid: string) => {
|
|
85
|
-
const shouldInterrupt = RuntimeLoader.exit();
|
|
85
|
+
const doExit = async (uuid: string) => {
|
|
86
|
+
const shouldInterrupt = await RuntimeLoader.exit();
|
|
86
87
|
_joliboxJSCore.doExit.postMessage({ uuid, shouldInterrupt });
|
|
87
88
|
};
|
|
88
89
|
|
|
@@ -96,14 +97,17 @@ if (window.webkit) {
|
|
|
96
97
|
callbackId
|
|
97
98
|
});
|
|
98
99
|
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
100
|
+
publish(event, params, webviewIds) {
|
|
101
|
+
const data = normalizeParams(params as Record<string, unknown>, 'joliboxJSCore');
|
|
102
|
+
window.prompt(
|
|
103
|
+
'publish',
|
|
104
|
+
JSON.stringify({
|
|
105
|
+
event,
|
|
106
|
+
paramsString: JSON.stringify(data),
|
|
107
|
+
webviewIds
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
},
|
|
107
111
|
call(method: string, params: Record<string, unknown>, callbackId) {
|
|
108
112
|
const res = window.prompt(
|
|
109
113
|
'invoke',
|
|
@@ -137,8 +141,8 @@ if (window.JoliAndroidSDKBridge) {
|
|
|
137
141
|
_joliboxJSCore.onDocumentReady(JSON.stringify({ path }));
|
|
138
142
|
};
|
|
139
143
|
|
|
140
|
-
const doExit = (uuid: string) => {
|
|
141
|
-
const shouldInterrupt = RuntimeLoader.exit();
|
|
144
|
+
const doExit = async (uuid: string) => {
|
|
145
|
+
const shouldInterrupt = await RuntimeLoader.exit();
|
|
142
146
|
_joliboxJSCore.doExit(JSON.stringify({ uuid, shouldInterrupt }));
|
|
143
147
|
};
|
|
144
148
|
|
|
@@ -154,14 +158,14 @@ if (window.JoliAndroidSDKBridge) {
|
|
|
154
158
|
);
|
|
155
159
|
},
|
|
156
160
|
doExit,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
161
|
+
publish(event, params, webviewIds) {
|
|
162
|
+
const data = normalizeParams(params as Record<string, unknown>, 'joliboxJSCore');
|
|
163
|
+
_joliboxJSCore.publish({
|
|
164
|
+
event,
|
|
165
|
+
paramsString: JSON.stringify(data),
|
|
166
|
+
webviewIds
|
|
167
|
+
});
|
|
168
|
+
},
|
|
165
169
|
call(method: string, params: Record<string, unknown>, callbackId) {
|
|
166
170
|
const res = window.prompt(
|
|
167
171
|
'invoke',
|
|
@@ -15,6 +15,7 @@ declare global {
|
|
|
15
15
|
onDocumentReady: (path: string) => void;
|
|
16
16
|
doExit: (uuid: string) => void;
|
|
17
17
|
bind?: (id: number, handler: (...args: unknown[]) => unknown) => void;
|
|
18
|
+
publish: (event: string, data: unknown, webviewIds: number[] | '*') => void;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
type ApplyNativeReturn<T> = T extends Promise<infer U>
|