@persistica/flux-agent 0.0.24 → 0.0.26

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/index.js CHANGED
@@ -40,6 +40,7 @@ var ON_NETWORK_CHANNEL_PUBLISH = "nc-on-pub";
40
40
  var SUBSCRIBE_NETWORK_CHANNEL_NAME = "subscribe-to-nc-name";
41
41
  var SUBSCRIBED_NETWORK_CHANNEL_NAME = "subscribed-to-nc-name";
42
42
  var UNSUBSCRIBE_NETWORK_CHANNEL_NAME = "unsubscribe-nc-name";
43
+ var UNSUBSCRIBED_NETWORK_CHANNEL_NAME = "unsubscribed-nc-name";
43
44
  var AUTHORITY_CHANNEL_SUBSCRIBE = "acs";
44
45
  var AUTHORITY_DISCONNECT_AGENT = "ada";
45
46
  var ERROR = "e";
@@ -87,7 +88,7 @@ var validateNetworkIdOrThrow = (networkId) => {
87
88
  // libs/flux/shared/types/src/lib/client-id.type.ts
88
89
  var NANOID_LENGTH = 21;
89
90
  var NANOID_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-";
90
- var isNanoId = (id) => {
91
+ var isClientId = (id) => {
91
92
  if (typeof id !== "string") {
92
93
  return false;
93
94
  }
@@ -198,7 +199,7 @@ var WebSocketClient = class extends RPCServer {
198
199
  );
199
200
  setTimeout(() => {
200
201
  this.reconnectAttempts++;
201
- this.connect();
202
+ void this.connect();
202
203
  }, delay);
203
204
  } else if (this.options.autoReconnect) {
204
205
  this.emit("error", new Error("Connection failed: retries exhausted"));
@@ -402,9 +403,7 @@ var FluxAgentNetworkConnection = class {
402
403
  this._fluxWebSocketClientConnection,
403
404
  this._webRTCStateChange
404
405
  );
405
- console.log(
406
- this.iceConnection ? `RTCPeerConnection is available` : `RTCPeerConnection is not available`
407
- );
406
+ console.log(`RTCPeerConnection is available`);
408
407
  } else {
409
408
  console.warn("\u2757WebRTC is not available in this environment.");
410
409
  }
@@ -422,7 +421,7 @@ var FluxAgentNetworkConnection = class {
422
421
  const fluxNetworkChannel = await this._fluxWebSocketConnection.joinChannel(
423
422
  channelName
424
423
  );
425
- this.connectedChannelSet.add(fluxNetworkChannel.channelName);
424
+ this.connectedChannelSet.add(channelName);
426
425
  return fluxNetworkChannel;
427
426
  }
428
427
  /**
@@ -442,7 +441,7 @@ var FluxAgentNetworkConnection = class {
442
441
  * Connects to a remote agent.
443
442
  */
444
443
  connectToAgent(clientId) {
445
- this._fluxWebSocketConnection.connectToAgent(clientId);
444
+ void this._fluxWebSocketConnection.connectToAgent(clientId);
446
445
  return new FluxRemoteClient(this.iceConnection);
447
446
  }
448
447
  /**
@@ -458,25 +457,25 @@ var hasOverlap = (a, b) => a && b ? [...a].some((item) => (Array.isArray(b) ? b
458
457
  var PicoLogger = /* @__PURE__ */ (() => {
459
458
  let allowScopes;
460
459
  const configure = (configuration) => {
461
- allowScopes = typeof configuration.allowScopes === "string" && configuration.allowScopes === "*" ? "*" : new Set(configuration.allowScopes);
460
+ allowScopes = configuration.allowScopes === "*" ? "*" : new Set(configuration.allowScopes);
462
461
  };
463
462
  const log = (message, scopes) => {
464
463
  if (allowScopes !== "*" && !hasOverlap(allowScopes, scopes)) {
465
464
  return;
466
465
  }
467
- console.log(`[${scopes}] ${message}`);
466
+ console.log(`[${scopes?.toString()}] ${message}`);
468
467
  };
469
468
  const warn = (message, scopes) => {
470
469
  if (allowScopes !== "*" && !hasOverlap(allowScopes, scopes)) {
471
470
  return;
472
471
  }
473
- console.warn(`[${scopes}] ${message}`);
472
+ console.warn(`[${scopes?.toString()}] ${message}`);
474
473
  };
475
474
  const error = (message, scopes) => {
476
475
  if (allowScopes !== "*" && !hasOverlap(allowScopes, scopes)) {
477
476
  return;
478
477
  }
479
- console.error(`[${scopes}] ${message}`);
478
+ console.error(`[${scopes?.toString()}] ${message}`);
480
479
  };
481
480
  return {
482
481
  configure,
@@ -488,8 +487,8 @@ var PicoLogger = /* @__PURE__ */ (() => {
488
487
 
489
488
  // libs/flux/shared/connection/src/lib/flux-network-channel.class.ts
490
489
  var FluxNetworkChannel = class {
491
- constructor(channelName, _fluxWebSocketConnection) {
492
- this.channelName = channelName;
490
+ constructor(_channelName, _fluxWebSocketConnection) {
491
+ this._channelName = _channelName;
493
492
  this._fluxWebSocketConnection = _fluxWebSocketConnection;
494
493
  }
495
494
  /**
@@ -497,7 +496,7 @@ var FluxNetworkChannel = class {
497
496
  */
498
497
  publish(message) {
499
498
  this._fluxWebSocketConnection.publish(
500
- this.channelName,
499
+ this._channelName,
501
500
  message
502
501
  );
503
502
  }
@@ -506,7 +505,7 @@ var FluxNetworkChannel = class {
506
505
  */
507
506
  onPublish(fn) {
508
507
  this._fluxWebSocketConnection.onPublish(
509
- this.channelName,
508
+ this._channelName,
510
509
  fn
511
510
  // TODO
512
511
  );
@@ -517,11 +516,17 @@ var FluxNetworkChannel = class {
517
516
  var CONNECTION_TIMEOUT_MS = 2e3;
518
517
  var ChannelStateManager = class {
519
518
  connectionTimeouts = /* @__PURE__ */ new Map();
519
+ joinedChannels = /* @__PURE__ */ new Map();
520
520
  /**
521
521
  * Join a channel.
522
522
  */
523
523
  async joinChannel(channelName, fluxWebSocketConnection, webSocketClient) {
524
524
  if (webSocketClient) {
525
+ const existingChannel = this.joinedChannels.get(channelName);
526
+ if (existingChannel) {
527
+ existingChannel.useCount++;
528
+ return Promise.resolve(existingChannel.channel);
529
+ }
525
530
  return new Promise((resolve, reject) => {
526
531
  const existingTimeout = this.connectionTimeouts.get(channelName);
527
532
  if (existingTimeout) {
@@ -539,7 +544,9 @@ var ChannelStateManager = class {
539
544
  this.connectionTimeouts.delete(channelName);
540
545
  }
541
546
  fluxWebSocketConnection.removePackageTypeInterceptor(SUBSCRIBED_NETWORK_CHANNEL_NAME, cb);
542
- resolve(new FluxNetworkChannel(channelName, fluxWebSocketConnection));
547
+ const channel = new FluxNetworkChannel(channelName, fluxWebSocketConnection);
548
+ this.joinedChannels.set(channelName, { channel, useCount: 1 });
549
+ resolve(channel);
543
550
  };
544
551
  fluxWebSocketConnection.interceptPackageTypeMessages(
545
552
  SUBSCRIBED_NETWORK_CHANNEL_NAME,
@@ -561,9 +568,46 @@ var ChannelStateManager = class {
561
568
  /**
562
569
  * Leave a channel.
563
570
  */
564
- async leaveChannel(channelName, webSocketClient) {
571
+ async leaveChannel(channelName, fluxWebSocketConnection, webSocketClient) {
572
+ const existingChannel = this.joinedChannels.get(channelName);
573
+ if (!existingChannel) {
574
+ return Promise.reject(new Error("No channel instance found"));
575
+ }
576
+ existingChannel.useCount--;
577
+ if (existingChannel.useCount <= 0) {
578
+ this.joinedChannels.delete(channelName);
579
+ }
580
+ if (webSocketClient && existingChannel.useCount <= 0) {
581
+ return new Promise((resolve, reject) => {
582
+ const cb = (message) => {
583
+ const receivedChannelName = message.substring(message.indexOf(":") + 1);
584
+ if (channelName !== receivedChannelName) {
585
+ return;
586
+ }
587
+ const timeout = this.connectionTimeouts.get(channelName);
588
+ if (timeout) {
589
+ clearTimeout(timeout);
590
+ this.connectionTimeouts.delete(channelName);
591
+ }
592
+ fluxWebSocketConnection.removePackageTypeInterceptor(UNSUBSCRIBED_NETWORK_CHANNEL_NAME, cb);
593
+ resolve(void 0);
594
+ };
595
+ fluxWebSocketConnection.interceptPackageTypeMessages(
596
+ UNSUBSCRIBED_NETWORK_CHANNEL_NAME,
597
+ cb
598
+ );
599
+ this.connectionTimeouts.set(
600
+ channelName,
601
+ setTimeout(() => {
602
+ this.connectionTimeouts.delete(channelName);
603
+ fluxWebSocketConnection.removePackageTypeInterceptor(UNSUBSCRIBED_NETWORK_CHANNEL_NAME, cb);
604
+ reject(new Error(`Channel unsubscription timed out for channel "${channelName}"`));
605
+ }, CONNECTION_TIMEOUT_MS)
606
+ );
607
+ webSocketClient.send(`${UNSUBSCRIBE_NETWORK_CHANNEL_NAME}:${channelName}`);
608
+ });
609
+ }
565
610
  if (webSocketClient) {
566
- webSocketClient.send(`${UNSUBSCRIBE_NETWORK_CHANNEL_NAME}:${channelName}`);
567
611
  return Promise.resolve(void 0);
568
612
  }
569
613
  return Promise.reject(new Error("Not connected"));
@@ -579,8 +623,8 @@ var createWSConnection = (id, ticket, stateManager, onReconnectCallback, options
579
623
  ticket,
580
624
  {
581
625
  domain: options.domain,
582
- secretKey: options?.secretKey,
583
- retries: options?.retries
626
+ secretKey: options.secretKey,
627
+ retries: options.retries
584
628
  }
585
629
  );
586
630
  };
@@ -593,8 +637,8 @@ var FluxWebSocketConnection = class {
593
637
  this.options = options;
594
638
  this.options = {
595
639
  domain: this.options.domain,
596
- secretKey: this.options?.secretKey,
597
- retries: this.options?.retries ?? 1e4
640
+ secretKey: this.options.secretKey,
641
+ retries: this.options.retries ?? 1e4
598
642
  };
599
643
  const url = new URL(this.options.domain);
600
644
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
@@ -677,7 +721,6 @@ var FluxWebSocketConnection = class {
677
721
  });
678
722
  this.socket.clearEventSubscribers();
679
723
  this.socket.on("message", this.socketMessageHandler).on("close", this.socketCloseHandler).on("connecting", this.socketConnectingHandler).on("error", this.socketErrorHandler);
680
- this.stateManager.emitNetworkState("connecting");
681
724
  void this.socket.connect();
682
725
  return this.connectPromise;
683
726
  }
@@ -731,6 +774,7 @@ var FluxWebSocketConnection = class {
731
774
  async leaveChannel(channelName) {
732
775
  return this.channelStateManager.leaveChannel(
733
776
  channelName,
777
+ this,
734
778
  this.webSocketClient
735
779
  );
736
780
  }
@@ -788,7 +832,7 @@ var FluxWebSocketConnection = class {
788
832
  * Disconnects an agent from the network.
789
833
  */
790
834
  disconnectAgent(id) {
791
- if (!isNanoId(id)) {
835
+ if (!isClientId(id)) {
792
836
  throw new Error(`Invalid agent id: '${id}'`);
793
837
  }
794
838
  if (this.webSocketClient) {
@@ -817,9 +861,9 @@ var FluxWebSocketConnection = class {
817
861
  this.stateManager.emitNetworkState(reason === "kicked" ? "kicked" : "disconnected");
818
862
  }
819
863
  handleSocketConnecting(retryAttempt) {
820
- this.stateManager.emitNetworkState("disconnected");
864
+ this.stateManager.emitNetworkState("connecting");
821
865
  if (retryAttempt > 0) {
822
- PicoLogger.log(`\u{1F504} Connecting attempt: #${retryAttempt} of ${this.options?.retries ?? "none"}`, this.fluxInstanceId);
866
+ PicoLogger.log(`\u{1F504} Connecting attempt: #${retryAttempt} of ${this.options.retries ?? "none"}`, this.fluxInstanceId);
823
867
  } else {
824
868
  PicoLogger.log(`\u{1F504} Connecting: ${this.fluxInstanceId}`);
825
869
  }
@@ -865,26 +909,26 @@ var FluxWebSocketConnection = class {
865
909
  }
866
910
  }
867
911
  }
868
- break;
912
+ return;
869
913
  }
870
914
  case RPC_REQUEST: {
871
915
  const payload = JSON.parse(message_.substring(message_.indexOf(":") + 1));
872
- this.socket.handleMessage(payload, (data) => {
916
+ void this.socket.handleMessage(payload, (data) => {
873
917
  this.socket.send(`${RPC_RESPONSE}:${JSON.stringify(data)}`);
874
918
  });
875
- break;
919
+ return;
876
920
  }
877
921
  case RPC_RESPONSE: {
878
922
  const payload = message_.substring(message_.indexOf(":") + 1);
879
923
  PicoLogger.log("\u{1F50C} Unhandled type rpc response", "ws-client");
880
924
  PicoLogger.log(`payload: ${payload}`, "ws-client");
881
- break;
925
+ return;
882
926
  }
883
927
  default:
884
928
  if (message_.startsWith(`${ERROR}:`)) {
885
929
  }
886
930
  PicoLogger.log(`\u{1F50C} Unhandled type: "${message_}"`, "ws-client");
887
- break;
931
+ return;
888
932
  }
889
933
  }
890
934
  };
@@ -1021,9 +1065,8 @@ var StateManager = class {
1021
1065
 
1022
1066
  // libs/flux/shared/utils/src/lib/promises.utils.ts
1023
1067
  var retryOrThrow = async (fn, shouldRetry, options) => {
1024
- let attempt = 0;
1025
1068
  let overrideDelay;
1026
- while (true) {
1069
+ for (let attempt = 0; attempt <= options.retries; attempt++) {
1027
1070
  try {
1028
1071
  if (attempt > 0) {
1029
1072
  if (options.onRetry) {
@@ -1032,12 +1075,11 @@ var retryOrThrow = async (fn, shouldRetry, options) => {
1032
1075
  }
1033
1076
  return await fn();
1034
1077
  } catch (err) {
1035
- attempt++;
1036
- if (attempt > options.retries || !shouldRetry(err)) {
1078
+ if (attempt >= options.retries || !shouldRetry(err)) {
1037
1079
  throw err;
1038
1080
  }
1039
1081
  if (options.delayMs > 0 || overrideDelay && overrideDelay > 0) {
1040
- let delay = overrideDelay ?? options.delayMs;
1082
+ const delay = overrideDelay ?? options.delayMs;
1041
1083
  await new Promise((res) => setTimeout(
1042
1084
  res,
1043
1085
  delay
@@ -1045,6 +1087,7 @@ var retryOrThrow = async (fn, shouldRetry, options) => {
1045
1087
  }
1046
1088
  }
1047
1089
  }
1090
+ throw new Error("Retry loop exited unexpectedly.");
1048
1091
  };
1049
1092
 
1050
1093
  // libs/flux/shared/utils/src/lib/machine-id.util.ts
@@ -1151,15 +1194,19 @@ var FluxClientData = class {
1151
1194
  // packages/flux/agent/src/lib/flux-agent.class.ts
1152
1195
  var DEFAULT_FLUX_DOMAIN = "https://mesh.persistica.io";
1153
1196
  var FluxAgent = class {
1154
- constructor(networkId, options) {
1155
- this.networkId = networkId;
1156
- this.options = options;
1157
- validateNetworkIdOrThrow(this.networkId);
1158
- }
1159
1197
  id = nanoid();
1160
1198
  fluxWebSocketConnection;
1161
1199
  fluxClientData = new FluxClientData();
1162
1200
  stateManager = new StateManager();
1201
+ networkId;
1202
+ options;
1203
+ constructor(networkId, options) {
1204
+ if (!validateNetworkIdOrThrow(networkId)) {
1205
+ throw new Error("Will never be thrown");
1206
+ }
1207
+ this.networkId = networkId;
1208
+ this.options = options;
1209
+ }
1163
1210
  /**
1164
1211
  * Connect to the network using provided identification.
1165
1212
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@persistica/flux-agent",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "description": "Persistica Flux Agent",
5
5
  "type": "module",
6
6
  "keywords": [],
@@ -1,17 +1,17 @@
1
1
  import type { TMessageCallback } from '@flux/shared/ws';
2
2
  import type { FluxAgentNetworkConnection } from '@flux/shared/connection';
3
3
  export declare class FluxAgent {
4
- private readonly networkId;
5
- private readonly options?;
6
4
  readonly id: string;
7
5
  private fluxWebSocketConnection;
8
6
  private readonly fluxClientData;
9
7
  private readonly stateManager;
8
+ private readonly networkId;
9
+ private readonly options;
10
10
  constructor(networkId: string, options?: {
11
11
  domain?: string;
12
12
  secretKey?: string;
13
13
  retries?: number;
14
- } | undefined);
14
+ });
15
15
  /**
16
16
  * Connect to the network using provided identification.
17
17
  *