@floegence/flowersec-core 0.16.0 → 0.16.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/browser/controlplane.d.ts +17 -0
- package/dist/browser/controlplane.js +72 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/reconnectConfig.d.ts +30 -0
- package/dist/browser/reconnectConfig.js +46 -0
- package/dist/proxy/appWindow.d.ts +13 -0
- package/dist/proxy/appWindow.js +25 -0
- package/dist/proxy/bootstrap.d.ts +15 -1
- package/dist/proxy/bootstrap.js +36 -0
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ChannelInitGrant } from "../facade.js";
|
|
2
|
+
type FetchLike = typeof fetch;
|
|
3
|
+
type BaseControlplaneRequestConfig = Readonly<{
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
endpointId: string;
|
|
6
|
+
payload?: Record<string, unknown>;
|
|
7
|
+
headers?: HeadersInit;
|
|
8
|
+
credentials?: RequestCredentials;
|
|
9
|
+
fetch?: FetchLike;
|
|
10
|
+
}>;
|
|
11
|
+
export type ControlplaneConfig = BaseControlplaneRequestConfig;
|
|
12
|
+
export type EntryControlplaneConfig = BaseControlplaneRequestConfig & Readonly<{
|
|
13
|
+
entryTicket: string;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function requestChannelGrant(config: ControlplaneConfig): Promise<ChannelInitGrant>;
|
|
16
|
+
export declare function requestEntryChannelGrant(config: EntryControlplaneConfig): Promise<ChannelInitGrant>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { assertChannelInitGrant } from "../facade.js";
|
|
2
|
+
function resolveFetch(fetchImpl) {
|
|
3
|
+
if (fetchImpl)
|
|
4
|
+
return fetchImpl;
|
|
5
|
+
if (typeof globalThis.fetch === "function")
|
|
6
|
+
return globalThis.fetch.bind(globalThis);
|
|
7
|
+
throw new Error("global fetch is not available");
|
|
8
|
+
}
|
|
9
|
+
function buildURL(baseUrl, path) {
|
|
10
|
+
const base = String(baseUrl ?? "").trim();
|
|
11
|
+
if (base === "")
|
|
12
|
+
return path;
|
|
13
|
+
return `${base.replace(/\/+$/, "")}${path}`;
|
|
14
|
+
}
|
|
15
|
+
function buildPayload(endpointId, payload) {
|
|
16
|
+
const id = String(endpointId ?? "").trim();
|
|
17
|
+
if (id === "")
|
|
18
|
+
throw new Error("endpointId is required");
|
|
19
|
+
const out = { ...(payload ?? {}) };
|
|
20
|
+
const raw = out.endpoint_id;
|
|
21
|
+
if (raw !== undefined && String(raw ?? "").trim() !== id) {
|
|
22
|
+
throw new Error("payload.endpoint_id must match endpointId");
|
|
23
|
+
}
|
|
24
|
+
out.endpoint_id = id;
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
async function requestGrant(url, init) {
|
|
28
|
+
const runFetch = resolveFetch(init.fetch);
|
|
29
|
+
const response = await runFetch(url, {
|
|
30
|
+
...init,
|
|
31
|
+
cache: "no-store",
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error(`Failed to get channel grant: ${response.status}`);
|
|
35
|
+
}
|
|
36
|
+
const data = (await response.json());
|
|
37
|
+
if (!data?.grant_client) {
|
|
38
|
+
throw new Error("Invalid controlplane response: missing `grant_client`");
|
|
39
|
+
}
|
|
40
|
+
return assertChannelInitGrant(data.grant_client);
|
|
41
|
+
}
|
|
42
|
+
export async function requestChannelGrant(config) {
|
|
43
|
+
const headers = new Headers(config.headers);
|
|
44
|
+
if (!headers.has("Content-Type")) {
|
|
45
|
+
headers.set("Content-Type", "application/json");
|
|
46
|
+
}
|
|
47
|
+
return await requestGrant(buildURL(config.baseUrl, "/v1/channel/init"), {
|
|
48
|
+
...(config.fetch === undefined ? {} : { fetch: config.fetch }),
|
|
49
|
+
method: "POST",
|
|
50
|
+
credentials: config.credentials ?? "omit",
|
|
51
|
+
headers,
|
|
52
|
+
body: JSON.stringify(buildPayload(config.endpointId, config.payload)),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
export async function requestEntryChannelGrant(config) {
|
|
56
|
+
const entryTicket = String(config.entryTicket ?? "").trim();
|
|
57
|
+
if (entryTicket === "")
|
|
58
|
+
throw new Error("entryTicket is required");
|
|
59
|
+
const endpointId = String(config.endpointId ?? "").trim();
|
|
60
|
+
const headers = new Headers(config.headers);
|
|
61
|
+
headers.set("Authorization", `Bearer ${entryTicket}`);
|
|
62
|
+
if (!headers.has("Content-Type")) {
|
|
63
|
+
headers.set("Content-Type", "application/json");
|
|
64
|
+
}
|
|
65
|
+
return await requestGrant(buildURL(config.baseUrl, `/v1/channel/init/entry?endpoint_id=${encodeURIComponent(endpointId)}`), {
|
|
66
|
+
...(config.fetch === undefined ? {} : { fetch: config.fetch }),
|
|
67
|
+
method: "POST",
|
|
68
|
+
credentials: config.credentials ?? "omit",
|
|
69
|
+
headers,
|
|
70
|
+
body: JSON.stringify(buildPayload(endpointId, config.payload)),
|
|
71
|
+
});
|
|
72
|
+
}
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
export type { ConnectBrowserOptions, DirectConnectBrowserOptions, TunnelConnectBrowserOptions } from "./connect.js";
|
|
2
2
|
export { connectBrowser, connectDirectBrowser, connectTunnelBrowser } from "./connect.js";
|
|
3
|
+
export type { ControlplaneConfig, EntryControlplaneConfig } from "./controlplane.js";
|
|
4
|
+
export { requestChannelGrant, requestEntryChannelGrant } from "./controlplane.js";
|
|
5
|
+
export type { BrowserReconnectConfig, DirectBrowserReconnectConfig, TunnelBrowserReconnectConfig, } from "./reconnectConfig.js";
|
|
6
|
+
export { createBrowserReconnectConfig, createDirectBrowserReconnectConfig, createTunnelBrowserReconnectConfig, } from "./reconnectConfig.js";
|
package/dist/browser/index.js
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
1
|
export { connectBrowser, connectDirectBrowser, connectTunnelBrowser } from "./connect.js";
|
|
2
|
+
export { requestChannelGrant, requestEntryChannelGrant } from "./controlplane.js";
|
|
3
|
+
export { createBrowserReconnectConfig, createDirectBrowserReconnectConfig, createTunnelBrowserReconnectConfig, } from "./reconnectConfig.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ChannelInitGrant } from "../gen/flowersec/controlplane/v1.gen.js";
|
|
2
|
+
import type { DirectConnectInfo } from "../gen/flowersec/direct/v1.gen.js";
|
|
3
|
+
import type { ClientObserverLike } from "../observability/observer.js";
|
|
4
|
+
import type { AutoReconnectConfig, ConnectConfig as ReconnectConnectConfig } from "../reconnect/index.js";
|
|
5
|
+
import type { DirectConnectBrowserOptions, TunnelConnectBrowserOptions } from "./connect.js";
|
|
6
|
+
import { type ControlplaneConfig } from "./controlplane.js";
|
|
7
|
+
type SharedReconnectOptions = Readonly<{
|
|
8
|
+
observer?: ClientObserverLike;
|
|
9
|
+
autoReconnect?: AutoReconnectConfig;
|
|
10
|
+
}>;
|
|
11
|
+
type TunnelReconnectConnectOptions = Omit<TunnelConnectBrowserOptions, "observer" | "signal">;
|
|
12
|
+
type DirectReconnectConnectOptions = Omit<DirectConnectBrowserOptions, "observer" | "signal">;
|
|
13
|
+
export type TunnelBrowserReconnectConfig = SharedReconnectOptions & Readonly<{
|
|
14
|
+
mode?: "tunnel";
|
|
15
|
+
connect?: TunnelReconnectConnectOptions;
|
|
16
|
+
grant?: ChannelInitGrant;
|
|
17
|
+
getGrant?: () => Promise<ChannelInitGrant>;
|
|
18
|
+
controlplane?: ControlplaneConfig;
|
|
19
|
+
}>;
|
|
20
|
+
export type DirectBrowserReconnectConfig = SharedReconnectOptions & Readonly<{
|
|
21
|
+
mode: "direct";
|
|
22
|
+
connect?: DirectReconnectConnectOptions;
|
|
23
|
+
directInfo?: DirectConnectInfo;
|
|
24
|
+
getDirectInfo?: () => Promise<DirectConnectInfo>;
|
|
25
|
+
}>;
|
|
26
|
+
export type BrowserReconnectConfig = TunnelBrowserReconnectConfig | DirectBrowserReconnectConfig;
|
|
27
|
+
export declare function createTunnelBrowserReconnectConfig(config: TunnelBrowserReconnectConfig): ReconnectConnectConfig;
|
|
28
|
+
export declare function createDirectBrowserReconnectConfig(config: DirectBrowserReconnectConfig): ReconnectConnectConfig;
|
|
29
|
+
export declare function createBrowserReconnectConfig(config: BrowserReconnectConfig): ReconnectConnectConfig;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { connectDirectBrowser, connectTunnelBrowser } from "./connect.js";
|
|
2
|
+
import { requestChannelGrant } from "./controlplane.js";
|
|
3
|
+
async function resolveTunnelGrant(config) {
|
|
4
|
+
if (config.getGrant)
|
|
5
|
+
return await config.getGrant();
|
|
6
|
+
if (config.grant)
|
|
7
|
+
return config.grant;
|
|
8
|
+
if (config.controlplane)
|
|
9
|
+
return await requestChannelGrant(config.controlplane);
|
|
10
|
+
throw new Error("Tunnel reconnect config requires `getGrant`, `grant`, or `controlplane`");
|
|
11
|
+
}
|
|
12
|
+
async function resolveDirectInfo(config) {
|
|
13
|
+
if (config.getDirectInfo)
|
|
14
|
+
return await config.getDirectInfo();
|
|
15
|
+
if (config.directInfo)
|
|
16
|
+
return config.directInfo;
|
|
17
|
+
throw new Error("Direct reconnect config requires `getDirectInfo` or `directInfo`");
|
|
18
|
+
}
|
|
19
|
+
export function createTunnelBrowserReconnectConfig(config) {
|
|
20
|
+
return {
|
|
21
|
+
...(config.observer === undefined ? {} : { observer: config.observer }),
|
|
22
|
+
...(config.autoReconnect === undefined ? {} : { autoReconnect: config.autoReconnect }),
|
|
23
|
+
connectOnce: async ({ signal, observer }) => await connectTunnelBrowser(await resolveTunnelGrant(config), {
|
|
24
|
+
...(config.connect ?? {}),
|
|
25
|
+
signal,
|
|
26
|
+
observer,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function createDirectBrowserReconnectConfig(config) {
|
|
31
|
+
return {
|
|
32
|
+
...(config.observer === undefined ? {} : { observer: config.observer }),
|
|
33
|
+
...(config.autoReconnect === undefined ? {} : { autoReconnect: config.autoReconnect }),
|
|
34
|
+
connectOnce: async ({ signal, observer }) => await connectDirectBrowser(await resolveDirectInfo(config), {
|
|
35
|
+
...(config.connect ?? {}),
|
|
36
|
+
signal,
|
|
37
|
+
observer,
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function createBrowserReconnectConfig(config) {
|
|
42
|
+
if (config.mode === "direct") {
|
|
43
|
+
return createDirectBrowserReconnectConfig(config);
|
|
44
|
+
}
|
|
45
|
+
return createTunnelBrowserReconnectConfig(config);
|
|
46
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { YamuxStream } from "../yamux/stream.js";
|
|
2
2
|
import type { ProxyRuntimeLimits } from "./runtime.js";
|
|
3
|
+
import { type ServiceWorkerControllerGuardConflictPolicy, type ServiceWorkerControllerGuardMonitorOptions, type ServiceWorkerControllerGuardRepairOptions } from "./controllerGuard.js";
|
|
3
4
|
export type RegisterProxyAppWindowOptions = Readonly<{
|
|
4
5
|
controllerOrigin: string;
|
|
5
6
|
controllerWindow?: Window | null;
|
|
@@ -19,4 +20,16 @@ export type ProxyAppWindowHandle = Readonly<{
|
|
|
19
20
|
}>;
|
|
20
21
|
dispose: () => void;
|
|
21
22
|
}>;
|
|
23
|
+
export type ProxyAppServiceWorkerControlOptions = Readonly<{
|
|
24
|
+
scriptUrl: string;
|
|
25
|
+
scope?: string;
|
|
26
|
+
expectedScriptPathSuffix: string;
|
|
27
|
+
repair?: ServiceWorkerControllerGuardRepairOptions;
|
|
28
|
+
monitor?: ServiceWorkerControllerGuardMonitorOptions;
|
|
29
|
+
conflicts?: ServiceWorkerControllerGuardConflictPolicy;
|
|
30
|
+
}>;
|
|
31
|
+
export type RegisterProxyAppWindowWithServiceWorkerControlOptions = RegisterProxyAppWindowOptions & Readonly<{
|
|
32
|
+
serviceWorker: ProxyAppServiceWorkerControlOptions;
|
|
33
|
+
}>;
|
|
22
34
|
export declare function registerProxyAppWindow(opts: RegisterProxyAppWindowOptions): ProxyAppWindowHandle;
|
|
35
|
+
export declare function registerProxyAppWindowWithServiceWorkerControl(opts: RegisterProxyAppWindowWithServiceWorkerControlOptions): Promise<ProxyAppWindowHandle>;
|
package/dist/proxy/appWindow.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createServiceWorkerControllerGuard, } from "./controllerGuard.js";
|
|
1
2
|
import { createMessagePortBackedStream } from "./portStream.js";
|
|
2
3
|
import { PROXY_WINDOW_FETCH_FORWARD_MSG_TYPE, PROXY_WINDOW_FETCH_MSG_TYPE, PROXY_WINDOW_WS_ERROR_MSG_TYPE, PROXY_WINDOW_WS_OPEN_ACK_MSG_TYPE, PROXY_WINDOW_WS_OPEN_MSG_TYPE, } from "./windowBridgeProtocol.js";
|
|
3
4
|
function resolveTargetWindow(raw) {
|
|
@@ -131,3 +132,27 @@ export function registerProxyAppWindow(opts) {
|
|
|
131
132
|
},
|
|
132
133
|
};
|
|
133
134
|
}
|
|
135
|
+
export async function registerProxyAppWindowWithServiceWorkerControl(opts) {
|
|
136
|
+
const targetWindow = resolveTargetWindow(opts.targetWindow);
|
|
137
|
+
const sw = targetWindow.navigator?.serviceWorker;
|
|
138
|
+
if (sw == null || typeof sw.register !== "function") {
|
|
139
|
+
throw new Error("serviceWorker is not available");
|
|
140
|
+
}
|
|
141
|
+
await sw.register(opts.serviceWorker.scriptUrl, {
|
|
142
|
+
...(opts.serviceWorker.scope === undefined ? {} : { scope: opts.serviceWorker.scope }),
|
|
143
|
+
});
|
|
144
|
+
const guard = createServiceWorkerControllerGuard({
|
|
145
|
+
targetWindow,
|
|
146
|
+
expectedScriptPathSuffix: opts.serviceWorker.expectedScriptPathSuffix,
|
|
147
|
+
...(opts.serviceWorker.repair === undefined ? {} : { repair: opts.serviceWorker.repair }),
|
|
148
|
+
...(opts.serviceWorker.monitor === undefined ? {} : { monitor: opts.serviceWorker.monitor }),
|
|
149
|
+
...(opts.serviceWorker.conflicts === undefined ? {} : { conflicts: opts.serviceWorker.conflicts }),
|
|
150
|
+
});
|
|
151
|
+
try {
|
|
152
|
+
await guard.ensure();
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
guard.dispose();
|
|
156
|
+
}
|
|
157
|
+
return registerProxyAppWindow(opts);
|
|
158
|
+
}
|
|
@@ -2,8 +2,9 @@ import type { Client } from "../client.js";
|
|
|
2
2
|
import type { TunnelConnectBrowserOptions } from "../browser/connect.js";
|
|
3
3
|
import type { ChannelInitGrant } from "../gen/flowersec/controlplane/v1.gen.js";
|
|
4
4
|
import { type ProxyIntegrationPlugin, type ProxyIntegrationServiceWorkerOptions, type RegisterProxyIntegrationOptions } from "./integration.js";
|
|
5
|
+
import { type RegisterProxyControllerWindowOptions } from "./controllerWindow.js";
|
|
5
6
|
import type { ProxyProfile, ProxyProfileName } from "./profiles.js";
|
|
6
|
-
import type
|
|
7
|
+
import { type ProxyRuntime } from "./runtime.js";
|
|
7
8
|
export type ConnectTunnelProxyBrowserOptions = Readonly<{
|
|
8
9
|
connect?: TunnelConnectBrowserOptions;
|
|
9
10
|
profile?: ProxyProfileName | Partial<ProxyProfile>;
|
|
@@ -17,4 +18,17 @@ export type ConnectTunnelProxyBrowserHandle = Readonly<{
|
|
|
17
18
|
runtime: ProxyRuntime;
|
|
18
19
|
dispose: () => Promise<void>;
|
|
19
20
|
}>;
|
|
21
|
+
export type ConnectTunnelProxyControllerBrowserOptions = Readonly<{
|
|
22
|
+
connect?: TunnelConnectBrowserOptions;
|
|
23
|
+
runtime?: RegisterProxyIntegrationOptions["runtime"];
|
|
24
|
+
allowedOrigins: RegisterProxyControllerWindowOptions["allowedOrigins"];
|
|
25
|
+
targetWindow?: RegisterProxyControllerWindowOptions["targetWindow"];
|
|
26
|
+
expectedSource?: RegisterProxyControllerWindowOptions["expectedSource"];
|
|
27
|
+
}>;
|
|
28
|
+
export type ConnectTunnelProxyControllerBrowserHandle = Readonly<{
|
|
29
|
+
client: Client;
|
|
30
|
+
runtime: ProxyRuntime;
|
|
31
|
+
dispose: () => void;
|
|
32
|
+
}>;
|
|
20
33
|
export declare function connectTunnelProxyBrowser(grant: ChannelInitGrant, opts: ConnectTunnelProxyBrowserOptions): Promise<ConnectTunnelProxyBrowserHandle>;
|
|
34
|
+
export declare function connectTunnelProxyControllerBrowser(grant: ChannelInitGrant, opts: ConnectTunnelProxyControllerBrowserOptions): Promise<ConnectTunnelProxyControllerBrowserHandle>;
|
package/dist/proxy/bootstrap.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { connectTunnelBrowser } from "../browser/connect.js";
|
|
2
2
|
import { registerProxyIntegration, } from "./integration.js";
|
|
3
|
+
import { registerProxyControllerWindow } from "./controllerWindow.js";
|
|
4
|
+
import { createProxyRuntime } from "./runtime.js";
|
|
3
5
|
export async function connectTunnelProxyBrowser(grant, opts) {
|
|
4
6
|
const client = await connectTunnelBrowser(grant, opts.connect ?? {});
|
|
5
7
|
const integrationInput = {
|
|
@@ -44,3 +46,37 @@ export async function connectTunnelProxyBrowser(grant, opts) {
|
|
|
44
46
|
},
|
|
45
47
|
};
|
|
46
48
|
}
|
|
49
|
+
export async function connectTunnelProxyControllerBrowser(grant, opts) {
|
|
50
|
+
const client = await connectTunnelBrowser(grant, opts.connect ?? {});
|
|
51
|
+
let runtime = null;
|
|
52
|
+
let controller = null;
|
|
53
|
+
try {
|
|
54
|
+
runtime = createProxyRuntime({
|
|
55
|
+
client,
|
|
56
|
+
...(opts.runtime ?? {}),
|
|
57
|
+
});
|
|
58
|
+
controller = registerProxyControllerWindow({
|
|
59
|
+
runtime,
|
|
60
|
+
allowedOrigins: opts.allowedOrigins,
|
|
61
|
+
...(opts.targetWindow === undefined ? {} : { targetWindow: opts.targetWindow }),
|
|
62
|
+
...(opts.expectedSource === undefined ? {} : { expectedSource: opts.expectedSource }),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
try {
|
|
67
|
+
client.close();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Best effort cleanup.
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
client,
|
|
76
|
+
runtime,
|
|
77
|
+
dispose: () => {
|
|
78
|
+
controller?.dispose();
|
|
79
|
+
client.close();
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|