@jolibox/implement 1.1.11-beta.1 → 1.1.11-beta.11
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 +16 -15
- package/.rush/temp/shrinkwrap-deps.json +1 -1
- package/dist/common/ads/anti-cheating.d.ts +24 -51
- package/dist/common/ads/anti-cheating.test.d.ts +1 -0
- package/dist/common/ads/index.d.ts +4 -9
- package/dist/common/context/index.d.ts +1 -0
- package/dist/common/context/url-parse.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/index.native.js +110 -4
- package/dist/native/network/create-fetch.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 +228 -139
- package/src/common/ads/index.ts +33 -20
- package/src/common/context/index.ts +7 -5
- package/src/common/context/url-parse.ts +9 -6
- package/src/native/api/ads.ts +7 -0
- package/src/native/api/navigate.ts +42 -4
- package/src/native/api/request.ts +19 -10
- package/src/native/bootstrap/index.ts +98 -51
- package/src/native/js-core/jolibox-js-core.ts +1 -1
- package/src/native/network/create-fetch.ts +1 -0
- package/src/native/network/utils.ts +13 -6
- package/src/native/types/native-method-map.d.ts +17 -0
- package/src/native/ui/retention.ts +152 -0
- package/src/native/bootstrap/retention.ts +0 -40
- /package/dist/native/{bootstrap → ui}/retention.d.ts +0 -0
|
@@ -100,16 +100,18 @@ const wrapContext = () => {
|
|
|
100
100
|
get from(): number | undefined {
|
|
101
101
|
return payloadJson?.__from;
|
|
102
102
|
},
|
|
103
|
+
get baseApiHost(): string {
|
|
104
|
+
return testMode ? 'https://stg-api.jolibox.com' : 'https://api.jolibox.com';
|
|
105
|
+
},
|
|
103
106
|
onEnvConfigChanged: (newConfig: Partial<Env>) => {
|
|
104
107
|
mergeWith(env, newConfig, mergeArray);
|
|
105
108
|
},
|
|
106
109
|
encodeJoliSourceQuery: (newPayloadJson: QueryParams['payloadJson']) => {
|
|
107
110
|
if (headerJson && signature) {
|
|
108
|
-
return encodeJoliSourceQuery(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
});
|
|
111
|
+
return encodeJoliSourceQuery(
|
|
112
|
+
{ ...payloadJson, ...newPayloadJson },
|
|
113
|
+
urlParams.get('joliSource') ?? ''
|
|
114
|
+
);
|
|
113
115
|
}
|
|
114
116
|
return urlParams.get('joliSource') ?? '';
|
|
115
117
|
}
|
|
@@ -76,10 +76,13 @@ export const parseUrlQuery = (url: string): QueryParams => {
|
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
-
export const encodeJoliSourceQuery = (
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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;
|
|
85
88
|
};
|
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
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { createCommands, UserCustomError } from '@jolibox/common';
|
|
2
2
|
import { invokeNative } from '../bootstrap/bridge';
|
|
3
3
|
import { createSyncAPI, t, registerCanIUse } from './base';
|
|
4
|
+
import { context } from '@/common/context';
|
|
4
5
|
|
|
5
6
|
const commands = createCommands();
|
|
6
7
|
|
|
7
8
|
const openSchemaSync = createSyncAPI('openSchemaSync', {
|
|
8
9
|
paramsSchema: t.tuple(t.string()),
|
|
9
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
|
+
|
|
10
17
|
const res = invokeNative('openSchemaSync', {
|
|
11
|
-
schema
|
|
18
|
+
schema: finalSchema
|
|
12
19
|
});
|
|
13
20
|
if (res.errNo !== 0) {
|
|
14
21
|
throw new UserCustomError(res.errMsg, res.errNo);
|
|
@@ -16,8 +23,39 @@ const openSchemaSync = createSyncAPI('openSchemaSync', {
|
|
|
16
23
|
}
|
|
17
24
|
});
|
|
18
25
|
|
|
19
|
-
|
|
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
|
+
});
|
|
20
58
|
|
|
21
|
-
registerCanIUse('
|
|
22
|
-
version: '1.1.
|
|
59
|
+
registerCanIUse('router.closePage', {
|
|
60
|
+
version: '1.1.10'
|
|
23
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
|
/**
|
|
@@ -1,70 +1,57 @@
|
|
|
1
|
-
import { invokeNative, onNative, RuntimeLoader
|
|
1
|
+
import { invokeNative, onNative, RuntimeLoader } 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 '
|
|
8
|
+
import { openRetentionSchema } from '../ui/retention';
|
|
9
|
+
import { innerFetch } from '../network';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
});
|
|
11
|
+
interface IBasicMetaConfig {
|
|
12
|
+
canShowRecommended: boolean;
|
|
13
|
+
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
timestamp: Date.now()
|
|
19
|
-
});
|
|
20
|
-
cleanStyles?.();
|
|
21
|
-
taskTracker.close(Date.now() - start_timestamp);
|
|
22
|
-
};
|
|
15
|
+
/**
|
|
16
|
+
* 全局变量
|
|
17
|
+
*/
|
|
23
18
|
|
|
24
19
|
/**
|
|
25
|
-
*
|
|
26
|
-
* 1. 如果正在展示广告,则禁止退出
|
|
27
|
-
* 2. 如果指定退出挽留逻辑,则按照指定逻辑运行
|
|
28
|
-
* 3. 如果退出挽留逻辑返回 true,则按照指定逻辑运行
|
|
29
|
-
* 4. 否则,按照默认逻辑运行
|
|
20
|
+
* 清除safari jolibox样式
|
|
30
21
|
*/
|
|
31
|
-
|
|
32
|
-
if (isAdShowing) {
|
|
33
|
-
return true; // Forbid exit on watching ads
|
|
34
|
-
}
|
|
22
|
+
let cleanStyles: () => void;
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
doActualExit();
|
|
41
|
-
}
|
|
42
|
-
return context.shouldInterupt;
|
|
43
|
-
}
|
|
24
|
+
/**
|
|
25
|
+
* 启动时间戳
|
|
26
|
+
*/
|
|
27
|
+
let start_timestamp: number;
|
|
44
28
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// }
|
|
29
|
+
/**
|
|
30
|
+
* 是否展示广告
|
|
31
|
+
*/
|
|
32
|
+
let isAdShowing = false;
|
|
50
33
|
|
|
51
|
-
|
|
52
|
-
//埋点上报
|
|
53
|
-
doActualExit();
|
|
54
|
-
return false;
|
|
55
|
-
});
|
|
34
|
+
let baskcMeta: IBasicMetaConfig | null = null;
|
|
56
35
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
isAdShowing = isShowing;
|
|
60
|
-
if (isBoolean(isAdShowing)) {
|
|
61
|
-
invokeNative('updateContainerConfigSync', {
|
|
62
|
-
displayCapsuleButton: !isAdShowing,
|
|
63
|
-
webviewId: context.webviewId
|
|
64
|
-
});
|
|
65
|
-
}
|
|
36
|
+
RuntimeLoader.onReady(() => {
|
|
37
|
+
// TODO: merge some env config
|
|
66
38
|
});
|
|
67
39
|
|
|
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
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
68
55
|
/**
|
|
69
56
|
* The DOMContentLoaded event might be triggered very early,
|
|
70
57
|
* so the global loaded listener should be executed during the bootstrap process.
|
|
@@ -105,11 +92,71 @@ function addGameServiceReadyListener() {
|
|
|
105
92
|
joliboxJSCore?.doExit(uuid);
|
|
106
93
|
});
|
|
107
94
|
}
|
|
108
|
-
|
|
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
|
+
}
|
|
109
153
|
|
|
110
154
|
export function config(): void {
|
|
111
155
|
start_timestamp = Date.now();
|
|
112
156
|
addGameServiceReadyListener();
|
|
157
|
+
addShowAdListener();
|
|
158
|
+
addDoExitLoader();
|
|
113
159
|
addWebviewReadyListener();
|
|
160
|
+
fetchMetaConfig();
|
|
114
161
|
cleanStyles = initializeNativeEnv();
|
|
115
162
|
}
|
|
@@ -84,7 +84,7 @@ if (window.webkit) {
|
|
|
84
84
|
};
|
|
85
85
|
const doExit = async (uuid: string) => {
|
|
86
86
|
const shouldInterrupt = await RuntimeLoader.exit();
|
|
87
|
-
_joliboxJSCore.doExit.postMessage({ uuid, shouldInterrupt });
|
|
87
|
+
_joliboxJSCore.doExit.postMessage({ uuid, shouldInterrupt: shouldInterrupt });
|
|
88
88
|
};
|
|
89
89
|
|
|
90
90
|
joliboxJSCore = {
|
|
@@ -79,12 +79,19 @@ export function dirtyURL(url: string, method: HTTPMethod, data?: unknown) {
|
|
|
79
79
|
|
|
80
80
|
if (!isObject(data)) return url;
|
|
81
81
|
|
|
82
|
-
const [
|
|
82
|
+
const [baseUrl, search = ''] = url.split('?');
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
...data
|
|
87
|
-
});
|
|
84
|
+
// Parse existing query parameters
|
|
85
|
+
const dataParams = new URLSearchParams(search);
|
|
88
86
|
|
|
89
|
-
|
|
87
|
+
// Add new parameters from data object
|
|
88
|
+
for (const [key, value] of Object.entries(data)) {
|
|
89
|
+
if (value !== undefined && value !== null) {
|
|
90
|
+
dataParams.append(key, String(value));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const queryString = dataParams.toString();
|
|
95
|
+
|
|
96
|
+
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
|
90
97
|
}
|
|
@@ -262,6 +262,23 @@ declare global {
|
|
|
262
262
|
/** 错误码 */
|
|
263
263
|
errNo?: number;
|
|
264
264
|
};
|
|
265
|
+
|
|
266
|
+
openPageSync: (params: { url: string }) => {
|
|
267
|
+
/** 错误消息 */
|
|
268
|
+
errMsg: string;
|
|
269
|
+
/** 错误码 */
|
|
270
|
+
errNo?: number;
|
|
271
|
+
data?: {
|
|
272
|
+
webviewId: number;
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
closePageSync: (params: { webviewId: number }) => {
|
|
277
|
+
/** 错误消息 */
|
|
278
|
+
errMsg: string;
|
|
279
|
+
/** 错误码 */
|
|
280
|
+
errNo?: number;
|
|
281
|
+
};
|
|
265
282
|
}
|
|
266
283
|
|
|
267
284
|
interface NativeEventMap {
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { context } from '@/common/context';
|
|
2
|
+
import { invokeNative, subscribe } from '../bootstrap/bridge';
|
|
3
|
+
import { Deferred } from '@jolibox/common';
|
|
4
|
+
import { createRecommendModal, IGame, IRecommendationButton, RecommendModalOnCloseParams } from '@jolibox/ui';
|
|
5
|
+
import { innerFetch as fetch } from '../network';
|
|
6
|
+
|
|
7
|
+
let exitRecommendationsCache: {
|
|
8
|
+
code: string;
|
|
9
|
+
data: {
|
|
10
|
+
gameListInfo: {
|
|
11
|
+
games: IGame[];
|
|
12
|
+
};
|
|
13
|
+
title: string;
|
|
14
|
+
buttons: IRecommendationButton[];
|
|
15
|
+
};
|
|
16
|
+
} | null = null;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fetches exit recommendations data from the API
|
|
20
|
+
*/
|
|
21
|
+
async function fetchExitRecommendations() {
|
|
22
|
+
if (exitRecommendationsCache) {
|
|
23
|
+
return exitRecommendationsCache;
|
|
24
|
+
}
|
|
25
|
+
const host = context.testMode ? `https://stg-game.jolibox.com` : `https://game.jolibox.com`;
|
|
26
|
+
const url = `${host}/api/user-retention/exit-recommendations?objectId=${context.mpId}&from=GAME_DETAIL`;
|
|
27
|
+
const {
|
|
28
|
+
response: { data }
|
|
29
|
+
} = await fetch<{
|
|
30
|
+
code: string;
|
|
31
|
+
data: {
|
|
32
|
+
gameListInfo: {
|
|
33
|
+
games: IGame[];
|
|
34
|
+
};
|
|
35
|
+
title: string;
|
|
36
|
+
buttons: IRecommendationButton[];
|
|
37
|
+
};
|
|
38
|
+
}>(url);
|
|
39
|
+
|
|
40
|
+
exitRecommendationsCache = data;
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//prefetch
|
|
45
|
+
fetchExitRecommendations();
|
|
46
|
+
|
|
47
|
+
const openGameSchema = (game: IGame) => {
|
|
48
|
+
const { data } = invokeNative('envSync');
|
|
49
|
+
|
|
50
|
+
// Parse the original URL to preserve its structure
|
|
51
|
+
|
|
52
|
+
const url = new URL(data.schema);
|
|
53
|
+
const originalSearch = new URLSearchParams(url.search);
|
|
54
|
+
|
|
55
|
+
// Set or replace gameId and joliSource parameters
|
|
56
|
+
originalSearch.set('gameId', game.gameId);
|
|
57
|
+
originalSearch.set(
|
|
58
|
+
'joliSource',
|
|
59
|
+
context.encodeJoliSourceQuery({
|
|
60
|
+
__mpType: 'game',
|
|
61
|
+
__orientation: game.orientation ?? 'VERTICAL'
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const host = `https://${game.gameId}.content.jolibox.com/`;
|
|
66
|
+
// Construct the final schema URL
|
|
67
|
+
const schema = `${host}index.html?${originalSearch.toString()}`;
|
|
68
|
+
|
|
69
|
+
invokeNative('openSchemaSync', {
|
|
70
|
+
schema
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export async function openRetentionSchema() {
|
|
75
|
+
// const { data } = invokeNative('envSync');
|
|
76
|
+
// const { orientation, webviewId } = data;
|
|
77
|
+
// let joliPayload: Record<string, unknown> = {
|
|
78
|
+
// __mpType: 'miniApp',
|
|
79
|
+
// __transparent: true,
|
|
80
|
+
// // set entryPath
|
|
81
|
+
// __orientation: orientation ?? 'VERTICAL', // 默认竖屏
|
|
82
|
+
// __showStatusBar: false,
|
|
83
|
+
// __shouldInterupt: false,
|
|
84
|
+
// __showCapsuleButton: false
|
|
85
|
+
// };
|
|
86
|
+
// if (webviewId) {
|
|
87
|
+
// joliPayload = {
|
|
88
|
+
// ...joliPayload,
|
|
89
|
+
// __from: webviewId
|
|
90
|
+
// };
|
|
91
|
+
// }
|
|
92
|
+
|
|
93
|
+
// const joliSource = context.encodeJoliSourceQuery(joliPayload);
|
|
94
|
+
|
|
95
|
+
// const host = context.testMode
|
|
96
|
+
// ? `https://G32115508989327465281365749294.app.jolibox.com`
|
|
97
|
+
// : `https://G32115508989327465281365749294.app.jolibox.com`;
|
|
98
|
+
// const retentionSchema = `${host}/recommended-guide/${context.mpId}?appId=${context.mpId}&joliSource=${joliSource}&navigationStyle=present`;
|
|
99
|
+
|
|
100
|
+
const quitResultDeffer = new Deferred<boolean>();
|
|
101
|
+
// 小程序不走挽留逻辑
|
|
102
|
+
if (context.mpType !== 'game') {
|
|
103
|
+
quitResultDeffer.resolve(false);
|
|
104
|
+
return quitResultDeffer.promise;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const data = await fetchExitRecommendations();
|
|
108
|
+
|
|
109
|
+
if (data.code !== 'SUCCESS') {
|
|
110
|
+
quitResultDeffer.resolve(false);
|
|
111
|
+
return quitResultDeffer.promise;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { gameListInfo, title, buttons } = data.data;
|
|
115
|
+
|
|
116
|
+
const modal = createRecommendModal({
|
|
117
|
+
games: gameListInfo.games,
|
|
118
|
+
title,
|
|
119
|
+
buttons,
|
|
120
|
+
onClose: (params: RecommendModalOnCloseParams) => {
|
|
121
|
+
switch (params.type) {
|
|
122
|
+
case 'quit':
|
|
123
|
+
quitResultDeffer.resolve(false);
|
|
124
|
+
break;
|
|
125
|
+
case 'dismiss':
|
|
126
|
+
quitResultDeffer.resolve(true);
|
|
127
|
+
modal.hide();
|
|
128
|
+
break;
|
|
129
|
+
case 'navigate':
|
|
130
|
+
// TODO: 跳转游戏
|
|
131
|
+
if (params.data?.game) {
|
|
132
|
+
openGameSchema(params.data.game);
|
|
133
|
+
} else {
|
|
134
|
+
quitResultDeffer.resolve(true);
|
|
135
|
+
}
|
|
136
|
+
modal.hide();
|
|
137
|
+
break;
|
|
138
|
+
default:
|
|
139
|
+
// 关闭弹框,留在当前游戏
|
|
140
|
+
quitResultDeffer.resolve(true);
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
// // 异步
|
|
146
|
+
// setTimeout(() => {
|
|
147
|
+
// invokeNative('openSchemaSync', {
|
|
148
|
+
// schema: retentionSchema
|
|
149
|
+
// });
|
|
150
|
+
// }, 0);
|
|
151
|
+
return quitResultDeffer.promise;
|
|
152
|
+
}
|
|
@@ -1,40 +0,0 @@
|
|
|
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
|
-
}
|
|
File without changes
|