@blocklet/launcher-workflow 2.4.4 → 2.4.6
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/es/components/in-progress-session.js +1 -1
- package/es/components/launch-serverless/allocate.js +181 -0
- package/es/components/launch-serverless/install.js +203 -0
- package/es/components/launch-serverless/shared/base-serverless-layout.js +53 -0
- package/es/components/launch-serverless/shared/common-components.js +605 -0
- package/es/components/launch-serverless/shared/loading-display-layout.js +122 -0
- package/es/components/launch-serverless/shared/retry-error-message.js +45 -0
- package/es/components/launch-serverless/start-app.js +356 -0
- package/es/contexts/request.js +2 -2
- package/es/hooks/use-serial-polling.js +43 -0
- package/es/install.js +28 -0
- package/es/launch.js +1 -1
- package/es/locales/en.js +71 -14
- package/es/locales/zh.js +68 -12
- package/es/paid.js +1 -1
- package/es/prepare.js +1 -1
- package/es/start-app.js +28 -0
- package/es/util.js +181 -2
- package/lib/components/in-progress-session.js +3 -3
- package/lib/components/launch-serverless/allocate.js +198 -0
- package/lib/components/launch-serverless/install.js +223 -0
- package/lib/components/launch-serverless/shared/base-serverless-layout.js +59 -0
- package/lib/components/launch-serverless/shared/common-components.js +635 -0
- package/lib/components/launch-serverless/shared/loading-display-layout.js +131 -0
- package/lib/components/launch-serverless/shared/retry-error-message.js +52 -0
- package/lib/components/launch-serverless/start-app.js +369 -0
- package/lib/contexts/request.js +2 -2
- package/lib/hooks/use-serial-polling.js +49 -0
- package/lib/install.js +35 -0
- package/lib/launch.js +2 -2
- package/lib/locales/en.js +71 -14
- package/lib/locales/zh.js +68 -12
- package/lib/paid.js +2 -2
- package/lib/prepare.js +2 -2
- package/lib/start-app.js +35 -0
- package/lib/util.js +214 -11
- package/package.json +16 -13
- package/es/components/launch-serverless.js +0 -115
- package/lib/components/launch-serverless.js +0 -89
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import AnimationWaiter from '@arcblock/ux/lib/AnimationWaiter';
|
|
2
|
+
import { Box, Typography } from '@mui/material';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { useRequest as useRequestHook } from 'ahooks';
|
|
6
|
+
import { withQuery } from 'ufo';
|
|
7
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
8
|
+
import useRequest from '../../../contexts/request';
|
|
9
|
+
import { ActionCardBox, ErrorDisplay, LoadingContainer, ProductIntroCarousel, StyledProgress, TimeText } from './common-components';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 加载显示的通用布局组件
|
|
13
|
+
* 抽取三个阶段组件的共同结构,具体内容由外部传入
|
|
14
|
+
*/
|
|
15
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
16
|
+
function LoadingDisplayLayout({
|
|
17
|
+
title,
|
|
18
|
+
// 进度条数据
|
|
19
|
+
progress,
|
|
20
|
+
elapsedTime,
|
|
21
|
+
estimatedTime,
|
|
22
|
+
// 动作描述数据
|
|
23
|
+
currentAction,
|
|
24
|
+
// 动画控制
|
|
25
|
+
tipFlag = null,
|
|
26
|
+
// 错误处理
|
|
27
|
+
error = null,
|
|
28
|
+
onRetry = null
|
|
29
|
+
}) {
|
|
30
|
+
const {
|
|
31
|
+
locale
|
|
32
|
+
} = useLocaleContext();
|
|
33
|
+
const {
|
|
34
|
+
t
|
|
35
|
+
} = useLocaleContext();
|
|
36
|
+
const {
|
|
37
|
+
api
|
|
38
|
+
} = useRequest();
|
|
39
|
+
const {
|
|
40
|
+
data: tips = []
|
|
41
|
+
} = useRequestHook(() => api.get(withQuery('/public/tips', {
|
|
42
|
+
locale
|
|
43
|
+
})).then(res => res.data?.tips || [], {
|
|
44
|
+
refreshDeps: [locale]
|
|
45
|
+
}));
|
|
46
|
+
return /*#__PURE__*/_jsxs(LoadingContainer, {
|
|
47
|
+
children: [/*#__PURE__*/_jsx(Typography, {
|
|
48
|
+
variant: "h3",
|
|
49
|
+
children: title
|
|
50
|
+
}), /*#__PURE__*/_jsx(Box, {
|
|
51
|
+
sx: {
|
|
52
|
+
'& div': {
|
|
53
|
+
p: 0,
|
|
54
|
+
m: 0
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
children: /*#__PURE__*/_jsx(AnimationWaiter, {
|
|
58
|
+
increaseSpeed: 0.3,
|
|
59
|
+
messageLoop: false,
|
|
60
|
+
size: 200
|
|
61
|
+
})
|
|
62
|
+
}), /*#__PURE__*/_jsxs(Box, {
|
|
63
|
+
sx: {
|
|
64
|
+
position: 'relative',
|
|
65
|
+
width: '100%'
|
|
66
|
+
},
|
|
67
|
+
children: [/*#__PURE__*/_jsx(Box, {
|
|
68
|
+
sx: {
|
|
69
|
+
display: 'flex',
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
justifyContent: 'flex-end',
|
|
72
|
+
mb: 1
|
|
73
|
+
},
|
|
74
|
+
children: /*#__PURE__*/_jsx(TimeText, {
|
|
75
|
+
children: t('loading.remainingTime', {
|
|
76
|
+
time: estimatedTime
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
}), /*#__PURE__*/_jsx(StyledProgress, {
|
|
80
|
+
value: progress
|
|
81
|
+
}), /*#__PURE__*/_jsxs(Box, {
|
|
82
|
+
sx: {
|
|
83
|
+
display: 'flex',
|
|
84
|
+
alignItems: 'center',
|
|
85
|
+
justifyContent: 'space-between',
|
|
86
|
+
my: 1
|
|
87
|
+
},
|
|
88
|
+
children: [/*#__PURE__*/_jsxs(Typography, {
|
|
89
|
+
variant: "caption",
|
|
90
|
+
color: "text.secondary",
|
|
91
|
+
children: [Math.round(progress), "% ", t('loading.completed')]
|
|
92
|
+
}), /*#__PURE__*/_jsx(TimeText, {
|
|
93
|
+
children: elapsedTime
|
|
94
|
+
})]
|
|
95
|
+
})]
|
|
96
|
+
}), /*#__PURE__*/_jsx(ActionCardBox, {
|
|
97
|
+
tipFlag: tipFlag || currentAction,
|
|
98
|
+
children: currentAction
|
|
99
|
+
}), /*#__PURE__*/_jsx(ProductIntroCarousel, {
|
|
100
|
+
tips: tips
|
|
101
|
+
}, locale), /*#__PURE__*/_jsx(ErrorDisplay, {
|
|
102
|
+
error: error,
|
|
103
|
+
onRetry: onRetry
|
|
104
|
+
})]
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
LoadingDisplayLayout.propTypes = {
|
|
108
|
+
// 基础信息
|
|
109
|
+
title: PropTypes.string.isRequired,
|
|
110
|
+
// 进度条数据
|
|
111
|
+
progress: PropTypes.number.isRequired,
|
|
112
|
+
elapsedTime: PropTypes.string.isRequired,
|
|
113
|
+
estimatedTime: PropTypes.string.isRequired,
|
|
114
|
+
// 动作描述数据
|
|
115
|
+
currentAction: PropTypes.string.isRequired,
|
|
116
|
+
// 动画控制
|
|
117
|
+
tipFlag: PropTypes.any,
|
|
118
|
+
// 可选项
|
|
119
|
+
error: PropTypes.string,
|
|
120
|
+
onRetry: PropTypes.func
|
|
121
|
+
};
|
|
122
|
+
export default LoadingDisplayLayout;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import LaunchResultMessage from '@blocklet/launcher-layout/lib/launch-result-message';
|
|
2
|
+
import Button from '@blocklet/launcher-ux/lib/button';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import React, { useState } from 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 重试错误消息组件
|
|
8
|
+
* 提供统一的错误展示和重试功能
|
|
9
|
+
*/
|
|
10
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
+
export default function RetryErrorMessage({
|
|
12
|
+
title,
|
|
13
|
+
onRetry,
|
|
14
|
+
retryText
|
|
15
|
+
}) {
|
|
16
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
17
|
+
const handleRetry = async e => {
|
|
18
|
+
e.preventDefault();
|
|
19
|
+
e.stopPropagation();
|
|
20
|
+
setIsLoading(true);
|
|
21
|
+
const result = onRetry();
|
|
22
|
+
if (result instanceof Promise) {
|
|
23
|
+
await result;
|
|
24
|
+
}
|
|
25
|
+
setIsLoading(false);
|
|
26
|
+
};
|
|
27
|
+
return /*#__PURE__*/_jsx(LaunchResultMessage, {
|
|
28
|
+
variant: "error",
|
|
29
|
+
title: title,
|
|
30
|
+
footer: /*#__PURE__*/_jsx(Button, {
|
|
31
|
+
size: "small",
|
|
32
|
+
style: {
|
|
33
|
+
marginTop: '-16px'
|
|
34
|
+
},
|
|
35
|
+
onClick: handleRetry,
|
|
36
|
+
loading: isLoading,
|
|
37
|
+
children: retryText
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
RetryErrorMessage.propTypes = {
|
|
42
|
+
title: PropTypes.string.isRequired,
|
|
43
|
+
onRetry: PropTypes.func.isRequired,
|
|
44
|
+
retryText: PropTypes.string.isRequired
|
|
45
|
+
};
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { useSetState } from 'ahooks';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import React, { useEffect, useMemo } from 'react';
|
|
4
|
+
import { useNavigate } from 'react-router-dom';
|
|
5
|
+
import { LAUNCH_STATUS } from '@blocklet/launcher-util/es/constant';
|
|
6
|
+
import { useLocaleContext } from '../../contexts/locale';
|
|
7
|
+
import useRequest from '../../contexts/request';
|
|
8
|
+
import useSerialPolling from '../../hooks/use-serial-polling';
|
|
9
|
+
import { checkBlockletAccessible, getBlockletUrls, sortUrls, waitingForRaceAccessible } from '../../util';
|
|
10
|
+
import BaseServerlessLayout from './shared/base-serverless-layout';
|
|
11
|
+
import { AppSuccessDisplay, calculateEstimatedTime, useDisplayProgress, useElapsedTime, useFormatTime } from './shared/common-components';
|
|
12
|
+
import LoadingDisplayLayout from './shared/loading-display-layout';
|
|
13
|
+
import RetryErrorMessage from './shared/retry-error-message';
|
|
14
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
15
|
+
const CHECK_INTERVAL = 3000;
|
|
16
|
+
export default function LaunchServerless({
|
|
17
|
+
sessionId
|
|
18
|
+
}) {
|
|
19
|
+
const {
|
|
20
|
+
t
|
|
21
|
+
} = useLocaleContext();
|
|
22
|
+
const {
|
|
23
|
+
api
|
|
24
|
+
} = useRequest();
|
|
25
|
+
const navigate = useNavigate();
|
|
26
|
+
const [state, setState] = useSetState({
|
|
27
|
+
starting: true,
|
|
28
|
+
started: false,
|
|
29
|
+
checkingBlockletStatus: false,
|
|
30
|
+
checkingAccessible: false,
|
|
31
|
+
launchSession: null,
|
|
32
|
+
blockletInfo: null,
|
|
33
|
+
accessibleUrl: null,
|
|
34
|
+
urls: [],
|
|
35
|
+
error: '',
|
|
36
|
+
startTime: 0,
|
|
37
|
+
hasTryStart: false,
|
|
38
|
+
retryRequestCount: 0
|
|
39
|
+
});
|
|
40
|
+
const formatTime = useFormatTime();
|
|
41
|
+
const time = useElapsedTime(0);
|
|
42
|
+
const actions = useMemo(() => {
|
|
43
|
+
// 基础步骤时间
|
|
44
|
+
const steps = [{
|
|
45
|
+
message: t('startApp.waiting.starting'),
|
|
46
|
+
time: 5
|
|
47
|
+
}, {
|
|
48
|
+
message: t('startApp.waiting.parsing'),
|
|
49
|
+
time: 1
|
|
50
|
+
}, {
|
|
51
|
+
message: t('startApp.waiting.initializing'),
|
|
52
|
+
time: 3
|
|
53
|
+
}, {
|
|
54
|
+
message: t('startApp.waiting.initializingOwner'),
|
|
55
|
+
time: 2
|
|
56
|
+
}, {
|
|
57
|
+
message: t('startApp.waiting.creatingSecurityRules'),
|
|
58
|
+
time: 2
|
|
59
|
+
}, {
|
|
60
|
+
message: t('startApp.waiting.assigningDomain'),
|
|
61
|
+
time: 2
|
|
62
|
+
}, {
|
|
63
|
+
message: t('startApp.waiting.waitingForDomain'),
|
|
64
|
+
time: 10
|
|
65
|
+
}];
|
|
66
|
+
|
|
67
|
+
// 计算总时间
|
|
68
|
+
const totalTime = steps.reduce((acc, step) => acc + step.time, 0);
|
|
69
|
+
|
|
70
|
+
// 计算每个步骤的进度区间
|
|
71
|
+
let currentProgress = 0;
|
|
72
|
+
return steps.map(step => {
|
|
73
|
+
const progressPercent = step.time / totalTime * 100;
|
|
74
|
+
const range = [Math.round(currentProgress), Math.round(currentProgress + progressPercent)];
|
|
75
|
+
currentProgress += progressPercent;
|
|
76
|
+
return {
|
|
77
|
+
...step,
|
|
78
|
+
range
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}, [t]);
|
|
82
|
+
const estimatedTime = useMemo(() => {
|
|
83
|
+
const components = window.blockletMeta?.components || [];
|
|
84
|
+
const additionalTime = calculateEstimatedTime(components);
|
|
85
|
+
return actions.reduce((acc, action) => acc + action.time, 0) + additionalTime;
|
|
86
|
+
}, [actions]);
|
|
87
|
+
const displayProgress = useDisplayProgress(0, state.started ? 1 : estimatedTime);
|
|
88
|
+
|
|
89
|
+
// 根据当前进度获取对应的 action
|
|
90
|
+
const getCurrentAction = progress => {
|
|
91
|
+
const action = actions.find(({
|
|
92
|
+
range: [start, end]
|
|
93
|
+
}) => progress >= start && progress < end);
|
|
94
|
+
return action?.message || actions[actions.length - 1].message;
|
|
95
|
+
};
|
|
96
|
+
const checkOneAccessibleUrl = async urls => {
|
|
97
|
+
try {
|
|
98
|
+
const [accessibleUrl] = await waitingForRaceAccessible(urls);
|
|
99
|
+
return accessibleUrl;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error(error);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const getUrls = async blocklet => {
|
|
106
|
+
let {
|
|
107
|
+
urls
|
|
108
|
+
} = state;
|
|
109
|
+
if (urls.length === 0) {
|
|
110
|
+
urls = getBlockletUrls({
|
|
111
|
+
blocklet
|
|
112
|
+
});
|
|
113
|
+
const sortedUrls = await sortUrls(urls);
|
|
114
|
+
urls = sortedUrls;
|
|
115
|
+
if (urls.length > 0) {
|
|
116
|
+
setState({
|
|
117
|
+
urls
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return urls;
|
|
122
|
+
};
|
|
123
|
+
const checkLaunchSession = async () => {
|
|
124
|
+
try {
|
|
125
|
+
const {
|
|
126
|
+
accessibleUrl
|
|
127
|
+
} = state;
|
|
128
|
+
if (!accessibleUrl) {
|
|
129
|
+
console.warn('the accessible url is not ready, retry getting accessible url');
|
|
130
|
+
setState({
|
|
131
|
+
checkingAccessible: true,
|
|
132
|
+
checkingBlockletStatus: false
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const blockletInfo = await checkBlockletAccessible(accessibleUrl);
|
|
137
|
+
if (!blockletInfo) {
|
|
138
|
+
console.warn('the blocklet is not ready or accessible url is not correct, retry getting accessible url');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
setState({
|
|
142
|
+
blockletInfo
|
|
143
|
+
});
|
|
144
|
+
if (blockletInfo.status === 'running') {
|
|
145
|
+
setState({
|
|
146
|
+
blockletInfo,
|
|
147
|
+
starting: false,
|
|
148
|
+
started: true,
|
|
149
|
+
checkingBlockletStatus: false,
|
|
150
|
+
checkingAccessible: false
|
|
151
|
+
});
|
|
152
|
+
} else if (blockletInfo.status === 'starting') {
|
|
153
|
+
if (!state.hasTryStart) {
|
|
154
|
+
setState(prev => {
|
|
155
|
+
if (Date.now() - prev.startTime > estimatedTime * 1000) {
|
|
156
|
+
api.post(`/launches/${sessionId}/start`);
|
|
157
|
+
return {
|
|
158
|
+
...prev,
|
|
159
|
+
hasTryStart: true
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return prev;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
} else if (blockletInfo.status === 'stopped') {
|
|
166
|
+
api.post(`/launches/${sessionId}/start`);
|
|
167
|
+
} else {
|
|
168
|
+
console.warn('the blocklet is not installed!', blockletInfo.status, blockletInfo);
|
|
169
|
+
setState({
|
|
170
|
+
error: t('startApp.startFailed'),
|
|
171
|
+
starting: false,
|
|
172
|
+
checkingBlockletStatus: false
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
setState({
|
|
176
|
+
retryRequestCount: 0
|
|
177
|
+
});
|
|
178
|
+
} catch (error) {
|
|
179
|
+
setState(prev => {
|
|
180
|
+
if (prev.retryRequestCount < 5) {
|
|
181
|
+
console.warn('check launch session occurred error, retry', prev.retryRequestCount, error);
|
|
182
|
+
return {
|
|
183
|
+
...prev,
|
|
184
|
+
retryRequestCount: prev.retryRequestCount + 1
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
...prev,
|
|
189
|
+
error: error.message,
|
|
190
|
+
starting: false,
|
|
191
|
+
retryRequestCount: 0
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
console.error(error);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// 串行检查,确保前一个请求完成后等待指定时间再发起下一个
|
|
199
|
+
useSerialPolling({
|
|
200
|
+
isEnabled: state.checkingBlockletStatus,
|
|
201
|
+
interval: CHECK_INTERVAL,
|
|
202
|
+
onPoll: checkLaunchSession
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// 检查可用性 url
|
|
206
|
+
const checkAccessibleUrls = async () => {
|
|
207
|
+
try {
|
|
208
|
+
const urls = await getUrls(state.launchSession.blocklet);
|
|
209
|
+
const accessibleUrl = await checkOneAccessibleUrl(urls);
|
|
210
|
+
setState(prev => {
|
|
211
|
+
const obj = accessibleUrl ? {
|
|
212
|
+
...prev,
|
|
213
|
+
checkingAccessible: false,
|
|
214
|
+
checkingBlockletStatus: true,
|
|
215
|
+
accessibleUrl
|
|
216
|
+
} : prev;
|
|
217
|
+
if (Date.now() - prev.startTime > estimatedTime * 1000) {
|
|
218
|
+
api.post(`/launches/${sessionId}/start`);
|
|
219
|
+
return {
|
|
220
|
+
...obj,
|
|
221
|
+
hasTryStart: true
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return obj;
|
|
225
|
+
});
|
|
226
|
+
} catch (error) {
|
|
227
|
+
// ignore
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
useSerialPolling({
|
|
231
|
+
isEnabled: state.checkingAccessible,
|
|
232
|
+
interval: 10000,
|
|
233
|
+
onPoll: checkAccessibleUrls
|
|
234
|
+
});
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
const fetch = async (tryCount = 0) => {
|
|
237
|
+
setState({
|
|
238
|
+
startTime: Date.now()
|
|
239
|
+
});
|
|
240
|
+
try {
|
|
241
|
+
const {
|
|
242
|
+
data: {
|
|
243
|
+
launch: launchSession
|
|
244
|
+
}
|
|
245
|
+
} = await api.get(`/launches/${sessionId}?health=1`);
|
|
246
|
+
const urls = await getUrls(launchSession.blocklet);
|
|
247
|
+
launchSession.urls = urls;
|
|
248
|
+
[launchSession.appUrl] = urls;
|
|
249
|
+
setState({
|
|
250
|
+
launchSession,
|
|
251
|
+
urls
|
|
252
|
+
});
|
|
253
|
+
if (launchSession.status >= LAUNCH_STATUS.consuming) {
|
|
254
|
+
const notStarting = !['starting', 'running'].includes(launchSession.blockletStatus) && (!launchSession.metadata?.starting || launchSession.metadata.starting !== true);
|
|
255
|
+
if (notStarting) {
|
|
256
|
+
await api.post(`/launches/${sessionId}/start`).catch(error => {
|
|
257
|
+
console.error(error);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
setState({
|
|
261
|
+
starting: true,
|
|
262
|
+
checkingAccessible: true
|
|
263
|
+
});
|
|
264
|
+
} else if (launchSession.status === LAUNCH_STATUS.allocated) {
|
|
265
|
+
navigate(`/install/${sessionId}${window.location.search || ''}`);
|
|
266
|
+
} else {
|
|
267
|
+
setState(prev => {
|
|
268
|
+
return {
|
|
269
|
+
...prev,
|
|
270
|
+
starting: false,
|
|
271
|
+
started: false,
|
|
272
|
+
checkingBlockletStatus: false,
|
|
273
|
+
checkingAccessible: false,
|
|
274
|
+
error: t('startApp.installFailed')
|
|
275
|
+
};
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (tryCount > 0) {
|
|
280
|
+
setTimeout(() => {
|
|
281
|
+
fetch(tryCount - 1);
|
|
282
|
+
}, 1000);
|
|
283
|
+
} else {
|
|
284
|
+
setState({
|
|
285
|
+
error: error.message,
|
|
286
|
+
starting: false,
|
|
287
|
+
checkingBlockletStatus: false,
|
|
288
|
+
checkingAccessible: false
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
console.error('get launch session error', error);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
if (window.blocklet.DEVELOPER_WORKFLOW_UI) {
|
|
295
|
+
setTimeout(() => {
|
|
296
|
+
fetch(0);
|
|
297
|
+
}, 10000);
|
|
298
|
+
} else {
|
|
299
|
+
fetch(0);
|
|
300
|
+
}
|
|
301
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
302
|
+
}, []);
|
|
303
|
+
const handleRetry = async () => {
|
|
304
|
+
if (['starting', 'stopped'].includes(state.blockletInfo.status)) {
|
|
305
|
+
await api.post(`/launches/${sessionId}/start`);
|
|
306
|
+
} else if (!state.blockletInfo && state.launchSession.status < LAUNCH_STATUS.allocated) {
|
|
307
|
+
await api.post('/serverless/allocate', {
|
|
308
|
+
launchId: sessionId
|
|
309
|
+
}).catch(error => {
|
|
310
|
+
console.error(error);
|
|
311
|
+
});
|
|
312
|
+
navigate(`/launch/${sessionId}${window.location.search || ''}`);
|
|
313
|
+
} else if (state.launchSession.status < LAUNCH_STATUS.consuming) {
|
|
314
|
+
await api.post('/serverless/install', {
|
|
315
|
+
launchId: sessionId
|
|
316
|
+
}).catch(error => {
|
|
317
|
+
console.error(error);
|
|
318
|
+
});
|
|
319
|
+
navigate(`/install/${sessionId}${window.location.search || ''}`);
|
|
320
|
+
} else {
|
|
321
|
+
await api.post(`/launches/${sessionId}/start`);
|
|
322
|
+
window.location.reload();
|
|
323
|
+
}
|
|
324
|
+
};
|
|
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
|
+
}
|
|
331
|
+
return /*#__PURE__*/_jsxs(BaseServerlessLayout, {
|
|
332
|
+
title: t('startApp.pageTitle'),
|
|
333
|
+
children: [showLoading && /*#__PURE__*/_jsx(LoadingDisplayLayout, {
|
|
334
|
+
title: t('startApp.pageTitle'),
|
|
335
|
+
progress: state.started && displayProgress >= 99 ? 100 : displayProgress,
|
|
336
|
+
elapsedTime: formatTime(time),
|
|
337
|
+
estimatedTime: formatTime(estimatedTime),
|
|
338
|
+
currentAction: getCurrentAction(displayProgress),
|
|
339
|
+
error: null,
|
|
340
|
+
onRetry: null
|
|
341
|
+
}), showSuccess && /*#__PURE__*/_jsx(AppSuccessDisplay, {
|
|
342
|
+
accessibleUrl: state.accessibleUrl,
|
|
343
|
+
sessionId: sessionId,
|
|
344
|
+
blockletInfo: state.blockletInfo,
|
|
345
|
+
launchSession: state.launchSession,
|
|
346
|
+
urls: state.urls
|
|
347
|
+
}), showError && /*#__PURE__*/_jsx(RetryErrorMessage, {
|
|
348
|
+
title: t('startApp.startFailed'),
|
|
349
|
+
onRetry: handleRetry,
|
|
350
|
+
retryText: t('common.retry')
|
|
351
|
+
})]
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
LaunchServerless.propTypes = {
|
|
355
|
+
sessionId: PropTypes.string.isRequired
|
|
356
|
+
};
|
package/es/contexts/request.js
CHANGED
|
@@ -2,7 +2,7 @@ import { create as createRequestInstance } from '@blocklet/launcher-util/es/api'
|
|
|
2
2
|
import get from 'lodash.get';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
import { createContext, useContext } from 'react';
|
|
5
|
-
import
|
|
5
|
+
import { joinURL } from 'ufo';
|
|
6
6
|
import { useLocaleContext } from './locale';
|
|
7
7
|
import { useSessionContext } from './session';
|
|
8
8
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
@@ -22,7 +22,7 @@ export function RequestProvider({
|
|
|
22
22
|
} = useLocaleContext();
|
|
23
23
|
const configRequest = r => {
|
|
24
24
|
r.interceptors.request.use(config => {
|
|
25
|
-
config.url =
|
|
25
|
+
config.url = joinURL('/api', config.url);
|
|
26
26
|
if (!config.params) {
|
|
27
27
|
config.params = {};
|
|
28
28
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A hook for serial polling that ensures each request is completed before starting the next one.
|
|
5
|
+
* This prevents request overlapping and maintains a consistent interval between completed requests.
|
|
6
|
+
*
|
|
7
|
+
* @param {Object} options
|
|
8
|
+
* @param {boolean} options.isEnabled - Whether to enable the polling
|
|
9
|
+
* @param {number} options.interval - Time to wait after a request completes before starting the next one (in ms)
|
|
10
|
+
* @param {Function} options.onPoll - Async function to be called for each poll
|
|
11
|
+
*/
|
|
12
|
+
export default function useSerialPolling({
|
|
13
|
+
isEnabled,
|
|
14
|
+
interval = 3000,
|
|
15
|
+
onPoll
|
|
16
|
+
}) {
|
|
17
|
+
const onPollRef = useRef(onPoll);
|
|
18
|
+
onPollRef.current = onPoll;
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
let timeoutId;
|
|
21
|
+
let isActive = true;
|
|
22
|
+
const scheduleNextPoll = async () => {
|
|
23
|
+
if (!isActive || !isEnabled) return;
|
|
24
|
+
try {
|
|
25
|
+
await onPollRef.current();
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Serial polling error:', error);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Only schedule next poll if still active and enabled
|
|
31
|
+
if (isActive && isEnabled) {
|
|
32
|
+
timeoutId = setTimeout(scheduleNextPoll, interval);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
if (isEnabled) {
|
|
36
|
+
scheduleNextPoll();
|
|
37
|
+
}
|
|
38
|
+
return () => {
|
|
39
|
+
isActive = false;
|
|
40
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
41
|
+
};
|
|
42
|
+
}, [isEnabled, interval]);
|
|
43
|
+
}
|
package/es/install.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useParams } from 'react-router-dom';
|
|
3
|
+
import InstallingServerless from './components/launch-serverless/install';
|
|
4
|
+
import { useSessionContext } from './contexts/session';
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
export default function Launch() {
|
|
7
|
+
const {
|
|
8
|
+
sessionId
|
|
9
|
+
} = useParams();
|
|
10
|
+
const {
|
|
11
|
+
session
|
|
12
|
+
} = useSessionContext();
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!session.user) {
|
|
15
|
+
return session.login(() => {
|
|
16
|
+
session.refresh();
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return () => {};
|
|
20
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
21
|
+
}, [session.user]);
|
|
22
|
+
if (!session.user) {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
return /*#__PURE__*/_jsx(InstallingServerless, {
|
|
26
|
+
sessionId: sessionId
|
|
27
|
+
});
|
|
28
|
+
}
|
package/es/launch.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import { useParams, useSearchParams } from 'react-router-dom';
|
|
3
|
-
import LaunchServerless from './components/launch-serverless';
|
|
3
|
+
import LaunchServerless from './components/launch-serverless/allocate';
|
|
4
4
|
import LaunchDedicated from './components/launch-dedicated';
|
|
5
5
|
import { useSessionContext } from './contexts/session';
|
|
6
6
|
import { jsx as _jsx } from "react/jsx-runtime";
|