wrangler 0.0.13 → 0.0.17
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/bin/wrangler.js +2 -2
- package/package.json +20 -11
- package/pages/functions/buildWorker.ts +1 -1
- package/pages/functions/filepath-routing.test.ts +112 -28
- package/pages/functions/filepath-routing.ts +44 -51
- package/pages/functions/routes.ts +11 -18
- package/pages/functions/template-worker.ts +3 -9
- package/src/__tests__/dev.test.tsx +42 -5
- package/src/__tests__/guess-worker-format.test.ts +66 -0
- package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
- package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
- package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
- package/src/__tests__/helpers/mock-account-id.ts +30 -0
- package/src/__tests__/helpers/mock-bin.ts +36 -0
- package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +43 -9
- package/src/__tests__/helpers/mock-console.ts +62 -0
- package/src/__tests__/{mock-dialogs.ts → helpers/mock-dialogs.ts} +1 -1
- package/src/__tests__/helpers/mock-kv.ts +40 -0
- package/src/__tests__/helpers/mock-user.ts +27 -0
- package/src/__tests__/helpers/mock-web-socket.ts +37 -0
- package/src/__tests__/{run-in-tmp.ts → helpers/run-in-tmp.ts} +1 -1
- package/src/__tests__/helpers/run-wrangler.ts +16 -0
- package/src/__tests__/helpers/write-wrangler-toml.ts +20 -0
- package/src/__tests__/index.test.ts +418 -71
- package/src/__tests__/jest.setup.ts +30 -2
- package/src/__tests__/kv.test.ts +147 -252
- package/src/__tests__/logout.test.ts +50 -0
- package/src/__tests__/package-manager.test.ts +206 -0
- package/src/__tests__/publish.test.ts +1136 -291
- package/src/__tests__/r2.test.ts +206 -0
- package/src/__tests__/secret.test.ts +210 -0
- package/src/__tests__/sentry.test.ts +146 -0
- package/src/__tests__/tail.test.ts +246 -0
- package/src/__tests__/whoami.test.tsx +6 -47
- package/src/api/form_data.ts +75 -25
- package/src/api/preview.ts +2 -2
- package/src/api/worker.ts +34 -15
- package/src/bundle.ts +127 -0
- package/src/cfetch/index.ts +7 -15
- package/src/cfetch/internal.ts +41 -6
- package/src/cli.ts +10 -0
- package/src/config.ts +125 -95
- package/src/dev.tsx +300 -193
- package/src/dialogs.tsx +2 -2
- package/src/guess-worker-format.ts +68 -0
- package/src/index.tsx +578 -192
- package/src/inspect.ts +29 -10
- package/src/kv.tsx +23 -17
- package/src/module-collection.ts +32 -12
- package/src/open-in-browser.ts +13 -0
- package/src/package-manager.ts +120 -0
- package/src/pages.tsx +28 -23
- package/src/paths.ts +26 -0
- package/src/proxy.ts +88 -14
- package/src/publish.ts +260 -297
- package/src/r2.ts +50 -0
- package/src/reporting.ts +115 -0
- package/src/sites.tsx +28 -27
- package/src/tail.tsx +178 -9
- package/src/user.tsx +58 -44
- package/templates/new-worker.js +15 -0
- package/templates/new-worker.ts +15 -0
- package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
- package/wrangler-dist/cli.js +124315 -104677
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/mock-console.ts +0 -34
- package/src/__tests__/run-wrangler.ts +0 -8
package/src/reporting.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { appendFile, readFile } from "fs/promises";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "path/posix";
|
|
5
|
+
import TOML from "@iarna/toml";
|
|
6
|
+
import { RewriteFrames } from "@sentry/integrations";
|
|
7
|
+
import {
|
|
8
|
+
captureException,
|
|
9
|
+
getCurrentHub,
|
|
10
|
+
startTransaction,
|
|
11
|
+
init,
|
|
12
|
+
Integrations,
|
|
13
|
+
setContext,
|
|
14
|
+
} from "@sentry/node";
|
|
15
|
+
import { execaSync } from "execa";
|
|
16
|
+
import prompts from "prompts";
|
|
17
|
+
import * as pkj from "../package.json";
|
|
18
|
+
|
|
19
|
+
export function initReporting() {
|
|
20
|
+
init({
|
|
21
|
+
release: `${pkj.name}@${pkj.version}`,
|
|
22
|
+
initialScope: {
|
|
23
|
+
tags: { [pkj.name]: pkj.version },
|
|
24
|
+
},
|
|
25
|
+
dsn: "https://5089b76bf8a64a9c949bf5c2b5e8003c@o51786.ingest.sentry.io/6190959",
|
|
26
|
+
tracesSampleRate: 1.0,
|
|
27
|
+
integrations: [
|
|
28
|
+
new RewriteFrames({
|
|
29
|
+
root: "",
|
|
30
|
+
prefix: "/",
|
|
31
|
+
}),
|
|
32
|
+
new Integrations.Http({ tracing: true }),
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
setContext("System Information", {
|
|
37
|
+
OS: process.platform,
|
|
38
|
+
node: process.version,
|
|
39
|
+
npm: execaSync("npm", ["--version"]).stdout,
|
|
40
|
+
wrangler: pkj.version,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function appendReportingDecision(userInput: "true" | "false") {
|
|
45
|
+
const homePath = path.join(os.homedir(), ".wrangler/config/");
|
|
46
|
+
fs.mkdirSync(homePath, { recursive: true });
|
|
47
|
+
await appendFile(
|
|
48
|
+
path.join(homePath, "reporting.toml"),
|
|
49
|
+
`error_tracking_opt = ${userInput} # Sentry \nerror_tracking_opt_date = ${new Date().toISOString()} # Sentry Date Decision \n`,
|
|
50
|
+
{ encoding: "utf-8" }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function exceptionTransaction(error: Error, origin = "") {
|
|
55
|
+
const transaction = startTransaction({
|
|
56
|
+
op: origin,
|
|
57
|
+
name: error.name,
|
|
58
|
+
});
|
|
59
|
+
captureException(error);
|
|
60
|
+
transaction.finish();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function reportError(err: Error, origin = "") {
|
|
64
|
+
if (!process.stdout.isTTY) return await appendReportingDecision("false");
|
|
65
|
+
|
|
66
|
+
const errorTrackingOpt = await reportingPermission();
|
|
67
|
+
|
|
68
|
+
if (errorTrackingOpt === undefined) {
|
|
69
|
+
const userInput = await prompts.prompt({
|
|
70
|
+
type: "select",
|
|
71
|
+
name: "sentryDecision",
|
|
72
|
+
message: "Would you like to submit a report when an error occurs?",
|
|
73
|
+
choices: [
|
|
74
|
+
{ title: "Always", value: true },
|
|
75
|
+
{ title: "Yes", value: "once" },
|
|
76
|
+
{ title: "No", value: false },
|
|
77
|
+
],
|
|
78
|
+
initial: 2,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (userInput.sentryDecision === "once") {
|
|
82
|
+
exceptionTransaction(err, origin);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
userInput.sentryDecision
|
|
87
|
+
? await appendReportingDecision("true")
|
|
88
|
+
: await appendReportingDecision("false");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!errorTrackingOpt) {
|
|
92
|
+
const sentryClient = getCurrentHub().getClient();
|
|
93
|
+
if (sentryClient !== undefined) sentryClient.getOptions().enabled = false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
exceptionTransaction(err, origin);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function reportingPermission() {
|
|
100
|
+
if (
|
|
101
|
+
!fs.existsSync(path.join(os.homedir(), ".wrangler/config/reporting.toml"))
|
|
102
|
+
)
|
|
103
|
+
return undefined;
|
|
104
|
+
|
|
105
|
+
const reportingTOML = TOML.parse(
|
|
106
|
+
await readFile(path.join(os.homedir(), ".wrangler/config/reporting.toml"), {
|
|
107
|
+
encoding: "utf-8",
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
const { error_tracking_opt } = reportingTOML as {
|
|
111
|
+
error_tracking_opt: string | undefined;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return error_tracking_opt;
|
|
115
|
+
}
|
package/src/sites.tsx
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
1
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
3
|
import ignore from "ignore";
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
4
|
+
import xxhash from "xxhash-wasm";
|
|
5
|
+
import {
|
|
6
|
+
createNamespace,
|
|
7
|
+
listNamespaceKeys,
|
|
8
|
+
listNamespaces,
|
|
9
|
+
putBulkKeyValue,
|
|
10
|
+
} from "./kv";
|
|
6
11
|
import type { Config } from "./config";
|
|
7
|
-
import {
|
|
12
|
+
import type { XXHashAPI } from "xxhash-wasm";
|
|
8
13
|
|
|
9
14
|
/** Paths to always ignore. */
|
|
10
15
|
const ALWAYS_IGNORE = ["node_modules"];
|
|
@@ -42,11 +47,8 @@ async function* getFilesInFolder(dirPath: string): AsyncIterable<string> {
|
|
|
42
47
|
* the most important thing here is to detect changes of a single file to invalidate the cache and
|
|
43
48
|
* it's impossible to serve two different files with the same name
|
|
44
49
|
*/
|
|
45
|
-
function hashFileContent(content: string): string {
|
|
46
|
-
|
|
47
|
-
hasher.update(Buffer.from(content));
|
|
48
|
-
const hash = hasher.digest();
|
|
49
|
-
return hash.toString("hex").substring(0, 10);
|
|
50
|
+
function hashFileContent(hasher: XXHashAPI, content: string): string {
|
|
51
|
+
return hasher.h64ToString(content).substring(0, 10);
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/**
|
|
@@ -55,11 +57,15 @@ function hashFileContent(content: string): string {
|
|
|
55
57
|
* The key will change if the file path or content of the asset changes.
|
|
56
58
|
* The algorithm used here matches that of Wrangler 1.
|
|
57
59
|
*/
|
|
58
|
-
function hashAsset(
|
|
60
|
+
function hashAsset(
|
|
61
|
+
hasher: XXHashAPI,
|
|
62
|
+
filePath: string,
|
|
63
|
+
content: string
|
|
64
|
+
): string {
|
|
59
65
|
const extName = path.extname(filePath) || "";
|
|
60
66
|
const baseName = path.basename(filePath, extName);
|
|
61
67
|
const directory = path.dirname(filePath);
|
|
62
|
-
const hash = hashFileContent(content);
|
|
68
|
+
const hash = hashFileContent(hasher, content);
|
|
63
69
|
return urlSafe(path.join(directory, `${baseName}.${hash}${extName}`));
|
|
64
70
|
}
|
|
65
71
|
|
|
@@ -76,21 +82,12 @@ async function createKVNamespaceIfNotAlreadyExisting(
|
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
// else we make the namespace
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
`/accounts/${accountId}/storage/kv/namespaces`,
|
|
82
|
-
{
|
|
83
|
-
method: "POST",
|
|
84
|
-
headers: {
|
|
85
|
-
"Content-Type": "application/json",
|
|
86
|
-
},
|
|
87
|
-
body: JSON.stringify({ title }),
|
|
88
|
-
}
|
|
89
|
-
);
|
|
85
|
+
const id = await createNamespace(accountId, title);
|
|
86
|
+
console.log(`🌀 Created namespace for Workers Site "${title}"`);
|
|
90
87
|
|
|
91
88
|
return {
|
|
92
89
|
created: true,
|
|
93
|
-
id
|
|
90
|
+
id,
|
|
94
91
|
};
|
|
95
92
|
}
|
|
96
93
|
|
|
@@ -111,7 +108,7 @@ export async function syncAssets(
|
|
|
111
108
|
scriptName: string,
|
|
112
109
|
siteAssets: AssetPaths | undefined,
|
|
113
110
|
preview: boolean,
|
|
114
|
-
|
|
111
|
+
env: string | undefined
|
|
115
112
|
): Promise<{
|
|
116
113
|
manifest: { [filePath: string]: string } | undefined;
|
|
117
114
|
namespace: string | undefined;
|
|
@@ -120,7 +117,9 @@ export async function syncAssets(
|
|
|
120
117
|
return { manifest: undefined, namespace: undefined };
|
|
121
118
|
}
|
|
122
119
|
|
|
123
|
-
const title = `__${scriptName}
|
|
120
|
+
const title = `__${scriptName}${env ? `-${env}` : ""}-workers_sites_assets${
|
|
121
|
+
preview ? "_preview" : ""
|
|
122
|
+
}`;
|
|
124
123
|
const { id: namespace } = await createKVNamespaceIfNotAlreadyExisting(
|
|
125
124
|
title,
|
|
126
125
|
accountId
|
|
@@ -130,7 +129,7 @@ export async function syncAssets(
|
|
|
130
129
|
const result = await listNamespaceKeys(accountId, namespace);
|
|
131
130
|
const keys = new Set(result.map((x) => x.name));
|
|
132
131
|
|
|
133
|
-
const manifest = {};
|
|
132
|
+
const manifest: Record<string, string> = {};
|
|
134
133
|
const upload: {
|
|
135
134
|
key: string;
|
|
136
135
|
value: string;
|
|
@@ -139,6 +138,8 @@ export async function syncAssets(
|
|
|
139
138
|
|
|
140
139
|
const include = createPatternMatcher(siteAssets.includePatterns, false);
|
|
141
140
|
const exclude = createPatternMatcher(siteAssets.excludePatterns, true);
|
|
141
|
+
const hasher = await xxhash();
|
|
142
|
+
|
|
142
143
|
// TODO: this can be more efficient by parallelising
|
|
143
144
|
for await (const file of getFilesInFolder(siteAssets.baseDirectory)) {
|
|
144
145
|
if (!include(file)) {
|
|
@@ -152,7 +153,7 @@ export async function syncAssets(
|
|
|
152
153
|
console.log(`reading ${file}...`);
|
|
153
154
|
const content = await readFile(file, "base64");
|
|
154
155
|
|
|
155
|
-
const assetKey = hashAsset(file, content);
|
|
156
|
+
const assetKey = await hashAsset(hasher, file, content);
|
|
156
157
|
validateAssetKey(assetKey);
|
|
157
158
|
|
|
158
159
|
// now put each of the files into kv
|
package/src/tail.tsx
CHANGED
|
@@ -8,6 +8,58 @@ export type TailApiResponse = {
|
|
|
8
8
|
expires_at: Date;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
export type TailCLIFilters = {
|
|
12
|
+
status?: Array<"ok" | "error" | "canceled">;
|
|
13
|
+
header?: string;
|
|
14
|
+
method?: string[];
|
|
15
|
+
search?: string;
|
|
16
|
+
samplingRate?: number;
|
|
17
|
+
clientIp?: string[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// due to the trace worker being built around wrangler 1 and
|
|
21
|
+
// some other stuff, the filters we send to the API are slightly
|
|
22
|
+
// different than the ones we read from the CLI
|
|
23
|
+
type SamplingRateFilter = {
|
|
24
|
+
sampling_rate: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type OutcomeFilter = {
|
|
28
|
+
outcome: string[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type MethodFilter = {
|
|
32
|
+
method: string[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type HeaderFilter = {
|
|
36
|
+
header: {
|
|
37
|
+
key: string;
|
|
38
|
+
query?: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type ClientIpFilter = {
|
|
43
|
+
client_ip: string[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type QueryFilter = {
|
|
47
|
+
query: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type ApiFilter =
|
|
51
|
+
| SamplingRateFilter
|
|
52
|
+
| OutcomeFilter
|
|
53
|
+
| MethodFilter
|
|
54
|
+
| HeaderFilter
|
|
55
|
+
| ClientIpFilter
|
|
56
|
+
| QueryFilter;
|
|
57
|
+
|
|
58
|
+
type ApiFilterMessage = {
|
|
59
|
+
filters: ApiFilter[];
|
|
60
|
+
debug: boolean;
|
|
61
|
+
};
|
|
62
|
+
|
|
11
63
|
function makeCreateTailUrl(accountId: string, workerName: string): string {
|
|
12
64
|
return `/accounts/${accountId}/workers/scripts/${workerName}/tails`;
|
|
13
65
|
}
|
|
@@ -35,7 +87,7 @@ async function createTailButDontConnect(
|
|
|
35
87
|
export async function createTail(
|
|
36
88
|
accountId: string,
|
|
37
89
|
workerName: string,
|
|
38
|
-
|
|
90
|
+
filters: ApiFilter[]
|
|
39
91
|
): Promise<{
|
|
40
92
|
tail: WebSocket;
|
|
41
93
|
expiration: Date;
|
|
@@ -60,14 +112,131 @@ export async function createTail(
|
|
|
60
112
|
},
|
|
61
113
|
});
|
|
62
114
|
|
|
63
|
-
//
|
|
115
|
+
// check if there's any filters to send
|
|
116
|
+
if (filters.length !== 0) {
|
|
117
|
+
const message: ApiFilterMessage = {
|
|
118
|
+
filters,
|
|
119
|
+
// if debug is set to true, then all logs will be sent through.
|
|
120
|
+
// logs that _would_ have been blocked will result with a message
|
|
121
|
+
// telling you what filter would have rejected it
|
|
122
|
+
debug: false,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
tail.on("open", function () {
|
|
126
|
+
tail.send(
|
|
127
|
+
JSON.stringify(message),
|
|
128
|
+
{ binary: false, compress: false, mask: false, fin: true },
|
|
129
|
+
(err) => {
|
|
130
|
+
if (err) {
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
64
138
|
return { tail, expiration, deleteTail };
|
|
65
139
|
}
|
|
66
140
|
|
|
67
|
-
export
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
141
|
+
export function translateCliFiltersToApiFilters(
|
|
142
|
+
cliFilters: TailCLIFilters
|
|
143
|
+
): ApiFilter[] {
|
|
144
|
+
const apiFilters: ApiFilter[] = [];
|
|
145
|
+
|
|
146
|
+
if (cliFilters.samplingRate) {
|
|
147
|
+
apiFilters.push(parseSamplingRate(cliFilters.samplingRate));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (cliFilters.status) {
|
|
151
|
+
apiFilters.push(parseOutcome(cliFilters.status));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (cliFilters.method) {
|
|
155
|
+
apiFilters.push(parseMethod(cliFilters.method));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (cliFilters.header) {
|
|
159
|
+
apiFilters.push(parseHeader(cliFilters.header));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (cliFilters.clientIp) {
|
|
163
|
+
apiFilters.push(parseClientIp(cliFilters.clientIp));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (cliFilters.search) {
|
|
167
|
+
apiFilters.push(parseQuery(cliFilters.search));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return apiFilters;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function parseSamplingRate(sampling_rate: number): SamplingRateFilter {
|
|
174
|
+
if (sampling_rate <= 0 || sampling_rate >= 1) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
"A sampling rate must be between 0 and 1 in order to have any effect.\nFor example, a sampling rate of 0.25 means 25% of events will be logged."
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { sampling_rate };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function parseOutcome(
|
|
184
|
+
statuses: Array<"ok" | "error" | "canceled">
|
|
185
|
+
): OutcomeFilter {
|
|
186
|
+
const outcomes = new Set<string>();
|
|
187
|
+
for (const status of statuses) {
|
|
188
|
+
switch (status) {
|
|
189
|
+
case "ok":
|
|
190
|
+
outcomes.add("ok");
|
|
191
|
+
break;
|
|
192
|
+
case "canceled":
|
|
193
|
+
outcomes.add("canceled");
|
|
194
|
+
break;
|
|
195
|
+
// there's more than one way to error
|
|
196
|
+
case "error":
|
|
197
|
+
outcomes.add("exception");
|
|
198
|
+
outcomes.add("exceededCpu");
|
|
199
|
+
outcomes.add("unknown");
|
|
200
|
+
break;
|
|
201
|
+
default:
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
outcome: Array.from(outcomes),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// we actually don't need to do anything here
|
|
212
|
+
function parseMethod(method: string[]): MethodFilter {
|
|
213
|
+
return { method };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function parseHeader(header: string): HeaderFilter {
|
|
217
|
+
// headers of the form "HEADER-KEY: VALUE" get split.
|
|
218
|
+
// the query is optional
|
|
219
|
+
const [headerKey, headerQuery] = header.split(":", 2);
|
|
220
|
+
return {
|
|
221
|
+
header: {
|
|
222
|
+
key: headerKey.trim(),
|
|
223
|
+
query: headerQuery?.trim(),
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function parseClientIp(client_ip: string[]): ClientIpFilter {
|
|
229
|
+
return { client_ip };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function parseQuery(query: string): QueryFilter {
|
|
233
|
+
return { query };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function prettyPrintLogs(_data: WebSocket.RawData): void {
|
|
237
|
+
throw new Error("TODO!");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function jsonPrintLogs(data: WebSocket.RawData): void {
|
|
241
|
+
console.log(JSON.stringify(JSON.parse(data.toString()), null, 2));
|
|
242
|
+
}
|
package/src/user.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Based heavily on code from https://github.com/BitySA/oauth2-auth-code-pkce */
|
|
2
2
|
|
|
3
|
-
/*
|
|
3
|
+
/*
|
|
4
4
|
|
|
5
5
|
Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -205,25 +205,26 @@
|
|
|
205
205
|
limitations under the License.
|
|
206
206
|
*/
|
|
207
207
|
|
|
208
|
-
import
|
|
209
|
-
import { render, Text } from "ink";
|
|
210
|
-
import Table from "ink-table";
|
|
211
|
-
import SelectInput from "ink-select-input";
|
|
212
|
-
import fetch from "node-fetch";
|
|
208
|
+
import assert from "node:assert";
|
|
213
209
|
import { webcrypto as crypto } from "node:crypto";
|
|
214
|
-
import { TextEncoder } from "node:util";
|
|
215
|
-
import open from "open";
|
|
216
|
-
import url from "node:url";
|
|
217
|
-
import http from "node:http";
|
|
218
210
|
import { readFile, writeFile, rm, mkdir } from "node:fs/promises";
|
|
211
|
+
import http from "node:http";
|
|
212
|
+
import os from "node:os";
|
|
219
213
|
import path from "node:path";
|
|
220
214
|
import process from "node:process";
|
|
221
|
-
import
|
|
215
|
+
import url from "node:url";
|
|
216
|
+
import { TextEncoder } from "node:util";
|
|
222
217
|
import TOML from "@iarna/toml";
|
|
223
|
-
import
|
|
224
|
-
import
|
|
218
|
+
import { render, Text } from "ink";
|
|
219
|
+
import SelectInput from "ink-select-input";
|
|
220
|
+
import Table from "ink-table";
|
|
221
|
+
import React from "react";
|
|
222
|
+
import { fetch } from "undici";
|
|
225
223
|
import { CF_API_BASE_URL } from "./cfetch";
|
|
226
|
-
import
|
|
224
|
+
import openInBrowser from "./open-in-browser";
|
|
225
|
+
import type { Item as SelectInputItem } from "ink-select-input/build/SelectInput";
|
|
226
|
+
import type { ParsedUrlQuery } from "node:querystring";
|
|
227
|
+
import type { Response } from "undici";
|
|
227
228
|
|
|
228
229
|
/**
|
|
229
230
|
* An implementation of rfc6749#section-4.1 and rfc7636.
|
|
@@ -276,14 +277,14 @@ const Scopes = {
|
|
|
276
277
|
*
|
|
277
278
|
* "offline_access" is automatically included.
|
|
278
279
|
*/
|
|
279
|
-
type Scope = keyof typeof Scopes
|
|
280
|
+
type Scope = keyof typeof Scopes;
|
|
280
281
|
|
|
281
282
|
const ScopeKeys = Object.keys(Scopes) as Scope[];
|
|
282
283
|
|
|
283
284
|
export function validateScopeKeys(
|
|
284
285
|
scopes: string[]
|
|
285
286
|
): scopes is typeof ScopeKeys {
|
|
286
|
-
return scopes.every((scope) => Scopes
|
|
287
|
+
return scopes.every((scope) => scope in Scopes);
|
|
287
288
|
}
|
|
288
289
|
|
|
289
290
|
const CLIENT_ID = "54d11594-84e4-41aa-b438-e81b8fa78ee7";
|
|
@@ -561,6 +562,7 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
|
|
|
561
562
|
`?response_type=code&` +
|
|
562
563
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
563
564
|
`redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
|
|
565
|
+
// @ts-expect-error we add offline_access manually
|
|
564
566
|
`scope=${encodeURIComponent(scopes.concat("offline_access").join(" "))}&` +
|
|
565
567
|
`state=${stateQueryParam}&` +
|
|
566
568
|
`code_challenge=${encodeURIComponent(codeChallenge)}&` +
|
|
@@ -568,6 +570,17 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
|
|
|
568
570
|
);
|
|
569
571
|
}
|
|
570
572
|
|
|
573
|
+
type TokenResponse =
|
|
574
|
+
| {
|
|
575
|
+
access_token: string;
|
|
576
|
+
expires_in: number;
|
|
577
|
+
refresh_token: string;
|
|
578
|
+
scope: string;
|
|
579
|
+
}
|
|
580
|
+
| {
|
|
581
|
+
error: string;
|
|
582
|
+
};
|
|
583
|
+
|
|
571
584
|
/**
|
|
572
585
|
* Refresh an access token from the remote service.
|
|
573
586
|
*/
|
|
@@ -592,13 +605,12 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
592
605
|
throw await response.json();
|
|
593
606
|
} else {
|
|
594
607
|
try {
|
|
595
|
-
const json = await response.json();
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
};
|
|
608
|
+
const json = (await response.json()) as TokenResponse;
|
|
609
|
+
if ("error" in json) {
|
|
610
|
+
throw json.error;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const { access_token, expires_in, refresh_token, scope } = json;
|
|
602
614
|
let scopes: Scope[] = [];
|
|
603
615
|
|
|
604
616
|
const accessToken: AccessToken = {
|
|
@@ -626,20 +638,24 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
626
638
|
refreshToken: LocalState.refreshToken,
|
|
627
639
|
};
|
|
628
640
|
return accessContext;
|
|
629
|
-
} catch (
|
|
630
|
-
const error = err?.error || "There was a network error.";
|
|
641
|
+
} catch (error) {
|
|
631
642
|
switch (error) {
|
|
632
643
|
case "invalid_grant":
|
|
633
|
-
console.
|
|
644
|
+
console.warn(
|
|
634
645
|
"Expired! Auth code or refresh token needs to be renewed."
|
|
635
646
|
);
|
|
636
647
|
// alert("Redirecting to auth server to obtain a new auth grant code.");
|
|
637
648
|
// TODO: return refreshAuthCodeOrRefreshToken();
|
|
638
649
|
break;
|
|
639
650
|
default:
|
|
651
|
+
console.error(error);
|
|
640
652
|
break;
|
|
641
653
|
}
|
|
642
|
-
|
|
654
|
+
if (typeof error === "string") {
|
|
655
|
+
throw toErrorClass(error);
|
|
656
|
+
} else {
|
|
657
|
+
throw error;
|
|
658
|
+
}
|
|
643
659
|
}
|
|
644
660
|
}
|
|
645
661
|
}
|
|
@@ -680,13 +696,11 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
680
696
|
}
|
|
681
697
|
throw toErrorClass(error);
|
|
682
698
|
}
|
|
683
|
-
const json = await response.json();
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
scope: string;
|
|
689
|
-
};
|
|
699
|
+
const json = (await response.json()) as TokenResponse;
|
|
700
|
+
if ("error" in json) {
|
|
701
|
+
throw new Error(json.error);
|
|
702
|
+
}
|
|
703
|
+
const { access_token, expires_in, refresh_token, scope } = json;
|
|
690
704
|
let scopes: Scope[] = [];
|
|
691
705
|
LocalState.hasAuthCodeBeenExchangedForAccessToken = true;
|
|
692
706
|
|
|
@@ -804,18 +818,18 @@ export async function loginOrRefreshIfRequired(): Promise<boolean> {
|
|
|
804
818
|
|
|
805
819
|
export async function login(props?: LoginProps): Promise<boolean> {
|
|
806
820
|
const urlToOpen = await getAuthURL(props?.scopes);
|
|
807
|
-
await
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
let server;
|
|
811
|
-
let loginTimeoutHandle;
|
|
821
|
+
await openInBrowser(urlToOpen);
|
|
822
|
+
let server: http.Server;
|
|
823
|
+
let loginTimeoutHandle: NodeJS.Timeout;
|
|
812
824
|
const timerPromise = new Promise<boolean>((resolve) => {
|
|
813
825
|
loginTimeoutHandle = setTimeout(() => {
|
|
814
|
-
console.error(
|
|
826
|
+
console.error(
|
|
827
|
+
"Timed out waiting for authorization code, please try again."
|
|
828
|
+
);
|
|
815
829
|
server.close();
|
|
816
830
|
clearTimeout(loginTimeoutHandle);
|
|
817
831
|
resolve(false);
|
|
818
|
-
}, 60000); // wait for
|
|
832
|
+
}, 60000); // wait for 60 seconds for the user to authorize
|
|
819
833
|
});
|
|
820
834
|
|
|
821
835
|
const loginPromise = new Promise<boolean>((resolve, reject) => {
|
|
@@ -847,7 +861,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
847
861
|
});
|
|
848
862
|
console.log(
|
|
849
863
|
"Error: Consent denied. You must grant consent to Wrangler in order to login. If you don't want to do this consider passing an API token with CF_API_TOKEN variable"
|
|
850
|
-
);
|
|
864
|
+
);
|
|
851
865
|
|
|
852
866
|
return;
|
|
853
867
|
} else {
|
|
@@ -939,7 +953,7 @@ export async function logout(): Promise<void> {
|
|
|
939
953
|
export function listScopes(): void {
|
|
940
954
|
throwIfNotInitialised();
|
|
941
955
|
console.log("💁 Available scopes:");
|
|
942
|
-
const data = ScopeKeys.map((scope) => ({
|
|
956
|
+
const data = ScopeKeys.map((scope: Scope) => ({
|
|
943
957
|
Scope: scope,
|
|
944
958
|
Description: Scopes[scope],
|
|
945
959
|
}));
|
|
@@ -1001,7 +1015,7 @@ type ChooseAccountItem = {
|
|
|
1001
1015
|
};
|
|
1002
1016
|
export function ChooseAccount(props: {
|
|
1003
1017
|
accounts: ChooseAccountItem[];
|
|
1004
|
-
onSelect: (item) => void;
|
|
1018
|
+
onSelect: (item: SelectInputItem<ChooseAccountItem>) => void;
|
|
1005
1019
|
}) {
|
|
1006
1020
|
return (
|
|
1007
1021
|
<>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Welcome to Cloudflare Workers! This is your first worker.
|
|
3
|
+
*
|
|
4
|
+
* - Run `npx wrangler dev src/index.js` in your terminal to start a development server
|
|
5
|
+
* - Open a browser tab at http://localhost:8787/ to see your worker in action
|
|
6
|
+
* - Run `npx wrangler publish src/index.js --name my-worker` to deploy your worker
|
|
7
|
+
*
|
|
8
|
+
* Learn more at https://developers.cloudflare.com/workers/
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
async fetch(request) {
|
|
13
|
+
return new Response("Hello World!");
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Welcome to Cloudflare Workers! This is your first worker.
|
|
3
|
+
*
|
|
4
|
+
* - Run `wrangler dev src/index.ts` in your terminal to start a development server
|
|
5
|
+
* - Open a browser tab at http://localhost:8787/ to see your worker in action
|
|
6
|
+
* - Run `wrangler publish src/index.ts --name my-worker` to deploy your worker
|
|
7
|
+
*
|
|
8
|
+
* Learn more at https://developers.cloudflare.com/workers/
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
async fetch(request: Request): Promise<Response> {
|
|
13
|
+
return new Response("Hello World!");
|
|
14
|
+
},
|
|
15
|
+
};
|
|
File without changes
|