wrangler 2.0.27 → 2.1.0
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 +1 -1
- package/miniflare-dist/index.mjs +1141 -369
- package/package.json +6 -4
- package/src/__tests__/api-dev.test.ts +19 -0
- package/src/__tests__/configuration.test.ts +27 -27
- package/src/__tests__/dev.test.tsx +8 -6
- package/src/__tests__/helpers/hello-world-worker.js +5 -0
- package/src/__tests__/helpers/mock-cfetch.ts +4 -4
- package/src/__tests__/helpers/mock-console.ts +11 -2
- package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
- package/src/__tests__/helpers/mock-known-routes.ts +7 -0
- package/src/__tests__/index.test.ts +37 -37
- package/src/__tests__/init.test.ts +356 -5
- package/src/__tests__/jest.setup.ts +13 -0
- package/src/__tests__/middleware.test.ts +768 -0
- package/src/__tests__/pages.test.ts +829 -104
- package/src/__tests__/paths.test.ts +17 -0
- package/src/__tests__/publish.test.ts +512 -445
- package/src/__tests__/tail.test.ts +79 -72
- package/src/__tests__/test-old-node-version.js +3 -3
- package/src/__tests__/worker-namespace.test.ts +37 -35
- package/src/api/dev.ts +93 -28
- package/src/bundle.ts +239 -12
- package/src/cfetch/internal.ts +64 -3
- package/src/cli.ts +1 -1
- package/src/config/environment.ts +1 -1
- package/src/config/index.ts +4 -4
- package/src/config/validation.ts +3 -3
- package/src/create-worker-upload-form.ts +29 -26
- package/src/dev/dev.tsx +3 -1
- package/src/dev/local.tsx +319 -171
- package/src/dev/remote.tsx +16 -4
- package/src/dev/start-server.ts +416 -0
- package/src/dev/use-esbuild.ts +4 -0
- package/src/dev.tsx +340 -166
- package/src/dialogs.tsx +12 -0
- package/src/{worker-namespace.ts → dispatch-namespace.ts} +18 -18
- package/src/entry.ts +2 -1
- package/src/index.tsx +59 -12
- package/src/init.ts +291 -16
- package/src/metrics/send-event.ts +6 -5
- package/src/miniflare-cli/assets.ts +130 -476
- package/src/miniflare-cli/index.ts +39 -33
- package/src/pages/constants.ts +3 -0
- package/src/pages/dev.tsx +8 -3
- package/src/pages/functions/buildPlugin.ts +2 -1
- package/src/pages/functions/buildWorker.ts +2 -1
- package/src/pages/functions/routes-transformation.test.ts +12 -1
- package/src/pages/functions/routes-transformation.ts +7 -1
- package/src/pages/hash.tsx +13 -0
- package/src/pages/publish.tsx +82 -38
- package/src/pages/upload.tsx +3 -18
- package/src/paths.ts +20 -1
- package/src/publish.ts +49 -8
- package/src/tail/filters.ts +1 -5
- package/src/tail/index.ts +6 -3
- package/src/worker.ts +10 -9
- package/src/zones.ts +91 -0
- 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.d.ts +22 -8
- package/wrangler-dist/cli.js +71020 -65212
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) {
|
package/src/worker.ts
CHANGED
|
@@ -65,14 +65,14 @@ export interface CfModule {
|
|
|
65
65
|
/**
|
|
66
66
|
* A map of variable names to values.
|
|
67
67
|
*/
|
|
68
|
-
interface CfVars {
|
|
68
|
+
export interface CfVars {
|
|
69
69
|
[key: string]: unknown;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* A KV namespace.
|
|
74
74
|
*/
|
|
75
|
-
interface CfKvNamespace {
|
|
75
|
+
export interface CfKvNamespace {
|
|
76
76
|
binding: string;
|
|
77
77
|
id: string;
|
|
78
78
|
}
|
|
@@ -81,7 +81,7 @@ interface CfKvNamespace {
|
|
|
81
81
|
* A binding to a wasm module (in service-worker format)
|
|
82
82
|
*/
|
|
83
83
|
|
|
84
|
-
interface CfWasmModuleBindings {
|
|
84
|
+
export interface CfWasmModuleBindings {
|
|
85
85
|
[key: string]: string;
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -89,7 +89,7 @@ interface CfWasmModuleBindings {
|
|
|
89
89
|
* A binding to a text blob (in service-worker format)
|
|
90
90
|
*/
|
|
91
91
|
|
|
92
|
-
interface CfTextBlobBindings {
|
|
92
|
+
export interface CfTextBlobBindings {
|
|
93
93
|
[key: string]: string;
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -97,21 +97,21 @@ interface CfTextBlobBindings {
|
|
|
97
97
|
* A binding to a data blob (in service-worker format)
|
|
98
98
|
*/
|
|
99
99
|
|
|
100
|
-
interface CfDataBlobBindings {
|
|
100
|
+
export interface CfDataBlobBindings {
|
|
101
101
|
[key: string]: string;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* A Durable Object.
|
|
106
106
|
*/
|
|
107
|
-
interface CfDurableObject {
|
|
107
|
+
export interface CfDurableObject {
|
|
108
108
|
name: string;
|
|
109
109
|
class_name: string;
|
|
110
110
|
script_name?: string;
|
|
111
111
|
environment?: string;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
interface CfR2Bucket {
|
|
114
|
+
export interface CfR2Bucket {
|
|
115
115
|
binding: string;
|
|
116
116
|
bucket_name: string;
|
|
117
117
|
}
|
|
@@ -122,7 +122,7 @@ interface CfService {
|
|
|
122
122
|
environment?: string;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
interface
|
|
125
|
+
interface CfDispatchNamespace {
|
|
126
126
|
binding: string;
|
|
127
127
|
namespace: string;
|
|
128
128
|
}
|
|
@@ -183,7 +183,7 @@ export interface CfWorkerInit {
|
|
|
183
183
|
durable_objects: { bindings: CfDurableObject[] } | undefined;
|
|
184
184
|
r2_buckets: CfR2Bucket[] | undefined;
|
|
185
185
|
services: CfService[] | undefined;
|
|
186
|
-
|
|
186
|
+
dispatch_namespaces: CfDispatchNamespace[] | undefined;
|
|
187
187
|
logfwdr: CfLogfwdr | undefined;
|
|
188
188
|
unsafe: CfUnsafeBinding[] | undefined;
|
|
189
189
|
};
|
|
@@ -191,6 +191,7 @@ export interface CfWorkerInit {
|
|
|
191
191
|
compatibility_date: string | undefined;
|
|
192
192
|
compatibility_flags: string[] | undefined;
|
|
193
193
|
usage_model: "bundled" | "unbound" | undefined;
|
|
194
|
+
keep_bindings: boolean | undefined;
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
export interface CfWorkerContext {
|
package/src/zones.ts
CHANGED
|
@@ -74,3 +74,94 @@ export async function getZoneIdFromHost(host: string): Promise<string> {
|
|
|
74
74
|
|
|
75
75
|
throw new Error(`Could not find zone for ${host}`);
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* An object holding information about an assigned worker route, returned from the API
|
|
80
|
+
*/
|
|
81
|
+
interface WorkerRoute {
|
|
82
|
+
id: string;
|
|
83
|
+
pattern: string;
|
|
84
|
+
script: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Given a zone within the user's account, return a list of all assigned worker routes
|
|
89
|
+
*/
|
|
90
|
+
export async function getRoutesForZone(zone: string): Promise<WorkerRoute[]> {
|
|
91
|
+
const routes = await fetchListResult<WorkerRoute>(
|
|
92
|
+
`/zones/${zone}/workers/routes`
|
|
93
|
+
);
|
|
94
|
+
return routes;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Given two strings, return the levenshtein distance between them as a simple text match heuristic
|
|
99
|
+
*/
|
|
100
|
+
function distanceBetween(a: string, b: string, cache = new Map()): number {
|
|
101
|
+
if (cache.has(`${a}|${b}`)) {
|
|
102
|
+
return cache.get(`${a}|${b}`);
|
|
103
|
+
}
|
|
104
|
+
let result;
|
|
105
|
+
if (b == "") {
|
|
106
|
+
result = a.length;
|
|
107
|
+
} else if (a == "") {
|
|
108
|
+
result = b.length;
|
|
109
|
+
} else if (a[0] === b[0]) {
|
|
110
|
+
result = distanceBetween(a.slice(1), b.slice(1), cache);
|
|
111
|
+
} else {
|
|
112
|
+
result =
|
|
113
|
+
1 +
|
|
114
|
+
Math.min(
|
|
115
|
+
distanceBetween(a.slice(1), b, cache),
|
|
116
|
+
distanceBetween(a, b.slice(1), cache),
|
|
117
|
+
distanceBetween(a.slice(1), b.slice(1), cache)
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
cache.set(`${a}|${b}`, result);
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Given an invalid route, sort the valid routes by closeness to the invalid route (levenstein distance)
|
|
126
|
+
*/
|
|
127
|
+
export function findClosestRoute(
|
|
128
|
+
providedRoute: string,
|
|
129
|
+
assignedRoutes: WorkerRoute[]
|
|
130
|
+
): WorkerRoute[] {
|
|
131
|
+
return assignedRoutes.sort((a, b) => {
|
|
132
|
+
const distanceA = distanceBetween(providedRoute, a.pattern);
|
|
133
|
+
const distanceB = distanceBetween(providedRoute, b.pattern);
|
|
134
|
+
return distanceA - distanceB;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Given a route (must be assigned and within the correct zone), return the name of the worker assigned to it
|
|
140
|
+
*/
|
|
141
|
+
export async function getWorkerForZone(worker: string) {
|
|
142
|
+
const zone = await getZoneForRoute(worker);
|
|
143
|
+
if (!zone) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`The route '${worker}' is not part of one of your zones. Either add this zone from the Cloudflare dashboard, or try using a route within one of your existing zones.`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const routes = await getRoutesForZone(zone.id);
|
|
149
|
+
|
|
150
|
+
const scriptName = routes.find((route) => route.pattern === worker)?.script;
|
|
151
|
+
|
|
152
|
+
if (!scriptName) {
|
|
153
|
+
const closestRoute = findClosestRoute(worker, routes)?.[0];
|
|
154
|
+
|
|
155
|
+
if (!closestRoute) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`The route '${worker}' has no workers assigned. You can assign a worker to it from wrangler.toml or the Cloudflare dashboard`
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`The route '${worker}' has no workers assigned. Did you mean to tail the route '${closestRoute.pattern}'?`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return scriptName;
|
|
167
|
+
}
|
|
@@ -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;
|