@openfin/node-adapter 47.100.27 → 47.100.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/out/node-adapter.js +276 -119
  2. package/package.json +2 -2
@@ -7571,19 +7571,39 @@ let ChannelError$1 = class ChannelError extends Error {
7571
7571
  }
7572
7572
  };
7573
7573
 
7574
- var __classPrivateFieldGet$k = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
7575
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
7576
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
7577
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
7578
- };
7574
+ /**
7575
+ * Tracks live channel clients by their current endpoint id and routes runtime lifecycle events to them.
7576
+ */
7577
+ class ChannelClientStore {
7578
+ // The provider for a channel has gone away: every client on that channel is disconnected.
7579
+ static handleProviderDisconnect(eventPayload) {
7580
+ [...ChannelClientStore.clients.values()].forEach((client) => {
7581
+ if (client.providerIdentity.channelId === eventPayload.channelId) {
7582
+ client[teardown](true, false);
7583
+ }
7584
+ });
7585
+ }
7586
+ // Old providers omit endpoint ids, so a new connection silently replaces the previous client on the channel.
7587
+ static handleEndpointDisconnect(endpointId) {
7588
+ ChannelClientStore.clients.get(endpointId)?.[teardown](false, false);
7589
+ }
7590
+ }
7591
+ ChannelClientStore.clients = new Map();
7592
+
7579
7593
  var __classPrivateFieldSet$j = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7580
7594
  if (kind === "m") throw new TypeError("Private method is not writable");
7581
7595
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
7582
7596
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
7583
7597
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7584
7598
  };
7585
- var _ChannelClient_protectedObj, _ChannelClient_strategy, _ChannelClient_close;
7586
- const channelClientsByEndpointId = new Map();
7599
+ var __classPrivateFieldGet$k = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
7600
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
7601
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
7602
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
7603
+ };
7604
+ var _ChannelClient_currentConnection, _ChannelClient_state, _ChannelClient_wire, _ChannelClient_reestablish, _ChannelClient_unsubscribeBFCache, _ChannelClient_disconnectListener, _ChannelClient_reconnectListener, _ChannelClient_releaseConnection, _ChannelClient_attach, _ChannelClient_sendDisconnectToRuntime, _ChannelClient_reconnect;
7605
+ // Key for ChannelClient's internal teardown function. Prevents the need to expose internal functions.
7606
+ const teardown = Symbol('teardown');
7587
7607
  /**
7588
7608
  * Instance created to enable use of a channel as a client. Allows for communication with the
7589
7609
  * {@link ChannelProvider ChannelProvider} by invoking an action on the
@@ -7612,50 +7632,135 @@ class ChannelClient extends ChannelBase {
7612
7632
  /**
7613
7633
  * @internal
7614
7634
  */
7615
- static closeChannelByEndpointId(id) {
7616
- const channel = channelClientsByEndpointId.get(id);
7617
- if (channel) {
7618
- __classPrivateFieldGet$k(channel, _ChannelClient_close, "f").call(channel);
7619
- }
7635
+ constructor(config) {
7636
+ super();
7637
+ _ChannelClient_currentConnection.set(this, void 0);
7638
+ _ChannelClient_state.set(this, { status: 'connected' });
7639
+ _ChannelClient_wire.set(this, void 0);
7640
+ _ChannelClient_reestablish.set(this, void 0);
7641
+ _ChannelClient_unsubscribeBFCache.set(this, void 0);
7642
+ _ChannelClient_disconnectListener.set(this, void 0);
7643
+ _ChannelClient_reconnectListener.set(this, void 0);
7644
+ // needs to be bound;
7645
+ this.processAction = (action, payload, senderIdentity) => super.processAction(action, payload, senderIdentity);
7646
+ _ChannelClient_releaseConnection.set(this, async (connection, notifyRuntime) => {
7647
+ connection.strategy.close().catch(() => undefined);
7648
+ if (notifyRuntime) {
7649
+ await __classPrivateFieldGet$k(this, _ChannelClient_sendDisconnectToRuntime, "f").call(this, connection);
7650
+ }
7651
+ });
7652
+ _ChannelClient_attach.set(this, (instance) => {
7653
+ ChannelClientStore.clients.set(instance.routingInfo.endpointId, this);
7654
+ instance.strategy.receive(this.processAction);
7655
+ instance.strategy.onEndpointDisconnect(instance.routingInfo.channelId, () => {
7656
+ // Only a drop of the live endpoint disconnects. During a self-heal the endpoint is already being replaced
7657
+ // and the handshake decides the outcome; after it, closing the replaced strategy (RTC/combined fire this on
7658
+ // close) must not tear down the client now live on a fresh endpoint.
7659
+ if (__classPrivateFieldGet$k(this, _ChannelClient_currentConnection, "f") === instance && __classPrivateFieldGet$k(this, _ChannelClient_state, "f").status === 'connected') {
7660
+ this[teardown](true).catch((error) => {
7661
+ // eslint-disable-next-line no-console
7662
+ console.warn(`Failed to notify the runtime that channel '${instance.routingInfo.channelName}' endpoint ${instance.routingInfo.endpointId} disconnected.`, error);
7663
+ });
7664
+ }
7665
+ });
7666
+ });
7667
+ _ChannelClient_sendDisconnectToRuntime.set(this, async ({ routingInfo }) => {
7668
+ const { channelName, uuid, name, endpointId } = routingInfo;
7669
+ await __classPrivateFieldGet$k(this, _ChannelClient_wire, "f").sendAction('disconnect-from-channel', { channelName, uuid, name, endpointId });
7670
+ });
7671
+ // Re-handshakes (new endpointId) and swaps in the fresh endpoint. Client-side registrations and middleware live
7672
+ // on `this` (ChannelBase), so they carry over. Overlapping BF-Cache restores share one in-flight handshake.
7673
+ _ChannelClient_reconnect.set(this, async () => {
7674
+ if (__classPrivateFieldGet$k(this, _ChannelClient_state, "f").status === 'connected') {
7675
+ const performReconnect = async () => {
7676
+ let next;
7677
+ try {
7678
+ next = await __classPrivateFieldGet$k(this, _ChannelClient_reestablish, "f").call(this);
7679
+ }
7680
+ catch (error) {
7681
+ // No automatic retry: recovery is up to the onDisconnection handler.
7682
+ // eslint-disable-next-line no-console
7683
+ console.warn(`Channel '${__classPrivateFieldGet$k(this, _ChannelClient_currentConnection, "f").routingInfo.channelName}' failed to self-heal after BF-Cache restoration; disconnecting.`, error);
7684
+ await this[teardown](true).catch((disconnectError) => {
7685
+ // eslint-disable-next-line no-console
7686
+ console.warn(`Failed to notify the runtime that channel '${__classPrivateFieldGet$k(this, _ChannelClient_currentConnection, "f").routingInfo.channelName}' disconnected.`, disconnectError);
7687
+ });
7688
+ return;
7689
+ }
7690
+ // A disconnect() or provider disconnect may have landed while the handshake was in flight; if so,
7691
+ // tear down the freshly established endpoint instead of resurrecting a closed client.
7692
+ if (__classPrivateFieldGet$k(this, _ChannelClient_state, "f").status !== 'reconnecting') {
7693
+ __classPrivateFieldGet$k(this, _ChannelClient_releaseConnection, "f").call(this, next, true).catch(() => undefined);
7694
+ return;
7695
+ }
7696
+ const previous = __classPrivateFieldGet$k(this, _ChannelClient_currentConnection, "f");
7697
+ __classPrivateFieldSet$j(this, _ChannelClient_currentConnection, next, "f");
7698
+ __classPrivateFieldGet$k(this, _ChannelClient_attach, "f").call(this, next);
7699
+ __classPrivateFieldSet$j(this, _ChannelClient_state, { status: 'connected' }, "f");
7700
+ // A throwing listener must not abort the reconnect.
7701
+ try {
7702
+ __classPrivateFieldGet$k(this, _ChannelClient_reconnectListener, "f")?.call(this, {
7703
+ providerIdentity: this.providerIdentity,
7704
+ previousEndpointId: previous.routingInfo.endpointId,
7705
+ endpointId: next.routingInfo.endpointId
7706
+ });
7707
+ }
7708
+ catch (error) {
7709
+ // eslint-disable-next-line no-console
7710
+ console.warn(`An unexpected error occurred in a Channel Client Reconnection handler for channel ${next.routingInfo.channelName}`, error);
7711
+ }
7712
+ // The old endpoint is typically already gone (navigation); closing is best-effort cleanup.
7713
+ __classPrivateFieldGet$k(this, _ChannelClient_releaseConnection, "f").call(this, previous, false).catch(() => undefined);
7714
+ };
7715
+ // The endpoint is being replaced: against old providers the handshake supersedes it by id, which must
7716
+ // not close the reconnecting client.
7717
+ ChannelClientStore.clients.delete(__classPrivateFieldGet$k(this, _ChannelClient_currentConnection, "f").routingInfo.endpointId);
7718
+ const reconnectPromise = performReconnect();
7719
+ __classPrivateFieldSet$j(this, _ChannelClient_state, { status: 'reconnecting', waitForReconnection: () => reconnectPromise }, "f");
7720
+ }
7721
+ if (__classPrivateFieldGet$k(this, _ChannelClient_state, "f").status === 'reconnecting') {
7722
+ await __classPrivateFieldGet$k(this, _ChannelClient_state, "f").waitForReconnection();
7723
+ }
7724
+ });
7725
+ __classPrivateFieldSet$j(this, _ChannelClient_currentConnection, config.initialConnection, "f");
7726
+ __classPrivateFieldSet$j(this, _ChannelClient_wire, config.wire, "f");
7727
+ __classPrivateFieldSet$j(this, _ChannelClient_reestablish, config.reestablish, "f");
7728
+ __classPrivateFieldSet$j(this, _ChannelClient_unsubscribeBFCache, __classPrivateFieldGet$k(this, _ChannelClient_wire, "f").environment.onBFCacheRestore(__classPrivateFieldGet$k(this, _ChannelClient_reconnect, "f")), "f");
7729
+ __classPrivateFieldGet$k(this, _ChannelClient_attach, "f").call(this, config.initialConnection);
7620
7730
  }
7621
7731
  /**
7622
7732
  * @internal
7623
- * closes the corresponding channel and invokes the disconnect listener if an event payload is passed.
7624
- */
7625
- static handleProviderDisconnect(eventPayload) {
7626
- for (const channelClient of channelClientsByEndpointId.values()) {
7627
- if (channelClient.providerIdentity.channelId === eventPayload.channelId) {
7628
- channelClient.disconnectListener(eventPayload);
7629
- __classPrivateFieldGet$k(channelClient, _ChannelClient_close, "f").call(channelClient);
7733
+ * Closes the client. `notifyRuntime` sends `disconnect-from-channel` for the current endpoint; `emitEvent` then
7734
+ * fires the onDisconnection listener, even if notifying the runtime failed.
7735
+ */
7736
+ async [(_ChannelClient_currentConnection = new WeakMap(), _ChannelClient_state = new WeakMap(), _ChannelClient_wire = new WeakMap(), _ChannelClient_reestablish = new WeakMap(), _ChannelClient_unsubscribeBFCache = new WeakMap(), _ChannelClient_disconnectListener = new WeakMap(), _ChannelClient_reconnectListener = new WeakMap(), _ChannelClient_releaseConnection = new WeakMap(), _ChannelClient_attach = new WeakMap(), _ChannelClient_sendDisconnectToRuntime = new WeakMap(), _ChannelClient_reconnect = new WeakMap(), teardown)](emitEvent = false, notifyRuntime = true) {
7737
+ if (__classPrivateFieldGet$k(this, _ChannelClient_state, "f").status !== 'closed') {
7738
+ const identity = this.providerIdentity;
7739
+ const connection = __classPrivateFieldGet$k(this, _ChannelClient_currentConnection, "f");
7740
+ __classPrivateFieldSet$j(this, _ChannelClient_state, { status: 'closed' }, "f");
7741
+ __classPrivateFieldGet$k(this, _ChannelClient_unsubscribeBFCache, "f").call(this);
7742
+ ChannelClientStore.clients.delete(connection.routingInfo.endpointId);
7743
+ try {
7744
+ await __classPrivateFieldGet$k(this, _ChannelClient_releaseConnection, "f").call(this, connection, notifyRuntime);
7745
+ }
7746
+ finally {
7747
+ if (emitEvent) {
7748
+ try {
7749
+ __classPrivateFieldGet$k(this, _ChannelClient_disconnectListener, "f")?.call(this, identity);
7750
+ }
7751
+ catch (error) {
7752
+ // eslint-disable-next-line no-console
7753
+ console.warn(`An unexpected error occurred in a Channel Client Disconnection handler for channel ${identity.channelName}`, error);
7754
+ }
7755
+ }
7630
7756
  }
7631
7757
  }
7632
7758
  }
7633
- /**
7634
- * @internal
7635
- */
7636
- constructor(routingInfo, close, strategy) {
7637
- super();
7638
- _ChannelClient_protectedObj.set(this, void 0);
7639
- _ChannelClient_strategy.set(this, void 0);
7640
- // needs to be bound;
7641
- this.processAction = (action, payload, senderIdentity) => super.processAction(action, payload, senderIdentity);
7642
- _ChannelClient_close.set(this, () => {
7643
- channelClientsByEndpointId.delete(this.endpointId);
7644
- __classPrivateFieldGet$k(this, _ChannelClient_strategy, "f").close();
7645
- });
7646
- __classPrivateFieldSet$j(this, _ChannelClient_protectedObj, new ProtectedItems(routingInfo, close), "f");
7647
- this.disconnectListener = () => undefined;
7648
- this.endpointId = routingInfo.endpointId;
7649
- __classPrivateFieldSet$j(this, _ChannelClient_strategy, strategy, "f");
7650
- channelClientsByEndpointId.set(this.endpointId, this);
7651
- strategy.receive(this.processAction);
7652
- }
7653
7759
  /**
7654
7760
  * a read-only provider identity
7655
7761
  */
7656
7762
  get providerIdentity() {
7657
- const protectedObj = __classPrivateFieldGet$k(this, _ChannelClient_protectedObj, "f");
7658
- return protectedObj.providerIdentity;
7763
+ return { ...__classPrivateFieldGet$k(this, _ChannelClient_currentConnection, "f").routingInfo };
7659
7764
  }
7660
7765
  /**
7661
7766
  * Dispatch the given action to the channel provider. Returns a promise that resolves with the response from
@@ -7683,17 +7788,22 @@ class ChannelClient extends ChannelBase {
7683
7788
  * ```
7684
7789
  */
7685
7790
  async dispatch(action, payload) {
7686
- if (__classPrivateFieldGet$k(this, _ChannelClient_strategy, "f").isEndpointConnected(this.providerIdentity.channelId)) {
7687
- const callSites = RuntimeError.getCallSite();
7688
- return __classPrivateFieldGet$k(this, _ChannelClient_strategy, "f").send(this.providerIdentity.channelId, action, payload).catch((e) => {
7689
- throw new ChannelError$1(e, action, payload, callSites);
7690
- });
7791
+ // App pageshow handlers run alongside the bfcache restore; sending now would go out on the frozen endpoint.
7792
+ if (__classPrivateFieldGet$k(this, _ChannelClient_state, "f").status === 'reconnecting') {
7793
+ await __classPrivateFieldGet$k(this, _ChannelClient_state, "f").waitForReconnection();
7691
7794
  }
7692
- throw new Error('The client you are trying to dispatch from is disconnected from the target provider.');
7795
+ const connection = __classPrivateFieldGet$k(this, _ChannelClient_currentConnection, "f");
7796
+ if (!connection.strategy.isEndpointConnected(this.providerIdentity.channelId)) {
7797
+ throw new Error('The client you are trying to dispatch from is disconnected from the target provider.');
7798
+ }
7799
+ const callSites = RuntimeError.getCallSite();
7800
+ return connection.strategy.send(this.providerIdentity.channelId, action, payload).catch((e) => {
7801
+ throw new ChannelError$1(e, action, payload, callSites);
7802
+ });
7693
7803
  }
7694
7804
  /**
7695
- * Register a listener that is called on provider disconnection. It is passed the disconnection event of the
7696
- * disconnecting provider.
7805
+ * Register a listener that is called when this client loses its channel: the provider disconnects, the
7806
+ * endpoint drops, or a self-heal fails. It is passed the provider identity.
7697
7807
  *
7698
7808
  * @param listener
7699
7809
  *
@@ -7710,17 +7820,46 @@ class ChannelClient extends ChannelBase {
7710
7820
  * ```
7711
7821
  */
7712
7822
  onDisconnection(listener) {
7713
- this.disconnectListener = (payload) => {
7714
- try {
7715
- listener(payload);
7716
- }
7717
- catch (err) {
7718
- throw new Error(`Error while calling the onDisconnection callback: ${err.message}`);
7719
- }
7720
- finally {
7721
- this.disconnectListener = () => undefined;
7722
- }
7723
- };
7823
+ __classPrivateFieldSet$j(this, _ChannelClient_disconnectListener, listener, "f");
7824
+ }
7825
+ /**
7826
+ * Register a listener that is called each time this client transparently self-heals its connection to the
7827
+ * provider.
7828
+ *
7829
+ * @remarks
7830
+ * A channel client's underlying endpoint is torn down while its document sits in the back/forward cache
7831
+ * (bfcache). If the back/forward cache is supported and the document is restored from it, the client
7832
+ * automatically re-handshakes with the provider and swaps in a fresh endpoint; all of this client's
7833
+ * `register`/middleware handlers are preserved, so no re-registration is required.
7834
+ *
7835
+ * A restore is not guaranteed: Chromium may evict a cached document at any time, in which case navigating
7836
+ * back reloads the page and a new client must connect.
7837
+ *
7838
+ * This listener fires once per successful reconnect, after the new endpoint is live, and can fire multiple
7839
+ * times over the client's lifetime. It is triggered when the document is restored from the back/forward
7840
+ * cache (a bfcache `pageshow`). It does not fire for the initial connection, nor after
7841
+ * {@link ChannelClient#disconnect disconnect}. {@link ChannelClient#onDisconnection onDisconnection} fires
7842
+ * when the provider or endpoint goes away.
7843
+ *
7844
+ * The info provides the reconnected {@link OpenFin.ProviderIdentity provider identity} along with
7845
+ * the previous and new endpoint ids.
7846
+ *
7847
+ * @param listener
7848
+ *
7849
+ * @example
7850
+ *
7851
+ * ```js
7852
+ * (async () => {
7853
+ * const client = await fin.InterApplicationBus.Channel.connect('channelName');
7854
+ *
7855
+ * client.onReconnection(({ providerIdentity, previousEndpointId, endpointId }) => {
7856
+ * console.log(`Reconnected to ${providerIdentity.channelName}: ${previousEndpointId} -> ${endpointId}`);
7857
+ * });
7858
+ * })();
7859
+ * ```
7860
+ */
7861
+ onReconnection(listener) {
7862
+ __classPrivateFieldSet$j(this, _ChannelClient_reconnectListener, listener, "f");
7724
7863
  }
7725
7864
  /**
7726
7865
  * Disconnects the client from the channel.
@@ -7736,28 +7875,16 @@ class ChannelClient extends ChannelBase {
7736
7875
  * ```
7737
7876
  */
7738
7877
  async disconnect() {
7739
- await this.sendDisconnectAction();
7740
- __classPrivateFieldGet$k(this, _ChannelClient_close, "f").call(this);
7741
- }
7742
- async sendDisconnectAction() {
7743
- const protectedObj = __classPrivateFieldGet$k(this, _ChannelClient_protectedObj, "f");
7744
- await protectedObj.close();
7878
+ await this[teardown](false);
7745
7879
  }
7746
7880
  /**
7747
7881
  * @internal
7748
- * Writing this as a static to keep code together, but in environments with a wire, this will be DI'd into the protectedObject class as close.
7882
+ * Notify the runtime that the current endpoint has disconnected.
7749
7883
  */
7750
- static async wireClose(wire, providerIdentity, endpointId) {
7751
- const { channelName, uuid, name } = providerIdentity;
7752
- await wire.sendAction('disconnect-from-channel', {
7753
- channelName,
7754
- uuid,
7755
- name,
7756
- endpointId
7757
- });
7884
+ async sendDisconnectAction() {
7885
+ await this.disconnect();
7758
7886
  }
7759
7887
  }
7760
- _ChannelClient_protectedObj = new WeakMap(), _ChannelClient_strategy = new WeakMap(), _ChannelClient_close = new WeakMap();
7761
7888
 
7762
7889
  function exhaustiveCheck(value, allowed) {
7763
7890
  throw new Error(`Unsupported value: ${value}${allowed ? `\n Supported values are: ${allowed.join('')}` : ''}`);
@@ -8681,7 +8808,7 @@ class MessageReceiver extends Base {
8681
8808
  const endpointIdFromPreviousConnection = this.latestEndpointIdByChannelId.get(channelId);
8682
8809
  if (endpointIdFromPreviousConnection) {
8683
8810
  // Not convinced by this way of doing things, but pushing up for now.
8684
- ChannelClient.closeChannelByEndpointId(endpointIdFromPreviousConnection);
8811
+ ChannelClientStore.handleEndpointDisconnect(endpointIdFromPreviousConnection);
8685
8812
  // eslint-disable-next-line no-console
8686
8813
  console.warn('You have created a second connection to an older provider. First connection has been removed from the clientMap');
8687
8814
  // eslint-disable-next-line no-console
@@ -9056,7 +9183,7 @@ var __classPrivateFieldGet$e = (undefined && undefined.__classPrivateFieldGet) |
9056
9183
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
9057
9184
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
9058
9185
  };
9059
- var _Channel_connectionManager, _Channel_internalEmitter, _Channel_readyForProvider, _Channel_readyToConnect;
9186
+ var _Channel_connectionManager, _Channel_internalEmitter, _Channel_readyForProvider, _Channel_readyToConnect, _Channel_establishConnection;
9060
9187
  function retryDelay(count) {
9061
9188
  const interval = 500; // base delay
9062
9189
  const steps = 10; // How many retries to do before incrementing the delay
@@ -9106,13 +9233,33 @@ class Channel extends EmitterBase {
9106
9233
  await Promise.all([
9107
9234
  __classPrivateFieldGet$e(this, _Channel_readyForProvider, "f").getValue(),
9108
9235
  this.on('disconnected', (eventPayload) => {
9109
- ChannelClient.handleProviderDisconnect(eventPayload);
9236
+ ChannelClientStore.handleProviderDisconnect(eventPayload);
9110
9237
  }),
9111
9238
  this.on('connected', (...args) => {
9112
9239
  __classPrivateFieldGet$e(this, _Channel_internalEmitter, "f").emit('connected', ...args);
9113
9240
  })
9114
9241
  ]).catch(() => new Error('error setting up channel connection listeners'));
9115
9242
  }));
9243
+ // Runs the client handshake (offer -> connect -> strategy). Re-runnable so a ChannelClient can reconnect
9244
+ // with a fresh endpointId; offers are single-use (esp. RTC) so a new offer is created each call.
9245
+ _Channel_establishConnection.set(this, async (channelName, opts, connectionUrl) => {
9246
+ const { offer, rtc: rtcPacket } = await __classPrivateFieldGet$e(this, _Channel_connectionManager, "f").createClientOffer(opts);
9247
+ // getInfo() can hang during a bfcache restore, so reconnects reuse the URL resolved on the first connect;
9248
+ // it cannot change within a client's lifetime.
9249
+ let targetUrl = connectionUrl;
9250
+ if (targetUrl === undefined && (this.fin.me.isFrame || this.fin.me.isView || this.fin.me.isWindow)) {
9251
+ targetUrl = (await this.fin.me.getInfo()).url;
9252
+ }
9253
+ const connectPayload = {
9254
+ channelName,
9255
+ ...opts,
9256
+ offer,
9257
+ connectionUrl: targetUrl
9258
+ };
9259
+ const routingInfo = await this.safeConnect(channelName, opts.wait, connectPayload);
9260
+ const strategy = await __classPrivateFieldGet$e(this, _Channel_connectionManager, "f").createClientStrategy(rtcPacket, routingInfo);
9261
+ return { routingInfo, strategy, connectionUrl: targetUrl };
9262
+ });
9116
9263
  __classPrivateFieldSet$d(this, _Channel_connectionManager, new ConnectionManager(wire), "f");
9117
9264
  }
9118
9265
  /**
@@ -9284,35 +9431,14 @@ class Channel extends EmitterBase {
9284
9431
  throw new Error('Please provide a channelName string to connect to a channel.');
9285
9432
  }
9286
9433
  const opts = { wait: true, ...this.wire.environment.getDefaultChannelOptions().connect, ...options };
9287
- const { offer, rtc: rtcPacket } = await __classPrivateFieldGet$e(this, _Channel_connectionManager, "f").createClientOffer(opts);
9288
- let connectionUrl;
9289
- if (this.fin.me.isFrame || this.fin.me.isView || this.fin.me.isWindow) {
9290
- connectionUrl = (await this.fin.me.getInfo()).url;
9291
- }
9292
- const connectPayload = {
9293
- channelName,
9294
- ...opts,
9295
- offer,
9296
- connectionUrl
9297
- };
9298
- const routingInfo = await this.safeConnect(channelName, opts.wait, connectPayload);
9299
- const strategy = await __classPrivateFieldGet$e(this, _Channel_connectionManager, "f").createClientStrategy(rtcPacket, routingInfo);
9300
- const channel = new ChannelClient(routingInfo, () => ChannelClient.wireClose(this.wire, routingInfo, routingInfo.endpointId), strategy);
9301
- // It is the client's responsibility to handle endpoint disconnection to the provider.
9302
- // If the endpoint dies, the client will force a disconnection through the core.
9303
- // The provider does not care about endpoint disconnection.
9304
- strategy.onEndpointDisconnect(routingInfo.channelId, async () => {
9305
- try {
9306
- await channel.sendDisconnectAction();
9307
- }
9308
- catch (error) {
9309
- console.warn(`Something went wrong during disconnect for client with uuid: ${routingInfo.uuid} / name: ${routingInfo.name} / endpointId: ${routingInfo.endpointId}.`);
9310
- }
9311
- finally {
9312
- ChannelClient.handleProviderDisconnect(routingInfo);
9313
- }
9434
+ const initialConnection = await __classPrivateFieldGet$e(this, _Channel_establishConnection, "f").call(this, channelName, opts);
9435
+ // Never wait on a self-heal: a provider missing at restore has gone away, which is a disconnection. A provider
9436
+ // created later under the same name is a new instance without this client's provider-side state.
9437
+ return new ChannelClient({
9438
+ initialConnection,
9439
+ wire: this.wire,
9440
+ reestablish: () => __classPrivateFieldGet$e(this, _Channel_establishConnection, "f").call(this, channelName, { ...opts, wait: false }, initialConnection.connectionUrl)
9314
9441
  });
9315
- return channel;
9316
9442
  }
9317
9443
  /**
9318
9444
  * Create a new channel.
@@ -9369,7 +9495,7 @@ class Channel extends EmitterBase {
9369
9495
  return __classPrivateFieldGet$e(this, _Channel_connectionManager, "f").createProvider(options, providerIdentity);
9370
9496
  }
9371
9497
  }
9372
- _Channel_connectionManager = new WeakMap(), _Channel_internalEmitter = new WeakMap(), _Channel_readyForProvider = new WeakMap(), _Channel_readyToConnect = new WeakMap();
9498
+ _Channel_connectionManager = new WeakMap(), _Channel_internalEmitter = new WeakMap(), _Channel_readyForProvider = new WeakMap(), _Channel_readyToConnect = new WeakMap(), _Channel_establishConnection = new WeakMap();
9373
9499
 
9374
9500
  /**
9375
9501
  * Entry point for the OpenFin `InterApplicationBus` API (`fin.InterApplicationBus`).
@@ -9403,11 +9529,27 @@ class InterApplicationBus extends Base {
9403
9529
  subscriberRemoved: 'subscriber-removed'
9404
9530
  };
9405
9531
  this.refCounter = new RefCounter();
9532
+ /**
9533
+ * One entry per subscription the runtime knows about, keyed by the same subscription key the
9534
+ * emitter and the ref counter use. Listeners sharing a key share a single entry.
9535
+ */
9536
+ this.activeSubscriptions = new Map();
9406
9537
  this.Channel = new Channel(wire);
9407
9538
  this.emitter = new events.EventEmitter();
9408
9539
  wire.registerMessageHandler(this.onmessage.bind(this));
9409
9540
  this.on = this.emitter.on.bind(this.emitter);
9410
9541
  this.removeAllListeners = this.emitter.removeAllListeners.bind(this.emitter);
9542
+ wire.environment.onBFCacheRestore(() => {
9543
+ Array.from(this.activeSubscriptions.values()).forEach(async (subscription) => {
9544
+ try {
9545
+ await this.wire.sendAction('subscribe', subscription);
9546
+ }
9547
+ catch (error) {
9548
+ // eslint-disable-next-line no-console
9549
+ console.error('Failed to restore InterApplicationBus subscription after bfcache restore', error);
9550
+ }
9551
+ });
9552
+ });
9411
9553
  }
9412
9554
  /**
9413
9555
  * Publishes a message to all applications running on OpenFin Runtime that
@@ -9459,30 +9601,39 @@ class InterApplicationBus extends Base {
9459
9601
  * Subscribes to messages from the specified application on the specified topic.
9460
9602
  * @param source This object is described in the Identity in the typedef
9461
9603
  * @param topic The topic on which the message is sent
9462
- * @param listener A function that is called when a message has
9463
- * been received. It is passed the message, uuid and name of the sending application.
9604
+ * @param listener A function that is called when a message has been received. It is passed
9605
+ * two arguments: the message, and the identity (`{ uuid, name }`) of the sender.
9464
9606
  * The message can be either a primitive data type (string, number, or boolean) or
9465
9607
  * composite data type (object, array) that is composed of other primitive or composite
9466
9608
  * data types
9467
9609
  *
9610
+ * @remarks This differs from the legacy `fin.desktop.InterApplicationBus.subscribe` API,
9611
+ * which passes `(message, uuid, name)` as separate arguments.
9612
+ *
9468
9613
  * @example
9469
9614
  * ```js
9470
9615
  * // subscribe to a specified application
9471
- * fin.InterApplicationBus.subscribe(fin.me, 'topic', sub_msg => console.log(sub_msg)).then(() => console.log('Subscribed to the specified application')).catch(err => console.log(err));
9616
+ * fin.InterApplicationBus.subscribe(fin.me, 'topic', (message, sender) => {
9617
+ * console.log(`Received from ${sender.uuid}/${sender.name}:`, message);
9618
+ * }).then(() => console.log('Subscribed to the specified application')).catch(err => console.log(err));
9472
9619
  *
9473
9620
  * // subscribe to wildcard
9474
9621
  * fin.InterApplicationBus.subscribe({ uuid: '*' }, 'topic', sub_msg => console.log(sub_msg)).then(() => console.log('Subscribed to *')).catch(err => console.log(err));
9475
9622
  * ```
9476
9623
  */
9477
9624
  subscribe(source, topic, listener) {
9478
- const subKey = this.createSubscriptionKey(source.uuid, source.name || '*', topic);
9625
+ const sourceWindowName = source.name || '*';
9626
+ const subKey = this.createSubscriptionKey(source.uuid, sourceWindowName, topic);
9627
+ const subscription = {
9628
+ sourceUuid: source.uuid,
9629
+ sourceWindowName,
9630
+ topic,
9631
+ destinationWindowName: this.me.name
9632
+ };
9479
9633
  const sendSubscription = async () => {
9480
- await this.wire.sendAction('subscribe', {
9481
- sourceUuid: source.uuid,
9482
- sourceWindowName: source.name || '*',
9483
- topic,
9484
- destinationWindowName: this.me.name
9485
- });
9634
+ // Journaled before the await so a concurrent unsubscribe can find it.
9635
+ this.activeSubscriptions.set(subKey, subscription);
9636
+ await this.wire.sendAction('subscribe', subscription);
9486
9637
  };
9487
9638
  const alreadySubscribed = () => {
9488
9639
  return Promise.resolve();
@@ -9518,6 +9669,9 @@ class InterApplicationBus extends Base {
9518
9669
  const sourceWindowName = source.name || '*';
9519
9670
  const subKey = this.createSubscriptionKey(source.uuid, sourceWindowName, topic);
9520
9671
  const sendUnsubscription = async () => {
9672
+ // Dropped before the await so that a restoration landing while this unsubscribe is
9673
+ // still in flight does not replay the subscription being torn down.
9674
+ this.activeSubscriptions.delete(subKey);
9521
9675
  await this.wire.sendAction('unsubscribe', {
9522
9676
  sourceUuid: source.uuid,
9523
9677
  sourceWindowName,
@@ -9526,6 +9680,9 @@ class InterApplicationBus extends Base {
9526
9680
  });
9527
9681
  };
9528
9682
  const dontSendUnsubscription = () => {
9683
+ // TODO(CORE-2749): never settles - the executor evaluates `r` instead of calling it,
9684
+ // so awaiting a non-final unsubscribe hangs the caller. Should be `Promise.resolve()`,
9685
+ // but that changes long-standing behavior, so it is tracked separately from CORE-2740.
9529
9686
  return new Promise((r) => r).then(() => undefined);
9530
9687
  };
9531
9688
  this.emitter.removeListener(subKey, listener);
@@ -17594,7 +17751,7 @@ class NodeEnvironment extends BaseEnvironment {
17594
17751
  };
17595
17752
  }
17596
17753
  getAdapterVersionSync() {
17597
- return "47.100.27";
17754
+ return "47.100.29";
17598
17755
  }
17599
17756
  observeBounds(element, onChange) {
17600
17757
  throw new Error('Method not implemented.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfin/node-adapter",
3
- "version": "47.100.27",
3
+ "version": "47.100.29",
4
4
  "description": "See README.md",
5
5
  "main": "out/node-adapter.js",
6
6
  "types": "out/node-adapter.d.ts",
@@ -20,7 +20,7 @@
20
20
  "es-toolkit": "^1.39.3",
21
21
  "ws": "^7.5.11",
22
22
  "tslib": "2.8.1",
23
- "@openfin/core": "47.100.27"
23
+ "@openfin/core": "47.100.29"
24
24
  },
25
25
  "scripts": {
26
26
  "prebuild": "rimraf ./out",