@elliemae/ssf-host 2.24.0 → 2.25.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 (40) hide show
  1. package/dist/cjs/guest.js +15 -16
  2. package/dist/cjs/host.js +37 -55
  3. package/dist/cjs/performanceTracker.js +111 -0
  4. package/dist/esm/guest.js +15 -16
  5. package/dist/esm/host.js +37 -55
  6. package/dist/esm/performanceTracker.js +91 -0
  7. package/dist/public/callchain-host.html +1 -1
  8. package/dist/public/callchain-intermediate.html +1 -1
  9. package/dist/public/e2e-host.html +1 -1
  10. package/dist/public/e2e-index.html +1 -1
  11. package/dist/public/index.html +1 -1
  12. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js +3 -0
  13. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.br +0 -0
  14. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.gz +0 -0
  15. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.map +1 -0
  16. package/dist/public/popup-focus-host.html +1 -1
  17. package/dist/public/utils.js +1 -1
  18. package/dist/public/utils.js.br +0 -0
  19. package/dist/public/utils.js.gz +0 -0
  20. package/dist/public/utils.js.map +1 -1
  21. package/dist/public/v1-guest-v2-host.html +1 -1
  22. package/dist/public/v2-host-v1-guest.html +1 -1
  23. package/dist/types/lib/guest.d.ts +4 -3
  24. package/dist/types/lib/host.d.ts +0 -1
  25. package/dist/types/lib/ihost.d.ts +18 -1
  26. package/dist/types/lib/performanceTracker.d.ts +46 -0
  27. package/dist/types/tsconfig.tsbuildinfo +1 -1
  28. package/dist/umd/index.js +1 -1
  29. package/dist/umd/index.js.br +0 -0
  30. package/dist/umd/index.js.gz +0 -0
  31. package/dist/umd/index.js.map +1 -1
  32. package/dist/umd/utils.js +1 -1
  33. package/dist/umd/utils.js.br +0 -0
  34. package/dist/umd/utils.js.gz +0 -0
  35. package/dist/umd/utils.js.map +1 -1
  36. package/package.json +4 -4
  37. package/dist/public/js/emuiSsfHost.071827d0d7e775690fbb.js +0 -3
  38. package/dist/public/js/emuiSsfHost.071827d0d7e775690fbb.js.br +0 -0
  39. package/dist/public/js/emuiSsfHost.071827d0d7e775690fbb.js.gz +0 -0
  40. package/dist/public/js/emuiSsfHost.071827d0d7e775690fbb.js.map +0 -1
package/dist/cjs/guest.js CHANGED
@@ -74,9 +74,9 @@ class Guest {
74
74
  */
75
75
  #remoting;
76
76
  /**
77
- * analytics object
77
+ * performance tracker for timing measurements
78
78
  */
79
- #analyticsObj;
79
+ #perfTracker;
80
80
  /**
81
81
  * Create object representing guest application
82
82
  * @param {GuestOption} option - options for creating a guest application
@@ -91,7 +91,7 @@ class Guest {
91
91
  searchParams = {},
92
92
  openMode = import_types.OpenMode.Embed,
93
93
  remoting,
94
- analyticsObj
94
+ perfTracker
95
95
  } = option;
96
96
  this.id = guestId;
97
97
  this.title = title;
@@ -102,7 +102,7 @@ class Guest {
102
102
  this.window = window;
103
103
  this.openMode = openMode;
104
104
  this.capabilities = {};
105
- this.#analyticsObj = analyticsObj;
105
+ this.#perfTracker = perfTracker;
106
106
  this.#remoting = remoting;
107
107
  }
108
108
  /**
@@ -188,16 +188,17 @@ class Guest {
188
188
  * invokes event callback on the guest application
189
189
  * @param {EventObject} event - event object
190
190
  * @param {number} timeout - timeout in milliseconds
191
+ * @returns {Promise} resolves when the guest handles the event
191
192
  */
192
193
  dispatchEvent = (event, timeout) => {
193
- this.#analyticsObj.startTiming(
194
- `ScriptingObject.Event.${event.object.objectId}.${event.eventName}`,
195
- {
194
+ let timingToken;
195
+ if (this.#perfTracker.enabled) {
196
+ const name = `ScriptingObject.Event.${event.object.objectId}.${event.eventName}`;
197
+ timingToken = this.#perfTracker.start(name, {
196
198
  appId: this.id,
197
199
  appUrl: this.url
198
- }
199
- ).catch(() => {
200
- });
200
+ });
201
+ }
201
202
  return this.#remoting.invoke({
202
203
  targetWin: this.window,
203
204
  targetOrigin: this.origin,
@@ -205,14 +206,12 @@ class Guest {
205
206
  messageBody: event,
206
207
  responseTimeoutMs: timeout
207
208
  }).finally(() => {
208
- this.#analyticsObj.endTiming(
209
- `ScriptingObject.Event.${event.object.objectId}.${event.eventName}`,
210
- {
209
+ if (timingToken) {
210
+ this.#perfTracker.end(timingToken, {
211
211
  appId: this.id,
212
212
  appUrl: this.url
213
- }
214
- ).catch(() => {
215
- });
213
+ });
214
+ }
216
215
  });
217
216
  };
218
217
  /**
package/dist/cjs/host.js CHANGED
@@ -27,6 +27,7 @@ var import_microfe_common = require("@elliemae/microfe-common");
27
27
  var import_types = require("./types.js");
28
28
  var import_guest = require("./guest.js");
29
29
  var import_utils = require("./utils.js");
30
+ var import_performanceTracker = require("./performanceTracker.js");
30
31
  const SANDBOX_DEFAULT = [
31
32
  import_types.IFrameSandboxValues.AllowScripts,
32
33
  import_types.IFrameSandboxValues.AllowPopups,
@@ -98,16 +99,13 @@ class SSFHost {
98
99
  */
99
100
  #guestMetadata = /* @__PURE__ */ new Map();
100
101
  /**
101
- * Tracks the last time a timing measurement was recorded for each metric name.
102
- * Used to skip redundant startTiming/endTiming cross-window calls for
103
- * the same API or event within the dedup window.
102
+ * Performance tracker for host-side timing (with dedup).
104
103
  */
105
- #timingLastRecorded = /* @__PURE__ */ new Map();
104
+ #perfTracker;
106
105
  /**
107
- * Minimum interval (ms) between timing measurements for the same metric name.
106
+ * Performance tracker shared by all guests (no dedup).
108
107
  */
109
- #timingDedupWindowMs;
110
- static DEFAULT_TIMING_DEDUP_WINDOW_MS = 1e4;
108
+ #guestPerfTracker;
111
109
  /**
112
110
  * Create a new host
113
111
  * @param hostId unique identifier for the host
@@ -119,7 +117,20 @@ class SSFHost {
119
117
  if (!option?.analyticsObj) throw new Error("Analytics object is required");
120
118
  this.#logger = option.logger;
121
119
  this.#analyticsObj = option.analyticsObj;
122
- this.#timingDedupWindowMs = option?.timingDedupWindowMs ?? SSFHost.DEFAULT_TIMING_DEDUP_WINDOW_MS;
120
+ const perfOpts = {
121
+ analyticsObj: option.analyticsObj,
122
+ enabled: option?.measurePerformance,
123
+ samplingRatio: option?.performanceSamplingRatio,
124
+ onError: (msg) => this.#logger.debug(msg)
125
+ };
126
+ this.#perfTracker = new import_performanceTracker.PerformanceTracker({
127
+ ...perfOpts,
128
+ dedupWindowMs: option?.performanceDedupWindowMs
129
+ });
130
+ this.#guestPerfTracker = new import_performanceTracker.PerformanceTracker({
131
+ ...perfOpts,
132
+ dedupWindowMs: 0
133
+ });
123
134
  this.#correlationId = (0, import_uuid.v4)();
124
135
  this.#remoting = new import_microfe_common.Remoting(this.#logger, this.#correlationId);
125
136
  if (option?.readyStateCallback && typeof option?.readyStateCallback !== "function")
@@ -143,34 +154,6 @@ class SSFHost {
143
154
  );
144
155
  });
145
156
  };
146
- #startTiming = (name, options) => {
147
- this.#analyticsObj.startTiming(name, options).catch((e) => {
148
- this.#logger.debug(
149
- `Analytics startTiming failed: ${e.message}`
150
- );
151
- });
152
- };
153
- #endTiming = (start, options) => {
154
- this.#analyticsObj.endTiming(start, options).catch((e) => {
155
- this.#logger.debug(`Analytics endTiming failed: ${e.message}`);
156
- });
157
- };
158
- /**
159
- * Returns true if a timing measurement should be recorded for the given
160
- * metric name. Skips the measurement when one was already recorded for the
161
- * same name within the configured dedup window ({@link HostOption.timingDedupWindowMs}).
162
- * Always returns true when dedup is disabled (window = 0).
163
- * @param name
164
- */
165
- #shouldTrackTiming = (name) => {
166
- if (this.#timingDedupWindowMs <= 0) return true;
167
- const now = performance.now();
168
- const last = this.#timingLastRecorded.get(name);
169
- if (last !== void 0 && now - last < this.#timingDedupWindowMs)
170
- return false;
171
- this.#timingLastRecorded.set(name, now);
172
- return true;
173
- };
174
157
  #closeAllPopupGuests = () => {
175
158
  const popupIds = Array.from(this.#guests.values()).filter((guest) => guest.openMode === import_types.OpenMode.Popup).map((guest) => guest.id);
176
159
  popupIds.forEach((id) => this.unloadGuest(id));
@@ -293,7 +276,7 @@ class SSFHost {
293
276
  if (!guest.ready) {
294
277
  guest.ready = true;
295
278
  const guestInfo = guest.getInfo();
296
- this.#endTiming("SSF.Guest.Load", {
279
+ this.#perfTracker.end("SSF.Guest.Load", {
297
280
  appId: guestInfo.guestId,
298
281
  appUrl: guestInfo.guestUrl
299
282
  });
@@ -566,10 +549,10 @@ class SSFHost {
566
549
  return false;
567
550
  }
568
551
  const guestInfo = guest.getInfo();
569
- const timingName = `ScriptingObject.API.${objectId}.${body.functionName}`;
570
- const trackTiming = this.#shouldTrackTiming(timingName);
571
- if (trackTiming) {
572
- this.#startTiming(timingName, {
552
+ let timingToken;
553
+ if (this.#perfTracker.enabled) {
554
+ const name = `ScriptingObject.API.${objectId}.${body.functionName}`;
555
+ timingToken = this.#perfTracker.start(name, {
573
556
  appId: guestInfo.guestId,
574
557
  appUrl: guestInfo.guestUrl
575
558
  });
@@ -609,8 +592,8 @@ class SSFHost {
609
592
  ...guestInfo
610
593
  });
611
594
  }).finally(() => {
612
- if (trackTiming) {
613
- this.#endTiming(timingName, {
595
+ if (timingToken) {
596
+ this.#perfTracker.end(timingToken, {
614
597
  appId: guestInfo.guestId,
615
598
  appUrl: guestInfo.guestUrl
616
599
  });
@@ -685,7 +668,7 @@ class SSFHost {
685
668
  const guest = new import_guest.Guest({
686
669
  ...param,
687
670
  remoting: this.#remoting,
688
- analyticsObj: this.#analyticsObj
671
+ perfTracker: this.#guestPerfTracker
689
672
  });
690
673
  guest.init();
691
674
  this.#guests.set(param.guestId, guest);
@@ -1058,18 +1041,16 @@ class SSFHost {
1058
1041
  };
1059
1042
  }
1060
1043
  const guestPromises = [];
1061
- const eventTimingName = `ScriptingObject.Event.${scriptingObject.id}.${name}`;
1062
- let timingMetricStarted = false;
1044
+ let eventTimingToken;
1063
1045
  const dispatchToGuest = (guest) => {
1064
1046
  const guestInfo = guest.getInfo();
1065
1047
  if (timeout && guest?.capabilities?.eventFeedback) {
1066
1048
  guestPromises.push(guest.dispatchEvent(eventObj, timeout));
1067
- if (!timingMetricStarted && this.#shouldTrackTiming(eventTimingName)) {
1068
- this.#startTiming(eventTimingName, {
1069
- appId: this.hostId,
1070
- appUrl: window.location.href
1071
- });
1072
- timingMetricStarted = true;
1049
+ if (!eventTimingToken && this.#perfTracker.enabled) {
1050
+ eventTimingToken = this.#perfTracker.start(
1051
+ `ScriptingObject.Event.${scriptingObject.id}.${name}`,
1052
+ { appId: this.hostId, appUrl: window.location.href }
1053
+ );
1073
1054
  }
1074
1055
  this.#logger.debug({
1075
1056
  message: "Event dispatched and awaiting feedback",
@@ -1107,11 +1088,12 @@ class SSFHost {
1107
1088
  });
1108
1089
  throw ex;
1109
1090
  }).finally(() => {
1110
- if (timingMetricStarted)
1111
- this.#endTiming(eventTimingName, {
1091
+ if (eventTimingToken) {
1092
+ this.#perfTracker.end(eventTimingToken, {
1112
1093
  appId: this.hostId,
1113
1094
  appUrl: window.location.href
1114
1095
  });
1096
+ }
1115
1097
  });
1116
1098
  };
1117
1099
  /**
@@ -1155,7 +1137,7 @@ class SSFHost {
1155
1137
  const { openMode = import_types.OpenMode.Embed, popupWindowFeatures = {} } = options;
1156
1138
  const srcUrl = this.#getGuestUrl(url, searchParams);
1157
1139
  let guest = null;
1158
- this.#startTiming("SSF.Guest.Load", {
1140
+ this.#perfTracker.start("SSF.Guest.Load", {
1159
1141
  appId: instanceId,
1160
1142
  appUrl: srcUrl
1161
1143
  });
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var performanceTracker_exports = {};
20
+ __export(performanceTracker_exports, {
21
+ PerformanceTracker: () => PerformanceTracker
22
+ });
23
+ module.exports = __toCommonJS(performanceTracker_exports);
24
+ class PerformanceTracker {
25
+ #analyticsObj;
26
+ #enabled;
27
+ #samplingRatio;
28
+ #samplingConfigured = /* @__PURE__ */ new Set();
29
+ #dedupWindowMs;
30
+ #lastRecorded = /* @__PURE__ */ new Map();
31
+ #onError;
32
+ static DEFAULT_SAMPLING_RATIO = 0.05;
33
+ static DEFAULT_DEDUP_WINDOW_MS = 1e4;
34
+ static #LOW_FREQUENCY = /* @__PURE__ */ new Set(["SSF.Guest.Load"]);
35
+ constructor(options) {
36
+ this.#analyticsObj = options.analyticsObj;
37
+ this.#enabled = options.enabled ?? false;
38
+ this.#samplingRatio = options.samplingRatio ?? PerformanceTracker.DEFAULT_SAMPLING_RATIO;
39
+ this.#dedupWindowMs = options.dedupWindowMs ?? PerformanceTracker.DEFAULT_DEDUP_WINDOW_MS;
40
+ this.#onError = options.onError;
41
+ }
42
+ /**
43
+ * Whether performance tracking is active. Callers can use this to
44
+ * skip expensive work (e.g. building timing name strings) when
45
+ * tracking is disabled.
46
+ * @returns {boolean} true when performance tracking is enabled
47
+ */
48
+ get enabled() {
49
+ return this.#enabled;
50
+ }
51
+ /**
52
+ * Begin a timing measurement. Returns the timing name as a token when
53
+ * timing was started, or `undefined` when timing is disabled, deduped,
54
+ * or skipped. Pass the returned token to {@link end}.
55
+ * Never throws — failures are reported via the `onError` callback.
56
+ * @param {string} name - timing event name
57
+ * @param {TimingOptions} options - context passed to the analytics object
58
+ * @returns {string | undefined} the timing token, or undefined if skipped
59
+ */
60
+ start(name, options) {
61
+ if (!this.#enabled) return void 0;
62
+ try {
63
+ const lowFreq = PerformanceTracker.#LOW_FREQUENCY.has(name);
64
+ if (!lowFreq && !this.#shouldTrack(name)) return void 0;
65
+ if (!lowFreq && !this.#samplingConfigured.has(name)) {
66
+ this.#analyticsObj.setTimingEventSamplingRatio({
67
+ [name]: this.#samplingRatio
68
+ });
69
+ this.#samplingConfigured.add(name);
70
+ }
71
+ this.#analyticsObj.startTiming(name, options).catch((e) => {
72
+ this.#reportError(`startTiming failed: ${e.message}`);
73
+ });
74
+ return name;
75
+ } catch (e) {
76
+ this.#reportError(`startTiming failed: ${e.message}`);
77
+ return void 0;
78
+ }
79
+ }
80
+ /**
81
+ * End a timing measurement previously started via {@link start}.
82
+ * No-op when token is `undefined` (timing was not started).
83
+ * Never throws — failures are reported via the `onError` callback.
84
+ * @param {string | undefined} token - token returned by {@link start}
85
+ * @param {TimingOptions} options - context passed to the analytics object
86
+ */
87
+ end(token, options) {
88
+ if (!token) return;
89
+ try {
90
+ this.#analyticsObj.endTiming(token, options).catch((e) => {
91
+ this.#reportError(`endTiming failed: ${e.message}`);
92
+ });
93
+ } catch (e) {
94
+ this.#reportError(`endTiming failed: ${e.message}`);
95
+ }
96
+ }
97
+ #reportError(message) {
98
+ try {
99
+ this.#onError?.(message);
100
+ } catch {
101
+ }
102
+ }
103
+ #shouldTrack(name) {
104
+ if (this.#dedupWindowMs <= 0) return true;
105
+ const now = performance.now();
106
+ const last = this.#lastRecorded.get(name);
107
+ if (last !== void 0 && now - last < this.#dedupWindowMs) return false;
108
+ this.#lastRecorded.set(name, now);
109
+ return true;
110
+ }
111
+ }
package/dist/esm/guest.js CHANGED
@@ -51,9 +51,9 @@ class Guest {
51
51
  */
52
52
  #remoting;
53
53
  /**
54
- * analytics object
54
+ * performance tracker for timing measurements
55
55
  */
56
- #analyticsObj;
56
+ #perfTracker;
57
57
  /**
58
58
  * Create object representing guest application
59
59
  * @param {GuestOption} option - options for creating a guest application
@@ -68,7 +68,7 @@ class Guest {
68
68
  searchParams = {},
69
69
  openMode = OpenMode.Embed,
70
70
  remoting,
71
- analyticsObj
71
+ perfTracker
72
72
  } = option;
73
73
  this.id = guestId;
74
74
  this.title = title;
@@ -79,7 +79,7 @@ class Guest {
79
79
  this.window = window;
80
80
  this.openMode = openMode;
81
81
  this.capabilities = {};
82
- this.#analyticsObj = analyticsObj;
82
+ this.#perfTracker = perfTracker;
83
83
  this.#remoting = remoting;
84
84
  }
85
85
  /**
@@ -165,16 +165,17 @@ class Guest {
165
165
  * invokes event callback on the guest application
166
166
  * @param {EventObject} event - event object
167
167
  * @param {number} timeout - timeout in milliseconds
168
+ * @returns {Promise} resolves when the guest handles the event
168
169
  */
169
170
  dispatchEvent = (event, timeout) => {
170
- this.#analyticsObj.startTiming(
171
- `ScriptingObject.Event.${event.object.objectId}.${event.eventName}`,
172
- {
171
+ let timingToken;
172
+ if (this.#perfTracker.enabled) {
173
+ const name = `ScriptingObject.Event.${event.object.objectId}.${event.eventName}`;
174
+ timingToken = this.#perfTracker.start(name, {
173
175
  appId: this.id,
174
176
  appUrl: this.url
175
- }
176
- ).catch(() => {
177
- });
177
+ });
178
+ }
178
179
  return this.#remoting.invoke({
179
180
  targetWin: this.window,
180
181
  targetOrigin: this.origin,
@@ -182,14 +183,12 @@ class Guest {
182
183
  messageBody: event,
183
184
  responseTimeoutMs: timeout
184
185
  }).finally(() => {
185
- this.#analyticsObj.endTiming(
186
- `ScriptingObject.Event.${event.object.objectId}.${event.eventName}`,
187
- {
186
+ if (timingToken) {
187
+ this.#perfTracker.end(timingToken, {
188
188
  appId: this.id,
189
189
  appUrl: this.url
190
- }
191
- ).catch(() => {
192
- });
190
+ });
191
+ }
193
192
  });
194
193
  };
195
194
  /**
package/dist/esm/host.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  } from "./types.js";
15
15
  import { Guest } from "./guest.js";
16
16
  import { flatten, isFunction, isTrustedDomain } from "./utils.js";
17
+ import { PerformanceTracker } from "./performanceTracker.js";
17
18
  const SANDBOX_DEFAULT = [
18
19
  IFrameSandboxValues.AllowScripts,
19
20
  IFrameSandboxValues.AllowPopups,
@@ -85,16 +86,13 @@ class SSFHost {
85
86
  */
86
87
  #guestMetadata = /* @__PURE__ */ new Map();
87
88
  /**
88
- * Tracks the last time a timing measurement was recorded for each metric name.
89
- * Used to skip redundant startTiming/endTiming cross-window calls for
90
- * the same API or event within the dedup window.
89
+ * Performance tracker for host-side timing (with dedup).
91
90
  */
92
- #timingLastRecorded = /* @__PURE__ */ new Map();
91
+ #perfTracker;
93
92
  /**
94
- * Minimum interval (ms) between timing measurements for the same metric name.
93
+ * Performance tracker shared by all guests (no dedup).
95
94
  */
96
- #timingDedupWindowMs;
97
- static DEFAULT_TIMING_DEDUP_WINDOW_MS = 1e4;
95
+ #guestPerfTracker;
98
96
  /**
99
97
  * Create a new host
100
98
  * @param hostId unique identifier for the host
@@ -106,7 +104,20 @@ class SSFHost {
106
104
  if (!option?.analyticsObj) throw new Error("Analytics object is required");
107
105
  this.#logger = option.logger;
108
106
  this.#analyticsObj = option.analyticsObj;
109
- this.#timingDedupWindowMs = option?.timingDedupWindowMs ?? SSFHost.DEFAULT_TIMING_DEDUP_WINDOW_MS;
107
+ const perfOpts = {
108
+ analyticsObj: option.analyticsObj,
109
+ enabled: option?.measurePerformance,
110
+ samplingRatio: option?.performanceSamplingRatio,
111
+ onError: (msg) => this.#logger.debug(msg)
112
+ };
113
+ this.#perfTracker = new PerformanceTracker({
114
+ ...perfOpts,
115
+ dedupWindowMs: option?.performanceDedupWindowMs
116
+ });
117
+ this.#guestPerfTracker = new PerformanceTracker({
118
+ ...perfOpts,
119
+ dedupWindowMs: 0
120
+ });
110
121
  this.#correlationId = uuidv4();
111
122
  this.#remoting = new Remoting(this.#logger, this.#correlationId);
112
123
  if (option?.readyStateCallback && typeof option?.readyStateCallback !== "function")
@@ -130,34 +141,6 @@ class SSFHost {
130
141
  );
131
142
  });
132
143
  };
133
- #startTiming = (name, options) => {
134
- this.#analyticsObj.startTiming(name, options).catch((e) => {
135
- this.#logger.debug(
136
- `Analytics startTiming failed: ${e.message}`
137
- );
138
- });
139
- };
140
- #endTiming = (start, options) => {
141
- this.#analyticsObj.endTiming(start, options).catch((e) => {
142
- this.#logger.debug(`Analytics endTiming failed: ${e.message}`);
143
- });
144
- };
145
- /**
146
- * Returns true if a timing measurement should be recorded for the given
147
- * metric name. Skips the measurement when one was already recorded for the
148
- * same name within the configured dedup window ({@link HostOption.timingDedupWindowMs}).
149
- * Always returns true when dedup is disabled (window = 0).
150
- * @param name
151
- */
152
- #shouldTrackTiming = (name) => {
153
- if (this.#timingDedupWindowMs <= 0) return true;
154
- const now = performance.now();
155
- const last = this.#timingLastRecorded.get(name);
156
- if (last !== void 0 && now - last < this.#timingDedupWindowMs)
157
- return false;
158
- this.#timingLastRecorded.set(name, now);
159
- return true;
160
- };
161
144
  #closeAllPopupGuests = () => {
162
145
  const popupIds = Array.from(this.#guests.values()).filter((guest) => guest.openMode === OpenMode.Popup).map((guest) => guest.id);
163
146
  popupIds.forEach((id) => this.unloadGuest(id));
@@ -280,7 +263,7 @@ class SSFHost {
280
263
  if (!guest.ready) {
281
264
  guest.ready = true;
282
265
  const guestInfo = guest.getInfo();
283
- this.#endTiming("SSF.Guest.Load", {
266
+ this.#perfTracker.end("SSF.Guest.Load", {
284
267
  appId: guestInfo.guestId,
285
268
  appUrl: guestInfo.guestUrl
286
269
  });
@@ -553,10 +536,10 @@ class SSFHost {
553
536
  return false;
554
537
  }
555
538
  const guestInfo = guest.getInfo();
556
- const timingName = `ScriptingObject.API.${objectId}.${body.functionName}`;
557
- const trackTiming = this.#shouldTrackTiming(timingName);
558
- if (trackTiming) {
559
- this.#startTiming(timingName, {
539
+ let timingToken;
540
+ if (this.#perfTracker.enabled) {
541
+ const name = `ScriptingObject.API.${objectId}.${body.functionName}`;
542
+ timingToken = this.#perfTracker.start(name, {
560
543
  appId: guestInfo.guestId,
561
544
  appUrl: guestInfo.guestUrl
562
545
  });
@@ -596,8 +579,8 @@ class SSFHost {
596
579
  ...guestInfo
597
580
  });
598
581
  }).finally(() => {
599
- if (trackTiming) {
600
- this.#endTiming(timingName, {
582
+ if (timingToken) {
583
+ this.#perfTracker.end(timingToken, {
601
584
  appId: guestInfo.guestId,
602
585
  appUrl: guestInfo.guestUrl
603
586
  });
@@ -672,7 +655,7 @@ class SSFHost {
672
655
  const guest = new Guest({
673
656
  ...param,
674
657
  remoting: this.#remoting,
675
- analyticsObj: this.#analyticsObj
658
+ perfTracker: this.#guestPerfTracker
676
659
  });
677
660
  guest.init();
678
661
  this.#guests.set(param.guestId, guest);
@@ -1045,18 +1028,16 @@ class SSFHost {
1045
1028
  };
1046
1029
  }
1047
1030
  const guestPromises = [];
1048
- const eventTimingName = `ScriptingObject.Event.${scriptingObject.id}.${name}`;
1049
- let timingMetricStarted = false;
1031
+ let eventTimingToken;
1050
1032
  const dispatchToGuest = (guest) => {
1051
1033
  const guestInfo = guest.getInfo();
1052
1034
  if (timeout && guest?.capabilities?.eventFeedback) {
1053
1035
  guestPromises.push(guest.dispatchEvent(eventObj, timeout));
1054
- if (!timingMetricStarted && this.#shouldTrackTiming(eventTimingName)) {
1055
- this.#startTiming(eventTimingName, {
1056
- appId: this.hostId,
1057
- appUrl: window.location.href
1058
- });
1059
- timingMetricStarted = true;
1036
+ if (!eventTimingToken && this.#perfTracker.enabled) {
1037
+ eventTimingToken = this.#perfTracker.start(
1038
+ `ScriptingObject.Event.${scriptingObject.id}.${name}`,
1039
+ { appId: this.hostId, appUrl: window.location.href }
1040
+ );
1060
1041
  }
1061
1042
  this.#logger.debug({
1062
1043
  message: "Event dispatched and awaiting feedback",
@@ -1094,11 +1075,12 @@ class SSFHost {
1094
1075
  });
1095
1076
  throw ex;
1096
1077
  }).finally(() => {
1097
- if (timingMetricStarted)
1098
- this.#endTiming(eventTimingName, {
1078
+ if (eventTimingToken) {
1079
+ this.#perfTracker.end(eventTimingToken, {
1099
1080
  appId: this.hostId,
1100
1081
  appUrl: window.location.href
1101
1082
  });
1083
+ }
1102
1084
  });
1103
1085
  };
1104
1086
  /**
@@ -1142,7 +1124,7 @@ class SSFHost {
1142
1124
  const { openMode = OpenMode.Embed, popupWindowFeatures = {} } = options;
1143
1125
  const srcUrl = this.#getGuestUrl(url, searchParams);
1144
1126
  let guest = null;
1145
- this.#startTiming("SSF.Guest.Load", {
1127
+ this.#perfTracker.start("SSF.Guest.Load", {
1146
1128
  appId: instanceId,
1147
1129
  appUrl: srcUrl
1148
1130
  });