@floegence/flowersec-core 0.22.1 → 0.24.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.
package/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # @floegence/flowersec-core
2
2
 
3
- Flowersec core TypeScript library for the complete portable Flowersec client/server contract in Node.js, plus the browser and Service Worker runtime owned by TypeScript.
4
-
5
- Status: experimental; not audited.
3
+ The TypeScript SDK for Flowersec end-to-end encrypted direct and tunneled sessions. It implements the portable Node.js client and endpoint contract plus the browser and Service Worker runtime owned by TypeScript.
6
4
 
7
5
  ## Install
8
6
 
@@ -10,73 +8,27 @@ Status: experimental; not audited.
10
8
  npm install @floegence/flowersec-core
11
9
  ```
12
10
 
13
- ## Recommended usage
14
-
15
- Browser:
16
-
17
- ```ts
18
- import { connectBrowser } from "@floegence/flowersec-core/browser";
19
- import { requestConnectArtifact } from "@floegence/flowersec-core/controlplane";
20
-
21
- const artifact = await requestConnectArtifact({
22
- endpointId: "env_demo",
23
- });
24
-
25
- const client = await connectBrowser(artifact);
26
- await client.ping();
27
- client.close();
28
- ```
29
-
30
- Node.js:
31
-
32
- ```ts
33
- import { connectNode, createNodeReconnectConfig } from "@floegence/flowersec-core/node";
34
- import { requestConnectArtifact } from "@floegence/flowersec-core/controlplane";
35
- import { createControlplaneArtifactSource } from "@floegence/flowersec-core/reconnect";
36
-
37
- const artifact = await requestConnectArtifact({
38
- baseUrl: "https://your-app.example/api/flowersec",
39
- endpointId: "env_demo",
40
- });
41
-
42
- const client = await connectNode(artifact, {
43
- origin: "https://your-app.example",
44
- });
45
- await client.ping();
46
- const rttMs = await client.probeLiveness();
47
- client.close();
48
-
49
- const reconnectConfig = createNodeReconnectConfig({
50
- source: createControlplaneArtifactSource({
51
- baseUrl: "https://your-app.example/api/flowersec",
52
- endpointId: "env_demo",
53
- }),
54
- connect: {
55
- origin: "https://your-app.example",
56
- },
57
- });
58
- ```
11
+ The package is ESM-only and exposes environment-specific entrypoints for browser and Node.js applications.
59
12
 
60
- Browser `requestConnectArtifact(...)`, `requestEntryConnectArtifact(...)`, and `ControlplaneRequestError` remain available from `@floegence/flowersec-core/browser` as stable aliases.
13
+ ## Cookbook
61
14
 
62
- High-level connects use `RequireTLS` by default. `AllowPlaintextForLoopback` permits only literal loopback
63
- targets without DNS resolution. Deliberate non-loopback `ws://` connections must use
64
- `createNetworkPlaintextPolicy(...)` with exact canonical IP literals and
65
- `PlaintextRiskAcceptance.acceptPreE2ECredentialExposure`. The unrestricted `AllowPlaintext` preset is deprecated.
15
+ Start with the [TypeScript cookbook](https://github.com/floegence/flowersec/tree/main/examples/ts). It contains runnable browser direct/tunnel pages, Node.js clients, the shared demo server, the Service Worker proxy runtime, and manual protocol-stack references.
66
16
 
67
- ## Node endpoint and controlplane
17
+ ## Entrypoints
68
18
 
69
- `@floegence/flowersec-core/node` exports the high-level endpoint APIs for accepted direct WebSockets and server-role tunnel grants. `@floegence/flowersec-core/endpoint` provides portable session and RPC serving, while `@floegence/flowersec-core/controlplane` provides bounded artifact envelopes, FST2 tokens, issuer rotation, and channel initialization.
19
+ - Browser client: `@floegence/flowersec-core/browser`
20
+ - Node.js client and endpoint transport: `@floegence/flowersec-core/node`
21
+ - Controlplane artifact helpers: `@floegence/flowersec-core/controlplane`
22
+ - Endpoint session serving: `@floegence/flowersec-core/endpoint`
23
+ - RPC: `@floegence/flowersec-core/rpc`
24
+ - Reconnect: `@floegence/flowersec-core/reconnect`
25
+ - HTTP/WebSocket proxy and browser runtime: `@floegence/flowersec-core/proxy`
26
+ - Observability: `@floegence/flowersec-core/observability`
70
27
 
71
- ## Proxy
28
+ High-level WebSocket connections require TLS by default. Use `AllowPlaintextForLoopback` only for literal local development targets.
72
29
 
73
- `@floegence/flowersec-core/proxy` contains both portable HTTP/1 and WebSocket proxy protocols and the TypeScript-owned browser runtime. Node endpoint servers use `serveProxySession(...)`; browser applications use the Service Worker or controller bridge helpers without changing the portable stream contract.
30
+ ## Runtime Boundaries
74
31
 
75
- ## Docs
32
+ TypeScript owns browser and Service Worker integration. Shared tunnel, proxy gateway, and helper binaries remain Go-owned.
76
33
 
77
- - Frontend quickstart: `docs/FRONTEND_QUICKSTART.md`
78
- - Integration guide: `docs/INTEGRATION_GUIDE.md`
79
- - API surface contract: `docs/API_SURFACE.md`
80
- - Controlplane artifact fetch: `docs/CONTROLPLANE_ARTIFACT_FETCH.md`
81
- - Error model: `docs/ERROR_MODEL.md`
82
- - Migration guide: `docs/V0_20_MIGRATION.md`
34
+ Review the shared [API contract](../docs/API_CONTRACT.md), [protocol](../docs/PROTOCOL.md), [threat model](../docs/THREAT_MODEL.md), and [error model](../docs/ERROR_MODEL.md).
@@ -77,19 +77,19 @@ Source: `flowersec-ts/src/yamux/constants.ts`, `flowersec-ts/src/yamux/header.ts
77
77
  The TS Yamux implementation is exercised in real interop scenarios:
78
78
 
79
79
  - **TS client ↔ Go server (minimal Yamux over TCP)**:
80
- - Test: `flowersec-ts/src/e2e/yamux_interop.test.ts` (minimal tcp mode)
81
- - Go harness: `flowersec-go/internal/cmd/flowersec-yamux-harness/main.go`
80
+ - Test: `flowersec-go/internal/cmd/flowersec-interop` with the TypeScript JSONL harness
81
+ - Go reference: production `client`, `endpoint`, and Yamux-backed session APIs
82
82
  - Covers: window update race, RST handling, concurrent open/close, session close.
83
83
  - Notes: opt-in via `YAMUX_INTEROP=1` (runs Go harnesses).
84
84
  - Notes: sizes scale via `YAMUX_INTEROP_SCALE` (e.g. `2` => 20 streams / 1 MiB per stream).
85
85
  - Notes: client-initiated RST scenarios run when `YAMUX_INTEROP_CLIENT_RST=1`.
86
86
  - Notes: window-update and concurrent-open/close stress runs when `YAMUX_INTEROP_STRESS=1`.
87
87
  - **TS client ↔ Go server (full chain: E2EE + tunnel + Yamux)**:
88
- - Test: `flowersec-ts/src/e2e/yamux_interop.test.ts` (full chain mode)
88
+ - Test: the Direct/Tunnel variants in `stability/interop_matrix.json`
89
89
  - Go harness: `flowersec-go/internal/cmd/flowersec-e2e-harness/main.go` with `-scenario`
90
90
  - Notes: reduced stream counts/payload sizes to keep end-to-end runtime bounded.
91
91
  - **Layered close/reset probes (memory + WS + full chain)**:
92
- - Test: `flowersec-ts/src/e2e/yamux_interop_layers.test.ts`
92
+ - Test: the Go-reference smoke and stress profiles in `testdata/interop/v1/profiles.json`
93
93
  - Covers: session-close wakeup, FIN/RST delivery across SecureChannel + WebSocketBinaryTransport,
94
94
  and a full-chain FIN/RST probe on `rst_mid_write_go`.
95
95
  - Notes: gated by `YAMUX_INTEROP=1`; the full-chain probe additionally requires
@@ -23,7 +23,7 @@ export type ConnectOptionsBase = Readonly<{
23
23
  maxHandshakePayload?: number;
24
24
  /** Maximum encrypted record size on the wire (0 uses default). */
25
25
  maxRecordBytes?: number;
26
- /** Maximum buffered plaintext bytes in the secure channel (0 uses default). */
26
+ /** Maximum queued inbound plaintext bytes in the secure channel (0 uses default). */
27
27
  maxBufferedBytes?: number;
28
28
  /** Maximum queued outbound plaintext bytes in the secure channel (default 4 MiB; 0 uses default). */
29
29
  maxOutboundBufferedBytes?: number;
@@ -39,9 +39,9 @@ export type ConnectOptionsBase = Readonly<{
39
39
  observer?: ClientObserverLike;
40
40
  /** Policy evaluated before any WebSocket network activity. */
41
41
  transportSecurityPolicy?: TransportSecurityPolicy;
42
- /** Experimental scope validators keyed by scope name. */
42
+ /** Scope validators keyed by scope name. */
43
43
  scopeResolvers?: ConnectScopeResolverMap;
44
- /** Experimental migration switch for optional scope failures. */
44
+ /** Explicit compatibility switch for optional scope failures. */
45
45
  relaxedOptionalScopeValidation?: boolean;
46
46
  /** Acknowledged Yamux liveness checks, or false to disable automatic checks. */
47
47
  liveness?: false | LivenessOptions;
@@ -14,7 +14,7 @@ import { prepareChannelId } from "./contract.js";
14
14
  import { isTunnelAttachCloseReason } from "./tunnelAttachCloseReason.js";
15
15
  import { enforceTransportSecurity } from "./transportSecurity.js";
16
16
  import { maxPlaintextBytes } from "../e2ee/record.js";
17
- import { isYamuxResourceExhaustedError } from "../yamux/errors.js";
17
+ import { isYamuxPingTimeoutError, isYamuxResourceExhaustedError } from "../yamux/errors.js";
18
18
  import { registerClientTermination } from "./termination.js";
19
19
  export async function connectCore(args) {
20
20
  const observer = normalizeObserver(args.opts.observer, { path: args.path });
@@ -143,12 +143,6 @@ export async function connectCore(args) {
143
143
  catch (err) {
144
144
  const reason = classifyConnectError(err);
145
145
  observer.onConnect(args.path, "fail", reason, nowSeconds() - connectStart);
146
- try {
147
- transport.close();
148
- }
149
- catch {
150
- // ignore
151
- }
152
146
  const code = reason === "timeout" ? "timeout" : reason === "canceled" ? "canceled" : "dial_failed";
153
147
  throw new FlowersecError({
154
148
  path: args.path,
@@ -167,12 +161,6 @@ export async function connectCore(args) {
167
161
  catch (err) {
168
162
  observer.onAttach("fail", "send_failed");
169
163
  attachState = "failed";
170
- try {
171
- transport.close();
172
- }
173
- catch {
174
- // ignore
175
- }
176
164
  throw new FlowersecError({ path: args.path, stage: "attach", code: "attach_failed", message: "attach failed", cause: err });
177
165
  }
178
166
  }
@@ -202,7 +190,6 @@ export async function connectCore(args) {
202
190
  catch (err) {
203
191
  const handshakeElapsedSeconds = nowSeconds() - handshakeStart;
204
192
  const handshakeCode = classifyHandshakeError(err);
205
- transport.close();
206
193
  if (args.path === "tunnel" && err instanceof WsCloseError) {
207
194
  const reason = err.reason;
208
195
  if (isTunnelAttachCloseReason(reason)) {
@@ -233,17 +220,20 @@ export async function connectCore(args) {
233
220
  });
234
221
  let terminationReported = false;
235
222
  let closeAll = () => {
236
- try {
237
- secure.close();
238
- }
239
- catch { /* ignore */ }
223
+ runSyncCleanups([() => secure.close()]);
240
224
  };
241
225
  const reportTermination = (error) => {
242
226
  if (terminationReported)
243
227
  return;
244
228
  terminationReported = true;
245
- closeAll();
246
- resolveTermination({ error });
229
+ let terminalError = error;
230
+ try {
231
+ closeAll();
232
+ }
233
+ catch (cleanupError) {
234
+ terminalError = errorWithCleanup(error, cleanupError, "session termination cleanup failed");
235
+ }
236
+ resolveTermination({ error: terminalError });
247
237
  };
248
238
  const mux = new YamuxSession(conn, {
249
239
  client: true,
@@ -261,23 +251,21 @@ export async function connectCore(args) {
261
251
  }),
262
252
  });
263
253
  closeAll = () => {
264
- try {
265
- mux.close();
266
- }
267
- catch { /* ignore */ }
268
- try {
269
- secure.close();
270
- }
271
- catch { /* ignore */ }
254
+ runSyncCleanups([() => mux.close(), () => secure.close()]);
272
255
  };
273
256
  let rpcStream;
274
257
  try {
275
258
  rpcStream = await mux.openStream(signal === undefined ? {} : { signal });
276
259
  }
277
260
  catch (e) {
278
- mux.close();
279
- secure.close();
280
- throw new FlowersecError({ path: args.path, stage: "yamux", code: "open_stream_failed", message: "open rpc stream failed", cause: e });
261
+ const cleanupError = captureSyncCleanup(() => runSyncCleanups([() => mux.close(), () => secure.close()]));
262
+ throw new FlowersecError({
263
+ path: args.path,
264
+ stage: "yamux",
265
+ code: "open_stream_failed",
266
+ message: "open rpc stream failed",
267
+ cause: causeWithCleanup(e, cleanupError, "RPC stream setup cleanup failed"),
268
+ });
281
269
  }
282
270
  const reader = new ByteReader(() => rpcStream.read());
283
271
  const readExactly = (n) => reader.readExactly(n);
@@ -286,20 +274,19 @@ export async function connectCore(args) {
286
274
  await writeStreamHello(write, "rpc");
287
275
  }
288
276
  catch (e) {
289
- try {
290
- await rpcStream.close();
291
- }
292
- catch {
293
- // ignore
294
- }
295
- mux.close();
296
- secure.close();
277
+ const cleanupError = await captureAsyncCleanup(async () => {
278
+ const streamCloseError = await captureAsyncCleanup(() => rpcStream.close());
279
+ const stackCloseError = captureSyncCleanup(() => runSyncCleanups([() => mux.close(), () => secure.close()]));
280
+ const combined = errorsFrom([streamCloseError, stackCloseError]);
281
+ if (combined != null)
282
+ throw combined;
283
+ });
297
284
  throw new FlowersecError({
298
285
  path: args.path,
299
286
  stage: "rpc",
300
287
  code: "stream_hello_failed",
301
288
  message: "rpc stream hello failed",
302
- cause: e,
289
+ cause: causeWithCleanup(e, cleanupError, "RPC bootstrap cleanup failed"),
303
290
  });
304
291
  }
305
292
  const rpc = new RpcClient(readExactly, write, { observer, onTerminal: reportTermination });
@@ -316,22 +303,13 @@ export async function connectCore(args) {
316
303
  return await mux.probeLiveness(liveness.timeoutMs);
317
304
  }
318
305
  catch (e) {
319
- if (String(e?.message).includes("ping timeout")) {
306
+ const timedOut = isYamuxPingTimeoutError(e);
307
+ if (timedOut) {
320
308
  emitObserverDiagnostic(args.opts.observer, { path: args.path, stage: "yamux", code_domain: "event", code: "liveness_timeout", result: "fail" });
321
309
  }
322
- try {
323
- rpc.close();
324
- }
325
- catch { /* ignore */ }
326
- try {
327
- mux.close();
328
- }
329
- catch { /* ignore */ }
330
- try {
331
- secure.close();
332
- }
333
- catch { /* ignore */ }
334
- throw new FlowersecError({ path: args.path, stage: "yamux", code: "ping_failed", message: "liveness probe failed", cause: e });
310
+ const error = new FlowersecError({ path: args.path, stage: "yamux", code: timedOut ? "timeout" : "ping_failed", message: "liveness probe failed", cause: e });
311
+ reportTermination(error);
312
+ throw error;
335
313
  }
336
314
  };
337
315
  let livenessTimer;
@@ -344,24 +322,7 @@ export async function connectCore(args) {
344
322
  };
345
323
  closeAll = () => {
346
324
  stopLiveness();
347
- try {
348
- rpc.close();
349
- }
350
- catch {
351
- // ignore
352
- }
353
- try {
354
- mux.close();
355
- }
356
- catch {
357
- // ignore
358
- }
359
- try {
360
- secure.close();
361
- }
362
- catch {
363
- // ignore
364
- }
325
+ runSyncCleanups([() => rpc.close(), () => mux.close(), () => secure.close()]);
365
326
  };
366
327
  if (liveness.intervalMs > 0) {
367
328
  livenessTimer = setInterval(() => {
@@ -385,10 +346,19 @@ export async function connectCore(args) {
385
346
  mux,
386
347
  rpc,
387
348
  ping,
349
+ rekey: async () => {
350
+ try {
351
+ await secure.rekeyNow();
352
+ }
353
+ catch (error) {
354
+ throw new FlowersecError({ path: args.path, stage: "secure", code: "rekey_failed", message: "rekey failed", cause: error });
355
+ }
356
+ },
388
357
  probeLiveness,
389
358
  openStream: async (kind, opts = {}) => {
390
- if (kind == null || kind === "")
391
- throw new FlowersecError({ path: args.path, stage: "validate", code: "missing_stream_kind", message: "missing stream kind" });
359
+ const streamKind = kind?.trim() ?? "";
360
+ if (streamKind === "")
361
+ throw new FlowersecError({ path: args.path, stage: "rpc", code: "missing_stream_kind", message: "missing stream kind" });
392
362
  if (opts.signal?.aborted) {
393
363
  throw new FlowersecError({
394
364
  path: args.path,
@@ -408,6 +378,7 @@ export async function connectCore(args) {
408
378
  };
409
379
  const signal = opts.signal;
410
380
  let abortListener;
381
+ let abortReset;
411
382
  let s;
412
383
  try {
413
384
  s = await mux.openStream(signal === undefined ? {} : { signal });
@@ -421,12 +392,7 @@ export async function connectCore(args) {
421
392
  }
422
393
  if (signal != null) {
423
394
  abortListener = () => {
424
- try {
425
- s.reset(abortReason(signal));
426
- }
427
- catch {
428
- // ignore
429
- }
395
+ abortReset ??= captureAsyncCleanup(() => Promise.resolve(s.reset(abortReason(signal))));
430
396
  };
431
397
  signal.addEventListener("abort", abortListener, { once: true });
432
398
  if (signal.aborted)
@@ -435,55 +401,52 @@ export async function connectCore(args) {
435
401
  if (signal?.aborted) {
436
402
  if (abortListener != null)
437
403
  signal.removeEventListener("abort", abortListener);
438
- try {
439
- await s.close();
440
- }
441
- catch {
442
- // ignore
443
- }
404
+ const cleanupError = await captureAsyncCleanup(async () => {
405
+ const resetError = abortReset == null ? undefined : await abortReset;
406
+ const closeError = await captureAsyncCleanup(() => s.close());
407
+ const combined = errorsFrom([resetError, closeError]);
408
+ if (combined != null)
409
+ throw combined;
410
+ });
444
411
  throw new FlowersecError({
445
412
  path: args.path,
446
413
  stage: "yamux",
447
414
  code: "canceled",
448
415
  message: "open stream aborted",
449
- cause: signal.reason,
416
+ cause: causeWithCleanup(signal.reason, cleanupError, "aborted stream cleanup failed"),
450
417
  });
451
418
  }
452
419
  try {
453
- await writeStreamHello((b) => s.write(b), kind);
420
+ await writeStreamHello((b) => s.write(b), streamKind);
454
421
  }
455
422
  catch (err) {
456
423
  if (signal?.aborted) {
457
424
  if (abortListener != null)
458
425
  signal.removeEventListener("abort", abortListener);
459
- try {
460
- await s.close();
461
- }
462
- catch {
463
- // ignore
464
- }
426
+ const cleanupError = await captureAsyncCleanup(async () => {
427
+ const resetError = abortReset == null ? undefined : await abortReset;
428
+ const closeError = await captureAsyncCleanup(() => s.close());
429
+ const combined = errorsFrom([resetError, closeError]);
430
+ if (combined != null)
431
+ throw combined;
432
+ });
465
433
  throw new FlowersecError({
466
434
  path: args.path,
467
435
  stage: "yamux",
468
436
  code: "canceled",
469
437
  message: "open stream aborted",
470
- cause: signal.reason,
438
+ cause: causeWithCleanup(signal.reason, cleanupError, "aborted stream cleanup failed"),
471
439
  });
472
440
  }
473
441
  if (signal != null && abortListener != null)
474
442
  signal.removeEventListener("abort", abortListener);
475
- try {
476
- await s.close();
477
- }
478
- catch {
479
- // ignore
480
- }
443
+ const cleanupError = await captureAsyncCleanup(() => s.close());
481
444
  throw new FlowersecError({
482
445
  path: args.path,
483
446
  stage: "rpc",
484
447
  code: "stream_hello_failed",
485
448
  message: "stream hello failed",
486
- cause: err,
449
+ cause: causeWithCleanup(err, cleanupError, "stream hello cleanup failed"),
487
450
  });
488
451
  }
489
452
  if (signal != null && abortListener != null) {
@@ -499,14 +462,65 @@ export async function connectCore(args) {
499
462
  return client;
500
463
  }
501
464
  catch (e) {
465
+ const cleanupError = captureSyncCleanup(() => transport.close());
466
+ throw cleanupError == null ? e : errorWithCleanup(e, cleanupError, "transport cleanup failed");
467
+ }
468
+ }
469
+ function runSyncCleanups(actions) {
470
+ const failures = [];
471
+ for (const action of actions) {
502
472
  try {
503
- transport.close();
473
+ action();
504
474
  }
505
- catch {
506
- // ignore
475
+ catch (error) {
476
+ failures.push(errorValue(error));
507
477
  }
508
- throw e;
509
478
  }
479
+ if (failures.length > 0)
480
+ throw new AggregateError(failures, "Flowersec cleanup failed");
481
+ }
482
+ function captureSyncCleanup(action) {
483
+ try {
484
+ action();
485
+ return undefined;
486
+ }
487
+ catch (error) {
488
+ return error;
489
+ }
490
+ }
491
+ async function captureAsyncCleanup(action) {
492
+ try {
493
+ await action();
494
+ return undefined;
495
+ }
496
+ catch (error) {
497
+ return error;
498
+ }
499
+ }
500
+ function causeWithCleanup(primary, cleanup, message) {
501
+ if (cleanup === undefined)
502
+ return primary;
503
+ return new AggregateError([errorValue(primary), errorValue(cleanup)], message);
504
+ }
505
+ function errorWithCleanup(primary, cleanup, message) {
506
+ const cause = causeWithCleanup(primary, cleanup, message);
507
+ if (primary instanceof FlowersecError) {
508
+ return new FlowersecError({
509
+ path: primary.path,
510
+ stage: primary.stage,
511
+ code: primary.code,
512
+ message,
513
+ cause,
514
+ });
515
+ }
516
+ return new AggregateError([errorValue(primary), errorValue(cleanup)], message);
517
+ }
518
+ function errorsFrom(values) {
519
+ const failures = values.filter((value) => value !== undefined).map(errorValue);
520
+ return failures.length === 0 ? undefined : new AggregateError(failures, "Flowersec cleanup failed");
521
+ }
522
+ function errorValue(error) {
523
+ return error instanceof Error ? error : new Error(String(error));
510
524
  }
511
525
  function validateLimitObject(input, name, invalid) {
512
526
  if (input === undefined)
package/dist/client.d.ts CHANGED
@@ -11,6 +11,8 @@ export type Client = Readonly<{
11
11
  signal?: AbortSignal;
12
12
  }>) => Promise<YamuxStream>;
13
13
  ping: () => Promise<void>;
14
+ /** Emits an authenticated rekey record and advances the E2EE send key. */
15
+ rekey: () => Promise<void>;
14
16
  /** Performs an acknowledged Yamux round trip and returns RTT in milliseconds. */
15
17
  probeLiveness: () => Promise<number>;
16
18
  close: () => void;
@@ -1,7 +1,7 @@
1
1
  import { type TokenPayload } from "./token.js";
2
2
  export declare class IssuerKeyset {
3
- private kid;
4
- private signingSeed;
3
+ private active;
4
+ private readonly verificationKeys;
5
5
  constructor(kid: string, signingSeed: Uint8Array);
6
6
  static random(kid: string): IssuerKeyset;
7
7
  currentKID(): string;
@@ -9,7 +9,10 @@ export declare class IssuerKeyset {
9
9
  sign(payload: Omit<TokenPayload, "kid"> & Readonly<{
10
10
  kid?: string;
11
11
  }>): string;
12
+ addVerificationKey(kid: string, publicKey: Uint8Array): void;
12
13
  rotate(kid: string, signingSeed: Uint8Array): void;
14
+ retireVerificationKey(kid: string): void;
13
15
  exportTunnelKeyset(): Uint8Array;
14
16
  dispose(): void;
17
+ private requireActive;
15
18
  }