wrangler 0.0.0-e14e589 → 0.0.0-e1c615f

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 (41) hide show
  1. package/README.md +51 -0
  2. package/package.json +39 -26
  3. package/pages/functions/buildWorker.ts +57 -0
  4. package/pages/functions/filepath-routing.test.ts +39 -0
  5. package/pages/functions/filepath-routing.ts +221 -0
  6. package/pages/functions/identifiers.ts +78 -0
  7. package/pages/functions/routes.ts +158 -0
  8. package/pages/functions/template-worker.ts +137 -0
  9. package/src/__tests__/dev.test.tsx +66 -0
  10. package/src/__tests__/index.test.ts +254 -120
  11. package/src/__tests__/jest.setup.ts +22 -0
  12. package/src/__tests__/kv.test.ts +1098 -0
  13. package/src/__tests__/mock-cfetch.ts +171 -0
  14. package/src/__tests__/mock-dialogs.ts +65 -0
  15. package/src/__tests__/run-in-tmp.ts +19 -0
  16. package/src/__tests__/run-wrangler.ts +32 -0
  17. package/src/api/form_data.ts +78 -72
  18. package/src/api/inspect.ts +0 -11
  19. package/src/api/preview.ts +29 -24
  20. package/src/api/worker.ts +46 -31
  21. package/src/cfetch/index.ts +102 -0
  22. package/src/cfetch/internal.ts +69 -0
  23. package/src/cli.ts +6 -7
  24. package/src/config.ts +39 -4
  25. package/src/dev.tsx +332 -128
  26. package/src/dialogs.tsx +4 -12
  27. package/src/index.tsx +397 -222
  28. package/src/kv.tsx +101 -45
  29. package/src/module-collection.ts +64 -0
  30. package/src/pages.tsx +839 -211
  31. package/src/proxy.ts +103 -0
  32. package/src/publish.ts +117 -45
  33. package/src/sites.tsx +6 -7
  34. package/src/tail.tsx +6 -4
  35. package/src/user.tsx +9 -16
  36. package/wrangler-dist/cli.js +123794 -528
  37. package/wrangler-dist/cli.js.map +3 -3
  38. package/src/__tests__/fixtures/init/.gitkeep +0 -0
  39. package/src/__tests__/mock-cfetch.js +0 -46
  40. package/src/cfetch.ts +0 -72
  41. package/src/util/fetch.ts +0 -74
package/src/proxy.ts ADDED
@@ -0,0 +1,103 @@
1
+ import { connect } from "node:http2";
2
+ import { createServer } from "node:http";
3
+ import type { Server, IncomingHttpHeaders, RequestListener } from "node:http";
4
+ import WebSocket from "faye-websocket";
5
+ import serveStatic from "serve-static";
6
+
7
+ export interface HttpProxyInit {
8
+ host: string;
9
+ assetPath?: string | null;
10
+ onRequest?: (headers: IncomingHttpHeaders) => void;
11
+ onResponse?: (headers: IncomingHttpHeaders) => void;
12
+ }
13
+
14
+ /**
15
+ * Creates a HTTP/1 proxy that sends requests over HTTP/2.
16
+ */
17
+ export function createHttpProxy(init: HttpProxyInit): Server {
18
+ const { host, assetPath, onRequest = () => {}, onResponse = () => {} } = init;
19
+ const remote = connect(`https://${host}`);
20
+ const local = createServer();
21
+ // HTTP/2 -> HTTP/2
22
+ local.on("stream", (stream, headers: IncomingHttpHeaders) => {
23
+ onRequest(headers);
24
+ headers[":authority"] = host;
25
+ const request = stream.pipe(remote.request(headers));
26
+ request.on("response", (responseHeaders: IncomingHttpHeaders) => {
27
+ onResponse(responseHeaders);
28
+ stream.respond(responseHeaders);
29
+ request.pipe(stream, { end: true });
30
+ });
31
+ });
32
+ // HTTP/1 -> HTTP/2
33
+ const handleRequest: RequestListener = (message, response) => {
34
+ const { httpVersionMajor, headers, method, url } = message;
35
+ if (httpVersionMajor >= 2) {
36
+ return; // Already handled by the "stream" event.
37
+ }
38
+ onRequest(headers);
39
+ headers[":method"] = method;
40
+ headers[":path"] = url;
41
+ headers[":authority"] = host;
42
+ headers[":scheme"] = "https";
43
+ for (const name of Object.keys(headers)) {
44
+ if (HTTP1_HEADERS.has(name.toLowerCase())) {
45
+ delete headers[name];
46
+ }
47
+ }
48
+ const request = message.pipe(remote.request(headers));
49
+ request.on("response", (responseHeaders) => {
50
+ const status = responseHeaders[":status"];
51
+ onResponse(responseHeaders);
52
+ for (const name of Object.keys(responseHeaders)) {
53
+ if (name.startsWith(":")) {
54
+ delete responseHeaders[name];
55
+ }
56
+ }
57
+ response.writeHead(status, responseHeaders);
58
+ request.pipe(response, { end: true });
59
+ });
60
+ };
61
+ // If an asset path is defined, check the file system
62
+ // for a file first and serve if it exists.
63
+ if (assetPath) {
64
+ const handleAsset = serveStatic(assetPath, {
65
+ cacheControl: false,
66
+ });
67
+ local.on("request", (request, response) => {
68
+ handleAsset(request, response, () => {
69
+ handleRequest(request, response);
70
+ });
71
+ });
72
+ } else {
73
+ local.on("request", handleRequest);
74
+ }
75
+ // HTTP/1 -> WebSocket (over HTTP/1)
76
+ local.on("upgrade", (message, socket, body) => {
77
+ const { headers, url } = message;
78
+ onRequest(headers);
79
+ headers["host"] = host;
80
+ const localWebsocket = new WebSocket(message, socket, body);
81
+ // TODO(soon): Custom WebSocket protocol is not working?
82
+ const remoteWebsocketClient = new WebSocket.Client(
83
+ `wss://${host}${url}`,
84
+ [],
85
+ { headers }
86
+ );
87
+ localWebsocket.pipe(remoteWebsocketClient).pipe(localWebsocket);
88
+ });
89
+ remote.on("close", () => {
90
+ local.close();
91
+ });
92
+ return local;
93
+ }
94
+
95
+ const HTTP1_HEADERS = new Set([
96
+ "host",
97
+ "connection",
98
+ "upgrade",
99
+ "keep-alive",
100
+ "proxy-connection",
101
+ "transfer-encoding",
102
+ "http2-settings",
103
+ ]);
package/src/publish.ts CHANGED
@@ -1,12 +1,14 @@
1
- import type { CfWorkerInit } from "./api/worker";
2
- import { toFormData } from "./api/form_data";
1
+ import assert from "node:assert";
2
+ import path from "node:path";
3
+ import { readFile } from "node:fs/promises";
3
4
  import esbuild from "esbuild";
5
+ import { execa } from "execa";
4
6
  import tmp from "tmp-promise";
7
+ import type { CfWorkerInit } from "./api/worker";
8
+ import { toFormData } from "./api/form_data";
9
+ import { fetchResult } from "./cfetch";
5
10
  import type { Config } from "./config";
6
- import path from "path";
7
- import { readFile } from "fs/promises";
8
- import cfetch from "./cfetch";
9
- import assert from "node:assert";
11
+ import makeModuleCollector from "./module-collection";
10
12
  import { syncAssets } from "./sites";
11
13
 
12
14
  type CfScriptFormat = void | "modules" | "service-worker";
@@ -22,6 +24,8 @@ type Props = {
22
24
  triggers?: (string | number)[];
23
25
  routes?: (string | number)[];
24
26
  legacyEnv?: boolean;
27
+ jsxFactory: void | string;
28
+ jsxFragment: void | string;
25
29
  };
26
30
 
27
31
  function sleep(ms: number) {
@@ -47,19 +51,25 @@ export default async function publish(props: Props): Promise<void> {
47
51
  const triggers = props.triggers || config.triggers?.crons;
48
52
  const routes = props.routes || config.routes;
49
53
 
54
+ const jsxFactory = props.jsxFactory || config.jsx_factory;
55
+ const jsxFragment = props.jsxFragment || config.jsx_fragment;
56
+
50
57
  assert(config.account_id, "missing account id");
51
58
 
59
+ let scriptName = props.name || config.name;
60
+ assert(
61
+ scriptName,
62
+ 'You need to provide a name when publishing a worker. Either pass it as a cli arg with `--name <name>` or in your config file as `name = "<name>"`'
63
+ );
64
+
52
65
  let file: string;
53
66
  if (props.script) {
54
67
  file = props.script;
55
- assert(props.name, "name is required when using script");
56
68
  } else {
57
69
  assert(build?.upload?.main, "missing main file");
58
- assert(config.name, "missing name");
59
70
  file = path.join(path.dirname(__path__), build.upload.main);
60
71
  }
61
72
 
62
- let scriptName = props.script ? props.name : config.name;
63
73
  if (props.legacyEnv) {
64
74
  scriptName += props.env ? `-${props.env}` : "";
65
75
  }
@@ -67,6 +77,18 @@ export default async function publish(props: Props): Promise<void> {
67
77
 
68
78
  const destination = await tmp.dir({ unsafeCleanup: true });
69
79
 
80
+ if (props.config.build?.command) {
81
+ // TODO: add a deprecation message here?
82
+ console.log("running:", props.config.build.command);
83
+ const buildCommandPieces = props.config.build.command.split(" ");
84
+ await execa(buildCommandPieces[0], buildCommandPieces.slice(1), {
85
+ stdout: "inherit",
86
+ stderr: "inherit",
87
+ ...(props.config.build?.cwd && { cwd: props.config.build.cwd }),
88
+ });
89
+ }
90
+
91
+ const moduleCollector = makeModuleCollector();
70
92
  const result = await esbuild.build({
71
93
  ...(props.public
72
94
  ? {
@@ -89,6 +111,13 @@ export default async function publish(props: Props): Promise<void> {
89
111
  format: "esm",
90
112
  sourcemap: true,
91
113
  metafile: true,
114
+ conditions: ["worker", "browser"],
115
+ loader: {
116
+ ".js": "jsx",
117
+ },
118
+ plugins: [moduleCollector.plugin],
119
+ ...(jsxFactory && { jsxFactory }),
120
+ ...(jsxFragment && { jsxFragment }),
92
121
  });
93
122
 
94
123
  const chunks = Object.entries(result.metafile.outputs).find(
@@ -120,7 +149,47 @@ export default async function publish(props: Props): Promise<void> {
120
149
  }
121
150
 
122
151
  const content = await readFile(chunks[0], { encoding: "utf-8" });
123
- destination.cleanup();
152
+ await destination.cleanup();
153
+
154
+ // if config.migrations
155
+ // get current migration tag
156
+ let migrations;
157
+ if ("migrations" in config) {
158
+ const scripts = await fetchResult<{ id: string; migration_tag: string }[]>(
159
+ `/accounts/${accountId}/workers/scripts`
160
+ );
161
+ const script = scripts.find(({ id }) => id === scriptName);
162
+ if (script?.migration_tag) {
163
+ // was already published once
164
+ const foundIndex = config.migrations.findIndex(
165
+ (migration) => migration.tag === script.migration_tag
166
+ );
167
+ if (foundIndex === -1) {
168
+ console.warn(
169
+ `The published script ${scriptName} has a migration tag "${script.migration_tag}, which was not found in wrangler.toml. You may have already deleted it. Applying all available migrations to the script...`
170
+ );
171
+ migrations = {
172
+ old_tag: script.migration_tag,
173
+ new_tag: config.migrations[config.migrations.length - 1].tag,
174
+ steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
175
+ };
176
+ } else {
177
+ migrations = {
178
+ old_tag: script.migration_tag,
179
+ new_tag: config.migrations[config.migrations.length - 1].tag,
180
+ steps: config.migrations
181
+ .slice(foundIndex + 1)
182
+ .map(({ tag: _tag, ...rest }) => rest),
183
+ };
184
+ }
185
+ } else {
186
+ migrations = {
187
+ new_tag: config.migrations[config.migrations.length - 1].tag,
188
+ steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
189
+ };
190
+ }
191
+ }
192
+
124
193
  const assets =
125
194
  props.public || props.site || props.config.site?.bucket // TODO: allow both
126
195
  ? await syncAssets(
@@ -131,42 +200,45 @@ export default async function publish(props: Props): Promise<void> {
131
200
  )
132
201
  : { manifest: undefined, namespace: undefined };
133
202
 
134
- const envRootObj = props.env ? config[`env.${props.env}`] : config;
203
+ const envRootObj = props.env ? config.env[props.env] || {} : config;
204
+ const bindings: CfWorkerInit["bindings"] = {
205
+ kv_namespaces: envRootObj.kv_namespaces?.concat(
206
+ assets.namespace
207
+ ? { binding: "__STATIC_CONTENT", id: assets.namespace }
208
+ : []
209
+ ),
210
+ vars: envRootObj.vars,
211
+ durable_objects: envRootObj.durable_objects,
212
+ services: envRootObj.experimental_services,
213
+ };
135
214
 
136
215
  const worker: CfWorkerInit = {
216
+ name: scriptName,
137
217
  main: {
138
- name: scriptName,
218
+ name: path.basename(chunks[0]),
139
219
  content: content,
140
- type:
141
- (bundle.type === "esm" ? "modules" : "service-worker") === "modules"
142
- ? "esm"
143
- : "commonjs",
220
+ type: bundle.type === "esm" ? "esm" : "commonjs",
144
221
  },
145
- variables: {
146
- ...(envRootObj?.vars || {}),
147
- ...(envRootObj?.kv_namespaces || []).reduce(
148
- (obj, { binding, preview_id, id }) => {
149
- return { ...obj, [binding]: { namespaceId: id } };
150
- },
151
- {}
152
- ),
153
- ...(assets.namespace
154
- ? { __STATIC_CONTENT: { namespaceId: assets.namespace } }
155
- : {}),
156
- },
157
- modules: assets.manifest
158
- ? [].concat({
159
- name: "__STATIC_CONTENT_MANIFEST",
160
- content: JSON.stringify(assets.manifest),
161
- type: "text",
162
- })
163
- : [],
222
+ bindings,
223
+ ...(migrations && { migrations }),
224
+ modules: moduleCollector.modules.concat(
225
+ assets.manifest
226
+ ? {
227
+ name: "__STATIC_CONTENT_MANIFEST",
228
+ content: JSON.stringify(assets.manifest),
229
+ type: "text",
230
+ }
231
+ : []
232
+ ),
233
+ compatibility_date: config.compatibility_date,
234
+ compatibility_flags: config.compatibility_flags,
235
+ usage_model: config.usage_model,
164
236
  };
165
237
 
166
238
  const start = Date.now();
167
- const formatTime = (duration) => {
239
+ function formatTime(duration: number) {
168
240
  return `(${(duration / 1000).toFixed(2)} sec)`;
169
- };
241
+ }
170
242
 
171
243
  const notProd = !props.legacyEnv && props.env;
172
244
  const workerName = notProd ? `${scriptName} (${envName})` : scriptName;
@@ -174,8 +246,8 @@ export default async function publish(props: Props): Promise<void> {
174
246
  ? `/accounts/${accountId}/workers/services/${scriptName}/environments/${envName}`
175
247
  : `/accounts/${accountId}/workers/scripts/${scriptName}`;
176
248
 
177
- // Upload the script so it has time to propogate.
178
- const { available_on_subdomain } = await cfetch(
249
+ // Upload the script so it has time to propagate.
250
+ const { available_on_subdomain } = await fetchResult(
179
251
  `${workerUrl}?available_on_subdomain=true`,
180
252
  {
181
253
  method: "PUT",
@@ -189,7 +261,7 @@ export default async function publish(props: Props): Promise<void> {
189
261
  const deployments: Promise<string[]>[] = [];
190
262
 
191
263
  const userSubdomain = (
192
- await cfetch<{ subdomain: string }>(
264
+ await fetchResult<{ subdomain: string }>(
193
265
  `/accounts/${accountId}/workers/subdomain`
194
266
  )
195
267
  ).subdomain;
@@ -203,7 +275,7 @@ export default async function publish(props: Props): Promise<void> {
203
275
  // TODO: Make this configurable.
204
276
  if (!available_on_subdomain) {
205
277
  deployments.push(
206
- cfetch(`${workerUrl}/subdomain`, {
278
+ fetchResult(`${workerUrl}/subdomain`, {
207
279
  method: "POST",
208
280
  body: JSON.stringify({ enabled: true }),
209
281
  headers: {
@@ -215,8 +287,8 @@ export default async function publish(props: Props): Promise<void> {
215
287
  // This is to prevent an issue where a negative cache-hit
216
288
  // causes the subdomain to be unavailable for 30 seconds.
217
289
  // This is a temporary measure until we fix this on the edge.
218
- .then((url) => {
219
- sleep(3000);
290
+ .then(async (url) => {
291
+ await sleep(3000);
220
292
  return url;
221
293
  })
222
294
  );
@@ -227,7 +299,7 @@ export default async function publish(props: Props): Promise<void> {
227
299
  // Update routing table for the script.
228
300
  if (routes && routes.length) {
229
301
  deployments.push(
230
- cfetch(`${workerUrl}/routes`, {
302
+ fetchResult(`${workerUrl}/routes`, {
231
303
  // TODO: PATCH will not delete previous routes on this script,
232
304
  // whereas PUT will. We need to decide on the default behaviour
233
305
  // and how to configure it.
@@ -252,7 +324,7 @@ export default async function publish(props: Props): Promise<void> {
252
324
  // TODO: rename this to `schedules`?
253
325
  if (triggers && triggers.length) {
254
326
  deployments.push(
255
- cfetch(`${workerUrl}/schedules`, {
327
+ fetchResult(`${workerUrl}/schedules`, {
256
328
  // TODO: Unlike routes, this endpoint does not support PATCH.
257
329
  // So technically, this will override any previous schedules.
258
330
  // We should change the endpoint to support PATCH.
package/src/sites.tsx CHANGED
@@ -1,11 +1,10 @@
1
- import { readdir, readFile } from "node:fs/promises";
1
+ import crypto from "node:crypto";
2
2
  import { createReadStream } from "node:fs";
3
- import cfetch from "./cfetch";
3
+ import * as path from "node:path";
4
+ import { readdir, readFile } from "node:fs/promises";
5
+ import { fetchResult } from "./cfetch";
4
6
  import { listNamespaceKeys, listNamespaces, putBulkKeyValue } from "./kv";
5
7
 
6
- import * as path from "path";
7
- import crypto from "node:crypto";
8
-
9
8
  async function* getFilesInFolder(dirPath: string): AsyncIterable<string> {
10
9
  const files = await readdir(dirPath, { withFileTypes: true });
11
10
  for (const file of files) {
@@ -54,7 +53,7 @@ async function createKVNamespaceIfNotAlreadyExisting(
54
53
 
55
54
  // else we make the namespace
56
55
  // TODO: use an export from ./kv
57
- const json = await cfetch<{ id: string }>(
56
+ const json = await fetchResult<{ id: string }>(
58
57
  `/accounts/${accountId}/storage/kv/namespaces`,
59
58
  {
60
59
  method: "POST",
@@ -76,7 +75,7 @@ export async function syncAssets(
76
75
  scriptName: string,
77
76
  dirPath: string,
78
77
  preview: boolean,
79
- env?: string
78
+ _env?: string
80
79
  ) {
81
80
  const title = `__${scriptName}_sites_assets${preview ? "_preview" : ""}`;
82
81
  const { id: namespace } = await createKVNamespaceIfNotAlreadyExisting(
package/src/tail.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import WebSocket from "ws";
2
- import cfetch from "./cfetch";
3
2
  import { version as packageVersion } from "../package.json";
3
+ import { fetchResult } from "./cfetch";
4
4
 
5
5
  export type TailApiResponse = {
6
6
  id: string;
@@ -27,13 +27,15 @@ async function createTailButDontConnect(
27
27
  ): Promise<TailApiResponse> {
28
28
  const createTailUrl = makeCreateTailUrl(accountId, workerName);
29
29
  /// https://api.cloudflare.com/#worker-tail-logs-start-tail
30
- return await cfetch<TailApiResponse>(createTailUrl, { method: "POST" });
30
+ return await fetchResult<TailApiResponse>(createTailUrl, {
31
+ method: "POST",
32
+ });
31
33
  }
32
34
 
33
35
  export async function createTail(
34
36
  accountId: string,
35
37
  workerName: string,
36
- filters: Filters
38
+ _filters: Filters
37
39
  ): Promise<{
38
40
  tail: WebSocket;
39
41
  expiration: Date;
@@ -48,7 +50,7 @@ export async function createTail(
48
50
 
49
51
  // deletes the tail
50
52
  async function deleteTail() {
51
- await cfetch(deleteUrl, { method: "DELETE" });
53
+ await fetchResult(deleteUrl, { method: "DELETE" });
52
54
  }
53
55
 
54
56
  const tail = new WebSocket(websocketUrl, "trace-v1", {
package/src/user.tsx CHANGED
@@ -578,19 +578,16 @@ export async function getAuthURL(scopes?: string[]): Promise<string> {
578
578
  * Refresh an access token from the remote service.
579
579
  */
580
580
  async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
581
- const { refreshToken } = LocalState;
582
-
583
- if (!refreshToken) {
581
+ if (!LocalState.refreshToken) {
584
582
  console.warn("No refresh token is present.");
585
583
  }
586
584
 
587
- const url = TOKEN_URL;
588
585
  const body =
589
586
  `grant_type=refresh_token&` +
590
- `refresh_token=${refreshToken?.value}&` +
587
+ `refresh_token=${LocalState.refreshToken?.value}&` +
591
588
  `client_id=${CLIENT_ID}`;
592
589
 
593
- const response = await fetch(url, {
590
+ const response = await fetch(TOKEN_URL, {
594
591
  method: "POST",
595
592
  body,
596
593
  headers: {
@@ -617,10 +614,9 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
617
614
  LocalState.accessToken = accessToken;
618
615
 
619
616
  if (refresh_token) {
620
- const refreshToken: RefreshToken = {
617
+ LocalState.refreshToken = {
621
618
  value: refresh_token,
622
619
  };
623
- LocalState.refreshToken = refreshToken;
624
620
  }
625
621
 
626
622
  if (scope) {
@@ -666,7 +662,6 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
666
662
  console.warn("No authorization grant code is being passed.");
667
663
  }
668
664
 
669
- const url = TOKEN_URL;
670
665
  const body =
671
666
  `grant_type=authorization_code&` +
672
667
  `code=${encodeURIComponent(authorizationCode || "")}&` +
@@ -674,7 +669,7 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
674
669
  `client_id=${encodeURIComponent(CLIENT_ID)}&` +
675
670
  `code_verifier=${codeVerifier}`;
676
671
 
677
- const response = await fetch(url, {
672
+ const response = await fetch(TOKEN_URL, {
678
673
  method: "POST",
679
674
  body,
680
675
  headers: {
@@ -709,10 +704,9 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
709
704
  LocalState.accessToken = accessToken;
710
705
 
711
706
  if (refresh_token) {
712
- const refreshToken: RefreshToken = {
707
+ LocalState.refreshToken = {
713
708
  value: refresh_token,
714
709
  };
715
- LocalState.refreshToken = refreshToken;
716
710
  }
717
711
 
718
712
  if (scope) {
@@ -816,7 +810,7 @@ export async function loginOrRefreshIfRequired(): Promise<boolean> {
816
810
 
817
811
  export async function login(props?: LoginProps): Promise<boolean> {
818
812
  const urlToOpen = await getAuthURL(props?.scopes);
819
- open(urlToOpen);
813
+ await open(urlToOpen);
820
814
  // TODO: log url only if on system where it's unreliable/unavailable
821
815
  // console.log(`💁 Opened ${urlToOpen}`);
822
816
  let server;
@@ -921,15 +915,14 @@ export async function refreshToken(): Promise<boolean> {
921
915
 
922
916
  export async function logout(): Promise<void> {
923
917
  throwIfNotInitialised();
924
- const { refreshToken } = LocalState;
925
- if (!refreshToken) {
918
+ if (!LocalState.refreshToken) {
926
919
  console.log("Not logged in, exiting...");
927
920
  return;
928
921
  }
929
922
  const body =
930
923
  `client_id=${encodeURIComponent(CLIENT_ID)}&` +
931
924
  `token_type_hint=refresh_token&` +
932
- `token=${encodeURIComponent(refreshToken?.value || "")}`;
925
+ `token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
933
926
 
934
927
  const response = await fetch(REVOKE_URL, {
935
928
  method: "POST",