@jolibox/implement 1.1.11 → 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.
- package/.rush/temp/package-deps_build.json +24 -20
- package/.rush/temp/shrinkwrap-deps.json +1 -1
- package/dist/common/ads/anti-cheating.d.ts +16 -53
- package/dist/common/ads/anti-cheating.test.d.ts +1 -0
- package/dist/common/context/index.d.ts +5 -0
- package/dist/common/context/url-parse.d.ts +8 -1
- package/dist/index.js +3 -3
- package/dist/index.native.js +110 -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/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/dist/native/network/create-fetch.d.ts +1 -0
- package/dist/native/ui/retention.d.ts +1 -0
- package/implement.build.log +2 -2
- package/package.json +4 -3
- package/src/common/ads/anti-cheating.test.ts +79 -0
- package/src/common/ads/anti-cheating.ts +222 -156
- package/src/common/ads/index.ts +21 -7
- package/src/common/context/index.ts +22 -3
- package/src/common/context/url-parse.ts +24 -1
- package/src/native/api/index.ts +1 -0
- package/src/native/api/lifecycle.ts +16 -4
- package/src/native/api/navigate.ts +61 -0
- package/src/native/api/request.ts +19 -10
- package/src/native/bootstrap/bridge.ts +10 -1
- package/src/native/bootstrap/index.ts +100 -23
- 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 +30 -26
- package/src/native/network/create-fetch.ts +1 -0
- package/src/native/network/utils.ts +13 -6
- package/src/native/types/global.d.ts +1 -0
- package/src/native/types/native-method-map.d.ts +29 -0
- package/src/native/ui/retention.ts +152 -0
package/src/common/ads/index.ts
CHANGED
|
@@ -481,32 +481,46 @@ export class JoliboxAdsImpl {
|
|
|
481
481
|
}
|
|
482
482
|
}
|
|
483
483
|
|
|
484
|
-
const
|
|
484
|
+
const { reason, info } = this.antiCheating.checkAdsDisplayPermission(
|
|
485
485
|
params.type === 'reward' ? 'reward' : 'interstitial'
|
|
486
486
|
);
|
|
487
487
|
|
|
488
|
-
switch (
|
|
488
|
+
switch (reason) {
|
|
489
489
|
case 'NETWORK_NOT_OK':
|
|
490
490
|
case 'BANNED_FOR_SESSION':
|
|
491
|
-
console.warn('Ads not allowed',
|
|
491
|
+
// console.warn('Ads not allowed', reason);
|
|
492
492
|
params.adBreakDone?.({
|
|
493
493
|
breakType: params.type,
|
|
494
|
-
breakName:
|
|
494
|
+
breakName: reason,
|
|
495
495
|
breakFormat: params.type === 'reward' ? 'reward' : 'interstitial',
|
|
496
496
|
breakStatus: 'noAdPreloaded'
|
|
497
497
|
});
|
|
498
|
-
this.antiCheating.report(
|
|
498
|
+
this.antiCheating.report(reason);
|
|
499
499
|
return;
|
|
500
500
|
case 'BLOCK_INITIAL':
|
|
501
501
|
case 'BANNED_FOR_TIME':
|
|
502
502
|
case 'WAITING_BANNED_RELEASE':
|
|
503
|
-
console.warn('Ads not allowed', adsDisplayPermission);
|
|
503
|
+
// console.warn('Ads not allowed', adsDisplayPermission);
|
|
504
504
|
params.adBreakDone?.({
|
|
505
505
|
breakType: params.type,
|
|
506
506
|
breakFormat: params.type === 'reward' ? 'reward' : 'interstitial',
|
|
507
507
|
breakStatus: 'frequencyCapped'
|
|
508
508
|
});
|
|
509
|
-
this.antiCheating.report(
|
|
509
|
+
this.antiCheating.report(reason);
|
|
510
|
+
return;
|
|
511
|
+
case 'HOLDING_HIGH_FREQ':
|
|
512
|
+
// follow google's style to forbid high freq request
|
|
513
|
+
params.adBreakDone?.({
|
|
514
|
+
breakFormat: params.type === 'reward' ? 'reward' : 'interstitial',
|
|
515
|
+
breakName: '',
|
|
516
|
+
breakStatus: (info?.count ?? 0) > 6 ? 'other' : 'frequencyCapped',
|
|
517
|
+
breakType: params.type
|
|
518
|
+
});
|
|
519
|
+
// every 3 hit, report to anti-cheating system
|
|
520
|
+
// if a user click 1 time every 1 second, it will cause 20 reports for 1 minute
|
|
521
|
+
if (info?.count && info.count !== 0 && info.count % 3 === 0) {
|
|
522
|
+
this.antiCheating.report(reason, info?.count);
|
|
523
|
+
}
|
|
510
524
|
return;
|
|
511
525
|
default:
|
|
512
526
|
// 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,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
|
+
};
|
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,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 {
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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<
|
|
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 {
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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
|
}
|