Skip to content

Commit 1851425

Browse files
committed
[supervisor frontend] debug mode
1 parent 9a97b9c commit 1851425

File tree

4 files changed

+339
-295
lines changed

4 files changed

+339
-295
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { DisposableCollection } from "@gitpod/gitpod-protocol/lib/util/disposable";
8+
import * as IDEFrontendService from "./ide/ide-frontend-service-impl";
9+
import * as IDEWorker from "./ide/ide-worker";
10+
import * as IDEWebSocket from "./ide/ide-web-socket";
11+
import { SupervisorServiceClient } from "./ide/supervisor-service-client";
12+
13+
Object.assign(window, { gitpod: {} });
14+
IDEWorker.install();
15+
IDEWebSocket.install();
16+
IDEWebSocket.connectWorkspace();
17+
const ideService = IDEFrontendService.create();
18+
const loadingIDE = new Promise((resolve) => window.addEventListener("DOMContentLoaded", resolve, { once: true }));
19+
const toStop = new DisposableCollection();
20+
21+
(async () => {
22+
const supervisorServiceClient = SupervisorServiceClient.get(Promise.resolve());
23+
const [ideStatus] = await Promise.all([
24+
supervisorServiceClient.ideReady,
25+
supervisorServiceClient.contentReady,
26+
loadingIDE,
27+
]);
28+
// TODO(desktop support)
29+
// const isDesktopIde = ideStatus && ideStatus.desktop && ideStatus.desktop.link;
30+
// if (!isDesktopIde) {
31+
toStop.push(ideService.start());
32+
// }
33+
})();

components/supervisor/frontend/src/ide/supervisor-service-client.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,18 @@ import { GitpodHostUrl } from "@gitpod/gitpod-protocol/lib/util/gitpod-host-url"
1414

1515
export class SupervisorServiceClient {
1616
private static _instance: SupervisorServiceClient | undefined;
17-
static get(gitpodServiceClient: GitpodServiceClient): SupervisorServiceClient {
17+
static get(gitpodAuth: Promise<void>): SupervisorServiceClient {
1818
if (!SupervisorServiceClient._instance) {
19-
SupervisorServiceClient._instance = new SupervisorServiceClient(gitpodServiceClient);
19+
SupervisorServiceClient._instance = new SupervisorServiceClient(gitpodAuth);
2020
}
2121
return SupervisorServiceClient._instance;
2222
}
2323

2424
readonly supervisorReady = this.checkReady("supervisor");
2525
readonly ideReady = this.supervisorReady.then(() => this.checkReady("ide"));
26-
readonly contentReady = Promise.all([this.supervisorReady, this.gitpodServiceClient.auth]).then(() =>
27-
this.checkReady("content"),
28-
);
26+
readonly contentReady = Promise.all([this.supervisorReady, this.gitpodAuth]).then(() => this.checkReady("content"));
2927

30-
private constructor(private readonly gitpodServiceClient: GitpodServiceClient) {}
28+
private constructor(private readonly gitpodAuth: Promise<void>) {}
3129

3230
private async checkReady(kind: "content" | "ide" | "supervisor", delay?: boolean): Promise<any> {
3331
if (delay) {

components/supervisor/frontend/src/index.ts

Lines changed: 5 additions & 289 deletions
Original file line numberDiff line numberDiff line change
@@ -8,294 +8,10 @@
88
* <script type="text/javascript" src="/_supervisor/frontend/main.js" charset="utf-8"></script> should be inserted to index.html as first body script,
99
* all other IDE scripts should go afterwards, head element should not have scripts
1010
*/
11-
import { IDEMetricsServiceClient, MetricsName } from "./ide/ide-metrics-service-client";
12-
IDEMetricsServiceClient.addCounter(MetricsName.SupervisorFrontendClientTotal).catch(() => {});
1311

14-
//#region supervisor frontend error capture
15-
function isElement(obj: any): obj is Element {
16-
return typeof obj.getAttribute === "function";
12+
import { workspaceUrl } from "./shared/urls";
13+
if (workspaceUrl.debugWorkspace) {
14+
require("./debug");
15+
} else {
16+
require("./regular");
1717
}
18-
19-
window.addEventListener("error", (event) => {
20-
const labels: Record<string, string> = {};
21-
let resourceSource: string | null | undefined;
22-
if (isElement(event.target)) {
23-
// We take a look at what is the resource that was attempted to load;
24-
resourceSource = event.target.getAttribute("src") || event.target.getAttribute("href");
25-
// If the event has a `target`, it means that it wasn't a script error
26-
if (resourceSource) {
27-
if (resourceSource.match(new RegExp(/\/build\/ide\/code:.+\/__files__\//g))) {
28-
// TODO(ak) reconsider how to hide knowledge of VS Code from supervisor frontend, i.e instrument amd loader instead
29-
labels["resource"] = "vscode-web-workbench";
30-
}
31-
labels["error"] = "LoadError";
32-
}
33-
}
34-
if (event.error) {
35-
IDEMetricsServiceClient.reportError(event.error).catch(() => {});
36-
} else if (labels["error"] == "LoadError") {
37-
let error = new Error("LoadError");
38-
IDEMetricsServiceClient.reportError(error, {
39-
resource: labels["resource"],
40-
url: resourceSource ?? "",
41-
}).catch(() => {});
42-
}
43-
IDEMetricsServiceClient.addCounter(MetricsName.SupervisorFrontendErrorTotal, labels).catch(() => {});
44-
});
45-
//#endregion
46-
47-
require("../src/shared/index.css");
48-
49-
import { createGitpodService, WorkspaceInstancePhase } from "@gitpod/gitpod-protocol";
50-
import { DisposableCollection } from "@gitpod/gitpod-protocol/lib/util/disposable";
51-
import * as GitpodServiceClient from "./ide/gitpod-service-client";
52-
import * as heartBeat from "./ide/heart-beat";
53-
import * as IDEFrontendService from "./ide/ide-frontend-service-impl";
54-
import * as IDEWorker from "./ide/ide-worker";
55-
import * as IDEWebSocket from "./ide/ide-web-socket";
56-
import { SupervisorServiceClient } from "./ide/supervisor-service-client";
57-
import * as LoadingFrame from "./shared/loading-frame";
58-
import { serverUrl, startUrl } from "./shared/urls";
59-
60-
window.gitpod = {
61-
service: createGitpodService(serverUrl.toString()),
62-
};
63-
IDEWorker.install();
64-
IDEWebSocket.install();
65-
const ideService = IDEFrontendService.create();
66-
const pendingGitpodServiceClient = GitpodServiceClient.create();
67-
const loadingIDE = new Promise((resolve) => window.addEventListener("DOMContentLoaded", resolve, { once: true }));
68-
const toStop = new DisposableCollection();
69-
70-
(async () => {
71-
const gitpodServiceClient = await pendingGitpodServiceClient;
72-
IDEMetricsServiceClient.loadWorkspaceInfo(gitpodServiceClient);
73-
74-
document.title = gitpodServiceClient.info.workspace.description;
75-
76-
if (gitpodServiceClient.info.workspace.type !== "regular") {
77-
return;
78-
}
79-
80-
//#region ide lifecycle
81-
function isWorkspaceInstancePhase(phase: WorkspaceInstancePhase): boolean {
82-
return gitpodServiceClient.info.latestInstance?.status.phase === phase;
83-
}
84-
if (!isWorkspaceInstancePhase("running")) {
85-
await new Promise<void>((resolve) => {
86-
const listener = gitpodServiceClient.onDidChangeInfo(() => {
87-
if (isWorkspaceInstancePhase("running")) {
88-
listener.dispose();
89-
resolve();
90-
}
91-
});
92-
});
93-
}
94-
const supervisorServiceClient = SupervisorServiceClient.get(gitpodServiceClient);
95-
const [ideStatus] = await Promise.all([
96-
supervisorServiceClient.ideReady,
97-
supervisorServiceClient.contentReady,
98-
loadingIDE,
99-
]);
100-
if (isWorkspaceInstancePhase("stopping") || isWorkspaceInstancePhase("stopped")) {
101-
return;
102-
}
103-
toStop.pushAll([
104-
IDEWebSocket.connectWorkspace(),
105-
gitpodServiceClient.onDidChangeInfo(() => {
106-
if (isWorkspaceInstancePhase("stopping") || isWorkspaceInstancePhase("stopped")) {
107-
toStop.dispose();
108-
}
109-
}),
110-
]);
111-
const isDesktopIde = ideStatus && ideStatus.desktop && ideStatus.desktop.link;
112-
if (!isDesktopIde) {
113-
toStop.push(ideService.start());
114-
}
115-
//#endregion
116-
})();
117-
118-
(async () => {
119-
document.body.style.visibility = "hidden";
120-
const [loading, gitpodServiceClient] = await Promise.all([
121-
LoadingFrame.load({ gitpodService: window.gitpod.service }),
122-
pendingGitpodServiceClient,
123-
]);
124-
const sessionId = await loading.sessionId;
125-
126-
if (gitpodServiceClient.info.workspace.type !== "regular") {
127-
return;
128-
}
129-
130-
const supervisorServiceClient = SupervisorServiceClient.get(gitpodServiceClient);
131-
132-
let hideDesktopIde = false;
133-
const serverOrigin = startUrl.url.origin;
134-
const hideDesktopIdeEventListener = (event: MessageEvent) => {
135-
if (event.origin === serverOrigin && event.data.type == "openBrowserIde") {
136-
window.removeEventListener("message", hideDesktopIdeEventListener);
137-
hideDesktopIde = true;
138-
toStop.push(ideService.start());
139-
}
140-
};
141-
window.addEventListener("message", hideDesktopIdeEventListener, false);
142-
toStop.push({ dispose: () => window.removeEventListener("message", hideDesktopIdeEventListener) });
143-
144-
//#region gitpod browser telemetry
145-
// TODO(ak) get rid of it
146-
// it is bad usage of window.postMessage
147-
// VS Code should use Segment directly here and publish to production/staging untrusted
148-
// supervisor frontend should not care about IDE specifics
149-
window.addEventListener("message", async (event) => {
150-
const type = event.data.type;
151-
if (type === "vscode_telemetry") {
152-
const { event: eventName, properties } = event.data;
153-
window.gitpod.service.server.trackEvent({
154-
event: eventName,
155-
properties: {
156-
sessionId,
157-
instanceId: gitpodServiceClient.info.latestInstance?.id,
158-
workspaceId: gitpodServiceClient.info.workspace.id,
159-
type: gitpodServiceClient.info.workspace.type,
160-
...properties,
161-
},
162-
});
163-
}
164-
});
165-
//#endregion
166-
167-
type DesktopIDEStatus = { link: string; label: string; clientID?: string; kind?: String };
168-
let isDesktopIde: undefined | boolean = undefined;
169-
let ideStatus: undefined | { desktop: DesktopIDEStatus } = undefined;
170-
171-
//#region current-frame
172-
let current: HTMLElement = loading.frame;
173-
let desktopRedirected = false;
174-
let currentInstanceId = "";
175-
const nextFrame = () => {
176-
const instance = gitpodServiceClient.info.latestInstance;
177-
if (instance) {
178-
// refresh web page when instanceId changed
179-
if (currentInstanceId !== "") {
180-
if (instance.id !== currentInstanceId && instance.ideUrl !== "") {
181-
currentInstanceId = instance.id;
182-
window.location.href = instance.ideUrl;
183-
}
184-
} else {
185-
currentInstanceId = instance.id;
186-
}
187-
if (instance.status.phase === "running") {
188-
if (!hideDesktopIde) {
189-
if (isDesktopIde == undefined) {
190-
return loading.frame;
191-
}
192-
if (isDesktopIde && !!ideStatus) {
193-
trackDesktopIDEReady(ideStatus.desktop);
194-
loading.setState({
195-
desktopIdeLink: ideStatus.desktop.link,
196-
desktopIdeLabel: ideStatus.desktop.label || "Open Desktop IDE",
197-
desktopIdeClientID: ideStatus.desktop.clientID,
198-
});
199-
if (!desktopRedirected) {
200-
desktopRedirected = true;
201-
loading.openDesktopLink(ideStatus.desktop.link);
202-
}
203-
return loading.frame;
204-
}
205-
}
206-
if (ideService.state === "ready") {
207-
return document.body;
208-
}
209-
}
210-
}
211-
return loading.frame;
212-
};
213-
const updateCurrentFrame = () => {
214-
const newCurrent = nextFrame();
215-
if (current === newCurrent) {
216-
return;
217-
}
218-
current.style.visibility = "hidden";
219-
newCurrent.style.visibility = "visible";
220-
if (current === document.body) {
221-
while (document.body.firstChild && document.body.firstChild !== newCurrent) {
222-
document.body.removeChild(document.body.firstChild);
223-
}
224-
while (document.body.lastChild && document.body.lastChild !== newCurrent) {
225-
document.body.removeChild(document.body.lastChild);
226-
}
227-
}
228-
current = newCurrent;
229-
};
230-
231-
const updateLoadingState = () => {
232-
loading.setState({
233-
ideFrontendFailureCause: ideService.failureCause?.message,
234-
});
235-
};
236-
const trackStatusRenderedEvent = (
237-
phase: string,
238-
properties?: {
239-
[prop: string]: any;
240-
},
241-
) => {
242-
window.gitpod.service.server.trackEvent({
243-
event: "status_rendered",
244-
properties: {
245-
sessionId,
246-
instanceId: gitpodServiceClient.info.latestInstance?.id,
247-
workspaceId: gitpodServiceClient.info.workspace.id,
248-
type: gitpodServiceClient.info.workspace.type,
249-
phase,
250-
...properties,
251-
},
252-
});
253-
};
254-
let trackedDesktopIDEReady = false;
255-
const trackDesktopIDEReady = ({ clientID, kind }: DesktopIDEStatus) => {
256-
if (trackedDesktopIDEReady) {
257-
return;
258-
}
259-
trackedDesktopIDEReady = true;
260-
trackStatusRenderedEvent("desktop-ide-ready", { clientID, kind });
261-
};
262-
const trackIDEStatusRenderedEvent = () => {
263-
let error: string | undefined;
264-
if (ideService.failureCause) {
265-
error = `${ideService.failureCause.message}\n${ideService.failureCause.stack}`;
266-
}
267-
trackStatusRenderedEvent(`ide-${ideService.state}`, { error });
268-
};
269-
270-
updateCurrentFrame();
271-
updateLoadingState();
272-
trackIDEStatusRenderedEvent();
273-
gitpodServiceClient.onDidChangeInfo(() => updateCurrentFrame());
274-
ideService.onDidChange(() => {
275-
updateLoadingState();
276-
updateCurrentFrame();
277-
trackIDEStatusRenderedEvent();
278-
});
279-
supervisorServiceClient.ideReady
280-
.then((newIdeStatus) => {
281-
ideStatus = newIdeStatus;
282-
isDesktopIde = !!ideStatus && !!ideStatus.desktop && !!ideStatus.desktop.link;
283-
updateCurrentFrame();
284-
})
285-
.catch((error) => console.error(`Unexpected error from supervisorServiceClient.ideReady: ${error}`));
286-
window.addEventListener("unload", () => trackStatusRenderedEvent("window-unload"), { capture: true });
287-
//#endregion
288-
289-
//#region heart-beat
290-
heartBeat.track(window);
291-
const updateHeartBeat = () => {
292-
if (gitpodServiceClient.info.latestInstance?.status.phase === "running") {
293-
heartBeat.schedule(gitpodServiceClient.info, sessionId);
294-
} else {
295-
heartBeat.cancel();
296-
}
297-
};
298-
updateHeartBeat();
299-
gitpodServiceClient.onDidChangeInfo(() => updateHeartBeat());
300-
//#endregion
301-
})();

0 commit comments

Comments
 (0)