Skip to content

Commit 963ebac

Browse files
committed
Register a service worker
To make installing as a PWA possible. Fixes #1181.
1 parent eef2ed0 commit 963ebac

17 files changed

+140
-78
lines changed

ci/build.ts

+18-11
Original file line numberDiff line numberDiff line change
@@ -339,17 +339,24 @@ class Builder {
339339
}
340340

341341
private createBundler(out = "dist", commit?: string): Bundler {
342-
return new Bundler(path.join(this.rootPath, "src/browser/pages/app.ts"), {
343-
cache: true,
344-
cacheDir: path.join(this.rootPath, ".cache"),
345-
detailedReport: true,
346-
minify: !!process.env.MINIFY,
347-
hmr: false,
348-
logLevel: 1,
349-
outDir: path.join(this.rootPath, out),
350-
publicUrl: `/static-${commit}/dist`,
351-
target: "browser",
352-
})
342+
return new Bundler(
343+
[
344+
path.join(this.rootPath, "src/browser/pages/app.ts"),
345+
path.join(this.rootPath, "src/browser/register.ts"),
346+
path.join(this.rootPath, "src/browser/serviceWorker.ts"),
347+
],
348+
{
349+
cache: true,
350+
cacheDir: path.join(this.rootPath, ".cache"),
351+
detailedReport: true,
352+
minify: !!process.env.MINIFY,
353+
hmr: false,
354+
logLevel: 1,
355+
outDir: path.join(this.rootPath, out),
356+
publicUrl: `/static-${commit || "development"}/dist`,
357+
target: "browser",
358+
},
359+
)
353360
}
354361
}
355362

src/browser/media/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"sizes": "96x96"
1313
},
1414
{
15-
"src": "/{{BASE}}/static-{{COMMIT}}/src/browser/media/pwa-icon-128.png",
15+
"src": "{{BASE}}/static-{{COMMIT}}/src/browser/media/pwa-icon-128.png",
1616
"type": "image/png",
1717
"sizes": "128x128"
1818
},

src/browser/pages/app.html

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<meta id="coder-options" data-settings="{{OPTIONS}}" />
2020
</head>
2121
<body>
22+
<script src="{{BASE}}/static-{{COMMIT}}/dist/register.js"></script>
2223
<script src="{{BASE}}/static-{{COMMIT}}/dist/app.js"></script>
2324
</body>
2425
</html>

src/browser/pages/app.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,5 @@ import "./login.css"
88
import "./update.css"
99

1010
const options = getOptions()
11-
const parts = window.location.pathname.replace(/^\//g, "").split("/")
12-
parts[parts.length - 1] = options.base
13-
const url = new URL(window.location.origin + "/" + parts.join("/"))
1411

15-
console.log(url)
12+
console.log(options)

src/browser/pages/error.html

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
/>
1717
<link rel="apple-touch-icon" href="{{BASE}}/static-{{COMMIT}}/src/browser/media/code-server.png" />
1818
<link href="{{BASE}}/static-{{COMMIT}}/dist/app.css" rel="stylesheet" />
19+
<meta id="coder-options" data-settings="{{OPTIONS}}" />
1920
</head>
2021
<body>
2122
<div class="center-container">
@@ -29,5 +30,6 @@ <h2 class="header">{{ERROR_HEADER}}</h2>
2930
</div>
3031
</div>
3132
</div>
33+
<script src="{{BASE}}/static-{{COMMIT}}/dist/register.js"></script>
3234
</body>
3335
</html>

src/browser/pages/home.html

+1
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,6 @@ <h2 class="main">Version</h2>
6060
</div>
6161
</div>
6262
</div>
63+
<script src="{{BASE}}/static-{{COMMIT}}/dist/register.js"></script>
6364
</body>
6465
</html>

src/browser/pages/login.html

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
/>
99
<meta
1010
http-equiv="Content-Security-Policy"
11-
content="style-src 'self'; script-src 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:;"
11+
content="style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:;"
1212
/>
1313
<title>code-server login</title>
1414
<link rel="icon" href="{{BASE}}/static-{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
@@ -19,6 +19,7 @@
1919
/>
2020
<link rel="apple-touch-icon" href="{{BASE}}/static-{{COMMIT}}/src/browser/media/code-server.png" />
2121
<link href="{{BASE}}/static-{{COMMIT}}/dist/app.css" rel="stylesheet" />
22+
<meta id="coder-options" data-settings="{{OPTIONS}}" />
2223
</head>
2324
<body>
2425
<div class="center-container">
@@ -52,6 +53,7 @@ <h1 class="main">Welcome to code-server</h1>
5253
</div>
5354
</div>
5455
</body>
56+
<script src="{{BASE}}/static-{{COMMIT}}/dist/register.js"></script>
5557
<script>
5658
const parts = window.location.pathname.replace(/^\//g, "").split("/")
5759
parts[parts.length - 1] = "{{BASE}}"

src/browser/pages/update.html

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
/>
1717
<link rel="apple-touch-icon" href="{{BASE}}/static-{{COMMIT}}/src/browser/media/code-server.png" />
1818
<link href="{{BASE}}/static-{{COMMIT}}/dist/app.css" rel="stylesheet" />
19+
<meta id="coder-options" data-settings="{{OPTIONS}}" />
1920
</head>
2021
<body>
2122
<div class="center-container">
@@ -32,5 +33,6 @@ <h1 class="main">Update</h1>
3233
</div>
3334
</div>
3435
</div>
36+
<script src="{{BASE}}/static-{{COMMIT}}/dist/register.js"></script>
3537
</body>
3638
</html>

src/browser/pages/vscode.html

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
<!-- PROD_ONLY
4242
<link rel="prefetch" href="{{VS_BASE}}/static-{{COMMIT}}/node_modules/semver-umd/lib/semver-umd.js">
4343
END_PROD_ONLY -->
44+
45+
<meta id="coder-options" data-settings="{{OPTIONS}}" />
4446
</head>
4547

4648
<body aria-label=""></body>
@@ -91,6 +93,7 @@
9193
"vs/nls": nlsConfig,
9294
}
9395
</script>
96+
<script src="{{BASE}}/static-{{COMMIT}}/dist/register.js"></script>
9497
<script src="{{VS_BASE}}/static-{{COMMIT}}/out/vs/loader.js"></script>
9598
<!-- PROD_ONLY
9699
<script src="{{VS_BASE}}/static-{{COMMIT}}/out/vs/workbench/workbench.web.api.nls.js"></script>

src/browser/register.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { getOptions, normalize } from "../common/util"
2+
3+
const options = getOptions()
4+
5+
if ("serviceWorker" in navigator) {
6+
const path = normalize(`${options.base}/static-${options.commit}/dist/serviceWorker.js`)
7+
navigator.serviceWorker
8+
.register(path, {
9+
scope: options.base || "/",
10+
})
11+
.then(function() {
12+
console.log("[Service Worker] registered")
13+
})
14+
}

src/browser/serviceWorker.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
3+
self.addEventListener("install", () => {
4+
console.log("[Service Worker] install")
5+
})
6+
7+
self.addEventListener("activate", (event: any) => {
8+
event.waitUntil((self as any).clients.claim())
9+
})
10+
11+
self.addEventListener("fetch", (event: any) => {
12+
if (!navigator.onLine) {
13+
event.respondWith(
14+
new Promise((resolve) => {
15+
resolve(
16+
new Response("OFFLINE", {
17+
status: 200,
18+
statusText: "OK",
19+
}),
20+
)
21+
}),
22+
)
23+
}
24+
})

src/common/util.ts

+16-9
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { logger } from "@coder/logger"
22

33
export interface Options {
44
base: string
5+
commit: string
56
logLevel: number
6-
sessionId: string
7+
sessionId?: string
78
}
89

910
/**
@@ -25,6 +26,13 @@ export const generateUuid = (length = 24): string => {
2526
.join("")
2627
}
2728

29+
/**
30+
* Remove extra slashes in a URL.
31+
*/
32+
export const normalize = (url: string, keepTrailing = false): string => {
33+
return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "")
34+
}
35+
2836
/**
2937
* Get options embedded in the HTML from the server.
3038
*/
@@ -45,16 +53,15 @@ export const getOptions = <T extends Options>(): T => {
4553
if (typeof options.logLevel !== "undefined") {
4654
logger.level = options.logLevel
4755
}
48-
return options
56+
const parts = window.location.pathname.replace(/^\//g, "").split("/")
57+
parts[parts.length - 1] = options.base
58+
const url = new URL(window.location.origin + "/" + parts.join("/"))
59+
return {
60+
...options,
61+
base: normalize(url.pathname, true),
62+
}
4963
} catch (error) {
5064
logger.warn(error.message)
5165
return {} as T
5266
}
5367
}
54-
55-
/**
56-
* Remove extra slashes in a URL.
57-
*/
58-
export const normalize = (url: string, keepTrailing = false): string => {
59-
return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "")
60-
}

src/node/app/app.ts

+11-27
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { logger } from "@coder/logger"
21
import * as http from "http"
32
import * as querystring from "querystring"
43
import { Application } from "../../common/api"
54
import { HttpCode, HttpError } from "../../common/http"
6-
import { Options } from "../../common/util"
75
import { HttpProvider, HttpProviderOptions, HttpResponse, Route } from "../http"
86
import { ApiHttpProvider } from "./api"
97
import { UpdateHttpProvider } from "./update"
@@ -61,15 +59,7 @@ export class MainHttpProvider extends HttpProvider {
6159
}
6260

6361
if (sessionId) {
64-
return this.getAppRoot(
65-
route,
66-
{
67-
sessionId,
68-
base: this.base(route),
69-
logLevel: logger.level,
70-
},
71-
(app && app.name) || "",
72-
)
62+
return this.getAppRoot(route, (app && app.name) || "", sessionId)
7363
}
7464

7565
return this.getErrorRoot(route, "404", "404", "Application not found")
@@ -79,12 +69,12 @@ export class MainHttpProvider extends HttpProvider {
7969
* Return a resource with variables replaced where necessary.
8070
*/
8171
protected async getReplacedResource(route: Route): Promise<HttpResponse> {
82-
if (route.requestPath.endsWith("/manifest.json")) {
83-
const response = await this.getUtf8Resource(this.rootPath, route.requestPath)
84-
response.content = response.content
85-
.replace(/{{BASE}}/g, this.base(route))
86-
.replace(/{{COMMIT}}/g, this.options.commit)
87-
return response
72+
const split = route.requestPath.split("/")
73+
switch (split[split.length - 1]) {
74+
case "manifest.json": {
75+
const response = await this.getUtf8Resource(this.rootPath, route.requestPath)
76+
return this.replaceTemplates(route, response)
77+
}
8878
}
8979
return this.getResource(this.rootPath, route.requestPath)
9080
}
@@ -94,8 +84,6 @@ export class MainHttpProvider extends HttpProvider {
9484
const apps = await this.api.installedApplications()
9585
const response = await this.getUtf8Resource(this.rootPath, "src/browser/pages/home.html")
9686
response.content = response.content
97-
.replace(/{{COMMIT}}/g, this.options.commit)
98-
.replace(/{{BASE}}/g, this.base(route))
9987
.replace(/{{UPDATE:NAME}}/, await this.getUpdate())
10088
.replace(/{{APP_LIST:RUNNING}}/, this.getAppRows(running.applications))
10189
.replace(
@@ -106,17 +94,13 @@ export class MainHttpProvider extends HttpProvider {
10694
/{{APP_LIST:OTHER}}/,
10795
this.getAppRows(apps.filter((app) => !app.categories || !app.categories.includes("Editor"))),
10896
)
109-
return response
97+
return this.replaceTemplates(route, response)
11098
}
11199

112-
public async getAppRoot(route: Route, options: Options, name: string): Promise<HttpResponse> {
100+
public async getAppRoot(route: Route, name: string, sessionId: string): Promise<HttpResponse> {
113101
const response = await this.getUtf8Resource(this.rootPath, "src/browser/pages/app.html")
114-
response.content = response.content
115-
.replace(/{{COMMIT}}/g, this.options.commit)
116-
.replace(/{{BASE}}/g, this.base(route))
117-
.replace(/{{APP_NAME}}/, name)
118-
.replace(/"{{OPTIONS}}"/, `'${JSON.stringify(options)}'`)
119-
return response
102+
response.content = response.content.replace(/{{APP_NAME}}/, name)
103+
return this.replaceTemplates(route, response, sessionId)
120104
}
121105

122106
public async handleWebSocket(): Promise<undefined> {

src/node/app/login.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,9 @@ export class LoginHttpProvider extends HttpProvider {
4848
public async getRoot(route: Route, value?: string, error?: Error): Promise<HttpResponse> {
4949
const response = await this.getUtf8Resource(this.rootPath, "src/browser/pages/login.html")
5050
response.content = response.content
51-
.replace(/{{COMMIT}}/g, this.options.commit)
52-
.replace(/{{BASE}}/g, this.base(route))
5351
.replace(/{{VALUE}}/, value || "")
5452
.replace(/{{ERROR}}/, error ? `<div class="error">${error.message}</div>` : "")
55-
return response
53+
return this.replaceTemplates(route, response)
5654
}
5755

5856
/**

src/node/app/update.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,9 @@ export class UpdateHttpProvider extends HttpProvider {
8686
public async getRoot(route: Route, error?: Error): Promise<HttpResponse> {
8787
const response = await this.getUtf8Resource(this.rootPath, "src/browser/pages/update.html")
8888
response.content = response.content
89-
.replace(/{{COMMIT}}/g, this.options.commit)
90-
.replace(/{{BASE}}/g, this.base(route))
9189
.replace(/{{UPDATE_STATUS}}/, await this.getUpdateHtml())
9290
.replace(/{{ERROR}}/, error ? `<div class="error">${error.message}</div>` : "")
93-
return response
91+
return this.replaceTemplates(route, response)
9492
}
9593

9694
public async handleWebSocket(): Promise<undefined> {

src/node/app/vscode.ts

+7-11
Original file line numberDiff line numberDiff line change
@@ -219,17 +219,13 @@ export class VscodeHttpProvider extends HttpProvider {
219219
response.content = response.content.replace(/<!-- PROD_ONLY/g, "").replace(/END_PROD_ONLY -->/g, "")
220220
}
221221

222-
return {
223-
...response,
224-
content: response.content
225-
.replace(/{{COMMIT}}/g, options.commit)
226-
.replace(/{{BASE}}/g, this.base(route))
227-
.replace(/{{VS_BASE}}/g, this.base(route) + this.options.base)
228-
.replace(`"{{REMOTE_USER_DATA_URI}}"`, `'${JSON.stringify(options.remoteUserDataUri)}'`)
229-
.replace(`"{{PRODUCT_CONFIGURATION}}"`, `'${JSON.stringify(options.productConfiguration)}'`)
230-
.replace(`"{{WORKBENCH_WEB_CONFIGURATION}}"`, `'${JSON.stringify(options.workbenchWebConfiguration)}'`)
231-
.replace(`"{{NLS_CONFIGURATION}}"`, `'${JSON.stringify(options.nlsConfiguration)}'`),
232-
}
222+
response.content = response.content
223+
.replace(/{{VS_BASE}}/g, this.base(route) + this.options.base)
224+
.replace(`"{{REMOTE_USER_DATA_URI}}"`, `'${JSON.stringify(options.remoteUserDataUri)}'`)
225+
.replace(`"{{PRODUCT_CONFIGURATION}}"`, `'${JSON.stringify(options.productConfiguration)}'`)
226+
.replace(`"{{WORKBENCH_WEB_CONFIGURATION}}"`, `'${JSON.stringify(options.workbenchWebConfiguration)}'`)
227+
.replace(`"{{NLS_CONFIGURATION}}"`, `'${JSON.stringify(options.nlsConfiguration)}'`)
228+
return this.replaceTemplates(route, response)
233229
}
234230

235231
/**

0 commit comments

Comments
 (0)