@elliemae/ssf-host 2.22.0 → 2.23.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.
Files changed (51) hide show
  1. package/dist/cjs/guest.js +15 -5
  2. package/dist/cjs/host.js +114 -88
  3. package/dist/cjs/utils.js +2 -19
  4. package/dist/esm/guest.js +15 -5
  5. package/dist/esm/host.js +114 -88
  6. package/dist/esm/utils.js +2 -19
  7. package/dist/public/analytics-object-v1.js.map +1 -1
  8. package/dist/public/analytics-object-v2.js.map +1 -1
  9. package/dist/public/application-object-v1.js.map +1 -1
  10. package/dist/public/application-object-v2.js.map +1 -1
  11. package/dist/public/autoFill.js.map +1 -1
  12. package/dist/public/index.html +1 -1
  13. package/dist/public/init.js.map +1 -1
  14. package/dist/public/js/emuiSsfHost.6b964dde53916a3845ae.js +3 -0
  15. package/dist/public/js/emuiSsfHost.6b964dde53916a3845ae.js.br +0 -0
  16. package/dist/public/js/emuiSsfHost.6b964dde53916a3845ae.js.gz +0 -0
  17. package/dist/public/js/emuiSsfHost.6b964dde53916a3845ae.js.map +1 -0
  18. package/dist/public/loan-object-v1.js.map +1 -1
  19. package/dist/public/loan-object.js.map +1 -1
  20. package/dist/public/utils.js +1 -1
  21. package/dist/public/utils.js.br +0 -0
  22. package/dist/public/utils.js.gz +0 -0
  23. package/dist/public/utils.js.map +1 -1
  24. package/dist/public/v1-guest-v2-host.html +1 -1
  25. package/dist/public/v2-host-v1-guest.html +1 -1
  26. package/dist/types/lib/host.d.ts +2 -2
  27. package/dist/types/lib/ihost.d.ts +7 -6
  28. package/dist/types/lib/types.d.ts +1 -2
  29. package/dist/types/lib/utils.d.ts +2 -5
  30. package/dist/types/tsconfig.tsbuildinfo +1 -1
  31. package/dist/umd/analytics-object-v1.js.map +1 -1
  32. package/dist/umd/analytics-object-v2.js.map +1 -1
  33. package/dist/umd/application-object-v1.js.map +1 -1
  34. package/dist/umd/application-object-v2.js.map +1 -1
  35. package/dist/umd/autoFill.js.map +1 -1
  36. package/dist/umd/index.js +1 -1
  37. package/dist/umd/index.js.br +0 -0
  38. package/dist/umd/index.js.gz +0 -0
  39. package/dist/umd/index.js.map +1 -1
  40. package/dist/umd/init.js.map +1 -1
  41. package/dist/umd/loan-object-v1.js.map +1 -1
  42. package/dist/umd/loan-object.js.map +1 -1
  43. package/dist/umd/utils.js +1 -1
  44. package/dist/umd/utils.js.br +0 -0
  45. package/dist/umd/utils.js.gz +0 -0
  46. package/dist/umd/utils.js.map +1 -1
  47. package/package.json +22 -23
  48. package/dist/public/js/emuiSsfHost.01546664223ea88ed947.js +0 -3
  49. package/dist/public/js/emuiSsfHost.01546664223ea88ed947.js.br +0 -0
  50. package/dist/public/js/emuiSsfHost.01546664223ea88ed947.js.gz +0 -0
  51. package/dist/public/js/emuiSsfHost.01546664223ea88ed947.js.map +0 -1
package/dist/cjs/guest.js CHANGED
@@ -142,9 +142,22 @@ class Guest {
142
142
  handShake = () => new Promise((resolve) => {
143
143
  let handShakeCount = 0;
144
144
  const handShakeRetries = 5;
145
- const handShakeHandle = setInterval(() => {
145
+ let handShakeHandle;
146
+ const onAck = () => {
147
+ clearInterval(handShakeHandle);
148
+ this.#remoting.unlisten({
149
+ messageType: import_microfe_common.MessageType.HandShakeAck,
150
+ callback: onAck
151
+ });
152
+ resolve(true);
153
+ };
154
+ handShakeHandle = setInterval(() => {
146
155
  if (handShakeCount >= handShakeRetries) {
147
156
  clearInterval(handShakeHandle);
157
+ this.#remoting.unlisten({
158
+ messageType: import_microfe_common.MessageType.HandShakeAck,
159
+ callback: onAck
160
+ });
148
161
  resolve(false);
149
162
  } else {
150
163
  this.#remoting.send({
@@ -158,10 +171,7 @@ class Guest {
158
171
  }, 1e3);
159
172
  this.#remoting.listen({
160
173
  messageType: import_microfe_common.MessageType.HandShakeAck,
161
- callback: () => {
162
- clearInterval(handShakeHandle);
163
- resolve(true);
164
- }
174
+ callback: onAck
165
175
  });
166
176
  });
167
177
  /**
package/dist/cjs/host.js CHANGED
@@ -61,6 +61,14 @@ class SSFHost {
61
61
  * list of guests
62
62
  */
63
63
  #guests = /* @__PURE__ */ new Map();
64
+ /**
65
+ * reverse lookup: window -> guest for O(1) message routing
66
+ */
67
+ #guestsByWindow = /* @__PURE__ */ new Map();
68
+ /**
69
+ * reverse lookup: url -> guest for O(1) popup dedup
70
+ */
71
+ #guestsByUrl = /* @__PURE__ */ new Map();
64
72
  /**
65
73
  * list of callbacks for guest close.
66
74
  */
@@ -107,50 +115,46 @@ class SSFHost {
107
115
  this.#remoting.initialize(window);
108
116
  this.#connect();
109
117
  window.addEventListener("beforeunload", this.#closeAllPopupGuests);
110
- this.#monitorPopupGuests();
111
118
  this.#logger.debug(
112
119
  `host is initialized. hostId: ${this.hostId}, correlationId: ${this.#correlationId}`
113
120
  );
114
121
  }
115
122
  #sendBAEvent = (event, data) => {
116
123
  const baEvent = { event, ...data };
117
- this.#analyticsObj.sendBAEvent(baEvent).catch(() => {
124
+ this.#analyticsObj.sendBAEvent(baEvent).catch((e) => {
125
+ this.#logger.debug(
126
+ `Analytics sendBAEvent failed: ${e.message}`
127
+ );
118
128
  });
119
129
  };
120
130
  #startTiming = (name, options) => {
121
- this.#analyticsObj.startTiming(name, options).catch(() => {
131
+ this.#analyticsObj.startTiming(name, options).catch((e) => {
132
+ this.#logger.debug(
133
+ `Analytics startTiming failed: ${e.message}`
134
+ );
122
135
  });
123
136
  };
124
137
  #endTiming = (start, options) => {
125
- this.#analyticsObj.endTiming(start, options).catch(() => {
138
+ this.#analyticsObj.endTiming(start, options).catch((e) => {
139
+ this.#logger.debug(`Analytics endTiming failed: ${e.message}`);
126
140
  });
127
141
  };
128
142
  #closeAllPopupGuests = () => {
129
- for (const guest of this.#guests.values()) {
130
- if (guest.openMode === import_types.OpenMode.Popup) {
131
- this.unloadGuest(guest.id);
132
- }
133
- }
143
+ const popupIds = Array.from(this.#guests.values()).filter((guest) => guest.openMode === import_types.OpenMode.Popup).map((guest) => guest.id);
144
+ popupIds.forEach((id) => this.unloadGuest(id));
134
145
  };
135
146
  /**
136
147
  * get the reference to the guest application by its window
137
148
  * @param guestWindow reference to the guest window
138
149
  * @returns reference to the guest
139
150
  */
140
- #getGuestForWindow = (guestWindow) => Array.from(this.#guests.values()).find(
141
- (guest) => guest.window === guestWindow
142
- );
151
+ #getGuestForWindow = (guestWindow) => this.#guestsByWindow.get(guestWindow);
143
152
  /**
144
153
  * get the reference to the guest application by its window
145
154
  * @param url url of the guest application
146
155
  * @returns reference to the guest
147
156
  */
148
- #getGuestForUrl = (url) => {
149
- for (const guest of this.#guests.values()) {
150
- if (guest.url === url) return guest;
151
- }
152
- return null;
153
- };
157
+ #getGuestForUrl = (url) => this.#guestsByUrl.get(url) ?? null;
154
158
  /**
155
159
  * check if a object is a scripting object
156
160
  * @param value javascript object
@@ -422,6 +426,9 @@ class SSFHost {
422
426
  token
423
427
  });
424
428
  } catch (error) {
429
+ this.#logger.warn(
430
+ `Error in onGuestEventSubscribe callback for event ${eventId}: ${error.message}`
431
+ );
425
432
  }
426
433
  }, 0);
427
434
  };
@@ -465,6 +472,9 @@ class SSFHost {
465
472
  token
466
473
  });
467
474
  } catch (error) {
475
+ this.#logger.warn(
476
+ `Error in onGuestEventUnsubscribe callback for event ${eventId}: ${error.message}`
477
+ );
468
478
  }
469
479
  }, 0);
470
480
  };
@@ -639,6 +649,8 @@ class SSFHost {
639
649
  });
640
650
  guest.init();
641
651
  this.#guests.set(param.guestId, guest);
652
+ this.#guestsByWindow.set(guest.window, guest);
653
+ this.#guestsByUrl.set(guest.url, guest);
642
654
  return guest;
643
655
  };
644
656
  /**
@@ -647,15 +659,17 @@ class SSFHost {
647
659
  * @returns guest object
648
660
  */
649
661
  #findGuest = (guestIdOrWindowOrEle) => {
650
- let guest = typeof guestIdOrWindowOrEle === "string" ? this.#guests.get(guestIdOrWindowOrEle) : null;
651
- if (!guest) {
652
- guest = Array.from(this.#guests.values()).find(
653
- (value) => value.window === guestIdOrWindowOrEle || value.domElement === guestIdOrWindowOrEle
654
- );
662
+ if (typeof guestIdOrWindowOrEle === "string") {
663
+ return this.#guests.get(guestIdOrWindowOrEle);
655
664
  }
656
- return guest;
665
+ const byWindow = this.#guestsByWindow.get(guestIdOrWindowOrEle);
666
+ if (byWindow) return byWindow;
667
+ return Array.from(this.#guests.values()).find(
668
+ (value) => value.domElement === guestIdOrWindowOrEle
669
+ );
657
670
  };
658
671
  #monitorPopupGuests = () => {
672
+ if (this.#popupGuestMonitor) return;
659
673
  this.#popupGuestMonitor = setInterval(() => {
660
674
  const guestsToRemove = [];
661
675
  this.#guests.forEach((guest) => {
@@ -668,12 +682,22 @@ class SSFHost {
668
682
  this.unloadGuest(id);
669
683
  const callbacks = this.#guestCloseCallbackList.get(id);
670
684
  callbacks?.forEach((callback) => {
671
- callback({ id }).catch(() => {
685
+ Promise.resolve(callback({ id })).catch(() => {
672
686
  });
673
687
  });
674
688
  });
675
689
  }, 1e3);
676
690
  };
691
+ #stopMonitoringPopupGuests = () => {
692
+ if (!this.#popupGuestMonitor) return;
693
+ const hasPopups = Array.from(this.#guests.values()).some(
694
+ (guest) => guest.openMode === import_types.OpenMode.Popup
695
+ );
696
+ if (!hasPopups) {
697
+ clearInterval(this.#popupGuestMonitor);
698
+ this.#popupGuestMonitor = null;
699
+ }
700
+ };
677
701
  #openPopupGuest = (param) => {
678
702
  const {
679
703
  url,
@@ -699,37 +723,29 @@ class SSFHost {
699
723
  });
700
724
  }
701
725
  } else {
702
- const windowFeatures = [
703
- { key: "width", value: width },
704
- { key: "height", value: height },
705
- { key: "top", value: top },
706
- { key: "left", value: left }
707
- ].reduce((acc, cur, curIndex) => {
708
- if (curIndex > 0 && cur.value) acc += ",";
709
- return cur.value ? `${acc}${cur.key}=${cur.value}` : acc;
710
- }, "");
726
+ const windowFeatures = Object.entries({ width, height, top, left }).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join(",");
711
727
  const guestWindow = window.open(url, title, `popup, ${windowFeatures}`);
712
728
  if (!guestWindow) {
713
- try {
714
- setTimeout(() => {
729
+ setTimeout(() => {
730
+ try {
715
731
  onError?.(guestId);
716
- }, 0);
717
- } catch (error) {
718
- this.#logger.debug(
719
- `Error occurred in onError for guest with id '${guestId}': ${error.message}`
720
- );
721
- }
732
+ } catch (error) {
733
+ this.#logger.debug(
734
+ `Error occurred in onError for guest with id '${guestId}': ${error.message}`
735
+ );
736
+ }
737
+ }, 0);
722
738
  throw new Error("Failed to open guest application in popup window");
723
739
  } else {
724
- try {
725
- setTimeout(() => {
740
+ setTimeout(() => {
741
+ try {
726
742
  onLoad?.(guestId);
727
- }, 0);
728
- } catch (error) {
729
- this.#logger.debug(
730
- `Error occurred in onLoad for guest with id '${guestId}': ${error.message}`
731
- );
732
- }
743
+ } catch (error) {
744
+ this.#logger.debug(
745
+ `Error occurred in onLoad for guest with id '${guestId}': ${error.message}`
746
+ );
747
+ }
748
+ }, 0);
733
749
  }
734
750
  guestWindow.opener = null;
735
751
  guest = this.#attachGuest({
@@ -740,6 +756,7 @@ class SSFHost {
740
756
  searchParams,
741
757
  openMode: import_types.OpenMode.Popup
742
758
  });
759
+ this.#monitorPopupGuests();
743
760
  }
744
761
  return guest;
745
762
  };
@@ -926,7 +943,10 @@ class SSFHost {
926
943
  * dispose the resources used by the host application
927
944
  */
928
945
  close = () => {
929
- clearInterval(this.#popupGuestMonitor);
946
+ if (this.#popupGuestMonitor) {
947
+ clearInterval(this.#popupGuestMonitor);
948
+ this.#popupGuestMonitor = null;
949
+ }
930
950
  this.#closeAllPopupGuests();
931
951
  this.#remoting.close();
932
952
  window.removeEventListener("beforeunload", this.#closeAllPopupGuests);
@@ -979,39 +999,42 @@ class SSFHost {
979
999
  }
980
1000
  const guestPromises = [];
981
1001
  let timingMetricStarted = false;
982
- this.#guests.forEach((guest) => {
1002
+ const dispatchToGuest = (guest) => {
983
1003
  const guestInfo = guest.getInfo();
984
- if (!targetWindow || targetWindow === guest.window) {
985
- if (timeout && guest?.capabilities?.eventFeedback) {
986
- guestPromises.push(guest.dispatchEvent(eventObj, timeout));
987
- if (!timingMetricStarted) {
988
- this.#startTiming(
989
- `ScriptingObject.Event.${scriptingObject.id}.${name}`,
990
- {
991
- appId: this.hostId,
992
- appUrl: window.location.href
993
- }
994
- );
995
- timingMetricStarted = true;
996
- }
997
- this.#logger.audit({
998
- message: "Event dispatched and awaiting feedback",
999
- scriptingEventId: id,
1000
- ...guestInfo
1001
- });
1002
- } else {
1003
- guest.send({
1004
- messageType: import_microfe_common.MessageType.ObjectEvent,
1005
- messageBody: eventObj
1006
- });
1007
- this.#logger.audit({
1008
- message: "Event dispatched",
1009
- scriptingEventId: id,
1010
- ...guestInfo
1011
- });
1004
+ if (timeout && guest?.capabilities?.eventFeedback) {
1005
+ guestPromises.push(guest.dispatchEvent(eventObj, timeout));
1006
+ if (!timingMetricStarted) {
1007
+ this.#startTiming(
1008
+ `ScriptingObject.Event.${scriptingObject.id}.${name}`,
1009
+ {
1010
+ appId: this.hostId,
1011
+ appUrl: window.location.href
1012
+ }
1013
+ );
1014
+ timingMetricStarted = true;
1012
1015
  }
1016
+ this.#logger.audit({
1017
+ message: "Event dispatched and awaiting feedback",
1018
+ scriptingEventId: id,
1019
+ ...guestInfo
1020
+ });
1021
+ } else {
1022
+ guest.send({
1023
+ messageType: import_microfe_common.MessageType.ObjectEvent,
1024
+ messageBody: eventObj
1025
+ });
1026
+ this.#logger.audit({
1027
+ message: "Event dispatched",
1028
+ scriptingEventId: id,
1029
+ ...guestInfo
1030
+ });
1013
1031
  }
1014
- });
1032
+ };
1033
+ if (targetGuest) {
1034
+ dispatchToGuest(targetGuest);
1035
+ } else {
1036
+ this.#guests.forEach(dispatchToGuest);
1037
+ }
1015
1038
  const retValue = await Promise.all(guestPromises).then((values) => {
1016
1039
  this.#logger.audit({
1017
1040
  message: "Event feedback received",
@@ -1041,13 +1064,7 @@ class SSFHost {
1041
1064
  * get reference to all guest applications
1042
1065
  * @returns list of guest application references
1043
1066
  */
1044
- getGuests = () => {
1045
- const guestList = [];
1046
- this.#guests.forEach((guest) => {
1047
- guestList.push(guest);
1048
- });
1049
- return guestList;
1050
- };
1067
+ getGuests = () => Array.from(this.#guests.values());
1051
1068
  /**
1052
1069
  * get the scripting object by id
1053
1070
  * @param objectId - id of the scripting object
@@ -1072,6 +1089,12 @@ class SSFHost {
1072
1089
  options = {}
1073
1090
  } = param;
1074
1091
  if (!guestId) throw new Error("id for guest application is required");
1092
+ if (this.#guests.has(guestId)) {
1093
+ this.#logger.warn(
1094
+ `Guest with id '${guestId}' is already loaded. Unloading existing guest first.`
1095
+ );
1096
+ this.unloadGuest(guestId);
1097
+ }
1075
1098
  const { openMode = import_types.OpenMode.Embed, popupWindowFeatures = {} } = options;
1076
1099
  const srcUrl = this.#getGuestUrl(url, searchParams);
1077
1100
  let guest = null;
@@ -1166,11 +1189,14 @@ class SSFHost {
1166
1189
  const guest = this.#findGuest(guestIdOrWindowOrEle);
1167
1190
  if (guest) {
1168
1191
  guest.dispose();
1192
+ this.#guestsByWindow.delete(guest.window);
1193
+ this.#guestsByUrl.delete(guest.url);
1169
1194
  this.#guests.delete(guest.id);
1170
1195
  this.#logger.audit({
1171
1196
  message: `Guest is removed from host`,
1172
1197
  ...guest.getInfo()
1173
1198
  });
1199
+ this.#stopMonitoringPopupGuests();
1174
1200
  }
1175
1201
  };
1176
1202
  /**
package/dist/cjs/utils.js CHANGED
@@ -19,10 +19,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var utils_exports = {};
20
20
  __export(utils_exports, {
21
21
  flatten: () => flatten,
22
- getObjectId: () => getObjectId,
23
22
  getOrigin: () => getOrigin,
24
- isFunction: () => isFunction,
25
- isScriptingObject: () => isScriptingObject
23
+ isFunction: () => isFunction
26
24
  });
27
25
  module.exports = __toCommonJS(utils_exports);
28
26
  const getOrigin = (url) => {
@@ -35,22 +33,7 @@ const getOrigin = (url) => {
35
33
  return origin;
36
34
  }
37
35
  };
38
- const flatten = (source, target = []) => {
39
- const retVal = target || [];
40
- if (source && source.forEach) {
41
- source.forEach((item) => {
42
- flatten(item, retVal);
43
- });
44
- } else if (typeof source !== "undefined") {
45
- retVal.push(source);
46
- }
47
- return retVal;
48
- };
49
- const isScriptingObject = (value) => (
50
- // eslint-disable-next-line no-underscore-dangle, @typescript-eslint/no-unsafe-member-access
51
- typeof value?._toJSON === "function"
52
- );
36
+ const flatten = (source) => source.flat(Infinity).filter((v) => v !== void 0);
53
37
  function isFunction(value) {
54
38
  return typeof value === "function";
55
39
  }
56
- const getObjectId = (elementOrId) => elementOrId?.id ?? elementOrId;
package/dist/esm/guest.js CHANGED
@@ -119,9 +119,22 @@ class Guest {
119
119
  handShake = () => new Promise((resolve) => {
120
120
  let handShakeCount = 0;
121
121
  const handShakeRetries = 5;
122
- const handShakeHandle = setInterval(() => {
122
+ let handShakeHandle;
123
+ const onAck = () => {
124
+ clearInterval(handShakeHandle);
125
+ this.#remoting.unlisten({
126
+ messageType: MessageType.HandShakeAck,
127
+ callback: onAck
128
+ });
129
+ resolve(true);
130
+ };
131
+ handShakeHandle = setInterval(() => {
123
132
  if (handShakeCount >= handShakeRetries) {
124
133
  clearInterval(handShakeHandle);
134
+ this.#remoting.unlisten({
135
+ messageType: MessageType.HandShakeAck,
136
+ callback: onAck
137
+ });
125
138
  resolve(false);
126
139
  } else {
127
140
  this.#remoting.send({
@@ -135,10 +148,7 @@ class Guest {
135
148
  }, 1e3);
136
149
  this.#remoting.listen({
137
150
  messageType: MessageType.HandShakeAck,
138
- callback: () => {
139
- clearInterval(handShakeHandle);
140
- resolve(true);
141
- }
151
+ callback: onAck
142
152
  });
143
153
  });
144
154
  /**