@jolibox/implement 1.1.11-beta.1 → 1.1.11-beta.10

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.
@@ -76,10 +76,13 @@ export const parseUrlQuery = (url: string): QueryParams => {
76
76
  }
77
77
  };
78
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}`;
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
  };
@@ -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
- commands.registerCommand('API.openSchemaSync', openSchemaSync);
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('openSchemaSync', {
22
- version: '1.1.9'
59
+ registerCanIUse('router.closePage', {
60
+ version: '1.1.10'
23
61
  });
@@ -5,7 +5,7 @@ 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
+ import { openRetentionSchema } from '../ui/retention';
9
9
 
10
10
  let cleanStyles: () => void;
11
11
  RuntimeLoader.onReady(() => {
@@ -42,11 +42,11 @@ RuntimeLoader.doExit(async () => {
42
42
  return context.shouldInterupt;
43
43
  }
44
44
 
45
- // const stay = await openRetentionSchema();
46
- // if (stay) {
47
- // // 挽留成功,打断退出
48
- // return true;
49
- // }
45
+ const stay = await openRetentionSchema();
46
+ if (stay) {
47
+ // 挽留成功,打断退出
48
+ return true;
49
+ }
50
50
 
51
51
  // 退出,对应上报
52
52
  //埋点上报
@@ -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 = {
@@ -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