@blocklet/launcher-workflow 2.4.7 → 2.4.8

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.
@@ -6,7 +6,8 @@ import { LAUNCH_STATUS } from '@blocklet/launcher-util/lib/constant';
6
6
  import { useLocaleContext } from '../../contexts/locale';
7
7
  import useRequest from '../../contexts/request';
8
8
  import BaseServerlessLayout from './shared/base-serverless-layout';
9
- import { useDisplayProgress, useElapsedTime, useFormatTime } from './shared/common-components';
9
+ import { useFormatTime } from './shared/common-components';
10
+ import useWorkflowProgress from './shared/use-workflow-progress';
10
11
  import LoadingDisplayLayout from './shared/loading-display-layout';
11
12
  import RetryErrorMessage from './shared/retry-error-message';
12
13
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -27,8 +28,6 @@ export default function LaunchServerless({
27
28
  error: '',
28
29
  launchSession: null
29
30
  });
30
- const formatTime = useFormatTime();
31
- const time = useElapsedTime(0);
32
31
  const actions = useMemo(() => {
33
32
  // 基础步骤时间
34
33
  const steps = [{
@@ -66,7 +65,21 @@ export default function LaunchServerless({
66
65
  const estimatedTime = useMemo(() => {
67
66
  return actions.reduce((acc, action) => acc + action.time, 0);
68
67
  }, [actions]);
69
- const displayProgress = useDisplayProgress(0, estimatedTime);
68
+ const formatTime = useFormatTime();
69
+ const {
70
+ time,
71
+ displayProgress
72
+ } = useWorkflowProgress({
73
+ sessionId,
74
+ pageName: 'allocate',
75
+ estimatedTime,
76
+ isCompleted: state.allocated
77
+ });
78
+ useEffect(() => {
79
+ if (displayProgress >= 99 && state.allocated) {
80
+ navigate(`/install/${sessionId}${window.location.search || ''}`);
81
+ }
82
+ }, [displayProgress, state.allocated, sessionId, navigate]);
70
83
 
71
84
  // 根据当前进度获取对应的 action
72
85
  const getCurrentAction = progress => {
@@ -76,6 +89,7 @@ export default function LaunchServerless({
76
89
  return action?.message || actions[actions.length - 1].message;
77
90
  };
78
91
  useEffect(() => {
92
+ const timersRef = timerRef;
79
93
  const fetch = async () => {
80
94
  try {
81
95
  const {
@@ -94,7 +108,7 @@ export default function LaunchServerless({
94
108
  await Promise.all([api.post('/serverless/allocate', {
95
109
  launchId: sessionId
96
110
  }), new Promise(resolve => {
97
- timerRef.current.raceTimer = setTimeout(() => {
111
+ timersRef.current.raceTimer = setTimeout(() => {
98
112
  resolve();
99
113
  }, estimatedTime * 1000);
100
114
  })]);
@@ -112,7 +126,7 @@ export default function LaunchServerless({
112
126
  }
113
127
  };
114
128
  if (window.blocklet.DEVELOPER_WORKFLOW_UI) {
115
- timerRef.current.mockTimer = setTimeout(() => {
129
+ timersRef.current.mockTimer = setTimeout(() => {
116
130
  navigate(`/install/${sessionId}${window.location.search || ''}`);
117
131
  }, 5000);
118
132
  } else {
@@ -122,17 +136,7 @@ export default function LaunchServerless({
122
136
  const timers = Object.values(timerRef.current); // eslint-disable-line react-hooks/exhaustive-deps
123
137
  timers.forEach(timer => clearTimeout(timer));
124
138
  };
125
- // eslint-disable-next-line react-hooks/exhaustive-deps
126
- }, []);
127
- useEffect(() => {
128
- if (state.allocated) {
129
- const timer = setTimeout(() => {
130
- navigate(`/install/${sessionId}${window.location.search || ''}`);
131
- }, 1000);
132
- return () => clearTimeout(timer);
133
- }
134
- return () => {};
135
- }, [state.allocated, sessionId, navigate]);
139
+ }, [api, estimatedTime, navigate, sessionId, setState]);
136
140
  const handleRetry = async () => {
137
141
  try {
138
142
  setState({
@@ -7,7 +7,8 @@ import { useLocaleContext } from '../../contexts/locale';
7
7
  import useRequest from '../../contexts/request';
8
8
  import useSerialPolling from '../../hooks/use-serial-polling';
9
9
  import BaseServerlessLayout from './shared/base-serverless-layout';
10
- import { calculateEstimatedTime, useDisplayProgress, useElapsedTime, useFormatTime } from './shared/common-components';
10
+ import { calculateEstimatedTime, useFormatTime } from './shared/common-components';
11
+ import useWorkflowProgress from './shared/use-workflow-progress';
11
12
  import LoadingDisplayLayout from './shared/loading-display-layout';
12
13
  import RetryErrorMessage from './shared/retry-error-message';
13
14
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -80,6 +81,20 @@ export default function LaunchServerless({
80
81
  time
81
82
  }) => acc + time, 0) + Math.ceil(additionalTime * 0.6);
82
83
  }, [actions]);
84
+ const {
85
+ time,
86
+ displayProgress
87
+ } = useWorkflowProgress({
88
+ sessionId,
89
+ pageName: 'install',
90
+ estimatedTime,
91
+ isCompleted: state.status === STATUS.installed
92
+ });
93
+ useEffect(() => {
94
+ if (displayProgress >= 99 && state.status === STATUS.installed) {
95
+ navigate(`/start-app/${sessionId}${window.location.search || ''}`);
96
+ }
97
+ }, [displayProgress, state.status, sessionId, navigate]);
83
98
 
84
99
  // 根据当前进度获取对应的 action
85
100
  const getCurrentAction = progress => {
@@ -88,18 +103,6 @@ export default function LaunchServerless({
88
103
  }) => progress >= start && progress < end);
89
104
  return action?.message || actions[actions.length - 1].message;
90
105
  };
91
-
92
- // 时间管理 - 参考 launch-dedicated.js
93
- let installStartTime = sessionStorage.getItem(`launcher-install-${sessionId}-time`);
94
- if (!installStartTime) {
95
- installStartTime = Date.now();
96
- sessionStorage.setItem(`launcher-install-${sessionId}-time`, installStartTime);
97
- }
98
- const time = useElapsedTime(Math.round((Date.now() - installStartTime) / 1000));
99
- const startProgress = useMemo(() => {
100
- return Math.min(time / estimatedTime * 100, 90);
101
- }, [time, estimatedTime]);
102
- const displayProgress = useDisplayProgress(startProgress, state.status === STATUS.installed ? Math.min(estimatedTime - time, 2) : estimatedTime);
103
106
  const checkLaunchSession = async () => {
104
107
  if (window.blocklet.DEVELOPER_WORKFLOW_UI) {
105
108
  // 开发模式下的模拟逻辑
@@ -107,7 +110,6 @@ export default function LaunchServerless({
107
110
  setState({
108
111
  status: STATUS.installed
109
112
  });
110
- sessionStorage.removeItem(`launcher-install-${sessionId}-time`);
111
113
  navigate(`/start-app/${sessionId}${window.location.search || ''}`);
112
114
  }, 47000);
113
115
  return;
@@ -123,7 +125,6 @@ export default function LaunchServerless({
123
125
  } = launchSession.metadata;
124
126
  const hasInstalled = ['starting', 'installed', 'running', 'stopped'].includes(status) || launchSession.running || launchSession.status >= LAUNCH_STATUS.consuming;
125
127
  if (hasInstalled) {
126
- sessionStorage.removeItem(`launcher-install-${sessionId}-time`);
127
128
  setState({
128
129
  status: STATUS.installed
129
130
  });
@@ -152,7 +153,6 @@ export default function LaunchServerless({
152
153
  retryCount: state.retryCount + 1
153
154
  });
154
155
  } else {
155
- sessionStorage.removeItem(`launcher-install-${sessionId}-time`);
156
156
  setState({
157
157
  error: error.message,
158
158
  status: STATUS.error,
@@ -168,18 +168,7 @@ export default function LaunchServerless({
168
168
  interval: CHECK_INTERVAL,
169
169
  onPoll: checkLaunchSession
170
170
  });
171
- useEffect(() => {
172
- if (state.status === STATUS.installed && displayProgress >= 99) {
173
- const timer = setTimeout(() => {
174
- sessionStorage.removeItem(`launcher-install-${sessionId}-time`);
175
- navigate(`/start-app/${sessionId}${window.location.search || ''}`);
176
- }, 1500);
177
- return () => clearTimeout(timer);
178
- }
179
- return () => {};
180
- }, [state.status, sessionId, navigate, displayProgress]);
181
171
  const handleRetry = () => {
182
- sessionStorage.removeItem(`launcher-install-${sessionId}-time`);
183
172
  navigate(`/launch/${sessionId}${window.location.search || ''}`);
184
173
  };
185
174
  return /*#__PURE__*/_jsxs(BaseServerlessLayout, {
@@ -163,9 +163,9 @@ export function useDisplayProgress(start, duration = 5) {
163
163
  setProgress(prev => Math.max(prev, start));
164
164
  }, [start]);
165
165
  useEffect(() => {
166
- const timerDuration = Math.round((duration || 1) * 1000 / 99);
166
+ const timerDuration = Math.round((duration || 1) * 1000 / 100);
167
167
  const interval = setInterval(() => {
168
- setProgress(prev => Math.min(prev + 1, 99));
168
+ setProgress(prev => Math.min(prev + 1, 100));
169
169
  }, timerDuration);
170
170
  return () => clearInterval(interval);
171
171
  }, [duration]);
@@ -10,6 +10,7 @@ import React, { useState } from 'react';
10
10
  import { jsx as _jsx } from "react/jsx-runtime";
11
11
  export default function RetryErrorMessage({
12
12
  title,
13
+ desc = '',
13
14
  onRetry,
14
15
  retryText
15
16
  }) {
@@ -25,9 +26,13 @@ export default function RetryErrorMessage({
25
26
  setIsLoading(false);
26
27
  };
27
28
  return /*#__PURE__*/_jsx(LaunchResultMessage, {
29
+ style: {
30
+ marginTop: '20vh'
31
+ },
28
32
  variant: "error",
29
33
  title: title,
30
- footer: /*#__PURE__*/_jsx(Button, {
34
+ subTitle: desc,
35
+ footer: onRetry && /*#__PURE__*/_jsx(Button, {
31
36
  size: "small",
32
37
  style: {
33
38
  marginTop: '-16px'
@@ -40,6 +45,7 @@ export default function RetryErrorMessage({
40
45
  }
41
46
  RetryErrorMessage.propTypes = {
42
47
  title: PropTypes.string.isRequired,
48
+ desc: PropTypes.string,
43
49
  onRetry: PropTypes.func.isRequired,
44
50
  retryText: PropTypes.string.isRequired
45
51
  };
@@ -0,0 +1,184 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { useDisplayProgress, useElapsedTime } from './common-components';
3
+ const CACHE_KEY = 'launcher-workflow';
4
+ const CACHE_EXPIRE_DAYS = 3;
5
+
6
+ /**
7
+ * 工作流进度管理 Hook
8
+ * @param {Object} options
9
+ * @param {string} options.sessionId - 会话ID
10
+ * @param {string} options.pageName - 页面名称,用于缓存标识
11
+ * @param {number} options.estimatedTime - 预计完成时间(秒)
12
+ * @param {boolean} options.isCompleted - 是否已完成
13
+ * @param {number} [options.initialStartTime] - 初始开始时间
14
+ * @returns {Object} 进度相关数据
15
+ */
16
+ export default function useWorkflowProgress({
17
+ sessionId,
18
+ pageName,
19
+ estimatedTime,
20
+ isCompleted,
21
+ initialStartTime = Date.now()
22
+ }) {
23
+ // 缓存操作函数
24
+ const cache = useMemo(() => {
25
+ const getWorkflowCache = () => {
26
+ try {
27
+ return JSON.parse(localStorage.getItem(CACHE_KEY) || '{}');
28
+ } catch (error) {
29
+ return {};
30
+ }
31
+ };
32
+ const setWorkflowCache = data => {
33
+ try {
34
+ localStorage.setItem(CACHE_KEY, JSON.stringify(data));
35
+ } catch (error) {
36
+ console.error('Failed to save cache:', error);
37
+ }
38
+ };
39
+ return {
40
+ get: () => {
41
+ try {
42
+ const allCache = getWorkflowCache();
43
+ const sessionCache = allCache[sessionId] || {};
44
+ const pageCache = sessionCache[pageName];
45
+ if (!pageCache) return null;
46
+ const {
47
+ startTime,
48
+ progress,
49
+ expireTime
50
+ } = pageCache;
51
+
52
+ // 检查是否过期
53
+ if (expireTime && expireTime < Date.now()) {
54
+ // 删除过期缓存
55
+ delete sessionCache[pageName];
56
+ allCache[sessionId] = sessionCache;
57
+ setWorkflowCache(allCache);
58
+ return null;
59
+ }
60
+ return {
61
+ startTime,
62
+ progress
63
+ };
64
+ } catch (error) {
65
+ console.error('Failed to get cache:', error);
66
+ return null;
67
+ }
68
+ },
69
+ save: (currentStartTime, progress) => {
70
+ try {
71
+ const allCache = getWorkflowCache();
72
+ const sessionCache = allCache[sessionId] || {};
73
+ const expireTime = Date.now() + CACHE_EXPIRE_DAYS * 24 * 60 * 60 * 1000;
74
+ sessionCache[pageName] = {
75
+ startTime: currentStartTime,
76
+ progress,
77
+ expireTime
78
+ };
79
+ allCache[sessionId] = sessionCache;
80
+ setWorkflowCache(allCache);
81
+ } catch (error) {
82
+ console.error('Failed to save cache:', error);
83
+ }
84
+ },
85
+ clear: () => {
86
+ try {
87
+ const allCache = getWorkflowCache();
88
+ if (allCache[sessionId]) {
89
+ delete allCache[sessionId];
90
+ setWorkflowCache(allCache);
91
+ }
92
+ } catch (error) {
93
+ console.error('Failed to clear cache:', error);
94
+ }
95
+ },
96
+ // 清理所有过期缓存
97
+ cleanExpired: () => {
98
+ try {
99
+ const allCache = getWorkflowCache();
100
+ const now = Date.now();
101
+ let hasExpired = false;
102
+
103
+ // 遍历所有会话
104
+ Object.keys(allCache).forEach(sid => {
105
+ const sessionCache = allCache[sid];
106
+ // 遍历会话中的所有页面缓存
107
+ Object.keys(sessionCache).forEach(page => {
108
+ if (sessionCache[page].expireTime < now) {
109
+ delete sessionCache[page];
110
+ hasExpired = true;
111
+ }
112
+ });
113
+ // 如果会话没有页面缓存了,删除会话
114
+ if (Object.keys(sessionCache).length === 0) {
115
+ delete allCache[sid];
116
+ }
117
+ });
118
+ if (hasExpired) {
119
+ setWorkflowCache(allCache);
120
+ }
121
+ } catch (error) {
122
+ console.error('Failed to clean expired cache:', error);
123
+ }
124
+ }
125
+ };
126
+ }, [sessionId, pageName]);
127
+
128
+ // 从缓存获取开始时间
129
+ const startTimeRef = useRef();
130
+ startTimeRef.current = useMemo(() => {
131
+ if (startTimeRef.current) return startTimeRef.current;
132
+ const cached = cache.get();
133
+ return cached?.startTime || initialStartTime;
134
+ }, [cache, initialStartTime]);
135
+ const startTime = startTimeRef.current;
136
+ // 计算已经过去的时间
137
+ const time = useElapsedTime(Math.round((Date.now() - startTime) / 1000));
138
+
139
+ // 计算开始进度
140
+ const progressRef = useRef();
141
+ progressRef.current = useMemo(() => {
142
+ if (progressRef.current) return progressRef.current;
143
+ if (isCompleted) return 100;
144
+ const cached = cache.get();
145
+ if (cached?.progress) {
146
+ return Math.max(cached.progress, Math.min(time / estimatedTime * 100, 100));
147
+ }
148
+ return Math.min(time / estimatedTime * 100, 100);
149
+ }, [cache, time, estimatedTime, isCompleted]);
150
+ const startProgress = progressRef.current;
151
+
152
+ // 自动保存进度
153
+ const [lastSaveTime, setLastSaveTime] = useState(Date.now());
154
+ useEffect(() => {
155
+ if (isCompleted) {
156
+ cache.save(startTime, 100);
157
+ return undefined;
158
+ }
159
+ const saveTimer = setInterval(() => {
160
+ const now = Date.now();
161
+ // 每秒最多保存一次
162
+ if (now - lastSaveTime >= 1000) {
163
+ cache.save(startTime, startProgress);
164
+ setLastSaveTime(now);
165
+ }
166
+ }, 1000);
167
+ return () => {
168
+ clearInterval(saveTimer);
169
+ if (!isCompleted) {
170
+ cache.save(startTime, startProgress);
171
+ }
172
+ };
173
+ }, [cache, isCompleted, startProgress, lastSaveTime, startTime]);
174
+
175
+ // 使用进度显示 hook
176
+ const displayProgress = useDisplayProgress(startProgress, isCompleted ? Math.min(estimatedTime - time, 2) : estimatedTime);
177
+ return {
178
+ time: isCompleted ? Math.round((Date.now() - startTime) / 1000) : time,
179
+ startTime,
180
+ displayProgress,
181
+ startProgress,
182
+ cleanExpiredCache: cache.cleanExpired
183
+ };
184
+ }
@@ -1,6 +1,6 @@
1
1
  import { useSetState } from 'ahooks';
2
2
  import PropTypes from 'prop-types';
3
- import React, { useEffect, useMemo } from 'react';
3
+ import React, { useEffect, useMemo, useCallback } from 'react';
4
4
  import { useNavigate } from 'react-router-dom';
5
5
  import { LAUNCH_STATUS } from '@blocklet/launcher-util/es/constant';
6
6
  import { useLocaleContext } from '../../contexts/locale';
@@ -8,7 +8,8 @@ import useRequest from '../../contexts/request';
8
8
  import useSerialPolling from '../../hooks/use-serial-polling';
9
9
  import { checkBlockletAccessible, getBlockletUrls, sortUrls, waitingForRaceAccessible } from '../../util';
10
10
  import BaseServerlessLayout from './shared/base-serverless-layout';
11
- import { AppSuccessDisplay, calculateEstimatedTime, useDisplayProgress, useElapsedTime, useFormatTime } from './shared/common-components';
11
+ import { AppSuccessDisplay, calculateEstimatedTime, useFormatTime } from './shared/common-components';
12
+ import useWorkflowProgress from './shared/use-workflow-progress';
12
13
  import LoadingDisplayLayout from './shared/loading-display-layout';
13
14
  import RetryErrorMessage from './shared/retry-error-message';
14
15
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -33,12 +34,12 @@ export default function LaunchServerless({
33
34
  accessibleUrl: null,
34
35
  urls: [],
35
36
  error: '',
37
+ hiddenRetry: false,
36
38
  startTime: 0,
37
39
  hasTryStart: false,
38
40
  retryRequestCount: 0
39
41
  });
40
42
  const formatTime = useFormatTime();
41
- const time = useElapsedTime(0);
42
43
  const actions = useMemo(() => {
43
44
  // 基础步骤时间
44
45
  const steps = [{
@@ -84,7 +85,16 @@ export default function LaunchServerless({
84
85
  const additionalTime = calculateEstimatedTime(components);
85
86
  return actions.reduce((acc, action) => acc + action.time, 0) + additionalTime;
86
87
  }, [actions]);
87
- const displayProgress = useDisplayProgress(0, state.started ? 1 : estimatedTime);
88
+ const {
89
+ time,
90
+ displayProgress,
91
+ cleanExpiredCache
92
+ } = useWorkflowProgress({
93
+ sessionId,
94
+ pageName: 'start-app',
95
+ estimatedTime,
96
+ isCompleted: state.started
97
+ });
88
98
 
89
99
  // 根据当前进度获取对应的 action
90
100
  const getCurrentAction = progress => {
@@ -102,24 +112,30 @@ export default function LaunchServerless({
102
112
  return null;
103
113
  }
104
114
  };
105
- const getUrls = async blocklet => {
115
+ const getUrls = useCallback(async blocklet => {
106
116
  let {
107
117
  urls
108
118
  } = state;
119
+ const {
120
+ launchSession
121
+ } = state;
109
122
  if (urls.length === 0) {
110
123
  urls = getBlockletUrls({
111
- blocklet
124
+ blocklet: blocklet || launchSession?.blocklet || {}
112
125
  });
113
- const sortedUrls = await sortUrls(urls);
114
- urls = sortedUrls;
126
+ const defaultUrl = launchSession?.appUrl || launchSession?.appInfo?.appUrl;
115
127
  if (urls.length > 0) {
116
- setState({
117
- urls
118
- });
128
+ const sortedUrls = await sortUrls(urls);
129
+ urls = sortedUrls;
130
+ } else if (defaultUrl) {
131
+ urls = [defaultUrl];
119
132
  }
133
+ setState({
134
+ urls
135
+ });
120
136
  }
121
137
  return urls;
122
- };
138
+ }, [state, setState]);
123
139
  const checkLaunchSession = async () => {
124
140
  try {
125
141
  const {
@@ -205,7 +221,17 @@ export default function LaunchServerless({
205
221
  // 检查可用性 url
206
222
  const checkAccessibleUrls = async () => {
207
223
  try {
208
- const urls = await getUrls(state.launchSession.blocklet);
224
+ const urls = await getUrls();
225
+ if (urls.length === 0) {
226
+ setState({
227
+ error: 'noAppUrl',
228
+ hiddenRetry: true,
229
+ starting: false,
230
+ checkingBlockletStatus: false,
231
+ checkingAccessible: false
232
+ });
233
+ return;
234
+ }
209
235
  const accessibleUrl = await checkOneAccessibleUrl(urls);
210
236
  setState(prev => {
211
237
  const obj = accessibleUrl ? {
@@ -271,7 +297,7 @@ export default function LaunchServerless({
271
297
  started: false,
272
298
  checkingBlockletStatus: false,
273
299
  checkingAccessible: false,
274
- error: t('startApp.installFailed')
300
+ error: 'installFailed'
275
301
  };
276
302
  });
277
303
  }
@@ -281,14 +307,14 @@ export default function LaunchServerless({
281
307
  fetch(tryCount - 1);
282
308
  }, 1000);
283
309
  } else {
310
+ console.error('get launch session error', error);
284
311
  setState({
285
- error: error.message,
312
+ error: 'installFailed',
286
313
  starting: false,
287
314
  checkingBlockletStatus: false,
288
315
  checkingAccessible: false
289
316
  });
290
317
  }
291
- console.error('get launch session error', error);
292
318
  }
293
319
  };
294
320
  if (window.blocklet.DEVELOPER_WORKFLOW_UI) {
@@ -322,12 +348,16 @@ export default function LaunchServerless({
322
348
  window.location.reload();
323
349
  }
324
350
  };
325
- const showLoading = state.starting || displayProgress < 99;
326
- const showSuccess = !showLoading && state.started && state.launchSession && displayProgress >= 99;
327
- const showError = !showLoading && !showSuccess && state.error;
328
- if (showError) {
329
- console.error('showError', state.error);
330
- }
351
+ const showError = !!state.error;
352
+ const showSuccess = !showError && state.started && state.launchSession && displayProgress >= 99;
353
+ const showLoading = !state.error && !showSuccess;
354
+
355
+ // 在成功页面清理缓存
356
+ useEffect(() => {
357
+ if (showSuccess) {
358
+ cleanExpiredCache();
359
+ }
360
+ }, [showSuccess, cleanExpiredCache]);
331
361
  return /*#__PURE__*/_jsxs(BaseServerlessLayout, {
332
362
  title: t('startApp.pageTitle'),
333
363
  children: [showLoading && /*#__PURE__*/_jsx(LoadingDisplayLayout, {
@@ -346,7 +376,8 @@ export default function LaunchServerless({
346
376
  urls: state.urls
347
377
  }), showError && /*#__PURE__*/_jsx(RetryErrorMessage, {
348
378
  title: t('startApp.startFailed'),
349
- onRetry: handleRetry,
379
+ desc: state.error && state.error !== 'installFailed' ? t(`startApp.error.${state.error}`) : '',
380
+ onRetry: state.hiddenRetry ? null : handleRetry,
350
381
  retryText: t('common.retry')
351
382
  })]
352
383
  });
package/es/locales/en.js CHANGED
@@ -124,6 +124,9 @@ export default {
124
124
  creatingSecurityRules: 'Creating default security rules...',
125
125
  assigningDomain: 'Assigning default domain name...',
126
126
  waitingForDomain: 'Waiting for default domain name...'
127
+ },
128
+ error: {
129
+ noAppUrl: 'No accessible URL found, please contact the administrator'
127
130
  }
128
131
  },
129
132
  loading: {
package/es/locales/zh.js CHANGED
@@ -123,6 +123,9 @@ export default {
123
123
  creatingSecurityRules: '正在创建默认安全规则...',
124
124
  assigningDomain: '正在为 Blocklet 分配默认域名...',
125
125
  waitingForDomain: '正在等待默认域名生效...'
126
+ },
127
+ error: {
128
+ noAppUrl: '没有发现 Blocklet 的访问地址,请联系管理员'
126
129
  }
127
130
  },
128
131
  loading: {
@@ -13,6 +13,7 @@ var _locale = require("../../contexts/locale");
13
13
  var _request = _interopRequireDefault(require("../../contexts/request"));
14
14
  var _baseServerlessLayout = _interopRequireDefault(require("./shared/base-serverless-layout"));
15
15
  var _commonComponents = require("./shared/common-components");
16
+ var _useWorkflowProgress = _interopRequireDefault(require("./shared/use-workflow-progress"));
16
17
  var _loadingDisplayLayout = _interopRequireDefault(require("./shared/loading-display-layout"));
17
18
  var _retryErrorMessage = _interopRequireDefault(require("./shared/retry-error-message"));
18
19
  var _jsxRuntime = require("react/jsx-runtime");
@@ -41,8 +42,6 @@ function LaunchServerless(_ref) {
41
42
  error: '',
42
43
  launchSession: null
43
44
  });
44
- const formatTime = (0, _commonComponents.useFormatTime)();
45
- const time = (0, _commonComponents.useElapsedTime)(0);
46
45
  const actions = (0, _react.useMemo)(() => {
47
46
  // 基础步骤时间
48
47
  const steps = [{
@@ -79,7 +78,21 @@ function LaunchServerless(_ref) {
79
78
  const estimatedTime = (0, _react.useMemo)(() => {
80
79
  return actions.reduce((acc, action) => acc + action.time, 0);
81
80
  }, [actions]);
82
- const displayProgress = (0, _commonComponents.useDisplayProgress)(0, estimatedTime);
81
+ const formatTime = (0, _commonComponents.useFormatTime)();
82
+ const {
83
+ time,
84
+ displayProgress
85
+ } = (0, _useWorkflowProgress.default)({
86
+ sessionId,
87
+ pageName: 'allocate',
88
+ estimatedTime,
89
+ isCompleted: state.allocated
90
+ });
91
+ (0, _react.useEffect)(() => {
92
+ if (displayProgress >= 99 && state.allocated) {
93
+ navigate("/install/".concat(sessionId).concat(window.location.search || ''));
94
+ }
95
+ }, [displayProgress, state.allocated, sessionId, navigate]);
83
96
 
84
97
  // 根据当前进度获取对应的 action
85
98
  const getCurrentAction = progress => {
@@ -92,6 +105,7 @@ function LaunchServerless(_ref) {
92
105
  return (action === null || action === void 0 ? void 0 : action.message) || actions[actions.length - 1].message;
93
106
  };
94
107
  (0, _react.useEffect)(() => {
108
+ const timersRef = timerRef;
95
109
  const fetch = async () => {
96
110
  try {
97
111
  const {
@@ -110,7 +124,7 @@ function LaunchServerless(_ref) {
110
124
  await Promise.all([api.post('/serverless/allocate', {
111
125
  launchId: sessionId
112
126
  }), new Promise(resolve => {
113
- timerRef.current.raceTimer = setTimeout(() => {
127
+ timersRef.current.raceTimer = setTimeout(() => {
114
128
  resolve();
115
129
  }, estimatedTime * 1000);
116
130
  })]);
@@ -128,7 +142,7 @@ function LaunchServerless(_ref) {
128
142
  }
129
143
  };
130
144
  if (window.blocklet.DEVELOPER_WORKFLOW_UI) {
131
- timerRef.current.mockTimer = setTimeout(() => {
145
+ timersRef.current.mockTimer = setTimeout(() => {
132
146
  navigate("/install/".concat(sessionId).concat(window.location.search || ''));
133
147
  }, 5000);
134
148
  } else {
@@ -138,17 +152,7 @@ function LaunchServerless(_ref) {
138
152
  const timers = Object.values(timerRef.current); // eslint-disable-line react-hooks/exhaustive-deps
139
153
  timers.forEach(timer => clearTimeout(timer));
140
154
  };
141
- // eslint-disable-next-line react-hooks/exhaustive-deps
142
- }, []);
143
- (0, _react.useEffect)(() => {
144
- if (state.allocated) {
145
- const timer = setTimeout(() => {
146
- navigate("/install/".concat(sessionId).concat(window.location.search || ''));
147
- }, 1000);
148
- return () => clearTimeout(timer);
149
- }
150
- return () => {};
151
- }, [state.allocated, sessionId, navigate]);
155
+ }, [api, estimatedTime, navigate, sessionId, setState]);
152
156
  const handleRetry = async () => {
153
157
  try {
154
158
  var _state$launchSession;
@@ -14,6 +14,7 @@ var _request = _interopRequireDefault(require("../../contexts/request"));
14
14
  var _useSerialPolling = _interopRequireDefault(require("../../hooks/use-serial-polling"));
15
15
  var _baseServerlessLayout = _interopRequireDefault(require("./shared/base-serverless-layout"));
16
16
  var _commonComponents = require("./shared/common-components");
17
+ var _useWorkflowProgress = _interopRequireDefault(require("./shared/use-workflow-progress"));
17
18
  var _loadingDisplayLayout = _interopRequireDefault(require("./shared/loading-display-layout"));
18
19
  var _retryErrorMessage = _interopRequireDefault(require("./shared/retry-error-message"));
19
20
  var _jsxRuntime = require("react/jsx-runtime");
@@ -97,6 +98,20 @@ function LaunchServerless(_ref) {
97
98
  return acc + time;
98
99
  }, 0) + Math.ceil(additionalTime * 0.6);
99
100
  }, [actions]);
101
+ const {
102
+ time,
103
+ displayProgress
104
+ } = (0, _useWorkflowProgress.default)({
105
+ sessionId,
106
+ pageName: 'install',
107
+ estimatedTime,
108
+ isCompleted: state.status === STATUS.installed
109
+ });
110
+ (0, _react.useEffect)(() => {
111
+ if (displayProgress >= 99 && state.status === STATUS.installed) {
112
+ navigate("/start-app/".concat(sessionId).concat(window.location.search || ''));
113
+ }
114
+ }, [displayProgress, state.status, sessionId, navigate]);
100
115
 
101
116
  // 根据当前进度获取对应的 action
102
117
  const getCurrentAction = progress => {
@@ -108,18 +123,6 @@ function LaunchServerless(_ref) {
108
123
  });
109
124
  return (action === null || action === void 0 ? void 0 : action.message) || actions[actions.length - 1].message;
110
125
  };
111
-
112
- // 时间管理 - 参考 launch-dedicated.js
113
- let installStartTime = sessionStorage.getItem("launcher-install-".concat(sessionId, "-time"));
114
- if (!installStartTime) {
115
- installStartTime = Date.now();
116
- sessionStorage.setItem("launcher-install-".concat(sessionId, "-time"), installStartTime);
117
- }
118
- const time = (0, _commonComponents.useElapsedTime)(Math.round((Date.now() - installStartTime) / 1000));
119
- const startProgress = (0, _react.useMemo)(() => {
120
- return Math.min(time / estimatedTime * 100, 90);
121
- }, [time, estimatedTime]);
122
- const displayProgress = (0, _commonComponents.useDisplayProgress)(startProgress, state.status === STATUS.installed ? Math.min(estimatedTime - time, 2) : estimatedTime);
123
126
  const checkLaunchSession = async () => {
124
127
  if (window.blocklet.DEVELOPER_WORKFLOW_UI) {
125
128
  // 开发模式下的模拟逻辑
@@ -127,7 +130,6 @@ function LaunchServerless(_ref) {
127
130
  setState({
128
131
  status: STATUS.installed
129
132
  });
130
- sessionStorage.removeItem("launcher-install-".concat(sessionId, "-time"));
131
133
  navigate("/start-app/".concat(sessionId).concat(window.location.search || ''));
132
134
  }, 47000);
133
135
  return;
@@ -143,7 +145,6 @@ function LaunchServerless(_ref) {
143
145
  } = launchSession.metadata;
144
146
  const hasInstalled = ['starting', 'installed', 'running', 'stopped'].includes(status) || launchSession.running || launchSession.status >= _constant.LAUNCH_STATUS.consuming;
145
147
  if (hasInstalled) {
146
- sessionStorage.removeItem("launcher-install-".concat(sessionId, "-time"));
147
148
  setState({
148
149
  status: STATUS.installed
149
150
  });
@@ -172,7 +173,6 @@ function LaunchServerless(_ref) {
172
173
  retryCount: state.retryCount + 1
173
174
  });
174
175
  } else {
175
- sessionStorage.removeItem("launcher-install-".concat(sessionId, "-time"));
176
176
  setState({
177
177
  error: error.message,
178
178
  status: STATUS.error,
@@ -188,18 +188,7 @@ function LaunchServerless(_ref) {
188
188
  interval: CHECK_INTERVAL,
189
189
  onPoll: checkLaunchSession
190
190
  });
191
- (0, _react.useEffect)(() => {
192
- if (state.status === STATUS.installed && displayProgress >= 99) {
193
- const timer = setTimeout(() => {
194
- sessionStorage.removeItem("launcher-install-".concat(sessionId, "-time"));
195
- navigate("/start-app/".concat(sessionId).concat(window.location.search || ''));
196
- }, 1500);
197
- return () => clearTimeout(timer);
198
- }
199
- return () => {};
200
- }, [state.status, sessionId, navigate, displayProgress]);
201
191
  const handleRetry = () => {
202
- sessionStorage.removeItem("launcher-install-".concat(sessionId, "-time"));
203
192
  navigate("/launch/".concat(sessionId).concat(window.location.search || ''));
204
193
  };
205
194
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_baseServerlessLayout.default, {
@@ -190,9 +190,9 @@ function useDisplayProgress(start) {
190
190
  setProgress(prev => Math.max(prev, start));
191
191
  }, [start]);
192
192
  (0, _react.useEffect)(() => {
193
- const timerDuration = Math.round((duration || 1) * 1000 / 99);
193
+ const timerDuration = Math.round((duration || 1) * 1000 / 100);
194
194
  const interval = setInterval(() => {
195
- setProgress(prev => Math.min(prev + 1, 99));
195
+ setProgress(prev => Math.min(prev + 1, 100));
196
196
  }, timerDuration);
197
197
  return () => clearInterval(interval);
198
198
  }, [duration]);
@@ -17,6 +17,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
17
17
  */function RetryErrorMessage(_ref) {
18
18
  let {
19
19
  title,
20
+ desc = '',
20
21
  onRetry,
21
22
  retryText
22
23
  } = _ref;
@@ -32,9 +33,13 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
32
33
  setIsLoading(false);
33
34
  };
34
35
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_launchResultMessage.default, {
36
+ style: {
37
+ marginTop: '20vh'
38
+ },
35
39
  variant: "error",
36
40
  title: title,
37
- footer: /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.default, {
41
+ subTitle: desc,
42
+ footer: onRetry && /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.default, {
38
43
  size: "small",
39
44
  style: {
40
45
  marginTop: '-16px'
@@ -47,6 +52,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
47
52
  }
48
53
  RetryErrorMessage.propTypes = {
49
54
  title: _propTypes.default.string.isRequired,
55
+ desc: _propTypes.default.string,
50
56
  onRetry: _propTypes.default.func.isRequired,
51
57
  retryText: _propTypes.default.string.isRequired
52
58
  };
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = useWorkflowProgress;
7
+ var _react = require("react");
8
+ var _commonComponents = require("./common-components");
9
+ const CACHE_KEY = 'launcher-workflow';
10
+ const CACHE_EXPIRE_DAYS = 3;
11
+
12
+ /**
13
+ * 工作流进度管理 Hook
14
+ * @param {Object} options
15
+ * @param {string} options.sessionId - 会话ID
16
+ * @param {string} options.pageName - 页面名称,用于缓存标识
17
+ * @param {number} options.estimatedTime - 预计完成时间(秒)
18
+ * @param {boolean} options.isCompleted - 是否已完成
19
+ * @param {number} [options.initialStartTime] - 初始开始时间
20
+ * @returns {Object} 进度相关数据
21
+ */
22
+ function useWorkflowProgress(_ref) {
23
+ let {
24
+ sessionId,
25
+ pageName,
26
+ estimatedTime,
27
+ isCompleted,
28
+ initialStartTime = Date.now()
29
+ } = _ref;
30
+ // 缓存操作函数
31
+ const cache = (0, _react.useMemo)(() => {
32
+ const getWorkflowCache = () => {
33
+ try {
34
+ return JSON.parse(localStorage.getItem(CACHE_KEY) || '{}');
35
+ } catch (error) {
36
+ return {};
37
+ }
38
+ };
39
+ const setWorkflowCache = data => {
40
+ try {
41
+ localStorage.setItem(CACHE_KEY, JSON.stringify(data));
42
+ } catch (error) {
43
+ console.error('Failed to save cache:', error);
44
+ }
45
+ };
46
+ return {
47
+ get: () => {
48
+ try {
49
+ const allCache = getWorkflowCache();
50
+ const sessionCache = allCache[sessionId] || {};
51
+ const pageCache = sessionCache[pageName];
52
+ if (!pageCache) return null;
53
+ const {
54
+ startTime,
55
+ progress,
56
+ expireTime
57
+ } = pageCache;
58
+
59
+ // 检查是否过期
60
+ if (expireTime && expireTime < Date.now()) {
61
+ // 删除过期缓存
62
+ delete sessionCache[pageName];
63
+ allCache[sessionId] = sessionCache;
64
+ setWorkflowCache(allCache);
65
+ return null;
66
+ }
67
+ return {
68
+ startTime,
69
+ progress
70
+ };
71
+ } catch (error) {
72
+ console.error('Failed to get cache:', error);
73
+ return null;
74
+ }
75
+ },
76
+ save: (currentStartTime, progress) => {
77
+ try {
78
+ const allCache = getWorkflowCache();
79
+ const sessionCache = allCache[sessionId] || {};
80
+ const expireTime = Date.now() + CACHE_EXPIRE_DAYS * 24 * 60 * 60 * 1000;
81
+ sessionCache[pageName] = {
82
+ startTime: currentStartTime,
83
+ progress,
84
+ expireTime
85
+ };
86
+ allCache[sessionId] = sessionCache;
87
+ setWorkflowCache(allCache);
88
+ } catch (error) {
89
+ console.error('Failed to save cache:', error);
90
+ }
91
+ },
92
+ clear: () => {
93
+ try {
94
+ const allCache = getWorkflowCache();
95
+ if (allCache[sessionId]) {
96
+ delete allCache[sessionId];
97
+ setWorkflowCache(allCache);
98
+ }
99
+ } catch (error) {
100
+ console.error('Failed to clear cache:', error);
101
+ }
102
+ },
103
+ // 清理所有过期缓存
104
+ cleanExpired: () => {
105
+ try {
106
+ const allCache = getWorkflowCache();
107
+ const now = Date.now();
108
+ let hasExpired = false;
109
+
110
+ // 遍历所有会话
111
+ Object.keys(allCache).forEach(sid => {
112
+ const sessionCache = allCache[sid];
113
+ // 遍历会话中的所有页面缓存
114
+ Object.keys(sessionCache).forEach(page => {
115
+ if (sessionCache[page].expireTime < now) {
116
+ delete sessionCache[page];
117
+ hasExpired = true;
118
+ }
119
+ });
120
+ // 如果会话没有页面缓存了,删除会话
121
+ if (Object.keys(sessionCache).length === 0) {
122
+ delete allCache[sid];
123
+ }
124
+ });
125
+ if (hasExpired) {
126
+ setWorkflowCache(allCache);
127
+ }
128
+ } catch (error) {
129
+ console.error('Failed to clean expired cache:', error);
130
+ }
131
+ }
132
+ };
133
+ }, [sessionId, pageName]);
134
+
135
+ // 从缓存获取开始时间
136
+ const startTimeRef = (0, _react.useRef)();
137
+ startTimeRef.current = (0, _react.useMemo)(() => {
138
+ if (startTimeRef.current) return startTimeRef.current;
139
+ const cached = cache.get();
140
+ return (cached === null || cached === void 0 ? void 0 : cached.startTime) || initialStartTime;
141
+ }, [cache, initialStartTime]);
142
+ const startTime = startTimeRef.current;
143
+ // 计算已经过去的时间
144
+ const time = (0, _commonComponents.useElapsedTime)(Math.round((Date.now() - startTime) / 1000));
145
+
146
+ // 计算开始进度
147
+ const progressRef = (0, _react.useRef)();
148
+ progressRef.current = (0, _react.useMemo)(() => {
149
+ if (progressRef.current) return progressRef.current;
150
+ if (isCompleted) return 100;
151
+ const cached = cache.get();
152
+ if (cached !== null && cached !== void 0 && cached.progress) {
153
+ return Math.max(cached.progress, Math.min(time / estimatedTime * 100, 100));
154
+ }
155
+ return Math.min(time / estimatedTime * 100, 100);
156
+ }, [cache, time, estimatedTime, isCompleted]);
157
+ const startProgress = progressRef.current;
158
+
159
+ // 自动保存进度
160
+ const [lastSaveTime, setLastSaveTime] = (0, _react.useState)(Date.now());
161
+ (0, _react.useEffect)(() => {
162
+ if (isCompleted) {
163
+ cache.save(startTime, 100);
164
+ return undefined;
165
+ }
166
+ const saveTimer = setInterval(() => {
167
+ const now = Date.now();
168
+ // 每秒最多保存一次
169
+ if (now - lastSaveTime >= 1000) {
170
+ cache.save(startTime, startProgress);
171
+ setLastSaveTime(now);
172
+ }
173
+ }, 1000);
174
+ return () => {
175
+ clearInterval(saveTimer);
176
+ if (!isCompleted) {
177
+ cache.save(startTime, startProgress);
178
+ }
179
+ };
180
+ }, [cache, isCompleted, startProgress, lastSaveTime, startTime]);
181
+
182
+ // 使用进度显示 hook
183
+ const displayProgress = (0, _commonComponents.useDisplayProgress)(startProgress, isCompleted ? Math.min(estimatedTime - time, 2) : estimatedTime);
184
+ return {
185
+ time: isCompleted ? Math.round((Date.now() - startTime) / 1000) : time,
186
+ startTime,
187
+ displayProgress,
188
+ startProgress,
189
+ cleanExpiredCache: cache.cleanExpired
190
+ };
191
+ }
@@ -15,6 +15,7 @@ var _useSerialPolling = _interopRequireDefault(require("../../hooks/use-serial-p
15
15
  var _util = require("../../util");
16
16
  var _baseServerlessLayout = _interopRequireDefault(require("./shared/base-serverless-layout"));
17
17
  var _commonComponents = require("./shared/common-components");
18
+ var _useWorkflowProgress = _interopRequireDefault(require("./shared/use-workflow-progress"));
18
19
  var _loadingDisplayLayout = _interopRequireDefault(require("./shared/loading-display-layout"));
19
20
  var _retryErrorMessage = _interopRequireDefault(require("./shared/retry-error-message"));
20
21
  var _jsxRuntime = require("react/jsx-runtime");
@@ -47,12 +48,12 @@ function LaunchServerless(_ref) {
47
48
  accessibleUrl: null,
48
49
  urls: [],
49
50
  error: '',
51
+ hiddenRetry: false,
50
52
  startTime: 0,
51
53
  hasTryStart: false,
52
54
  retryRequestCount: 0
53
55
  });
54
56
  const formatTime = (0, _commonComponents.useFormatTime)();
55
- const time = (0, _commonComponents.useElapsedTime)(0);
56
57
  const actions = (0, _react.useMemo)(() => {
57
58
  // 基础步骤时间
58
59
  const steps = [{
@@ -98,7 +99,16 @@ function LaunchServerless(_ref) {
98
99
  const additionalTime = (0, _commonComponents.calculateEstimatedTime)(components);
99
100
  return actions.reduce((acc, action) => acc + action.time, 0) + additionalTime;
100
101
  }, [actions]);
101
- const displayProgress = (0, _commonComponents.useDisplayProgress)(0, state.started ? 1 : estimatedTime);
102
+ const {
103
+ time,
104
+ displayProgress,
105
+ cleanExpiredCache
106
+ } = (0, _useWorkflowProgress.default)({
107
+ sessionId,
108
+ pageName: 'start-app',
109
+ estimatedTime,
110
+ isCompleted: state.started
111
+ });
102
112
 
103
113
  // 根据当前进度获取对应的 action
104
114
  const getCurrentAction = progress => {
@@ -119,24 +129,31 @@ function LaunchServerless(_ref) {
119
129
  return null;
120
130
  }
121
131
  };
122
- const getUrls = async blocklet => {
132
+ const getUrls = (0, _react.useCallback)(async blocklet => {
123
133
  let {
124
134
  urls
125
135
  } = state;
136
+ const {
137
+ launchSession
138
+ } = state;
126
139
  if (urls.length === 0) {
140
+ var _launchSession$appInf;
127
141
  urls = (0, _util.getBlockletUrls)({
128
- blocklet
142
+ blocklet: blocklet || (launchSession === null || launchSession === void 0 ? void 0 : launchSession.blocklet) || {}
129
143
  });
130
- const sortedUrls = await (0, _util.sortUrls)(urls);
131
- urls = sortedUrls;
144
+ const defaultUrl = (launchSession === null || launchSession === void 0 ? void 0 : launchSession.appUrl) || (launchSession === null || launchSession === void 0 || (_launchSession$appInf = launchSession.appInfo) === null || _launchSession$appInf === void 0 ? void 0 : _launchSession$appInf.appUrl);
132
145
  if (urls.length > 0) {
133
- setState({
134
- urls
135
- });
146
+ const sortedUrls = await (0, _util.sortUrls)(urls);
147
+ urls = sortedUrls;
148
+ } else if (defaultUrl) {
149
+ urls = [defaultUrl];
136
150
  }
151
+ setState({
152
+ urls
153
+ });
137
154
  }
138
155
  return urls;
139
- };
156
+ }, [state, setState]);
140
157
  const checkLaunchSession = async () => {
141
158
  try {
142
159
  const {
@@ -219,7 +236,17 @@ function LaunchServerless(_ref) {
219
236
  // 检查可用性 url
220
237
  const checkAccessibleUrls = async () => {
221
238
  try {
222
- const urls = await getUrls(state.launchSession.blocklet);
239
+ const urls = await getUrls();
240
+ if (urls.length === 0) {
241
+ setState({
242
+ error: 'noAppUrl',
243
+ hiddenRetry: true,
244
+ starting: false,
245
+ checkingBlockletStatus: false,
246
+ checkingAccessible: false
247
+ });
248
+ return;
249
+ }
223
250
  const accessibleUrl = await checkOneAccessibleUrl(urls);
224
251
  setState(prev => {
225
252
  const obj = accessibleUrl ? _objectSpread(_objectSpread({}, prev), {}, {
@@ -284,7 +311,7 @@ function LaunchServerless(_ref) {
284
311
  started: false,
285
312
  checkingBlockletStatus: false,
286
313
  checkingAccessible: false,
287
- error: t('startApp.installFailed')
314
+ error: 'installFailed'
288
315
  });
289
316
  });
290
317
  }
@@ -294,14 +321,14 @@ function LaunchServerless(_ref) {
294
321
  _fetch(tryCount - 1);
295
322
  }, 1000);
296
323
  } else {
324
+ console.error('get launch session error', error);
297
325
  setState({
298
- error: error.message,
326
+ error: 'installFailed',
299
327
  starting: false,
300
328
  checkingBlockletStatus: false,
301
329
  checkingAccessible: false
302
330
  });
303
331
  }
304
- console.error('get launch session error', error);
305
332
  }
306
333
  };
307
334
  if (window.blocklet.DEVELOPER_WORKFLOW_UI) {
@@ -335,12 +362,16 @@ function LaunchServerless(_ref) {
335
362
  window.location.reload();
336
363
  }
337
364
  };
338
- const showLoading = state.starting || displayProgress < 99;
339
- const showSuccess = !showLoading && state.started && state.launchSession && displayProgress >= 99;
340
- const showError = !showLoading && !showSuccess && state.error;
341
- if (showError) {
342
- console.error('showError', state.error);
343
- }
365
+ const showError = !!state.error;
366
+ const showSuccess = !showError && state.started && state.launchSession && displayProgress >= 99;
367
+ const showLoading = !state.error && !showSuccess;
368
+
369
+ // 在成功页面清理缓存
370
+ (0, _react.useEffect)(() => {
371
+ if (showSuccess) {
372
+ cleanExpiredCache();
373
+ }
374
+ }, [showSuccess, cleanExpiredCache]);
344
375
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_baseServerlessLayout.default, {
345
376
  title: t('startApp.pageTitle'),
346
377
  children: [showLoading && /*#__PURE__*/(0, _jsxRuntime.jsx)(_loadingDisplayLayout.default, {
@@ -359,7 +390,8 @@ function LaunchServerless(_ref) {
359
390
  urls: state.urls
360
391
  }), showError && /*#__PURE__*/(0, _jsxRuntime.jsx)(_retryErrorMessage.default, {
361
392
  title: t('startApp.startFailed'),
362
- onRetry: handleRetry,
393
+ desc: state.error && state.error !== 'installFailed' ? t("startApp.error.".concat(state.error)) : '',
394
+ onRetry: state.hiddenRetry ? null : handleRetry,
363
395
  retryText: t('common.retry')
364
396
  })]
365
397
  });
package/lib/locales/en.js CHANGED
@@ -130,6 +130,9 @@ var _default = exports.default = {
130
130
  creatingSecurityRules: 'Creating default security rules...',
131
131
  assigningDomain: 'Assigning default domain name...',
132
132
  waitingForDomain: 'Waiting for default domain name...'
133
+ },
134
+ error: {
135
+ noAppUrl: 'No accessible URL found, please contact the administrator'
133
136
  }
134
137
  },
135
138
  loading: {
package/lib/locales/zh.js CHANGED
@@ -129,6 +129,9 @@ var _default = exports.default = {
129
129
  creatingSecurityRules: '正在创建默认安全规则...',
130
130
  assigningDomain: '正在为 Blocklet 分配默认域名...',
131
131
  waitingForDomain: '正在等待默认域名生效...'
132
+ },
133
+ error: {
134
+ noAppUrl: '没有发现 Blocklet 的访问地址,请联系管理员'
132
135
  }
133
136
  },
134
137
  loading: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/launcher-workflow",
3
- "version": "2.4.7",
3
+ "version": "2.4.8",
4
4
  "description": "Purchase components for Launcher UI",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -49,8 +49,8 @@
49
49
  "@arcblock/react-hooks": "^3.0.27",
50
50
  "@arcblock/ux": "^3.0.27",
51
51
  "@blocklet/launcher-layout": "^3.0.27",
52
- "@blocklet/launcher-util": "2.4.7",
53
- "@blocklet/launcher-ux": "2.4.7",
52
+ "@blocklet/launcher-util": "2.4.8",
53
+ "@blocklet/launcher-ux": "2.4.8",
54
54
  "@blocklet/payment": "^1.14.8",
55
55
  "@blocklet/payment-react": "^1.19.5",
56
56
  "@emotion/react": "^11.14.0",
@@ -109,5 +109,5 @@
109
109
  "require": "./lib/locales/index.js"
110
110
  }
111
111
  },
112
- "gitHead": "46f2b90eb04391a02f3fc3dafad2517a73e25994"
112
+ "gitHead": "ad6b9051901b11aeacc305087d5d5ad89570a6e9"
113
113
  }