@floegence/flowersec-core 0.17.2 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +34 -9
  2. package/dist/browser/controlplane.d.ts +5 -18
  3. package/dist/browser/controlplane.js +17 -67
  4. package/dist/browser/index.d.ts +4 -2
  5. package/dist/browser/index.js +2 -1
  6. package/dist/browser/reconnectConfig.d.ts +15 -2
  7. package/dist/browser/reconnectConfig.js +43 -12
  8. package/dist/client-connect/connectCore.d.ts +5 -0
  9. package/dist/client-connect/connectCore.js +1 -1
  10. package/dist/client-connect/tunnelAttachCloseReason.d.ts +1 -1
  11. package/dist/client-connect/tunnelAttachCloseReason.js +4 -1
  12. package/dist/connect/artifact.d.ts +37 -0
  13. package/dist/connect/artifact.js +217 -0
  14. package/dist/connect/internalNormalize.d.ts +22 -0
  15. package/dist/connect/internalNormalize.js +173 -0
  16. package/dist/controlplane/index.d.ts +2 -0
  17. package/dist/controlplane/index.js +1 -0
  18. package/dist/controlplane/request.d.ts +50 -0
  19. package/dist/controlplane/request.js +136 -0
  20. package/dist/e2ee/secureChannel.js +2 -5
  21. package/dist/facade.d.ts +4 -0
  22. package/dist/facade.js +15 -47
  23. package/dist/node/connect.d.ts +3 -0
  24. package/dist/node/index.d.ts +4 -0
  25. package/dist/node/index.js +2 -0
  26. package/dist/node/reconnectConfig.d.ts +42 -0
  27. package/dist/node/reconnectConfig.js +78 -0
  28. package/dist/observability/observer.d.ts +28 -2
  29. package/dist/observability/observer.js +296 -13
  30. package/dist/proxy/bootstrap.d.ts +22 -4
  31. package/dist/proxy/bootstrap.js +51 -6
  32. package/dist/proxy/index.d.ts +3 -1
  33. package/dist/proxy/index.js +2 -1
  34. package/dist/proxy/integration.d.ts +3 -3
  35. package/dist/proxy/integration.js +21 -10
  36. package/dist/proxy/preset.d.ts +31 -0
  37. package/dist/proxy/preset.js +133 -0
  38. package/dist/proxy/profiles.d.ts +2 -0
  39. package/dist/proxy/profiles.js +40 -17
  40. package/dist/proxy/runtimeScope.d.ts +49 -0
  41. package/dist/proxy/runtimeScope.js +223 -0
  42. package/dist/reconnect/artifactControlplane.d.ts +13 -0
  43. package/dist/reconnect/artifactControlplane.js +34 -0
  44. package/dist/reconnect/index.d.ts +1 -1
  45. package/dist/reconnect/index.js +13 -6
  46. package/dist/utils/errors.d.ts +1 -1
  47. package/dist/utils/errors.js +3 -1
  48. package/package.json +7 -3
@@ -2,12 +2,33 @@ import type { ClientPath } from "../client.js";
2
2
  export type ConnectResult = "ok" | "fail";
3
3
  export type ConnectReason = "websocket_error" | "websocket_closed" | "timeout" | "canceled";
4
4
  export type AttachResult = "ok" | "fail";
5
- export type AttachReason = "send_failed" | "too_many_connections" | "expected_attach" | "invalid_attach" | "invalid_token" | "channel_mismatch" | "role_mismatch" | "init_exp_mismatch" | "idle_timeout_mismatch" | "token_replay" | "replace_rate_limited" | "attach_failed" | "timeout" | "canceled";
5
+ export type AttachReason = "send_failed" | "too_many_connections" | "expected_attach" | "invalid_attach" | "invalid_token" | "channel_mismatch" | "role_mismatch" | "init_exp_mismatch" | "idle_timeout_mismatch" | "token_replay" | "tenant_mismatch" | "policy_denied" | "policy_error" | "replace_rate_limited" | "attach_failed" | "timeout" | "canceled";
6
6
  export type HandshakeResult = "ok" | "fail";
7
7
  export type HandshakeReason = "auth_tag_mismatch" | "handshake_failed" | "invalid_suite" | "invalid_version" | "timestamp_after_init_exp" | "timestamp_out_of_skew" | "timeout" | "canceled";
8
8
  export type WsCloseKind = "local" | "peer_or_error";
9
9
  export type WsErrorReason = "error" | "recv_buffer_exceeded" | "unexpected_text_frame" | "unexpected_message_type";
10
10
  export type RpcCallResult = "ok" | "rpc_error" | "handler_not_found" | "transport_error" | "canceled";
11
+ export type DiagnosticEvent = Readonly<{
12
+ v: 1;
13
+ namespace: "connect";
14
+ path: ClientPath | "auto";
15
+ stage: "validate" | "normalize" | "scope" | "connect" | "attach" | "handshake" | "close" | "reconnect";
16
+ code_domain: "error" | "event";
17
+ code: string;
18
+ result: "ok" | "fail" | "retry" | "skip";
19
+ elapsed_ms: number;
20
+ attempt_seq: number;
21
+ trace_id?: string;
22
+ session_id?: string;
23
+ }>;
24
+ type ObserverContext = Readonly<{
25
+ path?: ClientPath | "auto";
26
+ traceId?: string;
27
+ sessionId?: string;
28
+ attemptSeq?: number;
29
+ attemptStartMs?: number;
30
+ maxQueuedItems?: number;
31
+ }>;
11
32
  export type ClientObserver = {
12
33
  onConnect(path: ClientPath, result: ConnectResult, reason: ConnectReason | undefined, elapsedSeconds: number): void;
13
34
  onAttach(result: AttachResult, reason: AttachReason | undefined): void;
@@ -16,8 +37,13 @@ export type ClientObserver = {
16
37
  onWsError(reason: WsErrorReason): void;
17
38
  onRpcCall(result: RpcCallResult, elapsedSeconds: number): void;
18
39
  onRpcNotify(): void;
40
+ onDiagnosticEvent(event: DiagnosticEvent): void;
19
41
  };
20
42
  export type ClientObserverLike = Partial<ClientObserver>;
43
+ type DiagnosticEventInput = Omit<DiagnosticEvent, "v" | "namespace" | "elapsed_ms" | "attempt_seq" | "trace_id" | "session_id" | "path"> & Partial<Pick<DiagnosticEvent, "path">>;
21
44
  export declare const NoopObserver: ClientObserver;
22
- export declare function normalizeObserver(observer?: ClientObserverLike): ClientObserver;
45
+ export declare function withObserverContext(observer: ClientObserverLike | undefined, context: ObserverContext): ClientObserverLike | undefined;
46
+ export declare function emitObserverDiagnostic(observer: ClientObserverLike | undefined, event: DiagnosticEventInput): void;
47
+ export declare function normalizeObserver(observer?: ClientObserverLike, context?: ObserverContext): ClientObserver;
23
48
  export declare function nowSeconds(): number;
49
+ export {};
@@ -1,3 +1,6 @@
1
+ const OBSERVER_CONTEXT = Symbol.for("floegence.flowersec.observer_context");
2
+ const NORMALIZED_OBSERVER = Symbol.for("floegence.flowersec.normalized_observer");
3
+ const DEFAULT_MAX_QUEUED_ITEMS = 64;
1
4
  export const NoopObserver = {
2
5
  onConnect: () => { },
3
6
  onAttach: () => { },
@@ -5,24 +8,304 @@ export const NoopObserver = {
5
8
  onWsClose: () => { },
6
9
  onWsError: () => { },
7
10
  onRpcCall: () => { },
8
- onRpcNotify: () => { }
11
+ onRpcNotify: () => { },
12
+ onDiagnosticEvent: () => { },
9
13
  };
10
- export function normalizeObserver(observer) {
14
+ function getObserverContext(observer) {
15
+ const context = observer?.[OBSERVER_CONTEXT];
16
+ if (context == null || typeof context !== "object")
17
+ return {};
18
+ return context;
19
+ }
20
+ function safeInvoke(fn) {
21
+ if (fn == null)
22
+ return;
23
+ try {
24
+ fn();
25
+ }
26
+ catch {
27
+ // Best effort only; observability must not affect connect semantics.
28
+ }
29
+ }
30
+ function hasAnyHandlers(observer) {
11
31
  if (observer == null)
12
- return NoopObserver;
32
+ return false;
33
+ return (observer.onConnect != null ||
34
+ observer.onAttach != null ||
35
+ observer.onHandshake != null ||
36
+ observer.onWsClose != null ||
37
+ observer.onWsError != null ||
38
+ observer.onRpcCall != null ||
39
+ observer.onRpcNotify != null ||
40
+ observer.onDiagnosticEvent != null);
41
+ }
42
+ function buildDiagnosticEvent(context, event) {
43
+ return Object.freeze({
44
+ v: 1,
45
+ namespace: "connect",
46
+ path: event.path ?? context.path ?? "auto",
47
+ stage: event.stage,
48
+ code_domain: event.code_domain,
49
+ code: event.code,
50
+ result: event.result,
51
+ elapsed_ms: Math.max(0, Math.floor(nowMilliseconds() - (context.attemptStartMs ?? nowMilliseconds()))),
52
+ attempt_seq: Math.max(1, Math.floor(context.attemptSeq ?? 1)),
53
+ ...(context.traceId === undefined ? {} : { trace_id: context.traceId }),
54
+ ...(context.sessionId === undefined
55
+ ? {}
56
+ : { session_id: context.sessionId }),
57
+ });
58
+ }
59
+ function mapConnectDiagnostic(path, result, reason) {
60
+ if (result === "ok") {
61
+ return {
62
+ path,
63
+ stage: "connect",
64
+ code_domain: "event",
65
+ code: "connect_ok",
66
+ result: "ok",
67
+ };
68
+ }
69
+ return {
70
+ path,
71
+ stage: "connect",
72
+ code_domain: "error",
73
+ code: reason === "timeout" || reason === "canceled" ? reason : "dial_failed",
74
+ result: "fail",
75
+ };
76
+ }
77
+ function mapAttachDiagnostic(result, reason, path) {
78
+ if (result === "ok") {
79
+ return {
80
+ path,
81
+ stage: "attach",
82
+ code_domain: "event",
83
+ code: "attach_ok",
84
+ result: "ok",
85
+ };
86
+ }
13
87
  return {
14
- onConnect: observer.onConnect ?? NoopObserver.onConnect,
15
- onAttach: observer.onAttach ?? NoopObserver.onAttach,
16
- onHandshake: observer.onHandshake ?? NoopObserver.onHandshake,
17
- onWsClose: observer.onWsClose ?? NoopObserver.onWsClose,
18
- onWsError: observer.onWsError ?? NoopObserver.onWsError,
19
- onRpcCall: observer.onRpcCall ?? NoopObserver.onRpcCall,
20
- onRpcNotify: observer.onRpcNotify ?? NoopObserver.onRpcNotify
88
+ path,
89
+ stage: "attach",
90
+ code_domain: "error",
91
+ code: reason === "send_failed" ? "attach_failed" : (reason ?? "attach_failed"),
92
+ result: "fail",
21
93
  };
22
94
  }
95
+ function mapHandshakeDiagnostic(path, result, reason) {
96
+ if (result === "ok") {
97
+ return {
98
+ path,
99
+ stage: "handshake",
100
+ code_domain: "event",
101
+ code: "handshake_ok",
102
+ result: "ok",
103
+ };
104
+ }
105
+ return {
106
+ path,
107
+ stage: "handshake",
108
+ code_domain: "error",
109
+ code: reason ?? "handshake_failed",
110
+ result: "fail",
111
+ };
112
+ }
113
+ function mapWsCloseDiagnostic(kind, path) {
114
+ return {
115
+ path,
116
+ stage: "close",
117
+ code_domain: "event",
118
+ code: kind === "local" ? "ws_close_local" : "ws_close_peer_or_error",
119
+ result: "skip",
120
+ };
121
+ }
122
+ function mapWsErrorDiagnostic(path) {
123
+ return {
124
+ path,
125
+ stage: "close",
126
+ code_domain: "event",
127
+ code: "ws_error",
128
+ result: "skip",
129
+ };
130
+ }
131
+ class ObserverDispatcher {
132
+ [NORMALIZED_OBSERVER] = true;
133
+ [OBSERVER_CONTEXT];
134
+ queue = [];
135
+ observer;
136
+ maxQueuedItems;
137
+ draining = false;
138
+ overflowQueued = false;
139
+ constructor(observer, context) {
140
+ this.observer = observer;
141
+ this[OBSERVER_CONTEXT] = context;
142
+ this.maxQueuedItems = Math.max(4, Math.floor(context.maxQueuedItems ?? DEFAULT_MAX_QUEUED_ITEMS));
143
+ }
144
+ onConnect(path, result, reason, elapsedSeconds) {
145
+ const diagnostic = mapConnectDiagnostic(path, result, reason);
146
+ this.enqueueCombined(() => this.observer.onConnect?.(path, result, reason, elapsedSeconds), diagnostic, result === "fail");
147
+ }
148
+ onAttach(result, reason) {
149
+ const diagnostic = mapAttachDiagnostic(result, reason, this[OBSERVER_CONTEXT].path ?? "auto");
150
+ this.enqueueCombined(() => this.observer.onAttach?.(result, reason), diagnostic, result === "fail");
151
+ }
152
+ onHandshake(path, result, reason, elapsedSeconds) {
153
+ const diagnostic = mapHandshakeDiagnostic(path, result, reason);
154
+ this.enqueueCombined(() => this.observer.onHandshake?.(path, result, reason, elapsedSeconds), diagnostic, result === "fail");
155
+ }
156
+ onWsClose(kind, code) {
157
+ const diagnostic = mapWsCloseDiagnostic(kind, this[OBSERVER_CONTEXT].path ?? "auto");
158
+ this.enqueueCombined(() => this.observer.onWsClose?.(kind, code), diagnostic, false);
159
+ }
160
+ onWsError(reason) {
161
+ const diagnostic = mapWsErrorDiagnostic(this[OBSERVER_CONTEXT].path ?? "auto");
162
+ this.enqueueCombined(() => this.observer.onWsError?.(reason), diagnostic, false);
163
+ }
164
+ onRpcCall(result, elapsedSeconds) {
165
+ this.enqueueCombined(() => this.observer.onRpcCall?.(result, elapsedSeconds), undefined, false);
166
+ }
167
+ onRpcNotify() {
168
+ this.enqueueCombined(() => this.observer.onRpcNotify?.(), undefined, false);
169
+ }
170
+ onDiagnosticEvent(event) {
171
+ this.enqueueCombined(() => this.observer.onDiagnosticEvent?.(event), undefined, event.result === "fail");
172
+ }
173
+ emitDiagnosticEvent(event) {
174
+ const diagnostic = buildDiagnosticEvent(this[OBSERVER_CONTEXT], event);
175
+ this.enqueue({
176
+ kind: event.code === "diagnostics_overflow"
177
+ ? "overflow"
178
+ : event.result === "fail"
179
+ ? "terminal"
180
+ : "normal",
181
+ deliver: () => this.observer.onDiagnosticEvent?.(diagnostic),
182
+ });
183
+ }
184
+ enqueueCombined(callback, diagnostic, terminal) {
185
+ if (callback == null && diagnostic == null)
186
+ return;
187
+ const event = diagnostic == null
188
+ ? undefined
189
+ : buildDiagnosticEvent(this[OBSERVER_CONTEXT], diagnostic);
190
+ this.enqueue({
191
+ kind: terminal ? "terminal" : "normal",
192
+ deliver: () => {
193
+ safeInvoke(callback);
194
+ if (event != null) {
195
+ safeInvoke(() => this.observer.onDiagnosticEvent?.(event));
196
+ }
197
+ },
198
+ });
199
+ }
200
+ enqueue(item) {
201
+ if (this.queue.length >= this.maxQueuedItems) {
202
+ if (!this.makeRoom(item.kind)) {
203
+ return;
204
+ }
205
+ }
206
+ if (item.kind === "overflow") {
207
+ if (this.overflowQueued)
208
+ return;
209
+ this.overflowQueued = true;
210
+ }
211
+ this.queue.push(item);
212
+ this.scheduleDrain();
213
+ }
214
+ makeRoom(kind) {
215
+ const removableIndex = this.queue.findIndex((entry) => entry.kind === "normal");
216
+ if (removableIndex >= 0) {
217
+ this.queue.splice(removableIndex, 1);
218
+ if (kind === "normal") {
219
+ this.queueOverflowEvent();
220
+ }
221
+ return true;
222
+ }
223
+ if (kind === "normal") {
224
+ return false;
225
+ }
226
+ if (this.queue.length > 0) {
227
+ const shifted = this.queue.shift();
228
+ if (shifted?.kind === "overflow")
229
+ this.overflowQueued = false;
230
+ return true;
231
+ }
232
+ return true;
233
+ }
234
+ queueOverflowEvent() {
235
+ if (this.overflowQueued || this.observer.onDiagnosticEvent == null)
236
+ return;
237
+ this.enqueue({
238
+ kind: "overflow",
239
+ deliver: () => {
240
+ this.overflowQueued = false;
241
+ safeInvoke(() => this.observer.onDiagnosticEvent?.(buildDiagnosticEvent(this[OBSERVER_CONTEXT], {
242
+ stage: "reconnect",
243
+ code_domain: "event",
244
+ code: "diagnostics_overflow",
245
+ result: "skip",
246
+ })));
247
+ },
248
+ });
249
+ }
250
+ scheduleDrain() {
251
+ if (this.draining)
252
+ return;
253
+ this.draining = true;
254
+ queueMicrotask(() => this.drainOne());
255
+ }
256
+ drainOne() {
257
+ this.draining = false;
258
+ const item = this.queue.shift();
259
+ if (item == null)
260
+ return;
261
+ if (item.kind === "overflow") {
262
+ this.overflowQueued = false;
263
+ }
264
+ safeInvoke(item.deliver);
265
+ if (this.queue.length > 0) {
266
+ this.scheduleDrain();
267
+ }
268
+ }
269
+ }
270
+ export function withObserverContext(observer, context) {
271
+ if (observer == null && Object.keys(context).length === 0)
272
+ return observer;
273
+ const previous = getObserverContext(observer);
274
+ return Object.assign({}, observer ?? {}, {
275
+ [OBSERVER_CONTEXT]: {
276
+ ...previous,
277
+ ...context,
278
+ },
279
+ });
280
+ }
281
+ export function emitObserverDiagnostic(observer, event) {
282
+ const normalized = normalizeObserver(observer);
283
+ if (normalized.emitDiagnosticEvent) {
284
+ normalized.emitDiagnosticEvent(event);
285
+ return;
286
+ }
287
+ safeInvoke(() => normalized.onDiagnosticEvent(buildDiagnosticEvent(getObserverContext(observer), event)));
288
+ }
289
+ export function normalizeObserver(observer, context = {}) {
290
+ if (observer?.[NORMALIZED_OBSERVER] === true) {
291
+ return observer;
292
+ }
293
+ if (!hasAnyHandlers(observer))
294
+ return NoopObserver;
295
+ return new ObserverDispatcher(observer ?? {}, {
296
+ attemptSeq: 1,
297
+ attemptStartMs: nowMilliseconds(),
298
+ ...getObserverContext(observer),
299
+ ...context,
300
+ });
301
+ }
23
302
  export function nowSeconds() {
24
- if (typeof performance !== "undefined" && typeof performance.now === "function") {
25
- return performance.now() / 1000;
303
+ return nowMilliseconds() / 1000;
304
+ }
305
+ function nowMilliseconds() {
306
+ if (typeof performance !== "undefined" &&
307
+ typeof performance.now === "function") {
308
+ return performance.now();
26
309
  }
27
- return Date.now() / 1000;
310
+ return Date.now();
28
311
  }
@@ -1,13 +1,14 @@
1
1
  import type { Client } from "../client.js";
2
- import type { TunnelConnectBrowserOptions } from "../browser/connect.js";
2
+ import type { ConnectArtifact } from "../connect/artifact.js";
3
+ import type { ConnectBrowserOptions, TunnelConnectBrowserOptions } from "../browser/connect.js";
3
4
  import type { ChannelInitGrant } from "../gen/flowersec/controlplane/v1.gen.js";
4
- import { type ProxyIntegrationPlugin, type ProxyIntegrationServiceWorkerOptions, type RegisterProxyIntegrationOptions } from "./integration.js";
5
+ import { type ProxyIntegrationPlugin, type RegisterProxyIntegrationOptions, type ProxyIntegrationServiceWorkerOptions } from "./integration.js";
5
6
  import { type RegisterProxyControllerWindowOptions } from "./controllerWindow.js";
6
- import type { ProxyProfile, ProxyProfileName } from "./profiles.js";
7
+ import type { ProxyPresetInput } from "./preset.js";
7
8
  import { type ProxyRuntime } from "./runtime.js";
8
9
  export type ConnectTunnelProxyBrowserOptions = Readonly<{
9
10
  connect?: TunnelConnectBrowserOptions;
10
- profile?: ProxyProfileName | Partial<ProxyProfile>;
11
+ preset?: ProxyPresetInput;
11
12
  runtimeGlobalKey?: string;
12
13
  runtime?: RegisterProxyIntegrationOptions["runtime"];
13
14
  serviceWorker: ProxyIntegrationServiceWorkerOptions;
@@ -18,6 +19,14 @@ export type ConnectTunnelProxyBrowserHandle = Readonly<{
18
19
  runtime: ProxyRuntime;
19
20
  dispose: () => Promise<void>;
20
21
  }>;
22
+ export type ConnectArtifactProxyBrowserOptions = Readonly<{
23
+ connect?: ConnectBrowserOptions;
24
+ preset?: ProxyPresetInput;
25
+ runtimeGlobalKey?: string;
26
+ runtime?: RegisterProxyIntegrationOptions["runtime"];
27
+ serviceWorker?: ProxyIntegrationServiceWorkerOptions;
28
+ plugins?: readonly ProxyIntegrationPlugin[];
29
+ }>;
21
30
  export type ConnectTunnelProxyControllerBrowserOptions = Readonly<{
22
31
  connect?: TunnelConnectBrowserOptions;
23
32
  runtime?: RegisterProxyIntegrationOptions["runtime"];
@@ -30,5 +39,14 @@ export type ConnectTunnelProxyControllerBrowserHandle = Readonly<{
30
39
  runtime: ProxyRuntime;
31
40
  dispose: () => void;
32
41
  }>;
42
+ export type ConnectArtifactProxyControllerBrowserOptions = Readonly<{
43
+ connect?: ConnectBrowserOptions;
44
+ runtime?: RegisterProxyIntegrationOptions["runtime"];
45
+ allowedOrigins?: RegisterProxyControllerWindowOptions["allowedOrigins"];
46
+ targetWindow?: RegisterProxyControllerWindowOptions["targetWindow"];
47
+ expectedSource?: RegisterProxyControllerWindowOptions["expectedSource"];
48
+ }>;
33
49
  export declare function connectTunnelProxyBrowser(grant: ChannelInitGrant, opts: ConnectTunnelProxyBrowserOptions): Promise<ConnectTunnelProxyBrowserHandle>;
50
+ export declare function connectArtifactProxyBrowser(artifact: ConnectArtifact, opts?: ConnectArtifactProxyBrowserOptions): Promise<ConnectTunnelProxyBrowserHandle>;
34
51
  export declare function connectTunnelProxyControllerBrowser(grant: ChannelInitGrant, opts: ConnectTunnelProxyControllerBrowserOptions): Promise<ConnectTunnelProxyControllerBrowserHandle>;
52
+ export declare function connectArtifactProxyControllerBrowser(artifact: ConnectArtifact, opts?: ConnectArtifactProxyControllerBrowserOptions): Promise<ConnectTunnelProxyControllerBrowserHandle>;
@@ -1,13 +1,34 @@
1
- import { connectTunnelBrowser } from "../browser/connect.js";
1
+ import { connectBrowser, connectTunnelBrowser } from "../browser/connect.js";
2
2
  import { registerProxyIntegration, } from "./integration.js";
3
3
  import { registerProxyControllerWindow } from "./controllerWindow.js";
4
+ import { extractProxyRuntimeScopeV1, resolvePresetInputFromScope, resolveRuntimeLimitsFromScope, resolveRuntimePresetLimits, } from "./runtimeScope.js";
4
5
  import { createProxyRuntime } from "./runtime.js";
5
- export async function connectTunnelProxyBrowser(grant, opts) {
6
- const client = await connectTunnelBrowser(grant, opts.connect ?? {});
6
+ function scopeRuntimeToIntegrationOptions(scope, opts) {
7
+ const runtimeLimits = resolveRuntimeLimitsFromScope(scope, opts.runtime);
8
+ const presetFromScope = resolvePresetInputFromScope(scope, opts.preset);
9
+ const presetFromLimits = presetFromScope ?? resolveRuntimePresetLimits(scope);
10
+ const serviceWorker = opts.serviceWorker ?? (scope.mode === "service_worker" ? {
11
+ scriptUrl: scope.serviceWorker.scriptUrl,
12
+ scope: scope.serviceWorker.scope,
13
+ } : undefined);
14
+ if (!serviceWorker) {
15
+ throw new Error("service worker config is required for proxy runtime mode");
16
+ }
17
+ return {
18
+ ...(presetFromLimits === undefined ? {} : { preset: presetFromLimits }),
19
+ ...(opts.runtimeGlobalKey === undefined ? {} : { runtimeGlobalKey: opts.runtimeGlobalKey }),
20
+ ...(runtimeLimits === undefined ? {} : { runtime: runtimeLimits }),
21
+ serviceWorker,
22
+ ...(opts.plugins === undefined ? {} : { plugins: opts.plugins }),
23
+ };
24
+ }
25
+ async function connectProxyBrowserClient(client, opts) {
26
+ const compat = opts;
7
27
  const integrationInput = {
8
28
  client,
9
29
  serviceWorker: opts.serviceWorker,
10
- ...(opts.profile === undefined ? {} : { profile: opts.profile }),
30
+ ...(opts.preset === undefined ? {} : { preset: opts.preset }),
31
+ ...(compat.profile === undefined ? {} : { profile: compat.profile }),
11
32
  ...(opts.runtimeGlobalKey === undefined ? {} : { runtimeGlobalKey: opts.runtimeGlobalKey }),
12
33
  ...(opts.runtime === undefined ? {} : { runtime: opts.runtime }),
13
34
  ...(opts.plugins === undefined ? {} : { plugins: opts.plugins }),
@@ -46,8 +67,7 @@ export async function connectTunnelProxyBrowser(grant, opts) {
46
67
  },
47
68
  };
48
69
  }
49
- export async function connectTunnelProxyControllerBrowser(grant, opts) {
50
- const client = await connectTunnelBrowser(grant, opts.connect ?? {});
70
+ function connectProxyControllerClient(client, opts) {
51
71
  let runtime = null;
52
72
  let controller = null;
53
73
  try {
@@ -80,3 +100,28 @@ export async function connectTunnelProxyControllerBrowser(grant, opts) {
80
100
  },
81
101
  };
82
102
  }
103
+ export async function connectTunnelProxyBrowser(grant, opts) {
104
+ const client = await connectTunnelBrowser(grant, opts.connect ?? {});
105
+ return await connectProxyBrowserClient(client, opts);
106
+ }
107
+ export async function connectArtifactProxyBrowser(artifact, opts = {}) {
108
+ const scope = extractProxyRuntimeScopeV1(artifact, "service_worker");
109
+ const client = await connectBrowser(artifact, opts.connect ?? {});
110
+ const nextOpts = scopeRuntimeToIntegrationOptions(scope, opts);
111
+ return await connectProxyBrowserClient(client, nextOpts);
112
+ }
113
+ export async function connectTunnelProxyControllerBrowser(grant, opts) {
114
+ const client = await connectTunnelBrowser(grant, opts.connect ?? {});
115
+ return connectProxyControllerClient(client, opts);
116
+ }
117
+ export async function connectArtifactProxyControllerBrowser(artifact, opts = {}) {
118
+ const scope = extractProxyRuntimeScopeV1(artifact, "controller_bridge");
119
+ const client = await connectBrowser(artifact, opts.connect ?? {});
120
+ const runtime = resolveRuntimeLimitsFromScope(scope, opts.runtime);
121
+ return connectProxyControllerClient(client, {
122
+ ...(runtime === undefined ? {} : { runtime }),
123
+ allowedOrigins: opts.allowedOrigins ?? scope.controllerBridge.allowedOrigins,
124
+ ...(opts.targetWindow === undefined ? {} : { targetWindow: opts.targetWindow }),
125
+ ...(opts.expectedSource === undefined ? {} : { expectedSource: opts.expectedSource }),
126
+ });
127
+ }
@@ -1,13 +1,15 @@
1
1
  export * from "./constants.js";
2
2
  export * from "./types.js";
3
3
  export * from "./cookieJar.js";
4
- export * from "./profiles.js";
4
+ export type { ProxyPresetInput, ProxyPresetLimits, ProxyPresetManifest, ResolvedProxyPreset } from "./preset.js";
5
+ export { DEFAULT_PROXY_PRESET_MANIFEST, assertProxyPresetManifest, resolveProxyPreset } from "./preset.js";
5
6
  export * from "./runtime.js";
6
7
  export * from "./serviceWorker.js";
7
8
  export { registerServiceWorkerAndEnsureControl } from "./registerServiceWorker.js";
8
9
  export * from "./integration.js";
9
10
  export * from "./controllerGuard.js";
10
11
  export * from "./bootstrap.js";
12
+ export * from "./runtimeScope.js";
11
13
  export * from "./controllerWindow.js";
12
14
  export * from "./appWindow.js";
13
15
  export * from "./wsPatch.js";
@@ -1,13 +1,14 @@
1
1
  export * from "./constants.js";
2
2
  export * from "./types.js";
3
3
  export * from "./cookieJar.js";
4
- export * from "./profiles.js";
4
+ export { DEFAULT_PROXY_PRESET_MANIFEST, assertProxyPresetManifest, resolveProxyPreset } from "./preset.js";
5
5
  export * from "./runtime.js";
6
6
  export * from "./serviceWorker.js";
7
7
  export { registerServiceWorkerAndEnsureControl } from "./registerServiceWorker.js";
8
8
  export * from "./integration.js";
9
9
  export * from "./controllerGuard.js";
10
10
  export * from "./bootstrap.js";
11
+ export * from "./runtimeScope.js";
11
12
  export * from "./controllerWindow.js";
12
13
  export * from "./appWindow.js";
13
14
  export * from "./wsPatch.js";
@@ -1,5 +1,5 @@
1
1
  import type { Client } from "../client.js";
2
- import { type ProxyProfile, type ProxyProfileName } from "./profiles.js";
2
+ import { type ProxyPresetInput, type ResolvedProxyPreset } from "./preset.js";
3
3
  import { type ProxyRuntime } from "./runtime.js";
4
4
  import { type ProxyServiceWorkerScriptOptions } from "./serviceWorker.js";
5
5
  export type ProxyIntegrationMonitorOptions = Readonly<{
@@ -26,7 +26,7 @@ export type ProxyIntegrationServiceWorkerOptions = Readonly<{
26
26
  }>;
27
27
  export type RegisterProxyIntegrationOptions = Readonly<{
28
28
  client: Client;
29
- profile?: ProxyProfileName | Partial<ProxyProfile>;
29
+ preset?: ProxyPresetInput;
30
30
  runtimeGlobalKey?: string;
31
31
  runtime?: Readonly<{
32
32
  maxJsonFrameBytes?: number;
@@ -41,7 +41,7 @@ export type RegisterProxyIntegrationOptions = Readonly<{
41
41
  export type ProxyIntegrationContext = Readonly<{
42
42
  runtime: ProxyRuntime;
43
43
  options: RegisterProxyIntegrationOptions;
44
- profile: ProxyProfile;
44
+ preset: ResolvedProxyPreset;
45
45
  }>;
46
46
  export type ControllerMismatchContext = Readonly<{
47
47
  expectedScriptPathSuffix: string;
@@ -1,4 +1,5 @@
1
- import { resolveProxyProfile } from "./profiles.js";
1
+ import { profileToPresetManifest } from "./profiles.js";
2
+ import { resolveProxyPreset } from "./preset.js";
2
3
  import { registerServiceWorkerAndEnsureControl } from "./registerServiceWorker.js";
3
4
  import { createProxyRuntime } from "./runtime.js";
4
5
  import { createProxyServiceWorkerScript } from "./serviceWorker.js";
@@ -163,13 +164,23 @@ function shouldIgnoreMismatch(plugins, ctx) {
163
164
  }
164
165
  return false;
165
166
  }
166
- function buildRuntimeOptions(profile, runtime) {
167
+ function resolveIntegrationPreset(opts) {
168
+ if (opts.preset !== undefined && opts.profile !== undefined) {
169
+ throw new Error("preset and deprecated profile cannot be used together");
170
+ }
171
+ if (opts.preset !== undefined)
172
+ return resolveProxyPreset(opts.preset);
173
+ if (opts.profile !== undefined)
174
+ return resolveProxyPreset(profileToPresetManifest(opts.profile));
175
+ return resolveProxyPreset();
176
+ }
177
+ function buildRuntimeOptions(preset, runtime) {
167
178
  return {
168
- maxJsonFrameBytes: runtime?.maxJsonFrameBytes ?? profile.maxJsonFrameBytes,
169
- maxChunkBytes: runtime?.maxChunkBytes ?? profile.maxChunkBytes,
170
- maxBodyBytes: runtime?.maxBodyBytes ?? profile.maxBodyBytes,
171
- maxWsFrameBytes: runtime?.maxWsFrameBytes ?? profile.maxWsFrameBytes,
172
- timeoutMs: runtime?.timeoutMs ?? profile.timeoutMs,
179
+ maxJsonFrameBytes: runtime?.maxJsonFrameBytes ?? preset.limits.max_json_frame_bytes,
180
+ maxChunkBytes: runtime?.maxChunkBytes ?? preset.limits.max_chunk_bytes,
181
+ maxBodyBytes: runtime?.maxBodyBytes ?? preset.limits.max_body_bytes,
182
+ maxWsFrameBytes: runtime?.maxWsFrameBytes ?? preset.limits.max_ws_frame_bytes,
183
+ timeoutMs: runtime?.timeoutMs ?? preset.limits.timeout_ms ?? 0,
173
184
  };
174
185
  }
175
186
  function bindRuntimeGlobal(runtime, key) {
@@ -221,8 +232,8 @@ export function createProxyIntegrationServiceWorkerScript(opts = {}) {
221
232
  export async function registerProxyIntegration(input) {
222
233
  const plugins = input.plugins ?? [];
223
234
  const opts = applyPluginMutations(input, plugins);
224
- const profile = resolveProxyProfile(opts.profile);
225
- const runtimeOpts = buildRuntimeOptions(profile, opts.runtime);
235
+ const preset = resolveIntegrationPreset(opts);
236
+ const runtimeOpts = buildRuntimeOptions(preset, opts.runtime);
226
237
  const runtime = createProxyRuntime({ ...runtimeOpts, client: opts.client });
227
238
  const releaseRuntimeGlobal = bindRuntimeGlobal(runtime, opts.runtimeGlobalKey);
228
239
  const swCfg = opts.serviceWorker;
@@ -285,7 +296,7 @@ export async function registerProxyIntegration(input) {
285
296
  const ctx = {
286
297
  runtime,
287
298
  options: opts,
288
- profile,
299
+ preset,
289
300
  };
290
301
  for (const plugin of plugins) {
291
302
  await plugin.onRegistered?.(ctx);
@@ -0,0 +1,31 @@
1
+ export type ProxyPresetLimits = Readonly<{
2
+ max_json_frame_bytes?: number;
3
+ max_chunk_bytes?: number;
4
+ max_body_bytes?: number;
5
+ max_ws_frame_bytes?: number;
6
+ timeout_ms?: number;
7
+ }>;
8
+ export type ProxyPresetManifest = Readonly<{
9
+ v: 1;
10
+ preset_id: string;
11
+ deprecated?: boolean;
12
+ limits: ProxyPresetLimits;
13
+ }>;
14
+ export type ResolvedProxyPreset = Readonly<{
15
+ v: 1;
16
+ preset_id: string;
17
+ deprecated: boolean;
18
+ limits: Readonly<{
19
+ max_json_frame_bytes: number;
20
+ max_chunk_bytes: number;
21
+ max_body_bytes: number;
22
+ max_ws_frame_bytes: number;
23
+ timeout_ms?: number;
24
+ }>;
25
+ }>;
26
+ export declare const DEFAULT_PROXY_PRESET_MANIFEST: ProxyPresetManifest;
27
+ export declare const CODESERVER_PROXY_PRESET_MANIFEST: ProxyPresetManifest;
28
+ export type ProxyPresetInput = ProxyPresetManifest | Partial<ProxyPresetLimits>;
29
+ export declare function assertProxyPresetManifest(value: unknown): ProxyPresetManifest;
30
+ export declare function resolveNamedProxyPreset(name: string): ProxyPresetManifest;
31
+ export declare function resolveProxyPreset(input?: ProxyPresetInput): ResolvedProxyPreset;