@openfin/remote-adapter 47.100.27 → 47.100.31

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/remote-adapter.js +275 -118
  2. package/package.json +2 -2
@@ -10200,19 +10200,39 @@ let ChannelError$1 = class ChannelError extends Error {
10200
10200
  }
10201
10201
  };
10202
10202
 
10203
- var __classPrivateFieldGet$i = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
10204
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10205
- 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");
10206
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
10207
- };
10203
+ /**
10204
+ * Tracks live channel clients by their current endpoint id and routes runtime lifecycle events to them.
10205
+ */
10206
+ class ChannelClientStore {
10207
+ // The provider for a channel has gone away: every client on that channel is disconnected.
10208
+ static handleProviderDisconnect(eventPayload) {
10209
+ [...ChannelClientStore.clients.values()].forEach((client) => {
10210
+ if (client.providerIdentity.channelId === eventPayload.channelId) {
10211
+ client[teardown](true, false);
10212
+ }
10213
+ });
10214
+ }
10215
+ // Old providers omit endpoint ids, so a new connection silently replaces the previous client on the channel.
10216
+ static handleEndpointDisconnect(endpointId) {
10217
+ ChannelClientStore.clients.get(endpointId)?.[teardown](false, false);
10218
+ }
10219
+ }
10220
+ ChannelClientStore.clients = new Map();
10221
+
10208
10222
  var __classPrivateFieldSet$h = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
10209
10223
  if (kind === "m") throw new TypeError("Private method is not writable");
10210
10224
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
10211
10225
  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");
10212
10226
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
10213
10227
  };
10214
- var _ChannelClient_protectedObj, _ChannelClient_strategy, _ChannelClient_close;
10215
- const channelClientsByEndpointId = new Map();
10228
+ var __classPrivateFieldGet$i = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
10229
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10230
+ 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");
10231
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
10232
+ };
10233
+ var _ChannelClient_currentConnection, _ChannelClient_state, _ChannelClient_wire, _ChannelClient_reestablish, _ChannelClient_unsubscribeBFCache, _ChannelClient_disconnectListener, _ChannelClient_reconnectListener, _ChannelClient_releaseConnection, _ChannelClient_attach, _ChannelClient_sendDisconnectToRuntime, _ChannelClient_reconnect;
10234
+ // Key for ChannelClient's internal teardown function. Prevents the need to expose internal functions.
10235
+ const teardown = Symbol('teardown');
10216
10236
  /**
10217
10237
  * Instance created to enable use of a channel as a client. Allows for communication with the
10218
10238
  * {@link ChannelProvider ChannelProvider} by invoking an action on the
@@ -10241,50 +10261,135 @@ class ChannelClient extends ChannelBase {
10241
10261
  /**
10242
10262
  * @internal
10243
10263
  */
10244
- static closeChannelByEndpointId(id) {
10245
- const channel = channelClientsByEndpointId.get(id);
10246
- if (channel) {
10247
- __classPrivateFieldGet$i(channel, _ChannelClient_close, "f").call(channel);
10248
- }
10264
+ constructor(config) {
10265
+ super();
10266
+ _ChannelClient_currentConnection.set(this, void 0);
10267
+ _ChannelClient_state.set(this, { status: 'connected' });
10268
+ _ChannelClient_wire.set(this, void 0);
10269
+ _ChannelClient_reestablish.set(this, void 0);
10270
+ _ChannelClient_unsubscribeBFCache.set(this, void 0);
10271
+ _ChannelClient_disconnectListener.set(this, void 0);
10272
+ _ChannelClient_reconnectListener.set(this, void 0);
10273
+ // needs to be bound;
10274
+ this.processAction = (action, payload, senderIdentity) => super.processAction(action, payload, senderIdentity);
10275
+ _ChannelClient_releaseConnection.set(this, async (connection, notifyRuntime) => {
10276
+ connection.strategy.close().catch(() => undefined);
10277
+ if (notifyRuntime) {
10278
+ await __classPrivateFieldGet$i(this, _ChannelClient_sendDisconnectToRuntime, "f").call(this, connection);
10279
+ }
10280
+ });
10281
+ _ChannelClient_attach.set(this, (instance) => {
10282
+ ChannelClientStore.clients.set(instance.routingInfo.endpointId, this);
10283
+ instance.strategy.receive(this.processAction);
10284
+ instance.strategy.onEndpointDisconnect(instance.routingInfo.channelId, () => {
10285
+ // Only a drop of the live endpoint disconnects. During a self-heal the endpoint is already being replaced
10286
+ // and the handshake decides the outcome; after it, closing the replaced strategy (RTC/combined fire this on
10287
+ // close) must not tear down the client now live on a fresh endpoint.
10288
+ if (__classPrivateFieldGet$i(this, _ChannelClient_currentConnection, "f") === instance && __classPrivateFieldGet$i(this, _ChannelClient_state, "f").status === 'connected') {
10289
+ this[teardown](true).catch((error) => {
10290
+ // eslint-disable-next-line no-console
10291
+ console.warn(`Failed to notify the runtime that channel '${instance.routingInfo.channelName}' endpoint ${instance.routingInfo.endpointId} disconnected.`, error);
10292
+ });
10293
+ }
10294
+ });
10295
+ });
10296
+ _ChannelClient_sendDisconnectToRuntime.set(this, async ({ routingInfo }) => {
10297
+ const { channelName, uuid, name, endpointId } = routingInfo;
10298
+ await __classPrivateFieldGet$i(this, _ChannelClient_wire, "f").sendAction('disconnect-from-channel', { channelName, uuid, name, endpointId });
10299
+ });
10300
+ // Re-handshakes (new endpointId) and swaps in the fresh endpoint. Client-side registrations and middleware live
10301
+ // on `this` (ChannelBase), so they carry over. Overlapping BF-Cache restores share one in-flight handshake.
10302
+ _ChannelClient_reconnect.set(this, async () => {
10303
+ if (__classPrivateFieldGet$i(this, _ChannelClient_state, "f").status === 'connected') {
10304
+ const performReconnect = async () => {
10305
+ let next;
10306
+ try {
10307
+ next = await __classPrivateFieldGet$i(this, _ChannelClient_reestablish, "f").call(this);
10308
+ }
10309
+ catch (error) {
10310
+ // No automatic retry: recovery is up to the onDisconnection handler.
10311
+ // eslint-disable-next-line no-console
10312
+ console.warn(`Channel '${__classPrivateFieldGet$i(this, _ChannelClient_currentConnection, "f").routingInfo.channelName}' failed to self-heal after BF-Cache restoration; disconnecting.`, error);
10313
+ await this[teardown](true).catch((disconnectError) => {
10314
+ // eslint-disable-next-line no-console
10315
+ console.warn(`Failed to notify the runtime that channel '${__classPrivateFieldGet$i(this, _ChannelClient_currentConnection, "f").routingInfo.channelName}' disconnected.`, disconnectError);
10316
+ });
10317
+ return;
10318
+ }
10319
+ // A disconnect() or provider disconnect may have landed while the handshake was in flight; if so,
10320
+ // tear down the freshly established endpoint instead of resurrecting a closed client.
10321
+ if (__classPrivateFieldGet$i(this, _ChannelClient_state, "f").status !== 'reconnecting') {
10322
+ __classPrivateFieldGet$i(this, _ChannelClient_releaseConnection, "f").call(this, next, true).catch(() => undefined);
10323
+ return;
10324
+ }
10325
+ const previous = __classPrivateFieldGet$i(this, _ChannelClient_currentConnection, "f");
10326
+ __classPrivateFieldSet$h(this, _ChannelClient_currentConnection, next, "f");
10327
+ __classPrivateFieldGet$i(this, _ChannelClient_attach, "f").call(this, next);
10328
+ __classPrivateFieldSet$h(this, _ChannelClient_state, { status: 'connected' }, "f");
10329
+ // A throwing listener must not abort the reconnect.
10330
+ try {
10331
+ __classPrivateFieldGet$i(this, _ChannelClient_reconnectListener, "f")?.call(this, {
10332
+ providerIdentity: this.providerIdentity,
10333
+ previousEndpointId: previous.routingInfo.endpointId,
10334
+ endpointId: next.routingInfo.endpointId
10335
+ });
10336
+ }
10337
+ catch (error) {
10338
+ // eslint-disable-next-line no-console
10339
+ console.warn(`An unexpected error occurred in a Channel Client Reconnection handler for channel ${next.routingInfo.channelName}`, error);
10340
+ }
10341
+ // The old endpoint is typically already gone (navigation); closing is best-effort cleanup.
10342
+ __classPrivateFieldGet$i(this, _ChannelClient_releaseConnection, "f").call(this, previous, false).catch(() => undefined);
10343
+ };
10344
+ // The endpoint is being replaced: against old providers the handshake supersedes it by id, which must
10345
+ // not close the reconnecting client.
10346
+ ChannelClientStore.clients.delete(__classPrivateFieldGet$i(this, _ChannelClient_currentConnection, "f").routingInfo.endpointId);
10347
+ const reconnectPromise = performReconnect();
10348
+ __classPrivateFieldSet$h(this, _ChannelClient_state, { status: 'reconnecting', waitForReconnection: () => reconnectPromise }, "f");
10349
+ }
10350
+ if (__classPrivateFieldGet$i(this, _ChannelClient_state, "f").status === 'reconnecting') {
10351
+ await __classPrivateFieldGet$i(this, _ChannelClient_state, "f").waitForReconnection();
10352
+ }
10353
+ });
10354
+ __classPrivateFieldSet$h(this, _ChannelClient_currentConnection, config.initialConnection, "f");
10355
+ __classPrivateFieldSet$h(this, _ChannelClient_wire, config.wire, "f");
10356
+ __classPrivateFieldSet$h(this, _ChannelClient_reestablish, config.reestablish, "f");
10357
+ __classPrivateFieldSet$h(this, _ChannelClient_unsubscribeBFCache, __classPrivateFieldGet$i(this, _ChannelClient_wire, "f").environment.onBFCacheRestore(__classPrivateFieldGet$i(this, _ChannelClient_reconnect, "f")), "f");
10358
+ __classPrivateFieldGet$i(this, _ChannelClient_attach, "f").call(this, config.initialConnection);
10249
10359
  }
10250
10360
  /**
10251
10361
  * @internal
10252
- * closes the corresponding channel and invokes the disconnect listener if an event payload is passed.
10253
- */
10254
- static handleProviderDisconnect(eventPayload) {
10255
- for (const channelClient of channelClientsByEndpointId.values()) {
10256
- if (channelClient.providerIdentity.channelId === eventPayload.channelId) {
10257
- channelClient.disconnectListener(eventPayload);
10258
- __classPrivateFieldGet$i(channelClient, _ChannelClient_close, "f").call(channelClient);
10362
+ * Closes the client. `notifyRuntime` sends `disconnect-from-channel` for the current endpoint; `emitEvent` then
10363
+ * fires the onDisconnection listener, even if notifying the runtime failed.
10364
+ */
10365
+ 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) {
10366
+ if (__classPrivateFieldGet$i(this, _ChannelClient_state, "f").status !== 'closed') {
10367
+ const identity = this.providerIdentity;
10368
+ const connection = __classPrivateFieldGet$i(this, _ChannelClient_currentConnection, "f");
10369
+ __classPrivateFieldSet$h(this, _ChannelClient_state, { status: 'closed' }, "f");
10370
+ __classPrivateFieldGet$i(this, _ChannelClient_unsubscribeBFCache, "f").call(this);
10371
+ ChannelClientStore.clients.delete(connection.routingInfo.endpointId);
10372
+ try {
10373
+ await __classPrivateFieldGet$i(this, _ChannelClient_releaseConnection, "f").call(this, connection, notifyRuntime);
10374
+ }
10375
+ finally {
10376
+ if (emitEvent) {
10377
+ try {
10378
+ __classPrivateFieldGet$i(this, _ChannelClient_disconnectListener, "f")?.call(this, identity);
10379
+ }
10380
+ catch (error) {
10381
+ // eslint-disable-next-line no-console
10382
+ console.warn(`An unexpected error occurred in a Channel Client Disconnection handler for channel ${identity.channelName}`, error);
10383
+ }
10384
+ }
10259
10385
  }
10260
10386
  }
10261
10387
  }
10262
- /**
10263
- * @internal
10264
- */
10265
- constructor(routingInfo, close, strategy) {
10266
- super();
10267
- _ChannelClient_protectedObj.set(this, void 0);
10268
- _ChannelClient_strategy.set(this, void 0);
10269
- // needs to be bound;
10270
- this.processAction = (action, payload, senderIdentity) => super.processAction(action, payload, senderIdentity);
10271
- _ChannelClient_close.set(this, () => {
10272
- channelClientsByEndpointId.delete(this.endpointId);
10273
- __classPrivateFieldGet$i(this, _ChannelClient_strategy, "f").close();
10274
- });
10275
- __classPrivateFieldSet$h(this, _ChannelClient_protectedObj, new ProtectedItems(routingInfo, close), "f");
10276
- this.disconnectListener = () => undefined;
10277
- this.endpointId = routingInfo.endpointId;
10278
- __classPrivateFieldSet$h(this, _ChannelClient_strategy, strategy, "f");
10279
- channelClientsByEndpointId.set(this.endpointId, this);
10280
- strategy.receive(this.processAction);
10281
- }
10282
10388
  /**
10283
10389
  * a read-only provider identity
10284
10390
  */
10285
10391
  get providerIdentity() {
10286
- const protectedObj = __classPrivateFieldGet$i(this, _ChannelClient_protectedObj, "f");
10287
- return protectedObj.providerIdentity;
10392
+ return { ...__classPrivateFieldGet$i(this, _ChannelClient_currentConnection, "f").routingInfo };
10288
10393
  }
10289
10394
  /**
10290
10395
  * Dispatch the given action to the channel provider. Returns a promise that resolves with the response from
@@ -10312,17 +10417,22 @@ class ChannelClient extends ChannelBase {
10312
10417
  * ```
10313
10418
  */
10314
10419
  async dispatch(action, payload) {
10315
- if (__classPrivateFieldGet$i(this, _ChannelClient_strategy, "f").isEndpointConnected(this.providerIdentity.channelId)) {
10316
- const callSites = RuntimeError.getCallSite();
10317
- return __classPrivateFieldGet$i(this, _ChannelClient_strategy, "f").send(this.providerIdentity.channelId, action, payload).catch((e) => {
10318
- throw new ChannelError$1(e, action, payload, callSites);
10319
- });
10420
+ // App pageshow handlers run alongside the bfcache restore; sending now would go out on the frozen endpoint.
10421
+ if (__classPrivateFieldGet$i(this, _ChannelClient_state, "f").status === 'reconnecting') {
10422
+ await __classPrivateFieldGet$i(this, _ChannelClient_state, "f").waitForReconnection();
10320
10423
  }
10321
- throw new Error('The client you are trying to dispatch from is disconnected from the target provider.');
10424
+ const connection = __classPrivateFieldGet$i(this, _ChannelClient_currentConnection, "f");
10425
+ if (!connection.strategy.isEndpointConnected(this.providerIdentity.channelId)) {
10426
+ throw new Error('The client you are trying to dispatch from is disconnected from the target provider.');
10427
+ }
10428
+ const callSites = RuntimeError.getCallSite();
10429
+ return connection.strategy.send(this.providerIdentity.channelId, action, payload).catch((e) => {
10430
+ throw new ChannelError$1(e, action, payload, callSites);
10431
+ });
10322
10432
  }
10323
10433
  /**
10324
- * Register a listener that is called on provider disconnection. It is passed the disconnection event of the
10325
- * disconnecting provider.
10434
+ * Register a listener that is called when this client loses its channel: the provider disconnects, the
10435
+ * endpoint drops, or a self-heal fails. It is passed the provider identity.
10326
10436
  *
10327
10437
  * @param listener
10328
10438
  *
@@ -10339,17 +10449,46 @@ class ChannelClient extends ChannelBase {
10339
10449
  * ```
10340
10450
  */
10341
10451
  onDisconnection(listener) {
10342
- this.disconnectListener = (payload) => {
10343
- try {
10344
- listener(payload);
10345
- }
10346
- catch (err) {
10347
- throw new Error(`Error while calling the onDisconnection callback: ${err.message}`);
10348
- }
10349
- finally {
10350
- this.disconnectListener = () => undefined;
10351
- }
10352
- };
10452
+ __classPrivateFieldSet$h(this, _ChannelClient_disconnectListener, listener, "f");
10453
+ }
10454
+ /**
10455
+ * Register a listener that is called each time this client transparently self-heals its connection to the
10456
+ * provider.
10457
+ *
10458
+ * @remarks
10459
+ * A channel client's underlying endpoint is torn down while its document sits in the back/forward cache
10460
+ * (bfcache). If the back/forward cache is supported and the document is restored from it, the client
10461
+ * automatically re-handshakes with the provider and swaps in a fresh endpoint; all of this client's
10462
+ * `register`/middleware handlers are preserved, so no re-registration is required.
10463
+ *
10464
+ * A restore is not guaranteed: Chromium may evict a cached document at any time, in which case navigating
10465
+ * back reloads the page and a new client must connect.
10466
+ *
10467
+ * This listener fires once per successful reconnect, after the new endpoint is live, and can fire multiple
10468
+ * times over the client's lifetime. It is triggered when the document is restored from the back/forward
10469
+ * cache (a bfcache `pageshow`). It does not fire for the initial connection, nor after
10470
+ * {@link ChannelClient#disconnect disconnect}. {@link ChannelClient#onDisconnection onDisconnection} fires
10471
+ * when the provider or endpoint goes away.
10472
+ *
10473
+ * The info provides the reconnected {@link OpenFin.ProviderIdentity provider identity} along with
10474
+ * the previous and new endpoint ids.
10475
+ *
10476
+ * @param listener
10477
+ *
10478
+ * @example
10479
+ *
10480
+ * ```js
10481
+ * (async () => {
10482
+ * const client = await fin.InterApplicationBus.Channel.connect('channelName');
10483
+ *
10484
+ * client.onReconnection(({ providerIdentity, previousEndpointId, endpointId }) => {
10485
+ * console.log(`Reconnected to ${providerIdentity.channelName}: ${previousEndpointId} -> ${endpointId}`);
10486
+ * });
10487
+ * })();
10488
+ * ```
10489
+ */
10490
+ onReconnection(listener) {
10491
+ __classPrivateFieldSet$h(this, _ChannelClient_reconnectListener, listener, "f");
10353
10492
  }
10354
10493
  /**
10355
10494
  * Disconnects the client from the channel.
@@ -10365,28 +10504,16 @@ class ChannelClient extends ChannelBase {
10365
10504
  * ```
10366
10505
  */
10367
10506
  async disconnect() {
10368
- await this.sendDisconnectAction();
10369
- __classPrivateFieldGet$i(this, _ChannelClient_close, "f").call(this);
10370
- }
10371
- async sendDisconnectAction() {
10372
- const protectedObj = __classPrivateFieldGet$i(this, _ChannelClient_protectedObj, "f");
10373
- await protectedObj.close();
10507
+ await this[teardown](false);
10374
10508
  }
10375
10509
  /**
10376
10510
  * @internal
10377
- * 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.
10511
+ * Notify the runtime that the current endpoint has disconnected.
10378
10512
  */
10379
- static async wireClose(wire, providerIdentity, endpointId) {
10380
- const { channelName, uuid, name } = providerIdentity;
10381
- await wire.sendAction('disconnect-from-channel', {
10382
- channelName,
10383
- uuid,
10384
- name,
10385
- endpointId
10386
- });
10513
+ async sendDisconnectAction() {
10514
+ await this.disconnect();
10387
10515
  }
10388
10516
  }
10389
- _ChannelClient_protectedObj = new WeakMap(), _ChannelClient_strategy = new WeakMap(), _ChannelClient_close = new WeakMap();
10390
10517
 
10391
10518
  function exhaustiveCheck(value, allowed) {
10392
10519
  throw new Error(`Unsupported value: ${value}${allowed ? `\n Supported values are: ${allowed.join('')}` : ''}`);
@@ -11296,7 +11423,7 @@ class MessageReceiver extends Base {
11296
11423
  const endpointIdFromPreviousConnection = this.latestEndpointIdByChannelId.get(channelId);
11297
11424
  if (endpointIdFromPreviousConnection) {
11298
11425
  // Not convinced by this way of doing things, but pushing up for now.
11299
- ChannelClient.closeChannelByEndpointId(endpointIdFromPreviousConnection);
11426
+ ChannelClientStore.handleEndpointDisconnect(endpointIdFromPreviousConnection);
11300
11427
  // eslint-disable-next-line no-console
11301
11428
  console.warn('You have created a second connection to an older provider. First connection has been removed from the clientMap');
11302
11429
  // eslint-disable-next-line no-console
@@ -11671,7 +11798,7 @@ var __classPrivateFieldGet$c = (undefined && undefined.__classPrivateFieldGet) |
11671
11798
  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");
11672
11799
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11673
11800
  };
11674
- var _Channel_connectionManager, _Channel_internalEmitter, _Channel_readyForProvider, _Channel_readyToConnect;
11801
+ var _Channel_connectionManager, _Channel_internalEmitter, _Channel_readyForProvider, _Channel_readyToConnect, _Channel_establishConnection;
11675
11802
  function retryDelay(count) {
11676
11803
  const interval = 500; // base delay
11677
11804
  const steps = 10; // How many retries to do before incrementing the delay
@@ -11721,13 +11848,33 @@ class Channel extends EmitterBase {
11721
11848
  await Promise.all([
11722
11849
  __classPrivateFieldGet$c(this, _Channel_readyForProvider, "f").getValue(),
11723
11850
  this.on('disconnected', (eventPayload) => {
11724
- ChannelClient.handleProviderDisconnect(eventPayload);
11851
+ ChannelClientStore.handleProviderDisconnect(eventPayload);
11725
11852
  }),
11726
11853
  this.on('connected', (...args) => {
11727
11854
  __classPrivateFieldGet$c(this, _Channel_internalEmitter, "f").emit('connected', ...args);
11728
11855
  })
11729
11856
  ]).catch(() => new Error('error setting up channel connection listeners'));
11730
11857
  }));
11858
+ // Runs the client handshake (offer -> connect -> strategy). Re-runnable so a ChannelClient can reconnect
11859
+ // with a fresh endpointId; offers are single-use (esp. RTC) so a new offer is created each call.
11860
+ _Channel_establishConnection.set(this, async (channelName, opts, connectionUrl) => {
11861
+ const { offer, rtc: rtcPacket } = await __classPrivateFieldGet$c(this, _Channel_connectionManager, "f").createClientOffer(opts);
11862
+ // getInfo() can hang during a bfcache restore, so reconnects reuse the URL resolved on the first connect;
11863
+ // it cannot change within a client's lifetime.
11864
+ let targetUrl = connectionUrl;
11865
+ if (targetUrl === undefined && (this.fin.me.isFrame || this.fin.me.isView || this.fin.me.isWindow)) {
11866
+ targetUrl = (await this.fin.me.getInfo()).url;
11867
+ }
11868
+ const connectPayload = {
11869
+ channelName,
11870
+ ...opts,
11871
+ offer,
11872
+ connectionUrl: targetUrl
11873
+ };
11874
+ const routingInfo = await this.safeConnect(channelName, opts.wait, connectPayload);
11875
+ const strategy = await __classPrivateFieldGet$c(this, _Channel_connectionManager, "f").createClientStrategy(rtcPacket, routingInfo);
11876
+ return { routingInfo, strategy, connectionUrl: targetUrl };
11877
+ });
11731
11878
  __classPrivateFieldSet$b(this, _Channel_connectionManager, new ConnectionManager(wire), "f");
11732
11879
  }
11733
11880
  /**
@@ -11899,35 +12046,14 @@ class Channel extends EmitterBase {
11899
12046
  throw new Error('Please provide a channelName string to connect to a channel.');
11900
12047
  }
11901
12048
  const opts = { wait: true, ...this.wire.environment.getDefaultChannelOptions().connect, ...options };
11902
- const { offer, rtc: rtcPacket } = await __classPrivateFieldGet$c(this, _Channel_connectionManager, "f").createClientOffer(opts);
11903
- let connectionUrl;
11904
- if (this.fin.me.isFrame || this.fin.me.isView || this.fin.me.isWindow) {
11905
- connectionUrl = (await this.fin.me.getInfo()).url;
11906
- }
11907
- const connectPayload = {
11908
- channelName,
11909
- ...opts,
11910
- offer,
11911
- connectionUrl
11912
- };
11913
- const routingInfo = await this.safeConnect(channelName, opts.wait, connectPayload);
11914
- const strategy = await __classPrivateFieldGet$c(this, _Channel_connectionManager, "f").createClientStrategy(rtcPacket, routingInfo);
11915
- const channel = new ChannelClient(routingInfo, () => ChannelClient.wireClose(this.wire, routingInfo, routingInfo.endpointId), strategy);
11916
- // It is the client's responsibility to handle endpoint disconnection to the provider.
11917
- // If the endpoint dies, the client will force a disconnection through the core.
11918
- // The provider does not care about endpoint disconnection.
11919
- strategy.onEndpointDisconnect(routingInfo.channelId, async () => {
11920
- try {
11921
- await channel.sendDisconnectAction();
11922
- }
11923
- catch (error) {
11924
- console.warn(`Something went wrong during disconnect for client with uuid: ${routingInfo.uuid} / name: ${routingInfo.name} / endpointId: ${routingInfo.endpointId}.`);
11925
- }
11926
- finally {
11927
- ChannelClient.handleProviderDisconnect(routingInfo);
11928
- }
12049
+ const initialConnection = await __classPrivateFieldGet$c(this, _Channel_establishConnection, "f").call(this, channelName, opts);
12050
+ // Never wait on a self-heal: a provider missing at restore has gone away, which is a disconnection. A provider
12051
+ // created later under the same name is a new instance without this client's provider-side state.
12052
+ return new ChannelClient({
12053
+ initialConnection,
12054
+ wire: this.wire,
12055
+ reestablish: () => __classPrivateFieldGet$c(this, _Channel_establishConnection, "f").call(this, channelName, { ...opts, wait: false }, initialConnection.connectionUrl)
11929
12056
  });
11930
- return channel;
11931
12057
  }
11932
12058
  /**
11933
12059
  * Create a new channel.
@@ -11984,7 +12110,7 @@ class Channel extends EmitterBase {
11984
12110
  return __classPrivateFieldGet$c(this, _Channel_connectionManager, "f").createProvider(options, providerIdentity);
11985
12111
  }
11986
12112
  }
11987
- _Channel_connectionManager = new WeakMap(), _Channel_internalEmitter = new WeakMap(), _Channel_readyForProvider = new WeakMap(), _Channel_readyToConnect = new WeakMap();
12113
+ _Channel_connectionManager = new WeakMap(), _Channel_internalEmitter = new WeakMap(), _Channel_readyForProvider = new WeakMap(), _Channel_readyToConnect = new WeakMap(), _Channel_establishConnection = new WeakMap();
11988
12114
 
11989
12115
  /**
11990
12116
  * Entry point for the OpenFin `InterApplicationBus` API (`fin.InterApplicationBus`).
@@ -12018,11 +12144,27 @@ class InterApplicationBus extends Base {
12018
12144
  subscriberRemoved: 'subscriber-removed'
12019
12145
  };
12020
12146
  this.refCounter = new RefCounter();
12147
+ /**
12148
+ * One entry per subscription the runtime knows about, keyed by the same subscription key the
12149
+ * emitter and the ref counter use. Listeners sharing a key share a single entry.
12150
+ */
12151
+ this.activeSubscriptions = new Map();
12021
12152
  this.Channel = new Channel(wire);
12022
12153
  this.emitter = new events.EventEmitter();
12023
12154
  wire.registerMessageHandler(this.onmessage.bind(this));
12024
12155
  this.on = this.emitter.on.bind(this.emitter);
12025
12156
  this.removeAllListeners = this.emitter.removeAllListeners.bind(this.emitter);
12157
+ wire.environment.onBFCacheRestore(() => {
12158
+ Array.from(this.activeSubscriptions.values()).forEach(async (subscription) => {
12159
+ try {
12160
+ await this.wire.sendAction('subscribe', subscription);
12161
+ }
12162
+ catch (error) {
12163
+ // eslint-disable-next-line no-console
12164
+ console.error('Failed to restore InterApplicationBus subscription after bfcache restore', error);
12165
+ }
12166
+ });
12167
+ });
12026
12168
  }
12027
12169
  /**
12028
12170
  * Publishes a message to all applications running on OpenFin Runtime that
@@ -12074,30 +12216,39 @@ class InterApplicationBus extends Base {
12074
12216
  * Subscribes to messages from the specified application on the specified topic.
12075
12217
  * @param source This object is described in the Identity in the typedef
12076
12218
  * @param topic The topic on which the message is sent
12077
- * @param listener A function that is called when a message has
12078
- * been received. It is passed the message, uuid and name of the sending application.
12219
+ * @param listener A function that is called when a message has been received. It is passed
12220
+ * two arguments: the message, and the identity (`{ uuid, name }`) of the sender.
12079
12221
  * The message can be either a primitive data type (string, number, or boolean) or
12080
12222
  * composite data type (object, array) that is composed of other primitive or composite
12081
12223
  * data types
12082
12224
  *
12225
+ * @remarks This differs from the legacy `fin.desktop.InterApplicationBus.subscribe` API,
12226
+ * which passes `(message, uuid, name)` as separate arguments.
12227
+ *
12083
12228
  * @example
12084
12229
  * ```js
12085
12230
  * // subscribe to a specified application
12086
- * 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));
12231
+ * fin.InterApplicationBus.subscribe(fin.me, 'topic', (message, sender) => {
12232
+ * console.log(`Received from ${sender.uuid}/${sender.name}:`, message);
12233
+ * }).then(() => console.log('Subscribed to the specified application')).catch(err => console.log(err));
12087
12234
  *
12088
12235
  * // subscribe to wildcard
12089
12236
  * fin.InterApplicationBus.subscribe({ uuid: '*' }, 'topic', sub_msg => console.log(sub_msg)).then(() => console.log('Subscribed to *')).catch(err => console.log(err));
12090
12237
  * ```
12091
12238
  */
12092
12239
  subscribe(source, topic, listener) {
12093
- const subKey = this.createSubscriptionKey(source.uuid, source.name || '*', topic);
12240
+ const sourceWindowName = source.name || '*';
12241
+ const subKey = this.createSubscriptionKey(source.uuid, sourceWindowName, topic);
12242
+ const subscription = {
12243
+ sourceUuid: source.uuid,
12244
+ sourceWindowName,
12245
+ topic,
12246
+ destinationWindowName: this.me.name
12247
+ };
12094
12248
  const sendSubscription = async () => {
12095
- await this.wire.sendAction('subscribe', {
12096
- sourceUuid: source.uuid,
12097
- sourceWindowName: source.name || '*',
12098
- topic,
12099
- destinationWindowName: this.me.name
12100
- });
12249
+ // Journaled before the await so a concurrent unsubscribe can find it.
12250
+ this.activeSubscriptions.set(subKey, subscription);
12251
+ await this.wire.sendAction('subscribe', subscription);
12101
12252
  };
12102
12253
  const alreadySubscribed = () => {
12103
12254
  return Promise.resolve();
@@ -12133,6 +12284,9 @@ class InterApplicationBus extends Base {
12133
12284
  const sourceWindowName = source.name || '*';
12134
12285
  const subKey = this.createSubscriptionKey(source.uuid, sourceWindowName, topic);
12135
12286
  const sendUnsubscription = async () => {
12287
+ // Dropped before the await so that a restoration landing while this unsubscribe is
12288
+ // still in flight does not replay the subscription being torn down.
12289
+ this.activeSubscriptions.delete(subKey);
12136
12290
  await this.wire.sendAction('unsubscribe', {
12137
12291
  sourceUuid: source.uuid,
12138
12292
  sourceWindowName,
@@ -12141,6 +12295,9 @@ class InterApplicationBus extends Base {
12141
12295
  });
12142
12296
  };
12143
12297
  const dontSendUnsubscription = () => {
12298
+ // TODO(CORE-2749): never settles - the executor evaluates `r` instead of calling it,
12299
+ // so awaiting a non-final unsubscribe hangs the caller. Should be `Promise.resolve()`,
12300
+ // but that changes long-standing behavior, so it is tracked separately from CORE-2740.
12144
12301
  return new Promise((r) => r).then(() => undefined);
12145
12302
  };
12146
12303
  this.emitter.removeListener(subKey, listener);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfin/remote-adapter",
3
- "version": "47.100.27",
3
+ "version": "47.100.31",
4
4
  "description": "Establish intermachine runtime connections using webRTC.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "private": false,
@@ -16,7 +16,7 @@
16
16
  "dependencies": {
17
17
  "es-toolkit": "^1.39.3",
18
18
  "tslib": "2.8.1",
19
- "@openfin/core": "47.100.27"
19
+ "@openfin/core": "47.100.31"
20
20
  },
21
21
  "scripts": {
22
22
  "prebuild": "rimraf ./out",