@void/platform 0.20.0 → 0.20.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.
Files changed (36) hide show
  1. package/README.md +10 -1
  2. package/dist/email-protocol.d.mts +48 -0
  3. package/dist/email-protocol.mjs +278 -0
  4. package/dist/index.d.mts +152 -13
  5. package/dist/index.mjs +346 -20
  6. package/dist/installer.d.mts +4 -2
  7. package/dist/installer.mjs +13 -4
  8. package/dist/runtime/api/README.md +1 -1
  9. package/dist/runtime/api/index.js +67906 -52205
  10. package/dist/runtime/api/index.js.map +5 -5
  11. package/dist/runtime/dispatch/README.md +1 -1
  12. package/dist/runtime/dispatch/index.js +44 -26
  13. package/dist/runtime/dispatch/index.js.map +3 -3
  14. package/dist/runtime/drizzle/0039_email_domains.sql +32 -0
  15. package/dist/runtime/drizzle/0040_productive_vector.sql +1 -0
  16. package/dist/runtime/drizzle/0041_military_beyonder.sql +119 -0
  17. package/dist/runtime/drizzle/0042_dark_the_anarchist.sql +1 -0
  18. package/dist/runtime/drizzle/0043_nostalgic_meggan.sql +21 -0
  19. package/dist/runtime/drizzle/0044_smart_mentor.sql +15 -0
  20. package/dist/runtime/drizzle/0045_platform_settings.sql +5 -0
  21. package/dist/runtime/drizzle/0046_email_domains_platform_credential.sql +8 -0
  22. package/dist/runtime/drizzle/0047_email_gateway.sql +226 -0
  23. package/dist/runtime/drizzle/0048_project_teams.sql +133 -0
  24. package/dist/runtime/drizzle/compatibility.json +4 -0
  25. package/dist/runtime/drizzle/meta/_journal.json +70 -0
  26. package/dist/runtime/emailGateway/README.md +1 -0
  27. package/dist/runtime/emailGateway/index.js +5893 -0
  28. package/dist/runtime/emailGateway/index.js.map +8 -0
  29. package/dist/runtime/manifest.json +84 -13
  30. package/dist/runtime/proxy/README.md +1 -1
  31. package/dist/runtime/proxy/index.js +1786 -1523
  32. package/dist/runtime/proxy/index.js.map +5 -5
  33. package/dist/runtime/tail/README.md +1 -1
  34. package/dist/runtime/tail/index.js +1 -1
  35. package/dist/runtime/tail/index.js.map +1 -1
  36. package/package.json +5 -1
@@ -1,365 +1,310 @@
1
- var __create = Object.create;
2
1
  var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
- var __esm = (fn, res) => function __init() {
9
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
- };
11
- var __commonJS = (cb, mod) => function __require() {
12
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
13
- };
14
- var __copyProps = (to, from, except, desc) => {
15
- if (from && typeof from === "object" || typeof from === "function") {
16
- for (let key of __getOwnPropNames(from))
17
- if (!__hasOwnProp.call(to, key) && key !== except)
18
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
- }
20
- return to;
21
- };
22
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
23
- // If the importer is in node compatibility mode or this is not an ESM
24
- // file that has been converted to a CommonJS file using a Babel-
25
- // compatible transform (i.e. "__esModule" has not been set), then set
26
- // "default" to the CommonJS "module.exports" for node compatibility.
27
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
28
- mod
29
- ));
30
3
 
31
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/unenv@2.0.0-rc.24/node_modules/unenv/dist/runtime/_internal/utils.mjs
4
+ // ../../home/runner/work/void/void/node_modules/.pnpm/unenv@2.0.0-rc.24/node_modules/unenv/dist/runtime/_internal/utils.mjs
32
5
  // @__NO_SIDE_EFFECTS__
33
6
  function createNotImplementedError(name) {
34
7
  return new Error(`[unenv] ${name} is not implemented yet!`);
35
8
  }
36
- var init_utils = __esm({
37
- "../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/unenv@2.0.0-rc.24/node_modules/unenv/dist/runtime/_internal/utils.mjs"() {
38
- init_performance2();
39
- __name(createNotImplementedError, "createNotImplementedError");
40
- }
41
- });
9
+ __name(createNotImplementedError, "createNotImplementedError");
42
10
 
43
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/unenv@2.0.0-rc.24/node_modules/unenv/dist/runtime/node/internal/perf_hooks/performance.mjs
44
- var _timeOrigin, _performanceNow, nodeTiming, PerformanceEntry, PerformanceMark, PerformanceMeasure, PerformanceResourceTiming, PerformanceObserverEntryList, Performance, PerformanceObserver, performance;
45
- var init_performance = __esm({
46
- "../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/unenv@2.0.0-rc.24/node_modules/unenv/dist/runtime/node/internal/perf_hooks/performance.mjs"() {
47
- init_performance2();
48
- init_utils();
49
- _timeOrigin = globalThis.performance?.timeOrigin ?? Date.now();
50
- _performanceNow = globalThis.performance?.now ? globalThis.performance.now.bind(globalThis.performance) : () => Date.now() - _timeOrigin;
51
- nodeTiming = {
52
- name: "node",
53
- entryType: "node",
54
- startTime: 0,
55
- duration: 0,
56
- nodeStart: 0,
57
- v8Start: 0,
58
- bootstrapComplete: 0,
59
- environment: 0,
60
- loopStart: 0,
61
- loopExit: 0,
62
- idleTime: 0,
63
- uvMetricsInfo: {
64
- loopCount: 0,
65
- events: 0,
66
- eventsWaiting: 0
67
- },
68
- detail: void 0,
69
- toJSON() {
70
- return this;
71
- }
72
- };
73
- PerformanceEntry = class {
74
- static {
75
- __name(this, "PerformanceEntry");
76
- }
77
- __unenv__ = true;
78
- detail;
79
- entryType = "event";
80
- name;
81
- startTime;
82
- constructor(name, options) {
83
- this.name = name;
84
- this.startTime = options?.startTime || _performanceNow();
85
- this.detail = options?.detail;
86
- }
87
- get duration() {
88
- return _performanceNow() - this.startTime;
89
- }
90
- toJSON() {
91
- return {
92
- name: this.name,
93
- entryType: this.entryType,
94
- startTime: this.startTime,
95
- duration: this.duration,
96
- detail: this.detail
97
- };
98
- }
99
- };
100
- PerformanceMark = class PerformanceMark2 extends PerformanceEntry {
101
- static {
102
- __name(this, "PerformanceMark");
103
- }
104
- entryType = "mark";
105
- constructor() {
106
- super(...arguments);
107
- }
108
- get duration() {
109
- return 0;
110
- }
111
- };
112
- PerformanceMeasure = class extends PerformanceEntry {
113
- static {
114
- __name(this, "PerformanceMeasure");
115
- }
116
- entryType = "measure";
117
- };
118
- PerformanceResourceTiming = class extends PerformanceEntry {
119
- static {
120
- __name(this, "PerformanceResourceTiming");
121
- }
122
- entryType = "resource";
123
- serverTiming = [];
124
- connectEnd = 0;
125
- connectStart = 0;
126
- decodedBodySize = 0;
127
- domainLookupEnd = 0;
128
- domainLookupStart = 0;
129
- encodedBodySize = 0;
130
- fetchStart = 0;
131
- initiatorType = "";
132
- name = "";
133
- nextHopProtocol = "";
134
- redirectEnd = 0;
135
- redirectStart = 0;
136
- requestStart = 0;
137
- responseEnd = 0;
138
- responseStart = 0;
139
- secureConnectionStart = 0;
140
- startTime = 0;
141
- transferSize = 0;
142
- workerStart = 0;
143
- responseStatus = 0;
144
- };
145
- PerformanceObserverEntryList = class {
146
- static {
147
- __name(this, "PerformanceObserverEntryList");
148
- }
149
- __unenv__ = true;
150
- getEntries() {
151
- return [];
152
- }
153
- getEntriesByName(_name, _type) {
154
- return [];
155
- }
156
- getEntriesByType(type) {
157
- return [];
158
- }
159
- };
160
- Performance = class {
161
- static {
162
- __name(this, "Performance");
163
- }
164
- __unenv__ = true;
165
- timeOrigin = _timeOrigin;
166
- eventCounts = /* @__PURE__ */ new Map();
167
- _entries = [];
168
- _resourceTimingBufferSize = 0;
169
- navigation = void 0;
170
- timing = void 0;
171
- timerify(_fn, _options) {
172
- throw createNotImplementedError("Performance.timerify");
173
- }
174
- get nodeTiming() {
175
- return nodeTiming;
176
- }
177
- eventLoopUtilization() {
178
- return {};
179
- }
180
- markResourceTiming() {
181
- return new PerformanceResourceTiming("");
182
- }
183
- onresourcetimingbufferfull = null;
184
- now() {
185
- if (this.timeOrigin === _timeOrigin) {
186
- return _performanceNow();
187
- }
188
- return Date.now() - this.timeOrigin;
189
- }
190
- clearMarks(markName) {
191
- this._entries = markName ? this._entries.filter((e) => e.name !== markName) : this._entries.filter((e) => e.entryType !== "mark");
192
- }
193
- clearMeasures(measureName) {
194
- this._entries = measureName ? this._entries.filter((e) => e.name !== measureName) : this._entries.filter((e) => e.entryType !== "measure");
195
- }
196
- clearResourceTimings() {
197
- this._entries = this._entries.filter((e) => e.entryType !== "resource" || e.entryType !== "navigation");
198
- }
199
- getEntries() {
200
- return this._entries;
201
- }
202
- getEntriesByName(name, type) {
203
- return this._entries.filter((e) => e.name === name && (!type || e.entryType === type));
204
- }
205
- getEntriesByType(type) {
206
- return this._entries.filter((e) => e.entryType === type);
207
- }
208
- mark(name, options) {
209
- const entry = new PerformanceMark(name, options);
210
- this._entries.push(entry);
211
- return entry;
212
- }
213
- measure(measureName, startOrMeasureOptions, endMark) {
214
- let start;
215
- let end;
216
- if (typeof startOrMeasureOptions === "string") {
217
- start = this.getEntriesByName(startOrMeasureOptions, "mark")[0]?.startTime;
218
- end = this.getEntriesByName(endMark, "mark")[0]?.startTime;
219
- } else {
220
- start = Number.parseFloat(startOrMeasureOptions?.start) || this.now();
221
- end = Number.parseFloat(startOrMeasureOptions?.end) || this.now();
222
- }
223
- const entry = new PerformanceMeasure(measureName, {
224
- startTime: start,
225
- detail: {
226
- start,
227
- end
228
- }
229
- });
230
- this._entries.push(entry);
231
- return entry;
232
- }
233
- setResourceTimingBufferSize(maxSize) {
234
- this._resourceTimingBufferSize = maxSize;
235
- }
236
- addEventListener(type, listener, options) {
237
- throw createNotImplementedError("Performance.addEventListener");
238
- }
239
- removeEventListener(type, listener, options) {
240
- throw createNotImplementedError("Performance.removeEventListener");
241
- }
242
- dispatchEvent(event) {
243
- throw createNotImplementedError("Performance.dispatchEvent");
244
- }
245
- toJSON() {
246
- return this;
247
- }
11
+ // ../../home/runner/work/void/void/node_modules/.pnpm/unenv@2.0.0-rc.24/node_modules/unenv/dist/runtime/node/internal/perf_hooks/performance.mjs
12
+ var _timeOrigin = globalThis.performance?.timeOrigin ?? Date.now();
13
+ var _performanceNow = globalThis.performance?.now ? globalThis.performance.now.bind(globalThis.performance) : () => Date.now() - _timeOrigin;
14
+ var nodeTiming = {
15
+ name: "node",
16
+ entryType: "node",
17
+ startTime: 0,
18
+ duration: 0,
19
+ nodeStart: 0,
20
+ v8Start: 0,
21
+ bootstrapComplete: 0,
22
+ environment: 0,
23
+ loopStart: 0,
24
+ loopExit: 0,
25
+ idleTime: 0,
26
+ uvMetricsInfo: {
27
+ loopCount: 0,
28
+ events: 0,
29
+ eventsWaiting: 0
30
+ },
31
+ detail: void 0,
32
+ toJSON() {
33
+ return this;
34
+ }
35
+ };
36
+ var PerformanceEntry = class {
37
+ static {
38
+ __name(this, "PerformanceEntry");
39
+ }
40
+ __unenv__ = true;
41
+ detail;
42
+ entryType = "event";
43
+ name;
44
+ startTime;
45
+ constructor(name, options) {
46
+ this.name = name;
47
+ this.startTime = options?.startTime || _performanceNow();
48
+ this.detail = options?.detail;
49
+ }
50
+ get duration() {
51
+ return _performanceNow() - this.startTime;
52
+ }
53
+ toJSON() {
54
+ return {
55
+ name: this.name,
56
+ entryType: this.entryType,
57
+ startTime: this.startTime,
58
+ duration: this.duration,
59
+ detail: this.detail
248
60
  };
249
- PerformanceObserver = class {
250
- static {
251
- __name(this, "PerformanceObserver");
252
- }
253
- __unenv__ = true;
254
- static supportedEntryTypes = [];
255
- _callback = null;
256
- constructor(callback) {
257
- this._callback = callback;
258
- }
259
- takeRecords() {
260
- return [];
261
- }
262
- disconnect() {
263
- throw createNotImplementedError("PerformanceObserver.disconnect");
264
- }
265
- observe(options) {
266
- throw createNotImplementedError("PerformanceObserver.observe");
267
- }
268
- bind(fn) {
269
- return fn;
270
- }
271
- runInAsyncScope(fn, thisArg, ...args) {
272
- return fn.call(thisArg, ...args);
273
- }
274
- asyncId() {
275
- return 0;
276
- }
277
- triggerAsyncId() {
278
- return 0;
279
- }
280
- emitDestroy() {
281
- return this;
61
+ }
62
+ };
63
+ var PerformanceMark = class PerformanceMark2 extends PerformanceEntry {
64
+ static {
65
+ __name(this, "PerformanceMark");
66
+ }
67
+ entryType = "mark";
68
+ constructor() {
69
+ super(...arguments);
70
+ }
71
+ get duration() {
72
+ return 0;
73
+ }
74
+ };
75
+ var PerformanceMeasure = class extends PerformanceEntry {
76
+ static {
77
+ __name(this, "PerformanceMeasure");
78
+ }
79
+ entryType = "measure";
80
+ };
81
+ var PerformanceResourceTiming = class extends PerformanceEntry {
82
+ static {
83
+ __name(this, "PerformanceResourceTiming");
84
+ }
85
+ entryType = "resource";
86
+ serverTiming = [];
87
+ connectEnd = 0;
88
+ connectStart = 0;
89
+ decodedBodySize = 0;
90
+ domainLookupEnd = 0;
91
+ domainLookupStart = 0;
92
+ encodedBodySize = 0;
93
+ fetchStart = 0;
94
+ initiatorType = "";
95
+ name = "";
96
+ nextHopProtocol = "";
97
+ redirectEnd = 0;
98
+ redirectStart = 0;
99
+ requestStart = 0;
100
+ responseEnd = 0;
101
+ responseStart = 0;
102
+ secureConnectionStart = 0;
103
+ startTime = 0;
104
+ transferSize = 0;
105
+ workerStart = 0;
106
+ responseStatus = 0;
107
+ };
108
+ var PerformanceObserverEntryList = class {
109
+ static {
110
+ __name(this, "PerformanceObserverEntryList");
111
+ }
112
+ __unenv__ = true;
113
+ getEntries() {
114
+ return [];
115
+ }
116
+ getEntriesByName(_name, _type) {
117
+ return [];
118
+ }
119
+ getEntriesByType(type) {
120
+ return [];
121
+ }
122
+ };
123
+ var Performance = class {
124
+ static {
125
+ __name(this, "Performance");
126
+ }
127
+ __unenv__ = true;
128
+ timeOrigin = _timeOrigin;
129
+ eventCounts = /* @__PURE__ */ new Map();
130
+ _entries = [];
131
+ _resourceTimingBufferSize = 0;
132
+ navigation = void 0;
133
+ timing = void 0;
134
+ timerify(_fn, _options) {
135
+ throw createNotImplementedError("Performance.timerify");
136
+ }
137
+ get nodeTiming() {
138
+ return nodeTiming;
139
+ }
140
+ eventLoopUtilization() {
141
+ return {};
142
+ }
143
+ markResourceTiming() {
144
+ return new PerformanceResourceTiming("");
145
+ }
146
+ onresourcetimingbufferfull = null;
147
+ now() {
148
+ if (this.timeOrigin === _timeOrigin) {
149
+ return _performanceNow();
150
+ }
151
+ return Date.now() - this.timeOrigin;
152
+ }
153
+ clearMarks(markName) {
154
+ this._entries = markName ? this._entries.filter((e) => e.name !== markName) : this._entries.filter((e) => e.entryType !== "mark");
155
+ }
156
+ clearMeasures(measureName) {
157
+ this._entries = measureName ? this._entries.filter((e) => e.name !== measureName) : this._entries.filter((e) => e.entryType !== "measure");
158
+ }
159
+ clearResourceTimings() {
160
+ this._entries = this._entries.filter((e) => e.entryType !== "resource" || e.entryType !== "navigation");
161
+ }
162
+ getEntries() {
163
+ return this._entries;
164
+ }
165
+ getEntriesByName(name, type) {
166
+ return this._entries.filter((e) => e.name === name && (!type || e.entryType === type));
167
+ }
168
+ getEntriesByType(type) {
169
+ return this._entries.filter((e) => e.entryType === type);
170
+ }
171
+ mark(name, options) {
172
+ const entry = new PerformanceMark(name, options);
173
+ this._entries.push(entry);
174
+ return entry;
175
+ }
176
+ measure(measureName, startOrMeasureOptions, endMark) {
177
+ let start;
178
+ let end;
179
+ if (typeof startOrMeasureOptions === "string") {
180
+ start = this.getEntriesByName(startOrMeasureOptions, "mark")[0]?.startTime;
181
+ end = this.getEntriesByName(endMark, "mark")[0]?.startTime;
182
+ } else {
183
+ start = Number.parseFloat(startOrMeasureOptions?.start) || this.now();
184
+ end = Number.parseFloat(startOrMeasureOptions?.end) || this.now();
185
+ }
186
+ const entry = new PerformanceMeasure(measureName, {
187
+ startTime: start,
188
+ detail: {
189
+ start,
190
+ end
282
191
  }
283
- };
284
- performance = globalThis.performance && "addEventListener" in globalThis.performance ? globalThis.performance : new Performance();
192
+ });
193
+ this._entries.push(entry);
194
+ return entry;
285
195
  }
286
- });
287
-
288
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/unenv@2.0.0-rc.24/node_modules/unenv/dist/runtime/node/perf_hooks.mjs
289
- var init_perf_hooks = __esm({
290
- "../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/unenv@2.0.0-rc.24/node_modules/unenv/dist/runtime/node/perf_hooks.mjs"() {
291
- init_performance2();
292
- init_performance();
196
+ setResourceTimingBufferSize(maxSize) {
197
+ this._resourceTimingBufferSize = maxSize;
293
198
  }
294
- });
199
+ addEventListener(type, listener, options) {
200
+ throw createNotImplementedError("Performance.addEventListener");
201
+ }
202
+ removeEventListener(type, listener, options) {
203
+ throw createNotImplementedError("Performance.removeEventListener");
204
+ }
205
+ dispatchEvent(event) {
206
+ throw createNotImplementedError("Performance.dispatchEvent");
207
+ }
208
+ toJSON() {
209
+ return this;
210
+ }
211
+ };
212
+ var PerformanceObserver = class {
213
+ static {
214
+ __name(this, "PerformanceObserver");
215
+ }
216
+ __unenv__ = true;
217
+ static supportedEntryTypes = [];
218
+ _callback = null;
219
+ constructor(callback) {
220
+ this._callback = callback;
221
+ }
222
+ takeRecords() {
223
+ return [];
224
+ }
225
+ disconnect() {
226
+ throw createNotImplementedError("PerformanceObserver.disconnect");
227
+ }
228
+ observe(options) {
229
+ throw createNotImplementedError("PerformanceObserver.observe");
230
+ }
231
+ bind(fn) {
232
+ return fn;
233
+ }
234
+ runInAsyncScope(fn, thisArg, ...args) {
235
+ return fn.call(thisArg, ...args);
236
+ }
237
+ asyncId() {
238
+ return 0;
239
+ }
240
+ triggerAsyncId() {
241
+ return 0;
242
+ }
243
+ emitDestroy() {
244
+ return this;
245
+ }
246
+ };
247
+ var performance = globalThis.performance && "addEventListener" in globalThis.performance ? globalThis.performance : new Performance();
295
248
 
296
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/@cloudflare+unenv-preset@2.16.1_unenv@2.0.0-rc.24_workerd@1.20260910.1/node_modules/@cloudflare/unenv-preset/dist/runtime/polyfill/performance.mjs
297
- var init_performance2 = __esm({
298
- "../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/@cloudflare+unenv-preset@2.16.1_unenv@2.0.0-rc.24_workerd@1.20260910.1/node_modules/@cloudflare/unenv-preset/dist/runtime/polyfill/performance.mjs"() {
299
- init_perf_hooks();
300
- if (!("__unenv__" in performance)) {
301
- const proto = Performance.prototype;
302
- for (const key of Object.getOwnPropertyNames(proto)) {
303
- if (key !== "constructor" && !(key in performance)) {
304
- const desc = Object.getOwnPropertyDescriptor(proto, key);
305
- if (desc) {
306
- Object.defineProperty(performance, key, desc);
307
- }
308
- }
249
+ // ../../home/runner/work/void/void/node_modules/.pnpm/@cloudflare+unenv-preset@2.16.1_unenv@2.0.0-rc.24_workerd@1.20260910.1/node_modules/@cloudflare/unenv-preset/dist/runtime/polyfill/performance.mjs
250
+ if (!("__unenv__" in performance)) {
251
+ const proto = Performance.prototype;
252
+ for (const key of Object.getOwnPropertyNames(proto)) {
253
+ if (key !== "constructor" && !(key in performance)) {
254
+ const desc = Object.getOwnPropertyDescriptor(proto, key);
255
+ if (desc) {
256
+ Object.defineProperty(performance, key, desc);
309
257
  }
310
258
  }
311
- globalThis.performance = performance;
312
- globalThis.Performance = Performance;
313
- globalThis.PerformanceEntry = PerformanceEntry;
314
- globalThis.PerformanceMark = PerformanceMark;
315
- globalThis.PerformanceMeasure = PerformanceMeasure;
316
- globalThis.PerformanceObserver = PerformanceObserver;
317
- globalThis.PerformanceObserverEntryList = PerformanceObserverEntryList;
318
- globalThis.PerformanceResourceTiming = PerformanceResourceTiming;
319
259
  }
320
- });
260
+ }
261
+ globalThis.performance = performance;
262
+ globalThis.Performance = Performance;
263
+ globalThis.PerformanceEntry = PerformanceEntry;
264
+ globalThis.PerformanceMark = PerformanceMark;
265
+ globalThis.PerformanceMeasure = PerformanceMeasure;
266
+ globalThis.PerformanceObserver = PerformanceObserver;
267
+ globalThis.PerformanceObserverEntryList = PerformanceObserverEntryList;
268
+ globalThis.PerformanceResourceTiming = PerformanceResourceTiming;
321
269
 
322
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/email-validator@2.0.4/node_modules/email-validator/index.js
323
- var require_email_validator = __commonJS({
324
- "../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/email-validator@2.0.4/node_modules/email-validator/index.js"(exports) {
325
- "use strict";
326
- init_performance2();
327
- var tester = /^[-!#$%&'*+\/0-9=?A-Z^_a-z{|}~](\.?[-!#$%&'*+\/0-9=?A-Z^_a-z`{|}~])*@[a-zA-Z0-9](-*\.?[a-zA-Z0-9])*\.[a-zA-Z](-?[a-zA-Z0-9])+$/;
328
- exports.validate = function(email) {
329
- if (!email)
330
- return false;
331
- if (email.length > 254)
332
- return false;
333
- var valid = tester.test(email);
334
- if (!valid)
335
- return false;
336
- var parts = email.split("@");
337
- if (parts[0].length > 64)
338
- return false;
339
- var domainParts = parts[1].split(".");
340
- if (domainParts.some(function(part) {
341
- return part.length > 63;
342
- }))
343
- return false;
344
- return true;
345
- };
270
+ // ../../home/runner/work/void/void/packages/platform/dist/email-protocol.mjs
271
+ var encoder = new TextEncoder();
272
+ var decoder = new TextDecoder("utf-8", { fatal: true });
273
+ function unbase64url(value) {
274
+ if (!/^[A-Za-z0-9_-]+$/.test(value) || value.length > 1024) throw new Error("invalid receipt");
275
+ const binary = atob(value.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - value.length % 4) % 4));
276
+ return Uint8Array.from(binary, (char) => char.charCodeAt(0));
277
+ }
278
+ __name(unbase64url, "unbase64url");
279
+ async function receiptKey(secret, usage) {
280
+ if (!secret) throw new Error("Email signing key is not configured");
281
+ return crypto.subtle.importKey("raw", encoder.encode(secret), {
282
+ name: "HMAC",
283
+ hash: "SHA-256"
284
+ }, false, [usage]);
285
+ }
286
+ __name(receiptKey, "receiptKey");
287
+ async function verifyEmailReceipt(token, signingSecret, expected, now = Date.now()) {
288
+ try {
289
+ if (token.length > 1536) return null;
290
+ const parts = token.split(".");
291
+ if (parts.length !== 2) return null;
292
+ const payload = unbase64url(parts[0]);
293
+ const signature = unbase64url(parts[1]);
294
+ if (payload.byteLength > 768 || signature.byteLength !== 32) return null;
295
+ if (!await crypto.subtle.verify("HMAC", await receiptKey(signingSecret, "verify"), Uint8Array.from(signature), Uint8Array.from(payload))) return null;
296
+ const claims = JSON.parse(decoder.decode(payload));
297
+ if (typeof claims !== "object" || claims === null) return null;
298
+ const value = claims;
299
+ if (value.version !== 1 || typeof value.receiptId !== "string" || !/^[a-f0-9-]{16,64}$/.test(value.receiptId) || value.projectId !== expected.projectId || value.deploymentId !== expected.deploymentId || value.recipient !== expected.recipient || typeof value.expiresAt !== "number" || !Number.isSafeInteger(value.expiresAt) || value.expiresAt < now || value.expiresAt > now + 6e4) return null;
300
+ return value;
301
+ } catch {
302
+ return null;
346
303
  }
347
- });
348
-
349
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/index.ts
350
- init_performance2();
351
-
352
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/index.js
353
- init_performance2();
354
-
355
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/hono.js
356
- init_performance2();
357
-
358
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/hono-base.js
359
- init_performance2();
304
+ }
305
+ __name(verifyEmailReceipt, "verifyEmailReceipt");
360
306
 
361
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/compose.js
362
- init_performance2();
307
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/compose.js
363
308
  var compose = /* @__PURE__ */ __name((middleware, onError, onNotFound) => {
364
309
  return (context, next) => {
365
310
  let index = -1;
@@ -404,29 +349,10 @@ var compose = /* @__PURE__ */ __name((middleware, onError, onNotFound) => {
404
349
  };
405
350
  }, "compose");
406
351
 
407
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/context.js
408
- init_performance2();
409
-
410
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/request.js
411
- init_performance2();
412
-
413
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/http-exception.js
414
- init_performance2();
415
-
416
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/request/constants.js
417
- init_performance2();
352
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/request/constants.js
418
353
  var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
419
354
 
420
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/body.js
421
- init_performance2();
422
-
423
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/buffer.js
424
- init_performance2();
425
-
426
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/crypto.js
427
- init_performance2();
428
-
429
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/buffer.js
355
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/buffer.js
430
356
  var bufferToFormData = /* @__PURE__ */ __name((arrayBuffer, contentType) => {
431
357
  const response = new Response(arrayBuffer, {
432
358
  headers: {
@@ -437,7 +363,7 @@ var bufferToFormData = /* @__PURE__ */ __name((arrayBuffer, contentType) => {
437
363
  return response.formData();
438
364
  }, "bufferToFormData");
439
365
 
440
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/body.js
366
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/body.js
441
367
  var MAX_NESTING_DEPTH = 32;
442
368
  var MAX_NESTED_OBJECTS = 1e4;
443
369
  var isRawRequest = /* @__PURE__ */ __name((request) => "headers" in request, "isRawRequest");
@@ -537,8 +463,7 @@ var throwNestingLimitExceeded = /* @__PURE__ */ __name(() => {
537
463
  throw new Error("Nesting limit exceeded");
538
464
  }, "throwNestingLimitExceeded");
539
465
 
540
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/url.js
541
- init_performance2();
466
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/url.js
542
467
  var splitPath = /* @__PURE__ */ __name((path) => {
543
468
  const paths = path.split("/");
544
469
  if (paths[0] === "") {
@@ -591,13 +516,13 @@ var getPattern = /* @__PURE__ */ __name((label, next) => {
591
516
  }
592
517
  return null;
593
518
  }, "getPattern");
594
- var tryDecode = /* @__PURE__ */ __name((str, decoder2) => {
519
+ var tryDecode = /* @__PURE__ */ __name((str, decoder5) => {
595
520
  try {
596
- return decoder2(str);
521
+ return decoder5(str);
597
522
  } catch {
598
523
  return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match2) => {
599
524
  try {
600
- return decoder2(match2);
525
+ return decoder5(match2);
601
526
  } catch {
602
527
  return match2;
603
528
  }
@@ -744,7 +669,7 @@ var getQueryParams = /* @__PURE__ */ __name((url, key) => {
744
669
  }, "getQueryParams");
745
670
  var decodeURIComponent_ = decodeURIComponent;
746
671
 
747
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/request.js
672
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/request.js
748
673
  var HonoRequest = class {
749
674
  static {
750
675
  __name(this, "HonoRequest");
@@ -1028,8 +953,7 @@ var HonoRequest = class {
1028
953
  }
1029
954
  };
1030
955
 
1031
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/html.js
1032
- init_performance2();
956
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/html.js
1033
957
  var HtmlEscapedCallbackPhase = {
1034
958
  Stringify: 1,
1035
959
  BeforeStream: 2,
@@ -1071,7 +995,7 @@ var resolveCallback = /* @__PURE__ */ __name(async (str, phase, preserveCallback
1071
995
  }
1072
996
  }, "resolveCallback");
1073
997
 
1074
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/context.js
998
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/context.js
1075
999
  var TEXT_PLAIN = "text/plain; charset=UTF-8";
1076
1000
  var setDefaultContentType = /* @__PURE__ */ __name((contentType, headers) => {
1077
1001
  return {
@@ -1500,8 +1424,7 @@ var Context = class {
1500
1424
  }, "notFound");
1501
1425
  };
1502
1426
 
1503
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router.js
1504
- init_performance2();
1427
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router.js
1505
1428
  var METHOD_NAME_ALL = "ALL";
1506
1429
  var METHOD_NAME_ALL_LOWERCASE = "all";
1507
1430
  var METHODS = ["get", "post", "put", "delete", "options", "patch", "query"];
@@ -1512,11 +1435,10 @@ var UnsupportedPathError = class extends Error {
1512
1435
  }
1513
1436
  };
1514
1437
 
1515
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/constants.js
1516
- init_performance2();
1438
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/constants.js
1517
1439
  var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
1518
1440
 
1519
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/hono-base.js
1441
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/hono-base.js
1520
1442
  var notFoundHandler = /* @__PURE__ */ __name((c) => {
1521
1443
  return c.text("404 Not Found", 404);
1522
1444
  }, "notFoundHandler");
@@ -1897,18 +1819,10 @@ var Hono = class _Hono {
1897
1819
  }, "fire");
1898
1820
  };
1899
1821
 
1900
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/index.js
1901
- init_performance2();
1902
-
1903
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/router.js
1904
- init_performance2();
1905
-
1906
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/utils.js
1907
- init_performance2();
1822
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/utils.js
1908
1823
  var createNullObject = /* @__PURE__ */ __name(() => /* @__PURE__ */ Object.create(null), "createNullObject");
1909
1824
 
1910
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/matcher.js
1911
- init_performance2();
1825
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/matcher.js
1912
1826
  var emptyParam = [];
1913
1827
  function match(method, path) {
1914
1828
  const matchers = this.buildAllMatchers();
@@ -1930,8 +1844,7 @@ function match(method, path) {
1930
1844
  }
1931
1845
  __name(match, "match");
1932
1846
 
1933
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/node.js
1934
- init_performance2();
1847
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/node.js
1935
1848
  var LABEL_REG_EXP_STR = "[^/]+";
1936
1849
  var ONLY_WILDCARD_REG_EXP_STR = ".*";
1937
1850
  var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
@@ -2042,8 +1955,7 @@ var Node = class _Node {
2042
1955
  }
2043
1956
  };
2044
1957
 
2045
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/trie.js
2046
- init_performance2();
1958
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/trie.js
2047
1959
  var Trie = class {
2048
1960
  static {
2049
1961
  __name(this, "Trie");
@@ -2110,7 +2022,7 @@ var Trie = class {
2110
2022
  }
2111
2023
  };
2112
2024
 
2113
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/router.js
2025
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/router.js
2114
2026
  var wildcardRegExpCache = createNullObject();
2115
2027
  function buildWildcardRegExp(path) {
2116
2028
  return wildcardRegExpCache[path] ??= new RegExp(
@@ -2236,14 +2148,7 @@ var RegExpRouter = class {
2236
2148
  }
2237
2149
  };
2238
2150
 
2239
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/prepared-router.js
2240
- init_performance2();
2241
-
2242
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/smart-router/index.js
2243
- init_performance2();
2244
-
2245
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/smart-router/router.js
2246
- init_performance2();
2151
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/smart-router/router.js
2247
2152
  var SmartRouter = class {
2248
2153
  static {
2249
2154
  __name(this, "SmartRouter");
@@ -2301,14 +2206,7 @@ var SmartRouter = class {
2301
2206
  }
2302
2207
  };
2303
2208
 
2304
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/trie-router/index.js
2305
- init_performance2();
2306
-
2307
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/trie-router/router.js
2308
- init_performance2();
2309
-
2310
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/trie-router/node.js
2311
- init_performance2();
2209
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/trie-router/node.js
2312
2210
  var emptyParams = createNullObject();
2313
2211
  var order = 0;
2314
2212
  var Node2 = class _Node2 {
@@ -2471,7 +2369,7 @@ var Node2 = class _Node2 {
2471
2369
  }
2472
2370
  };
2473
2371
 
2474
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/trie-router/router.js
2372
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/trie-router/router.js
2475
2373
  var TrieRouter = class {
2476
2374
  static {
2477
2375
  __name(this, "TrieRouter");
@@ -2488,7 +2386,7 @@ var TrieRouter = class {
2488
2386
  }
2489
2387
  };
2490
2388
 
2491
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/hono.js
2389
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/hono.js
2492
2390
  var Hono2 = class extends Hono {
2493
2391
  static {
2494
2392
  __name(this, "Hono");
@@ -2506,8 +2404,7 @@ var Hono2 = class extends Hono {
2506
2404
  }
2507
2405
  };
2508
2406
 
2509
- // ../../../../../../../Users/cnakazawa/Projects/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/middleware/cors/index.js
2510
- init_performance2();
2407
+ // ../../home/runner/work/void/void/node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/middleware/cors/index.js
2511
2408
  var cors = /* @__PURE__ */ __name((options) => {
2512
2409
  const opts = {
2513
2410
  origin: "*",
@@ -2593,11 +2490,7 @@ var cors = /* @__PURE__ */ __name((options) => {
2593
2490
  }, "cors2");
2594
2491
  }, "cors");
2595
2492
 
2596
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/ai.ts
2597
- init_performance2();
2598
-
2599
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/body.ts
2600
- init_performance2();
2493
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/body.ts
2601
2494
  function isProxyBody(value) {
2602
2495
  return typeof value === "object" && value !== null && !Array.isArray(value);
2603
2496
  }
@@ -2606,10 +2499,6 @@ function asString(value) {
2606
2499
  return typeof value === "string" ? value : null;
2607
2500
  }
2608
2501
  __name(asString, "asString");
2609
- function asNumber(value) {
2610
- return typeof value === "number" && Number.isFinite(value) ? value : null;
2611
- }
2612
- __name(asNumber, "asNumber");
2613
2502
  function asRecord(value) {
2614
2503
  return isProxyBody(value) ? value : {};
2615
2504
  }
@@ -2619,7 +2508,7 @@ function isQueryValue(value) {
2619
2508
  }
2620
2509
  __name(isQueryValue, "isQueryValue");
2621
2510
 
2622
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/ai.ts
2511
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/ai.ts
2623
2512
  var DEFAULT_GATEWAY_ID = "void";
2624
2513
  var PROVIDER_AUTH_DEFAULTS = {
2625
2514
  "google-ai-studio": { header: "x-goog-api-key", prefix: "" }
@@ -2983,8 +2872,7 @@ ai.post("/toMarkdown", async (context) => {
2983
2872
  }
2984
2873
  });
2985
2874
 
2986
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/capabilities.ts
2987
- init_performance2();
2875
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/capabilities.ts
2988
2876
  function proxyCapabilityEnabled(value, capability) {
2989
2877
  if (capability === "sandbox") {
2990
2878
  return false;
@@ -3001,11 +2889,7 @@ function proxyCapabilityEnabled(value, capability) {
3001
2889
  }
3002
2890
  __name(proxyCapabilityEnabled, "proxyCapabilityEnabled");
3003
2891
 
3004
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/d1.ts
3005
- init_performance2();
3006
-
3007
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/dispatch.ts
3008
- init_performance2();
2892
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/dispatch.ts
3009
2893
  async function resolveWorker(api, jwtSecret, projectId) {
3010
2894
  const token = await hmacSign(projectId, jwtSecret);
3011
2895
  const res = await api.fetch(`https://api/internal/resolve-worker/${projectId}`, {
@@ -3066,7 +2950,7 @@ async function dispatchToWorker(env, projectId, path, body, options = {}) {
3066
2950
  }
3067
2951
  }
3068
2952
  __name(dispatchToWorker, "dispatchToWorker");
3069
- var encoder = new TextEncoder();
2953
+ var encoder2 = new TextEncoder();
3070
2954
  var cachedSignKey;
3071
2955
  var cachedKeySecret;
3072
2956
  async function getSignKey(secret) {
@@ -3075,7 +2959,7 @@ async function getSignKey(secret) {
3075
2959
  }
3076
2960
  cachedSignKey = await crypto.subtle.importKey(
3077
2961
  "raw",
3078
- encoder.encode(secret),
2962
+ encoder2.encode(secret),
3079
2963
  { name: "HMAC", hash: "SHA-256" },
3080
2964
  false,
3081
2965
  ["sign"]
@@ -3086,14 +2970,13 @@ async function getSignKey(secret) {
3086
2970
  __name(getSignKey, "getSignKey");
3087
2971
  async function hmacSign(data, secret) {
3088
2972
  const key = await getSignKey(secret);
3089
- const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(data));
2973
+ const sig = await crypto.subtle.sign("HMAC", key, encoder2.encode(data));
3090
2974
  const binary = String.fromCharCode(...new Uint8Array(sig));
3091
2975
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3092
2976
  }
3093
2977
  __name(hmacSign, "hmacSign");
3094
2978
 
3095
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/resolve.ts
3096
- init_performance2();
2979
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/resolve.ts
3097
2980
  var cache = /* @__PURE__ */ new Map();
3098
2981
  async function resolveResource(api, jwtSecret, projectId, type, bindingName) {
3099
2982
  const cacheKey = `${projectId}:${type}:${bindingName}`;
@@ -3119,24 +3002,38 @@ async function resolveResource(api, jwtSecret, projectId, type, bindingName) {
3119
3002
  return data.cf_id;
3120
3003
  }
3121
3004
  __name(resolveResource, "resolveResource");
3122
- async function verifyProjectOwner(api, jwtSecret, projectId, userId) {
3005
+ async function verifyProjectDeployAccess(api, jwtSecret, projectId, userId, jwt, accessAssertion, proxyOrigin) {
3123
3006
  try {
3124
- const token = await hmacSign2(projectId, jwtSecret);
3007
+ if (!proxyOrigin || new URL(proxyOrigin).origin !== proxyOrigin) {
3008
+ return false;
3009
+ }
3010
+ const token = await hmacSign2(ownerVerificationMessage(projectId, proxyOrigin), jwtSecret);
3125
3011
  const res = await api.fetch(`https://api/internal/verify-owner/${projectId}`, {
3126
3012
  method: "POST",
3127
3013
  headers: {
3128
3014
  "content-type": "application/json",
3129
3015
  "x-void-internal": token
3130
3016
  },
3131
- body: JSON.stringify({ userId })
3017
+ body: JSON.stringify({
3018
+ userId,
3019
+ ...jwt ? { jwt } : {},
3020
+ ...accessAssertion ? { accessAssertion } : {},
3021
+ proxyOrigin
3022
+ })
3132
3023
  });
3133
3024
  return res.ok;
3134
3025
  } catch {
3135
3026
  return false;
3136
3027
  }
3137
3028
  }
3138
- __name(verifyProjectOwner, "verifyProjectOwner");
3139
- async function verifyPrerenderBuild(api, jwtSecret, projectId, buildId) {
3029
+ __name(verifyProjectDeployAccess, "verifyProjectDeployAccess");
3030
+ function ownerVerificationMessage(projectId, proxyOrigin) {
3031
+ return `void-proxy-owner-v2
3032
+ ${projectId}
3033
+ ${proxyOrigin}`;
3034
+ }
3035
+ __name(ownerVerificationMessage, "ownerVerificationMessage");
3036
+ async function verifyPrerenderAuthority(api, jwtSecret, projectId, userId, provenance) {
3140
3037
  try {
3141
3038
  const token = await hmacSign2(projectId, jwtSecret);
3142
3039
  const res = await api.fetch(`https://api/internal/verify-prerender-build/${projectId}`, {
@@ -3145,15 +3042,19 @@ async function verifyPrerenderBuild(api, jwtSecret, projectId, buildId) {
3145
3042
  "content-type": "application/json",
3146
3043
  "x-void-internal": token
3147
3044
  },
3148
- body: JSON.stringify({ buildId })
3045
+ body: JSON.stringify({ userId, ...provenance })
3149
3046
  });
3047
+ if (res.status === 400 && !provenance.buildId && !provenance.credentialId) {
3048
+ const body = await res.json().catch(() => null);
3049
+ return body?.error === "userId and credential provenance required";
3050
+ }
3150
3051
  return res.ok;
3151
3052
  } catch {
3152
3053
  return false;
3153
3054
  }
3154
3055
  }
3155
- __name(verifyPrerenderBuild, "verifyPrerenderBuild");
3156
- var encoder2 = new TextEncoder();
3056
+ __name(verifyPrerenderAuthority, "verifyPrerenderAuthority");
3057
+ var encoder3 = new TextEncoder();
3157
3058
  var cachedSignKey2;
3158
3059
  var cachedKeySecret2;
3159
3060
  async function getSignKey2(secret) {
@@ -3162,7 +3063,7 @@ async function getSignKey2(secret) {
3162
3063
  }
3163
3064
  cachedSignKey2 = await crypto.subtle.importKey(
3164
3065
  "raw",
3165
- encoder2.encode(secret),
3066
+ encoder3.encode(secret),
3166
3067
  { name: "HMAC", hash: "SHA-256" },
3167
3068
  false,
3168
3069
  ["sign"]
@@ -3173,13 +3074,13 @@ async function getSignKey2(secret) {
3173
3074
  __name(getSignKey2, "getSignKey");
3174
3075
  async function hmacSign2(data, secret) {
3175
3076
  const key = await getSignKey2(secret);
3176
- const sig = await crypto.subtle.sign("HMAC", key, encoder2.encode(data));
3077
+ const sig = await crypto.subtle.sign("HMAC", key, encoder3.encode(data));
3177
3078
  const binary = String.fromCharCode(...new Uint8Array(sig));
3178
3079
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3179
3080
  }
3180
3081
  __name(hmacSign2, "hmacSign");
3181
3082
 
3182
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/d1.ts
3083
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/d1.ts
3183
3084
  var CF_API = "https://api.cloudflare.com/client/v4";
3184
3085
  var d1 = new Hono2();
3185
3086
  d1.post("/query", async (context) => {
@@ -3252,101 +3153,669 @@ d1.post("/batch", async (context) => {
3252
3153
  return context.json({ error: "atomic D1 batch unavailable for active deployment" }, 503);
3253
3154
  });
3254
3155
 
3255
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/email-router.ts
3256
- init_performance2();
3257
-
3258
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/api/src/reserved-slugs.ts
3259
- init_performance2();
3260
- var EMAIL_RESERVED_SLUGS = /* @__PURE__ */ new Set([
3261
- "abuse",
3262
- "bounce",
3263
- "noreply",
3264
- "postmaster",
3265
- "security"
3266
- ]);
3267
- function isReservedEmailSlug(slug) {
3268
- return EMAIL_RESERVED_SLUGS.has(slug);
3269
- }
3270
- __name(isReservedEmailSlug, "isReservedEmailSlug");
3271
-
3272
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/email-router.ts
3273
- var import_email_validator = __toESM(require_email_validator(), 1);
3274
- import { EmailMessage as EmailMessage2 } from "cloudflare:email";
3275
-
3276
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/email.ts
3277
- init_performance2();
3278
- import { EmailMessage } from "cloudflare:email";
3279
- var app = new Hono2();
3280
- var MAX_MIME_BYTES = 10 * 1024 * 1024;
3281
- var MAX_RECIPIENTS = 100;
3282
- var MAX_RECIPIENT_LENGTH = 320;
3283
- var MAX_SENDER_LENGTH = 320;
3284
- function readSenderDomain(env) {
3285
- const raw2 = env.EMAIL_SENDER_DOMAIN;
3286
- if (typeof raw2 !== "string") {
3287
- return null;
3288
- }
3289
- const domain = raw2.trim().toLowerCase();
3290
- return domain.length > 0 ? domain : null;
3291
- }
3292
- __name(readSenderDomain, "readSenderDomain");
3293
- var RE_METACHARS_RE = /[.*+?^${}()|[\]\\]/g;
3294
- function escapeRegExp(value) {
3295
- return value.replace(RE_METACHARS_RE, "\\$&");
3296
- }
3297
- __name(escapeRegExp, "escapeRegExp");
3298
- var strictFromCache = /* @__PURE__ */ new Map();
3299
- function strictFromRe(domain) {
3300
- const cached = strictFromCache.get(domain);
3301
- if (cached) {
3302
- return cached;
3303
- }
3304
- const re = new RegExp(`^[a-z0-9._-]+(?:\\+[a-z0-9._=/-]+)?@${escapeRegExp(domain)}$`);
3305
- strictFromCache.set(domain, re);
3306
- return re;
3307
- }
3308
- __name(strictFromRe, "strictFromRe");
3309
- var SLUG_RE = /^[a-z0-9._-]+$/;
3310
- var CONTROL_CHARS_RE = new RegExp("[\\u0000-\\u001f\\u007f]+", "g");
3311
- function sanitizeForLog(value, maxLen = 200) {
3312
- return value.slice(0, maxLen).replace(CONTROL_CHARS_RE, " ");
3313
- }
3314
- __name(sanitizeForLog, "sanitizeForLog");
3315
- var MAX_REQUEST_ID_LENGTH = 128;
3316
- function bytesToStream(bytes) {
3317
- return new ReadableStream({
3318
- start(controller) {
3319
- controller.enqueue(bytes);
3320
- controller.close();
3156
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/email-request-body.ts
3157
+ var EMAIL_REQUEST_DEADLINE_MS = 5e3;
3158
+ var decoder2 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
3159
+ async function beforeDeadline(deadline, run) {
3160
+ const remaining = deadline - Date.now();
3161
+ if (remaining <= 0) {
3162
+ throw new Error("email request timeout");
3163
+ }
3164
+ let timer;
3165
+ return Promise.race([
3166
+ run(),
3167
+ new Promise((_, reject) => {
3168
+ timer = setTimeout(() => reject(new Error("email request timeout")), remaining);
3169
+ })
3170
+ ]).finally(() => {
3171
+ if (timer !== void 0) {
3172
+ clearTimeout(timer);
3321
3173
  }
3322
3174
  });
3323
3175
  }
3324
- __name(bytesToStream, "bytesToStream");
3325
- var LF = 10;
3326
- var CR = 13;
3327
- var SP = 32;
3328
- var HTAB = 9;
3329
- var COLON = 58;
3330
- var FROM_NAME = [102, 114, 111, 109];
3331
- function fromValueStart(raw2, start, end) {
3332
- if (end - start <= FROM_NAME.length) {
3333
- return -1;
3176
+ __name(beforeDeadline, "beforeDeadline");
3177
+ async function readEmailRequestJson(request, maxBytes = 2 * 1024) {
3178
+ const declaredLength = request.headers.get("content-length");
3179
+ if (request.headers.has("content-encoding") || !request.body || declaredLength !== null && (!/^\d+$/.test(declaredLength) || Number(declaredLength) > maxBytes)) {
3180
+ void request.body?.cancel().catch(() => {
3181
+ });
3182
+ throw new Error("invalid email request body");
3334
3183
  }
3335
- for (let i = 0; i < FROM_NAME.length; i++) {
3336
- if ((raw2[start + i] | 32) !== FROM_NAME[i]) {
3337
- return -1;
3184
+ const expectedLength = declaredLength === null ? null : Number(declaredLength);
3185
+ const reader = request.body.getReader();
3186
+ let bytes = new Uint8Array(Math.min(1024, maxBytes));
3187
+ const deadline = Date.now() + EMAIL_REQUEST_DEADLINE_MS;
3188
+ let size = 0;
3189
+ try {
3190
+ for (; ; ) {
3191
+ const chunk = await beforeDeadline(deadline, () => reader.read());
3192
+ if (chunk.done) {
3193
+ break;
3194
+ }
3195
+ const nextSize = size + chunk.value.byteLength;
3196
+ if (nextSize > maxBytes) {
3197
+ throw new Error("email request body too large");
3198
+ }
3199
+ if (nextSize > bytes.byteLength) {
3200
+ const grown = new Uint8Array(Math.min(maxBytes, Math.max(nextSize, bytes.byteLength * 2)));
3201
+ grown.set(bytes.subarray(0, size));
3202
+ bytes = grown;
3203
+ }
3204
+ bytes.set(chunk.value, size);
3205
+ size = nextSize;
3338
3206
  }
3207
+ } finally {
3208
+ void reader.cancel().catch(() => {
3209
+ });
3210
+ reader.releaseLock();
3339
3211
  }
3340
- let colon = start + FROM_NAME.length;
3341
- while (colon < end && (raw2[colon] === SP || raw2[colon] === HTAB)) {
3342
- colon++;
3212
+ if (size === 0 || expectedLength !== null && size !== expectedLength) {
3213
+ throw new Error("invalid email request body length");
3343
3214
  }
3344
- return colon < end && raw2[colon] === COLON ? colon + 1 : -1;
3215
+ return JSON.parse(decoder2.decode(bytes.subarray(0, size)));
3345
3216
  }
3346
- __name(fromValueStart, "fromValueStart");
3347
- var MIME_FROM_REFUSALS = {
3348
- missing: "the message has no From header",
3349
- multiple: "the message has more than one From header",
3217
+ __name(readEmailRequestJson, "readEmailRequestJson");
3218
+
3219
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/email.ts
3220
+ import { EmailMessage } from "cloudflare:email";
3221
+
3222
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/email-admission.ts
3223
+ var encoder4 = new TextEncoder();
3224
+ var decoder3 = new TextDecoder();
3225
+ var EMAIL_DEFAULT_DEADLINE_MS = 5e3;
3226
+ var EMAIL_ACTION_DEADLINE_MS = 15e3;
3227
+ var EMAIL_ADMISSION_DEADLINE_MS = 15e3;
3228
+ var MAX_ADMISSION_RESPONSE_BYTES = 65536;
3229
+ async function sign(data, secret) {
3230
+ if (!secret) {
3231
+ throw new Error("Email signing key is not configured");
3232
+ }
3233
+ const key = await crypto.subtle.importKey(
3234
+ "raw",
3235
+ encoder4.encode(secret),
3236
+ { name: "HMAC", hash: "SHA-256" },
3237
+ false,
3238
+ ["sign"]
3239
+ );
3240
+ const bytes = new Uint8Array(await crypto.subtle.sign("HMAC", key, encoder4.encode(data)));
3241
+ return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3242
+ }
3243
+ __name(sign, "sign");
3244
+ async function call(env, path, body, deadlineMs = EMAIL_DEFAULT_DEADLINE_MS) {
3245
+ const controller = new AbortController();
3246
+ const deadline = Date.now() + deadlineMs;
3247
+ const beforeDeadline2 = /* @__PURE__ */ __name(async (run) => {
3248
+ const remaining = deadline - Date.now();
3249
+ if (remaining <= 0) {
3250
+ controller.abort();
3251
+ throw new Error("EMAIL_ADMISSION_TIMEOUT");
3252
+ }
3253
+ const promise = run();
3254
+ let timer;
3255
+ try {
3256
+ return await Promise.race([
3257
+ promise,
3258
+ new Promise((_, reject) => {
3259
+ timer = setTimeout(() => {
3260
+ controller.abort();
3261
+ reject(new Error("EMAIL_ADMISSION_TIMEOUT"));
3262
+ }, remaining);
3263
+ })
3264
+ ]);
3265
+ } finally {
3266
+ if (timer !== void 0) {
3267
+ clearTimeout(timer);
3268
+ }
3269
+ }
3270
+ }, "beforeDeadline");
3271
+ const raw2 = JSON.stringify(body);
3272
+ const proof = await sign(raw2, env.EMAIL_SIGNING_SECRET);
3273
+ const response = await beforeDeadline2(
3274
+ () => env.API.fetch(`https://api/internal/email/actions${path}`, {
3275
+ method: "POST",
3276
+ headers: {
3277
+ "content-type": "application/json",
3278
+ "x-void-internal": proof
3279
+ },
3280
+ body: raw2,
3281
+ signal: controller.signal
3282
+ })
3283
+ );
3284
+ if (!response.ok) {
3285
+ void response.body?.cancel().catch(() => {
3286
+ });
3287
+ if (response.status === 409) {
3288
+ throw new Error("IDEMPOTENCY_CONFLICT");
3289
+ }
3290
+ throw new Error(`EMAIL_ADMISSION_${response.status}`);
3291
+ }
3292
+ const reader = response.body?.getReader();
3293
+ if (!reader) {
3294
+ throw new Error("EMAIL_ADMISSION_EMPTY_RESPONSE");
3295
+ }
3296
+ const bytes = new Uint8Array(MAX_ADMISSION_RESPONSE_BYTES);
3297
+ let size = 0;
3298
+ try {
3299
+ for (; ; ) {
3300
+ const chunk = await beforeDeadline2(() => reader.read());
3301
+ if (chunk.done) {
3302
+ break;
3303
+ }
3304
+ if (size + chunk.value.byteLength > bytes.length) {
3305
+ throw new Error("EMAIL_ADMISSION_RESPONSE_TOO_LARGE");
3306
+ }
3307
+ bytes.set(chunk.value, size);
3308
+ size += chunk.value.byteLength;
3309
+ }
3310
+ return JSON.parse(decoder3.decode(bytes.subarray(0, size)));
3311
+ } finally {
3312
+ void reader.cancel().catch(() => {
3313
+ });
3314
+ try {
3315
+ reader.releaseLock();
3316
+ } catch {
3317
+ }
3318
+ }
3319
+ }
3320
+ __name(call, "call");
3321
+ function canonicalizeVoidMime(raw2) {
3322
+ if (findCrlfCrlf(raw2) === -1) {
3323
+ return raw2;
3324
+ }
3325
+ const boundaries = /* @__PURE__ */ new Map();
3326
+ const output = [];
3327
+ let outputBytes = 0;
3328
+ let copyStart = 0;
3329
+ let changed = false;
3330
+ let inHeaders = true;
3331
+ let topHeaders = true;
3332
+ const append = /* @__PURE__ */ __name((bytes) => {
3333
+ if (bytes.byteLength === 0) {
3334
+ return;
3335
+ }
3336
+ output.push(bytes);
3337
+ outputBytes += bytes.byteLength;
3338
+ }, "append");
3339
+ const replace = /* @__PURE__ */ __name((start, end, replacement) => {
3340
+ append(raw2.subarray(copyStart, start));
3341
+ append(replacement);
3342
+ copyStart = end;
3343
+ changed = true;
3344
+ }, "replace");
3345
+ let lineStart = 0;
3346
+ while (lineStart < raw2.byteLength) {
3347
+ const lineEnd = findCrlf(raw2, lineStart);
3348
+ const nextLine = lineEnd === raw2.byteLength ? raw2.byteLength : lineEnd + 2;
3349
+ if (inHeaders) {
3350
+ if (lineEnd === lineStart) {
3351
+ inHeaders = false;
3352
+ topHeaders = false;
3353
+ } else if (topHeaders && (asciiPrefixEquals(raw2, lineStart, lineEnd, "Date: ") || asciiPrefixEquals(raw2, lineStart, lineEnd, "Message-ID: "))) {
3354
+ replace(lineStart, nextLine, new Uint8Array());
3355
+ } else if (isMultipartContentType(raw2, lineStart, lineEnd)) {
3356
+ const line = decoder3.decode(raw2.subarray(lineStart, lineEnd));
3357
+ const normalized2 = line.replace(
3358
+ /boundary="(=_void_(?:mixed|alternative|related)_[A-Za-z0-9_-]+)"/g,
3359
+ (_, token) => {
3360
+ let marker = boundaries.get(token);
3361
+ if (!marker) {
3362
+ marker = `<void-boundary-${boundaries.size}>`;
3363
+ boundaries.set(token, marker);
3364
+ }
3365
+ return `boundary="${marker}"`;
3366
+ }
3367
+ );
3368
+ if (normalized2 !== line) {
3369
+ replace(lineStart, lineEnd, encoder4.encode(normalized2));
3370
+ }
3371
+ }
3372
+ } else if (raw2[lineStart] === 45 && raw2[lineStart + 1] === 45) {
3373
+ const tokenStart = lineStart + 2;
3374
+ const token = isBoundaryToken(raw2, tokenStart, lineEnd) ? decoder3.decode(raw2.subarray(tokenStart, lineEnd)) : void 0;
3375
+ const openingMarker = token === void 0 ? void 0 : boundaries.get(token);
3376
+ if (openingMarker !== void 0) {
3377
+ replace(lineStart, lineEnd, encoder4.encode(`--${openingMarker}`));
3378
+ inHeaders = true;
3379
+ } else if (lineEnd - tokenStart >= 2 && raw2[lineEnd - 2] === 45 && raw2[lineEnd - 1] === 45 && isBoundaryToken(raw2, tokenStart, lineEnd - 2)) {
3380
+ const closingToken = decoder3.decode(raw2.subarray(tokenStart, lineEnd - 2));
3381
+ const closingMarker = boundaries.get(closingToken);
3382
+ if (closingMarker !== void 0) {
3383
+ replace(lineStart, lineEnd, encoder4.encode(`--${closingMarker}--`));
3384
+ }
3385
+ }
3386
+ }
3387
+ lineStart = nextLine;
3388
+ }
3389
+ if (!changed) {
3390
+ return raw2;
3391
+ }
3392
+ append(raw2.subarray(copyStart));
3393
+ const normalized = new Uint8Array(outputBytes);
3394
+ let offset = 0;
3395
+ for (const chunk of output) {
3396
+ normalized.set(chunk, offset);
3397
+ offset += chunk.byteLength;
3398
+ }
3399
+ return normalized;
3400
+ }
3401
+ __name(canonicalizeVoidMime, "canonicalizeVoidMime");
3402
+ function findCrlfCrlf(raw2) {
3403
+ for (let i = 0; i + 3 < raw2.byteLength; i++) {
3404
+ if (raw2[i] === 13 && raw2[i + 1] === 10 && raw2[i + 2] === 13 && raw2[i + 3] === 10) {
3405
+ return i;
3406
+ }
3407
+ }
3408
+ return -1;
3409
+ }
3410
+ __name(findCrlfCrlf, "findCrlfCrlf");
3411
+ function findCrlf(raw2, start) {
3412
+ let cursor = start;
3413
+ while (cursor < raw2.byteLength) {
3414
+ const cr = raw2.indexOf(13, cursor);
3415
+ if (cr === -1) {
3416
+ return raw2.byteLength;
3417
+ }
3418
+ if (raw2[cr + 1] === 10) {
3419
+ return cr;
3420
+ }
3421
+ cursor = cr + 1;
3422
+ }
3423
+ return raw2.byteLength;
3424
+ }
3425
+ __name(findCrlf, "findCrlf");
3426
+ function asciiPrefixEquals(raw2, start, end, expected) {
3427
+ if (end - start < expected.length) {
3428
+ return false;
3429
+ }
3430
+ for (let i = 0; i < expected.length; i++) {
3431
+ const actual = raw2[start + i];
3432
+ const wanted = expected.charCodeAt(i);
3433
+ if ((actual >= 65 && actual <= 90 ? actual + 32 : actual) !== (wanted | 32)) {
3434
+ return false;
3435
+ }
3436
+ }
3437
+ return true;
3438
+ }
3439
+ __name(asciiPrefixEquals, "asciiPrefixEquals");
3440
+ function isMultipartContentType(raw2, start, end) {
3441
+ const field = "Content-Type:";
3442
+ if (!asciiPrefixEquals(raw2, start, end, field)) {
3443
+ return false;
3444
+ }
3445
+ let cursor = start + field.length;
3446
+ while (cursor < end && (raw2[cursor] === 32 || raw2[cursor] === 9 || raw2[cursor] === 11 || raw2[cursor] === 12)) {
3447
+ cursor++;
3448
+ }
3449
+ return asciiPrefixEquals(raw2, cursor, end, "multipart/");
3450
+ }
3451
+ __name(isMultipartContentType, "isMultipartContentType");
3452
+ function isBoundaryToken(raw2, start, end) {
3453
+ if (end <= start) {
3454
+ return false;
3455
+ }
3456
+ for (let i = start; i < end; i++) {
3457
+ const byte = raw2[i];
3458
+ if (!(byte >= 65 && byte <= 90 || byte >= 97 && byte <= 122 || byte >= 48 && byte <= 57 || byte === 95 || byte === 45 || byte === 61)) {
3459
+ return false;
3460
+ }
3461
+ }
3462
+ return true;
3463
+ }
3464
+ __name(isBoundaryToken, "isBoundaryToken");
3465
+ async function emailPayloadDigest(kind, from, recipients, raw2) {
3466
+ const canonical = canonicalizeVoidMime(raw2);
3467
+ const prefix = encoder4.encode(
3468
+ JSON.stringify({
3469
+ kind,
3470
+ from: from.toLowerCase(),
3471
+ recipients: [...new Set(recipients.map((r) => r.toLowerCase()))].sort()
3472
+ })
3473
+ );
3474
+ const input = new Uint8Array(prefix.length + 1 + canonical.length);
3475
+ input.set(prefix);
3476
+ input[prefix.length] = 0;
3477
+ input.set(canonical, prefix.length + 1);
3478
+ return [...new Uint8Array(await crypto.subtle.digest("SHA-256", input))].map((b) => b.toString(16).padStart(2, "0")).join("");
3479
+ }
3480
+ __name(emailPayloadDigest, "emailPayloadDigest");
3481
+ function admitEmail(env, body, deadlineMs = EMAIL_ADMISSION_DEADLINE_MS) {
3482
+ return call(env, "/admit", body, Math.min(deadlineMs, EMAIL_ADMISSION_DEADLINE_MS));
3483
+ }
3484
+ __name(admitEmail, "admitEmail");
3485
+ function startEmailAttempt(env, operationId, recipients, deadlineMs = EMAIL_ACTION_DEADLINE_MS) {
3486
+ return call(
3487
+ env,
3488
+ `/${encodeURIComponent(operationId)}/start`,
3489
+ { recipients },
3490
+ Math.min(deadlineMs, EMAIL_ACTION_DEADLINE_MS)
3491
+ );
3492
+ }
3493
+ __name(startEmailAttempt, "startEmailAttempt");
3494
+ function finishEmailAttempt(env, operationId, recipients, state, details = {}, deadlineMs = EMAIL_ACTION_DEADLINE_MS) {
3495
+ return call(
3496
+ env,
3497
+ `/${encodeURIComponent(operationId)}/finish`,
3498
+ { recipients, state, ...details },
3499
+ Math.min(deadlineMs, EMAIL_ACTION_DEADLINE_MS)
3500
+ );
3501
+ }
3502
+ __name(finishEmailAttempt, "finishEmailAttempt");
3503
+
3504
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/email-lease.ts
3505
+ var LEASE_DEADLINE_MS = 5e3;
3506
+ var MAX_LEASE_RESPONSE_BYTES = 4 * 1024;
3507
+ function leaseBearer(lease) {
3508
+ return lease.token;
3509
+ }
3510
+ __name(leaseBearer, "leaseBearer");
3511
+ async function leaseEmailDomainCredential(env, domain, projectId, deadlineMs = LEASE_DEADLINE_MS) {
3512
+ if (!env.EMAIL_SIGNING_SECRET) {
3513
+ return null;
3514
+ }
3515
+ const controller = new AbortController();
3516
+ const deadline = Date.now() + Math.min(deadlineMs, LEASE_DEADLINE_MS);
3517
+ const beforeDeadline2 = /* @__PURE__ */ __name(async (run) => {
3518
+ const remaining = deadline - Date.now();
3519
+ if (remaining <= 0) {
3520
+ controller.abort();
3521
+ throw new Error("email credential lease timed out");
3522
+ }
3523
+ const promise = run();
3524
+ let timer;
3525
+ try {
3526
+ return await Promise.race([
3527
+ promise,
3528
+ new Promise((_, reject) => {
3529
+ timer = setTimeout(() => {
3530
+ controller.abort();
3531
+ reject(new Error("email credential lease timed out"));
3532
+ }, remaining);
3533
+ })
3534
+ ]);
3535
+ } finally {
3536
+ if (timer !== void 0) {
3537
+ clearTimeout(timer);
3538
+ }
3539
+ }
3540
+ }, "beforeDeadline");
3541
+ let res;
3542
+ try {
3543
+ const token = await hmacSign3(
3544
+ `email-lease-v1
3545
+ ${projectId}
3546
+ ${domain.toLowerCase()}`,
3547
+ env.EMAIL_SIGNING_SECRET
3548
+ );
3549
+ res = await beforeDeadline2(
3550
+ () => env.API.fetch("https://api/internal/email/domain-credential", {
3551
+ method: "POST",
3552
+ headers: {
3553
+ "content-type": "application/json",
3554
+ "x-void-internal": token
3555
+ },
3556
+ body: JSON.stringify({ domain, projectId }),
3557
+ signal: controller.signal
3558
+ })
3559
+ );
3560
+ } catch {
3561
+ return null;
3562
+ }
3563
+ if (!res.ok) {
3564
+ void res.body?.cancel().catch(() => {
3565
+ });
3566
+ return null;
3567
+ }
3568
+ const declared = res.headers.get("content-length");
3569
+ if (declared !== null && (!/^\d+$/.test(declared) || Number(declared) > MAX_LEASE_RESPONSE_BYTES)) {
3570
+ void res.body?.cancel().catch(() => {
3571
+ });
3572
+ return null;
3573
+ }
3574
+ const reader = res.body?.getReader();
3575
+ if (!reader) {
3576
+ return null;
3577
+ }
3578
+ const bytes = new Uint8Array(MAX_LEASE_RESPONSE_BYTES);
3579
+ let size = 0;
3580
+ try {
3581
+ for (; ; ) {
3582
+ const chunk = await beforeDeadline2(() => reader.read());
3583
+ if (chunk.done) {
3584
+ break;
3585
+ }
3586
+ if (size + chunk.value.byteLength > bytes.byteLength) {
3587
+ throw new Error("email credential lease response too large");
3588
+ }
3589
+ bytes.set(chunk.value, size);
3590
+ size += chunk.value.byteLength;
3591
+ }
3592
+ if (declared !== null && size !== Number(declared)) {
3593
+ return null;
3594
+ }
3595
+ const data = JSON.parse(
3596
+ new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(bytes.subarray(0, size))
3597
+ );
3598
+ return parseLease(data);
3599
+ } catch {
3600
+ return null;
3601
+ } finally {
3602
+ void reader.cancel().catch(() => {
3603
+ });
3604
+ try {
3605
+ reader.releaseLock();
3606
+ } catch {
3607
+ }
3608
+ }
3609
+ }
3610
+ __name(leaseEmailDomainCredential, "leaseEmailDomainCredential");
3611
+ function parseLease(data) {
3612
+ if (typeof data !== "object" || data === null) {
3613
+ return null;
3614
+ }
3615
+ const kind = data.kind;
3616
+ const accountId = data.accountId;
3617
+ if (typeof accountId !== "string" || !/^[a-zA-Z0-9_-]{1,128}$/.test(accountId)) {
3618
+ return null;
3619
+ }
3620
+ if (kind === "api_token") {
3621
+ const token = data.token;
3622
+ return typeof token === "string" && token.length > 0 ? { kind, token, accountId } : null;
3623
+ }
3624
+ return null;
3625
+ }
3626
+ __name(parseLease, "parseLease");
3627
+ var encoder5 = new TextEncoder();
3628
+ var cachedSignKey3;
3629
+ var cachedKeySecret3;
3630
+ async function getSignKey3(secret) {
3631
+ if (cachedSignKey3 && cachedKeySecret3 === secret) {
3632
+ return cachedSignKey3;
3633
+ }
3634
+ cachedSignKey3 = await crypto.subtle.importKey(
3635
+ "raw",
3636
+ encoder5.encode(secret),
3637
+ { name: "HMAC", hash: "SHA-256" },
3638
+ false,
3639
+ ["sign"]
3640
+ );
3641
+ cachedKeySecret3 = secret;
3642
+ return cachedSignKey3;
3643
+ }
3644
+ __name(getSignKey3, "getSignKey");
3645
+ async function hmacSign3(data, secret) {
3646
+ const key = await getSignKey3(secret);
3647
+ const sig = await crypto.subtle.sign("HMAC", key, encoder5.encode(data));
3648
+ const binary = String.fromCharCode(...new Uint8Array(sig));
3649
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3650
+ }
3651
+ __name(hmacSign3, "hmacSign");
3652
+
3653
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/email-provider-deadline.ts
3654
+ var EMAIL_PROVIDER_DEADLINE_MS = 3e4;
3655
+ var EMAIL_PROVIDER_BATCH_DEADLINE_MS = 6e4;
3656
+ var EmailProviderTimeoutError = class extends Error {
3657
+ static {
3658
+ __name(this, "EmailProviderTimeoutError");
3659
+ }
3660
+ constructor() {
3661
+ super("email provider outcome unknown after timeout");
3662
+ this.name = "EmailProviderTimeoutError";
3663
+ }
3664
+ };
3665
+ function createEmailProviderDeadline(totalMs = EMAIL_PROVIDER_BATCH_DEADLINE_MS) {
3666
+ const deadline = Date.now() + totalMs;
3667
+ return {
3668
+ remainingMs(reserveMs = 0) {
3669
+ return Math.max(0, deadline - Date.now() - reserveMs);
3670
+ },
3671
+ expired(reserveMs = 0) {
3672
+ return Date.now() >= deadline - reserveMs;
3673
+ },
3674
+ async run(submit, reserveMs = 0) {
3675
+ const remaining = Math.min(EMAIL_PROVIDER_DEADLINE_MS, deadline - Date.now() - reserveMs);
3676
+ if (remaining <= 0) {
3677
+ throw new EmailProviderTimeoutError();
3678
+ }
3679
+ const work = submit();
3680
+ let timer;
3681
+ try {
3682
+ return await Promise.race([
3683
+ work,
3684
+ new Promise((_, reject) => {
3685
+ timer = setTimeout(() => reject(new EmailProviderTimeoutError()), remaining);
3686
+ })
3687
+ ]);
3688
+ } finally {
3689
+ if (timer !== void 0) {
3690
+ clearTimeout(timer);
3691
+ }
3692
+ }
3693
+ }
3694
+ };
3695
+ }
3696
+ __name(createEmailProviderDeadline, "createEmailProviderDeadline");
3697
+
3698
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/email.ts
3699
+ var app = new Hono2();
3700
+ var EMAIL_SEND_DEADLINE_MS = 5e4;
3701
+ var EMAIL_SEND_FINALIZATION_MARGIN_MS = 5e3;
3702
+ var MAX_MIME_BYTES = 5 * 1024 * 1024;
3703
+ var MAX_CUSTOM_HEADER_BYTES = 16 * 1024;
3704
+ var BUILTIN_MIME_HEADERS = /* @__PURE__ */ new Set([
3705
+ "from",
3706
+ "to",
3707
+ "cc",
3708
+ "bcc",
3709
+ "subject",
3710
+ "reply-to",
3711
+ "date",
3712
+ "message-id",
3713
+ "in-reply-to",
3714
+ "references",
3715
+ "mime-version",
3716
+ "content-type",
3717
+ "content-transfer-encoding",
3718
+ "content-disposition"
3719
+ ]);
3720
+ function customMimeHeaderBytesWithinLimit(raw2) {
3721
+ let used = 0;
3722
+ let custom = false;
3723
+ for (let start = 0; start < raw2.byteLength; ) {
3724
+ let end = start;
3725
+ while (end < raw2.byteLength && raw2[end] !== 10) {
3726
+ end++;
3727
+ }
3728
+ const lineEnd = end > start && raw2[end - 1] === 13 ? end - 1 : end;
3729
+ if (lineEnd === start) {
3730
+ return true;
3731
+ }
3732
+ if (raw2[start] !== 32 && raw2[start] !== 9) {
3733
+ let colon = start;
3734
+ while (colon < lineEnd && raw2[colon] !== 58) {
3735
+ colon++;
3736
+ }
3737
+ const name = new TextDecoder().decode(raw2.subarray(start, colon)).toLowerCase();
3738
+ custom = colon < lineEnd && !BUILTIN_MIME_HEADERS.has(name);
3739
+ }
3740
+ if (custom) {
3741
+ used += lineEnd - start + 2;
3742
+ if (used > MAX_CUSTOM_HEADER_BYTES) {
3743
+ return false;
3744
+ }
3745
+ }
3746
+ start = end + 1;
3747
+ }
3748
+ return true;
3749
+ }
3750
+ __name(customMimeHeaderBytesWithinLimit, "customMimeHeaderBytesWithinLimit");
3751
+ var MAX_RECIPIENTS = 50;
3752
+ var MAX_RECIPIENT_LENGTH = 320;
3753
+ var MAX_SENDER_LENGTH = 320;
3754
+ function readSenderDomain(env) {
3755
+ const raw2 = env.EMAIL_SENDER_DOMAIN;
3756
+ if (typeof raw2 !== "string") {
3757
+ return null;
3758
+ }
3759
+ const domain = raw2.trim().toLowerCase();
3760
+ return domain.length > 0 ? domain : null;
3761
+ }
3762
+ __name(readSenderDomain, "readSenderDomain");
3763
+ var RE_METACHARS_RE = /[.*+?^${}()|[\]\\]/g;
3764
+ function escapeRegExp(value) {
3765
+ return value.replace(RE_METACHARS_RE, "\\$&");
3766
+ }
3767
+ __name(escapeRegExp, "escapeRegExp");
3768
+ var strictFromCache = /* @__PURE__ */ new Map();
3769
+ function strictFromRe(domain) {
3770
+ const cached = strictFromCache.get(domain);
3771
+ if (cached) {
3772
+ return cached;
3773
+ }
3774
+ const re = new RegExp(`^[a-z0-9._-]+(?:\\+[a-z0-9._=/-]+)*@${escapeRegExp(domain)}$`);
3775
+ strictFromCache.set(domain, re);
3776
+ return re;
3777
+ }
3778
+ __name(strictFromRe, "strictFromRe");
3779
+ var CONTROL_CHARS_RE = new RegExp("[\\u0000-\\u001f\\u007f]+", "g");
3780
+ function sanitizeForLog(value, maxLen = 200) {
3781
+ return value.slice(0, maxLen).replace(CONTROL_CHARS_RE, " ");
3782
+ }
3783
+ __name(sanitizeForLog, "sanitizeForLog");
3784
+ var MAX_REQUEST_ID_LENGTH = 128;
3785
+ function bytesToStream(bytes) {
3786
+ return new ReadableStream({
3787
+ start(controller) {
3788
+ controller.enqueue(bytes);
3789
+ controller.close();
3790
+ }
3791
+ });
3792
+ }
3793
+ __name(bytesToStream, "bytesToStream");
3794
+ var LF = 10;
3795
+ var CR = 13;
3796
+ var SP = 32;
3797
+ var HTAB = 9;
3798
+ var COLON = 58;
3799
+ var FROM_NAME = [102, 114, 111, 109];
3800
+ function fromValueStart(raw2, start, end) {
3801
+ if (end - start <= FROM_NAME.length) {
3802
+ return -1;
3803
+ }
3804
+ for (let i = 0; i < FROM_NAME.length; i++) {
3805
+ if ((raw2[start + i] | 32) !== FROM_NAME[i]) {
3806
+ return -1;
3807
+ }
3808
+ }
3809
+ let colon = start + FROM_NAME.length;
3810
+ while (colon < end && (raw2[colon] === SP || raw2[colon] === HTAB)) {
3811
+ colon++;
3812
+ }
3813
+ return colon < end && raw2[colon] === COLON ? colon + 1 : -1;
3814
+ }
3815
+ __name(fromValueStart, "fromValueStart");
3816
+ var MIME_FROM_REFUSALS = {
3817
+ missing: "the message has no From header",
3818
+ multiple: "the message has more than one From header",
3350
3819
  malformed: "the From header must hold a single mailbox"
3351
3820
  };
3352
3821
  function readMimeFromAddrSpec(raw2) {
@@ -3486,37 +3955,22 @@ function singleMailboxAddrSpec(value) {
3486
3955
  return /^[\x21-\x7e]+$/.test(addrSpec) ? addrSpec : null;
3487
3956
  }
3488
3957
  __name(singleMailboxAddrSpec, "singleMailboxAddrSpec");
3489
- async function readRoutingSuspension(kv2, slug) {
3490
- const raw2 = await kv2.get(slug);
3491
- if (!raw2) {
3492
- return { kind: "absent" };
3493
- }
3494
- let parsed;
3495
- try {
3496
- parsed = JSON.parse(raw2);
3497
- } catch {
3498
- return { kind: "corrupt" };
3499
- }
3500
- if (typeof parsed !== "object" || parsed === null) {
3501
- return { kind: "corrupt" };
3502
- }
3503
- const suspended = parsed.suspended;
3504
- if (!suspended) {
3505
- return { kind: "clear" };
3506
- }
3507
- if (typeof suspended !== "string") {
3508
- return { kind: "corrupt" };
3509
- }
3510
- return { kind: "suspended", reason: suspended };
3511
- }
3512
- __name(readRoutingSuspension, "readRoutingSuspension");
3513
3958
  app.post("/send", async (context) => {
3959
+ const sendDeadline = createEmailProviderDeadline(EMAIL_SEND_DEADLINE_MS);
3514
3960
  const projectId = context.get("projectId");
3515
3961
  const body = context.get("body");
3516
3962
  const from = asString(body.from);
3517
3963
  const mimeBase64 = asString(body.mimeBase64);
3518
3964
  const requestIdRaw = asString(body.requestId);
3519
3965
  const requestId = requestIdRaw === null ? "unknown" : sanitizeForLog(requestIdRaw, MAX_REQUEST_ID_LENGTH);
3966
+ const callerKey = asString(body.idempotencyKey);
3967
+ if (callerKey !== null && (callerKey.length === 0 || callerKey.length > 128 || !/^[\x20-\x7e]+$/.test(callerKey))) {
3968
+ return context.json(
3969
+ { ok: false, error: { code: "INVALID_TO", message: "invalid idempotency key" } },
3970
+ 400
3971
+ );
3972
+ }
3973
+ const operationKey = callerKey ?? (requestIdRaw && requestIdRaw.length <= 128 && /^[\x20-\x7e]+$/.test(requestIdRaw) ? requestIdRaw : crypto.randomUUID());
3520
3974
  const recipientsRaw = Array.isArray(body.recipients) ? body.recipients : null;
3521
3975
  if (!from || !mimeBase64 || !recipientsRaw) {
3522
3976
  return context.json(
@@ -3561,19 +4015,6 @@ app.post("/send", async (context) => {
3561
4015
  502
3562
4016
  );
3563
4017
  }
3564
- const sendBinding = context.env.SEND_EMAIL;
3565
- if (typeof sendBinding?.send !== "function") {
3566
- return context.json(
3567
- {
3568
- ok: false,
3569
- error: {
3570
- code: "UPSTREAM_ERROR",
3571
- message: "email sending is not configured"
3572
- }
3573
- },
3574
- 502
3575
- );
3576
- }
3577
4018
  const recipients = [];
3578
4019
  const seen = /* @__PURE__ */ new Set();
3579
4020
  for (const r of recipientsRaw) {
@@ -3638,46 +4079,45 @@ app.post("/send", async (context) => {
3638
4079
  413
3639
4080
  );
3640
4081
  }
3641
- const expectedSlug = await context.env.ROUTING.get(`email-slug:${projectId}`);
3642
- if (!expectedSlug) {
4082
+ if (!customMimeHeaderBytesWithinLimit(raw2)) {
3643
4083
  return context.json(
3644
4084
  {
3645
4085
  ok: false,
3646
- error: {
3647
- code: "UPSTREAM_ERROR",
3648
- message: "project slug unknown \u2014 refusing to send"
3649
- }
4086
+ error: { code: "MIME_ERROR", message: "custom headers exceed the 16 KiB limit" }
3650
4087
  },
3651
- 502
4088
+ 413
3652
4089
  );
3653
4090
  }
3654
- if (!SLUG_RE.test(expectedSlug)) {
3655
- console.error(
3656
- `[email-send] project ${projectId} slug malformed in KV: ${sanitizeForLog(expectedSlug, 64)}`
4091
+ const fromAt = from.indexOf("@");
4092
+ const fromDomain = fromAt === -1 ? "" : from.slice(fromAt + 1).toLowerCase();
4093
+ if (fromDomain !== "" && fromDomain !== senderDomain) {
4094
+ return sendViaCustomDomain(
4095
+ context,
4096
+ projectId,
4097
+ from,
4098
+ fromDomain,
4099
+ senderDomain,
4100
+ recipients,
4101
+ raw2,
4102
+ requestId,
4103
+ operationKey,
4104
+ sendDeadline
3657
4105
  );
4106
+ }
4107
+ const sendBinding = context.env.SEND_EMAIL;
4108
+ if (typeof sendBinding?.send !== "function") {
3658
4109
  return context.json(
3659
4110
  {
3660
4111
  ok: false,
3661
4112
  error: {
3662
4113
  code: "UPSTREAM_ERROR",
3663
- message: "project slug malformed in routing"
4114
+ message: "email sending is not configured"
3664
4115
  }
3665
4116
  },
3666
4117
  502
3667
4118
  );
3668
4119
  }
3669
- if (isReservedEmailSlug(expectedSlug)) {
3670
- return context.json(
3671
- {
3672
- ok: false,
3673
- error: {
3674
- code: "INVALID_FROM",
3675
- message: `project slug "${expectedSlug}" is a reserved email identity on ${senderDomain} \u2014 refusing to send`
3676
- }
3677
- },
3678
- 403
3679
- );
3680
- }
4120
+ const expectedSlug = from.split("@")[0]?.split("+")[0] ?? "";
3681
4121
  if (!/^[\x21-\x7e]+$/.test(from)) {
3682
4122
  return context.json(
3683
4123
  {
@@ -3728,119 +4168,160 @@ app.post("/send", async (context) => {
3728
4168
  403
3729
4169
  );
3730
4170
  }
3731
- const platformSuspension = await readRoutingSuspension(context.env.ROUTING, expectedSlug);
3732
- if (platformSuspension.kind === "corrupt") {
3733
- console.error(
3734
- `[email-send] project ${projectId} routing blob malformed in KV for slug ${sanitizeForLog(expectedSlug, 64)}`
3735
- );
3736
- return context.json(
4171
+ let admission;
4172
+ try {
4173
+ admission = await admitEmail(
4174
+ context.env,
3737
4175
  {
3738
- ok: false,
3739
- error: {
3740
- code: "UPSTREAM_ERROR",
3741
- message: "project routing entry malformed"
3742
- }
4176
+ projectId,
4177
+ kind: "direct",
4178
+ key: operationKey,
4179
+ digest: await emailPayloadDigest("direct", from, recipients, raw2),
4180
+ from,
4181
+ recipients,
4182
+ transport: "binding"
3743
4183
  },
3744
- 502
4184
+ sendDeadline.remainingMs(EMAIL_SEND_FINALIZATION_MARGIN_MS)
3745
4185
  );
3746
- }
3747
- if (platformSuspension.kind === "suspended") {
4186
+ } catch (error) {
4187
+ const conflict = error instanceof Error && error.message === "IDEMPOTENCY_CONFLICT";
3748
4188
  return context.json(
3749
4189
  {
3750
4190
  ok: false,
3751
4191
  error: {
3752
- code: "QUOTA_EXCEEDED",
3753
- message: `project is suspended (${sanitizeForLog(platformSuspension.reason, 64)})`
4192
+ code: conflict ? "IDEMPOTENCY_CONFLICT" : "UPSTREAM_ERROR",
4193
+ message: conflict ? "idempotency key has different content" : "email admission unavailable"
3754
4194
  }
3755
4195
  },
3756
- 200
3757
- );
3758
- }
3759
- if (await context.env.ROUTING.get(`email-suspended:${projectId}`)) {
3760
- return context.json(
3761
- { ok: false, error: { code: "QUOTA_EXCEEDED", message: "project email is suspended" } },
3762
- 200
4196
+ conflict ? 409 : 503
3763
4197
  );
3764
4198
  }
3765
- const allowlist = await readAllowlist(context.env.ROUTING, projectId);
3766
- const allowed = [];
3767
- const deliveries = [];
3768
- for (const r of recipients) {
3769
- if (allowlist.has(r)) {
3770
- allowed.push(r);
3771
- } else {
4199
+ const deliveries = admission.recipients.filter((r) => r.state !== "reserved").map((r) => ({
4200
+ recipient: r.recipient,
4201
+ ok: r.state === "provider_accepted",
4202
+ state: r.state,
4203
+ messageId: r.providerReference ?? void 0,
4204
+ error: r.state === "provider_accepted" ? void 0 : {
4205
+ code: r.errorCode ?? (r.state === "outcome_unknown" || r.state === "attempt_started" ? "OUTCOME_UNKNOWN" : r.state === "cancelled" ? "UPSTREAM_ERROR" : "UNVERIFIED_DESTINATION"),
4206
+ message: r.state === "outcome_unknown" || r.state === "attempt_started" ? "provider outcome unknown; inspect email logs before retrying" : r.state === "cancelled" ? "email attempt was cancelled before provider submission" : "recipient was not admitted"
4207
+ }
4208
+ }));
4209
+ if (admission.replay) {
4210
+ for (const row of admission.recipients.filter((r) => r.state === "reserved")) {
3772
4211
  deliveries.push({
3773
- recipient: r,
4212
+ recipient: row.recipient,
3774
4213
  ok: false,
4214
+ state: "reserved",
3775
4215
  error: {
3776
- code: "UNVERIFIED_DESTINATION",
3777
- message: `recipient '${r}' is not a verified destination for this project`
4216
+ code: "OUTCOME_UNKNOWN",
4217
+ message: "email action remains reserved; inspect email logs before retrying"
3778
4218
  }
3779
4219
  });
3780
4220
  }
3781
- }
3782
- if (allowed.length === 0) {
3783
- return context.json({ ok: false, deliveries }, 200);
3784
- }
3785
- const now = /* @__PURE__ */ new Date();
3786
- const burstKey = `email-burst:${projectId}:${minuteBucket(now)}`;
3787
- const burstCurrent = readCounter(await context.env.ROUTING.get(burstKey));
3788
- const burstLimit = await getBurstLimit(context.env.ROUTING, projectId);
3789
- if (burstCurrent + allowed.length > burstLimit) {
3790
- return context.json(
3791
- {
3792
- ok: false,
3793
- error: {
3794
- code: "QUOTA_EXCEEDED",
3795
- message: `outbound burst limit ${burstLimit}/min exceeded`
3796
- }
3797
- },
3798
- 429
3799
- );
3800
- }
3801
- const counterKey = `email-sent:${projectId}:${monthBucket(now)}`;
3802
- const monthCurrent = readCounter(await context.env.ROUTING.get(counterKey));
3803
- const monthLimit = await getMonthlyLimit(context.env.ROUTING, projectId);
3804
- if (monthCurrent + allowed.length > monthLimit) {
3805
- return context.json(
3806
- {
4221
+ return emailActionResponse(context, admission.operationId, deliveries);
4222
+ }
4223
+ const allowed = admission.recipients.filter((r) => r.state === "reserved").map((r) => r.recipient);
4224
+ for (let index = 0; index < allowed.length; index++) {
4225
+ const r = allowed[index];
4226
+ if (sendDeadline.expired(EMAIL_SEND_FINALIZATION_MARGIN_MS)) {
4227
+ const unstarted = allowed.slice(index);
4228
+ const cancelled = await cancelUnstartedRecipients(
4229
+ context.env,
4230
+ admission.operationId,
4231
+ unstarted,
4232
+ sendDeadline.remainingMs()
4233
+ );
4234
+ for (const recipient of unstarted) {
4235
+ deliveries.push({
4236
+ recipient,
4237
+ ok: false,
4238
+ state: cancelled ? "cancelled" : "outcome_unknown",
4239
+ error: cancelled ? {
4240
+ code: "UPSTREAM_ERROR",
4241
+ message: "email batch deadline elapsed before provider submission"
4242
+ } : { code: "OUTCOME_UNKNOWN", message: "could not cancel unstarted email attempt" }
4243
+ });
4244
+ }
4245
+ break;
4246
+ }
4247
+ try {
4248
+ await startEmailAttempt(
4249
+ context.env,
4250
+ admission.operationId,
4251
+ [r],
4252
+ sendDeadline.remainingMs(EMAIL_SEND_FINALIZATION_MARGIN_MS)
4253
+ );
4254
+ } catch {
4255
+ const cancelled = await cancelUnstartedRecipients(
4256
+ context.env,
4257
+ admission.operationId,
4258
+ [r],
4259
+ sendDeadline.remainingMs()
4260
+ );
4261
+ deliveries.push({
4262
+ recipient: r,
3807
4263
  ok: false,
3808
- error: {
3809
- code: "QUOTA_EXCEEDED",
3810
- message: `monthly outbound limit ${monthLimit} exceeded`
3811
- }
3812
- },
3813
- 429
3814
- );
3815
- }
3816
- await Promise.all([
3817
- context.env.ROUTING.put(counterKey, String(monthCurrent + allowed.length), {
3818
- expirationTtl: monthTtlSeconds(now)
3819
- }).catch((e) => {
3820
- console.error(`[email-send] monthly counter put failed for ${projectId}:`, e);
3821
- }),
3822
- context.env.ROUTING.put(burstKey, String(burstCurrent + allowed.length), {
3823
- expirationTtl: 5 * 60
3824
- }).catch((e) => {
3825
- console.error(`[email-send] burst counter put failed for ${projectId}:`, e);
3826
- })
3827
- ]);
3828
- for (const r of allowed) {
4264
+ state: cancelled ? "cancelled" : "outcome_unknown",
4265
+ error: cancelled ? {
4266
+ code: "UPSTREAM_ERROR",
4267
+ message: "email attempt did not start; reservation cancelled"
4268
+ } : { code: "OUTCOME_UNKNOWN", message: "could not confirm email attempt status" }
4269
+ });
4270
+ continue;
4271
+ }
3829
4272
  try {
3830
4273
  const msg = new EmailMessage(from, r, bytesToStream(raw2));
3831
- const result = await sendBinding.send(msg);
4274
+ const result = await sendDeadline.run(
4275
+ () => sendBinding.send(msg),
4276
+ EMAIL_SEND_FINALIZATION_MARGIN_MS
4277
+ );
4278
+ const providerReference = typeof result?.messageId === "string" ? result.messageId : "";
4279
+ try {
4280
+ await finishEmailAttempt(
4281
+ context.env,
4282
+ admission.operationId,
4283
+ [r],
4284
+ "provider_accepted",
4285
+ { providerReference },
4286
+ sendDeadline.remainingMs()
4287
+ );
4288
+ } catch {
4289
+ deliveries.push({
4290
+ recipient: r,
4291
+ ok: false,
4292
+ state: "outcome_unknown",
4293
+ error: {
4294
+ code: "OUTCOME_UNKNOWN",
4295
+ message: "provider accepted email but outcome could not be recorded"
4296
+ }
4297
+ });
4298
+ continue;
4299
+ }
3832
4300
  deliveries.push({
3833
4301
  recipient: r,
3834
4302
  ok: true,
3835
- messageId: typeof result?.messageId === "string" ? result.messageId : ""
4303
+ state: "provider_accepted",
4304
+ messageId: providerReference
3836
4305
  });
3837
- } catch (e) {
4306
+ } catch {
4307
+ try {
4308
+ await finishEmailAttempt(
4309
+ context.env,
4310
+ admission.operationId,
4311
+ [r],
4312
+ "outcome_unknown",
4313
+ { errorCode: "OUTCOME_UNKNOWN" },
4314
+ sendDeadline.remainingMs()
4315
+ );
4316
+ } catch {
4317
+ }
3838
4318
  deliveries.push({
3839
4319
  recipient: r,
3840
4320
  ok: false,
4321
+ state: "outcome_unknown",
3841
4322
  error: {
3842
- code: classifyUpstream(e, projectId),
3843
- message: e instanceof Error ? e.message : String(e)
4323
+ code: "OUTCOME_UNKNOWN",
4324
+ message: "provider outcome unknown; inspect email logs before retrying"
3844
4325
  }
3845
4326
  });
3846
4327
  }
@@ -3848,839 +4329,612 @@ app.post("/send", async (context) => {
3848
4329
  logSend(projectId, requestId, deliveries);
3849
4330
  const anyFailed = deliveries.some((d) => !d.ok);
3850
4331
  if (anyFailed) {
3851
- return context.json({ ok: false, deliveries }, 200);
4332
+ return context.json({ ok: false, operationId: admission.operationId, deliveries }, 200);
3852
4333
  }
3853
4334
  return context.json({
3854
4335
  ok: true,
4336
+ operationId: admission.operationId,
3855
4337
  ids: deliveries.map((d) => ({
3856
4338
  recipient: d.recipient,
3857
- messageId: d.messageId
4339
+ messageId: d.messageId,
4340
+ state: "provider_accepted"
3858
4341
  }))
3859
4342
  });
3860
4343
  });
3861
- async function recordInboundDelivery(env, projectId, info) {
3862
- const now = /* @__PURE__ */ new Date();
3863
- const counterKey = `email-recv:${projectId}:${monthBucket(now)}`;
3864
- const current = readCounter(await env.ROUTING.get(counterKey));
3865
- await env.ROUTING.put(counterKey, String(current + 1), {
3866
- expirationTtl: monthTtlSeconds(now)
4344
+ var MAX_SEND_RAW_RESPONSE_BYTES = 64 * 1024;
4345
+ async function requestSendRaw(input, deadlineMs = EMAIL_PROVIDER_DEADLINE_MS) {
4346
+ const controller = new AbortController();
4347
+ const deadline = Date.now() + Math.min(EMAIL_PROVIDER_DEADLINE_MS, deadlineMs);
4348
+ const requestBody = JSON.stringify({
4349
+ from: input.from,
4350
+ recipients: input.recipients,
4351
+ mime_message: input.mimeMessage
3867
4352
  });
3868
- console.log(
3869
- `[email-recv] ${projectId} ${sanitizeForLog(info.from, 320)} -> ${sanitizeForLog(info.to, 320)} ${info.status} (${info.byteSize}B) @${Date.now()} subject="${sanitizeForLog(info.subject)}"`
4353
+ const beforeDeadline2 = /* @__PURE__ */ __name(async (run) => {
4354
+ const remaining = deadline - Date.now();
4355
+ if (remaining <= 0) {
4356
+ controller.abort();
4357
+ throw new Error("email sending request timed out");
4358
+ }
4359
+ const promise = run();
4360
+ let timer;
4361
+ try {
4362
+ return await Promise.race([
4363
+ promise,
4364
+ new Promise((_, reject) => {
4365
+ timer = setTimeout(() => {
4366
+ controller.abort();
4367
+ reject(new Error("email sending request timed out"));
4368
+ }, remaining);
4369
+ })
4370
+ ]);
4371
+ } finally {
4372
+ if (timer !== void 0) {
4373
+ clearTimeout(timer);
4374
+ }
4375
+ }
4376
+ }, "beforeDeadline");
4377
+ const response = await beforeDeadline2(
4378
+ () => fetch(
4379
+ `https://api.cloudflare.com/client/v4/accounts/${input.accountId}/email/sending/send_raw`,
4380
+ {
4381
+ method: "POST",
4382
+ headers: {
4383
+ authorization: `Bearer ${input.bearer}`,
4384
+ "content-type": "application/json"
4385
+ },
4386
+ body: requestBody,
4387
+ signal: controller.signal
4388
+ }
4389
+ )
3870
4390
  );
3871
- }
3872
- __name(recordInboundDelivery, "recordInboundDelivery");
3873
- var UNVERIFIED_DESTINATION_RE = /^[^\n]{0,200}destination address[^\n]{0,200}not (?:yet )?verified/i;
3874
- function classifyUpstream(e, projectId) {
3875
- const message = e instanceof Error ? e.message : String(e);
3876
- const code = typeof e?.code === "string" ? e.code : "";
3877
- switch (code) {
3878
- case "E_RECIPIENT_NOT_ALLOWED":
3879
- return "UNVERIFIED_DESTINATION";
3880
- case "E_RATE_LIMIT_EXCEEDED":
3881
- case "E_DAILY_LIMIT_EXCEEDED":
3882
- return "QUOTA_EXCEEDED";
3883
- case "E_SENDER_NOT_VERIFIED":
3884
- case "E_SENDER_DOMAIN_NOT_AVAILABLE":
3885
- console.error(
3886
- `[email-send] project ${projectId} send binding refused the platform sender (${code}): ${sanitizeForLog(message)}`
3887
- );
3888
- return "UPSTREAM_ERROR";
3889
- default:
3890
- break;
4391
+ if (!response.ok) {
4392
+ void response.body?.cancel().catch(() => {
4393
+ });
4394
+ return { ok: false, status: response.status };
3891
4395
  }
3892
- if (UNVERIFIED_DESTINATION_RE.test(message)) {
3893
- return "UNVERIFIED_DESTINATION";
4396
+ const declared = response.headers.get("content-length");
4397
+ if (declared !== null && (!/^\d+$/.test(declared) || Number(declared) > MAX_SEND_RAW_RESPONSE_BYTES)) {
4398
+ void response.body?.cancel().catch(() => {
4399
+ });
4400
+ throw new Error("email sending API response too large");
3894
4401
  }
3895
- return "UPSTREAM_ERROR";
3896
- }
3897
- __name(classifyUpstream, "classifyUpstream");
3898
- function logSend(projectId, requestId, deliveries) {
3899
- for (const d of deliveries) {
3900
- const status = d.ok ? "sent" : d.error?.code ?? "unknown_error";
3901
- const id = d.messageId ?? "";
3902
- console.log(
3903
- `[email-log] ${projectId} ${requestId} ${sanitizeForLog(d.recipient, 320)} ${status} ${id} @${Date.now()}`
3904
- );
3905
- }
3906
- }
3907
- __name(logSend, "logSend");
3908
- async function readAllowlist(kv2, projectId) {
3909
- const raw2 = await kv2.get(`email-allowlist:${projectId}`, { cacheTtl: 60 });
3910
- if (!raw2) {
3911
- return /* @__PURE__ */ new Set();
4402
+ const reader = response.body?.getReader();
4403
+ if (!reader) {
4404
+ throw new Error("email sending API response unreadable");
3912
4405
  }
4406
+ const bytes = new Uint8Array(MAX_SEND_RAW_RESPONSE_BYTES);
4407
+ let size = 0;
3913
4408
  try {
3914
- const arr = JSON.parse(raw2);
3915
- if (!Array.isArray(arr)) {
3916
- return /* @__PURE__ */ new Set();
3917
- }
3918
- const out = /* @__PURE__ */ new Set();
3919
- for (const v of arr) {
3920
- if (typeof v === "string") {
3921
- out.add(v.toLowerCase());
4409
+ for (; ; ) {
4410
+ const chunk = await beforeDeadline2(() => reader.read());
4411
+ if (chunk.done) {
4412
+ break;
4413
+ }
4414
+ if (size + chunk.value.byteLength > bytes.byteLength) {
4415
+ throw new Error("email sending API response too large");
3922
4416
  }
4417
+ bytes.set(chunk.value, size);
4418
+ size += chunk.value.byteLength;
3923
4419
  }
3924
- return out;
3925
- } catch {
3926
- return /* @__PURE__ */ new Set();
3927
- }
3928
- }
3929
- __name(readAllowlist, "readAllowlist");
3930
- function readCounter(raw2) {
3931
- if (!raw2) {
3932
- return 0;
3933
- }
3934
- const n = parseInt(raw2, 10);
3935
- return Number.isFinite(n) && n >= 0 ? n : 0;
3936
- }
3937
- __name(readCounter, "readCounter");
3938
- function base64Decode(b64) {
3939
- const binary = atob(b64);
3940
- const out = new Uint8Array(binary.length);
3941
- for (let i = 0; i < binary.length; i++) {
3942
- out[i] = binary.charCodeAt(i);
3943
- }
3944
- return out;
3945
- }
3946
- __name(base64Decode, "base64Decode");
3947
- function monthBucket(now) {
3948
- return `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}`;
3949
- }
3950
- __name(monthBucket, "monthBucket");
3951
- function minuteBucket(now) {
3952
- return `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}${String(
3953
- now.getUTCDate()
3954
- ).padStart(2, "0")}T${String(now.getUTCHours()).padStart(2, "0")}${String(
3955
- now.getUTCMinutes()
3956
- ).padStart(2, "0")}`;
3957
- }
3958
- __name(minuteBucket, "minuteBucket");
3959
- function monthTtlSeconds(now) {
3960
- const endOfMonth = Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1, 0, 0, 0, 0);
3961
- const seconds = Math.ceil((endOfMonth - now.getTime()) / 1e3) + 60 * 60;
3962
- return Math.max(60, seconds);
3963
- }
3964
- __name(monthTtlSeconds, "monthTtlSeconds");
3965
- async function getBurstLimit(kv2, projectId) {
3966
- const raw2 = await kv2.get(`email-burst-limit:${projectId}`, { cacheTtl: 60 });
3967
- if (raw2) {
3968
- const n = parseInt(raw2, 10);
3969
- if (Number.isFinite(n) && n > 0) {
3970
- return n;
4420
+ if (declared !== null && size !== Number(declared)) {
4421
+ throw new Error("email sending API response length mismatch");
3971
4422
  }
3972
- }
3973
- return 10;
3974
- }
3975
- __name(getBurstLimit, "getBurstLimit");
3976
- async function getMonthlyLimit(kv2, projectId) {
3977
- const raw2 = await kv2.get(`email-limit:${projectId}`, { cacheTtl: 60 });
3978
- if (raw2) {
3979
- const n = parseInt(raw2, 10);
3980
- if (Number.isFinite(n) && n > 0) {
3981
- return n;
4423
+ return {
4424
+ ok: true,
4425
+ data: JSON.parse(
4426
+ new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(bytes.subarray(0, size))
4427
+ )
4428
+ };
4429
+ } finally {
4430
+ void reader.cancel().catch(() => {
4431
+ });
4432
+ try {
4433
+ reader.releaseLock();
4434
+ } catch {
3982
4435
  }
3983
4436
  }
3984
- return 200;
3985
4437
  }
3986
- __name(getMonthlyLimit, "getMonthlyLimit");
3987
- var INTERNAL_FROM_LOCAL_PART = "noreply";
3988
- app.post("/internal/send", async (context) => {
3989
- const body = context.get("body");
3990
- const senderDomain = readSenderDomain(context.env);
3991
- if (!senderDomain) {
3992
- return context.json({ ok: false, error: "email sender domain is not configured" }, 502);
3993
- }
3994
- const from = asString(body.from);
3995
- const to = asString(body.to);
3996
- const mimeBase64 = asString(body.mimeBase64);
3997
- if (!from || !to || !mimeBase64) {
3998
- return context.json({ ok: false, error: "invalid body" }, 400);
3999
- }
4000
- if (!/^[\x21-\x7e]+$/.test(from)) {
4001
- return context.json({ ok: false, error: "from not allowed for internal send" }, 403);
4002
- }
4003
- if (from.toLowerCase() !== `${INTERNAL_FROM_LOCAL_PART}@${senderDomain}`) {
4004
- return context.json({ ok: false, error: "from not allowed for internal send" }, 403);
4005
- }
4006
- const sendBinding = context.env.SEND_EMAIL;
4007
- if (typeof sendBinding?.send !== "function") {
4008
- return context.json({ ok: false, error: "email sending is not configured" }, 502);
4009
- }
4010
- let raw2;
4011
- try {
4012
- raw2 = base64Decode(mimeBase64);
4013
- } catch {
4014
- return context.json({ ok: false, error: "invalid base64 mime" }, 400);
4015
- }
4016
- if (raw2.byteLength > MAX_MIME_BYTES) {
4017
- return context.json({ ok: false, error: "too large" }, 413);
4018
- }
4019
- try {
4020
- const msg = new EmailMessage(from, to, bytesToStream(raw2));
4021
- await sendBinding.send(msg);
4022
- return context.json({ ok: true });
4023
- } catch (err) {
4024
- const message = err instanceof Error ? err.message : String(err);
4025
- console.error(
4026
- `[email-internal] send failed from=${sanitizeForLog(from, 320)} to=${sanitizeForLog(to, 320)}: ${sanitizeForLog(message, 320)}`
4027
- );
4028
- return context.json({ ok: false, error: "upstream error" }, 502);
4029
- }
4030
- });
4031
-
4032
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/email-router.ts
4033
- var CONTROL_CHARS_RE2 = new RegExp("[\\u0000-\\u001f\\u007f]+", "g");
4034
- function sanitize(value, maxLen) {
4035
- return value.slice(0, maxLen).replace(CONTROL_CHARS_RE2, " ");
4036
- }
4037
- __name(sanitize, "sanitize");
4038
- function describeUntrusted(value, maxLen) {
4039
- switch (typeof value) {
4040
- case "string":
4041
- return sanitize(value, maxLen);
4042
- case "number":
4043
- case "boolean":
4044
- case "undefined":
4045
- return String(value);
4046
- case "object":
4047
- return value === null ? "null" : sanitize(Object.prototype.toString.call(value), 64);
4048
- default:
4049
- return `[${typeof value}]`;
4050
- }
4051
- }
4052
- __name(describeUntrusted, "describeUntrusted");
4053
- var MAX_INBOUND_BYTES = 10 * 1024 * 1024;
4054
- var MAX_INBOUND_HEADERS = 1024;
4055
- var MAX_REJECT_REASON_LENGTH = 1e3;
4056
- var MAX_ACTIONS = 100;
4057
- var MAX_ACTION_ADDRESS_LENGTH = 320;
4058
- var MAX_FORWARD_HEADERS = 32;
4059
- var MAX_FORWARD_HEADER_LENGTH = 1024;
4060
- var MAX_REPLY_BYTES = MAX_MIME_BYTES;
4061
- var MAX_REPLY_BYTES_TOTAL = 2 * MAX_REPLY_BYTES;
4062
- var RPC_METHOD_MISSING = /does not implement the method|receiver does not.*method/i;
4063
- var InboundTooLargeError = class extends Error {
4064
- constructor(size) {
4065
- super(`inbound message exceeds ${MAX_INBOUND_BYTES} bytes (got ${size})`);
4066
- this.size = size;
4067
- this.name = "InboundTooLargeError";
4068
- }
4069
- size;
4070
- static {
4071
- __name(this, "InboundTooLargeError");
4072
- }
4073
- };
4074
- var InboundTooManyHeadersError = class extends Error {
4075
- static {
4076
- __name(this, "InboundTooManyHeadersError");
4077
- }
4078
- constructor() {
4079
- super(`inbound message carries more than ${MAX_INBOUND_HEADERS} headers`);
4080
- this.name = "InboundTooManyHeadersError";
4081
- }
4082
- };
4083
- function isValidEmailAddress(s) {
4084
- return typeof s === "string" && s.length <= MAX_ACTION_ADDRESS_LENGTH && (0, import_email_validator.validate)(s);
4085
- }
4086
- __name(isValidEmailAddress, "isValidEmailAddress");
4087
- function logAddress(value) {
4088
- return describeUntrusted(value, MAX_ACTION_ADDRESS_LENGTH);
4089
- }
4090
- __name(logAddress, "logAddress");
4091
- function forwardHeadersWithinBounds(headers) {
4092
- if (typeof headers !== "object" || headers === null) {
4093
- console.error("[email-router] action list dropped: forward headers is not a plain object");
4094
- return false;
4095
- }
4096
- const proto = Object.getPrototypeOf(headers);
4097
- if (proto !== Object.prototype && proto !== null) {
4098
- console.error("[email-router] action list dropped: forward headers is not a plain object");
4099
- return false;
4100
- }
4101
- const names = Object.keys(headers);
4102
- if (names.length > MAX_FORWARD_HEADERS) {
4103
- console.error(
4104
- `[email-router] action list dropped: forward has ${names.length} headers (max ${MAX_FORWARD_HEADERS})`
4105
- );
4106
- return false;
4438
+ __name(requestSendRaw, "requestSendRaw");
4439
+ function readSendRawAddressList(value) {
4440
+ if (!Array.isArray(value)) {
4441
+ return null;
4107
4442
  }
4108
- for (const name of names) {
4109
- const value = headers[name];
4110
- if (name.length > MAX_FORWARD_HEADER_LENGTH) {
4111
- console.error(
4112
- `[email-router] action list dropped: forward header name exceeds ${MAX_FORWARD_HEADER_LENGTH} characters`
4113
- );
4114
- return false;
4115
- }
4116
- if (typeof value !== "string") {
4117
- console.error(
4118
- `[email-router] action list dropped: forward header "${sanitize(name, 64)}" value is not a string`
4119
- );
4120
- return false;
4121
- }
4122
- if (value.length > MAX_FORWARD_HEADER_LENGTH) {
4123
- console.error(
4124
- `[email-router] action list dropped: forward header "${sanitize(name, 64)}" value exceeds ${MAX_FORWARD_HEADER_LENGTH} characters`
4125
- );
4126
- return false;
4443
+ const out = /* @__PURE__ */ new Set();
4444
+ for (const entry of value) {
4445
+ if (typeof entry !== "string") {
4446
+ return null;
4127
4447
  }
4448
+ out.add(entry.toLowerCase());
4128
4449
  }
4129
- return true;
4130
- }
4131
- __name(forwardHeadersWithinBounds, "forwardHeadersWithinBounds");
4132
- function isPermittedReplySender(from, pin) {
4133
- if (!/^[\x21-\x7e]+$/.test(from)) {
4134
- return false;
4135
- }
4136
- const lower = from.toLowerCase();
4137
- const at = lower.indexOf("@");
4138
- if (at <= 0) {
4139
- return false;
4140
- }
4141
- if (lower.slice(at + 1) !== pin.domain) {
4142
- return false;
4143
- }
4144
- const localPrefix = lower.slice(0, at).split("+")[0] ?? "";
4145
- return localPrefix.length > 0 && localPrefix === pin.slug;
4146
- }
4147
- __name(isPermittedReplySender, "isPermittedReplySender");
4148
- function readCounter2(raw2) {
4149
- if (!raw2) {
4150
- return 0;
4151
- }
4152
- const n = parseInt(raw2, 10);
4153
- return Number.isFinite(n) && n >= 0 ? n : 0;
4154
- }
4155
- __name(readCounter2, "readCounter");
4156
- function monthBucket2(now) {
4157
- return `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}`;
4158
- }
4159
- __name(monthBucket2, "monthBucket");
4160
- function minuteBucket2(now) {
4161
- return `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}${String(
4162
- now.getUTCDate()
4163
- ).padStart(2, "0")}T${String(now.getUTCHours()).padStart(2, "0")}${String(
4164
- now.getUTCMinutes()
4165
- ).padStart(2, "0")}`;
4166
- }
4167
- __name(minuteBucket2, "minuteBucket");
4168
- function monthTtlSeconds2(now) {
4169
- const endOfMonth = Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1, 0, 0, 0, 0);
4170
- const seconds = Math.ceil((endOfMonth - now.getTime()) / 1e3) + 60 * 60;
4171
- return Math.max(60, seconds);
4450
+ return out;
4172
4451
  }
4173
- __name(monthTtlSeconds2, "monthTtlSeconds");
4174
- async function readAllowlist2(kv2, projectId) {
4175
- const raw2 = await kv2.get(`email-allowlist:${projectId}`, { cacheTtl: 60 });
4176
- if (!raw2) {
4177
- return /* @__PURE__ */ new Set();
4178
- }
4179
- try {
4180
- const arr = JSON.parse(raw2);
4181
- if (!Array.isArray(arr)) {
4182
- return /* @__PURE__ */ new Set();
4183
- }
4184
- const out = /* @__PURE__ */ new Set();
4185
- for (const v of arr) {
4186
- if (typeof v === "string") {
4187
- out.add(v.toLowerCase());
4188
- }
4189
- }
4190
- return out;
4191
- } catch {
4192
- return /* @__PURE__ */ new Set();
4452
+ __name(readSendRawAddressList, "readSendRawAddressList");
4453
+ function parseSendRawResult(value) {
4454
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
4455
+ return null;
4193
4456
  }
4194
- }
4195
- __name(readAllowlist2, "readAllowlist");
4196
- async function getBurstLimit2(kv2, projectId) {
4197
- const raw2 = await kv2.get(`email-burst-limit:${projectId}`, { cacheTtl: 60 });
4198
- if (raw2) {
4199
- const n = parseInt(raw2, 10);
4200
- if (Number.isFinite(n) && n > 0) {
4201
- return n;
4202
- }
4457
+ const result = value;
4458
+ const delivered = readSendRawAddressList(result.delivered);
4459
+ const permanentBounces = readSendRawAddressList(result.permanent_bounces);
4460
+ const queued = readSendRawAddressList(result.queued);
4461
+ const suppressedRecipients = readSendRawAddressList(result.suppressed_recipients);
4462
+ if (!delivered || !permanentBounces || !queued || !suppressedRecipients || typeof result.message_id !== "string" || result.message_id === "") {
4463
+ return null;
4203
4464
  }
4204
- return 10;
4465
+ return {
4466
+ delivered,
4467
+ messageId: result.message_id,
4468
+ permanentBounces,
4469
+ queued,
4470
+ suppressedRecipients
4471
+ };
4205
4472
  }
4206
- __name(getBurstLimit2, "getBurstLimit");
4207
- async function getMonthlyLimit2(kv2, projectId) {
4208
- const raw2 = await kv2.get(`email-limit:${projectId}`, { cacheTtl: 60 });
4209
- if (raw2) {
4210
- const n = parseInt(raw2, 10);
4211
- if (Number.isFinite(n) && n > 0) {
4212
- return n;
4213
- }
4214
- }
4215
- return 200;
4473
+ __name(parseSendRawResult, "parseSendRawResult");
4474
+ function classifySendRawRecipient(result, recipient) {
4475
+ const target = recipient.toLowerCase();
4476
+ const classifications = [
4477
+ result.delivered.has(target),
4478
+ result.queued.has(target),
4479
+ result.permanentBounces.has(target),
4480
+ result.suppressedRecipients.has(target)
4481
+ ];
4482
+ if (classifications.filter(Boolean).length !== 1) {
4483
+ return "outcome_unknown";
4484
+ }
4485
+ if (classifications[0] || classifications[1]) {
4486
+ return "provider_accepted";
4487
+ }
4488
+ return classifications[2] ? "permanent_bounce" : "suppressed";
4216
4489
  }
4217
- __name(getMonthlyLimit2, "getMonthlyLimit");
4218
- async function routeInbound(message, env, ctx) {
4219
- const label = readSenderDomain(env) ?? "void email";
4220
- const to = String(message.to ?? "").toLowerCase();
4221
- const local = to.split("@")[0] ?? "";
4222
- const plus = local.indexOf("+");
4223
- const slug = plus === -1 ? local : local.slice(0, plus);
4224
- const atIdx = to.indexOf("@");
4225
- const receivedDomain = atIdx === -1 ? "" : to.slice(atIdx + 1);
4226
- if (!slug) {
4227
- message.setReject(`${label}: missing project slug in recipient`);
4228
- return;
4229
- }
4230
- if (isReservedEmailSlug(slug)) {
4231
- message.setReject(`${label}: "${sanitize(slug, 64)}" is a reserved platform address`);
4232
- return;
4490
+ __name(classifySendRawRecipient, "classifySendRawRecipient");
4491
+ async function sendViaCustomDomain(context, projectId, from, fromDomain, senderDomain, recipients, raw2, requestId, operationKey, sendDeadline) {
4492
+ if (!/^[\x21-\x7e]+$/.test(from)) {
4493
+ return context.json(
4494
+ {
4495
+ ok: false,
4496
+ error: { code: "INVALID_FROM", message: "from address must be printable ASCII" }
4497
+ },
4498
+ 403
4499
+ );
4233
4500
  }
4234
- const routingJson = await env.ROUTING.get(slug);
4235
- if (!routingJson) {
4236
- message.setReject(`${label}: no project found for slug "${sanitize(slug, 64)}"`);
4237
- return;
4501
+ const fromLower = from.toLowerCase();
4502
+ const mimeFrom = readMimeFromAddrSpec(raw2);
4503
+ if (!mimeFrom.ok || mimeFrom.addrSpec.toLowerCase() !== fromLower) {
4504
+ const detail = mimeFrom.ok ? "the From header address does not match the envelope sender" : MIME_FROM_REFUSALS[mimeFrom.reason];
4505
+ return context.json(
4506
+ {
4507
+ ok: false,
4508
+ error: {
4509
+ code: "INVALID_FROM",
4510
+ message: `mime From must be the envelope sender ${from}: ${detail}`
4511
+ }
4512
+ },
4513
+ 403
4514
+ );
4238
4515
  }
4239
- let parsedRouting;
4516
+ let mimeMessage;
4240
4517
  try {
4241
- parsedRouting = JSON.parse(routingJson);
4518
+ mimeMessage = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(raw2);
4242
4519
  } catch {
4243
- message.setReject(`${label}: routing data corrupt`);
4244
- return;
4245
- }
4246
- if (typeof parsedRouting !== "object" || parsedRouting === null) {
4247
- message.setReject(`${label}: routing data corrupt`);
4248
- return;
4249
- }
4250
- const routing = parsedRouting;
4251
- if (routing.suspended) {
4252
- const reason = typeof routing.suspended === "string" ? routing.suspended : "suspended";
4253
- message.setReject(
4254
- `${label}: project "${sanitize(slug, 64)}" is suspended (${sanitize(reason, 64)})`
4520
+ return context.json(
4521
+ { ok: false, error: { code: "MIME_ERROR", message: "mime must be valid UTF-8" } },
4522
+ 400
4255
4523
  );
4256
- return;
4257
4524
  }
4258
- const workerName = routing.workerName;
4259
- if (!workerName) {
4260
- message.setReject(`${label}: project "${sanitize(slug, 64)}" has no worker (static deploy)`);
4261
- return;
4262
- }
4263
- if (typeof workerName !== "string") {
4264
- message.setReject(`${label}: routing data corrupt`);
4265
- return;
4266
- }
4267
- if (typeof routing.projectId !== "string" || routing.projectId === "") {
4268
- message.setReject(`${label}: routing data corrupt`);
4269
- return;
4270
- }
4271
- if (await env.ROUTING.get(`email-suspended:${routing.projectId}`)) {
4272
- message.setReject(`${label}: project "${sanitize(slug, 64)}" inbound is suspended`);
4273
- return;
4274
- }
4275
- let stub;
4276
- try {
4277
- stub = routing.limits ? env.DISPATCH.get(workerName, {}, { limits: routing.limits }) : env.DISPATCH.get(workerName);
4278
- } catch (e) {
4279
- console.error(`[email-router] DISPATCH.get(${sanitize(workerName, 320)}) failed:`, e);
4280
- message.setReject(`${label}: project worker unavailable`);
4281
- return;
4282
- }
4283
- if (typeof message.rawSize === "number" && message.rawSize > MAX_INBOUND_BYTES) {
4284
- message.setReject(`${label}: message too large (max ${MAX_INBOUND_BYTES} bytes)`);
4285
- return;
4286
- }
4287
- let envelope;
4525
+ let admission;
4288
4526
  try {
4289
- envelope = await buildEnvelope(message);
4290
- } catch (e) {
4291
- if (e instanceof InboundTooLargeError) {
4292
- message.setReject(`${label}: message too large (max ${MAX_INBOUND_BYTES} bytes)`);
4293
- return;
4294
- }
4295
- if (e instanceof InboundTooManyHeadersError) {
4296
- message.setReject(`${label}: too many headers (max ${MAX_INBOUND_HEADERS})`);
4297
- return;
4298
- }
4299
- console.error(`[email-router] failed to read message body for ${sanitize(slug, 64)}:`, e);
4300
- message.setReject(`${label}: could not read inbound message body`);
4301
- return;
4302
- }
4303
- let gate;
4304
- try {
4305
- gate = await loadOutboundGate(env, routing.projectId);
4306
- } catch (e) {
4307
- console.error(
4308
- `[email-router] failed to load outbound gate for ${sanitize(routing.projectId ?? "", 64)}:`,
4309
- e
4527
+ admission = await admitEmail(
4528
+ context.env,
4529
+ {
4530
+ projectId,
4531
+ kind: "direct",
4532
+ key: operationKey,
4533
+ digest: await emailPayloadDigest("direct", from, recipients, raw2),
4534
+ from,
4535
+ recipients,
4536
+ transport: "send_raw"
4537
+ },
4538
+ sendDeadline.remainingMs(EMAIL_SEND_FINALIZATION_MARGIN_MS)
4310
4539
  );
4311
- gate = {
4312
- projectId: routing.projectId,
4313
- allowlist: /* @__PURE__ */ new Set(),
4314
- burstKey: "",
4315
- burstLimit: 0,
4316
- burstCurrent: 0,
4317
- monthKey: "",
4318
- monthLimit: 0,
4319
- monthCurrent: 0,
4320
- quotaExhausted: true
4321
- };
4322
- }
4323
- let actions;
4324
- try {
4325
- const result = await stub.email(envelope);
4326
- if (result === void 0 || result === null) {
4327
- actions = [];
4328
- } else if (Array.isArray(result)) {
4329
- actions = result;
4330
- } else {
4331
- console.warn(
4332
- `[email-router] ${sanitize(workerName, 320)} email handler returned non-array; treating as no-op`
4333
- );
4334
- actions = [];
4335
- }
4336
- } catch (e) {
4337
- const msg = e instanceof Error ? e.message.slice(0, 320) : describeUntrusted(e, 320);
4338
- if (RPC_METHOD_MISSING.test(msg)) {
4339
- console.error(
4340
- `[email-router] ${sanitize(workerName, 320)} has no .email() (needs redeploy):`,
4341
- sanitize(msg, 320)
4342
- );
4343
- message.setReject(`${label}: project has no email handler`);
4344
- ctx.waitUntil(
4345
- logRecv(env, routing.projectId, message, [{ type: "reject", reason: "no-handler" }])
4346
- );
4347
- return;
4348
- }
4349
- console.error(`[email-router] dispatch to ${sanitize(workerName, 320)} failed:`, msg);
4350
- message.setReject(`${label}: project handler error`);
4351
- ctx.waitUntil(
4352
- logRecv(env, routing.projectId, message, [{ type: "reject", reason: "dispatch-error" }])
4540
+ } catch (error) {
4541
+ const conflict = error instanceof Error && error.message === "IDEMPOTENCY_CONFLICT";
4542
+ return context.json(
4543
+ {
4544
+ ok: false,
4545
+ error: {
4546
+ code: conflict ? "IDEMPOTENCY_CONFLICT" : "UPSTREAM_ERROR",
4547
+ message: conflict ? "idempotency key has different content" : "email admission unavailable"
4548
+ }
4549
+ },
4550
+ conflict ? 409 : 503
4353
4551
  );
4354
- return;
4355
4552
  }
4356
- await enactActions(env, message, actions, gate, { slug, domain: receivedDomain });
4357
- ctx.waitUntil(logRecv(env, routing.projectId, message, actions));
4358
- }
4359
- __name(routeInbound, "routeInbound");
4360
- async function loadOutboundGate(env, projectId) {
4361
- if (!projectId) {
4362
- return {
4363
- projectId: void 0,
4364
- allowlist: /* @__PURE__ */ new Set(),
4365
- burstKey: "",
4366
- burstLimit: 0,
4367
- burstCurrent: 0,
4368
- monthKey: "",
4369
- monthLimit: 0,
4370
- monthCurrent: 0,
4371
- quotaExhausted: true
4372
- };
4553
+ const deliveries = admission.recipients.filter((r) => r.state !== "reserved").map((r) => ({
4554
+ recipient: r.recipient,
4555
+ ok: r.state === "provider_accepted",
4556
+ state: r.state,
4557
+ messageId: r.providerReference ?? void 0,
4558
+ error: r.state === "provider_accepted" ? void 0 : {
4559
+ code: r.errorCode ?? (r.state === "outcome_unknown" || r.state === "attempt_started" ? "OUTCOME_UNKNOWN" : r.state === "cancelled" ? "UPSTREAM_ERROR" : "UNVERIFIED_DESTINATION"),
4560
+ message: r.state === "outcome_unknown" || r.state === "attempt_started" ? "provider outcome unknown; inspect email logs before retrying" : r.state === "cancelled" ? "email attempt was cancelled before provider submission" : "recipient was not admitted"
4561
+ }
4562
+ }));
4563
+ if (admission.replay) {
4564
+ for (const row of admission.recipients.filter((r) => r.state === "reserved")) {
4565
+ deliveries.push({
4566
+ recipient: row.recipient,
4567
+ ok: false,
4568
+ state: "reserved",
4569
+ error: {
4570
+ code: "OUTCOME_UNKNOWN",
4571
+ message: "email action remains reserved; inspect email logs before retrying"
4572
+ }
4573
+ });
4574
+ }
4575
+ return emailActionResponse(context, admission.operationId, deliveries);
4576
+ }
4577
+ recipients = admission.recipients.filter((r) => r.state === "reserved").map((r) => r.recipient);
4578
+ if (recipients.length === 0) {
4579
+ return emailActionResponse(context, admission.operationId, deliveries);
4373
4580
  }
4374
- const allowlist = await readAllowlist2(env.ROUTING, projectId);
4375
- const now = /* @__PURE__ */ new Date();
4376
- const burstKey = `email-burst:${projectId}:${minuteBucket2(now)}`;
4377
- const monthKey = `email-sent:${projectId}:${monthBucket2(now)}`;
4378
- const [burstLimit, monthLimit, burstRaw, monthRaw] = await Promise.all([
4379
- getBurstLimit2(env.ROUTING, projectId),
4380
- getMonthlyLimit2(env.ROUTING, projectId),
4381
- env.ROUTING.get(burstKey),
4382
- env.ROUTING.get(monthKey)
4383
- ]);
4384
- return {
4581
+ const lease = await leaseEmailDomainCredential(
4582
+ context.env,
4583
+ fromDomain,
4385
4584
  projectId,
4386
- allowlist,
4387
- burstKey,
4388
- burstLimit,
4389
- burstCurrent: readCounter2(burstRaw),
4390
- monthKey,
4391
- monthLimit,
4392
- monthCurrent: readCounter2(monthRaw),
4393
- quotaExhausted: false
4394
- };
4395
- }
4396
- __name(loadOutboundGate, "loadOutboundGate");
4397
- async function commitGateCounters(env, gate) {
4398
- if (!gate.projectId) {
4399
- return;
4400
- }
4401
- const now = /* @__PURE__ */ new Date();
4402
- await Promise.all([
4403
- env.ROUTING.put(gate.monthKey, String(gate.monthCurrent), {
4404
- expirationTtl: monthTtlSeconds2(now)
4405
- }).catch((e) => {
4406
- console.error(
4407
- `[email-router] monthly counter put failed for ${sanitize(gate.projectId ?? "", 64)}:`,
4408
- e
4409
- );
4410
- }),
4411
- env.ROUTING.put(gate.burstKey, String(gate.burstCurrent), {
4412
- expirationTtl: 5 * 60
4413
- }).catch((e) => {
4414
- console.error(
4415
- `[email-router] burst counter put failed for ${sanitize(gate.projectId ?? "", 64)}:`,
4416
- e
4417
- );
4418
- })
4419
- ]);
4420
- }
4421
- __name(commitGateCounters, "commitGateCounters");
4422
- async function logRecv(env, projectId, message, actions) {
4423
- if (!projectId) {
4424
- console.error(
4425
- `[email-router] cannot log inbound delivery: routing entry has no projectId (to=${sanitize(String(message.to ?? ""), 320)})`
4585
+ sendDeadline.remainingMs(EMAIL_SEND_FINALIZATION_MARGIN_MS)
4586
+ );
4587
+ if (!lease) {
4588
+ await finishEmailAttempt(
4589
+ context.env,
4590
+ admission.operationId,
4591
+ recipients,
4592
+ "cancelled",
4593
+ {},
4594
+ sendDeadline.remainingMs()
4595
+ ).catch(() => {
4596
+ });
4597
+ return context.json(
4598
+ {
4599
+ ok: false,
4600
+ operationId: admission.operationId,
4601
+ error: { code: "UPSTREAM_ERROR", message: "could not lease the domain credential" }
4602
+ },
4603
+ 502
4426
4604
  );
4427
- return;
4428
4605
  }
4429
- const status = actions.some((a) => a && typeof a === "object" && a.type === "reject") ? "rejected" : actions.length === 0 ? "accepted" : "handled";
4430
- try {
4431
- await recordInboundDelivery(env, projectId, {
4432
- from: String(message.from ?? ""),
4433
- to: String(message.to ?? ""),
4434
- // sanitize() in email.ts handles control-char stripping before logging;
4435
- // don't pre-truncate here so the full subject is available if logging
4436
- // ever moves to structured storage.
4437
- subject: message.headers.get("Subject") ?? "",
4438
- byteSize: message.rawSize ?? 0,
4439
- status
4606
+ if (admission.accountId !== lease.accountId) {
4607
+ await finishEmailAttempt(
4608
+ context.env,
4609
+ admission.operationId,
4610
+ recipients,
4611
+ "cancelled",
4612
+ {},
4613
+ sendDeadline.remainingMs()
4614
+ ).catch(() => {
4440
4615
  });
4441
- } catch (e) {
4442
- console.error("[email-router] log-recv failed:", e);
4616
+ return context.json(
4617
+ {
4618
+ ok: false,
4619
+ operationId: admission.operationId,
4620
+ error: { code: "UPSTREAM_ERROR", message: "email domain changed during admission" }
4621
+ },
4622
+ 503
4623
+ );
4443
4624
  }
4444
- }
4445
- __name(logRecv, "logRecv");
4446
- async function buildEnvelope(message) {
4447
- const entries = [];
4448
- for (const entry of message.headers) {
4449
- if (entries.length >= MAX_INBOUND_HEADERS) {
4450
- throw new InboundTooManyHeadersError();
4625
+ try {
4626
+ await startEmailAttempt(
4627
+ context.env,
4628
+ admission.operationId,
4629
+ recipients,
4630
+ sendDeadline.remainingMs(EMAIL_SEND_FINALIZATION_MARGIN_MS)
4631
+ );
4632
+ } catch {
4633
+ const cancelled = await cancelUnstartedRecipients(
4634
+ context.env,
4635
+ admission.operationId,
4636
+ recipients,
4637
+ sendDeadline.remainingMs()
4638
+ );
4639
+ for (const recipient of recipients) {
4640
+ deliveries.push({
4641
+ recipient,
4642
+ ok: false,
4643
+ state: cancelled ? "cancelled" : "outcome_unknown",
4644
+ error: cancelled ? {
4645
+ code: "UPSTREAM_ERROR",
4646
+ message: "email attempt did not start; reservation cancelled"
4647
+ } : { code: "OUTCOME_UNKNOWN", message: "could not confirm email attempt status" }
4648
+ });
4451
4649
  }
4452
- entries.push(entry);
4650
+ return emailActionResponse(context, admission.operationId, deliveries);
4453
4651
  }
4454
- const raw2 = await readStreamToBytes(message.raw);
4455
- const headers = Object.fromEntries(entries);
4456
- return {
4457
- from: String(message.from ?? ""),
4458
- to: String(message.to ?? ""),
4459
- headers,
4460
- raw: raw2,
4461
- rawSize: message.rawSize ?? raw2.byteLength
4462
- };
4463
- }
4464
- __name(buildEnvelope, "buildEnvelope");
4465
- async function readStreamToBytes(stream) {
4466
- const reader = stream.getReader();
4467
- const chunks = [];
4468
- let total = 0;
4469
- for (; ; ) {
4470
- const { value, done } = await reader.read();
4471
- if (done) {
4472
- break;
4473
- }
4474
- total += value.byteLength;
4475
- if (total > MAX_INBOUND_BYTES) {
4476
- try {
4477
- await reader.cancel();
4478
- } catch {
4652
+ let failure = null;
4653
+ let failureState = "failed";
4654
+ let sendRawResult = null;
4655
+ let providerResponse = null;
4656
+ try {
4657
+ providerResponse = await requestSendRaw(
4658
+ {
4659
+ accountId: lease.accountId,
4660
+ bearer: leaseBearer(lease),
4661
+ from,
4662
+ recipients,
4663
+ mimeMessage
4664
+ },
4665
+ sendDeadline.remainingMs(EMAIL_SEND_FINALIZATION_MARGIN_MS)
4666
+ );
4667
+ } catch {
4668
+ failure = { code: "UPSTREAM_ERROR", message: "email sending request failed" };
4669
+ failureState = "outcome_unknown";
4670
+ }
4671
+ if (providerResponse) {
4672
+ if (!providerResponse.ok) {
4673
+ failure = {
4674
+ code: providerResponse.status === 429 ? "QUOTA_EXCEEDED" : "UPSTREAM_ERROR",
4675
+ message: `email sending API returned ${providerResponse.status}`
4676
+ };
4677
+ failureState = providerResponse.status >= 500 || providerResponse.status === 408 ? "outcome_unknown" : "failed";
4678
+ } else {
4679
+ const data = providerResponse.data;
4680
+ const success = typeof data === "object" && data !== null ? data.success : void 0;
4681
+ if (success === false) {
4682
+ failure = { code: "UPSTREAM_ERROR", message: "email sending API refused the message" };
4683
+ } else if (success !== true) {
4684
+ failure = { code: "OUTCOME_UNKNOWN", message: "email sending API response invalid" };
4685
+ failureState = "outcome_unknown";
4686
+ } else {
4687
+ sendRawResult = parseSendRawResult(data.result);
4688
+ if (!sendRawResult) {
4689
+ failure = { code: "OUTCOME_UNKNOWN", message: "email sending API response invalid" };
4690
+ failureState = "outcome_unknown";
4691
+ }
4479
4692
  }
4480
- throw new InboundTooLargeError(total);
4481
4693
  }
4482
- chunks.push(value);
4483
- }
4484
- const out = new Uint8Array(total);
4485
- let offset = 0;
4486
- for (const c of chunks) {
4487
- out.set(c, offset);
4488
- offset += c.byteLength;
4489
4694
  }
4490
- return out;
4491
- }
4492
- __name(readStreamToBytes, "readStreamToBytes");
4493
- function validateActions(actions, replySender) {
4494
- if (actions.length > MAX_ACTIONS) {
4495
- console.error(
4496
- `[email-router] action list dropped: ${actions.length} actions returned (max ${MAX_ACTIONS})`
4497
- );
4498
- return null;
4499
- }
4500
- const out = [];
4501
- let replyBytesUsed = 0;
4502
- for (const action of actions) {
4503
- if (!action || typeof action !== "object") {
4504
- console.error(`[email-router] dropping non-object action: ${describeUntrusted(action, 320)}`);
4695
+ for (const r of recipients) {
4696
+ if (failure) {
4697
+ deliveries.push({ recipient: r, ok: false, state: failureState, error: failure });
4505
4698
  continue;
4506
4699
  }
4507
- if (action.type === "reject") {
4508
- const raw2 = typeof action.reason === "string" ? action.reason : "";
4509
- const reason = raw2.length > 0 ? sanitize(raw2, MAX_REJECT_REASON_LENGTH) : "rejected by handler";
4510
- out.push({ type: "reject", reason });
4511
- return out;
4512
- }
4513
- if (action.type === "forward") {
4514
- if (!isValidEmailAddress(action.to)) {
4515
- console.error(
4516
- `[email-router] forward dropped: invalid "to" address ${logAddress(action.to)}`
4517
- );
4518
- continue;
4519
- }
4520
- let headers;
4521
- if (action.headers) {
4522
- if (!forwardHeadersWithinBounds(action.headers)) {
4523
- return null;
4700
+ const outcome = classifySendRawRecipient(sendRawResult, r);
4701
+ if (outcome === "permanent_bounce") {
4702
+ deliveries.push({
4703
+ recipient: r,
4704
+ ok: false,
4705
+ state: "failed",
4706
+ error: {
4707
+ code: "UNVERIFIED_DESTINATION",
4708
+ message: `recipient '${r}' permanently bounced`
4524
4709
  }
4525
- try {
4526
- headers = new Headers(action.headers);
4527
- } catch (e) {
4528
- console.error(
4529
- `[email-router] action list dropped: forward headers invalid:`,
4530
- e instanceof Error ? sanitize(e.message, 320) : sanitize(String(e), 320)
4531
- );
4532
- return null;
4710
+ });
4711
+ } else if (outcome === "suppressed") {
4712
+ deliveries.push({
4713
+ recipient: r,
4714
+ ok: false,
4715
+ state: "failed",
4716
+ error: {
4717
+ code: "UNVERIFIED_DESTINATION",
4718
+ message: `recipient '${r}' is on the suppression list`
4533
4719
  }
4534
- }
4535
- out.push({ type: "forward", to: action.to, headers });
4536
- } else if (action.type === "reply") {
4537
- if (!isValidEmailAddress(action.from) || !isValidEmailAddress(action.to)) {
4538
- console.error(
4539
- `[email-router] reply dropped: invalid address (from=${logAddress(action.from)} to=${logAddress(action.to)})`
4540
- );
4541
- continue;
4542
- }
4543
- if (!isPermittedReplySender(action.from, replySender)) {
4544
- console.error(
4545
- `[email-router] reply dropped: sender not owned by project "${sanitize(replySender.slug, 64)}" (from=${logAddress(action.from)})`
4546
- );
4547
- continue;
4548
- }
4549
- if (!(action.raw instanceof Uint8Array)) {
4550
- console.error(
4551
- `[email-router] reply dropped: raw is not a Uint8Array (from=${logAddress(action.from)})`
4552
- );
4553
- continue;
4554
- }
4555
- const replyBytes = action.raw.byteLength;
4556
- if (replyBytes > MAX_REPLY_BYTES) {
4557
- console.error(
4558
- `[email-router] reply dropped: raw is ${replyBytes} bytes (max ${MAX_REPLY_BYTES}) (from=${logAddress(action.from)})`
4559
- );
4560
- continue;
4561
- }
4562
- if (replyBytesUsed + replyBytes > MAX_REPLY_BYTES_TOTAL) {
4563
- console.error(
4564
- `[email-router] reply dropped: cumulative reply bytes ${replyBytesUsed + replyBytes} exceed this delivery's budget of ${MAX_REPLY_BYTES_TOTAL} (from=${logAddress(action.from)})`
4565
- );
4566
- continue;
4567
- }
4568
- const mimeFrom = readMimeFromAddrSpec(action.raw);
4569
- if (!mimeFrom.ok || mimeFrom.addrSpec.toLowerCase() !== action.from.toLowerCase()) {
4570
- console.error(
4571
- `[email-router] reply dropped: MIME From is not the envelope sender (from=${logAddress(action.from)}, ${mimeFrom.ok ? "header names another address" : mimeFrom.reason})`
4572
- );
4573
- continue;
4574
- }
4575
- replyBytesUsed += replyBytes;
4576
- out.push({ type: "reply", from: action.from, to: action.to, raw: action.raw });
4720
+ });
4721
+ } else if (outcome === "outcome_unknown") {
4722
+ deliveries.push({
4723
+ recipient: r,
4724
+ ok: false,
4725
+ state: "outcome_unknown",
4726
+ error: {
4727
+ code: "OUTCOME_UNKNOWN",
4728
+ message: "email sending API did not classify the recipient"
4729
+ }
4730
+ });
4577
4731
  } else {
4578
- console.warn(
4579
- `[email-router] dropping unknown action type: ${describeUntrusted(action.type, 320)}`
4580
- );
4732
+ deliveries.push({
4733
+ recipient: r,
4734
+ ok: true,
4735
+ state: "provider_accepted",
4736
+ messageId: sendRawResult.messageId
4737
+ });
4581
4738
  }
4582
4739
  }
4583
- return out;
4584
- }
4585
- __name(validateActions, "validateActions");
4586
- async function enactActions(env, message, actions, gate, replySender) {
4587
- const sanitized = validateActions(actions, replySender);
4588
- if (sanitized === null) {
4589
- return;
4590
- }
4591
- await enactSanitized(env, message, sanitized, gate);
4592
- }
4593
- __name(enactActions, "enactActions");
4594
- async function enactSanitized(env, message, actions, gate) {
4595
- let counterDirty = false;
4596
- for (const action of actions) {
4597
- if (action.type === "reject") {
4598
- message.setReject(action.reason);
4599
- if (counterDirty) {
4600
- await commitGateCounters(env, gate);
4601
- }
4602
- return;
4603
- }
4604
- if (action.type === "forward") {
4605
- if (!checkOutboundGate(gate, action.to, "forward")) {
4606
- continue;
4607
- }
4608
- try {
4609
- await message.forward(action.to, action.headers);
4610
- gate.burstCurrent += 1;
4611
- gate.monthCurrent += 1;
4612
- counterDirty = true;
4613
- } catch (e) {
4614
- console.error(
4615
- `[email-router] forward to ${sanitize(action.to, 320)} failed:`,
4616
- e instanceof Error ? sanitize(e.message, 320) : sanitize(String(e), 320)
4617
- );
4618
- }
4619
- } else if (action.type === "reply") {
4620
- if (!checkOutboundGate(gate, action.to, "reply")) {
4621
- continue;
4622
- }
4623
- try {
4624
- const stream = new ReadableStream({
4625
- start(controller) {
4626
- controller.enqueue(action.raw);
4627
- controller.close();
4628
- }
4629
- });
4630
- const reply = new EmailMessage2(action.from, action.to, stream);
4631
- await message.reply(reply);
4632
- gate.burstCurrent += 1;
4633
- gate.monthCurrent += 1;
4634
- counterDirty = true;
4635
- } catch (e) {
4636
- console.error(
4637
- `[email-router] reply ${sanitize(action.from, 320)}->${sanitize(action.to, 320)} failed:`,
4638
- e instanceof Error ? sanitize(e.message, 320) : sanitize(String(e), 320)
4639
- );
4740
+ for (const delivery of deliveries.filter(
4741
+ (d) => admission.recipients.some((r) => r.recipient === d.recipient && r.state === "reserved")
4742
+ )) {
4743
+ try {
4744
+ await finishEmailAttempt(
4745
+ context.env,
4746
+ admission.operationId,
4747
+ [delivery.recipient],
4748
+ delivery.ok ? "provider_accepted" : delivery.state === "outcome_unknown" ? "outcome_unknown" : "failed",
4749
+ {
4750
+ providerReference: delivery.messageId,
4751
+ errorCode: delivery.ok ? void 0 : delivery.state === "outcome_unknown" ? "OUTCOME_UNKNOWN" : failure?.code ?? delivery.error?.code
4752
+ },
4753
+ sendDeadline.remainingMs()
4754
+ );
4755
+ delivery.state = delivery.ok ? "provider_accepted" : delivery.state === "outcome_unknown" ? "outcome_unknown" : "failed";
4756
+ if (delivery.state === "outcome_unknown" && !delivery.ok) {
4757
+ delivery.error = {
4758
+ code: "OUTCOME_UNKNOWN",
4759
+ message: "provider outcome unknown; inspect email logs before retrying"
4760
+ };
4640
4761
  }
4762
+ } catch {
4763
+ delivery.ok = false;
4764
+ delivery.state = "outcome_unknown";
4765
+ delivery.error = {
4766
+ code: "OUTCOME_UNKNOWN",
4767
+ message: "provider outcome could not be recorded"
4768
+ };
4641
4769
  }
4642
4770
  }
4643
- if (counterDirty) {
4644
- await commitGateCounters(env, gate);
4771
+ logSend(projectId, requestId, deliveries);
4772
+ if (deliveries.some((d) => !d.ok)) {
4773
+ return context.json({ ok: false, operationId: admission.operationId, deliveries }, 200);
4645
4774
  }
4775
+ return context.json({
4776
+ ok: true,
4777
+ operationId: admission.operationId,
4778
+ ids: deliveries.map((d) => ({
4779
+ recipient: d.recipient,
4780
+ messageId: d.messageId,
4781
+ state: "provider_accepted"
4782
+ }))
4783
+ });
4646
4784
  }
4647
- __name(enactSanitized, "enactSanitized");
4648
- function checkOutboundGate(gate, target, kind) {
4649
- if (gate.quotaExhausted) {
4650
- console.error(
4651
- `[email-router] ${kind} dropped: project quota exhausted (target=${sanitize(target, 320)})`
4785
+ __name(sendViaCustomDomain, "sendViaCustomDomain");
4786
+ async function cancelUnstartedRecipients(env, operationId, recipients, deadlineMs) {
4787
+ try {
4788
+ const result = await finishEmailAttempt(
4789
+ env,
4790
+ operationId,
4791
+ recipients,
4792
+ "cancelled",
4793
+ {},
4794
+ deadlineMs
4795
+ );
4796
+ return recipients.every(
4797
+ (recipient) => result.recipients.some((row) => row.recipient === recipient && row.state === "cancelled")
4652
4798
  );
4799
+ } catch {
4653
4800
  return false;
4654
4801
  }
4655
- if (!gate.projectId) {
4656
- console.error(
4657
- `[email-router] ${kind} dropped: no projectId on routing entry (target=${sanitize(target, 320)})`
4802
+ }
4803
+ __name(cancelUnstartedRecipients, "cancelUnstartedRecipients");
4804
+ function emailActionResponse(context, operationId, deliveries) {
4805
+ if (deliveries.length === 0) {
4806
+ return context.json(
4807
+ {
4808
+ ok: false,
4809
+ operationId,
4810
+ error: { code: "OUTCOME_UNKNOWN", message: "email action remains reserved" }
4811
+ },
4812
+ 200
4658
4813
  );
4659
- return false;
4660
4814
  }
4661
- const lowered = target.toLowerCase();
4662
- if (!gate.allowlist.has(lowered)) {
4663
- console.error(
4664
- `[email-router] ${kind} dropped: recipient not in project allowlist (target=${sanitize(target, 320)})`
4815
+ if (deliveries.some((d) => !d.ok)) {
4816
+ return context.json({ ok: false, operationId, deliveries }, 200);
4817
+ }
4818
+ return context.json(
4819
+ {
4820
+ ok: true,
4821
+ operationId,
4822
+ ids: deliveries.map((d) => ({
4823
+ recipient: d.recipient,
4824
+ messageId: d.messageId ?? "",
4825
+ state: d.state
4826
+ }))
4827
+ },
4828
+ 200
4829
+ );
4830
+ }
4831
+ __name(emailActionResponse, "emailActionResponse");
4832
+ function logSend(projectId, requestId, deliveries) {
4833
+ for (const d of deliveries) {
4834
+ const status = d.ok ? "sent" : d.error?.code ?? "unknown_error";
4835
+ const id = d.messageId ?? "";
4836
+ console.log(
4837
+ `[email-log] ${projectId} ${requestId} ${sanitizeForLog(d.recipient, 320)} ${status} ${id} @${Date.now()}`
4665
4838
  );
4666
- return false;
4667
4839
  }
4668
- if (gate.burstCurrent + 1 > gate.burstLimit) {
4669
- console.error(`[email-router] ${kind} dropped: burst limit ${gate.burstLimit}/min exceeded`);
4670
- gate.quotaExhausted = true;
4671
- return false;
4840
+ }
4841
+ __name(logSend, "logSend");
4842
+ function base64Decode(b64) {
4843
+ const binary = atob(b64);
4844
+ const out = new Uint8Array(binary.length);
4845
+ for (let i = 0; i < binary.length; i++) {
4846
+ out[i] = binary.charCodeAt(i);
4672
4847
  }
4673
- if (gate.monthCurrent + 1 > gate.monthLimit) {
4674
- console.error(`[email-router] ${kind} dropped: monthly limit ${gate.monthLimit} exceeded`);
4675
- gate.quotaExhausted = true;
4848
+ return out;
4849
+ }
4850
+ __name(base64Decode, "base64Decode");
4851
+
4852
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/human-stream.ts
4853
+ function guardHumanStream(source, validate, signal) {
4854
+ const reader = source.getReader();
4855
+ let closed = false;
4856
+ let timer;
4857
+ let controller;
4858
+ const cleanup = /* @__PURE__ */ __name(() => {
4859
+ closed = true;
4860
+ if (timer !== void 0) {
4861
+ clearTimeout(timer);
4862
+ }
4863
+ signal?.removeEventListener("abort", abort);
4864
+ }, "cleanup");
4865
+ const stop = /* @__PURE__ */ __name(() => {
4866
+ if (closed) {
4867
+ return;
4868
+ }
4869
+ cleanup();
4870
+ controller.error(new Error("Authentication expired or was revoked. Sign in again."));
4871
+ void reader.cancel().catch(() => {
4872
+ });
4873
+ }, "stop");
4874
+ const abort = /* @__PURE__ */ __name(() => stop(), "abort");
4875
+ const check = /* @__PURE__ */ __name(async () => {
4876
+ if (closed) {
4877
+ return false;
4878
+ }
4879
+ try {
4880
+ if (await validate()) {
4881
+ return !closed;
4882
+ }
4883
+ } catch {
4884
+ }
4885
+ stop();
4676
4886
  return false;
4677
- }
4678
- return true;
4887
+ }, "check");
4888
+ const schedule = /* @__PURE__ */ __name(() => {
4889
+ timer = setTimeout(async () => {
4890
+ if (await check()) {
4891
+ schedule();
4892
+ }
4893
+ }, 15e3);
4894
+ }, "schedule");
4895
+ return new ReadableStream(
4896
+ {
4897
+ start(target) {
4898
+ controller = target;
4899
+ signal?.addEventListener("abort", abort, { once: true });
4900
+ if (signal?.aborted) {
4901
+ stop();
4902
+ } else {
4903
+ schedule();
4904
+ }
4905
+ },
4906
+ async pull(target) {
4907
+ try {
4908
+ const chunk = await reader.read();
4909
+ if (closed) {
4910
+ return;
4911
+ }
4912
+ if (chunk.done) {
4913
+ cleanup();
4914
+ target.close();
4915
+ } else if (await check()) {
4916
+ target.enqueue(chunk.value);
4917
+ }
4918
+ } catch (error) {
4919
+ if (!closed) {
4920
+ cleanup();
4921
+ target.error(error);
4922
+ void reader.cancel().catch(() => {
4923
+ });
4924
+ }
4925
+ }
4926
+ },
4927
+ async cancel(reason) {
4928
+ cleanup();
4929
+ await reader.cancel(reason);
4930
+ }
4931
+ },
4932
+ { highWaterMark: 0 }
4933
+ );
4679
4934
  }
4680
- __name(checkOutboundGate, "checkOutboundGate");
4935
+ __name(guardHumanStream, "guardHumanStream");
4681
4936
 
4682
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/kv.ts
4683
- init_performance2();
4937
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/kv.ts
4684
4938
  var CF_API2 = "https://api.cloudflare.com/client/v4";
4685
4939
  var kv = new Hono2();
4686
4940
  kv.post("/get", async (context) => {
@@ -4861,13 +5115,11 @@ kv.post("/list", async (context) => {
4861
5115
  });
4862
5116
  });
4863
5117
 
4864
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/platform-lifecycle.ts
4865
- init_performance2();
5118
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/platform-lifecycle.ts
4866
5119
  var PLATFORM_DISABLED_KEY = "__void/platform/disabled/v1";
4867
5120
  var PLATFORM_DISABLED_VALUE = "disabled";
4868
5121
 
4869
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/r2.ts
4870
- init_performance2();
5122
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/r2.ts
4871
5123
  var CF_API3 = "https://api.cloudflare.com/client/v4";
4872
5124
  var r2 = new Hono2();
4873
5125
  function cfHeaders(token) {
@@ -5128,8 +5380,7 @@ r2.post("/head", async (context) => {
5128
5380
  });
5129
5381
  });
5130
5382
 
5131
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/revalidate.ts
5132
- init_performance2();
5383
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/revalidate.ts
5133
5384
  var app2 = new Hono2();
5134
5385
  app2.post("/", async (context) => {
5135
5386
  const projectId = context.get("projectId");
@@ -5151,7 +5402,7 @@ app2.post("/", async (context) => {
5151
5402
  });
5152
5403
  });
5153
5404
 
5154
- // ../../../../../../../Users/cnakazawa/Projects/void/platform/packages/proxy/src/index.ts
5405
+ // ../../home/runner/work/void/void/platform/packages/proxy/src/index.ts
5155
5406
  var app3 = new Hono2();
5156
5407
  app3.use("/*", async (context, next) => {
5157
5408
  const healthProbeToken = context.env.PLATFORM_HEALTH_PROBE_TOKEN;
@@ -5189,14 +5440,58 @@ app3.all("/*", async (context, next) => {
5189
5440
  }
5190
5441
  await next();
5191
5442
  });
5192
- var PLATFORM_INTERNAL_EMAIL_SENTINEL = "void-platform-internal-email-v1";
5193
- var MAX_PLATFORM_MIME_BASE64_LENGTH = Math.ceil(10 * 1024 * 1024 * 4 / 3) + 4;
5194
- var PLATFORM_TOKEN_MAX_SKEW_SECONDS = 5 * 60;
5195
- var PLATFORM_TOKEN_LENGTH = 43;
5443
+ async function hmacSign4(data, secret) {
5444
+ const key = await crypto.subtle.importKey(
5445
+ "raw",
5446
+ new TextEncoder().encode(secret),
5447
+ { name: "HMAC", hash: "SHA-256" },
5448
+ false,
5449
+ ["sign"]
5450
+ );
5451
+ const bytes = new Uint8Array(
5452
+ await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(data))
5453
+ );
5454
+ let binary = "";
5455
+ for (const byte of bytes) {
5456
+ binary += String.fromCharCode(byte);
5457
+ }
5458
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
5459
+ }
5460
+ __name(hmacSign4, "hmacSign");
5461
+ app3.post("/email/verify-receipt", async (context) => {
5462
+ let body;
5463
+ try {
5464
+ body = await readEmailRequestJson(context.req.raw);
5465
+ } catch {
5466
+ return context.json({ valid: false }, 400);
5467
+ }
5468
+ if (!body || typeof body !== "object") {
5469
+ return context.json({ valid: false }, 400);
5470
+ }
5471
+ const input = body;
5472
+ if (typeof input.projectId !== "string" || input.projectId.length > 128 || typeof input.deploymentId !== "string" || input.deploymentId.length > 128 || typeof input.recipient !== "string" || input.recipient.length > 320 || typeof input.receipt !== "string" || input.receipt.length > 1536) {
5473
+ return context.json({ valid: false }, 400);
5474
+ }
5475
+ const projectToken = await hmacSign4(input.projectId, context.env.JWT_SECRET);
5476
+ if (context.req.header("x-void-internal") !== projectToken) {
5477
+ return context.json({ valid: false }, 403);
5478
+ }
5479
+ if (!context.env.EMAIL_SIGNING_SECRET) {
5480
+ return context.json({ valid: false }, 503);
5481
+ }
5482
+ const claims = await verifyEmailReceipt(input.receipt, context.env.EMAIL_SIGNING_SECRET, {
5483
+ projectId: input.projectId,
5484
+ deploymentId: input.deploymentId,
5485
+ recipient: input.recipient
5486
+ });
5487
+ return context.json({ valid: claims !== null, receiptId: claims?.receiptId });
5488
+ });
5489
+ app3.post("/email/relay", (context) => context.json({ error: "not found" }, 404));
5196
5490
  app3.post("/*", async (context, next) => {
5491
+ const path = new URL(context.req.url).pathname;
5197
5492
  let body;
5198
5493
  try {
5199
- const json = await context.req.json();
5494
+ const json = path === "/email" || path.startsWith("/email/") ? await readEmailRequestJson(context.req.raw, 8 * 1024 * 1024) : await context.req.json();
5200
5495
  if (!isProxyBody(json)) {
5201
5496
  return context.json({ error: "invalid JSON body" }, 400);
5202
5497
  }
@@ -5205,40 +5500,7 @@ app3.post("/*", async (context, next) => {
5205
5500
  return context.json({ error: "invalid JSON body" }, 400);
5206
5501
  }
5207
5502
  context.set("body", body);
5208
- const path = new URL(context.req.url).pathname;
5209
- if (path.startsWith("/email/internal/")) {
5210
- const platformToken = asString(body.platformToken);
5211
- const platformTimestamp = asNumber(body.platformTimestamp);
5212
- if (!platformToken || platformTimestamp === null) {
5213
- return context.json({ error: "platform token required" }, 403);
5214
- }
5215
- if (platformToken.length !== PLATFORM_TOKEN_LENGTH) {
5216
- return context.json({ error: "invalid platform token" }, 403);
5217
- }
5218
- const nowSec = Math.floor(Date.now() / 1e3);
5219
- if (Math.abs(nowSec - platformTimestamp) > PLATFORM_TOKEN_MAX_SKEW_SECONDS) {
5220
- return context.json({ error: "invalid platform token" }, 403);
5221
- }
5222
- const bindingFrom = asString(body.from);
5223
- const bindingTo = asString(body.to);
5224
- const bindingMime = asString(body.mimeBase64);
5225
- if (!bindingFrom || !bindingTo || !bindingMime) {
5226
- return context.json({ error: "invalid platform token" }, 403);
5227
- }
5228
- if (bindingMime.length > MAX_PLATFORM_MIME_BASE64_LENGTH) {
5229
- return context.json({ error: "invalid platform token" }, 403);
5230
- }
5231
- if (bindingFrom.length > MAX_SENDER_LENGTH || bindingTo.length > MAX_RECIPIENT_LENGTH) {
5232
- return context.json({ error: "invalid platform token" }, 403);
5233
- }
5234
- const contentHash = await sha256Hex(`${bindingFrom}|${bindingTo}|${bindingMime}`);
5235
- const signed = `${PLATFORM_INTERNAL_EMAIL_SENTINEL}.${platformTimestamp}.${contentHash}`;
5236
- if (!await verifyHmac(signed, platformToken, context.env.JWT_SECRET)) {
5237
- return context.json({ error: "invalid platform token" }, 403);
5238
- }
5239
- await next();
5240
- return;
5241
- }
5503
+ let revalidateHuman;
5242
5504
  const authHeader = context.req.header("authorization");
5243
5505
  if (authHeader?.startsWith("Bearer ")) {
5244
5506
  const token = authHeader.slice(7);
@@ -5254,22 +5516,36 @@ app3.post("/*", async (context, next) => {
5254
5516
  if (!payload.project || payload.project !== projectId) {
5255
5517
  return context.json({ error: "forbidden" }, 403);
5256
5518
  }
5257
- if (payload.build && !await verifyPrerenderBuild(
5519
+ const active = await verifyPrerenderAuthority(
5258
5520
  context.env.API,
5259
5521
  context.env.JWT_SECRET,
5260
5522
  projectId,
5261
- payload.build
5262
- )) {
5263
- return context.json({ error: "prerender token build is no longer active" }, 403);
5523
+ payload.sub,
5524
+ {
5525
+ ...payload.build ? { buildId: payload.build } : {},
5526
+ ...payload.credential ? { credentialId: payload.credential } : {}
5527
+ }
5528
+ );
5529
+ if (!active) {
5530
+ return context.json(
5531
+ {
5532
+ error: payload.credential ? "prerender token credential is no longer active" : payload.build ? "prerender token build is no longer active" : "prerender token authority is no longer active"
5533
+ },
5534
+ 403
5535
+ );
5264
5536
  }
5265
5537
  } else {
5266
- const owns = await verifyProjectOwner(
5538
+ const checkOwner = /* @__PURE__ */ __name(() => verifyProjectDeployAccess(
5267
5539
  context.env.API,
5268
5540
  context.env.JWT_SECRET,
5269
5541
  projectId,
5270
- payload.sub
5271
- );
5272
- if (!owns) {
5542
+ payload.sub,
5543
+ token,
5544
+ context.req.header("Cf-Access-Jwt-Assertion"),
5545
+ new URL(context.req.url).origin
5546
+ ), "checkOwner");
5547
+ revalidateHuman = /* @__PURE__ */ __name(async () => payload.exp > Date.now() / 1e3 && await checkOwner(), "revalidateHuman");
5548
+ if (!await revalidateHuman()) {
5273
5549
  return context.json({ error: "forbidden" }, 403);
5274
5550
  }
5275
5551
  }
@@ -5286,6 +5562,12 @@ app3.post("/*", async (context, next) => {
5286
5562
  context.set("projectId", projectId);
5287
5563
  }
5288
5564
  await next();
5565
+ if (revalidateHuman && context.res.body && (context.res.headers.get("content-type")?.includes("text/event-stream") || path.startsWith("/ai/") || path === "/r2/get")) {
5566
+ context.res = new Response(
5567
+ guardHumanStream(context.res.body, revalidateHuman, context.req.raw.signal),
5568
+ context.res
5569
+ );
5570
+ }
5289
5571
  });
5290
5572
  app3.post("/ai/*", async (context, next) => {
5291
5573
  if (await context.env.ROUTING.get(`ai-block:${context.get("projectId")}`, { cacheTtl: 60 })) {
@@ -5323,50 +5605,31 @@ app3.route("/kv", kv);
5323
5605
  app3.route("/r2", r2);
5324
5606
  app3.route("/revalidate", app2);
5325
5607
  app3.route("/email", app);
5326
- var index_default = {
5327
- fetch: app3.fetch,
5328
- /**
5329
- * Inbound email entry point — bound as the catch-all CF Email Routing rule
5330
- * on the platform mail zone. See `email-router.ts`.
5331
- */
5332
- async email(message, env, ctx) {
5333
- await routeInbound(message, env, ctx);
5334
- }
5335
- };
5336
- var encoder3 = new TextEncoder();
5337
- var decoder = new TextDecoder();
5608
+ var index_default = { fetch: app3.fetch };
5609
+ var encoder6 = new TextEncoder();
5610
+ var decoder4 = new TextDecoder();
5338
5611
  var cachedVerifyKey;
5339
- var cachedKeySecret3;
5612
+ var cachedKeySecret4;
5340
5613
  async function getVerifyKey(secret) {
5341
- if (cachedVerifyKey && cachedKeySecret3 === secret) {
5614
+ if (cachedVerifyKey && cachedKeySecret4 === secret) {
5342
5615
  return cachedVerifyKey;
5343
5616
  }
5344
5617
  cachedVerifyKey = await crypto.subtle.importKey(
5345
5618
  "raw",
5346
- encoder3.encode(secret),
5619
+ encoder6.encode(secret),
5347
5620
  { name: "HMAC", hash: "SHA-256" },
5348
5621
  false,
5349
5622
  ["verify"]
5350
5623
  );
5351
- cachedKeySecret3 = secret;
5624
+ cachedKeySecret4 = secret;
5352
5625
  return cachedVerifyKey;
5353
5626
  }
5354
5627
  __name(getVerifyKey, "getVerifyKey");
5355
- async function sha256Hex(input) {
5356
- const digest = await crypto.subtle.digest("SHA-256", encoder3.encode(input));
5357
- const bytes = new Uint8Array(digest);
5358
- let out = "";
5359
- for (const b of bytes) {
5360
- out += b.toString(16).padStart(2, "0");
5361
- }
5362
- return out;
5363
- }
5364
- __name(sha256Hex, "sha256Hex");
5365
5628
  async function verifyHmac(data, signature, secret) {
5366
5629
  try {
5367
5630
  const key = await getVerifyKey(secret);
5368
5631
  const sigBytes = base64urlDecode(signature);
5369
- return crypto.subtle.verify("HMAC", key, sigBytes.buffer, encoder3.encode(data));
5632
+ return crypto.subtle.verify("HMAC", key, sigBytes.buffer, encoder6.encode(data));
5370
5633
  } catch {
5371
5634
  return false;
5372
5635
  }
@@ -5391,12 +5654,12 @@ async function verifyJwt(token, secret) {
5391
5654
  "HMAC",
5392
5655
  key,
5393
5656
  sigBytes.buffer,
5394
- encoder3.encode(`${header}.${body}`)
5657
+ encoder6.encode(`${header}.${body}`)
5395
5658
  );
5396
5659
  if (!valid) {
5397
5660
  return null;
5398
5661
  }
5399
- const payload = JSON.parse(decoder.decode(base64urlDecode(body)));
5662
+ const payload = JSON.parse(decoder4.decode(base64urlDecode(body)));
5400
5663
  const now = Math.floor(Date.now() / 1e3);
5401
5664
  if (payload.exp <= now) {
5402
5665
  return null;