@floegence/floe-webapp-protocol 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.
package/dist/client.d.ts CHANGED
@@ -1,20 +1,31 @@
1
+ import type { ArtifactSource, ConnectionSnapshot, ConnectionState, Session } from '@floegence/flowersec-core';
2
+ import type { ConnectionControllerOptions } from '@floegence/flowersec-core/browser';
1
3
  import { type JSX } from 'solid-js';
2
- import type { ArtifactSource, ConnectionControllerOptions, ConnectionState, Session } from '@floegence/flowersec-core';
3
4
  import type { ProtocolContract } from './contract';
4
5
  interface ProtocolContextValue {
5
6
  status: () => ConnectionState;
7
+ snapshot: () => ConnectionSnapshot;
6
8
  error: () => Error | null;
7
9
  session: () => Session | null;
8
10
  rpcTransport: () => Session['rpc'] | null;
9
11
  contract: () => ProtocolContract;
10
12
  connect: (config: ConnectConfig) => Promise<void>;
11
- reconnect: (config?: ConnectConfig) => Promise<void>;
13
+ replaceConnection: (config: ConnectConfig) => Promise<void>;
14
+ retryNow: () => boolean;
12
15
  disconnect: () => void;
13
16
  }
17
+ export interface ConnectionLifecycle {
18
+ synchronize(snapshot: ConnectionSnapshot): void;
19
+ dispose(): void;
20
+ }
14
21
  export type ConnectConfig = Readonly<{
15
22
  source: ArtifactSource;
16
23
  controller?: ConnectionControllerOptions;
24
+ lifecycle?: ConnectionLifecycle;
17
25
  }>;
26
+ export declare class ConnectionReplacementRequiredError extends Error {
27
+ constructor();
28
+ }
18
29
  export declare function ProtocolProvider(props: {
19
30
  children: JSX.Element;
20
31
  contract: ProtocolContract;
@@ -1,26 +1,15 @@
1
- export type RpcTransportCall = (typeId: number, payload: unknown) => Promise<{
2
- payload: unknown;
3
- error?: {
4
- code: number;
5
- message?: string;
6
- };
7
- }>;
8
- export type RpcTransportNotify = (typeId: number, payload: unknown) => Promise<void> | void;
9
- export type RpcTransportOnNotify = (typeId: number, handler: (payload: unknown) => void) => () => void;
1
+ import type { JsonValue, RpcPeer } from '@floegence/flowersec-core';
10
2
  export interface RpcClientLike {
11
- rpc: {
12
- call: RpcTransportCall;
13
- notify: RpcTransportNotify;
14
- onNotify: RpcTransportOnNotify;
15
- };
3
+ rpc: Pick<RpcPeer, 'call' | 'notify' | 'onNotify'>;
16
4
  }
5
+ export type RpcDecoder<T> = (payload: JsonValue) => T;
17
6
  export interface RpcHelpers {
18
- call: <Req, Res>(typeId: number, payload: Req) => Promise<Res>;
7
+ call: <Req extends JsonValue, Res>(typeId: number, payload: Req, decodeResponse: RpcDecoder<Res>) => Promise<Res>;
19
8
  /** Strict notification: detached transports fail with ProtocolNotConnectedError. */
20
- notify: <Req>(typeId: number, payload: Req) => Promise<void>;
9
+ notify: <Req extends JsonValue>(typeId: number, payload: Req) => Promise<void>;
21
10
  /** Best-effort notification: detached transports are treated as already dropped. */
22
- notifyBestEffort: <Req>(typeId: number, payload: Req) => Promise<void>;
23
- onNotify: <Payload>(typeId: number, handler: (payload: Payload) => void) => () => void;
11
+ notifyBestEffort: <Req extends JsonValue>(typeId: number, payload: Req) => Promise<void>;
12
+ onNotify: <Payload>(typeId: number, decodePayload: RpcDecoder<Payload>, handler: (payload: Payload) => void | Promise<void>) => () => void;
24
13
  }
25
14
  export interface ProtocolContract<TApi = unknown> {
26
15
  /** A stable identifier for logging and debugging (e.g. "app_v1"). */
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export { ProtocolProvider, useProtocol, type ConnectConfig } from './client';
1
+ export { ConnectionReplacementRequiredError, ProtocolProvider, useProtocol, type ConnectConfig, type ConnectionLifecycle, } from './client';
2
2
  export { useRpc, RpcError, ProtocolNotConnectedError } from './rpc';
3
- export { ControlplaneRequestError, requestConnectArtifact, requestEntryConnectArtifact, type RequestConnectArtifactInput, type RequestEntryConnectArtifactInput, } from './controlplane';
4
- export type { ProtocolContract, RpcClientLike, RpcHelpers } from './contract';
3
+ export type { ProtocolContract, RpcClientLike, RpcDecoder, RpcHelpers, } from './contract';
package/dist/index.js CHANGED
@@ -1,13 +1,10 @@
1
- import { ProtocolProvider as t, useProtocol as e } from "./index2.js";
2
- import { ProtocolNotConnectedError as n, RpcError as p, useRpc as f } from "./index3.js";
3
- import { ControlplaneRequestError as s, requestConnectArtifact as u, requestEntryConnectArtifact as C } from "./index4.js";
1
+ import { ConnectionReplacementRequiredError as e, ProtocolProvider as t, useProtocol as c } from "./index2.js";
2
+ import { ProtocolNotConnectedError as p, RpcError as l, useRpc as P } from "./index3.js";
4
3
  export {
5
- s as ControlplaneRequestError,
6
- n as ProtocolNotConnectedError,
4
+ e as ConnectionReplacementRequiredError,
5
+ p as ProtocolNotConnectedError,
7
6
  t as ProtocolProvider,
8
- p as RpcError,
9
- u as requestConnectArtifact,
10
- C as requestEntryConnectArtifact,
11
- e as useProtocol,
12
- f as useRpc
7
+ l as RpcError,
8
+ c as useProtocol,
9
+ P as useRpc
13
10
  };
package/dist/index2.js CHANGED
@@ -1,72 +1,114 @@
1
- import { createContext as b, onCleanup as y, createComponent as h, useContext as x } from "solid-js";
2
- import { createStore as S } from "solid-js/store";
3
- const E = () => import("@floegence/flowersec-core/browser"), w = b();
4
- function F(r) {
5
- const [s, u] = S({
6
- status: "idle",
7
- error: null,
8
- session: null
9
- }), C = r.contract;
10
- let o = null, c = null, a = null, n = null, l = 0;
11
- const m = (t) => {
12
- const e = t.failure;
13
- u({
14
- status: t.state,
15
- session: t.currentSession ?? null,
16
- error: e === void 0 ? null : new Error(`${e.phase}:${e.code}`)
17
- });
18
- }, f = async () => {
19
- c?.(), c = null;
20
- const t = o;
21
- o = null, t && await t.close(), u({
22
- status: "idle",
23
- session: null,
1
+ import { createContext as b, onCleanup as v, createComponent as P, useContext as S } from "solid-js";
2
+ import { createStore as x } from "solid-js/store";
3
+ const E = () => import("@floegence/flowersec-core/browser");
4
+ class z extends Error {
5
+ constructor() {
6
+ super("Connection source or options changed; call replaceConnection() explicitly"), this.name = "ConnectionReplacementRequiredError";
7
+ }
8
+ }
9
+ const w = b(), y = Object.freeze({
10
+ state: "idle",
11
+ attempt: 0
12
+ });
13
+ function D(t) {
14
+ const [n, f] = x({
15
+ snapshot: y,
16
+ error: null
17
+ }), C = t.contract;
18
+ let o = null, a = null, c = null, r = null, s = 0;
19
+ const m = (e) => {
20
+ const l = c;
21
+ if (l !== null)
22
+ try {
23
+ l.lifecycle?.synchronize(e), f({
24
+ snapshot: e,
25
+ error: O(e)
26
+ });
27
+ } catch (u) {
28
+ const i = u instanceof Error ? u : new Error("Floe connection binding failed");
29
+ l.lifecycle?.dispose(), f({
30
+ snapshot: Object.freeze({
31
+ state: "failed",
32
+ attempt: e.attempt,
33
+ failure: Object.freeze({
34
+ phase: "artifact",
35
+ code: "connected_acquisition_failed"
36
+ }),
37
+ retryDisposition: Object.freeze({
38
+ kind: "terminal"
39
+ })
40
+ }),
41
+ error: i
42
+ }), o?.close();
43
+ }
44
+ }, p = async () => {
45
+ a?.(), a = null;
46
+ const e = o, l = c;
47
+ o = null, c = null, l?.lifecycle?.dispose(), e !== null && await e.close(), f({
48
+ snapshot: y,
24
49
  error: null
25
50
  });
26
- }, p = async (t) => {
27
- const e = ++l;
28
- await f(), a = t;
29
- const {
30
- createConnectionController: P
51
+ }, d = async (e) => {
52
+ const l = s, {
53
+ createConnectionController: u
31
54
  } = await E();
32
- if (e !== l) return;
33
- const i = P(t.source, t.controller);
34
- o = i, c = i.subscribe(m), i.start(), await i.waitForSession();
35
- }, d = async (t) => n || (n = (async () => {
36
- o && a?.source === t.source && o.state === "connected" || await p(t);
37
- })().finally(() => {
38
- n = null;
39
- }), n), v = {
40
- status: () => s.status,
41
- error: () => s.error,
42
- session: () => s.session,
43
- rpcTransport: () => s.session?.rpc ?? null,
55
+ if (l !== s) return;
56
+ const i = u(e.source, e.controller);
57
+ c = e, o = i, a = i.subscribe(m), i.start(), await i.waitForSession();
58
+ }, h = {
59
+ status: () => n.snapshot.state,
60
+ snapshot: () => n.snapshot,
61
+ error: () => n.error,
62
+ session: () => n.snapshot.currentSession ?? null,
63
+ rpcTransport: () => n.snapshot.currentSession?.rpc ?? null,
44
64
  contract: () => C,
45
- connect: d,
46
- reconnect: async (t) => {
47
- const e = t ?? a;
48
- if (!e) throw new Error("reconnect() requires a config before the first connect() call");
49
- await d(e);
65
+ connect: async (e) => {
66
+ if (o !== null && c !== null && !g(c, e))
67
+ throw new z();
68
+ return r !== null || (r = (async () => {
69
+ if (o === null) {
70
+ await d(e);
71
+ return;
72
+ }
73
+ o.start(), await o.waitForSession();
74
+ })().finally(() => {
75
+ r = null;
76
+ })), r;
50
77
  },
78
+ replaceConnection: async (e) => (r !== null && await r.catch(() => {
79
+ }), r = (async () => {
80
+ s += 1, await p(), s += 1, await d(e);
81
+ })().finally(() => {
82
+ r = null;
83
+ }), r),
84
+ retryNow: () => o?.retryNow() ?? !1,
51
85
  disconnect: () => {
52
- l += 1, f();
86
+ s += 1, p();
53
87
  }
54
88
  };
55
- return y(() => {
56
- l += 1, c?.(), o?.close();
57
- }), h(w.Provider, {
58
- value: v,
89
+ return v(() => {
90
+ s += 1, a?.(), c?.lifecycle?.dispose(), o?.close();
91
+ }), P(w.Provider, {
92
+ value: h,
59
93
  get children() {
60
- return r.children;
94
+ return t.children;
61
95
  }
62
96
  });
63
97
  }
64
- function G() {
65
- const r = x(w);
66
- if (!r) throw new Error("useProtocol must be used within a ProtocolProvider");
67
- return r;
98
+ function T() {
99
+ const t = S(w);
100
+ if (!t) throw new Error("useProtocol must be used within a ProtocolProvider");
101
+ return t;
102
+ }
103
+ function g(t, n) {
104
+ return t.source === n.source && t.controller === n.controller && t.lifecycle === n.lifecycle;
105
+ }
106
+ function O(t) {
107
+ const n = t.failure;
108
+ return n === void 0 ? null : new Error(`${n.phase}:${n.code}`);
68
109
  }
69
110
  export {
70
- F as ProtocolProvider,
71
- G as useProtocol
111
+ z as ConnectionReplacementRequiredError,
112
+ D as ProtocolProvider,
113
+ T as useProtocol
72
114
  };
package/dist/index3.js CHANGED
@@ -1,5 +1,5 @@
1
- import { useProtocol as u } from "./index2.js";
2
- class d extends Error {
1
+ import { useProtocol as y } from "./index2.js";
2
+ class f extends Error {
3
3
  constructor() {
4
4
  super("Not connected"), this.name = "ProtocolNotConnectedError";
5
5
  }
@@ -11,54 +11,52 @@ class p extends Error {
11
11
  super(o.message ?? `RPC error: ${o.code}`, { cause: o.cause }), this.name = "RpcError", this.typeId = o.typeId, this.code = o.code;
12
12
  }
13
13
  }
14
- function y(c) {
15
- const o = async (t, e) => {
16
- const n = c.rpcTransport();
17
- if (!n) throw new d();
18
- let r;
14
+ function h(n) {
15
+ const o = async (r, t, s) => {
16
+ const c = n.rpcTransport();
17
+ if (!c) throw new f();
18
+ let e;
19
19
  try {
20
- r = await n.call(t, e, (s) => s);
21
- } catch (s) {
22
- throw new p({ typeId: t, code: -1, message: "RPC transport error", cause: s });
20
+ e = await c.call(r, t, s);
21
+ } catch (u) {
22
+ throw new p({ typeId: r, code: -1, message: "RPC transport or response decode error", cause: u });
23
23
  }
24
- if (!r.ok)
24
+ if (!e.ok)
25
25
  throw new p({
26
- typeId: t,
27
- code: r.error.code,
28
- message: r.error.message ?? `RPC error: ${r.error.code}`,
29
- cause: r.error
26
+ typeId: r,
27
+ code: e.error.code,
28
+ message: e.error.message ?? `RPC error: ${e.error.code}`,
29
+ cause: e.error
30
30
  });
31
- return r.payload;
32
- }, a = async (t, e, n) => {
33
- const r = c.rpcTransport();
34
- if (!r) {
35
- if (n.detached === "ignore") return;
36
- throw new d();
31
+ return e.payload;
32
+ }, a = async (r, t, s) => {
33
+ const c = n.rpcTransport();
34
+ if (!c) {
35
+ if (s.detached === "ignore") return;
36
+ throw new f();
37
37
  }
38
38
  try {
39
- await r.notify(t, e);
40
- } catch (s) {
41
- throw new p({ typeId: t, code: -1, message: "RPC notify transport error", cause: s });
39
+ await c.notify(r, t);
40
+ } catch (e) {
41
+ throw new p({ typeId: r, code: -1, message: "RPC notify transport error", cause: e });
42
42
  }
43
43
  };
44
- return { call: o, notify: async (t, e) => {
45
- await a(t, e, { detached: "throw" });
46
- }, notifyBestEffort: async (t, e) => {
47
- await a(t, e, { detached: "ignore" });
48
- }, onNotify: (t, e) => {
49
- const n = c.rpcTransport();
50
- return n ? n.onNotify(t, (r) => r, (r) => {
51
- e(r);
52
- }) : () => {
44
+ return { call: o, notify: async (r, t) => {
45
+ await a(r, t, { detached: "throw" });
46
+ }, notifyBestEffort: async (r, t) => {
47
+ await a(r, t, { detached: "ignore" });
48
+ }, onNotify: (r, t, s) => {
49
+ const c = n.rpcTransport();
50
+ return c ? c.onNotify(r, t, s) : () => {
53
51
  };
54
52
  } };
55
53
  }
56
- function l(c) {
57
- const o = u(), a = c?.contract ?? o.contract(), i = y(o), f = a.createRpc(i);
58
- return Object.assign(f, i);
54
+ function m(n) {
55
+ const o = y(), a = n?.contract ?? o.contract(), i = h(o), d = a.createRpc(i);
56
+ return Object.assign(d, i);
59
57
  }
60
58
  export {
61
- d as ProtocolNotConnectedError,
59
+ f as ProtocolNotConnectedError,
62
60
  p as RpcError,
63
- l as useRpc
61
+ m as useRpc
64
62
  };
package/dist/rpc.d.ts CHANGED
@@ -1,9 +1,4 @@
1
1
  import type { ProtocolContract, RpcHelpers } from './contract';
2
- /**
3
- * RPC wrapper for typed remote calls.
4
- *
5
- * This module is contract-driven: a contract defines TypeIds + wire codecs + domain surface.
6
- */
7
2
  export declare class ProtocolNotConnectedError extends Error {
8
3
  constructor();
9
4
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floegence/floe-webapp-protocol",
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",
@@ -33,7 +33,7 @@
33
33
  "solid-js": "^1.8.0"
34
34
  },
35
35
  "dependencies": {
36
- "@floegence/flowersec-core": "2.4.2"
36
+ "@floegence/flowersec-core": "2.5.1"
37
37
  },
38
38
  "devDependencies": {
39
39
  "solid-js": "^1.9.11",
@@ -1,20 +0,0 @@
1
- import { type Artifact, type JsonObject } from '@floegence/flowersec-core';
2
- export type RequestConnectArtifactInput = Readonly<{
3
- baseUrl: string;
4
- endpointId: string;
5
- payload?: JsonObject;
6
- correlation?: Readonly<{
7
- traceId?: string;
8
- }>;
9
- fetch?: typeof globalThis.fetch;
10
- }>;
11
- export type RequestEntryConnectArtifactInput = RequestConnectArtifactInput & Readonly<{
12
- entryTicket: string;
13
- }>;
14
- export declare class ControlplaneRequestError extends Error {
15
- readonly status: number;
16
- readonly code: string;
17
- constructor(status: number, code: string, message: string);
18
- }
19
- export declare function requestConnectArtifact(input: RequestConnectArtifactInput): Promise<Artifact>;
20
- export declare function requestEntryConnectArtifact(input: RequestEntryConnectArtifactInput): Promise<Artifact>;
package/dist/index4.js DELETED
@@ -1,42 +0,0 @@
1
- import { parseArtifact as f } from "@floegence/flowersec-core";
2
- class a extends Error {
3
- constructor(c, o, e) {
4
- super(e), this.status = c, this.code = o, this.name = "ControlplaneRequestError";
5
- }
6
- }
7
- async function i(t) {
8
- const c = t.fetch ?? globalThis.fetch, o = new URL(t.baseUrl);
9
- if (o.protocol !== "https:") throw new a(0, "transport_policy_denied", "controlplane transport policy denied");
10
- const e = "entryTicket" in t, s = new URL(`/v1/connect/artifact${e ? "/entry" : ""}`, o).toString(), l = {
11
- endpoint_id: t.endpointId,
12
- ...t.payload === void 0 ? {} : { payload: t.payload },
13
- ...!e && t.correlation === void 0 ? {} : e ? {} : { correlation: { trace_id: t.correlation?.traceId } }
14
- }, r = await c(s, {
15
- method: "POST",
16
- headers: {
17
- accept: "application/json",
18
- "content-type": "application/json",
19
- ...e ? { authorization: `Bearer ${t.entryTicket}` } : {}
20
- },
21
- body: JSON.stringify(l),
22
- credentials: "omit",
23
- redirect: "error"
24
- });
25
- if (!r.ok) throw new a(r.status, "request_failed", `controlplane request failed: ${r.status}`);
26
- const n = await r.json();
27
- if (n.connect_artifact === void 0)
28
- throw new a(200, "invalid_request", "Invalid controlplane response: missing `connect_artifact`");
29
- const d = typeof n.connect_artifact == "string" || n.connect_artifact instanceof Uint8Array ? n.connect_artifact : JSON.stringify(n.connect_artifact);
30
- return f(d);
31
- }
32
- function y(t) {
33
- return i(t);
34
- }
35
- function h(t) {
36
- return i(t);
37
- }
38
- export {
39
- a as ControlplaneRequestError,
40
- y as requestConnectArtifact,
41
- h as requestEntryConnectArtifact
42
- };