wrangler 2.0.29 → 2.1.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/miniflare-dist/index.mjs +1136 -372
- package/package.json +3 -2
- package/src/__tests__/helpers/mock-cfetch.ts +39 -19
- package/src/__tests__/helpers/mock-console.ts +11 -2
- package/src/__tests__/helpers/msw/handlers/index.ts +13 -0
- package/src/__tests__/helpers/msw/handlers/namespaces.ts +104 -0
- package/src/__tests__/helpers/msw/handlers/oauth.ts +36 -0
- package/src/__tests__/helpers/msw/handlers/r2.ts +80 -0
- package/src/__tests__/helpers/msw/handlers/user.ts +63 -0
- package/src/__tests__/helpers/msw/index.ts +4 -0
- package/src/__tests__/index.test.ts +9 -7
- package/src/__tests__/init.test.ts +356 -5
- package/src/__tests__/jest.setup.ts +16 -0
- package/src/__tests__/middleware.test.ts +768 -0
- package/src/__tests__/pages.test.ts +11 -12
- package/src/__tests__/publish.test.ts +516 -438
- package/src/__tests__/r2.test.ts +128 -93
- package/src/__tests__/secret.test.ts +78 -0
- package/src/__tests__/tail.test.ts +47 -74
- package/src/__tests__/whoami.test.tsx +49 -64
- package/src/api/dev.ts +23 -4
- package/src/bundle.ts +225 -1
- package/src/dev/dev.tsx +3 -1
- package/src/dev/local.tsx +2 -2
- package/src/dev/remote.tsx +6 -3
- package/src/dev/start-server.ts +11 -7
- package/src/dev/use-esbuild.ts +4 -0
- package/src/dev.tsx +6 -16
- package/src/dialogs.tsx +12 -0
- package/src/index.tsx +95 -4
- package/src/init.ts +286 -11
- package/src/miniflare-cli/assets.ts +130 -415
- package/src/miniflare-cli/index.ts +3 -1
- package/src/pages/dev.tsx +5 -1
- package/src/pages/hash.tsx +13 -0
- package/src/pages/upload.tsx +3 -18
- package/src/publish.ts +38 -4
- package/src/tail/filters.ts +1 -5
- package/src/tail/index.ts +6 -3
- package/templates/middleware/common.ts +62 -0
- package/templates/middleware/loader-modules.ts +84 -0
- package/templates/middleware/loader-sw.ts +213 -0
- package/templates/middleware/middleware-pretty-error.ts +40 -0
- package/templates/middleware/middleware-scheduled.ts +14 -0
- package/wrangler-dist/cli.js +65900 -65432
package/src/pages/upload.tsx
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
-
import {
|
|
3
|
-
basename,
|
|
4
|
-
dirname,
|
|
5
|
-
extname,
|
|
6
|
-
join,
|
|
7
|
-
relative,
|
|
8
|
-
resolve,
|
|
9
|
-
sep,
|
|
10
|
-
} from "node:path";
|
|
11
|
-
import { hash as blake3hash } from "blake3-wasm";
|
|
2
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
12
3
|
import { render, Text } from "ink";
|
|
13
4
|
import Spinner from "ink-spinner";
|
|
14
5
|
import { getType } from "mime";
|
|
@@ -25,6 +16,7 @@ import {
|
|
|
25
16
|
MAX_CHECK_MISSING_ATTEMPTS,
|
|
26
17
|
MAX_UPLOAD_ATTEMPTS,
|
|
27
18
|
} from "./constants";
|
|
19
|
+
import { hashFile } from "./hash";
|
|
28
20
|
import { pagesBetaWarning } from "./utils";
|
|
29
21
|
import type { UploadPayloadFile, YargsOptionsToInterface } from "./types";
|
|
30
22
|
import type { Argv } from "yargs";
|
|
@@ -144,11 +136,6 @@ export const upload = async (
|
|
|
144
136
|
} else {
|
|
145
137
|
const name = relative(startingDir, filepath).split(sep).join("/");
|
|
146
138
|
|
|
147
|
-
const fileContent = await readFile(filepath);
|
|
148
|
-
|
|
149
|
-
const base64Content = fileContent.toString("base64");
|
|
150
|
-
const extension = extname(basename(name)).substring(1);
|
|
151
|
-
|
|
152
139
|
if (filestat.size > 25 * 1024 * 1024) {
|
|
153
140
|
throw new FatalError(
|
|
154
141
|
`Error: Pages only supports files up to ${prettyBytes(
|
|
@@ -163,9 +150,7 @@ export const upload = async (
|
|
|
163
150
|
path: filepath,
|
|
164
151
|
contentType: getType(name) || "application/octet-stream",
|
|
165
152
|
sizeInBytes: filestat.size,
|
|
166
|
-
hash:
|
|
167
|
-
.toString("hex")
|
|
168
|
-
.slice(0, 32),
|
|
153
|
+
hash: hashFile(filepath),
|
|
169
154
|
});
|
|
170
155
|
}
|
|
171
156
|
})
|
package/src/publish.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { printBundleSize } from "./bundle-reporter";
|
|
|
8
8
|
import { fetchListResult, fetchResult } from "./cfetch";
|
|
9
9
|
import { printBindings } from "./config";
|
|
10
10
|
import { createWorkerUploadForm } from "./create-worker-upload-form";
|
|
11
|
-
import { confirm } from "./dialogs";
|
|
11
|
+
import { confirm, fromDashMessagePrompt } from "./dialogs";
|
|
12
12
|
import { getMigrationsToUpload } from "./durable";
|
|
13
13
|
import { logger } from "./logger";
|
|
14
14
|
import { getMetricsUsageHeaders } from "./metrics";
|
|
@@ -218,11 +218,38 @@ Update them to point to this script instead?`;
|
|
|
218
218
|
|
|
219
219
|
export default async function publish(props: Props): Promise<void> {
|
|
220
220
|
// TODO: warn if git/hg has uncommitted changes
|
|
221
|
-
const { config, accountId } = props;
|
|
221
|
+
const { config, accountId, name } = props;
|
|
222
|
+
if (accountId && name) {
|
|
223
|
+
try {
|
|
224
|
+
const serviceMetaData = await fetchResult(
|
|
225
|
+
`/accounts/${accountId}/workers/services/${name}`
|
|
226
|
+
);
|
|
227
|
+
const { default_environment } = serviceMetaData as {
|
|
228
|
+
default_environment: {
|
|
229
|
+
script: { last_deployed_from: "dash" | "wrangler" | "api" };
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
if (
|
|
234
|
+
(await fromDashMessagePrompt(
|
|
235
|
+
default_environment.script.last_deployed_from
|
|
236
|
+
)) === false
|
|
237
|
+
)
|
|
238
|
+
return;
|
|
239
|
+
} catch (e) {
|
|
240
|
+
// code: 10090, message: workers.api.error.service_not_found
|
|
241
|
+
// is thrown from the above fetchResult on the first publish of a Worker
|
|
242
|
+
if ((e as { code?: number }).code !== 10090) {
|
|
243
|
+
logger.error(e);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
222
247
|
|
|
223
248
|
if (!(props.compatibilityDate || config.compatibility_date)) {
|
|
224
249
|
const compatibilityDateStr = `${new Date().getFullYear()}-${(
|
|
225
|
-
new Date().getMonth() +
|
|
250
|
+
new Date().getMonth() +
|
|
251
|
+
1 +
|
|
252
|
+
""
|
|
226
253
|
).padStart(2, "0")}-${(new Date().getDate() + "").padStart(2, "0")}`;
|
|
227
254
|
|
|
228
255
|
throw new Error(`A compatibility_date is required when publishing. Add the following to your wrangler.toml file:.
|
|
@@ -397,6 +424,9 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
397
424
|
// facades on top of it
|
|
398
425
|
workerDefinitions: undefined,
|
|
399
426
|
firstPartyWorkerDevFacade: false,
|
|
427
|
+
// We want to know if the build is for development or publishing
|
|
428
|
+
// This could potentially cause issues as we no longer have identical behaviour between dev and publish?
|
|
429
|
+
targetConsumer: "publish",
|
|
400
430
|
}
|
|
401
431
|
);
|
|
402
432
|
|
|
@@ -475,10 +505,14 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
475
505
|
keep_bindings: true,
|
|
476
506
|
};
|
|
477
507
|
|
|
478
|
-
|
|
508
|
+
// As this is not deterministic for testing, we detect if in a jest environment and run asynchronously
|
|
509
|
+
// We do not care about the timing outside of testing
|
|
510
|
+
const bundleSizePromise = printBundleSize(
|
|
479
511
|
{ name: path.basename(resolvedEntryPointPath), content: content },
|
|
480
512
|
modules
|
|
481
513
|
);
|
|
514
|
+
if (process.env.JEST_WORKER_ID !== undefined) await bundleSizePromise;
|
|
515
|
+
else void bundleSizePromise;
|
|
482
516
|
|
|
483
517
|
const withoutStaticAssets = {
|
|
484
518
|
...bindings,
|
package/src/tail/filters.ts
CHANGED
|
@@ -122,7 +122,6 @@ type QueryFilter = {
|
|
|
122
122
|
*/
|
|
123
123
|
export type TailFilterMessage = {
|
|
124
124
|
filters: TailAPIFilter[];
|
|
125
|
-
debug: boolean;
|
|
126
125
|
};
|
|
127
126
|
|
|
128
127
|
/**
|
|
@@ -130,12 +129,10 @@ export type TailFilterMessage = {
|
|
|
130
129
|
* into a message that we can send to the tail worker.
|
|
131
130
|
*
|
|
132
131
|
* @param cliFilters An object containing all the filters passed in from the CLI
|
|
133
|
-
* @param debug Whether or not we should be in debug mode
|
|
134
132
|
* @returns A filter message ready to be sent to the tail worker
|
|
135
133
|
*/
|
|
136
134
|
export function translateCLICommandToFilterMessage(
|
|
137
|
-
cliFilters: TailCLIFilters
|
|
138
|
-
debug: boolean
|
|
135
|
+
cliFilters: TailCLIFilters
|
|
139
136
|
): TailFilterMessage {
|
|
140
137
|
const apiFilters: TailAPIFilter[] = [];
|
|
141
138
|
|
|
@@ -165,7 +162,6 @@ export function translateCLICommandToFilterMessage(
|
|
|
165
162
|
|
|
166
163
|
return {
|
|
167
164
|
filters: apiFilters,
|
|
168
|
-
debug,
|
|
169
165
|
};
|
|
170
166
|
}
|
|
171
167
|
|
package/src/tail/index.ts
CHANGED
|
@@ -69,13 +69,15 @@ function makeDeleteTailUrl(
|
|
|
69
69
|
*
|
|
70
70
|
* @param accountId the account ID associated with the worker to tail
|
|
71
71
|
* @param workerName the name of the worker to tail
|
|
72
|
-
* @param
|
|
72
|
+
* @param filters A list of `TailAPIFilters` given to the tail
|
|
73
|
+
* @param debug Flag to run tail in debug mode
|
|
73
74
|
* @returns a websocket connection, an expiration, and a function to call to delete the tail
|
|
74
75
|
*/
|
|
75
76
|
export async function createTail(
|
|
76
77
|
accountId: string,
|
|
77
78
|
workerName: string,
|
|
78
|
-
|
|
79
|
+
filters: TailFilterMessage,
|
|
80
|
+
debug: boolean,
|
|
79
81
|
env: string | undefined
|
|
80
82
|
): Promise<{
|
|
81
83
|
tail: WebSocket;
|
|
@@ -90,6 +92,7 @@ export async function createTail(
|
|
|
90
92
|
expires_at: expiration,
|
|
91
93
|
} = await fetchResult<TailCreationApiResponse>(createTailUrl, {
|
|
92
94
|
method: "POST",
|
|
95
|
+
body: JSON.stringify(filters),
|
|
93
96
|
});
|
|
94
97
|
|
|
95
98
|
// delete the tail (not yet!)
|
|
@@ -109,7 +112,7 @@ export async function createTail(
|
|
|
109
112
|
// send filters when we open up
|
|
110
113
|
tail.on("open", function () {
|
|
111
114
|
tail.send(
|
|
112
|
-
JSON.stringify(
|
|
115
|
+
JSON.stringify({ debug: debug }),
|
|
113
116
|
{ binary: false, compress: false, mask: false, fin: true },
|
|
114
117
|
(err) => {
|
|
115
118
|
if (err) {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type Awaitable<T> = T | Promise<T>;
|
|
2
|
+
// TODO: allow dispatching more events?
|
|
3
|
+
export type Dispatcher = (
|
|
4
|
+
type: "scheduled",
|
|
5
|
+
init: { cron?: string }
|
|
6
|
+
) => Awaitable<void>;
|
|
7
|
+
|
|
8
|
+
export interface MiddlewareContext {
|
|
9
|
+
dispatch: Dispatcher;
|
|
10
|
+
next(request: Request, env: any): Awaitable<Response>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type Middleware = (
|
|
14
|
+
request: Request,
|
|
15
|
+
env: any,
|
|
16
|
+
ctx: ExecutionContext,
|
|
17
|
+
middlewareCtx: MiddlewareContext
|
|
18
|
+
) => Awaitable<Response>;
|
|
19
|
+
|
|
20
|
+
const __facade_middleware__: Middleware[] = [];
|
|
21
|
+
|
|
22
|
+
// The register functions allow for the insertion of one or many middleware,
|
|
23
|
+
// We register internal middleware first in the stack, but have no way of controlling
|
|
24
|
+
// the order that addMiddleware is run in service workers so need an internal function.
|
|
25
|
+
export function __facade_register__(...args: (Middleware | Middleware[])[]) {
|
|
26
|
+
__facade_middleware__.push(...args.flat());
|
|
27
|
+
}
|
|
28
|
+
export function __facade_registerInternal__(
|
|
29
|
+
...args: (Middleware | Middleware[])[]
|
|
30
|
+
) {
|
|
31
|
+
__facade_middleware__.unshift(...args.flat());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function __facade_invokeChain__(
|
|
35
|
+
request: Request,
|
|
36
|
+
env: any,
|
|
37
|
+
ctx: ExecutionContext,
|
|
38
|
+
dispatch: Dispatcher,
|
|
39
|
+
middlewareChain: Middleware[]
|
|
40
|
+
): Awaitable<Response> {
|
|
41
|
+
const [head, ...tail] = middlewareChain;
|
|
42
|
+
const middlewareCtx: MiddlewareContext = {
|
|
43
|
+
dispatch,
|
|
44
|
+
next(newRequest, newEnv) {
|
|
45
|
+
return __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
return head(request, env, ctx, middlewareCtx);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function __facade_invoke__(
|
|
52
|
+
request: Request,
|
|
53
|
+
env: any,
|
|
54
|
+
ctx: ExecutionContext,
|
|
55
|
+
dispatch: Dispatcher,
|
|
56
|
+
finalMiddleware: Middleware
|
|
57
|
+
): Awaitable<Response> {
|
|
58
|
+
return __facade_invokeChain__(request, env, ctx, dispatch, [
|
|
59
|
+
...__facade_middleware__,
|
|
60
|
+
finalMiddleware,
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// // This loads all middlewares exposed on the middleware object
|
|
2
|
+
// // and then starts the invocation chain.
|
|
3
|
+
// // The big idea is that we can add these to the middleware export dynamically
|
|
4
|
+
// // through wrangler, or we can potentially let users directly add them as a sort
|
|
5
|
+
// // of "plugin" system.
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Dispatcher,
|
|
9
|
+
Middleware,
|
|
10
|
+
__facade_invoke__,
|
|
11
|
+
__facade_register__,
|
|
12
|
+
} from "./common";
|
|
13
|
+
|
|
14
|
+
// @ts-expect-error We'll swap in the entry point during build
|
|
15
|
+
import worker from "__ENTRY_POINT__";
|
|
16
|
+
|
|
17
|
+
// We need to preserve all of the exports from the worker
|
|
18
|
+
// @ts-expect-error We'll swap in the entry point during build
|
|
19
|
+
export * from "__ENTRY_POINT__";
|
|
20
|
+
|
|
21
|
+
class __Facade_ScheduledController__ implements ScheduledController {
|
|
22
|
+
#noRetry: ScheduledController["noRetry"];
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
readonly scheduledTime: number,
|
|
26
|
+
readonly cron: string,
|
|
27
|
+
noRetry: ScheduledController["noRetry"]
|
|
28
|
+
) {
|
|
29
|
+
this.#noRetry = noRetry;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
noRetry() {
|
|
33
|
+
if (!(this instanceof __Facade_ScheduledController__)) {
|
|
34
|
+
throw new TypeError("Illegal invocation");
|
|
35
|
+
}
|
|
36
|
+
// Need to call native method immediately in case uncaught error thrown
|
|
37
|
+
this.#noRetry();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const __facade_modules_fetch__: Middleware = function (request, env, ctx) {
|
|
42
|
+
if (worker.fetch === undefined) throw new Error("No fetch handler!"); // TODO: proper error message
|
|
43
|
+
return worker.fetch(request, env, ctx);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const facade: ExportedHandler<unknown> = {
|
|
47
|
+
fetch(request, env, ctx) {
|
|
48
|
+
// Get the chain of middleware from the worker object
|
|
49
|
+
if (worker.middleware && worker.middleware.length > 0) {
|
|
50
|
+
for (const middleware of worker.middleware) {
|
|
51
|
+
__facade_register__(middleware);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const __facade_modules_dispatch__: Dispatcher = function (type, init) {
|
|
55
|
+
if (type === "scheduled" && worker.scheduled !== undefined) {
|
|
56
|
+
const controller = new __Facade_ScheduledController__(
|
|
57
|
+
Date.now(),
|
|
58
|
+
init.cron ?? "",
|
|
59
|
+
() => {}
|
|
60
|
+
);
|
|
61
|
+
return worker.scheduled(controller, env, ctx);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return __facade_invoke__(
|
|
66
|
+
request,
|
|
67
|
+
env,
|
|
68
|
+
ctx,
|
|
69
|
+
__facade_modules_dispatch__,
|
|
70
|
+
__facade_modules_fetch__
|
|
71
|
+
);
|
|
72
|
+
} else {
|
|
73
|
+
// We didn't have any middleware so we can skip the invocation chain,
|
|
74
|
+
// and just call the fetch handler directly
|
|
75
|
+
|
|
76
|
+
// We "don't care" if this is undefined as we want to have the same behaviour
|
|
77
|
+
// as if the worker completely bypassed middleware.
|
|
78
|
+
return worker.fetch!(request, env, ctx);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
scheduled: worker.scheduled,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export default facade;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { Awaitable, Dispatcher, Middleware, __facade_invoke__ } from "./common";
|
|
2
|
+
export { __facade_register__, __facade_registerInternal__ } from "./common";
|
|
3
|
+
|
|
4
|
+
// Miniflare's `EventTarget` follows the spec and doesn't allow exceptions to
|
|
5
|
+
// be caught by `dispatchEvent`. Instead it has a custom`ThrowingEventTarget`
|
|
6
|
+
// class that rethrows errors from event listeners in `dispatchEvent`.
|
|
7
|
+
// We'd like errors to be propagated to the top-level `addEventListener`, so
|
|
8
|
+
// we'd like to use `ThrowingEventTarget`. Unfortunately, `ThrowingEventTarget`
|
|
9
|
+
// isn't exposed on the global scope, but `WorkerGlobalScope` (which extends
|
|
10
|
+
// `ThrowingEventTarget`) is. Therefore, we get at it in this nasty way.
|
|
11
|
+
let __FACADE_EVENT_TARGET__: EventTarget;
|
|
12
|
+
if ((globalThis as any).MINIFLARE) {
|
|
13
|
+
__FACADE_EVENT_TARGET__ = new (Object.getPrototypeOf(WorkerGlobalScope))();
|
|
14
|
+
} else {
|
|
15
|
+
__FACADE_EVENT_TARGET__ = new EventTarget();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare global {
|
|
19
|
+
var __facade_addEventListener__: (
|
|
20
|
+
type: string,
|
|
21
|
+
listener: EventListenerOrEventListenerObject,
|
|
22
|
+
options?: EventTargetAddEventListenerOptions | boolean
|
|
23
|
+
) => void;
|
|
24
|
+
var __facade_removeEventListener__: (
|
|
25
|
+
type: string,
|
|
26
|
+
listener: EventListenerOrEventListenerObject,
|
|
27
|
+
options?: EventTargetEventListenerOptions | boolean
|
|
28
|
+
) => void;
|
|
29
|
+
var __facade_dispatchEvent__: (event: Event) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function __facade_isSpecialEvent__(type: string) {
|
|
33
|
+
return type === "fetch" || type === "scheduled";
|
|
34
|
+
}
|
|
35
|
+
globalThis.__facade_addEventListener__ = function (type, listener, options) {
|
|
36
|
+
if (__facade_isSpecialEvent__(type)) {
|
|
37
|
+
__FACADE_EVENT_TARGET__.addEventListener(type, listener, options);
|
|
38
|
+
} else {
|
|
39
|
+
globalThis.addEventListener(type as any, listener, options);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
globalThis.__facade_removeEventListener__ = function (type, listener, options) {
|
|
43
|
+
if (__facade_isSpecialEvent__(type)) {
|
|
44
|
+
__FACADE_EVENT_TARGET__.removeEventListener(type, listener, options);
|
|
45
|
+
} else {
|
|
46
|
+
globalThis.removeEventListener(type as any, listener, options);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
globalThis.__facade_dispatchEvent__ = function (event) {
|
|
50
|
+
if (__facade_isSpecialEvent__(event.type)) {
|
|
51
|
+
__FACADE_EVENT_TARGET__.dispatchEvent(event);
|
|
52
|
+
} else {
|
|
53
|
+
globalThis.dispatchEvent(event as any);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const __facade_waitUntil__ = Symbol("__facade_waitUntil__");
|
|
58
|
+
const __facade_response__ = Symbol("__facade_response__");
|
|
59
|
+
const __facade_dispatched__ = Symbol("__facade_dispatched__");
|
|
60
|
+
|
|
61
|
+
class __Facade_ExtendableEvent__ extends Event {
|
|
62
|
+
[__facade_waitUntil__]: Awaitable<unknown>[] = [];
|
|
63
|
+
|
|
64
|
+
waitUntil(promise: Awaitable<any>) {
|
|
65
|
+
if (!(this instanceof __Facade_ExtendableEvent__)) {
|
|
66
|
+
throw new TypeError("Illegal invocation");
|
|
67
|
+
}
|
|
68
|
+
this[__facade_waitUntil__].push(promise);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface FetchEventInit extends EventInit {
|
|
73
|
+
request: Request;
|
|
74
|
+
passThroughOnException: FetchEvent["passThroughOnException"];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class __Facade_FetchEvent__ extends __Facade_ExtendableEvent__ {
|
|
78
|
+
#request: Request;
|
|
79
|
+
#passThroughOnException: FetchEvent["passThroughOnException"];
|
|
80
|
+
[__facade_response__]?: Awaitable<Response>;
|
|
81
|
+
[__facade_dispatched__] = false;
|
|
82
|
+
|
|
83
|
+
constructor(type: "fetch", init: FetchEventInit) {
|
|
84
|
+
super(type);
|
|
85
|
+
this.#request = init.request;
|
|
86
|
+
this.#passThroughOnException = init.passThroughOnException;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get request() {
|
|
90
|
+
return this.#request;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
respondWith(response: Awaitable<Response>) {
|
|
94
|
+
if (!(this instanceof __Facade_FetchEvent__)) {
|
|
95
|
+
throw new TypeError("Illegal invocation");
|
|
96
|
+
}
|
|
97
|
+
if (this[__facade_response__] !== undefined) {
|
|
98
|
+
throw new DOMException(
|
|
99
|
+
"FetchEvent.respondWith() has already been called; it can only be called once.",
|
|
100
|
+
"InvalidStateError"
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (this[__facade_dispatched__]) {
|
|
104
|
+
throw new DOMException(
|
|
105
|
+
"Too late to call FetchEvent.respondWith(). It must be called synchronously in the event handler.",
|
|
106
|
+
"InvalidStateError"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
this.stopImmediatePropagation();
|
|
110
|
+
this[__facade_response__] = response;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
passThroughOnException() {
|
|
114
|
+
if (!(this instanceof __Facade_FetchEvent__)) {
|
|
115
|
+
throw new TypeError("Illegal invocation");
|
|
116
|
+
}
|
|
117
|
+
// Need to call native method immediately in case uncaught error thrown
|
|
118
|
+
this.#passThroughOnException();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface ScheduledEventInit extends EventInit {
|
|
123
|
+
scheduledTime: number;
|
|
124
|
+
cron: string;
|
|
125
|
+
noRetry: ScheduledEvent["noRetry"];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
class __Facade_ScheduledEvent__ extends __Facade_ExtendableEvent__ {
|
|
129
|
+
#scheduledTime: number;
|
|
130
|
+
#cron: string;
|
|
131
|
+
#noRetry: ScheduledEvent["noRetry"];
|
|
132
|
+
|
|
133
|
+
constructor(type: "scheduled", init: ScheduledEventInit) {
|
|
134
|
+
super(type);
|
|
135
|
+
this.#scheduledTime = init.scheduledTime;
|
|
136
|
+
this.#cron = init.cron;
|
|
137
|
+
this.#noRetry = init.noRetry;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get scheduledTime() {
|
|
141
|
+
return this.#scheduledTime;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get cron() {
|
|
145
|
+
return this.#cron;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
noRetry() {
|
|
149
|
+
if (!(this instanceof __Facade_ScheduledEvent__)) {
|
|
150
|
+
throw new TypeError("Illegal invocation");
|
|
151
|
+
}
|
|
152
|
+
// Need to call native method immediately in case uncaught error thrown
|
|
153
|
+
this.#noRetry();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
globalThis.addEventListener("fetch", (event) => {
|
|
158
|
+
const ctx: ExecutionContext = {
|
|
159
|
+
waitUntil: event.waitUntil.bind(event),
|
|
160
|
+
passThroughOnException: event.passThroughOnException.bind(event),
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const __facade_sw_dispatch__: Dispatcher = function (type, init) {
|
|
164
|
+
if (type === "scheduled") {
|
|
165
|
+
const facadeEvent = new __Facade_ScheduledEvent__("scheduled", {
|
|
166
|
+
scheduledTime: Date.now(),
|
|
167
|
+
cron: init.cron ?? "",
|
|
168
|
+
noRetry() {},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
__FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent);
|
|
172
|
+
event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__]));
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const __facade_sw_fetch__: Middleware = function (request, _env, ctx) {
|
|
177
|
+
const facadeEvent = new __Facade_FetchEvent__("fetch", {
|
|
178
|
+
request,
|
|
179
|
+
passThroughOnException: ctx.passThroughOnException,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
__FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent);
|
|
183
|
+
facadeEvent[__facade_dispatched__] = true;
|
|
184
|
+
event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__]));
|
|
185
|
+
|
|
186
|
+
const response = facadeEvent[__facade_response__];
|
|
187
|
+
if (response === undefined) {
|
|
188
|
+
throw new Error("No response!"); // TODO: proper error message
|
|
189
|
+
}
|
|
190
|
+
return response;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
event.respondWith(
|
|
194
|
+
__facade_invoke__(
|
|
195
|
+
event.request,
|
|
196
|
+
globalThis,
|
|
197
|
+
ctx,
|
|
198
|
+
__facade_sw_dispatch__,
|
|
199
|
+
__facade_sw_fetch__
|
|
200
|
+
)
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
globalThis.addEventListener("scheduled", (event) => {
|
|
205
|
+
const facadeEvent = new __Facade_ScheduledEvent__("scheduled", {
|
|
206
|
+
scheduledTime: event.scheduledTime,
|
|
207
|
+
cron: event.cron,
|
|
208
|
+
noRetry: event.noRetry.bind(event),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
__FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent);
|
|
212
|
+
event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__]));
|
|
213
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Middleware } from "./common";
|
|
2
|
+
|
|
3
|
+
// A middleware has to be a function of type Middleware
|
|
4
|
+
const prettyError: Middleware = async (request, env, _ctx, middlewareCtx) => {
|
|
5
|
+
try {
|
|
6
|
+
const response = await middlewareCtx.next(request, env);
|
|
7
|
+
return response;
|
|
8
|
+
} catch (e: any) {
|
|
9
|
+
const html = `
|
|
10
|
+
<!DOCTYPE html>
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="UTF-8">
|
|
14
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
15
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
16
|
+
<title>Error 🚨</title>
|
|
17
|
+
<style>
|
|
18
|
+
pre {
|
|
19
|
+
margin: 16px auto;
|
|
20
|
+
max-width: 600px;
|
|
21
|
+
background-color: #eeeeee;
|
|
22
|
+
border-radius: 4px;
|
|
23
|
+
padding: 16px;
|
|
24
|
+
}
|
|
25
|
+
</style>
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<pre>${e.stack}</pre>
|
|
29
|
+
</body>
|
|
30
|
+
</html>
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
return new Response(html, {
|
|
34
|
+
status: 500,
|
|
35
|
+
headers: { "Content-Type": "text/html;charset=utf-8" },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default prettyError;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Middleware } from "./common";
|
|
2
|
+
|
|
3
|
+
// A middleware has to be a function of type Middleware
|
|
4
|
+
const scheduled: Middleware = async (request, env, _ctx, middlewareCtx) => {
|
|
5
|
+
const url = new URL(request.url);
|
|
6
|
+
if (url.pathname === "/__scheduled") {
|
|
7
|
+
const cron = url.searchParams.get("cron") ?? "";
|
|
8
|
+
await middlewareCtx.dispatch("scheduled", { cron });
|
|
9
|
+
return new Response("OK");
|
|
10
|
+
}
|
|
11
|
+
return middlewareCtx.next(request, env);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default scheduled;
|