senza-sdk 4.5.1 → 4.5.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "senza-sdk",
3
- "version": "4.5.1",
3
+ "version": "4.5.2",
4
4
  "main": "./src/api.js",
5
5
  "description": "API for Senza application",
6
6
  "license": "MIT",
@@ -1,6 +1,28 @@
1
1
  import { Overlay as OverlayInterface } from "../interface/overlay.js";
2
2
  import { getFCID, sdkLogger, SenzaError } from "./utils";
3
3
 
4
+ /**
5
+ * Named overlay capture plans. Each step: `{ quality, conditional, delay }`.
6
+ * Steps run when refresh() is called; `delay` is ms after the previous step in the batch.
7
+ * `conditional`: when true, capture only if overlay content changed since the last
8
+ * required (non-conditional) capture in this batch; when false, always capture on that step.
9
+ * Set per step by the plan author; the SDK does not derive it.
10
+ */
11
+ const OVERLAY_CAPTURE_PRESETS = {
12
+ default: [
13
+ { quality: "low", conditional: false, delay: 0 },
14
+ { quality: "low", conditional: true, delay: 150 },
15
+ { quality: "low", conditional: true, delay: 300 },
16
+ { quality: "high", conditional: false, delay: 600 }
17
+ ],
18
+ once: [
19
+ { quality: "high", conditional: true, delay: 0 }
20
+ ]
21
+ };
22
+
23
+ const DEFAULT_CAPTURE_PRESET = "default";
24
+ const VALID_QUALITIES = new Set(["low", "mid", "high"]);
25
+
4
26
  class Overlay extends OverlayInterface {
5
27
  constructor() {
6
28
  super();
@@ -8,51 +30,102 @@ class Overlay extends OverlayInterface {
8
30
  this._element = null;
9
31
  this._configuration = {
10
32
  useTransparency: true,
11
- autoRefreshSeconds: 2
33
+ overlayCapturePreset: DEFAULT_CAPTURE_PRESET,
34
+ overlayCapturePlan: null
12
35
  };
36
+ this._activeCapturePlan = OVERLAY_CAPTURE_PRESETS[DEFAULT_CAPTURE_PRESET];
13
37
  this._retryGeneration = 0;
14
- this._retryTimerId = null;
38
+ this._planTimerId = null;
39
+ this._currentBatchId = null;
15
40
  }
16
41
 
17
42
  _isTransparencyEnabled() {
18
43
  return this._configuration.useTransparency !== false;
19
44
  }
20
45
 
46
+ _clearPlanTimer() {
47
+ if (this._planTimerId != null) {
48
+ clearTimeout(this._planTimerId);
49
+ this._planTimerId = null;
50
+ }
51
+ }
52
+
21
53
  _clearRetryTimers() {
22
54
  this._retryGeneration++;
23
- clearTimeout(this._retryTimerId);
24
- this._retryTimerId = null;
55
+ this._clearPlanTimer();
25
56
  }
26
57
 
27
- _scheduleRetries(maxMs) {
28
- const startTime = Date.now();
29
- let delay = 100;
30
- const generation = ++this._retryGeneration;
58
+ _validateCapturePlan(plan) {
59
+ if (!Array.isArray(plan) || plan.length === 0) {
60
+ return false;
61
+ }
62
+ for (let i = 0; i < plan.length; i++) {
63
+ const step = plan[i];
64
+ if (!step || typeof step !== "object") {
65
+ return false;
66
+ }
67
+ if (!VALID_QUALITIES.has(step.quality)) {
68
+ return false;
69
+ }
70
+ if (typeof step.conditional !== "boolean") {
71
+ return false;
72
+ }
73
+ if (typeof step.delay !== "number" || !Number.isFinite(step.delay) || step.delay < 0) {
74
+ return false;
75
+ }
76
+ }
77
+ return true;
78
+ }
31
79
 
32
- const scheduleNext = () => {
33
- if (this._retryGeneration !== generation) return;
34
- const elapsed = Date.now() - startTime;
35
- if (elapsed + delay > maxMs) return;
80
+ _resolveCapturePlan() {
81
+ if (this._configuration.overlayCapturePlan != null) {
82
+ return structuredClone(this._configuration.overlayCapturePlan);
83
+ }
84
+ const presetName = this._configuration.overlayCapturePreset ?? DEFAULT_CAPTURE_PRESET;
85
+ const preset = OVERLAY_CAPTURE_PRESETS[presetName];
86
+ if (!preset) {
87
+ throw new Error(`Overlay: unknown overlayCapturePreset "${presetName}"`);
88
+ }
89
+ return structuredClone(preset);
90
+ }
36
91
 
37
- this._retryTimerId = setTimeout(async () => {
38
- if (this._retryGeneration !== generation) return;
92
+ _scheduleCapturePlanFromStep(batchId, plan, generation, startIndex) {
93
+ const scheduleStep = (index) => {
94
+ if (this._retryGeneration !== generation) {
95
+ return;
96
+ }
97
+ const step = plan[index];
98
+ if (!step) {
99
+ sdkLogger.error(`Overlay: missing capture plan step at index ${index} (plan length ${plan.length})`);
100
+ return;
101
+ }
102
+ this._clearPlanTimer();
103
+ this._planTimerId = setTimeout(async () => {
104
+ if (this._retryGeneration !== generation) {
105
+ return;
106
+ }
39
107
  try {
40
- await this._renderFrame(true);
108
+ await this._renderFrame(batchId, step, index);
41
109
  } catch (err) {
42
- sdkLogger.log(`Overlay: conditional refresh error: ${err}`);
110
+ sdkLogger.error("Overlay: capture plan step error:", err);
111
+ }
112
+ if (index + 1 < plan.length && this._retryGeneration === generation) {
113
+ scheduleStep(index + 1);
43
114
  }
44
- delay *= 2;
45
- scheduleNext();
46
- }, delay);
115
+ }, step.delay);
47
116
  };
48
117
 
49
- scheduleNext();
118
+ scheduleStep(startIndex);
50
119
  }
51
120
 
52
- _sendFrame(x, y, width, height, conditional = false) {
121
+ /**
122
+ * @param {boolean} conditional - When true, capture only if overlay content changed since
123
+ * the last required capture in this batch; when false, always capture on this step.
124
+ */
125
+ _sendFrame(x, y, width, height, batchId, quality, conditional, stepIndex = 0) {
53
126
  if (window.cefQuery) {
54
127
  return new Promise((resolve, reject) => {
55
- const FCID = getFCID();
128
+ const FCID = `${stepIndex}-${getFCID()}`;
56
129
  const message = {
57
130
  type: "displayOverlay",
58
131
  x,
@@ -61,8 +134,11 @@ class Overlay extends OverlayInterface {
61
134
  height,
62
135
  useTransparency: this._isTransparencyEnabled(),
63
136
  fcid: FCID,
137
+ batchId,
138
+ quality,
64
139
  conditional
65
140
  };
141
+ sdkLogger.log(`Overlay: rendering frame x=${x} y=${y} width=${width} height=${height} fcid=${FCID} (batchId=${batchId}, quality=${quality}, conditional=${conditional})`);
66
142
  const request = { target: "UI-Streamer", waitForResponse: false, message: JSON.stringify(message) };
67
143
  window.cefQuery({
68
144
  request: JSON.stringify(request),
@@ -79,7 +155,7 @@ class Overlay extends OverlayInterface {
79
155
  return Promise.reject("Overlay is not supported if NOT running e2e");
80
156
  }
81
157
 
82
- _renderFrame(conditional = false) {
158
+ _renderFrame(batchId, captureStep = { quality: "high", conditional: false }, stepIndex = 0) {
83
159
  if (!this._element) {
84
160
  return Promise.reject("No element configured");
85
161
  }
@@ -91,9 +167,10 @@ class Overlay extends OverlayInterface {
91
167
  return Promise.resolve(false);
92
168
  }
93
169
 
94
- sdkLogger.log(`Overlay: rendering frame x=${frame.x} y=${frame.y} width=${frame.width} height=${frame.height} (conditional=${conditional})`);
95
- return this._sendFrame(frame.x, frame.y, frame.width, frame.height, conditional).then(() => {
96
- if (!conditional) {
170
+ const id = batchId ?? this._currentBatchId ?? getFCID();
171
+
172
+ return this._sendFrame(frame.x, frame.y, frame.width, frame.height, id, captureStep.quality, captureStep.conditional, stepIndex).then(() => {
173
+ if (!captureStep.conditional) {
97
174
  const refreshEvent = new Event("refresh");
98
175
  refreshEvent.frame = frame;
99
176
  this.dispatchEvent(refreshEvent);
@@ -108,7 +185,7 @@ class Overlay extends OverlayInterface {
108
185
  const x = Math.round(toFiniteNumber(rect.x));
109
186
  const y = Math.round(toFiniteNumber(rect.y));
110
187
  const width = Math.round(toFiniteNumber(rect.width));
111
- const height =Math.round(toFiniteNumber(rect.height));
188
+ const height = Math.round(toFiniteNumber(rect.height));
112
189
 
113
190
  return { x, y, width, height };
114
191
  }
@@ -116,16 +193,41 @@ class Overlay extends OverlayInterface {
116
193
  _applyConfiguration(configuration) {
117
194
  const normalizedConfiguration = { ...configuration };
118
195
 
196
+ if (
197
+ Object.prototype.hasOwnProperty.call(normalizedConfiguration, "overlayCapturePreset") ||
198
+ Object.prototype.hasOwnProperty.call(normalizedConfiguration, "overlayCapturePlan")
199
+ ) {
200
+ this._clearRetryTimers();
201
+ }
202
+
119
203
  if (Object.prototype.hasOwnProperty.call(normalizedConfiguration, "useTransparency")) {
120
204
  normalizedConfiguration.useTransparency = normalizedConfiguration.useTransparency !== false;
121
205
  }
122
206
 
123
- if (Object.prototype.hasOwnProperty.call(normalizedConfiguration, "autoRefreshSeconds")) {
124
- const v = normalizedConfiguration.autoRefreshSeconds;
125
- normalizedConfiguration.autoRefreshSeconds = (typeof v === "number" && v >= 0) ? v : this._configuration.autoRefreshSeconds;
207
+ if (Object.prototype.hasOwnProperty.call(normalizedConfiguration, "overlayCapturePreset")) {
208
+ const preset = normalizedConfiguration.overlayCapturePreset;
209
+ if (typeof preset === "string" && OVERLAY_CAPTURE_PRESETS[preset]) {
210
+ normalizedConfiguration.overlayCapturePreset = preset;
211
+ } else if (preset != null) {
212
+ sdkLogger.warn(`Overlay: invalid overlayCapturePreset "${preset}", keeping previous preset`);
213
+ delete normalizedConfiguration.overlayCapturePreset;
214
+ }
215
+ }
216
+
217
+ if (Object.prototype.hasOwnProperty.call(normalizedConfiguration, "overlayCapturePlan")) {
218
+ const plan = normalizedConfiguration.overlayCapturePlan;
219
+ if (plan == null) {
220
+ normalizedConfiguration.overlayCapturePlan = null;
221
+ } else if (this._validateCapturePlan(plan)) {
222
+ normalizedConfiguration.overlayCapturePlan = structuredClone(plan);
223
+ } else {
224
+ sdkLogger.warn("Overlay: invalid overlayCapturePlan, keeping previous plan");
225
+ delete normalizedConfiguration.overlayCapturePlan;
226
+ }
126
227
  }
127
228
 
128
229
  this._configuration = { ...this._configuration, ...normalizedConfiguration };
230
+ this._activeCapturePlan = this._resolveCapturePlan();
129
231
  }
130
232
 
131
233
  async addElement(element) {
@@ -171,14 +273,19 @@ class Overlay extends OverlayInterface {
171
273
  return Promise.reject(errorMsg);
172
274
  }
173
275
  this._clearRetryTimers();
174
- const result = await this._renderFrame();
175
- if (result === true) {
176
- const maxMs = (this._configuration.autoRefreshSeconds ?? 0) * 1000;
177
- if (maxMs > 0) {
178
- this._scheduleRetries(maxMs);
276
+ this._currentBatchId = getFCID();
277
+ const batchId = this._currentBatchId;
278
+ const generation = this._retryGeneration;
279
+ const plan = this._activeCapturePlan;
280
+ if (plan[0].delay === 0) {
281
+ const result = await this._renderFrame(batchId, plan[0], 0);
282
+ if (result === true && plan.length > 1) {
283
+ this._scheduleCapturePlanFromStep(batchId, plan, generation, 1);
179
284
  }
285
+ return result;
180
286
  }
181
- return result;
287
+ this._scheduleCapturePlanFromStep(batchId, plan, generation, 0);
288
+ return true;
182
289
  }
183
290
 
184
291
  async removeAllElements() {
@@ -46,13 +46,11 @@ class Overlay extends EventTarget {
46
46
  * This is useful when the element's content or position has changed.
47
47
  * The overlay must have an element registered before calling refresh.
48
48
  *
49
- * After the initial capture, conditional retries are automatically scheduled using
50
- * exponential backoff (starting at 100 ms, doubling each step) until the window
51
- * configured by `configure({ autoRefreshSeconds })` expires (default: 2 seconds).
52
- * Each retry is skipped by the UI-Streamer unless the browser has repainted since
53
- * the last non-conditional capture, eliminating redundant JPEG encodes.
54
- * Pending retries are cancelled by a subsequent call to refresh(), hideOverlay(),
55
- * or removeElement().
49
+ * Runs the configured overlay capture plan after each call. The `default` preset sends
50
+ * low-quality captures with relative delays, ending with a high-quality capture.
51
+ * The `once` preset runs one high-quality conditional step immediately.
52
+ * All steps in one refresh share the same `batchId` (from `getFCID()`). Pending plan
53
+ * steps are cancelled by a subsequent call to refresh(), hideOverlay(), or removeElement().
56
54
  *
57
55
  * @returns {Promise<boolean>} Resolves to true if successful, rejects with error if failed.
58
56
  */
@@ -85,9 +83,13 @@ class Overlay extends EventTarget {
85
83
  * @param {Object} configuration - The new configuration to apply.
86
84
  * @param {boolean} [configuration.useTransparency=true] - Controls whether the overlay should be rendered with transparency.
87
85
  * When set to `true`, the value is forwarded to UI-Streamer in `displayOverlay` requests.
88
- * @param {number} [configuration.autoRefreshSeconds=2] - Maximum window in seconds for automatic
89
- * conditional retries after each refresh() call. Retries are spaced with exponential backoff
90
- * starting at 100 ms. Set to 0 to disable automatic retries.
86
+ * @param {string} [configuration.overlayCapturePreset="default"] - Named capture plan preset:
87
+ * `default` (progressive low→high captures) or `once` (one immediate high-quality conditional step).
88
+ * @param {Array<{quality: "low"|"mid"|"high", conditional: boolean, delay: number}>} [configuration.overlayCapturePlan]
89
+ * Explicit capture plan; overrides preset. Steps run on refresh(); each step's `delay` is ms after
90
+ * the previous step in the batch. Set `conditional` per step (the SDK does not derive it):
91
+ * when `true`, capture only if overlay content changed since the last required (non-conditional)
92
+ * capture in this batch; when `false`, always capture on that step.
91
93
  */
92
94
  configure(configuration) {
93
95
  noop(configuration);
@@ -1 +1 @@
1
- export const version = "4.5.1";
1
+ export const version = "4.5.2";