roxxie-proxy-u-prod 0.1.0 → 0.2.2
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/README.md +62 -74
- package/dist/assets/{index-DcvPYWS3.js → index-CttvagWK.js} +164 -172
- package/dist/assets/index-EeRhLAof.css +1 -0
- package/dist/assets/{inject-BnVzMVpl.js → inject-xtH4w5z9.js} +8 -5
- package/dist/favicon.png +0 -0
- package/embed.js +2 -1
- package/examples/single-page.html +3 -1
- package/package.json +43 -41
- package/runtime/index.html +5 -4
- package/scripts/build.mjs +11 -5
- package/scripts/smoke.mjs +264 -73
- package/scripts/verify.mjs +100 -1
- package/source/packages/chrome/index.html +3 -2
- package/source/packages/chrome/public/favicon.png +0 -0
- package/source/packages/chrome/src/Tab/Tab.tsx +1 -1
- package/source/packages/chrome/src/assets/favicon.ts +1 -1
- package/source/packages/chrome/src/components/Button.tsx +11 -22
- package/source/packages/chrome/src/components/Checkbox.tsx +8 -12
- package/source/packages/chrome/src/components/Input.tsx +3 -4
- package/source/packages/chrome/src/components/StartScreen.tsx +34 -49
- package/source/packages/chrome/src/components/TopSiteButton.tsx +2 -2
- package/source/packages/chrome/src/pages/NewTabPage.tsx +42 -10
- package/source/packages/chrome/src/pages/SettingsPage.tsx +10 -11
- package/source/packages/chrome/src/runtime-base.ts +1 -1
- package/source/packages/chrome/src/services/SettingsService.ts +1 -1
- package/source/packages/chrome/src/style.css +7 -7
- package/source/packages/chrome/src/themes.ts +21 -21
- package/source/packages/inject/src/errorpage/errorpage.css +22 -35
- package/source/packages/inject/src/errorpage/errorpage.ts +6 -3
- package/source/packages/roxxie-transport/package.json +1 -1
- package/source/packages/roxxie-transport/src/connection.ts +1 -1
- package/source/packages/roxxie-transport/src/transport.ts +2 -2
- package/source/packages/scramjet/packages/core/dist/scramjet.js +1 -1
- package/source/packages/scramjet/packages/core/dist/scramjet.mjs +1 -1
- package/source/packages/scramjet/packages/core/dist/scramjet_bundled.js +1 -1
- package/source/packages/scramjet/packages/core/dist/scramjet_bundled.mjs +1 -1
- package/source/packages/tracker-protocol/package.json +1 -1
- package/source/packages/tracker-protocol/src/index.ts +34 -10
- package/source/pnpm-lock.yaml +4 -1
- package/standalone.html +27 -0
- package/dist/assets/index-BdRlQ7d_.css +0 -1
- package/dist/icon.png +0 -0
- package/dist/roxxie-icon.svg +0 -12
- package/source/packages/chrome/public/icon.png +0 -0
- package/source/packages/chrome/public/roxxie-icon.svg +0 -12
- package/source/packages/scramjet/packages/core/dist/temp-types-build/index.js +0 -23
- package/source/packages/scramjet/packages/core/dist/temp-types-build/index.js.map +0 -1
package/dist/favicon.png
ADDED
|
Binary file
|
package/embed.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
(function installRoxxieProxyU(global, document) {
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
|
-
var DEFAULT_SHELL_URL =
|
|
5
|
+
var DEFAULT_SHELL_URL =
|
|
6
|
+
"https://unpkg.com/roxxie-proxy-u-prod@0.2.2/runtime/index.html";
|
|
6
7
|
var loadingScript = document.currentScript;
|
|
7
8
|
|
|
8
9
|
function resolveContainer(target) {
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
<div id="roxxie-browser"></div>
|
|
19
19
|
<script
|
|
20
20
|
defer
|
|
21
|
-
src="https://cdn.jsdelivr.net/npm/roxxie-proxy-u-prod@0.
|
|
21
|
+
src="https://cdn.jsdelivr.net/npm/roxxie-proxy-u-prod@0.2.2/embed.js"
|
|
22
|
+
integrity="sha384-3QVF5CQiWk0SKqCDSEAoupN0KXK0bxo9qaZeS5Tj8/8mO8J719dv+fvmrqmdthH2"
|
|
23
|
+
crossorigin="anonymous"
|
|
22
24
|
data-roxxie-target="#roxxie-browser"
|
|
23
25
|
></script>
|
|
24
26
|
</body>
|
package/package.json
CHANGED
|
@@ -1,42 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
2
|
+
"name": "roxxie-proxy-u-prod",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Production CDN assets and embed loader for Roxxie Proxy U",
|
|
5
|
+
"license": "AGPL-3.0-only",
|
|
6
|
+
"private": false,
|
|
7
|
+
"main": "embed.js",
|
|
8
|
+
"browser": "embed.js",
|
|
9
|
+
"jsdelivr": "embed.js",
|
|
10
|
+
"unpkg": "embed.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"runtime",
|
|
14
|
+
"examples",
|
|
15
|
+
"scripts",
|
|
16
|
+
"source",
|
|
17
|
+
"third_party",
|
|
18
|
+
"embed.js",
|
|
19
|
+
"standalone.html",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"SOURCE.md",
|
|
22
|
+
"THIRD_PARTY_NOTICES.md",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "node scripts/build.mjs",
|
|
27
|
+
"smoke": "node scripts/smoke.mjs",
|
|
28
|
+
"verify": "node scripts/verify.mjs",
|
|
29
|
+
"prepack": "npm run verify"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"playwright": "1.59.1"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"roxxie",
|
|
39
|
+
"browser",
|
|
40
|
+
"proxy",
|
|
41
|
+
"webrtc",
|
|
42
|
+
"embed"
|
|
43
|
+
]
|
|
44
|
+
}
|
package/runtime/index.html
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<link rel="icon" type="image/
|
|
5
|
+
<link rel="icon" type="image/png" sizes="any" href="https://cdn.jsdelivr.net/npm/roxxie-proxy-u-prod@0.2.2/dist/favicon.png" />
|
|
6
|
+
<meta name="theme-color" content="#222230" />
|
|
6
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
8
|
<title>Roxxie Proxy U</title>
|
|
8
9
|
|
|
9
10
|
<!--ssr-head-->
|
|
10
|
-
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/roxxie-proxy-u-prod@0.
|
|
11
|
-
<link rel="stylesheet" crossorigin href="https://cdn.jsdelivr.net/npm/roxxie-proxy-u-prod@0.
|
|
11
|
+
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/roxxie-proxy-u-prod@0.2.2/dist/assets/index-CttvagWK.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="https://cdn.jsdelivr.net/npm/roxxie-proxy-u-prod@0.2.2/dist/assets/index-EeRhLAof.css">
|
|
12
13
|
</head>
|
|
13
14
|
|
|
14
|
-
<body
|
|
15
|
+
<body>
|
|
15
16
|
<!--ssr-body-->
|
|
16
17
|
<div id="app"></div>
|
|
17
18
|
</body>
|
package/scripts/build.mjs
CHANGED
|
@@ -44,12 +44,14 @@ const thirdPartyRoot = join(packageRoot, "third_party");
|
|
|
44
44
|
const manifest = JSON.parse(
|
|
45
45
|
await readFile(join(packageRoot, "package.json"), "utf8")
|
|
46
46
|
);
|
|
47
|
+
const packageSpec = `${manifest.name}@${manifest.version}`;
|
|
47
48
|
|
|
48
49
|
const trackerUrl = process.env.ROXXIE_TRACKER_URL ?? "https://100.58.76.81/";
|
|
49
|
-
const
|
|
50
|
+
const expectedRuntimeBase = `/${packageSpec}/runtime/`;
|
|
51
|
+
const runtimeBase = process.env.ROXXIE_RUNTIME_BASE ?? expectedRuntimeBase;
|
|
50
52
|
const cdnBase =
|
|
51
53
|
process.env.ROXXIE_CDN_BASE ??
|
|
52
|
-
`https://cdn.jsdelivr.net/npm/${
|
|
54
|
+
`https://cdn.jsdelivr.net/npm/${packageSpec}/dist/`;
|
|
53
55
|
|
|
54
56
|
function validateConfiguration() {
|
|
55
57
|
const tracker = new URL(trackerUrl);
|
|
@@ -66,8 +68,10 @@ function validateConfiguration() {
|
|
|
66
68
|
"ROXXIE_TRACKER_URL must be the production origin https://100.58.76.81/"
|
|
67
69
|
);
|
|
68
70
|
}
|
|
69
|
-
if (
|
|
70
|
-
throw new Error(
|
|
71
|
+
if (runtimeBase !== expectedRuntimeBase) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`ROXXIE_RUNTIME_BASE must be the versioned unpkg path ${expectedRuntimeBase}`
|
|
74
|
+
);
|
|
71
75
|
}
|
|
72
76
|
if (!cdnBase.startsWith("https://") || !cdnBase.endsWith("/")) {
|
|
73
77
|
throw new Error("ROXXIE_CDN_BASE must be an HTTPS URL ending in /");
|
|
@@ -338,7 +342,9 @@ if (!buildingFromPackagedSource) {
|
|
|
338
342
|
}
|
|
339
343
|
console.log(`Built ${manifest.name}@${manifest.version}`);
|
|
340
344
|
console.log(`CDN assets: ${outputRoot}`);
|
|
341
|
-
console.log(
|
|
345
|
+
console.log(
|
|
346
|
+
`Same-origin runtime: https://unpkg.com/${packageSpec}/runtime/index.html (${runtimeRoot})`
|
|
347
|
+
);
|
|
342
348
|
console.log(
|
|
343
349
|
`Build source: ${buildingFromPackagedSource ? "packaged source/ workspace" : "repository checkout"}`
|
|
344
350
|
);
|
package/scripts/smoke.mjs
CHANGED
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
/* SPDX-License-Identifier: AGPL-3.0-only */
|
|
2
2
|
|
|
3
3
|
import assert from "node:assert/strict";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
4
5
|
import { createServer } from "node:http";
|
|
5
6
|
import { readFile } from "node:fs/promises";
|
|
6
|
-
import { dirname, extname, join, normalize, resolve } from "node:path";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { dirname, extname, join, normalize, resolve, sep } from "node:path";
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
9
|
import { chromium } from "playwright";
|
|
9
10
|
|
|
10
11
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
12
|
const runtimeRoot = join(packageRoot, "runtime");
|
|
12
13
|
const distRoot = join(packageRoot, "dist");
|
|
13
|
-
const
|
|
14
|
+
const standalonePath = join(packageRoot, "standalone.html");
|
|
15
|
+
const manifest = JSON.parse(
|
|
16
|
+
await readFile(join(packageRoot, "package.json"), "utf8")
|
|
17
|
+
);
|
|
18
|
+
const packageSpec = `${manifest.name}@${manifest.version}`;
|
|
19
|
+
const cdnRoot = `https://cdn.jsdelivr.net/npm/${packageSpec}/`;
|
|
20
|
+
const cdnPath = `/npm/${packageSpec}/`;
|
|
21
|
+
const cdnDistRoot = `${cdnRoot}dist/`;
|
|
22
|
+
const unpkgRuntimeRoot = `https://unpkg.com/${packageSpec}/runtime/`;
|
|
23
|
+
const unpkgRuntimePath = `/${packageSpec}/runtime/`;
|
|
24
|
+
const shellUrl = `${unpkgRuntimeRoot}index.html`;
|
|
25
|
+
const workerUrl = `${unpkgRuntimeRoot}localcontrollersw.js`;
|
|
26
|
+
const workerScope = `${unpkgRuntimeRoot}~/sj/`;
|
|
27
|
+
const embedBytes = await readFile(join(packageRoot, "embed.js"));
|
|
28
|
+
const embedIntegrity = `sha384-${createHash("sha384")
|
|
29
|
+
.update(embedBytes)
|
|
30
|
+
.digest("base64")}`;
|
|
14
31
|
|
|
15
32
|
const contentTypes = {
|
|
16
33
|
".bin": "application/octet-stream",
|
|
@@ -25,99 +42,273 @@ const contentTypes = {
|
|
|
25
42
|
|
|
26
43
|
function safePath(root, relative) {
|
|
27
44
|
const resolved = resolve(root, normalize(relative));
|
|
28
|
-
if (resolved !== root && !resolved.startsWith(root +
|
|
45
|
+
if (resolved !== root && !resolved.startsWith(root + sep)) {
|
|
29
46
|
throw new Error("Invalid test path");
|
|
30
47
|
}
|
|
31
48
|
return resolved;
|
|
32
49
|
}
|
|
33
50
|
|
|
34
|
-
async function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
51
|
+
async function fulfillFile(route, path) {
|
|
52
|
+
await route.fulfill({
|
|
53
|
+
body: await readFile(path),
|
|
54
|
+
contentType: contentTypes[extname(path)] ?? "application/octet-stream",
|
|
55
|
+
headers: {
|
|
56
|
+
"access-control-allow-origin": "*",
|
|
57
|
+
"cache-control": "public, max-age=31536000, immutable",
|
|
58
|
+
"x-content-type-options": "nosniff",
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function installRoutes(context, hits) {
|
|
64
|
+
await context.route(`${cdnRoot}**`, async (route) => {
|
|
65
|
+
const url = new URL(route.request().url());
|
|
66
|
+
assert.equal(url.origin, "https://cdn.jsdelivr.net");
|
|
67
|
+
assert(url.pathname.startsWith(cdnPath));
|
|
68
|
+
const relative = url.pathname.slice(cdnPath.length);
|
|
69
|
+
const path =
|
|
70
|
+
relative === "embed.js"
|
|
71
|
+
? join(packageRoot, "embed.js")
|
|
72
|
+
: relative.startsWith("dist/")
|
|
73
|
+
? safePath(distRoot, relative.slice("dist/".length))
|
|
74
|
+
: undefined;
|
|
75
|
+
assert(path, `Unexpected jsDelivr package request: ${url.href}`);
|
|
76
|
+
hits.add(url.href);
|
|
77
|
+
await fulfillFile(route, path);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await context.route(`${unpkgRuntimeRoot}**`, async (route) => {
|
|
81
|
+
const url = new URL(route.request().url());
|
|
82
|
+
assert.equal(url.origin, "https://unpkg.com");
|
|
83
|
+
assert(url.pathname.startsWith(unpkgRuntimePath));
|
|
84
|
+
const relative = url.pathname.slice(unpkgRuntimePath.length);
|
|
85
|
+
assert(relative.length > 0, `Unexpected unpkg request: ${url.href}`);
|
|
86
|
+
hits.add(url.href);
|
|
87
|
+
await fulfillFile(route, safePath(runtimeRoot, relative));
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function registerRuntimeWorker(frame, scriptURL, scope) {
|
|
92
|
+
return frame.evaluate(
|
|
93
|
+
async ({ scriptURL, scope }) => {
|
|
94
|
+
if (!("serviceWorker" in navigator)) {
|
|
95
|
+
throw new Error("Service workers are unavailable in the unpkg shell");
|
|
96
|
+
}
|
|
97
|
+
const registration = await navigator.serviceWorker.register(scriptURL, {
|
|
98
|
+
scope,
|
|
99
|
+
});
|
|
100
|
+
let worker =
|
|
101
|
+
registration.installing ?? registration.waiting ?? registration.active;
|
|
102
|
+
if (!worker) throw new Error("Service worker registration has no worker");
|
|
103
|
+
if (worker.state !== "activated") {
|
|
104
|
+
await new Promise((resolveActivation, rejectActivation) => {
|
|
105
|
+
const timeout = setTimeout(
|
|
106
|
+
() =>
|
|
107
|
+
rejectActivation(
|
|
108
|
+
new Error("Service worker activation timed out")
|
|
109
|
+
),
|
|
110
|
+
10_000
|
|
111
|
+
);
|
|
112
|
+
worker.addEventListener("statechange", () => {
|
|
113
|
+
if (worker?.state !== "activated") return;
|
|
114
|
+
clearTimeout(timeout);
|
|
115
|
+
resolveActivation();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
worker = registration.active ?? worker;
|
|
120
|
+
return {
|
|
121
|
+
scriptURL: worker.scriptURL,
|
|
122
|
+
scope: registration.scope,
|
|
123
|
+
secureContext: globalThis.isSecureContext,
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
{ scriptURL, scope }
|
|
127
|
+
);
|
|
44
128
|
}
|
|
45
129
|
|
|
46
|
-
const
|
|
130
|
+
const standalone = await readFile(standalonePath);
|
|
131
|
+
const hostServer = createServer(async (request, response) => {
|
|
47
132
|
const path = new URL(request.url ?? "/", "http://localhost").pathname;
|
|
48
|
-
if (path === "/
|
|
49
|
-
await sendFile(response, join(runtimeRoot, "index.html"));
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (path.startsWith("/browser/")) {
|
|
53
|
-
await sendFile(response, safePath(runtimeRoot, path.slice(9)));
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (path === "/embed.js") {
|
|
57
|
-
await sendFile(response, join(packageRoot, "embed.js"));
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
if (path === "/embed-test/") {
|
|
133
|
+
if (path === "/" || path === "/standalone.html") {
|
|
61
134
|
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
62
|
-
response.end(
|
|
63
|
-
<div id="target" style="width:800px;height:600px"></div>
|
|
64
|
-
<script src="/embed.js" data-roxxie-target="#target"
|
|
65
|
-
data-roxxie-src="/browser/"></script>`);
|
|
135
|
+
response.end(standalone);
|
|
66
136
|
return;
|
|
67
137
|
}
|
|
138
|
+
if (path.startsWith(unpkgRuntimePath)) {
|
|
139
|
+
const relative = path.slice(unpkgRuntimePath.length);
|
|
140
|
+
if (relative.length === 0) {
|
|
141
|
+
response.writeHead(308, { location: `${path}index.html` }).end();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const file = safePath(runtimeRoot, relative);
|
|
146
|
+
response.writeHead(200, {
|
|
147
|
+
"content-type":
|
|
148
|
+
contentTypes[extname(file)] ?? "application/octet-stream",
|
|
149
|
+
"cache-control": "no-cache",
|
|
150
|
+
"x-content-type-options": "nosniff",
|
|
151
|
+
});
|
|
152
|
+
response.end(await readFile(file));
|
|
153
|
+
return;
|
|
154
|
+
} catch {
|
|
155
|
+
response.writeHead(404).end("Not found");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
68
159
|
response.writeHead(404).end("Not found");
|
|
69
160
|
});
|
|
70
|
-
|
|
71
161
|
await new Promise((resolveListen) =>
|
|
72
|
-
|
|
162
|
+
hostServer.listen(0, "127.0.0.1", resolveListen)
|
|
73
163
|
);
|
|
74
|
-
const
|
|
75
|
-
assert(
|
|
76
|
-
const
|
|
164
|
+
const hostAddress = hostServer.address();
|
|
165
|
+
assert(hostAddress && typeof hostAddress === "object");
|
|
166
|
+
const hostOrigin = `http://127.0.0.1:${hostAddress.port}`;
|
|
77
167
|
|
|
78
168
|
const browser = await chromium.launch({ headless: true });
|
|
79
169
|
try {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
170
|
+
for (const [label, hostUrl] of [
|
|
171
|
+
["file", pathToFileURL(standalonePath).href],
|
|
172
|
+
["loopback HTTP", `${hostOrigin}/`],
|
|
173
|
+
]) {
|
|
174
|
+
const context = await browser.newContext({ serviceWorkers: "allow" });
|
|
175
|
+
const hits = new Set();
|
|
176
|
+
await installRoutes(context, hits);
|
|
177
|
+
const page = await context.newPage();
|
|
178
|
+
const pageErrors = [];
|
|
179
|
+
const failedRequests = [];
|
|
180
|
+
page.on("pageerror", (error) => pageErrors.push(error.message));
|
|
181
|
+
page.on("requestfailed", (request) =>
|
|
182
|
+
failedRequests.push(
|
|
183
|
+
`${request.url()}: ${request.failure()?.errorText ?? "failed"}`
|
|
184
|
+
)
|
|
185
|
+
);
|
|
95
186
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
await page
|
|
104
|
-
|
|
105
|
-
|
|
187
|
+
await page.goto(hostUrl);
|
|
188
|
+
const embedded = page.locator("iframe[data-roxxie-proxy-u]");
|
|
189
|
+
await embedded.waitFor();
|
|
190
|
+
assert.equal(await embedded.getAttribute("src"), shellUrl);
|
|
191
|
+
const launcher = page.locator(`script[src="${cdnRoot}embed.js"]`);
|
|
192
|
+
assert.equal(await launcher.getAttribute("integrity"), embedIntegrity);
|
|
193
|
+
assert.equal(await launcher.getAttribute("crossorigin"), "anonymous");
|
|
194
|
+
await page
|
|
195
|
+
.frameLocator("iframe[data-roxxie-proxy-u]")
|
|
196
|
+
.getByRole("button", { name: "Start" })
|
|
197
|
+
.waitFor();
|
|
198
|
+
const runtimeFrame = page
|
|
199
|
+
.frames()
|
|
200
|
+
.find((frame) => frame.url() === shellUrl);
|
|
201
|
+
assert(runtimeFrame, "The unpkg runtime frame was not created");
|
|
202
|
+
const productMark = runtimeFrame.locator("img.product-mark");
|
|
203
|
+
await productMark.waitFor();
|
|
204
|
+
assert.equal(
|
|
205
|
+
await productMark.getAttribute("src"),
|
|
206
|
+
`${cdnDistRoot}favicon.png`
|
|
207
|
+
);
|
|
208
|
+
const brandedStyles = await runtimeFrame.evaluate(() => {
|
|
209
|
+
const startScreen = document.querySelector(".start-screen");
|
|
210
|
+
const panel = document.querySelector(".start-panel");
|
|
211
|
+
const action = document.querySelector(".start-button");
|
|
212
|
+
if (!(startScreen instanceof HTMLElement)) {
|
|
213
|
+
throw new Error("Start screen is missing");
|
|
214
|
+
}
|
|
215
|
+
if (!(panel instanceof HTMLElement)) {
|
|
216
|
+
throw new Error("Start panel is missing");
|
|
217
|
+
}
|
|
218
|
+
if (!(action instanceof HTMLElement)) {
|
|
219
|
+
throw new Error("Start action is missing");
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
background: getComputedStyle(startScreen).backgroundColor,
|
|
223
|
+
panel: getComputedStyle(panel).backgroundColor,
|
|
224
|
+
action: getComputedStyle(action).backgroundColor,
|
|
225
|
+
panelShadow: getComputedStyle(panel).boxShadow,
|
|
226
|
+
actionShadow: getComputedStyle(action).boxShadow,
|
|
227
|
+
};
|
|
228
|
+
});
|
|
229
|
+
assert.deepEqual(brandedStyles, {
|
|
230
|
+
background: "rgb(17, 17, 27)",
|
|
231
|
+
panel: "rgb(34, 34, 48)",
|
|
232
|
+
action: "rgb(235, 69, 139)",
|
|
233
|
+
panelShadow: "none",
|
|
234
|
+
actionShadow: "none",
|
|
235
|
+
});
|
|
106
236
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
237
|
+
const network = await runtimeFrame.evaluate(async () => {
|
|
238
|
+
const response = await fetch(
|
|
239
|
+
new URL("roxxie-network.json", location.href),
|
|
240
|
+
{
|
|
241
|
+
cache: "no-store",
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
if (!response.ok)
|
|
245
|
+
throw new Error(`Network document returned ${response.status}`);
|
|
246
|
+
return response.json();
|
|
247
|
+
});
|
|
248
|
+
assert.equal(network?.tracker?.url, "https://100.58.76.81/");
|
|
116
249
|
|
|
117
|
-
|
|
250
|
+
assert(hits.has(`${cdnRoot}embed.js`));
|
|
251
|
+
assert(hits.has(shellUrl));
|
|
252
|
+
assert(hits.has(`${unpkgRuntimeRoot}roxxie-network.json`));
|
|
253
|
+
assert([...hits].some((url) => url.startsWith(cdnDistRoot)));
|
|
254
|
+
assert.deepEqual(pageErrors, []);
|
|
255
|
+
assert.deepEqual(failedRequests, []);
|
|
256
|
+
await context.close();
|
|
257
|
+
console.log(`${label} standalone embed passed`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Service-worker script requests intentionally bypass Playwright routing.
|
|
261
|
+
// Exercise the same versioned pathname and scope on a real loopback origin;
|
|
262
|
+
// the embed checks above independently pin their production unpkg origin.
|
|
263
|
+
const runtimeContext = await browser.newContext({ serviceWorkers: "allow" });
|
|
264
|
+
const runtimeHits = new Set();
|
|
265
|
+
await installRoutes(runtimeContext, runtimeHits);
|
|
266
|
+
const runtimePage = await runtimeContext.newPage();
|
|
267
|
+
const localRuntimeRoot = `${hostOrigin}${unpkgRuntimePath}`;
|
|
268
|
+
await runtimePage.goto(`${localRuntimeRoot}index.html`);
|
|
269
|
+
await runtimePage.getByRole("button", { name: "Start" }).waitFor();
|
|
270
|
+
const localNetwork = await runtimePage.evaluate(async () => {
|
|
271
|
+
const response = await fetch(
|
|
272
|
+
new URL("roxxie-network.json", location.href),
|
|
273
|
+
{ cache: "no-store" }
|
|
274
|
+
);
|
|
275
|
+
if (!response.ok)
|
|
276
|
+
throw new Error(`Network document returned ${response.status}`);
|
|
277
|
+
return response.json();
|
|
278
|
+
});
|
|
279
|
+
assert.equal(localNetwork?.tracker?.url, "https://100.58.76.81/");
|
|
280
|
+
|
|
281
|
+
const localWorkerUrl = `${localRuntimeRoot}localcontrollersw.js`;
|
|
282
|
+
const localWorkerScope = `${localRuntimeRoot}~/sj/`;
|
|
283
|
+
const localRegistration = await registerRuntimeWorker(
|
|
284
|
+
runtimePage.mainFrame(),
|
|
285
|
+
localWorkerUrl,
|
|
286
|
+
localWorkerScope
|
|
287
|
+
);
|
|
288
|
+
assert.equal(localRegistration.secureContext, true);
|
|
289
|
+
assert.equal(localRegistration.scriptURL, localWorkerUrl);
|
|
290
|
+
assert.equal(localRegistration.scope, localWorkerScope);
|
|
291
|
+
assert.equal(
|
|
292
|
+
new URL(localRegistration.scriptURL).pathname,
|
|
293
|
+
new URL(workerUrl).pathname
|
|
294
|
+
);
|
|
295
|
+
assert.equal(
|
|
296
|
+
new URL(localRegistration.scope).pathname,
|
|
297
|
+
new URL(workerScope).pathname
|
|
298
|
+
);
|
|
299
|
+
await runtimePage.evaluate(async () => {
|
|
300
|
+
for (const registration of await navigator.serviceWorker.getRegistrations()) {
|
|
301
|
+
await registration.unregister();
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
await runtimeContext.close();
|
|
305
|
+
console.log("Versioned same-origin runtime service worker passed");
|
|
306
|
+
console.log(
|
|
307
|
+
"Pinned unpkg runtime, service worker, and tracker config passed"
|
|
308
|
+
);
|
|
118
309
|
} finally {
|
|
119
310
|
await browser.close();
|
|
120
311
|
await new Promise((resolveClose, rejectClose) =>
|
|
121
|
-
|
|
312
|
+
hostServer.close((error) => (error ? rejectClose(error) : resolveClose()))
|
|
122
313
|
);
|
|
123
314
|
}
|