Skip to content

Commit be80aee

Browse files
authored
SSE bug fixes & improvements (codesandbox#1218)
* SSE bug fixes & improvements. * Notifications: increased show time and doubled it for errors. * SSE: handle initial socket connection timeout.
1 parent e377e2b commit be80aee

File tree

6 files changed

+103
-36
lines changed

6 files changed

+103
-36
lines changed

packages/app/src/app/components/Preview/elements.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ export const Loading = styled.div`
3030
bottom: 0;
3131
right: 0;
3232
left: 0;
33-
width: 100%;
34-
height: 100%;
3533
background-color: rgba(0, 0, 0, 0.75);
3634
padding: 2rem;
3735
display: flex;
3836
align-items: center;
3937
justify-content: center;
4038
4139
font-size: 2rem;
42-
font-weight: 800;
40+
font-weight: 300;
4341
color: white;
42+
line-height: 1.3;
43+
text-align: center;
4444
`;

packages/app/src/app/components/Preview/index.js

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ type State = {
4848
urlInAddressBar: string,
4949
url: ?string,
5050
overlayMessage: ?string,
51+
hibernated: boolean,
52+
sseError: boolean,
5153
};
5254

5355
const getSSEUrl = (id?: string) =>
@@ -131,6 +133,13 @@ async function retrieveSSEToken() {
131133
return null;
132134
}
133135

136+
function sseTerminalMessage(msg) {
137+
dispatch({
138+
type: 'terminal:message',
139+
data: `> Sandbox Container: ${msg}\n\r`,
140+
});
141+
}
142+
134143
class BasePreview extends React.Component<Props, State> {
135144
serverPreview: boolean;
136145
lastSent: {
@@ -157,6 +166,8 @@ class BasePreview extends React.Component<Props, State> {
157166
: frameUrl(props.sandbox.id, props.initialPath || ''),
158167
url: null,
159168
overlayMessage: null,
169+
hibernated: false,
170+
sseError: false,
160171
};
161172

162173
// we need a value that doesn't change when receiving `initialPath`
@@ -192,6 +203,14 @@ class BasePreview extends React.Component<Props, State> {
192203

193204
setupSSESockets = async () => {
194205
const hasInitialized = !!this.$socket;
206+
let connectTimeout = null;
207+
208+
function onTimeout(setServerStatus) {
209+
connectTimeout = null;
210+
if (setServerStatus) {
211+
setServerStatus('disconnected');
212+
}
213+
}
195214

196215
if (hasInitialized) {
197216
this.setState({
@@ -201,6 +220,10 @@ class BasePreview extends React.Component<Props, State> {
201220
this.$socket.close();
202221
setTimeout(() => {
203222
if (this.$socket) {
223+
connectTimeout = setTimeout(
224+
() => onTimeout(this.props.setServerStatus),
225+
3000
226+
);
204227
this.$socket.open();
205228
}
206229
}, 0);
@@ -216,12 +239,23 @@ class BasePreview extends React.Component<Props, State> {
216239

217240
socket.on('disconnect', () => {
218241
if (this.props.setServerStatus) {
219-
this.props.setServerStatus('disconnected');
242+
let status = 'disconnected';
243+
if (this.state.hibernated) {
244+
status = 'hibernated';
245+
} else if (this.state.sseError) {
246+
status = 'error';
247+
}
248+
this.props.setServerStatus(status);
220249
dispatch({ type: 'codesandbox:sse:disconnect' });
221250
}
222251
});
223252

224253
socket.on('connect', async () => {
254+
if (connectTimeout) {
255+
clearTimeout(connectTimeout);
256+
connectTimeout = null;
257+
}
258+
225259
if (this.props.setServerStatus) {
226260
this.props.setServerStatus('connected');
227261
}
@@ -231,10 +265,7 @@ class BasePreview extends React.Component<Props, State> {
231265

232266
socket.emit('sandbox', { id, token });
233267

234-
dispatch({
235-
type: 'terminal:message',
236-
data: `> CodeSandbox SSE: connected! Starting sandbox ${id}...\n\r`,
237-
});
268+
sseTerminalMessage(`connected, starting sandbox ${id}...`);
238269

239270
socket.emit('sandbox:start');
240271
});
@@ -257,12 +288,7 @@ class BasePreview extends React.Component<Props, State> {
257288
});
258289

259290
socket.on('sandbox:start', () => {
260-
const { id } = this.props.sandbox;
261-
262-
dispatch({
263-
type: 'terminal:message',
264-
data: `> CodeSandbox SSE: sandbox ${id} started\n\r`,
265-
});
291+
sseTerminalMessage(`sandbox ${this.props.sandbox.id} started.`);
266292

267293
if (!this.state.frameInitialized && this.props.onInitialized) {
268294
this.disposeInitializer = this.props.onInitialized(this);
@@ -275,30 +301,60 @@ class BasePreview extends React.Component<Props, State> {
275301
});
276302

277303
socket.on('sandbox:hibernate', () => {
278-
this.setState({
279-
frameInitialized: false,
280-
overlayMessage:
281-
'The sandbox is hibernating, refresh to start the sandbox',
282-
});
283-
284-
this.$socket.close();
304+
sseTerminalMessage(`sandbox ${this.props.sandbox.id} hibernated.`);
305+
306+
this.setState(
307+
{
308+
frameInitialized: false,
309+
overlayMessage:
310+
'The sandbox was hibernated because of inactivity. Refresh the page to restart it.',
311+
hibernated: true,
312+
},
313+
() => this.$socket.close()
314+
);
285315
});
286316

287317
socket.on('sandbox:stop', () => {
318+
sseTerminalMessage(`sandbox ${this.props.sandbox.id} restarting...`);
319+
288320
this.setState({
289321
frameInitialized: false,
290322
overlayMessage: 'Restarting the sandbox...',
291323
});
292324
});
293325

294-
socket.on('sandbox:log', ({ chan, data }) => {
326+
socket.on('sandbox:log', ({ data }) => {
295327
dispatch({
296328
type: 'terminal:message',
297-
chan,
298329
data,
299330
});
300331
});
301332

333+
socket.on('sandbox:error', ({ message, unrecoverable }) => {
334+
sseTerminalMessage(
335+
`sandbox ${this.props.sandbox.id} ${
336+
unrecoverable ? 'unrecoverable ' : ''
337+
}error "${message}"`
338+
);
339+
if (unrecoverable) {
340+
this.setState(
341+
{
342+
frameInitialized: false,
343+
overlayMessage:
344+
'An unrecoverable sandbox error occurred. :-( Try refreshing the page.',
345+
sseError: true,
346+
},
347+
() => this.$socket.close()
348+
);
349+
} else {
350+
window.showNotification(`Sandbox Container: ${message}`, 'error');
351+
}
352+
});
353+
354+
connectTimeout = setTimeout(
355+
() => onTimeout(this.props.setServerStatus),
356+
3000
357+
);
302358
socket.open();
303359
}
304360
};

packages/app/src/app/pages/Sandbox/Editor/Workspace/SSEDownNotice/index.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ const ConnectionNotice = ({ store }) => {
1010
return null;
1111
}
1212

13-
if (
14-
store.server.status === 'connected' ||
15-
store.server.status === 'initializing'
16-
) {
13+
if (store.server.status !== 'disconnected') {
1714
return null;
1815
}
1916

packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Server/Status.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import React from 'react';
33
import styled from 'styled-components';
44

55
type Props = {
6-
status: 'connected' | 'disconnected',
6+
status:
7+
| 'connected'
8+
| 'disconnected'
9+
| 'initializing'
10+
| 'hibernated'
11+
| 'error',
712
};
813

914
const StatusCircle = styled.div`
@@ -24,15 +29,19 @@ const Container = styled.div`
2429
`;
2530

2631
const STATUS_MESSAGES = {
27-
disconnected: 'Reconnecting to the server...',
28-
connected: 'Connected to the server!',
29-
initializing: 'Initializing connection to the server...',
32+
disconnected: 'Reconnecting to sandbox...',
33+
connected: 'Connected to sandbox',
34+
initializing: 'Initializing connection to sandbox...',
35+
hibernated: 'Sandbox hibernated',
36+
error: 'Unrecoverable sandbox error',
3037
};
3138

3239
const STATUS_COLOR = {
33-
disconnected: '#fd2439fa',
40+
disconnected: '#FD2439',
3441
connected: '#4CFF00',
3542
initializing: '#FFD399',
43+
hibernated: '#FF662E',
44+
error: '#FD2439',
3645
};
3746

3847
export default ({ status }: Props) => (

packages/app/src/app/store/factories.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,22 @@ export function setCurrentModule(id) {
7777
export function addNotification(
7878
title,
7979
notificationType,
80-
timeAlive = 2,
80+
timeAlive,
8181
buttons = []
8282
) {
83-
// eslint-disable-next-line
83+
// eslint-disable-next-line no-shadow
8484
return function addNotification({ state, resolve }) {
8585
const now = Date.now();
86+
const notificationTypeValue = resolve.value(notificationType);
87+
const timeAliveDefault = notificationTypeValue === 'error' ? 6 : 3;
8688

8789
state.push('notifications', {
8890
id: now,
8991
title: resolve.value(title),
90-
notificationType: resolve.value(notificationType),
92+
notificationType: notificationTypeValue,
9193
buttons: resolve.value(buttons),
92-
endTime: now + resolve.value(timeAlive) * 1000,
94+
endTime:
95+
now + (timeAlive ? resolve.value(timeAlive) : timeAliveDefault) * 1000,
9396
});
9497
};
9598
}

packages/app/src/app/store/modules/server/model.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ export default {
55
'connected',
66
'disconnected',
77
'initializing',
8+
'hibernated',
9+
'error',
810
]),
911
};

0 commit comments

Comments
 (0)