@floegence/floe-webapp-boot 0.40.21 → 0.41.1

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.
@@ -0,0 +1,12 @@
1
+ import type { ArtifactSource, ConnectionSnapshot } from '@floegence/flowersec-core';
2
+ import { ConnectedAcquisition } from './acquisition';
3
+ import { ProxyBootstrapOwner } from './proxy-bootstrap';
4
+ export interface AcquisitionConnectionLifecycle {
5
+ synchronize(snapshot: ConnectionSnapshot): void;
6
+ dispose(): void;
7
+ }
8
+ export type AcquisitionConnectionLifecycleOptions = Readonly<{
9
+ proxyBootstrap?: ProxyBootstrapOwner;
10
+ onConnected?: (acquisition: ConnectedAcquisition) => void;
11
+ }>;
12
+ export declare function createAcquisitionConnectionLifecycle(source: ArtifactSource, options?: AcquisitionConnectionLifecycleOptions): AcquisitionConnectionLifecycle;
@@ -0,0 +1,20 @@
1
+ import { clearAcquisitionSource as s, AcquisitionError as c, synchronizeAcquisitionSourceSnapshot as u } from "./acquisition.js";
2
+ import { closeProxyBootstrap as l, synchronizeProxyBootstrap as a } from "./proxy-bootstrap.js";
3
+ function y(t, i = {}) {
4
+ let e = !1, r = null;
5
+ return Object.freeze({
6
+ synchronize(n) {
7
+ if (e) throw new c("acquisition_lifecycle_disposed");
8
+ const o = u(t, n);
9
+ if (n.state === "connected" && o === null)
10
+ throw new c("connected_acquisition_missing");
11
+ i.proxyBootstrap !== void 0 && a(i.proxyBootstrap, o), o !== null && o !== r && i.onConnected?.(o), r = o;
12
+ },
13
+ dispose() {
14
+ e || (e = !0, r = null, i.proxyBootstrap !== void 0 && l(i.proxyBootstrap), s(t));
15
+ }
16
+ });
17
+ }
18
+ export {
19
+ y as createAcquisitionConnectionLifecycle
20
+ };
@@ -0,0 +1,111 @@
1
+ import { type ArtifactLease, type ArtifactSource, type ConnectionSnapshot, type JsonValue, type Session } from '@floegence/flowersec-core';
2
+ import type { ConnectionControllerOptions, SessionOptions } from '@floegence/flowersec-core/browser';
3
+ import { type ProxyRuntimeScope } from '@floegence/flowersec-core/proxy';
4
+ export type CriticalScopeProjectionV1 = Readonly<{
5
+ scope: 'proxy.runtime';
6
+ scope_version: 2;
7
+ critical: true;
8
+ payload: JsonValue;
9
+ }>;
10
+ export type SpendScopeV1 = Readonly<{
11
+ v: 1;
12
+ receipt: string;
13
+ artifact_digest_b64u: string;
14
+ projection_digest_b64u: string;
15
+ launcher_origin: string;
16
+ runtime_origin: string;
17
+ app_origin: string;
18
+ consumer: 'trusted' | 'isolated';
19
+ target_binding: JsonValue;
20
+ expires_at: string;
21
+ }>;
22
+ export type SerializedAcquisitionEnvelopeV1 = Readonly<{
23
+ v: 1;
24
+ connect_artifact: string;
25
+ critical_scope_projection_json: string;
26
+ spend_scope: SpendScopeV1;
27
+ }>;
28
+ export type RuntimeBootInitPayloadV6 = Readonly<{
29
+ v: 6;
30
+ env_public_id: string;
31
+ floe_app: string;
32
+ code_space_id: string;
33
+ app_path: string;
34
+ launcher_kind: 'cs' | 'pf';
35
+ launcher_id: string;
36
+ launcher_origin: string;
37
+ runtime_origin: string;
38
+ app_origin: string;
39
+ acquisition: SerializedAcquisitionEnvelopeV1;
40
+ }>;
41
+ export type SpendBindingView = Readonly<{
42
+ artifactDigestB64u: string;
43
+ projectionDigestB64u: string;
44
+ launcherOrigin: string;
45
+ runtimeOrigin: string;
46
+ appOrigin: string;
47
+ consumer: 'trusted' | 'isolated';
48
+ targetBinding: JsonValue;
49
+ expiresAt: string;
50
+ }>;
51
+ export type SpendCommitRequest = SpendBindingView & Readonly<{
52
+ attemptId: string;
53
+ receipt: string;
54
+ }>;
55
+ export type CommitSpend = (request: SpendCommitRequest, signal?: AbortSignal) => Promise<void>;
56
+ export type ValidateSpendBinding = (binding: SpendBindingView) => string | void;
57
+ export declare class AcquisitionError extends Error {
58
+ readonly code: string;
59
+ constructor(code: string);
60
+ }
61
+ export declare class ConnectedAcquisition {
62
+ #private;
63
+ private constructor();
64
+ static create(): ConnectedAcquisition;
65
+ static assertAuthentic(value: ConnectedAcquisition): void;
66
+ }
67
+ export declare class IsolatedOneShotAcquisition {
68
+ #private;
69
+ private constructor();
70
+ static create(): IsolatedOneShotAcquisition;
71
+ static assertAuthentic(value: IsolatedOneShotAcquisition): void;
72
+ }
73
+ export type ConnectedAcquisitionDetails = Readonly<{
74
+ session: Session;
75
+ scope: ProxyRuntimeScope;
76
+ bindingIdentity: string;
77
+ attempt: number;
78
+ }>;
79
+ type MaterializeOptions = Readonly<{
80
+ commitSpend: CommitSpend;
81
+ validateSpendBinding: ValidateSpendBinding;
82
+ expectedConsumer: 'trusted' | 'isolated';
83
+ }>;
84
+ export declare function registerAcquisitionSource(source: ArtifactSource): void;
85
+ export declare function materializeAcquisitionForSource(source: ArtifactSource, value: unknown, options: MaterializeOptions): Promise<ArtifactLease>;
86
+ export declare function synchronizeAcquisitionSourceSnapshot(source: ArtifactSource, snapshot: ConnectionSnapshot): ConnectedAcquisition | null;
87
+ export declare function clearAcquisitionSource(source: ArtifactSource): void;
88
+ export declare function connectedAcquisitionDetails(acquisition: ConnectedAcquisition): ConnectedAcquisitionDetails;
89
+ export type IsolatedHandoffValidationContext = Readonly<{
90
+ envPublicId: string;
91
+ floeApp: string;
92
+ codeSpaceId: string;
93
+ appPath: string;
94
+ launcherKind: 'cs' | 'pf';
95
+ launcherId: string;
96
+ launcherOrigin: string;
97
+ runtimeOrigin: string;
98
+ appOrigin: string;
99
+ validateTargetBinding: (targetBinding: JsonValue) => void;
100
+ }>;
101
+ export type MaterializeIsolatedOneShotOptions = Readonly<{
102
+ rawHandoff: string;
103
+ clearSensitiveLocation: () => boolean;
104
+ navigateToLauncher: () => void;
105
+ validationContext: IsolatedHandoffValidationContext;
106
+ commitSpend: CommitSpend;
107
+ }>;
108
+ export declare function materializeIsolatedOneShot(options: MaterializeIsolatedOneShotOptions): Promise<IsolatedOneShotAcquisition>;
109
+ export declare function connectIsolatedOneShot(acquisition: IsolatedOneShotAcquisition, options?: SessionOptions): Promise<ConnectedAcquisition>;
110
+ export type BrowserControllerOptions = ConnectionControllerOptions;
111
+ export {};
@@ -0,0 +1,361 @@
1
+ import { parseArtifact as R, createArtifactLease as $ } from "@floegence/flowersec-core";
2
+ import { assertProxyRuntimeScope as C } from "@floegence/flowersec-core/proxy";
3
+ const j = 32, F = 16384, L = "#redeven=";
4
+ class n extends Error {
5
+ constructor(i) {
6
+ super(`Floe acquisition failed (code=${i})`), this.code = i, this.name = "AcquisitionError";
7
+ }
8
+ }
9
+ class h {
10
+ #e = void 0;
11
+ constructor() {
12
+ Object.freeze(this);
13
+ }
14
+ static create() {
15
+ return new h();
16
+ }
17
+ static assertAuthentic(i) {
18
+ i.#e;
19
+ }
20
+ }
21
+ class m {
22
+ #e = void 0;
23
+ constructor() {
24
+ Object.freeze(this);
25
+ }
26
+ static create() {
27
+ return new m();
28
+ }
29
+ static assertAuthentic(i) {
30
+ i.#e;
31
+ }
32
+ }
33
+ const g = /* @__PURE__ */ new WeakMap(), S = /* @__PURE__ */ new WeakMap(), z = /* @__PURE__ */ new WeakMap(), O = /* @__PURE__ */ new Set();
34
+ function X(e) {
35
+ if (g.has(e)) throw new n("duplicate_acquisition_source");
36
+ g.set(e, { pending: [] });
37
+ }
38
+ async function Y(e, i, t) {
39
+ const r = g.get(e);
40
+ if (r === void 0) throw new n("invalid_acquisition_source");
41
+ const c = await x(i, t);
42
+ return D(c, t.commitSpend, (o) => {
43
+ r.pending.push(o);
44
+ }, (o) => {
45
+ r.pending = r.pending.filter((a) => a !== o);
46
+ });
47
+ }
48
+ function K(e, i) {
49
+ const t = g.get(e);
50
+ if (t === void 0) return null;
51
+ if (i.state !== "connected" || i.currentSession === void 0)
52
+ return (i.state === "idle" || i.state === "waiting" || i.state === "failed" || i.state === "closed") && (t.pending = [], t.connected = void 0, t.connectedSession = void 0), null;
53
+ if (t.connected !== void 0 && t.connectedSession === i.currentSession) return t.connected;
54
+ const r = t.pending.filter((a) => a.committed);
55
+ if (r.length !== 1 || t.pending.length !== 1)
56
+ throw t.pending = [], new n("connected_acquisition_mismatch");
57
+ const c = r[0];
58
+ if (c === void 0) throw new n("connected_acquisition_mismatch");
59
+ const o = h.create();
60
+ return S.set(o, Object.freeze({
61
+ session: i.currentSession,
62
+ scope: c.scope,
63
+ bindingIdentity: c.bindingIdentity,
64
+ attempt: i.attempt
65
+ })), t.pending = [], t.connected = o, t.connectedSession = i.currentSession, o;
66
+ }
67
+ function Q(e) {
68
+ const i = g.get(e);
69
+ i !== void 0 && (i.pending = [], i.connected = void 0, i.connectedSession = void 0);
70
+ }
71
+ function ee(e) {
72
+ h.assertAuthentic(e);
73
+ const i = S.get(e);
74
+ if (i === void 0) throw new n("invalid_connected_acquisition");
75
+ return i;
76
+ }
77
+ async function te(e) {
78
+ const i = e.rawHandoff, t = typeof i != "string" || Z(`${L}${i}`) > F;
79
+ let r = !1;
80
+ try {
81
+ r = e.clearSensitiveLocation() === !0;
82
+ } catch {
83
+ r = !1;
84
+ }
85
+ if (!r)
86
+ throw e.navigateToLauncher(), new n("isolated_location_clear_failed");
87
+ if (t) throw new n("isolated_handoff_too_large");
88
+ const c = y(i, "invalid_isolated_handoff"), o = await V(c);
89
+ if (O.has(o)) throw new n("isolated_handoff_consumed");
90
+ O.add(o);
91
+ let a;
92
+ try {
93
+ a = JSON.parse(new TextDecoder("utf-8", { fatal: !0 }).decode(c));
94
+ } catch {
95
+ throw new n("invalid_isolated_handoff");
96
+ }
97
+ const _ = P(a, e.validationContext), u = await x(_.acquisition, {
98
+ commitSpend: e.commitSpend,
99
+ expectedConsumer: "isolated",
100
+ validateSpendBinding: (d) => (k(_, d, e.validationContext), e.validationContext.validateTargetBinding(d.targetBinding), `${d.artifactDigestB64u}.${d.projectionDigestB64u}`)
101
+ });
102
+ if (u.scope.appBasePath !== _.app_path) throw new n("isolated_app_path_mismatch");
103
+ if (u.scope.mode === "controller_bridge") {
104
+ const d = u.scope.controllerBridge.allowedOrigins;
105
+ if (d.length !== 1 || d[0] !== _.app_origin)
106
+ throw new n("isolated_allowed_origins_mismatch");
107
+ }
108
+ let s;
109
+ const l = D(u, e.commitSpend, (d) => {
110
+ s = d;
111
+ }, () => {
112
+ });
113
+ if (s === void 0 || s.lease !== l) throw new n("isolated_materialization_failed");
114
+ const p = m.create();
115
+ return z.set(p, { pending: s, state: "ready" }), p;
116
+ }
117
+ async function ie(e, i = {}) {
118
+ m.assertAuthentic(e);
119
+ const t = z.get(e);
120
+ if (t === void 0 || t.state !== "ready") throw new n("isolated_acquisition_consumed");
121
+ t.state = "connecting";
122
+ try {
123
+ const { connect: r } = await import("@floegence/flowersec-core/browser"), c = await r(t.pending.lease, i);
124
+ if (!t.pending.committed)
125
+ throw await c.close().catch(() => {
126
+ }), new n("isolated_spend_not_committed");
127
+ const o = h.create();
128
+ return S.set(o, Object.freeze({
129
+ session: c,
130
+ scope: t.pending.scope,
131
+ bindingIdentity: t.pending.bindingIdentity,
132
+ attempt: 1
133
+ })), t.state = "consumed", o;
134
+ } catch (r) {
135
+ throw t.state = "consumed", r;
136
+ }
137
+ }
138
+ async function x(e, i) {
139
+ const t = w(e, ["v", "connect_artifact", "critical_scope_projection_json", "spend_scope"], "invalid_acquisition_envelope");
140
+ if (t.v !== 1 || typeof t.connect_artifact != "string" || t.connect_artifact.length === 0 || typeof t.critical_scope_projection_json != "string" || t.critical_scope_projection_json.length === 0)
141
+ throw new n("invalid_acquisition_envelope");
142
+ const r = U(t.spend_scope, i.expectedConsumer), c = new TextEncoder().encode(t.connect_artifact), o = new TextEncoder().encode(t.critical_scope_projection_json);
143
+ await q(c, r.artifact_digest_b64u, "artifact_digest_mismatch"), await q(o, r.projection_digest_b64u, "projection_digest_mismatch");
144
+ let a;
145
+ try {
146
+ a = JSON.parse(t.critical_scope_projection_json);
147
+ } catch {
148
+ throw new n("invalid_critical_scope_projection");
149
+ }
150
+ const _ = w(a, ["scope", "scope_version", "critical", "payload"], "invalid_critical_scope_projection");
151
+ if (_.scope !== "proxy.runtime" || _.scope_version !== 2 || _.critical !== !0)
152
+ throw new n("invalid_critical_scope_projection");
153
+ let u;
154
+ try {
155
+ u = C(_.payload);
156
+ } catch {
157
+ throw new n("invalid_proxy_runtime_scope");
158
+ }
159
+ let s;
160
+ try {
161
+ s = R(t.connect_artifact);
162
+ } catch {
163
+ throw new n("invalid_connect_artifact");
164
+ }
165
+ const l = N(r), p = i.validateSpendBinding(l);
166
+ if (p !== void 0 && (typeof p != "string" || p.trim() === "" || p !== p.trim()))
167
+ throw new n("invalid_spend_binding_identity");
168
+ return Object.freeze({
169
+ artifact: s,
170
+ scope: u,
171
+ binding: l,
172
+ bindingIdentity: p ?? `${l.artifactDigestB64u}.${l.projectionDigestB64u}`,
173
+ receipt: r.receipt
174
+ });
175
+ }
176
+ function D(e, i, t, r) {
177
+ const c = e.artifact, o = e.scope, a = e.binding, _ = e.bindingIdentity;
178
+ let u = e.receipt, s;
179
+ const l = $(c, async (p) => {
180
+ const d = u;
181
+ if (u = void 0, d === void 0) throw new n("spend_binding_consumed");
182
+ const T = Object.freeze({
183
+ ...a,
184
+ attemptId: J(),
185
+ receipt: d
186
+ });
187
+ try {
188
+ await i(T, p), s.committed = !0;
189
+ } catch (E) {
190
+ throw r(s), E;
191
+ }
192
+ });
193
+ return s = { lease: l, scope: o, bindingIdentity: _, committed: !1 }, t(s), l;
194
+ }
195
+ function U(e, i) {
196
+ const t = w(e, [
197
+ "v",
198
+ "receipt",
199
+ "artifact_digest_b64u",
200
+ "projection_digest_b64u",
201
+ "launcher_origin",
202
+ "runtime_origin",
203
+ "app_origin",
204
+ "consumer",
205
+ "target_binding",
206
+ "expires_at"
207
+ ], "invalid_spend_scope");
208
+ if (t.v !== 1 || typeof t.receipt != "string" || !H(t.receipt) || typeof t.artifact_digest_b64u != "string" || !B(t.artifact_digest_b64u) || typeof t.projection_digest_b64u != "string" || !B(t.projection_digest_b64u) || t.consumer !== i || typeof t.expires_at != "string" || !M(t.expires_at) || !b(t.target_binding))
209
+ throw new n("invalid_spend_scope");
210
+ const r = f(t.launcher_origin), c = f(t.runtime_origin), o = f(t.app_origin);
211
+ if (Date.parse(t.expires_at) <= Date.now()) throw new n("expired_spend_scope");
212
+ return Object.freeze({
213
+ v: 1,
214
+ receipt: t.receipt,
215
+ artifact_digest_b64u: t.artifact_digest_b64u,
216
+ projection_digest_b64u: t.projection_digest_b64u,
217
+ launcher_origin: r,
218
+ runtime_origin: c,
219
+ app_origin: o,
220
+ consumer: i,
221
+ target_binding: v(t.target_binding),
222
+ expires_at: t.expires_at
223
+ });
224
+ }
225
+ function N(e) {
226
+ return Object.freeze({
227
+ artifactDigestB64u: e.artifact_digest_b64u,
228
+ projectionDigestB64u: e.projection_digest_b64u,
229
+ launcherOrigin: e.launcher_origin,
230
+ runtimeOrigin: e.runtime_origin,
231
+ appOrigin: e.app_origin,
232
+ consumer: e.consumer,
233
+ targetBinding: e.target_binding,
234
+ expiresAt: e.expires_at
235
+ });
236
+ }
237
+ function P(e, i) {
238
+ const t = w(e, [
239
+ "v",
240
+ "env_public_id",
241
+ "floe_app",
242
+ "code_space_id",
243
+ "app_path",
244
+ "launcher_kind",
245
+ "launcher_id",
246
+ "launcher_origin",
247
+ "runtime_origin",
248
+ "app_origin",
249
+ "acquisition"
250
+ ], "invalid_isolated_handoff");
251
+ if (t.v !== 6 || t.env_public_id !== i.envPublicId || t.floe_app !== i.floeApp || t.code_space_id !== i.codeSpaceId || t.app_path !== i.appPath || t.launcher_kind !== i.launcherKind || t.launcher_id !== i.launcherId || t.launcher_origin !== i.launcherOrigin || t.runtime_origin !== i.runtimeOrigin || t.app_origin !== i.appOrigin)
252
+ throw new n("isolated_context_mismatch");
253
+ return f(t.launcher_origin), f(t.runtime_origin), f(t.app_origin), t;
254
+ }
255
+ function k(e, i, t) {
256
+ if (i.consumer !== "isolated" || i.launcherOrigin !== e.launcher_origin || i.runtimeOrigin !== e.runtime_origin || i.appOrigin !== e.app_origin || e.runtime_origin !== t.runtimeOrigin || e.app_origin !== t.appOrigin)
257
+ throw new n("isolated_spend_binding_mismatch");
258
+ }
259
+ function w(e, i, t) {
260
+ if (e === null || typeof e != "object" || Array.isArray(e)) throw new n(t);
261
+ const r = e, c = new Set(i), o = Object.keys(r);
262
+ if (o.length !== i.length || o.some((a) => !c.has(a))) throw new n(t);
263
+ return r;
264
+ }
265
+ function f(e) {
266
+ if (typeof e != "string") throw new n("invalid_spend_scope");
267
+ let i;
268
+ try {
269
+ i = new URL(e);
270
+ } catch {
271
+ throw new n("invalid_spend_scope");
272
+ }
273
+ if (i.protocol !== "https:" && i.protocol !== "http:" || i.origin !== e || i.username !== "" || i.password !== "")
274
+ throw new n("invalid_spend_scope");
275
+ return e;
276
+ }
277
+ function H(e) {
278
+ const i = /^r1\.([A-Za-z0-9_-]{1,64})\.([A-Za-z0-9_-]+)$/u.exec(e);
279
+ if (i === null || i[2] === void 0) return !1;
280
+ try {
281
+ return y(i[2], "invalid_spend_scope").length === j;
282
+ } catch {
283
+ return !1;
284
+ }
285
+ }
286
+ function B(e) {
287
+ try {
288
+ return y(e, "invalid_spend_scope").length === j;
289
+ } catch {
290
+ return !1;
291
+ }
292
+ }
293
+ function M(e) {
294
+ return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/u.test(e) ? Number.isFinite(Date.parse(e)) : !1;
295
+ }
296
+ function b(e, i = 0) {
297
+ return i > 32 ? !1 : e === null || typeof e == "string" || typeof e == "boolean" ? !0 : typeof e == "number" ? Number.isFinite(e) : Array.isArray(e) ? e.every((t) => b(t, i + 1)) : typeof e != "object" ? !1 : Object.values(e).every((t) => b(t, i + 1));
298
+ }
299
+ function v(e) {
300
+ if (Array.isArray(e)) {
301
+ for (const i of e) v(i);
302
+ return Object.freeze(e);
303
+ }
304
+ if (e !== null && typeof e == "object") {
305
+ for (const i of Object.values(e)) v(i);
306
+ return Object.freeze(e);
307
+ }
308
+ return e;
309
+ }
310
+ async function q(e, i, t) {
311
+ const r = await I(e), c = y(i, t);
312
+ if (r.length !== c.length) throw new n(t);
313
+ let o = 0;
314
+ for (let a = 0; a < r.length; a += 1) o |= r[a] ^ c[a];
315
+ if (o !== 0) throw new n(t);
316
+ }
317
+ async function V(e) {
318
+ return A(await I(e));
319
+ }
320
+ async function I(e) {
321
+ if (globalThis.crypto?.subtle === void 0) throw new n("acquisition_crypto_unavailable");
322
+ const i = e.buffer.slice(e.byteOffset, e.byteOffset + e.byteLength), t = await globalThis.crypto.subtle.digest("SHA-256", i);
323
+ return new Uint8Array(t);
324
+ }
325
+ function J() {
326
+ if (globalThis.crypto?.getRandomValues === void 0) throw new n("acquisition_entropy_unavailable");
327
+ return A(globalThis.crypto.getRandomValues(new Uint8Array(j)));
328
+ }
329
+ function y(e, i) {
330
+ if (!/^[A-Za-z0-9_-]*$/u.test(e) || e.length % 4 === 1) throw new n(i);
331
+ const t = e.replace(/-/gu, "+").replace(/_/gu, "/") + "=".repeat((4 - e.length % 4) % 4);
332
+ let r;
333
+ try {
334
+ r = atob(t);
335
+ } catch {
336
+ throw new n(i);
337
+ }
338
+ const c = Uint8Array.from(r, (o) => o.charCodeAt(0));
339
+ if (A(c) !== e) throw new n(i);
340
+ return c;
341
+ }
342
+ function A(e) {
343
+ let i = "";
344
+ for (const t of e) i += String.fromCharCode(t);
345
+ return btoa(i).replace(/\+/gu, "-").replace(/\//gu, "_").replace(/=+$/u, "");
346
+ }
347
+ function Z(e) {
348
+ return new TextEncoder().encode(e).length;
349
+ }
350
+ export {
351
+ n as AcquisitionError,
352
+ h as ConnectedAcquisition,
353
+ m as IsolatedOneShotAcquisition,
354
+ Q as clearAcquisitionSource,
355
+ ie as connectIsolatedOneShot,
356
+ ee as connectedAcquisitionDetails,
357
+ Y as materializeAcquisitionForSource,
358
+ te as materializeIsolatedOneShot,
359
+ X as registerAcquisitionSource,
360
+ K as synchronizeAcquisitionSourceSnapshot
361
+ };
@@ -1,4 +1,5 @@
1
- import { type ArtifactSource, type JsonObject } from '@floegence/flowersec-core';
1
+ import type { ArtifactSource, JsonObject, RetryDisposition } from '@floegence/flowersec-core';
2
+ import { type CommitSpend, type ValidateSpendBinding } from './acquisition';
2
3
  export type ControlplaneArtifactSourceOptions = Readonly<{
3
4
  baseUrl: string;
4
5
  endpointId: string;
@@ -9,10 +10,25 @@ export type ControlplaneArtifactSourceOptions = Readonly<{
9
10
  entryTicket?: string;
10
11
  allowLoopbackHTTP?: boolean;
11
12
  fetch?: typeof globalThis.fetch;
13
+ commitSpend: CommitSpend;
14
+ validateSpendBinding: ValidateSpendBinding;
15
+ retryableBusinessCodes?: readonly string[];
12
16
  }>;
13
17
  export declare class ControlplaneRequestError extends Error {
14
18
  readonly status: number;
15
19
  readonly code: string;
16
- constructor(status: number, code: string, message: string);
20
+ constructor(status: number, code: string);
17
21
  }
22
+ export type ControlplaneFailureInput = Readonly<{
23
+ status: number;
24
+ code: string;
25
+ retryAfter?: string | null;
26
+ retryableBusinessCodes?: readonly string[];
27
+ nowUnixMilliseconds?: number;
28
+ }>;
29
+ export type ClassifiedControlplaneFailure = Readonly<{
30
+ code: string;
31
+ disposition: RetryDisposition;
32
+ }>;
33
+ export declare function classifyControlplaneFailure(input: ControlplaneFailureInput): ClassifiedControlplaneFailure;
18
34
  export declare function createControlplaneArtifactSource(options: ControlplaneArtifactSourceOptions): ArtifactSource;
@@ -1,75 +1,142 @@
1
- import { parseArtifact as d, createArtifactLease as f } from "@floegence/flowersec-core";
2
- class a extends Error {
3
- status;
4
- code;
5
- constructor(e, r, c) {
6
- super(c), this.name = "ControlplaneRequestError", this.status = e, this.code = r;
1
+ import { registerAcquisitionSource as b, materializeAcquisitionForSource as h, AcquisitionError as u } from "./acquisition.js";
2
+ const y = /^[a-z][a-z0-9_]{0,63}$/u;
3
+ class f extends Error {
4
+ constructor(t, r) {
5
+ super(`Floe controlplane request failed (code=${r})`), this.status = t, this.code = r, this.name = "ControlplaneRequestError";
7
6
  }
8
7
  }
9
- function p(t) {
10
- const e = t.toLowerCase();
11
- return e === "localhost" || e === "::1" || e === "127.0.0.1" || e.startsWith("127.");
8
+ function m(e) {
9
+ const t = e.toLowerCase();
10
+ return t === "localhost" || t === "::1" || t === "127.0.0.1" || t.startsWith("127.");
12
11
  }
13
- function u(t, e) {
14
- const r = new URL(t);
15
- if (r.protocol !== "https:" && !(e && r.protocol === "http:" && p(r.hostname)))
16
- throw new a(0, "transport_policy_denied", "controlplane transport policy denied");
12
+ function w(e, t) {
13
+ let r;
14
+ try {
15
+ r = new URL(e);
16
+ } catch {
17
+ throw new f(0, "transport_policy_denied");
18
+ }
19
+ if (r.protocol !== "https:" && !(t && r.protocol === "http:" && m(r.hostname)))
20
+ throw new f(0, "transport_policy_denied");
17
21
  return r.pathname = r.pathname.replace(/\/+$/u, ""), r;
18
22
  }
19
- function h(t, e) {
20
- const r = e === void 0 ? "/v1/connect/artifact" : "/v1/connect/artifact/entry";
21
- return new URL(`${t.pathname}${r}`, t).toString();
23
+ function j(e, t) {
24
+ const r = t === void 0 ? "/v1/connect/artifact" : "/v1/connect/artifact/entry";
25
+ return new URL(`${e.pathname}${r}`, e).toString();
22
26
  }
23
- function y(t) {
24
- if (!t || typeof t != "object" || !("connect_artifact" in t))
25
- throw new a(200, "invalid_request", "Invalid controlplane response: missing `connect_artifact`");
26
- return JSON.stringify(t.connect_artifact);
27
+ function v(e) {
28
+ if (!y.test(e.code))
29
+ return Object.freeze({ code: "invalid_error_code", disposition: Object.freeze({ kind: "terminal" }) });
30
+ if (new Set(e.retryableBusinessCodes ?? []).has(e.code))
31
+ return Object.freeze({ code: e.code, disposition: Object.freeze({ kind: "retryable" }) });
32
+ if (e.status === 429) {
33
+ const r = O(e.retryAfter, e.nowUnixMilliseconds ?? Date.now());
34
+ return Object.freeze({
35
+ code: e.code,
36
+ disposition: Object.freeze(r === void 0 ? { kind: "retryable" } : { kind: "retry_after", notBeforeUnixMilliseconds: r })
37
+ });
38
+ }
39
+ return e.status === 408 || e.status === 425 || e.status >= 500 ? Object.freeze({ code: e.code, disposition: Object.freeze({ kind: "retryable" }) }) : Object.freeze({ code: e.code, disposition: Object.freeze({ kind: "terminal" }) });
27
40
  }
28
- function k(t) {
29
- const e = t.fetch ?? globalThis.fetch, r = u(t.baseUrl, t.allowLoopbackHTTP === !0), c = h(r, t.entryTicket);
30
- return {
31
- acquire: async ({ signal: s }) => {
41
+ function _(e) {
42
+ if (typeof e.commitSpend != "function") throw new TypeError("commitSpend is required");
43
+ if (typeof e.validateSpendBinding != "function") throw new TypeError("validateSpendBinding is required");
44
+ const t = e.fetch ?? globalThis.fetch;
45
+ if (typeof t != "function") throw new TypeError("fetch is required");
46
+ const r = w(e.baseUrl, e.allowLoopbackHTTP === !0), n = j(r, e.entryTicket), c = {
47
+ acquire: async ({ signal: i }) => {
32
48
  try {
33
49
  const o = {
34
- endpoint_id: t.endpointId,
35
- ...t.payload === void 0 ? {} : { payload: t.payload },
36
- ...t.entryTicket === void 0 && t.correlation !== void 0 ? { correlation: { trace_id: t.correlation.traceId } } : {}
37
- }, n = await e(c, {
50
+ endpoint_id: e.endpointId,
51
+ ...e.payload === void 0 ? {} : { payload: e.payload },
52
+ ...e.entryTicket === void 0 && e.correlation !== void 0 ? { correlation: { trace_id: e.correlation.traceId } } : {}
53
+ }, a = await t(n, {
38
54
  method: "POST",
39
55
  headers: {
40
56
  accept: "application/json",
41
57
  "content-type": "application/json",
42
- ...t.entryTicket === void 0 ? {} : { authorization: `Bearer ${t.entryTicket}` }
58
+ ...e.entryTicket === void 0 ? {} : { authorization: `Bearer ${e.entryTicket}` }
43
59
  },
44
60
  body: JSON.stringify(o),
45
61
  credentials: "omit",
46
62
  redirect: "error",
47
- signal: s
63
+ signal: i
48
64
  });
49
- if (!n.ok) {
50
- let i = "request_failed";
51
- try {
52
- i = (await n.json()).error?.code ?? i;
53
- } catch {
54
- }
55
- throw new a(n.status, i, `controlplane request failed: ${n.status}`);
65
+ if (!a.ok) {
66
+ const s = await z(a);
67
+ return { kind: "failure", ...v({
68
+ status: a.status,
69
+ code: s,
70
+ retryAfter: a.headers.get("retry-after"),
71
+ retryableBusinessCodes: e.retryableBusinessCodes
72
+ }) };
56
73
  }
57
- const l = d(y(await n.json()));
58
- return {
59
- kind: "lease",
60
- lease: f(l, async () => {
61
- })
62
- };
74
+ let l;
75
+ try {
76
+ l = await a.json();
77
+ } catch {
78
+ return d("invalid_controlplane_response");
79
+ }
80
+ const p = await h(c, l, {
81
+ commitSpend: e.commitSpend,
82
+ validateSpendBinding: (s) => {
83
+ try {
84
+ return e.validateSpendBinding(s);
85
+ } catch {
86
+ throw new u("invalid_spend_binding");
87
+ }
88
+ },
89
+ expectedConsumer: "trusted"
90
+ });
91
+ return Object.freeze({ kind: "lease", lease: p });
63
92
  } catch (o) {
93
+ if (i.aborted) throw i.reason ?? new DOMException("Aborted", "AbortError");
64
94
  if (o instanceof DOMException && o.name === "AbortError") throw o;
65
- if (o instanceof a)
66
- return { kind: "failure", code: o.code, disposition: { kind: "retryable" } };
67
- throw o;
95
+ return o instanceof u || o instanceof f ? d(o.code) : Object.freeze({
96
+ kind: "failure",
97
+ code: "network_error",
98
+ disposition: Object.freeze({ kind: "retryable" })
99
+ });
68
100
  }
69
101
  }
70
102
  };
103
+ return b(c), c;
104
+ }
105
+ async function z(e) {
106
+ try {
107
+ const t = await e.json();
108
+ if (t !== null && typeof t == "object" && !Array.isArray(t)) {
109
+ const r = t.error;
110
+ if (r !== null && typeof r == "object" && !Array.isArray(r)) {
111
+ const n = r.code;
112
+ if (typeof n == "string") return n;
113
+ }
114
+ }
115
+ } catch {
116
+ }
117
+ return "request_failed";
118
+ }
119
+ function O(e, t) {
120
+ if (e == null) return;
121
+ const r = e.trim();
122
+ if (/^\d+$/u.test(r)) {
123
+ const c = Number(r);
124
+ if (!Number.isSafeInteger(c)) return;
125
+ const i = t + c * 1e3;
126
+ return Number.isSafeInteger(i) && i > t ? i : void 0;
127
+ }
128
+ const n = Date.parse(r);
129
+ return Number.isSafeInteger(n) && n > t ? n : void 0;
130
+ }
131
+ function d(e) {
132
+ return Object.freeze({
133
+ kind: "failure",
134
+ code: y.test(e) ? e : "invalid_error_code",
135
+ disposition: Object.freeze({ kind: "terminal" })
136
+ });
71
137
  }
72
138
  export {
73
- a as ControlplaneRequestError,
74
- k as createControlplaneArtifactSource
139
+ f as ControlplaneRequestError,
140
+ v as classifyControlplaneFailure,
141
+ _ as createControlplaneArtifactSource
75
142
  };
@@ -1,10 +1,24 @@
1
- import type { ArtifactSource, ConnectionControllerOptions } from '@floegence/flowersec-core';
1
+ import type { ArtifactSource } from '@floegence/flowersec-core';
2
+ import type { ConnectionControllerOptions } from '@floegence/flowersec-core/browser';
3
+ import { type AcquisitionConnectionLifecycle } from './acquisition-lifecycle';
4
+ import type { ConnectedAcquisition } from './acquisition';
5
+ import type { ProxyBootstrapOwner } from './proxy-bootstrap';
2
6
  export type FlowersecConnectionConfig = Readonly<{
3
7
  source: ArtifactSource;
4
8
  controller?: ConnectionControllerOptions;
9
+ lifecycle: AcquisitionConnectionLifecycle;
10
+ }>;
11
+ type AcquisitionConnectionOptions = Readonly<{
12
+ source: ArtifactSource;
13
+ controller?: ConnectionControllerOptions;
14
+ onConnected?: (acquisition: ConnectedAcquisition) => void;
15
+ }>;
16
+ export type TunnelArtifactConnectionOptions = AcquisitionConnectionOptions;
17
+ export type DirectArtifactConnectionOptions = AcquisitionConnectionOptions;
18
+ export type ProxyRuntimeTunnelConnectionOptions = AcquisitionConnectionOptions & Readonly<{
19
+ proxyBootstrap: ProxyBootstrapOwner;
5
20
  }>;
6
- export type TunnelArtifactConnectionOptions = FlowersecConnectionConfig;
7
- export type DirectArtifactConnectionOptions = FlowersecConnectionConfig;
8
21
  export declare function createArtifactTunnelConnectionConfig(options: TunnelArtifactConnectionOptions): FlowersecConnectionConfig;
9
- export declare function createProxyRuntimeTunnelConnectionConfig(options: TunnelArtifactConnectionOptions): FlowersecConnectionConfig;
22
+ export declare function createProxyRuntimeTunnelConnectionConfig(options: ProxyRuntimeTunnelConnectionOptions): FlowersecConnectionConfig;
10
23
  export declare function createArtifactDirectConnectionConfig(options: DirectArtifactConnectionOptions): FlowersecConnectionConfig;
24
+ export {};
@@ -1,14 +1,25 @@
1
- function t(n) {
2
- return n;
1
+ import { createAcquisitionConnectionLifecycle as r } from "./acquisition-lifecycle.js";
2
+ function o(e) {
3
+ return n(e);
3
4
  }
4
- function e(n) {
5
- return n;
5
+ function i(e) {
6
+ return n(e, e.proxyBootstrap);
6
7
  }
7
- function o(n) {
8
- return n;
8
+ function u(e) {
9
+ return n(e);
10
+ }
11
+ function n(e, c) {
12
+ return Object.freeze({
13
+ source: e.source,
14
+ ...e.controller === void 0 ? {} : { controller: e.controller },
15
+ lifecycle: r(e.source, {
16
+ ...c === void 0 ? {} : { proxyBootstrap: c },
17
+ ...e.onConnected === void 0 ? {} : { onConnected: e.onConnected }
18
+ })
19
+ });
9
20
  }
10
21
  export {
11
- o as createArtifactDirectConnectionConfig,
12
- t as createArtifactTunnelConnectionConfig,
13
- e as createProxyRuntimeTunnelConnectionConfig
22
+ u as createArtifactDirectConnectionConfig,
23
+ o as createArtifactTunnelConnectionConfig,
24
+ i as createProxyRuntimeTunnelConnectionConfig
14
25
  };
package/dist/hash.d.ts CHANGED
@@ -1,4 +1,2 @@
1
- export declare function base64UrlToBase64(s: string): string;
2
1
  export declare function parseHashParam(key: string): string | null;
3
- export declare function parseBase64UrlJsonFromHash<T>(key: string): T | null;
4
2
  export declare function clearLocationHash(): void;
package/dist/hash.js CHANGED
@@ -1,39 +1,22 @@
1
- function l(r) {
2
- let t = String(r ?? "").replace(/-/g, "+").replace(/_/g, "/");
3
- for (; t.length % 4 !== 0; ) t += "=";
4
- return t;
5
- }
6
1
  function o(r) {
7
- const t = String(r ?? "").trim();
8
- if (!t) return null;
9
- const n = String(window.location.hash ?? "").trim(), e = n.startsWith("#") ? n.slice(1) : n;
10
- if (!e) return null;
11
- try {
12
- const a = new URLSearchParams(e);
13
- return String(a.get(t) ?? "").trim() || null;
14
- } catch {
15
- return null;
16
- }
17
- }
18
- function i(r) {
19
- const t = o(r);
20
- if (!t) return null;
2
+ const n = String(r ?? "").trim();
3
+ if (!n) return null;
4
+ const t = String(window.location.hash ?? "").trim(), a = t.startsWith("#") ? t.slice(1) : t;
5
+ if (!a) return null;
21
6
  try {
22
- const n = atob(l(t));
23
- return n ? JSON.parse(n) : null;
7
+ const e = new URLSearchParams(a);
8
+ return String(e.get(n) ?? "").trim() || null;
24
9
  } catch {
25
10
  return null;
26
11
  }
27
12
  }
28
- function s() {
13
+ function c() {
29
14
  try {
30
15
  history.replaceState(null, document.title, window.location.pathname + window.location.search);
31
16
  } catch {
32
17
  }
33
18
  }
34
19
  export {
35
- l as base64UrlToBase64,
36
- s as clearLocationHash,
37
- i as parseBase64UrlJsonFromHash,
20
+ c as clearLocationHash,
38
21
  o as parseHashParam
39
22
  };
package/dist/index.d.ts CHANGED
@@ -1,13 +1,20 @@
1
- export { base64UrlToBase64, clearLocationHash, parseBase64UrlJsonFromHash, parseHashParam, } from './hash';
1
+ export { clearLocationHash, parseHashParam, } from './hash';
2
2
  export type { WaitForMessageOptions } from './messaging';
3
3
  export { postMessageToOrigins, waitForMessage } from './messaging';
4
4
  export { getSessionStorage, removeSessionStorage, setSessionStorage } from './storage';
5
5
  export type { ArtifactSource, ArtifactSourceResult } from '@floegence/flowersec-core';
6
6
  export { createControlplaneArtifactSource, ControlplaneRequestError } from './artifact-source';
7
- export type { ControlplaneArtifactSourceOptions } from './artifact-source';
8
- export type { DirectArtifactConnectionOptions, TunnelArtifactConnectionOptions, FlowersecConnectionConfig } from './connection';
7
+ export { classifyControlplaneFailure } from './artifact-source';
8
+ export type { ClassifiedControlplaneFailure, ControlplaneArtifactSourceOptions, ControlplaneFailureInput, } from './artifact-source';
9
+ export { AcquisitionError, clearAcquisitionSource, connectIsolatedOneShot, ConnectedAcquisition, IsolatedOneShotAcquisition, materializeIsolatedOneShot, synchronizeAcquisitionSourceSnapshot, } from './acquisition';
10
+ export type { CommitSpend, CriticalScopeProjectionV1, IsolatedHandoffValidationContext, MaterializeIsolatedOneShotOptions, RuntimeBootInitPayloadV6, SerializedAcquisitionEnvelopeV1, SpendBindingView, SpendCommitRequest, SpendScopeV1, ValidateSpendBinding, } from './acquisition';
11
+ export { createAcquisitionConnectionLifecycle } from './acquisition-lifecycle';
12
+ export type { AcquisitionConnectionLifecycle, AcquisitionConnectionLifecycleOptions, } from './acquisition-lifecycle';
13
+ export type { DirectArtifactConnectionOptions, FlowersecConnectionConfig, ProxyRuntimeTunnelConnectionOptions, TunnelArtifactConnectionOptions, } from './connection';
9
14
  export { createArtifactDirectConnectionConfig, createArtifactTunnelConnectionConfig, createProxyRuntimeTunnelConnectionConfig, } from './connection';
10
15
  export type { FetchServerSentEventsOptions, ServerSentEvent, ServerSentEventStreamErrorCode, } from './server-sent-events';
11
16
  export { fetchServerSentEvents, ServerSentEventStreamError, } from './server-sent-events';
12
- export type { ScopeEnvelope, ScopeResolver, ScopeResolverMap } from './scope';
17
+ export { closeProxyBootstrap, createProxyBootstrapOwner, ProxyBootstrapOwner, synchronizeProxyBootstrap, } from './proxy-bootstrap';
18
+ export type { ControllerBridgeProxyBootstrapContext, ProxyBootstrapBinding, ProxyBootstrapOwnerOptions, ProxyBootstrapSnapshot, ServiceWorkerProxyBootstrapContext, } from './proxy-bootstrap';
19
+ export type { ScopeEnvelope, ScopeResolver, ScopeResolverMap, ValidatedCriticalScopeProjection, } from './scope';
13
20
  export { createBootstrapScopeResolvers, FLOWERSEC_BOOTSTRAP_SCOPE_RESOLVERS, PROXY_RUNTIME_SCOPE_NAME, validateProxyRuntimeScopeEntry, } from './scope';
package/dist/index.js CHANGED
@@ -1,29 +1,43 @@
1
- import { base64UrlToBase64 as o, clearLocationHash as t, parseBase64UrlJsonFromHash as n, parseHashParam as a } from "./hash.js";
2
- import { postMessageToOrigins as i, waitForMessage as S } from "./messaging.js";
3
- import { getSessionStorage as p, removeSessionStorage as f, setSessionStorage as m } from "./storage.js";
4
- import { ControlplaneRequestError as l, createControlplaneArtifactSource as C } from "./artifact-source.js";
5
- import { createArtifactDirectConnectionConfig as R, createArtifactTunnelConnectionConfig as x, createProxyRuntimeTunnelConnectionConfig as O } from "./connection.js";
6
- import { ServerSentEventStreamError as P, fetchServerSentEvents as T } from "./server-sent-events.js";
7
- import { FLOWERSEC_BOOTSTRAP_SCOPE_RESOLVERS as _, PROXY_RUNTIME_SCOPE_NAME as A, createBootstrapScopeResolvers as h, validateProxyRuntimeScopeEntry as B } from "./scope.js";
1
+ import { clearLocationHash as r, parseHashParam as t } from "./hash.js";
2
+ import { postMessageToOrigins as i, waitForMessage as a } from "./messaging.js";
3
+ import { getSessionStorage as c, removeSessionStorage as S, setSessionStorage as p } from "./storage.js";
4
+ import { ControlplaneRequestError as f, classifyControlplaneFailure as m, createControlplaneArtifactSource as x } from "./artifact-source.js";
5
+ import { AcquisitionError as C, ConnectedAcquisition as E, IsolatedOneShotAcquisition as O, clearAcquisitionSource as y, connectIsolatedOneShot as A, materializeIsolatedOneShot as P, synchronizeAcquisitionSourceSnapshot as g } from "./acquisition.js";
6
+ import { createAcquisitionConnectionLifecycle as h } from "./acquisition-lifecycle.js";
7
+ import { createArtifactDirectConnectionConfig as v, createArtifactTunnelConnectionConfig as B, createProxyRuntimeTunnelConnectionConfig as T } from "./connection.js";
8
+ import { ServerSentEventStreamError as d, fetchServerSentEvents as I } from "./server-sent-events.js";
9
+ import { ProxyBootstrapOwner as M, closeProxyBootstrap as w, createProxyBootstrapOwner as z, synchronizeProxyBootstrap as F } from "./proxy-bootstrap.js";
10
+ import { FLOWERSEC_BOOTSTRAP_SCOPE_RESOLVERS as N, PROXY_RUNTIME_SCOPE_NAME as D, createBootstrapScopeResolvers as U, validateProxyRuntimeScopeEntry as V } from "./scope.js";
8
11
  export {
9
- l as ControlplaneRequestError,
10
- _ as FLOWERSEC_BOOTSTRAP_SCOPE_RESOLVERS,
11
- A as PROXY_RUNTIME_SCOPE_NAME,
12
- P as ServerSentEventStreamError,
13
- o as base64UrlToBase64,
14
- t as clearLocationHash,
15
- R as createArtifactDirectConnectionConfig,
16
- x as createArtifactTunnelConnectionConfig,
17
- h as createBootstrapScopeResolvers,
18
- C as createControlplaneArtifactSource,
19
- O as createProxyRuntimeTunnelConnectionConfig,
20
- T as fetchServerSentEvents,
21
- p as getSessionStorage,
22
- n as parseBase64UrlJsonFromHash,
23
- a as parseHashParam,
12
+ C as AcquisitionError,
13
+ E as ConnectedAcquisition,
14
+ f as ControlplaneRequestError,
15
+ N as FLOWERSEC_BOOTSTRAP_SCOPE_RESOLVERS,
16
+ O as IsolatedOneShotAcquisition,
17
+ D as PROXY_RUNTIME_SCOPE_NAME,
18
+ M as ProxyBootstrapOwner,
19
+ d as ServerSentEventStreamError,
20
+ m as classifyControlplaneFailure,
21
+ y as clearAcquisitionSource,
22
+ r as clearLocationHash,
23
+ w as closeProxyBootstrap,
24
+ A as connectIsolatedOneShot,
25
+ h as createAcquisitionConnectionLifecycle,
26
+ v as createArtifactDirectConnectionConfig,
27
+ B as createArtifactTunnelConnectionConfig,
28
+ U as createBootstrapScopeResolvers,
29
+ x as createControlplaneArtifactSource,
30
+ z as createProxyBootstrapOwner,
31
+ T as createProxyRuntimeTunnelConnectionConfig,
32
+ I as fetchServerSentEvents,
33
+ c as getSessionStorage,
34
+ P as materializeIsolatedOneShot,
35
+ t as parseHashParam,
24
36
  i as postMessageToOrigins,
25
- f as removeSessionStorage,
26
- m as setSessionStorage,
27
- B as validateProxyRuntimeScopeEntry,
28
- S as waitForMessage
37
+ S as removeSessionStorage,
38
+ p as setSessionStorage,
39
+ g as synchronizeAcquisitionSourceSnapshot,
40
+ F as synchronizeProxyBootstrap,
41
+ V as validateProxyRuntimeScopeEntry,
42
+ a as waitForMessage
29
43
  };
@@ -0,0 +1,39 @@
1
+ import { type ProxyRuntime } from '@floegence/flowersec-core/proxy';
2
+ import { ConnectedAcquisition } from './acquisition';
3
+ export type ProxyBootstrapBinding = Readonly<{
4
+ dispose(): void;
5
+ }>;
6
+ export type ServiceWorkerProxyBootstrapContext = Readonly<{
7
+ runtime: ProxyRuntime;
8
+ generation: number;
9
+ bindingIdentity: string;
10
+ scriptUrl: string;
11
+ serviceWorkerScope: string;
12
+ appBasePath?: string;
13
+ }>;
14
+ export type ControllerBridgeProxyBootstrapContext = Readonly<{
15
+ runtime: ProxyRuntime;
16
+ generation: number;
17
+ bindingIdentity: string;
18
+ allowedOrigins: readonly string[];
19
+ appBasePath?: string;
20
+ capabilityNonce: string;
21
+ }>;
22
+ export type ProxyBootstrapOwnerOptions = Readonly<{
23
+ serviceWorker?: (context: ServiceWorkerProxyBootstrapContext) => ProxyBootstrapBinding;
24
+ controllerBridge?: (context: ControllerBridgeProxyBootstrapContext) => ProxyBootstrapBinding;
25
+ }>;
26
+ export type ProxyBootstrapSnapshot = Readonly<{
27
+ generation: number;
28
+ mode: 'service_worker' | 'controller_bridge';
29
+ bindingIdentity: string;
30
+ }>;
31
+ export declare class ProxyBootstrapOwner {
32
+ #private;
33
+ private constructor();
34
+ static create(): ProxyBootstrapOwner;
35
+ static assertAuthentic(value: ProxyBootstrapOwner): void;
36
+ }
37
+ export declare function createProxyBootstrapOwner(options: ProxyBootstrapOwnerOptions): ProxyBootstrapOwner;
38
+ export declare function synchronizeProxyBootstrap(owner: ProxyBootstrapOwner, acquisition: ConnectedAcquisition | null): ProxyBootstrapSnapshot | null;
39
+ export declare function closeProxyBootstrap(owner: ProxyBootstrapOwner): void;
@@ -0,0 +1,97 @@
1
+ import { createProxyRuntime as f } from "@floegence/flowersec-core/proxy";
2
+ import { connectedAcquisitionDetails as g, AcquisitionError as a } from "./acquisition.js";
3
+ class d {
4
+ #e = void 0;
5
+ constructor() {
6
+ Object.freeze(this);
7
+ }
8
+ static create() {
9
+ return new d();
10
+ }
11
+ static assertAuthentic(t) {
12
+ t.#e;
13
+ }
14
+ }
15
+ const b = /* @__PURE__ */ new WeakMap();
16
+ function m(e) {
17
+ const t = d.create();
18
+ return b.set(t, { options: e, generation: 0 }), t;
19
+ }
20
+ function B(e, t) {
21
+ const o = y(e);
22
+ if (t === null)
23
+ return l(o), null;
24
+ if (o.acquisition === t && o.snapshot !== void 0) return o.snapshot;
25
+ const r = g(t);
26
+ l(o);
27
+ const c = o.generation + 1;
28
+ o.generation = c;
29
+ const n = r.scope, p = f({
30
+ session: r.session,
31
+ ...n.limits ?? {},
32
+ ...n.appBasePath === void 0 ? {} : { pathPolicy: { allowedPathPrefixes: Object.freeze([n.appBasePath]) } }
33
+ });
34
+ try {
35
+ let i;
36
+ if (n.mode === "service_worker") {
37
+ const s = o.options.serviceWorker;
38
+ if (s === void 0) throw new a("service_worker_bootstrap_unavailable");
39
+ i = s(Object.freeze({
40
+ runtime: p,
41
+ generation: c,
42
+ bindingIdentity: r.bindingIdentity,
43
+ scriptUrl: n.serviceWorker.scriptUrl,
44
+ serviceWorkerScope: n.serviceWorker.scope,
45
+ ...n.appBasePath === void 0 ? {} : { appBasePath: n.appBasePath }
46
+ }));
47
+ } else {
48
+ const s = o.options.controllerBridge;
49
+ if (s === void 0) throw new a("controller_bridge_bootstrap_unavailable");
50
+ i = s(Object.freeze({
51
+ runtime: p,
52
+ generation: c,
53
+ bindingIdentity: r.bindingIdentity,
54
+ allowedOrigins: n.controllerBridge.allowedOrigins,
55
+ ...n.appBasePath === void 0 ? {} : { appBasePath: n.appBasePath },
56
+ capabilityNonce: h()
57
+ }));
58
+ }
59
+ if (i === null || typeof i != "object" || typeof i.dispose != "function")
60
+ throw new a("invalid_proxy_bootstrap_binding");
61
+ const u = Object.freeze({ generation: c, mode: n.mode, bindingIdentity: r.bindingIdentity });
62
+ return o.acquisition = t, o.runtime = p, o.binding = i, o.snapshot = u, u;
63
+ } catch (i) {
64
+ throw p.dispose(), i;
65
+ }
66
+ }
67
+ function P(e) {
68
+ l(y(e));
69
+ }
70
+ function y(e) {
71
+ d.assertAuthentic(e);
72
+ const t = b.get(e);
73
+ if (t === void 0) throw new a("invalid_proxy_bootstrap_owner");
74
+ return t;
75
+ }
76
+ function l(e) {
77
+ const t = e.binding, o = e.runtime;
78
+ e.acquisition = void 0, e.binding = void 0, e.runtime = void 0, e.snapshot = void 0;
79
+ try {
80
+ t?.dispose();
81
+ } finally {
82
+ o?.dispose();
83
+ }
84
+ }
85
+ function h() {
86
+ if (globalThis.crypto?.getRandomValues === void 0) throw new a("acquisition_entropy_unavailable");
87
+ const e = globalThis.crypto.getRandomValues(new Uint8Array(32));
88
+ let t = "";
89
+ for (const o of e) t += String.fromCharCode(o);
90
+ return btoa(t).replace(/\+/gu, "-").replace(/\//gu, "_").replace(/=+$/u, "");
91
+ }
92
+ export {
93
+ d as ProxyBootstrapOwner,
94
+ P as closeProxyBootstrap,
95
+ m as createProxyBootstrapOwner,
96
+ B as synchronizeProxyBootstrap
97
+ };
package/dist/scope.d.ts CHANGED
@@ -1,11 +1,20 @@
1
+ import { type ProxyRuntimeScope } from '@floegence/flowersec-core/proxy';
1
2
  export declare const PROXY_RUNTIME_SCOPE_NAME = "proxy.runtime";
2
3
  export type ScopeEnvelope = Readonly<{
4
+ scope: string;
3
5
  scope_version: number;
6
+ critical: boolean;
4
7
  payload: unknown;
5
8
  }>;
6
- export type ScopeResolver = (entry: ScopeEnvelope) => void;
9
+ export type ValidatedCriticalScopeProjection = Readonly<{
10
+ scope: 'proxy.runtime';
11
+ scope_version: 2;
12
+ critical: true;
13
+ payload: ProxyRuntimeScope;
14
+ }>;
15
+ export type ScopeResolver = (entry: ScopeEnvelope) => ValidatedCriticalScopeProjection;
7
16
  export type ScopeResolverMap = Readonly<Record<string, ScopeResolver>>;
8
- export declare function validateProxyRuntimeScopeEntry(entry: ScopeEnvelope): void;
17
+ export declare function validateProxyRuntimeScopeEntry(entry: ScopeEnvelope): ValidatedCriticalScopeProjection;
9
18
  export declare const FLOWERSEC_BOOTSTRAP_SCOPE_RESOLVERS: Readonly<{
10
19
  "proxy.runtime": typeof validateProxyRuntimeScopeEntry;
11
20
  }>;
package/dist/scope.js CHANGED
@@ -1,22 +1,29 @@
1
- import { PROXY_RUNTIME_SCOPE as t, assertProxyRuntimeScope as n } from "@floegence/flowersec-core/proxy";
2
- const o = "proxy.runtime";
3
- function r(e) {
4
- if (e.scope_version !== t.version)
5
- throw new Error(`unsupported ${o} scope_version: ${e.scope_version}`);
6
- n(e.payload);
1
+ import { PROXY_RUNTIME_SCOPE as o, assertProxyRuntimeScope as t } from "@floegence/flowersec-core/proxy";
2
+ const r = "proxy.runtime";
3
+ function c(e) {
4
+ if (e === null || typeof e != "object" || Array.isArray(e) || Object.keys(e).length !== 4 || !Object.keys(e).every((i) => ["scope", "scope_version", "critical", "payload"].includes(i)))
5
+ throw new TypeError(`invalid ${r} projection envelope`);
6
+ if (e.scope !== o.name || e.scope_version !== o.version || e.critical !== !0)
7
+ throw new TypeError(`unsupported ${r} critical scope projection`);
8
+ return Object.freeze({
9
+ scope: o.name,
10
+ scope_version: o.version,
11
+ critical: !0,
12
+ payload: t(e.payload)
13
+ });
7
14
  }
8
- const s = Object.freeze({
9
- [o]: r
15
+ const p = Object.freeze({
16
+ [r]: c
10
17
  });
11
- function i(e) {
18
+ function n(e) {
12
19
  return e ? Object.freeze({
13
20
  ...e,
14
- [o]: r
15
- }) : s;
21
+ [r]: c
22
+ }) : p;
16
23
  }
17
24
  export {
18
- s as FLOWERSEC_BOOTSTRAP_SCOPE_RESOLVERS,
19
- o as PROXY_RUNTIME_SCOPE_NAME,
20
- i as createBootstrapScopeResolvers,
21
- r as validateProxyRuntimeScopeEntry
25
+ p as FLOWERSEC_BOOTSTRAP_SCOPE_RESOLVERS,
26
+ r as PROXY_RUNTIME_SCOPE_NAME,
27
+ n as createBootstrapScopeResolvers,
28
+ c as validateProxyRuntimeScopeEntry
22
29
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floegence/floe-webapp-boot",
3
- "version": "0.40.21",
3
+ "version": "0.41.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -34,7 +34,7 @@
34
34
  "clean": "rm -rf dist *.tsbuildinfo"
35
35
  },
36
36
  "dependencies": {
37
- "@floegence/flowersec-core": "2.4.2"
37
+ "@floegence/flowersec-core": "2.5.1"
38
38
  },
39
39
  "devDependencies": {
40
40
  "typescript": "^5.9.3",