23 | );
24 | }
25 |
26 | export default App;
27 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 | on: [push]
3 |
4 | jobs:
5 | deploy:
6 | runs-on: ubuntu-latest
7 | name: Test
8 | steps:
9 | - name: Checkout
10 | uses: actions/checkout@v2
11 | - name: Setup Node
12 | uses: actions/setup-node@v1
13 | - name: Setup Playwright
14 | uses: microsoft/playwright-github-action@v1
15 | - name: Install
16 | run: npm ci
17 | - name: Lint
18 | run: npm run lint
19 | - name: Test
20 | run: npm run test
21 | env:
22 | CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
23 | CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
24 | - name: Upload Coverage Report
25 | uses: codecov/codecov-action@v1.0.6
26 |
--------------------------------------------------------------------------------
/packages/client/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/worker/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const webpack = require("webpack");
3 |
4 | module.exports = {
5 | entry: path.join(__dirname, "src", "index.ts"),
6 | output: {
7 | filename: `worker.js`,
8 | path: path.join(__dirname, "build"),
9 | },
10 | target: "webworker",
11 | resolve: {
12 | extensions: [".ts", ".tsx", ".js"],
13 | alias: {
14 | fs: path.resolve(__dirname, "./null.js"),
15 | },
16 | },
17 | mode: "production",
18 | optimization: {
19 | usedExports: true,
20 | },
21 | module: {
22 | rules: [
23 | {
24 | test: /\.tsx?$/,
25 | loader: "ts-loader",
26 | options: {
27 | transpileOnly: true,
28 | },
29 | },
30 | ],
31 | },
32 | plugins: [new webpack.EnvironmentPlugin({ CLOUDFLARED_TUNNEL: "" })],
33 | };
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Greg Brimble
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | require("cross-fetch/polyfill");
2 | const { spawn } = require("child_process");
3 | const cwd = process.cwd();
4 |
5 | const SLEEP_DURATION = 2000;
6 | const hostNameRegex = /userHostname=\"(.*)\"/g;
7 |
8 | const startServer = (hostName) => {
9 | process.env.CLOUDFLARED_TUNNEL = "true";
10 | spawn("npm", ["run", "start:worker", "--", "--host", hostName], {
11 | cwd,
12 | stdio: "inherit",
13 | });
14 | };
15 |
16 | const findTunnelHostname = async () => {
17 | console.log("Waiting for tunnel to initiate...");
18 | try {
19 | const resp = await fetch("http://localhost:8081/metrics");
20 | const data = await resp.text();
21 | const matches = Array.from(data.matchAll(hostNameRegex));
22 | const hostName = matches[0][1];
23 | console.log(`Tunnel initiated: ${hostName}`);
24 | startServer(hostName);
25 | } catch {
26 | setTimeout(findTunnelHostname, SLEEP_DURATION);
27 | }
28 | };
29 |
30 | const startTunnel = () => {
31 | console.log("Starting tunnel...");
32 | spawn("npm", ["run", "start:tunnel"], {
33 | cwd,
34 | });
35 | };
36 |
37 | startTunnel();
38 | findTunnelHostname();
39 |
--------------------------------------------------------------------------------
/packages/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "5.11.8",
7 | "@testing-library/react": "11.2.3",
8 | "@testing-library/user-event": "12.6.0",
9 | "@types/jest": "26.0.20",
10 | "@types/node": "14.14.20",
11 | "@types/react": "17.0.0",
12 | "@types/react-dom": "17.0.0",
13 | "react": "17.0.1",
14 | "react-dom": "17.0.1",
15 | "react-scripts": "4.0.1",
16 | "typescript": "4.1.3"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test",
22 | "test:ci": "npm run test -- --coverage",
23 | "eject": "react-scripts eject",
24 | "lint": "prettier -c .",
25 | "lint:fix": "npm run lint -- --write"
26 | },
27 | "eslintConfig": {
28 | "extends": "react-app"
29 | },
30 | "browserslist": {
31 | "production": [
32 | ">0.2%",
33 | "not dead",
34 | "not op_mini all"
35 | ],
36 | "development": [
37 | "last 1 chrome version",
38 | "last 1 firefox version",
39 | "last 1 safari version"
40 | ]
41 | },
42 | "devDependencies": {
43 | "prettier": "2.2.1"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/worker/src/index.ts:
--------------------------------------------------------------------------------
1 | import {} from "@cloudflare/workers-types";
2 | import {
3 | getAssetFromKV,
4 | serveSinglePageApp,
5 | } from "@cloudflare/kv-asset-handler";
6 | import { handleRequest as server } from "../../server/src";
7 | import { handleError } from "./handleError";
8 | import { internalServerError, notFound } from "./pages";
9 |
10 | const assetOptions = {
11 | mapRequestToAsset: serveSinglePageApp,
12 | };
13 |
14 | const handleProductionAssetRequest = async (
15 | event: FetchEvent
16 | ): Promise