bosia 0.6.3 → 0.6.5
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/package.json +1 -1
- package/src/cli/feat.ts +171 -7
- package/src/cli/index.ts +11 -4
- package/src/cli/registry.ts +4 -0
- package/src/core/build.ts +2 -0
- package/src/core/cookies.ts +34 -7
- package/src/core/hooks.ts +1 -1
- package/src/core/server.ts +9 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosia",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A fast, batteries-included fullstack framework — SSR · Svelte 5 Runes · Bun · ElysiaJS. File-based routing inspired by SvelteKit. No Node.js, no Vite, no adapters.",
|
|
6
6
|
"keywords": [
|
package/src/cli/feat.ts
CHANGED
|
@@ -28,6 +28,17 @@ interface FileEntry {
|
|
|
28
28
|
target: string;
|
|
29
29
|
strategy?: FileStrategy;
|
|
30
30
|
marker?: string; // unique id within target (default = feature name)
|
|
31
|
+
when?: Record<string, string>; // install only if every option value matches
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface FeatureOption {
|
|
35
|
+
name: string; // option key, also used in FileEntry.when
|
|
36
|
+
flag?: string; // short flag, e.g. "-d"
|
|
37
|
+
long?: string; // long flag, e.g. "--dialect"
|
|
38
|
+
prompt?: string; // interactive prompt label
|
|
39
|
+
choices?: { value: string; label?: string; hint?: string }[]; // enum picker
|
|
40
|
+
default?: string; // fallback when -y is set or user accepts default
|
|
41
|
+
required?: boolean; // when true, missing value with no default errors out
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
interface FeatureMeta {
|
|
@@ -40,6 +51,7 @@ interface FeatureMeta {
|
|
|
40
51
|
npmDevDeps?: Record<string, string>;
|
|
41
52
|
scripts?: Record<string, string>; // package.json scripts to add
|
|
42
53
|
envVars?: Record<string, string>; // env vars to append to .env if missing
|
|
54
|
+
options?: FeatureOption[]; // feature-specific CLI flags (e.g. file-upload's -d)
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
let registryRoot: string | null = null;
|
|
@@ -47,15 +59,18 @@ let registryRoot: string | null = null;
|
|
|
47
59
|
// Track installed features to prevent circular dependencies
|
|
48
60
|
const installedFeats = new Set<string>();
|
|
49
61
|
|
|
50
|
-
export async function runFeat(name: string | undefined,
|
|
62
|
+
export async function runFeat(name: string | undefined, args: string[] = []) {
|
|
63
|
+
// Strip global flags (-y/--yes, --local); everything else is feature args.
|
|
64
|
+
const { autoYes, local, featureArgs } = splitGlobalFlags(args);
|
|
65
|
+
|
|
51
66
|
if (!name) {
|
|
52
67
|
console.error(
|
|
53
|
-
"❌ Please provide a feature name.\n Usage: bun x bosia@latest feat <feature> [
|
|
68
|
+
"❌ Please provide a feature name.\n Usage: bun x bosia@latest feat [-y] [--local] <feature> [feature options...]",
|
|
54
69
|
);
|
|
55
70
|
process.exit(1);
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
if (
|
|
73
|
+
if (local) {
|
|
59
74
|
registryRoot = resolveLocalRegistryOrExit();
|
|
60
75
|
console.log(`⬡ Using local registry: ${registryRoot}\n`);
|
|
61
76
|
}
|
|
@@ -63,7 +78,119 @@ export async function runFeat(name: string | undefined, flags: string[] = []) {
|
|
|
63
78
|
// Initialize add.ts registry context so addComponent resolves paths correctly
|
|
64
79
|
await initAddRegistry(registryRoot);
|
|
65
80
|
|
|
66
|
-
await installFeature(name, true);
|
|
81
|
+
await installFeature(name, true, { skipPrompts: autoYes, featureArgs });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function splitGlobalFlags(args: string[]): {
|
|
85
|
+
autoYes: boolean;
|
|
86
|
+
local: boolean;
|
|
87
|
+
featureArgs: string[];
|
|
88
|
+
} {
|
|
89
|
+
let autoYes = false;
|
|
90
|
+
let local = false;
|
|
91
|
+
const featureArgs: string[] = [];
|
|
92
|
+
for (const a of args) {
|
|
93
|
+
if (a === "-y" || a === "--yes") autoYes = true;
|
|
94
|
+
else if (a === "--local") local = true;
|
|
95
|
+
else featureArgs.push(a);
|
|
96
|
+
}
|
|
97
|
+
return { autoYes, local, featureArgs };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parse `args` against `options` (the feature's declared schema). Unknown flags abort.
|
|
102
|
+
* Returns a `{name: value}` map; missing entries are filled by prompt or `default`.
|
|
103
|
+
*/
|
|
104
|
+
async function resolveFeatureOptions(
|
|
105
|
+
featName: string,
|
|
106
|
+
options: FeatureOption[],
|
|
107
|
+
args: string[],
|
|
108
|
+
skipPrompts: boolean,
|
|
109
|
+
): Promise<Record<string, string>> {
|
|
110
|
+
const values: Record<string, string> = {};
|
|
111
|
+
const byFlag = new Map<string, FeatureOption>();
|
|
112
|
+
for (const opt of options) {
|
|
113
|
+
if (opt.flag) byFlag.set(opt.flag, opt);
|
|
114
|
+
if (opt.long) byFlag.set(opt.long, opt);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (let i = 0; i < args.length; i++) {
|
|
118
|
+
const tok = args[i];
|
|
119
|
+
const opt = byFlag.get(tok);
|
|
120
|
+
if (!opt) {
|
|
121
|
+
console.error(`❌ Unknown option "${tok}" for feature "${featName}".`);
|
|
122
|
+
if (options.length > 0) {
|
|
123
|
+
const valid = options
|
|
124
|
+
.map((o) => [o.flag, o.long].filter(Boolean).join("/"))
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.join(", ");
|
|
127
|
+
console.error(` Valid options: ${valid}`);
|
|
128
|
+
}
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
const val = args[++i];
|
|
132
|
+
if (val === undefined) {
|
|
133
|
+
console.error(`❌ Option "${tok}" requires a value.`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
if (opt.choices && !opt.choices.some((c) => c.value === val)) {
|
|
137
|
+
console.error(
|
|
138
|
+
`❌ Invalid value "${val}" for "${tok}". Expected: ${opt.choices.map((c) => c.value).join(", ")}`,
|
|
139
|
+
);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
values[opt.name] = val;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
for (const opt of options) {
|
|
146
|
+
if (opt.name in values) continue;
|
|
147
|
+
if (skipPrompts) {
|
|
148
|
+
if (opt.default !== undefined) {
|
|
149
|
+
values[opt.name] = opt.default;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (opt.required) {
|
|
153
|
+
console.error(
|
|
154
|
+
`❌ Feature "${featName}" requires "${opt.flag ?? opt.long ?? opt.name}".`,
|
|
155
|
+
);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
values[opt.name] = await promptOption(featName, opt);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return values;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function promptOption(featName: string, opt: FeatureOption): Promise<string> {
|
|
167
|
+
const message = opt.prompt ?? `Choose "${opt.name}" for "${featName}"`;
|
|
168
|
+
if (opt.choices && opt.choices.length > 0) {
|
|
169
|
+
const selected = await p.select({
|
|
170
|
+
message,
|
|
171
|
+
options: opt.choices.map((c) => ({
|
|
172
|
+
value: c.value,
|
|
173
|
+
label: c.label ?? c.value,
|
|
174
|
+
hint: c.hint,
|
|
175
|
+
})),
|
|
176
|
+
initialValue: opt.default ?? opt.choices[0].value,
|
|
177
|
+
});
|
|
178
|
+
if (p.isCancel(selected)) {
|
|
179
|
+
p.cancel("Operation cancelled.");
|
|
180
|
+
process.exit(0);
|
|
181
|
+
}
|
|
182
|
+
return selected as string;
|
|
183
|
+
}
|
|
184
|
+
const typed = await p.text({
|
|
185
|
+
message,
|
|
186
|
+
initialValue: opt.default,
|
|
187
|
+
validate: (v) => (opt.required && !v ? "Required" : undefined),
|
|
188
|
+
});
|
|
189
|
+
if (p.isCancel(typed)) {
|
|
190
|
+
p.cancel("Operation cancelled.");
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
return (typed as string) ?? "";
|
|
67
194
|
}
|
|
68
195
|
|
|
69
196
|
/** Set the registry root for feature resolution. Called by create.ts for template features. */
|
|
@@ -83,10 +210,39 @@ export async function installFeature(name: string, isRoot: boolean, options?: In
|
|
|
83
210
|
|
|
84
211
|
const meta = await readRegistryJSON<FeatureMeta>(registryRoot, "features", name, "meta.json");
|
|
85
212
|
|
|
213
|
+
// Resolve this feature's own options (from `featureArgs` if root, else from `featureOptions`).
|
|
214
|
+
const inheritedOptions = options?.featureOptions ?? {};
|
|
215
|
+
let myOptions: Record<string, string> = {};
|
|
216
|
+
if (meta.options && meta.options.length > 0) {
|
|
217
|
+
myOptions = isRoot
|
|
218
|
+
? await resolveFeatureOptions(
|
|
219
|
+
name,
|
|
220
|
+
meta.options,
|
|
221
|
+
options?.featureArgs ?? [],
|
|
222
|
+
options?.skipPrompts ?? false,
|
|
223
|
+
)
|
|
224
|
+
: // Dependency features inherit any caller-provided values; prompt only for unresolved required opts.
|
|
225
|
+
await resolveFeatureOptions(name, meta.options, [], options?.skipPrompts ?? false);
|
|
226
|
+
for (const [k, v] of Object.entries(inheritedOptions)) {
|
|
227
|
+
const [feat, optName] = k.split(".");
|
|
228
|
+
if (feat === name && !(optName in myOptions)) myOptions[optName] = v;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Merge into the namespaced map for downstream dependency features.
|
|
233
|
+
const featureOptions = { ...inheritedOptions };
|
|
234
|
+
for (const [k, v] of Object.entries(myOptions)) featureOptions[`${name}.${k}`] = v;
|
|
235
|
+
|
|
236
|
+
const nextOptions: InstallOptions = {
|
|
237
|
+
...options,
|
|
238
|
+
featureOptions,
|
|
239
|
+
featureArgs: undefined, // already consumed by the root feature
|
|
240
|
+
};
|
|
241
|
+
|
|
86
242
|
// Install required feature dependencies first (recursive)
|
|
87
243
|
if (meta.features && meta.features.length > 0) {
|
|
88
244
|
for (const feat of meta.features) {
|
|
89
|
-
await installFeature(feat, false,
|
|
245
|
+
await installFeature(feat, false, nextOptions);
|
|
90
246
|
}
|
|
91
247
|
}
|
|
92
248
|
|
|
@@ -99,9 +255,10 @@ export async function installFeature(name: string, isRoot: boolean, options?: In
|
|
|
99
255
|
console.log("");
|
|
100
256
|
}
|
|
101
257
|
|
|
102
|
-
// Apply each file entry per its strategy
|
|
258
|
+
// Apply each file entry per its strategy. Skip entries whose `when` clause doesn't match.
|
|
103
259
|
const createdDirs = new Set<string>();
|
|
104
260
|
for (const entry of meta.files) {
|
|
261
|
+
if (entry.when && !whenMatches(entry.when, myOptions)) continue;
|
|
105
262
|
const dest = join(cwd, entry.target);
|
|
106
263
|
const strategy: FileStrategy = entry.strategy ?? "write";
|
|
107
264
|
const dir = dirname(dest);
|
|
@@ -117,7 +274,7 @@ export async function installFeature(name: string, isRoot: boolean, options?: In
|
|
|
117
274
|
strategy,
|
|
118
275
|
feat: name,
|
|
119
276
|
marker: entry.marker ?? name,
|
|
120
|
-
skipPrompts:
|
|
277
|
+
skipPrompts: nextOptions.skipPrompts ?? false,
|
|
121
278
|
});
|
|
122
279
|
}
|
|
123
280
|
|
|
@@ -285,6 +442,13 @@ async function applyStrategy(args: StrategyArgs): Promise<void> {
|
|
|
285
442
|
}
|
|
286
443
|
}
|
|
287
444
|
|
|
445
|
+
function whenMatches(when: Record<string, string>, values: Record<string, string>): boolean {
|
|
446
|
+
for (const [k, expected] of Object.entries(when)) {
|
|
447
|
+
if (values[k] !== expected) return false;
|
|
448
|
+
}
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
|
|
288
452
|
function blockDelim(ext: string): { start: string; end: string } {
|
|
289
453
|
if (ext === ".html" || ext === ".svelte") return { start: "<!--", end: "-->" };
|
|
290
454
|
if (ext === ".css") return { start: "/*", end: "*/" };
|
package/src/cli/index.ts
CHANGED
|
@@ -59,9 +59,14 @@ async function main() {
|
|
|
59
59
|
}
|
|
60
60
|
case "feat": {
|
|
61
61
|
const { runFeat } = await import("./feat.ts");
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
// First non-flag token is the feature name; everything else flows through to the
|
|
63
|
+
// feature's own option parser. Global flags (-y, --local) are also accepted here
|
|
64
|
+
// and get split out inside runFeat.
|
|
65
|
+
const nameIdx = args.findIndex((a) => !a.startsWith("-"));
|
|
66
|
+
const featName = nameIdx === -1 ? undefined : args[nameIdx];
|
|
67
|
+
const rest =
|
|
68
|
+
nameIdx === -1 ? args : [...args.slice(0, nameIdx), ...args.slice(nameIdx + 1)];
|
|
69
|
+
await runFeat(featName, rest);
|
|
65
70
|
break;
|
|
66
71
|
}
|
|
67
72
|
default: {
|
|
@@ -81,7 +86,9 @@ Commands:
|
|
|
81
86
|
add block <cat>/<name> Add a composed block from the registry
|
|
82
87
|
add theme <name> Add a theme (tokens.css) from the registry
|
|
83
88
|
add font <family> <url> Prepend an @import url(...) for a font family to src/app.css
|
|
84
|
-
feat <feature>
|
|
89
|
+
feat [-y] <feature> [feature options...] Add a feature scaffold from the registry [--local]
|
|
90
|
+
-y / --yes auto-confirms prompts and uses each feature's default option values
|
|
91
|
+
Feature-specific options (e.g. file-upload's -d) follow the feature name
|
|
85
92
|
|
|
86
93
|
Examples:
|
|
87
94
|
bun x bosia@latest create my-app
|
package/src/cli/registry.ts
CHANGED
|
@@ -10,6 +10,10 @@ export interface InstallOptions {
|
|
|
10
10
|
skipInstall?: boolean; // write deps to package.json instead of `bun add`
|
|
11
11
|
skipPrompts?: boolean; // auto-overwrite, no interactive prompts
|
|
12
12
|
cwd?: string; // override process.cwd() for file operations
|
|
13
|
+
/** Pre-resolved feature-specific option values, keyed by `featureName.optionName`. */
|
|
14
|
+
featureOptions?: Record<string, string>;
|
|
15
|
+
/** Remaining argv tokens to be parsed as the root feature's own options. */
|
|
16
|
+
featureArgs?: string[];
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
// ─── Local registry resolution ────────────────────────────
|
package/src/core/build.ts
CHANGED
|
@@ -154,6 +154,7 @@ const clientPromise = Bun.build({
|
|
|
154
154
|
entrypoints: [join(CORE_DIR, "client", "hydrate.ts")],
|
|
155
155
|
outdir: `${OUT_DIR}/client`,
|
|
156
156
|
target: "browser",
|
|
157
|
+
conditions: ["svelte"],
|
|
157
158
|
splitting: true,
|
|
158
159
|
naming: { chunk: "[name]-[hash].[ext]" },
|
|
159
160
|
minify: isProduction,
|
|
@@ -169,6 +170,7 @@ const serverPromise = Bun.build({
|
|
|
169
170
|
entrypoints: [join(CORE_DIR, "server.ts")],
|
|
170
171
|
outdir: `${OUT_DIR}/server`,
|
|
171
172
|
target: "bun",
|
|
173
|
+
conditions: ["svelte"],
|
|
172
174
|
splitting: true,
|
|
173
175
|
naming: { entry: "index.[ext]", chunk: "[name]-[hash].[ext]" },
|
|
174
176
|
minify: isProduction,
|
package/src/core/cookies.ts
CHANGED
|
@@ -3,7 +3,18 @@ import type { Cookies, CookieOptions } from "./hooks.ts";
|
|
|
3
3
|
// ─── Cookie Validation (RFC 6265) ────────────────────────
|
|
4
4
|
/** Rejects characters that could inject into Set-Cookie headers. */
|
|
5
5
|
const UNSAFE_COOKIE_VALUE = /[;\r\n]/;
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Accept both casings (matches SvelteKit/Express convention) and write the
|
|
8
|
+
* canonical capitalized form into the Set-Cookie header.
|
|
9
|
+
*/
|
|
10
|
+
const SAMESITE_NORMALIZE: Record<string, "Strict" | "Lax" | "None"> = {
|
|
11
|
+
strict: "Strict",
|
|
12
|
+
lax: "Lax",
|
|
13
|
+
none: "None",
|
|
14
|
+
Strict: "Strict",
|
|
15
|
+
Lax: "Lax",
|
|
16
|
+
None: "None",
|
|
17
|
+
};
|
|
7
18
|
|
|
8
19
|
/**
|
|
9
20
|
* RFC 6265 §4.1.1: cookie-name is an HTTP token (RFC 2616 §2.2).
|
|
@@ -41,15 +52,20 @@ function parseCookies(header: string): Record<string, string> {
|
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
export class CookieJar implements Cookies {
|
|
55
|
+
private static _warnedSecureOverHttp = false;
|
|
56
|
+
|
|
44
57
|
private _incoming: Record<string, string>;
|
|
45
58
|
private _outgoing: string[] = [];
|
|
46
59
|
private _defaults: CookieOptions;
|
|
47
60
|
private _accessed = false;
|
|
61
|
+
private _isHttps: boolean;
|
|
48
62
|
|
|
49
|
-
constructor(cookieHeader: string,
|
|
63
|
+
constructor(cookieHeader: string, isHttps = false) {
|
|
50
64
|
this._incoming = parseCookies(cookieHeader);
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
this._isHttps = isHttps;
|
|
66
|
+
// Browsers drop Secure cookies sent over HTTP — only default `secure` on
|
|
67
|
+
// when the current request actually arrived over HTTPS.
|
|
68
|
+
this._defaults = isHttps ? COOKIE_DEFAULTS : { ...COOKIE_DEFAULTS, secure: false };
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
get(name: string): string | undefined {
|
|
@@ -69,6 +85,17 @@ export class CookieJar implements Cookies {
|
|
|
69
85
|
set(name: string, value: string, options?: CookieOptions): void {
|
|
70
86
|
if (!VALID_COOKIE_NAME.test(name)) throw new Error(`Invalid cookie name: ${name}`);
|
|
71
87
|
const opts = { ...this._defaults, ...options };
|
|
88
|
+
if (!this._isHttps && opts.secure) {
|
|
89
|
+
opts.secure = false;
|
|
90
|
+
if (!CookieJar._warnedSecureOverHttp) {
|
|
91
|
+
console.warn(
|
|
92
|
+
"[bosia] cookies.set passed secure:true over HTTP — downgrading. " +
|
|
93
|
+
"Browsers drop Secure cookies on non-HTTPS. " +
|
|
94
|
+
"Remove the `secure` option; Bosia auto-applies it when the request is HTTPS.",
|
|
95
|
+
);
|
|
96
|
+
CookieJar._warnedSecureOverHttp = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
72
99
|
let header = `${name}=${encodeURIComponent(value)}`;
|
|
73
100
|
if (opts.path) {
|
|
74
101
|
if (UNSAFE_COOKIE_VALUE.test(opts.path))
|
|
@@ -85,9 +112,9 @@ export class CookieJar implements Cookies {
|
|
|
85
112
|
if (opts.httpOnly) header += "; HttpOnly";
|
|
86
113
|
if (opts.secure) header += "; Secure";
|
|
87
114
|
if (opts.sameSite) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
header += `; SameSite=${
|
|
115
|
+
const canonical = SAMESITE_NORMALIZE[opts.sameSite as string];
|
|
116
|
+
if (!canonical) throw new Error(`Invalid cookie sameSite: ${opts.sameSite}`);
|
|
117
|
+
header += `; SameSite=${canonical}`;
|
|
91
118
|
}
|
|
92
119
|
this._outgoing.push(header);
|
|
93
120
|
}
|
package/src/core/hooks.ts
CHANGED
package/src/core/server.ts
CHANGED
|
@@ -711,6 +711,11 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
711
711
|
// (preview/proxy hubs, design tools, etc.). Other security headers stay on.
|
|
712
712
|
const _xfoDisabled = process.env.DISABLE_X_FRAME_OPTIONS === "true";
|
|
713
713
|
|
|
714
|
+
// Trust `x-forwarded-proto` header behind a TLS-terminating proxy when computing
|
|
715
|
+
// per-request HTTPS-ness (drives `Secure` cookie flag). Off by default — the
|
|
716
|
+
// header is spoofable from any client that talks directly to the app.
|
|
717
|
+
const TRUST_PROXY = process.env.TRUST_PROXY === "true";
|
|
718
|
+
|
|
714
719
|
const SECURITY_HEADERS: Record<string, string> = {
|
|
715
720
|
"X-Content-Type-Options": "nosniff",
|
|
716
721
|
...(_xfoDisabled ? {} : { "X-Frame-Options": "SAMEORIGIN" }),
|
|
@@ -754,7 +759,10 @@ async function handleRequest(request: Request, url: URL): Promise<Response> {
|
|
|
754
759
|
return Response.json({ error: "Forbidden", message: csrfError }, { status: 403 });
|
|
755
760
|
}
|
|
756
761
|
|
|
757
|
-
const
|
|
762
|
+
const isHttps =
|
|
763
|
+
(TRUST_PROXY && request.headers.get("x-forwarded-proto") === "https") ||
|
|
764
|
+
url.protocol === "https:";
|
|
765
|
+
const cookieJar = new CookieJar(request.headers.get("cookie") ?? "", isHttps);
|
|
758
766
|
const nonce = CSP_ENABLED ? generateNonce() : "";
|
|
759
767
|
const event: RequestEvent = {
|
|
760
768
|
request,
|