@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.
- package/.rush/temp/package-deps_build.json +21 -18
- package/.rush/temp/shrinkwrap-deps.json +1 -1
- 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/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
|
@@ -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:
|
|
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>;
|
package/implement.build.log
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Invoking: npm run clean && npm run build:esm && tsc
|
|
2
2
|
|
|
3
|
-
> @jolibox/implement@1.1.
|
|
3
|
+
> @jolibox/implement@1.1.13-beta.1 clean
|
|
4
4
|
> rimraf ./dist
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
> @jolibox/implement@1.1.
|
|
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.
|
|
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.
|
|
10
|
-
"@jolibox/types": "1.1.
|
|
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 =
|
|
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
|
+
}
|