@floegence/flowersec-core 0.19.0 → 0.19.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/dist/controlplane/request.js +60 -1
- package/dist/proxy/cookieJar.d.ts +3 -2
- package/dist/proxy/cookieJar.js +68 -20
- package/dist/proxy/integration.js +2 -1
- package/dist/proxy/runtime.d.ts +5 -0
- package/dist/proxy/runtime.js +84 -8
- package/dist/proxy/serviceWorker.js +21 -12
- package/dist/proxy/types.d.ts +1 -0
- package/dist/proxy/windowBridgeProtocol.d.ts +1 -0
- package/package.json +1 -1
|
@@ -13,6 +13,13 @@ export class ControlplaneRequestError extends Error {
|
|
|
13
13
|
}
|
|
14
14
|
export const DEFAULT_CONNECT_ARTIFACT_PATH = "/v1/connect/artifact";
|
|
15
15
|
export const DEFAULT_ENTRY_CONNECT_ARTIFACT_PATH = "/v1/connect/artifact/entry";
|
|
16
|
+
const DEFAULT_MAX_CONTROLPLANE_RESPONSE_BYTES = 1 << 20;
|
|
17
|
+
class ControlplaneResponseTooLargeError extends Error {
|
|
18
|
+
constructor(maxBytes) {
|
|
19
|
+
super(`controlplane response exceeded ${maxBytes} bytes`);
|
|
20
|
+
this.name = "ControlplaneResponseTooLargeError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
16
23
|
function resolveFetch(fetchImpl) {
|
|
17
24
|
if (fetchImpl)
|
|
18
25
|
return fetchImpl;
|
|
@@ -37,6 +44,46 @@ function parseMaybeJSON(bodyText) {
|
|
|
37
44
|
return trimmed;
|
|
38
45
|
}
|
|
39
46
|
}
|
|
47
|
+
function parseContentLength(headerValue) {
|
|
48
|
+
const raw = String(headerValue ?? "").trim();
|
|
49
|
+
if (raw === "")
|
|
50
|
+
return null;
|
|
51
|
+
if (!/^[0-9]+$/.test(raw))
|
|
52
|
+
return null;
|
|
53
|
+
const parsed = Number(raw);
|
|
54
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0)
|
|
55
|
+
return null;
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
async function readControlplaneText(response, maxBytes) {
|
|
59
|
+
const contentLength = parseContentLength(response.headers.get("Content-Length"));
|
|
60
|
+
if (contentLength !== null && contentLength > maxBytes) {
|
|
61
|
+
throw new ControlplaneResponseTooLargeError(maxBytes);
|
|
62
|
+
}
|
|
63
|
+
if (!response.body) {
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
const reader = response.body.getReader();
|
|
67
|
+
const decoder = new TextDecoder();
|
|
68
|
+
let totalBytes = 0;
|
|
69
|
+
let text = "";
|
|
70
|
+
while (true) {
|
|
71
|
+
const chunk = await reader.read();
|
|
72
|
+
if (chunk.done)
|
|
73
|
+
break;
|
|
74
|
+
totalBytes += chunk.value.byteLength;
|
|
75
|
+
if (totalBytes > maxBytes) {
|
|
76
|
+
try {
|
|
77
|
+
await reader.cancel();
|
|
78
|
+
}
|
|
79
|
+
catch { }
|
|
80
|
+
throw new ControlplaneResponseTooLargeError(maxBytes);
|
|
81
|
+
}
|
|
82
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
83
|
+
}
|
|
84
|
+
text += decoder.decode();
|
|
85
|
+
return text;
|
|
86
|
+
}
|
|
40
87
|
function decodeErrorMessage(status, responseBody) {
|
|
41
88
|
let message = `controlplane request failed: ${status}`;
|
|
42
89
|
let code = "";
|
|
@@ -61,7 +108,19 @@ export async function requestControlplaneJSON(url, init) {
|
|
|
61
108
|
...init,
|
|
62
109
|
cache: "no-store",
|
|
63
110
|
});
|
|
64
|
-
|
|
111
|
+
let rawBody = "";
|
|
112
|
+
try {
|
|
113
|
+
rawBody = await readControlplaneText(response, DEFAULT_MAX_CONTROLPLANE_RESPONSE_BYTES);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (err instanceof ControlplaneResponseTooLargeError && !response.ok) {
|
|
117
|
+
throw new ControlplaneRequestError({
|
|
118
|
+
status: response.status,
|
|
119
|
+
message: err.message,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
65
124
|
const responseBody = parseMaybeJSON(rawBody);
|
|
66
125
|
if (!response.ok) {
|
|
67
126
|
const error = decodeErrorMessage(response.status, responseBody);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare class CookieJar {
|
|
2
2
|
private readonly cookies;
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
private nextCreatedAtSeq;
|
|
4
|
+
setCookie(setCookieHeader: string, requestPath?: string): void;
|
|
5
|
+
updateFromSetCookieHeaders(headers: readonly string[], requestPath?: string): void;
|
|
5
6
|
getCookieHeader(path: string): string;
|
|
6
7
|
}
|
package/dist/proxy/cookieJar.js
CHANGED
|
@@ -11,9 +11,54 @@ function parseCookieNameValue(s) {
|
|
|
11
11
|
return null;
|
|
12
12
|
return { name, value };
|
|
13
13
|
}
|
|
14
|
+
function cookieStorageKey(name, path) {
|
|
15
|
+
return `${name}\u0000${path}`;
|
|
16
|
+
}
|
|
17
|
+
function requestPathOnly(path) {
|
|
18
|
+
const raw = path.trim();
|
|
19
|
+
if (!raw.startsWith("/"))
|
|
20
|
+
return "/";
|
|
21
|
+
const q = raw.indexOf("?");
|
|
22
|
+
const out = q >= 0 ? raw.slice(0, q) : raw;
|
|
23
|
+
return out === "" ? "/" : out;
|
|
24
|
+
}
|
|
25
|
+
function defaultCookiePathFromRequestPath(requestPath) {
|
|
26
|
+
const path = requestPathOnly(requestPath);
|
|
27
|
+
if (path === "/")
|
|
28
|
+
return "/";
|
|
29
|
+
const lastSlash = path.lastIndexOf("/");
|
|
30
|
+
if (lastSlash <= 0)
|
|
31
|
+
return "/";
|
|
32
|
+
return path.slice(0, lastSlash);
|
|
33
|
+
}
|
|
34
|
+
function normalizeCookiePath(pathAttr, requestPath) {
|
|
35
|
+
const path = pathAttr?.trim() ?? "";
|
|
36
|
+
if (path === "" || !path.startsWith("/"))
|
|
37
|
+
return defaultCookiePathFromRequestPath(requestPath);
|
|
38
|
+
return path;
|
|
39
|
+
}
|
|
40
|
+
function pathMatchesCookiePath(requestPath, cookiePath) {
|
|
41
|
+
const path = requestPathOnly(requestPath);
|
|
42
|
+
if (cookiePath === "/")
|
|
43
|
+
return true;
|
|
44
|
+
if (path === cookiePath)
|
|
45
|
+
return true;
|
|
46
|
+
if (!path.startsWith(cookiePath))
|
|
47
|
+
return false;
|
|
48
|
+
if (cookiePath.endsWith("/"))
|
|
49
|
+
return true;
|
|
50
|
+
return path.charAt(cookiePath.length) === "/";
|
|
51
|
+
}
|
|
52
|
+
function compareCookiesForHeader(a, b) {
|
|
53
|
+
const pathLenDiff = b.path.length - a.path.length;
|
|
54
|
+
if (pathLenDiff !== 0)
|
|
55
|
+
return pathLenDiff;
|
|
56
|
+
return a.createdAtSeq - b.createdAtSeq;
|
|
57
|
+
}
|
|
14
58
|
export class CookieJar {
|
|
15
59
|
cookies = new Map();
|
|
16
|
-
|
|
60
|
+
nextCreatedAtSeq = 0;
|
|
61
|
+
setCookie(setCookieHeader, requestPath = "/") {
|
|
17
62
|
const raw = setCookieHeader.trim();
|
|
18
63
|
if (raw === "")
|
|
19
64
|
return;
|
|
@@ -23,15 +68,16 @@ export class CookieJar {
|
|
|
23
68
|
const nv = parseCookieNameValue(parts[0] ?? "");
|
|
24
69
|
if (nv == null)
|
|
25
70
|
return;
|
|
26
|
-
let
|
|
71
|
+
let pathAttr;
|
|
27
72
|
let expiresAtMs;
|
|
73
|
+
let maxAgeSeen = false;
|
|
28
74
|
for (let i = 1; i < parts.length; i++) {
|
|
29
75
|
const p = parts[i];
|
|
30
76
|
const lower = p.toLowerCase();
|
|
31
77
|
if (lower.startsWith("path=")) {
|
|
32
78
|
const v = p.slice("path=".length).trim();
|
|
33
79
|
if (v !== "")
|
|
34
|
-
|
|
80
|
+
pathAttr = v;
|
|
35
81
|
continue;
|
|
36
82
|
}
|
|
37
83
|
if (lower.startsWith("max-age=")) {
|
|
@@ -39,14 +85,13 @@ export class CookieJar {
|
|
|
39
85
|
const n = Number.parseInt(v, 10);
|
|
40
86
|
if (!Number.isFinite(n))
|
|
41
87
|
continue;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
expiresAtMs = nowMs() + n * 1000;
|
|
88
|
+
maxAgeSeen = true;
|
|
89
|
+
expiresAtMs = n <= 0 ? 0 : nowMs() + n * 1000;
|
|
47
90
|
continue;
|
|
48
91
|
}
|
|
49
92
|
if (lower.startsWith("expires=")) {
|
|
93
|
+
if (maxAgeSeen)
|
|
94
|
+
continue;
|
|
50
95
|
const v = p.slice("expires=".length).trim();
|
|
51
96
|
const t = Date.parse(v);
|
|
52
97
|
if (!Number.isFinite(t))
|
|
@@ -55,34 +100,37 @@ export class CookieJar {
|
|
|
55
100
|
continue;
|
|
56
101
|
}
|
|
57
102
|
}
|
|
58
|
-
|
|
103
|
+
const path = normalizeCookiePath(pathAttr, requestPath);
|
|
104
|
+
const key = cookieStorageKey(nv.name, path);
|
|
59
105
|
if (expiresAtMs != null && expiresAtMs <= nowMs()) {
|
|
60
|
-
this.cookies.delete(
|
|
106
|
+
this.cookies.delete(key);
|
|
61
107
|
return;
|
|
62
108
|
}
|
|
109
|
+
const createdAtSeq = this.cookies.get(key)?.createdAtSeq ?? this.nextCreatedAtSeq++;
|
|
63
110
|
if (expiresAtMs == null) {
|
|
64
|
-
this.cookies.set(
|
|
111
|
+
this.cookies.set(key, { name: nv.name, value: nv.value, path, createdAtSeq });
|
|
65
112
|
}
|
|
66
113
|
else {
|
|
67
|
-
this.cookies.set(
|
|
114
|
+
this.cookies.set(key, { name: nv.name, value: nv.value, path, expiresAtMs, createdAtSeq });
|
|
68
115
|
}
|
|
69
116
|
}
|
|
70
|
-
updateFromSetCookieHeaders(headers) {
|
|
117
|
+
updateFromSetCookieHeaders(headers, requestPath = "/") {
|
|
71
118
|
for (const h of headers)
|
|
72
|
-
this.setCookie(h);
|
|
119
|
+
this.setCookie(h, requestPath);
|
|
73
120
|
}
|
|
74
121
|
getCookieHeader(path) {
|
|
75
122
|
const now = nowMs();
|
|
76
|
-
const
|
|
77
|
-
for (const c of this.cookies.
|
|
123
|
+
const matched = [];
|
|
124
|
+
for (const [key, c] of this.cookies.entries()) {
|
|
78
125
|
if (c.expiresAtMs != null && c.expiresAtMs <= now) {
|
|
79
|
-
this.cookies.delete(
|
|
126
|
+
this.cookies.delete(key);
|
|
80
127
|
continue;
|
|
81
128
|
}
|
|
82
|
-
if (
|
|
129
|
+
if (!pathMatchesCookiePath(path, c.path))
|
|
83
130
|
continue;
|
|
84
|
-
|
|
131
|
+
matched.push(c);
|
|
85
132
|
}
|
|
86
|
-
|
|
133
|
+
matched.sort(compareCookiesForHeader);
|
|
134
|
+
return matched.map((c) => `${c.name}=${c.value}`).join("; ");
|
|
87
135
|
}
|
|
88
136
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { profileToPresetManifest } from "./profiles.js";
|
|
2
2
|
import { resolveProxyPreset } from "./preset.js";
|
|
3
3
|
import { registerServiceWorkerAndEnsureControl } from "./registerServiceWorker.js";
|
|
4
|
-
import { createProxyRuntime } from "./runtime.js";
|
|
4
|
+
import { createProxyRuntime, ensureServiceWorkerRuntimeRegistered } from "./runtime.js";
|
|
5
5
|
import { createProxyServiceWorkerScript } from "./serviceWorker.js";
|
|
6
6
|
function dedupeStrings(values) {
|
|
7
7
|
const out = [];
|
|
@@ -252,6 +252,7 @@ export async function registerProxyIntegration(input) {
|
|
|
252
252
|
maxRepairAttempts,
|
|
253
253
|
controllerTimeoutMs,
|
|
254
254
|
});
|
|
255
|
+
await ensureServiceWorkerRuntimeRegistered({ timeoutMs: controllerTimeoutMs });
|
|
255
256
|
if (expectedScriptPathSuffix !== "") {
|
|
256
257
|
const ok = await waitForControllerSuffix(expectedScriptPathSuffix, controllerTimeoutMs);
|
|
257
258
|
if (!ok) {
|
package/dist/proxy/runtime.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ type ProxyFetchReq = Readonly<{
|
|
|
7
7
|
method: string;
|
|
8
8
|
path: string;
|
|
9
9
|
headers: readonly Header[];
|
|
10
|
+
external_origin?: string;
|
|
10
11
|
body?: ArrayBuffer;
|
|
11
12
|
}>;
|
|
12
13
|
export type ProxyRuntimeLimits = Readonly<{
|
|
@@ -39,5 +40,9 @@ export type ProxyRuntimeOptions = Readonly<{
|
|
|
39
40
|
extraWsHeaders?: readonly string[];
|
|
40
41
|
cookieJar?: CookieJar;
|
|
41
42
|
}>;
|
|
43
|
+
export type EnsureServiceWorkerRuntimeRegisteredOptions = Readonly<{
|
|
44
|
+
timeoutMs?: number;
|
|
45
|
+
}>;
|
|
42
46
|
export declare function createProxyRuntime(opts: ProxyRuntimeOptions): ProxyRuntime;
|
|
47
|
+
export declare function ensureServiceWorkerRuntimeRegistered(opts?: EnsureServiceWorkerRuntimeRegisteredOptions): Promise<void>;
|
|
43
48
|
export {};
|
package/dist/proxy/runtime.js
CHANGED
|
@@ -38,6 +38,27 @@ function normalizeTimeoutMs(timeoutMs) {
|
|
|
38
38
|
throw new Error("timeout_ms must be >= 0");
|
|
39
39
|
return v;
|
|
40
40
|
}
|
|
41
|
+
function normalizeExternalOrigin(externalOriginRaw) {
|
|
42
|
+
if (typeof externalOriginRaw !== "string")
|
|
43
|
+
return undefined;
|
|
44
|
+
const externalOrigin = externalOriginRaw.trim();
|
|
45
|
+
if (externalOrigin === "")
|
|
46
|
+
return undefined;
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = new URL(externalOrigin);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new Error("external_origin must be an http(s) origin");
|
|
53
|
+
}
|
|
54
|
+
if ((parsed.protocol !== "http:" && parsed.protocol !== "https:") || parsed.host === "") {
|
|
55
|
+
throw new Error("external_origin must be an http(s) origin");
|
|
56
|
+
}
|
|
57
|
+
if (parsed.username !== "" || parsed.password !== "" || (parsed.pathname !== "" && parsed.pathname !== "/") || parsed.search !== "" || parsed.hash !== "") {
|
|
58
|
+
throw new Error("external_origin must be an origin without credentials, path, query, or fragment");
|
|
59
|
+
}
|
|
60
|
+
return parsed.origin;
|
|
61
|
+
}
|
|
41
62
|
function normalizeMaxBytes(name, v, defaultValue) {
|
|
42
63
|
if (v == null)
|
|
43
64
|
return defaultValue;
|
|
@@ -97,13 +118,9 @@ export function createProxyRuntime(opts) {
|
|
|
97
118
|
const extraResponseHeaders = opts.extraResponseHeaders ?? [];
|
|
98
119
|
const extraWsHeaders = opts.extraWsHeaders ?? [];
|
|
99
120
|
const registerRuntime = () => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
// Best-effort: runtime can still work if SW picks it via matchAll().
|
|
106
|
-
}
|
|
121
|
+
void ensureServiceWorkerRuntimeRegistered({ timeoutMs: 2_000 }).catch(() => {
|
|
122
|
+
// Best-effort: controllerchange will retry once the active Service Worker is ready.
|
|
123
|
+
});
|
|
107
124
|
};
|
|
108
125
|
const onMessage = (ev) => {
|
|
109
126
|
const data = ev.data;
|
|
@@ -134,6 +151,7 @@ export function createProxyRuntime(opts) {
|
|
|
134
151
|
try {
|
|
135
152
|
const path = pathOnly(req.path);
|
|
136
153
|
const requestID = req.id.trim() !== "" ? req.id : randomB64u(18);
|
|
154
|
+
const externalOrigin = normalizeExternalOrigin(req.external_origin);
|
|
137
155
|
stream = await client.openStream(PROXY_KIND_HTTP1, { signal: ac.signal });
|
|
138
156
|
const reader = createByteReader(stream, { signal: ac.signal });
|
|
139
157
|
const filteredReqHeaders = filterRequestHeaders(req.headers, { extraAllowed: extraRequestHeaders });
|
|
@@ -145,6 +163,7 @@ export function createProxyRuntime(opts) {
|
|
|
145
163
|
method: req.method,
|
|
146
164
|
path,
|
|
147
165
|
headers: reqHeaders,
|
|
166
|
+
...(externalOrigin === undefined ? {} : { external_origin: externalOrigin }),
|
|
148
167
|
timeout_ms: timeoutMs
|
|
149
168
|
});
|
|
150
169
|
const body = req.body != null ? new Uint8Array(req.body) : new Uint8Array();
|
|
@@ -168,7 +187,7 @@ export function createProxyRuntime(opts) {
|
|
|
168
187
|
const status = Math.max(0, Math.floor(respMeta.status ?? 502));
|
|
169
188
|
const rawHeaders = Array.isArray(respMeta.headers) ? respMeta.headers : [];
|
|
170
189
|
const { passthrough, setCookie } = filterResponseHeaders(rawHeaders, { extraAllowed: extraResponseHeaders });
|
|
171
|
-
cookieJar.updateFromSetCookieHeaders(setCookie);
|
|
190
|
+
cookieJar.updateFromSetCookieHeaders(setCookie, path);
|
|
172
191
|
port.postMessage({ type: "flowersec-proxy:response_meta", status, headers: passthrough });
|
|
173
192
|
const chunks = await readChunkFrames(reader, maxChunkBytes, maxBodyBytes);
|
|
174
193
|
for await (const chunk of chunks) {
|
|
@@ -237,3 +256,60 @@ export function createProxyRuntime(opts) {
|
|
|
237
256
|
}
|
|
238
257
|
};
|
|
239
258
|
}
|
|
259
|
+
export async function ensureServiceWorkerRuntimeRegistered(opts = {}) {
|
|
260
|
+
const ctl = globalThis.navigator?.serviceWorker?.controller;
|
|
261
|
+
if (!ctl || typeof ctl.postMessage !== "function")
|
|
262
|
+
return;
|
|
263
|
+
const timeoutMs = Math.max(0, Math.floor(opts.timeoutMs ?? 2_000));
|
|
264
|
+
const ch = new MessageChannel();
|
|
265
|
+
await new Promise((resolve, reject) => {
|
|
266
|
+
let done = false;
|
|
267
|
+
let timer = null;
|
|
268
|
+
const finish = (error) => {
|
|
269
|
+
if (done)
|
|
270
|
+
return;
|
|
271
|
+
done = true;
|
|
272
|
+
if (timer != null)
|
|
273
|
+
clearTimeout(timer);
|
|
274
|
+
ch.port1.onmessage = null;
|
|
275
|
+
ch.port1.onmessageerror = null;
|
|
276
|
+
try {
|
|
277
|
+
ch.port1.close();
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// Best-effort.
|
|
281
|
+
}
|
|
282
|
+
if (error == null) {
|
|
283
|
+
resolve();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
287
|
+
};
|
|
288
|
+
ch.port1.onmessage = (ev) => {
|
|
289
|
+
const data = ev.data;
|
|
290
|
+
if (data == null || typeof data !== "object")
|
|
291
|
+
return;
|
|
292
|
+
if (data.type !== "flowersec-proxy:register-runtime-ack")
|
|
293
|
+
return;
|
|
294
|
+
if (data.ok !== true) {
|
|
295
|
+
finish(new Error("service worker runtime registration was rejected"));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
finish();
|
|
299
|
+
};
|
|
300
|
+
ch.port1.onmessageerror = () => {
|
|
301
|
+
finish(new Error("service worker runtime registration failed"));
|
|
302
|
+
};
|
|
303
|
+
if (timeoutMs > 0) {
|
|
304
|
+
timer = setTimeout(() => {
|
|
305
|
+
finish(new Error("service worker runtime registration timed out"));
|
|
306
|
+
}, timeoutMs);
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
ctl.postMessage({ type: "flowersec-proxy:register-runtime" }, [ch.port2]);
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
finish(error);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
@@ -159,6 +159,7 @@ self.addEventListener("install", (event) => {
|
|
|
159
159
|
});
|
|
160
160
|
|
|
161
161
|
self.addEventListener("activate", (event) => {
|
|
162
|
+
runtimeClientId = null;
|
|
162
163
|
event.waitUntil(self.clients.claim());
|
|
163
164
|
});
|
|
164
165
|
|
|
@@ -167,7 +168,13 @@ self.addEventListener("message", (event) => {
|
|
|
167
168
|
if (!data || typeof data !== "object") return;
|
|
168
169
|
const msgType = typeof data.type === "string" ? data.type : "";
|
|
169
170
|
if (msgType === "flowersec-proxy:register-runtime") {
|
|
170
|
-
|
|
171
|
+
const ok = Boolean(event.source && typeof event.source.id === "string");
|
|
172
|
+
if (ok) runtimeClientId = event.source.id;
|
|
173
|
+
const port = event.ports && event.ports[0];
|
|
174
|
+
if (port) {
|
|
175
|
+
try { port.postMessage({ type: "flowersec-proxy:register-runtime-ack", ok }); } catch {}
|
|
176
|
+
try { port.close(); } catch {}
|
|
177
|
+
}
|
|
171
178
|
return;
|
|
172
179
|
}
|
|
173
180
|
if (!FORWARD_FETCH_MESSAGE_TYPES.has(msgType)) return;
|
|
@@ -207,16 +214,10 @@ async function getWindowClient(preferredClientId) {
|
|
|
207
214
|
return cs.length > 0 ? cs[0] : null;
|
|
208
215
|
}
|
|
209
216
|
|
|
210
|
-
if (runtimeClientId)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
const cs = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
|
|
216
|
-
if (cs.length > 0) {
|
|
217
|
-
runtimeClientId = cs[0].id;
|
|
218
|
-
return cs[0];
|
|
219
|
-
}
|
|
217
|
+
if (!runtimeClientId) return null;
|
|
218
|
+
const c = await self.clients.get(runtimeClientId);
|
|
219
|
+
if (c) return c;
|
|
220
|
+
runtimeClientId = null;
|
|
220
221
|
return null;
|
|
221
222
|
}
|
|
222
223
|
|
|
@@ -367,6 +368,7 @@ async function handleFetch(event) {
|
|
|
367
368
|
const req = event.request;
|
|
368
369
|
const url = new URL(req.url);
|
|
369
370
|
const id = Math.random().toString(16).slice(2) + Date.now().toString(16);
|
|
371
|
+
const externalOrigin = url.origin === self.location.origin ? url.origin : "";
|
|
370
372
|
|
|
371
373
|
let body;
|
|
372
374
|
if (req.method === "GET" || req.method === "HEAD") {
|
|
@@ -488,7 +490,14 @@ async function handleFetch(event) {
|
|
|
488
490
|
|
|
489
491
|
target.postMessage({
|
|
490
492
|
type: WINDOW_CLIENT_MESSAGE_TYPE,
|
|
491
|
-
req: {
|
|
493
|
+
req: {
|
|
494
|
+
id,
|
|
495
|
+
method: req.method,
|
|
496
|
+
path,
|
|
497
|
+
headers: headersToPairs(req.headers),
|
|
498
|
+
...(externalOrigin ? { external_origin: externalOrigin } : {}),
|
|
499
|
+
body
|
|
500
|
+
}
|
|
492
501
|
}, [port2]);
|
|
493
502
|
|
|
494
503
|
const meta = await metaPromise;
|
package/dist/proxy/types.d.ts
CHANGED