@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.
- package/README.md +10 -1
- package/dist/email-protocol.d.mts +48 -0
- package/dist/email-protocol.mjs +278 -0
- package/dist/index.d.mts +152 -13
- package/dist/index.mjs +346 -20
- package/dist/installer.d.mts +4 -2
- package/dist/installer.mjs +13 -4
- package/dist/runtime/api/README.md +1 -1
- package/dist/runtime/api/index.js +67906 -52205
- package/dist/runtime/api/index.js.map +5 -5
- package/dist/runtime/dispatch/README.md +1 -1
- package/dist/runtime/dispatch/index.js +44 -26
- package/dist/runtime/dispatch/index.js.map +3 -3
- package/dist/runtime/drizzle/0039_email_domains.sql +32 -0
- package/dist/runtime/drizzle/0040_productive_vector.sql +1 -0
- package/dist/runtime/drizzle/0041_military_beyonder.sql +119 -0
- package/dist/runtime/drizzle/0042_dark_the_anarchist.sql +1 -0
- package/dist/runtime/drizzle/0043_nostalgic_meggan.sql +21 -0
- package/dist/runtime/drizzle/0044_smart_mentor.sql +15 -0
- package/dist/runtime/drizzle/0045_platform_settings.sql +5 -0
- package/dist/runtime/drizzle/0046_email_domains_platform_credential.sql +8 -0
- package/dist/runtime/drizzle/0047_email_gateway.sql +226 -0
- package/dist/runtime/drizzle/0048_project_teams.sql +133 -0
- package/dist/runtime/drizzle/compatibility.json +4 -0
- package/dist/runtime/drizzle/meta/_journal.json +70 -0
- package/dist/runtime/emailGateway/README.md +1 -0
- package/dist/runtime/emailGateway/index.js +5893 -0
- package/dist/runtime/emailGateway/index.js.map +8 -0
- package/dist/runtime/manifest.json +84 -13
- package/dist/runtime/proxy/README.md +1 -1
- package/dist/runtime/proxy/index.js +1786 -1523
- package/dist/runtime/proxy/index.js.map +5 -5
- package/dist/runtime/tail/README.md +1 -1
- package/dist/runtime/tail/index.js +1 -1
- package/dist/runtime/tail/index.js.map +1 -1
- 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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
44
|
-
var _timeOrigin
|
|
45
|
-
var
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if (
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
//
|
|
323
|
-
var
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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,
|
|
519
|
+
var tryDecode = /* @__PURE__ */ __name((str, decoder5) => {
|
|
595
520
|
try {
|
|
596
|
-
return
|
|
521
|
+
return decoder5(str);
|
|
597
522
|
} catch {
|
|
598
523
|
return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match2) => {
|
|
599
524
|
try {
|
|
600
|
-
return
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
//
|
|
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
|
|
3005
|
+
async function verifyProjectDeployAccess(api, jwtSecret, projectId, userId, jwt, accessAssertion, proxyOrigin) {
|
|
3123
3006
|
try {
|
|
3124
|
-
|
|
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({
|
|
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(
|
|
3139
|
-
|
|
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({
|
|
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(
|
|
3156
|
-
var
|
|
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
|
-
|
|
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,
|
|
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
|
-
//
|
|
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
|
-
//
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
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(
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
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
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
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
|
-
|
|
3341
|
-
|
|
3342
|
-
colon++;
|
|
3212
|
+
if (size === 0 || expectedLength !== null && size !== expectedLength) {
|
|
3213
|
+
throw new Error("invalid email request body length");
|
|
3343
3214
|
}
|
|
3344
|
-
return
|
|
3215
|
+
return JSON.parse(decoder2.decode(bytes.subarray(0, size)));
|
|
3345
3216
|
}
|
|
3346
|
-
__name(
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4088
|
+
413
|
|
3652
4089
|
);
|
|
3653
4090
|
}
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
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: "
|
|
4114
|
+
message: "email sending is not configured"
|
|
3664
4115
|
}
|
|
3665
4116
|
},
|
|
3666
4117
|
502
|
|
3667
4118
|
);
|
|
3668
4119
|
}
|
|
3669
|
-
|
|
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
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
);
|
|
3736
|
-
return context.json(
|
|
4171
|
+
let admission;
|
|
4172
|
+
try {
|
|
4173
|
+
admission = await admitEmail(
|
|
4174
|
+
context.env,
|
|
3737
4175
|
{
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
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
|
-
|
|
4184
|
+
sendDeadline.remainingMs(EMAIL_SEND_FINALIZATION_MARGIN_MS)
|
|
3745
4185
|
);
|
|
3746
|
-
}
|
|
3747
|
-
|
|
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: "
|
|
3753
|
-
message:
|
|
4192
|
+
code: conflict ? "IDEMPOTENCY_CONFLICT" : "UPSTREAM_ERROR",
|
|
4193
|
+
message: conflict ? "idempotency key has different content" : "email admission unavailable"
|
|
3754
4194
|
}
|
|
3755
4195
|
},
|
|
3756
|
-
|
|
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
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
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:
|
|
4212
|
+
recipient: row.recipient,
|
|
3774
4213
|
ok: false,
|
|
4214
|
+
state: "reserved",
|
|
3775
4215
|
error: {
|
|
3776
|
-
code: "
|
|
3777
|
-
message:
|
|
4216
|
+
code: "OUTCOME_UNKNOWN",
|
|
4217
|
+
message: "email action remains reserved; inspect email logs before retrying"
|
|
3778
4218
|
}
|
|
3779
4219
|
});
|
|
3780
4220
|
}
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
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
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
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
|
|
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
|
-
|
|
4303
|
+
state: "provider_accepted",
|
|
4304
|
+
messageId: providerReference
|
|
3836
4305
|
});
|
|
3837
|
-
} catch
|
|
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:
|
|
3843
|
-
message:
|
|
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
|
-
|
|
3862
|
-
|
|
3863
|
-
const
|
|
3864
|
-
const
|
|
3865
|
-
|
|
3866
|
-
|
|
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
|
-
|
|
3869
|
-
|
|
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
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
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
|
-
|
|
3893
|
-
|
|
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
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
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
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
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
|
-
|
|
3925
|
-
|
|
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
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
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(
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
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
|
-
|
|
4109
|
-
|
|
4110
|
-
if (
|
|
4111
|
-
|
|
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
|
|
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(
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
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
|
-
|
|
4196
|
-
|
|
4197
|
-
const
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
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
|
|
4465
|
+
return {
|
|
4466
|
+
delivered,
|
|
4467
|
+
messageId: result.message_id,
|
|
4468
|
+
permanentBounces,
|
|
4469
|
+
queued,
|
|
4470
|
+
suppressedRecipients
|
|
4471
|
+
};
|
|
4205
4472
|
}
|
|
4206
|
-
__name(
|
|
4207
|
-
|
|
4208
|
-
const
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
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(
|
|
4218
|
-
async function
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
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
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
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
|
|
4516
|
+
let mimeMessage;
|
|
4240
4517
|
try {
|
|
4241
|
-
|
|
4518
|
+
mimeMessage = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(raw2);
|
|
4242
4519
|
} catch {
|
|
4243
|
-
|
|
4244
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
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
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
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
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
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
|
|
4375
|
-
|
|
4376
|
-
|
|
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
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
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
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
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
|
-
|
|
4442
|
-
|
|
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
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
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
|
-
|
|
4650
|
+
return emailActionResponse(context, admission.operationId, deliveries);
|
|
4453
4651
|
}
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
}
|
|
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
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
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
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
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
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
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
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
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
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4732
|
+
deliveries.push({
|
|
4733
|
+
recipient: r,
|
|
4734
|
+
ok: true,
|
|
4735
|
+
state: "provider_accepted",
|
|
4736
|
+
messageId: sendRawResult.messageId
|
|
4737
|
+
});
|
|
4581
4738
|
}
|
|
4582
4739
|
}
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
if (
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
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
|
-
|
|
4644
|
-
|
|
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(
|
|
4648
|
-
function
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
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
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
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
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
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
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
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
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
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
|
-
|
|
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(
|
|
4935
|
+
__name(guardHumanStream, "guardHumanStream");
|
|
4681
4936
|
|
|
4682
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5519
|
+
const active = await verifyPrerenderAuthority(
|
|
5258
5520
|
context.env.API,
|
|
5259
5521
|
context.env.JWT_SECRET,
|
|
5260
5522
|
projectId,
|
|
5261
|
-
payload.
|
|
5262
|
-
|
|
5263
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
5612
|
+
var cachedKeySecret4;
|
|
5340
5613
|
async function getVerifyKey(secret) {
|
|
5341
|
-
if (cachedVerifyKey &&
|
|
5614
|
+
if (cachedVerifyKey && cachedKeySecret4 === secret) {
|
|
5342
5615
|
return cachedVerifyKey;
|
|
5343
5616
|
}
|
|
5344
5617
|
cachedVerifyKey = await crypto.subtle.importKey(
|
|
5345
5618
|
"raw",
|
|
5346
|
-
|
|
5619
|
+
encoder6.encode(secret),
|
|
5347
5620
|
{ name: "HMAC", hash: "SHA-256" },
|
|
5348
5621
|
false,
|
|
5349
5622
|
["verify"]
|
|
5350
5623
|
);
|
|
5351
|
-
|
|
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,
|
|
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
|
-
|
|
5657
|
+
encoder6.encode(`${header}.${body}`)
|
|
5395
5658
|
);
|
|
5396
5659
|
if (!valid) {
|
|
5397
5660
|
return null;
|
|
5398
5661
|
}
|
|
5399
|
-
const payload = JSON.parse(
|
|
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;
|