@jtl-software/cloud-apps-core 0.0.0-dev.fd5f03e → 0.0.0-dev.fdea84c

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.
@@ -1,7 +1,17 @@
1
1
  import { AppBridgeEventCapability, AppBridgeMethodCapability } from './capabilities';
2
2
  import { AppBridgeInfrastructure, AppBridgeMessagePort } from './core';
3
+ /** The app-scoped panel token the host mints and pushes; the payload of `getAppToken`/`appTokenChanged`. */
4
+ export type AppToken = {
5
+ accessToken: string;
6
+ refreshToken?: string;
7
+ expiresIn: number;
8
+ };
3
9
  export default class AppBridge extends AppBridgeInfrastructure {
4
10
  readonly method: AppBridgeMethodCapability;
5
11
  readonly event: AppBridgeEventCapability;
12
+ private cachedAppToken;
13
+ private readonly disposers;
6
14
  constructor(appBridgeMessagePort: AppBridgeMessagePort);
15
+ /** Removes the panel-token cache subscription and wake listeners. Call when tearing the bridge down. */
16
+ dispose(): void;
7
17
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,7 +1,15 @@
1
1
  import { Disposer, AppBridgeCapability, AppBridgeMessagePort } from '../../core';
2
2
  export default class AppBridgeMethodCapability extends AppBridgeCapability<'method'> {
3
3
  private readonly _exposedMethods;
4
+ private readonly _localResponders;
4
5
  constructor(appBridgeMessagePort: AppBridgeMessagePort);
6
+ /**
7
+ * Answer a `call(methodName, ...)` locally instead of sending it to the other side, when the
8
+ * handler returns a defined value; a returned `undefined` falls through to the remote method.
9
+ * Lets the bridge serve a locally cached result (e.g. a pushed panel token) without a round-trip,
10
+ * transparently to the caller. Returns a disposer that removes the responder.
11
+ */
12
+ respondLocally<TMethod>(methodName: string, handler: TMethod): Disposer;
5
13
  expose<TMethod>(methodName: string, method: TMethod): Disposer;
6
14
  removeExposedMethod(methodName: string): void;
7
15
  isExposed(methodName: string): boolean;
package/dist/main.js CHANGED
@@ -3740,8 +3740,21 @@ class AppBridgeMethodCapability extends AppBridgeCapability {
3740
3740
  constructor(appBridgeMessagePort) {
3741
3741
  super(appBridgeMessagePort);
3742
3742
  __publicField(this, "_exposedMethods", {});
3743
+ __publicField(this, "_localResponders", {});
3743
3744
  this.registerMessageHandler("method:call", this.handleMethodCall);
3744
3745
  }
3746
+ /**
3747
+ * Answer a `call(methodName, ...)` locally instead of sending it to the other side, when the
3748
+ * handler returns a defined value; a returned `undefined` falls through to the remote method.
3749
+ * Lets the bridge serve a locally cached result (e.g. a pushed panel token) without a round-trip,
3750
+ * transparently to the caller. Returns a disposer that removes the responder.
3751
+ */
3752
+ respondLocally(methodName, handler) {
3753
+ this._localResponders[methodName] = handler;
3754
+ return () => {
3755
+ delete this._localResponders[methodName];
3756
+ };
3757
+ }
3745
3758
  expose(methodName, method) {
3746
3759
  this._exposedMethods[methodName] = method;
3747
3760
  return () => {
@@ -3755,6 +3768,16 @@ class AppBridgeMethodCapability extends AppBridgeCapability {
3755
3768
  return !!this._exposedMethods[methodName];
3756
3769
  }
3757
3770
  async call(methodName, ...args) {
3771
+ const localResponder = this._localResponders[methodName];
3772
+ if (localResponder) {
3773
+ let local = localResponder(...args);
3774
+ if (local instanceof Promise) {
3775
+ local = await local;
3776
+ }
3777
+ if (local !== void 0) {
3778
+ return local;
3779
+ }
3780
+ }
3758
3781
  return this.sendMessage("method:call", { methodName, args });
3759
3782
  }
3760
3783
  async handleMethodCall({ body }) {
@@ -3770,13 +3793,49 @@ class AppBridgeMethodCapability extends AppBridgeCapability {
3770
3793
  return result;
3771
3794
  }
3772
3795
  }
3796
+ const REFRESH_BUFFER_MS = 6e4;
3773
3797
  class AppBridge extends AppBridgeInfrastructure {
3774
3798
  constructor(appBridgeMessagePort) {
3775
3799
  super(appBridgeMessagePort);
3776
3800
  __publicField(this, "method");
3777
3801
  __publicField(this, "event");
3802
+ __publicField(this, "cachedAppToken");
3803
+ __publicField(this, "disposers", []);
3778
3804
  this.method = this.registerCapability(AppBridgeMethodCapability);
3779
3805
  this.event = this.registerCapability(AppBridgeEventCapability);
3806
+ this.disposers.push(
3807
+ this.event.subscribe("appTokenChanged", (token) => {
3808
+ this.cachedAppToken = { token, expiresAtMs: Date.now() + token.expiresIn * 1e3 };
3809
+ })
3810
+ );
3811
+ this.disposers.push(
3812
+ this.method.respondLocally("getAppToken", () => {
3813
+ if (this.cachedAppToken && this.cachedAppToken.expiresAtMs - Date.now() > REFRESH_BUFFER_MS) {
3814
+ return this.cachedAppToken.token;
3815
+ }
3816
+ return void 0;
3817
+ })
3818
+ );
3819
+ const onWake = () => {
3820
+ if (!document.hidden) {
3821
+ void this.method.call("getAppToken").catch(() => {
3822
+ });
3823
+ }
3824
+ };
3825
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
3826
+ document.addEventListener("visibilitychange", onWake);
3827
+ window.addEventListener("focus", onWake);
3828
+ window.addEventListener("online", onWake);
3829
+ this.disposers.push(() => {
3830
+ document.removeEventListener("visibilitychange", onWake);
3831
+ window.removeEventListener("focus", onWake);
3832
+ window.removeEventListener("online", onWake);
3833
+ });
3834
+ }
3835
+ }
3836
+ /** Removes the panel-token cache subscription and wake listeners. Call when tearing the bridge down. */
3837
+ dispose() {
3838
+ this.disposers.splice(0).forEach((dispose) => dispose());
3780
3839
  }
3781
3840
  }
3782
3841
  function createAppBridge() {