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.
Files changed (67) hide show
  1. package/bin/wrangler.js +2 -2
  2. package/package.json +20 -11
  3. package/pages/functions/buildWorker.ts +1 -1
  4. package/pages/functions/filepath-routing.test.ts +112 -28
  5. package/pages/functions/filepath-routing.ts +44 -51
  6. package/pages/functions/routes.ts +11 -18
  7. package/pages/functions/template-worker.ts +3 -9
  8. package/src/__tests__/dev.test.tsx +42 -5
  9. package/src/__tests__/guess-worker-format.test.ts +66 -0
  10. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  11. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  12. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  13. package/src/__tests__/helpers/mock-account-id.ts +30 -0
  14. package/src/__tests__/helpers/mock-bin.ts +36 -0
  15. package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +43 -9
  16. package/src/__tests__/helpers/mock-console.ts +62 -0
  17. package/src/__tests__/{mock-dialogs.ts → helpers/mock-dialogs.ts} +1 -1
  18. package/src/__tests__/helpers/mock-kv.ts +40 -0
  19. package/src/__tests__/helpers/mock-user.ts +27 -0
  20. package/src/__tests__/helpers/mock-web-socket.ts +37 -0
  21. package/src/__tests__/{run-in-tmp.ts → helpers/run-in-tmp.ts} +1 -1
  22. package/src/__tests__/helpers/run-wrangler.ts +16 -0
  23. package/src/__tests__/helpers/write-wrangler-toml.ts +20 -0
  24. package/src/__tests__/index.test.ts +418 -71
  25. package/src/__tests__/jest.setup.ts +30 -2
  26. package/src/__tests__/kv.test.ts +147 -252
  27. package/src/__tests__/logout.test.ts +50 -0
  28. package/src/__tests__/package-manager.test.ts +206 -0
  29. package/src/__tests__/publish.test.ts +1136 -291
  30. package/src/__tests__/r2.test.ts +206 -0
  31. package/src/__tests__/secret.test.ts +210 -0
  32. package/src/__tests__/sentry.test.ts +146 -0
  33. package/src/__tests__/tail.test.ts +246 -0
  34. package/src/__tests__/whoami.test.tsx +6 -47
  35. package/src/api/form_data.ts +75 -25
  36. package/src/api/preview.ts +2 -2
  37. package/src/api/worker.ts +34 -15
  38. package/src/bundle.ts +127 -0
  39. package/src/cfetch/index.ts +7 -15
  40. package/src/cfetch/internal.ts +41 -6
  41. package/src/cli.ts +10 -0
  42. package/src/config.ts +125 -95
  43. package/src/dev.tsx +300 -193
  44. package/src/dialogs.tsx +2 -2
  45. package/src/guess-worker-format.ts +68 -0
  46. package/src/index.tsx +578 -192
  47. package/src/inspect.ts +29 -10
  48. package/src/kv.tsx +23 -17
  49. package/src/module-collection.ts +32 -12
  50. package/src/open-in-browser.ts +13 -0
  51. package/src/package-manager.ts +120 -0
  52. package/src/pages.tsx +28 -23
  53. package/src/paths.ts +26 -0
  54. package/src/proxy.ts +88 -14
  55. package/src/publish.ts +260 -297
  56. package/src/r2.ts +50 -0
  57. package/src/reporting.ts +115 -0
  58. package/src/sites.tsx +28 -27
  59. package/src/tail.tsx +178 -9
  60. package/src/user.tsx +58 -44
  61. package/templates/new-worker.js +15 -0
  62. package/templates/new-worker.ts +15 -0
  63. package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
  64. package/wrangler-dist/cli.js +124315 -104677
  65. package/wrangler-dist/cli.js.map +3 -3
  66. package/src/__tests__/mock-console.ts +0 -34
  67. package/src/__tests__/run-wrangler.ts +0 -8
@@ -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 { XXHash64 } from "xxhash-addon";
5
- import { fetchResult } from "./cfetch";
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 { listNamespaceKeys, listNamespaces, putBulkKeyValue } from "./kv";
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
- const hasher = new XXHash64();
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(filePath: string, content: string): string {
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
- // TODO: use an export from ./kv
80
- const json = await fetchResult<{ id: string }>(
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: json.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
- _env?: string
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}_sites_assets${preview ? "_preview" : ""}`;
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
- _filters: Filters
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
- // TODO: send filters as well
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 type Filters = {
68
- status?: "ok" | "error" | "canceled";
69
- header?: string;
70
- method?: string;
71
- "sampling-rate"?: number;
72
- search?: string;
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 React from "react";
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 os from "node:os";
215
+ import url from "node:url";
216
+ import { TextEncoder } from "node:util";
222
217
  import TOML from "@iarna/toml";
223
- import assert from "node:assert";
224
- import type { ParsedUrlQuery } from "node:querystring";
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 type { Response } from "node-fetch";
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 | "offline_access";
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[scope]);
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
- const { access_token, expires_in, refresh_token, scope } = json as {
597
- access_token: string;
598
- expires_in: number;
599
- refresh_token: string;
600
- scope: string;
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 (err) {
630
- const error = err?.error || "There was a network error.";
641
+ } catch (error) {
631
642
  switch (error) {
632
643
  case "invalid_grant":
633
- console.log(
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
- throw toErrorClass(error);
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
- const { access_token, expires_in, refresh_token, scope } = json as {
685
- access_token: string;
686
- expires_in: number;
687
- refresh_token: string;
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 open(urlToOpen);
808
- // TODO: log url only if on system where it's unreliable/unavailable
809
- // console.log(`💁 Opened ${urlToOpen}`);
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("Timed out waiting for authorization code.");
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 30 seconds for the user to authorize
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
- ); // TODO: implement wrangler config lol
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
+ };