mirror of https://github.com/coder/code-server.git
Integrate update notifications into VS Code
This commit is contained in:
parent
069c5230cd
commit
ccd01c49b9
|
@ -444,10 +444,10 @@ index d0f6e6b18a..1966fd297d 100644
|
||||||
-
|
-
|
||||||
diff --git a/src/vs/server/browser/client.ts b/src/vs/server/browser/client.ts
|
diff --git a/src/vs/server/browser/client.ts b/src/vs/server/browser/client.ts
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
index 0000000000..3a62205b38
|
index 0000000000..1e6bca3b52
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/src/vs/server/browser/client.ts
|
+++ b/src/vs/server/browser/client.ts
|
||||||
@@ -0,0 +1,162 @@
|
@@ -0,0 +1,224 @@
|
||||||
+import { Emitter } from 'vs/base/common/event';
|
+import { Emitter } from 'vs/base/common/event';
|
||||||
+import { URI } from 'vs/base/common/uri';
|
+import { URI } from 'vs/base/common/uri';
|
||||||
+import { localize } from 'vs/nls';
|
+import { localize } from 'vs/nls';
|
||||||
|
@ -455,6 +455,7 @@ index 0000000000..3a62205b38
|
||||||
+import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
+import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||||
+import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
+import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||||
+import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
+import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
||||||
|
+import { ILogService } from 'vs/platform/log/common/log';
|
||||||
+import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
+import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||||
+import { Registry } from 'vs/platform/registry/common/platform';
|
+import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
+import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
+import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||||
|
@ -575,6 +576,67 @@ index 0000000000..3a62205b38
|
||||||
+ }
|
+ }
|
||||||
+ });
|
+ });
|
||||||
+ }
|
+ }
|
||||||
|
+
|
||||||
|
+ const applyUpdate = async (): Promise<void> => {
|
||||||
|
+ (services.get(ILogService) as ILogService).debug("Applying update...");
|
||||||
|
+
|
||||||
|
+ const response = await fetch("./update/apply", {
|
||||||
|
+ headers: { "content-type": "application/json" },
|
||||||
|
+ });
|
||||||
|
+ if (response.status !== 200) {
|
||||||
|
+ throw new Error("Unexpected response");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ const json = await response.json();
|
||||||
|
+ if (!json.isLatest) {
|
||||||
|
+ throw new Error("Update failed");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ (services.get(INotificationService) as INotificationService).info(`Updated to ${json.version}`);
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ const getUpdate = async (): Promise<void> => {
|
||||||
|
+ (services.get(ILogService) as ILogService).debug("Checking for update...");
|
||||||
|
+
|
||||||
|
+ const response = await fetch("./update", {
|
||||||
|
+ headers: { "content-type": "application/json" },
|
||||||
|
+ });
|
||||||
|
+ if (response.status !== 200) {
|
||||||
|
+ throw new Error("unexpected response");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ const json = await response.json();
|
||||||
|
+ if (json.isLatest) {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ (services.get(INotificationService) as INotificationService).notify({
|
||||||
|
+ severity: Severity.Info,
|
||||||
|
+ message: `code-server has an update: ${json.version}`,
|
||||||
|
+ actions: {
|
||||||
|
+ primary: [{
|
||||||
|
+ id: 'update',
|
||||||
|
+ label: 'Apply Update',
|
||||||
|
+ tooltip: '',
|
||||||
|
+ class: undefined,
|
||||||
|
+ enabled: true,
|
||||||
|
+ checked: true,
|
||||||
|
+ dispose: () => undefined,
|
||||||
|
+ run: applyUpdate,
|
||||||
|
+ }],
|
||||||
|
+ }
|
||||||
|
+ });
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ const updateLoop = (): void => {
|
||||||
|
+ getUpdate().catch((error) => {
|
||||||
|
+ (services.get(ILogService) as ILogService).warn(error);
|
||||||
|
+ }).finally(() => {
|
||||||
|
+ setTimeout(updateLoop, 300000);
|
||||||
|
+ });
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ updateLoop();
|
||||||
+};
|
+};
|
||||||
+
|
+
|
||||||
+export interface Query {
|
+export interface Query {
|
||||||
|
@ -2968,7 +3030,7 @@ index bbb72e9511..0785d3391d 100644
|
||||||
-registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) { whenReady = Promise.resolve(); });
|
-registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) { whenReady = Promise.resolve(); });
|
||||||
+registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths);
|
+registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths);
|
||||||
diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts
|
diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts
|
||||||
index 79455414c0..a407593b4d 100644
|
index 79455414c0..8931c1355a 100644
|
||||||
--- a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts
|
--- a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts
|
||||||
+++ b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts
|
+++ b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts
|
||||||
@@ -14,7 +14,11 @@
|
@@ -14,7 +14,11 @@
|
||||||
|
@ -2978,8 +3040,8 @@ index 79455414c0..a407593b4d 100644
|
||||||
- catchError: true
|
- catchError: true
|
||||||
+ catchError: true,
|
+ catchError: true,
|
||||||
+ paths: {
|
+ paths: {
|
||||||
+ '@coder/node-browser': `../node_modules/@coder/node-browser/out/client/client.js`,
|
+ '@coder/node-browser': `{{BASE}}/static/{{COMMIT}}/lib/vscode/node_modules/@coder/node-browser/out/client/client.js`,
|
||||||
+ '@coder/requirefs': `../node_modules/@coder/requirefs/out/requirefs.js`,
|
+ '@coder/requirefs': `{{BASE}}/static/{{COMMIT}}/lib/vscode/node_modules/@coder/requirefs/out/requirefs.js`,
|
||||||
+ }
|
+ }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
crossorigin="use-credentials"
|
crossorigin="use-credentials"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
|
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
|
||||||
<link href="{{BASE}}/static/{{COMMIT}}/dist/app.css" rel="stylesheet" />
|
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
|
||||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
crossorigin="use-credentials"
|
crossorigin="use-credentials"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
|
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
|
||||||
<link href="{{BASE}}/static/{{COMMIT}}/dist/app.css" rel="stylesheet" />
|
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
|
||||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
{{ERROR_BODY}}
|
{{ERROR_BODY}}
|
||||||
</div>
|
</div>
|
||||||
<div class="links">
|
<div class="links">
|
||||||
<a class="link" href="{{BASE}}">go home</a>
|
<a class="link" href="{{BASE}}{{TO}}">go home</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
crossorigin="use-credentials"
|
crossorigin="use-credentials"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
|
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
|
||||||
<link href="{{BASE}}/static/{{COMMIT}}/dist/app.css" rel="stylesheet" />
|
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
|
||||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
crossorigin="use-credentials"
|
crossorigin="use-credentials"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
|
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
|
||||||
<link href="{{BASE}}/static/{{COMMIT}}/dist/app.css" rel="stylesheet" />
|
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
|
||||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -10,3 +10,17 @@
|
||||||
color: red;
|
color: red;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.update-form > .links {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-form > .links > .link {
|
||||||
|
color: rgb(87, 114, 245);
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-form > .links > .link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
crossorigin="use-credentials"
|
crossorigin="use-credentials"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
|
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
|
||||||
<link href="{{BASE}}/static/{{COMMIT}}/dist/app.css" rel="stylesheet" />
|
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
|
||||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -26,9 +26,11 @@
|
||||||
<div class="sub">Update code-server.</div>
|
<div class="sub">Update code-server.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<form class="update-form" method="post">
|
<form class="update-form" action="{{BASE}}/update/apply">
|
||||||
{{UPDATE_STATUS}} {{ERROR}}
|
{{UPDATE_STATUS}} {{ERROR}}
|
||||||
<a class="cancel -button" href="{{BASE}}">Cancel</a>
|
<div class="links">
|
||||||
|
<a class="link" href="{{BASE}}{{TO}}">go home</a>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -59,6 +59,9 @@ export class ApiHttpProvider extends HttpProvider {
|
||||||
|
|
||||||
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
|
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
|
||||||
this.ensureAuthenticated(request)
|
this.ensureAuthenticated(request)
|
||||||
|
if (route.requestPath !== "/index.html") {
|
||||||
|
throw new HttpError("Not found", HttpCode.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
switch (route.base) {
|
switch (route.base) {
|
||||||
case ApiEndpoint.applications:
|
case ApiEndpoint.applications:
|
||||||
|
|
|
@ -16,6 +16,11 @@ export class AppHttpProvider extends HttpProvider {
|
||||||
return { redirect: "/login", query: { to: route.fullPath } }
|
return { redirect: "/login", query: { to: route.fullPath } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.ensureMethod(request)
|
||||||
|
if (route.requestPath !== "/index.html") {
|
||||||
|
throw new HttpError("Not found", HttpCode.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
// Run an existing app, but if it doesn't exist go ahead and start it.
|
// Run an existing app, but if it doesn't exist go ahead and start it.
|
||||||
let app = this.api.getRunningApplication(route.base)
|
let app = this.api.getRunningApplication(route.base)
|
||||||
let sessionId = app && app.sessionId
|
let sessionId = app && app.sessionId
|
||||||
|
@ -38,8 +43,4 @@ export class AppHttpProvider extends HttpProvider {
|
||||||
response.content = response.content.replace(/{{APP_NAME}}/, name)
|
response.content = response.content.replace(/{{APP_NAME}}/, name)
|
||||||
return this.replaceTemplates(route, response, sessionId)
|
return this.replaceTemplates(route, response, sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handleWebSocket(): Promise<true> {
|
|
||||||
throw new HttpError("Not found", HttpCode.NotFound)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,14 @@ export class DashboardHttpProvider extends HttpProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
|
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
|
||||||
|
if (route.requestPath !== "/index.html") {
|
||||||
|
throw new HttpError("Not found", HttpCode.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
switch (route.base) {
|
switch (route.base) {
|
||||||
case "/delete": {
|
case "/delete": {
|
||||||
this.ensureMethod(request, "POST")
|
|
||||||
this.ensureAuthenticated(request)
|
this.ensureAuthenticated(request)
|
||||||
|
this.ensureMethod(request, "POST")
|
||||||
const data = await this.getData(request)
|
const data = await this.getData(request)
|
||||||
const p = data ? querystring.parse(data) : {}
|
const p = data ? querystring.parse(data) : {}
|
||||||
this.api.deleteSession(p.sessionId as string)
|
this.api.deleteSession(p.sessionId as string)
|
||||||
|
@ -32,9 +36,7 @@ export class DashboardHttpProvider extends HttpProvider {
|
||||||
|
|
||||||
case "/": {
|
case "/": {
|
||||||
this.ensureMethod(request)
|
this.ensureMethod(request)
|
||||||
if (route.requestPath !== "/index.html") {
|
if (!this.authenticated(request)) {
|
||||||
throw new HttpError("Not found", HttpCode.NotFound)
|
|
||||||
} else if (!this.authenticated(request)) {
|
|
||||||
return { redirect: "/login", query: { to: this.options.base } }
|
return { redirect: "/login", query: { to: this.options.base } }
|
||||||
}
|
}
|
||||||
return this.getRoot(route)
|
return this.getRoot(route)
|
||||||
|
@ -69,10 +71,6 @@ export class DashboardHttpProvider extends HttpProvider {
|
||||||
return this.replaceTemplates(route, response)
|
return this.replaceTemplates(route, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handleWebSocket(): Promise<true> {
|
|
||||||
throw new HttpError("Not found", HttpCode.NotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
private getRecentProjectRows(base: string, recents: RecentResponse): string {
|
private getRecentProjectRows(base: string, recents: RecentResponse): string {
|
||||||
return recents.paths.length > 0 || recents.workspaces.length > 0
|
return recents.paths.length > 0 || recents.workspaces.length > 0
|
||||||
? recents.paths.map((recent) => this.getRecentProjectRow(base, recent)).join("\n") +
|
? recents.paths.map((recent) => this.getRecentProjectRow(base, recent)).join("\n") +
|
||||||
|
@ -151,7 +149,7 @@ export class DashboardHttpProvider extends HttpProvider {
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
${humanize(update.checked)}
|
${humanize(update.checked)}
|
||||||
<a class="sub -link" href="${base}/update">Update now</a>
|
<a class="sub -link" href="${base}/update?to=${this.options.base}">Update now</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="item" >Current: ${this.update.currentVersion}</div>
|
<div class="item" >Current: ${this.update.currentVersion}</div>
|
||||||
</div>`
|
</div>`
|
||||||
|
|
|
@ -18,17 +18,14 @@ interface LoginPayload {
|
||||||
*/
|
*/
|
||||||
export class LoginHttpProvider extends HttpProvider {
|
export class LoginHttpProvider extends HttpProvider {
|
||||||
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
|
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
|
||||||
if (this.options.auth !== AuthType.Password) {
|
if (this.options.auth !== AuthType.Password || route.requestPath !== "/index.html") {
|
||||||
throw new HttpError("Not found", HttpCode.NotFound)
|
throw new HttpError("Not found", HttpCode.NotFound)
|
||||||
}
|
}
|
||||||
switch (route.base) {
|
switch (route.base) {
|
||||||
case "/":
|
case "/":
|
||||||
if (route.requestPath !== "/index.html") {
|
|
||||||
throw new HttpError("Not found", HttpCode.NotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case "POST":
|
case "POST":
|
||||||
|
this.ensureMethod(request, ["GET", "POST"])
|
||||||
return this.tryLogin(route, request)
|
return this.tryLogin(route, request)
|
||||||
default:
|
default:
|
||||||
this.ensureMethod(request)
|
this.ensureMethod(request)
|
||||||
|
@ -110,8 +107,4 @@ export class LoginHttpProvider extends HttpProvider {
|
||||||
|
|
||||||
throw new Error("Missing password")
|
throw new Error("Missing password")
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handleWebSocket(): Promise<undefined> {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import * as http from "http"
|
import * as http from "http"
|
||||||
import { HttpCode, HttpError } from "../../common/http"
|
|
||||||
import { HttpProvider, HttpResponse, Route } from "../http"
|
import { HttpProvider, HttpResponse, Route } from "../http"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,8 +31,4 @@ export class StaticHttpProvider extends HttpProvider {
|
||||||
}
|
}
|
||||||
return this.getResource(this.rootPath, ...split)
|
return this.getResource(this.rootPath, ...split)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handleWebSocket(): Promise<true> {
|
|
||||||
throw new HttpError("Not found", HttpCode.NotFound)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,10 +59,14 @@ export class UpdateHttpProvider extends HttpProvider {
|
||||||
|
|
||||||
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
|
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
|
||||||
this.ensureAuthenticated(request)
|
this.ensureAuthenticated(request)
|
||||||
|
this.ensureMethod(request)
|
||||||
|
|
||||||
|
if (route.requestPath !== "/index.html") {
|
||||||
|
throw new HttpError("Not found", HttpCode.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
switch (route.base) {
|
switch (route.base) {
|
||||||
case "/check":
|
case "/check":
|
||||||
this.ensureMethod(request)
|
|
||||||
this.getUpdate(true)
|
this.getUpdate(true)
|
||||||
if (route.query && route.query.to) {
|
if (route.query && route.query.to) {
|
||||||
return {
|
return {
|
||||||
|
@ -70,37 +74,45 @@ export class UpdateHttpProvider extends HttpProvider {
|
||||||
query: { to: undefined },
|
query: { to: undefined },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.getRoot(route)
|
return this.getRoot(route, request)
|
||||||
case "/": {
|
case "/apply":
|
||||||
this.ensureMethod(request, ["GET", "POST"])
|
return this.tryUpdate(route, request)
|
||||||
if (route.requestPath !== "/index.html") {
|
case "/":
|
||||||
throw new HttpError("Not found", HttpCode.NotFound)
|
return this.getRoot(route, request)
|
||||||
}
|
|
||||||
|
|
||||||
switch (request.method) {
|
|
||||||
case "GET":
|
|
||||||
return this.getRoot(route)
|
|
||||||
case "POST":
|
|
||||||
return this.tryUpdate(route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new HttpError("Not found", HttpCode.NotFound)
|
throw new HttpError("Not found", HttpCode.NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getRoot(route: Route, error?: Error): Promise<HttpResponse> {
|
public async getRoot(
|
||||||
|
route: Route,
|
||||||
|
request: http.IncomingMessage,
|
||||||
|
appliedUpdate?: string,
|
||||||
|
error?: Error,
|
||||||
|
): Promise<HttpResponse> {
|
||||||
|
if (request.headers["content-type"] === "application/json") {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return {
|
||||||
|
content: {
|
||||||
|
isLatest: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const update = await this.getUpdate()
|
||||||
|
return {
|
||||||
|
content: {
|
||||||
|
...update,
|
||||||
|
isLatest: this.isLatestVersion(update),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
const response = await this.getUtf8Resource(this.rootPath, "src/browser/pages/update.html")
|
const response = await this.getUtf8Resource(this.rootPath, "src/browser/pages/update.html")
|
||||||
response.content = response.content
|
response.content = response.content
|
||||||
.replace(/{{UPDATE_STATUS}}/, await this.getUpdateHtml())
|
.replace(/{{UPDATE_STATUS}}/, appliedUpdate ? `Updated to ${appliedUpdate}` : await this.getUpdateHtml())
|
||||||
.replace(/{{ERROR}}/, error ? `<div class="error">${error.message}</div>` : "")
|
.replace(/{{ERROR}}/, error ? `<div class="error">${error.message}</div>` : "")
|
||||||
return this.replaceTemplates(route, response)
|
return this.replaceTemplates(route, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handleWebSocket(): Promise<true> {
|
|
||||||
throw new HttpError("Not found", HttpCode.NotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query for and return the latest update.
|
* Query for and return the latest update.
|
||||||
*/
|
*/
|
||||||
|
@ -163,25 +175,26 @@ export class UpdateHttpProvider extends HttpProvider {
|
||||||
|
|
||||||
const update = await this.getUpdate()
|
const update = await this.getUpdate()
|
||||||
if (this.isLatestVersion(update)) {
|
if (this.isLatestVersion(update)) {
|
||||||
throw new Error("No update available")
|
return "No update available"
|
||||||
}
|
}
|
||||||
|
|
||||||
return `<button type="submit" class="apply -button">Update to ${update.version}</button>`
|
return `<button type="submit" class="apply -button">Update to ${update.version}</button>`
|
||||||
}
|
}
|
||||||
|
|
||||||
public async tryUpdate(route: Route): Promise<HttpResponse> {
|
public async tryUpdate(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
|
||||||
try {
|
try {
|
||||||
const update = await this.getUpdate()
|
const update = await this.getUpdate()
|
||||||
if (!this.isLatestVersion(update)) {
|
if (!this.isLatestVersion(update)) {
|
||||||
await this.downloadUpdate(update)
|
await this.downloadAndApplyUpdate(update)
|
||||||
|
return this.getRoot(route, request, update.version)
|
||||||
}
|
}
|
||||||
return this.getRoot(route)
|
return this.getRoot(route, request)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return this.getRoot(route, error)
|
return this.getRoot(route, request, undefined, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async downloadUpdate(update: Update, targetPath?: string, target?: string): Promise<void> {
|
public async downloadAndApplyUpdate(update: Update, targetPath?: string, target?: string): Promise<void> {
|
||||||
const releaseName = await this.getReleaseName(update, target)
|
const releaseName = await this.getReleaseName(update, target)
|
||||||
const url = this.downloadUrl.replace("{{VERSION}}", update.version).replace("{{RELEASE_NAME}}", releaseName)
|
const url = this.downloadUrl.replace("{{VERSION}}", update.version).replace("{{RELEASE_NAME}}", releaseName)
|
||||||
|
|
||||||
|
|
|
@ -140,12 +140,16 @@ export abstract class HttpProvider {
|
||||||
/**
|
/**
|
||||||
* Handle web sockets on the registered endpoint.
|
* Handle web sockets on the registered endpoint.
|
||||||
*/
|
*/
|
||||||
public abstract handleWebSocket(
|
public handleWebSocket(
|
||||||
route: Route,
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
request: http.IncomingMessage,
|
_route: Route,
|
||||||
socket: net.Socket,
|
_request: http.IncomingMessage,
|
||||||
head: Buffer,
|
_socket: net.Socket,
|
||||||
): Promise<true | undefined>
|
_head: Buffer,
|
||||||
|
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||||
|
): Promise<true | undefined> {
|
||||||
|
throw new HttpError("Not found", HttpCode.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle requests to the registered endpoint.
|
* Handle requests to the registered endpoint.
|
||||||
|
@ -194,6 +198,7 @@ export abstract class HttpProvider {
|
||||||
}
|
}
|
||||||
response.content = response.content
|
response.content = response.content
|
||||||
.replace(/{{COMMIT}}/g, this.options.commit)
|
.replace(/{{COMMIT}}/g, this.options.commit)
|
||||||
|
.replace(/{{TO}}/g, Array.isArray(route.query.to) ? route.query.to[0] : route.query.to || "/dashboard")
|
||||||
.replace(/{{BASE}}/g, this.base(route))
|
.replace(/{{BASE}}/g, this.base(route))
|
||||||
.replace(/"{{OPTIONS}}"/, `'${JSON.stringify(options)}'`)
|
.replace(/"{{OPTIONS}}"/, `'${JSON.stringify(options)}'`)
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -205,12 +205,12 @@ describe("update", () => {
|
||||||
assert.equal(`console.log("OLD")`, await fs.readFile(entry, "utf8"))
|
assert.equal(`console.log("OLD")`, await fs.readFile(entry, "utf8"))
|
||||||
|
|
||||||
// Updating should replace the existing version.
|
// Updating should replace the existing version.
|
||||||
await p.downloadUpdate(update, destination, "linux")
|
await p.downloadAndApplyUpdate(update, destination, "linux")
|
||||||
assert.equal(`console.log("UPDATED")`, await fs.readFile(entry, "utf8"))
|
assert.equal(`console.log("UPDATED")`, await fs.readFile(entry, "utf8"))
|
||||||
|
|
||||||
// Should still work if there is no existing version somehow.
|
// Should still work if there is no existing version somehow.
|
||||||
await fs.remove(destination)
|
await fs.remove(destination)
|
||||||
await p.downloadUpdate(update, destination, "linux")
|
await p.downloadAndApplyUpdate(update, destination, "linux")
|
||||||
assert.equal(`console.log("UPDATED")`, await fs.readFile(entry, "utf8"))
|
assert.equal(`console.log("UPDATED")`, await fs.readFile(entry, "utf8"))
|
||||||
|
|
||||||
assert.deepEqual(spy, [
|
assert.deepEqual(spy, [
|
||||||
|
|
Loading…
Reference in New Issue