videomail-client 16.5.0 → 16.7.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.
package/dist/esm/index.js CHANGED
@@ -3620,7 +3620,7 @@ const constants = {
3620
3620
  }
3621
3621
  };
3622
3622
  var package_namespaceObject = {
3623
- rE: "16.5.0"
3623
+ rE: "16.7.0"
3624
3624
  };
3625
3625
  function isAudioEnabled(options) {
3626
3626
  return Boolean(options.audio.enabled);
@@ -4286,6 +4286,17 @@ class Form extends util_Despot {
4286
4286
  enable(buttonsToo) {
4287
4287
  this.setDisabled(false, buttonsToo);
4288
4288
  }
4289
+ disableForSubmission() {
4290
+ for (const formControl of this.formElement.elements){
4291
+ this.submissionDisabledStates.set(formControl, formControl.hasAttribute("disabled"));
4292
+ formControl.setAttribute("disabled", "disabled");
4293
+ }
4294
+ }
4295
+ restoreAfterFailedSubmission() {
4296
+ for (const [formControl, wasDisabled] of this.submissionDisabledStates)if (wasDisabled) formControl.setAttribute("disabled", "disabled");
4297
+ else formControl.removeAttribute("disabled");
4298
+ this.submissionDisabledStates.clear();
4299
+ }
4289
4300
  build() {
4290
4301
  this.options.logger.debug("Form: build()");
4291
4302
  this.keyInput = this.formElement.querySelector(`input[name="${this.options.selectors.keyInputName}"]`);
@@ -4446,7 +4457,7 @@ class Form extends util_Despot {
4446
4457
  html_showElement(this.formElement);
4447
4458
  }
4448
4459
  constructor(container, formElement, options){
4449
- super("Form", options), form_define_property(this, "container", void 0), form_define_property(this, "formElement", void 0), form_define_property(this, "keyInput", void 0), form_define_property(this, "submitButton", void 0), form_define_property(this, "submitButtonFormAttribute", null), form_define_property(this, "generatedFormId", void 0), form_define_property(this, "isSubmitting", false), form_define_property(this, "FORM_FIELDS", {}), form_define_property(this, "validate", (event)=>{
4460
+ super("Form", options), form_define_property(this, "container", void 0), form_define_property(this, "formElement", void 0), form_define_property(this, "keyInput", void 0), form_define_property(this, "submitButton", void 0), form_define_property(this, "submitButtonFormAttribute", null), form_define_property(this, "generatedFormId", void 0), form_define_property(this, "isSubmitting", false), form_define_property(this, "submissionDisabledStates", new Map()), form_define_property(this, "FORM_FIELDS", {}), form_define_property(this, "validate", (event)=>{
4450
4461
  this.container.validate(event);
4451
4462
  }), form_define_property(this, "handleSubmit", (event)=>{
4452
4463
  if (!event.defaultPrevented) this.doTheSubmit(event);
@@ -5085,8 +5096,13 @@ class Buttons extends util_Despot {
5085
5096
  this.hide();
5086
5097
  }
5087
5098
  onUserMediaReady(params) {
5088
- this.onFormReady();
5099
+ this.onFormReady(params);
5089
5100
  html_showElement(this.buttonsElement);
5101
+ if (params.recovering && params.paused) {
5102
+ html_hideElement(this.recordButton);
5103
+ html_disableElement(this.recordButton);
5104
+ return;
5105
+ }
5090
5106
  html_showElement(this.audioOnRadioPair);
5091
5107
  html_showElement(this.audioOffRadioPair);
5092
5108
  if (html_isShown(this.recordButton) && !params.recordWhenReady) html_enableElement(this.recordButton);
@@ -5789,8 +5805,8 @@ class RecorderInsides extends util_Despot {
5789
5805
  this.on("PAUSED", ()=>{
5790
5806
  this.pauseRecording();
5791
5807
  });
5792
- this.on("ERROR", ()=>{
5793
- this.onResetting();
5808
+ this.on("ERROR", ({ recoverable })=>{
5809
+ if (!recoverable) this.onResetting();
5794
5810
  });
5795
5811
  this.on("RESETTING", ()=>{
5796
5812
  this.onResetting();
@@ -5973,12 +5989,12 @@ class Notifier extends util_Despot {
5973
5989
  this.entertainTimeoutId = void 0;
5974
5990
  this.entertaining = false;
5975
5991
  }
5976
- error(err) {
5992
+ error(err, blocking = true) {
5977
5993
  const message = err.message;
5978
5994
  const explanation = err.explanation;
5979
5995
  if (!message) this.options.logger.debug(`Weird empty error message generated for error ${string_pretty(err)}`);
5980
5996
  this.notify(message, explanation, {
5981
- blocking: true,
5997
+ blocking,
5982
5998
  problem: true,
5983
5999
  classList: err.getClassList(),
5984
6000
  removeDimensions: util_getBrowser(this.options).isMobile()
@@ -6103,6 +6119,18 @@ function summarize(title, contents) {
6103
6119
  return line;
6104
6120
  }
6105
6121
  const string_summarize = summarize;
6122
+ function isAutomatedUserAgent(userAgent = navigator.userAgent) {
6123
+ return /bot|crawler|spider|headlesschrome|phantomjs|puppeteer|playwright/iu.test(userAgent);
6124
+ }
6125
+ const util_isAutomatedUserAgent = isAutomatedUserAgent;
6126
+ function getAutomationSignals() {
6127
+ const webdriver = navigator.webdriver;
6128
+ return {
6129
+ webdriver: true === webdriver,
6130
+ userAgentMatched: util_isAutomatedUserAgent()
6131
+ };
6132
+ }
6133
+ const util_getAutomationSignals = getAutomationSignals;
6106
6134
  function getEdgeCodes(value, amount, fromEnd) {
6107
6135
  const chars = fromEnd ? value.slice(-amount) : value.slice(0, amount);
6108
6136
  const codes = Array.from(chars, (char)=>String(char.codePointAt(0))).join(".");
@@ -6130,15 +6158,16 @@ function getLanguagesValue() {
6130
6158
  if (languageList.length > 0) return languageList.join(",");
6131
6159
  return navigator.language || "none";
6132
6160
  }
6133
- function getAutomationSignals(probeBase, probeSimpleQuery) {
6161
+ function getDiagnosticSignals(probeBase, probeSimpleQuery) {
6134
6162
  const reasons = [];
6135
- if (navigator.webdriver) reasons.push("webdriver=true");
6136
- if (/HeadlessChrome|PhantomJS|puppeteer|playwright/iu.test(navigator.userAgent)) reasons.push("headless_ua_marker");
6163
+ const automation = util_getAutomationSignals();
6164
+ if (automation.webdriver) reasons.push("webdriver=true");
6165
+ if (automation.userAgentMatched) reasons.push("headless_ua_marker");
6137
6166
  if (0 === navigator.languages.length) reasons.push("no_languages");
6138
6167
  if ("ok" !== probeBase) reasons.push("probe_base_failed");
6139
6168
  if ("ok" !== probeSimpleQuery) reasons.push("probe_simple_query_failed");
6140
6169
  return {
6141
- looksAutomated: reasons.length > 0,
6170
+ looksAutomated: automation.webdriver || automation.userAgentMatched,
6142
6171
  reasons: reasons.length > 0 ? reasons.join(",") : "none"
6143
6172
  };
6144
6173
  }
@@ -6155,7 +6184,7 @@ function getWebSocketDiagnostic(url2Connect) {
6155
6184
  const probeBase = probeWebSocketConstructor("wss://videomail.io");
6156
6185
  const probeWithSimpleQuery = probeWebSocketConstructor("wss://videomail.io/?a=b");
6157
6186
  const languages = getLanguagesValue();
6158
- const automationSignals = getAutomationSignals(probeBase, probeWithSimpleQuery);
6187
+ const automationSignals = getDiagnosticSignals(probeBase, probeWithSimpleQuery);
6159
6188
  const diagnosticPairs = [
6160
6189
  `webdriver=${String(navigator.webdriver)}`,
6161
6190
  `wsCtorNative=${String(wsCtorNative)}`,
@@ -6182,10 +6211,6 @@ function getWebSocketDiagnostic(url2Connect) {
6182
6211
  };
6183
6212
  }
6184
6213
  const error_getWebSocketDiagnostic = getWebSocketDiagnostic;
6185
- function isAutomatedUserAgent(userAgent = navigator.userAgent) {
6186
- return /bot|crawler|spider|headlesschrome|phantomjs|puppeteer|playwright/iu.test(userAgent);
6187
- }
6188
- const util_isAutomatedUserAgent = isAutomatedUserAgent;
6189
6214
  function boundedWriteQueue_define_property(obj, key, value) {
6190
6215
  if (key in obj) Object.defineProperty(obj, key, {
6191
6216
  value: value,
@@ -6381,6 +6406,9 @@ class RecorderStreamWriter extends util_Despot {
6381
6406
  explanation: "The server connection closed before this recording completed. It cannot be safely resumed, so it has been stopped. Please start a new recording.",
6382
6407
  options: this.options,
6383
6408
  cause: {
6409
+ transportPhase: "recording",
6410
+ mediaTransmissionStarted: true,
6411
+ automation: util_getAutomationSignals(),
6384
6412
  unconfirmedFrames,
6385
6413
  unconfirmedSamples
6386
6414
  }
@@ -6448,15 +6476,32 @@ class RecorderConnection extends streamWriter {
6448
6476
  this.resumeAfterDrain = false;
6449
6477
  this.reconnecting = true;
6450
6478
  this.initSocket();
6451
- } else if (this.shouldReportStreamClose()) window.setTimeout(()=>{
6452
- this.handleConnectionFailure({
6453
- url2Connect,
6454
- cause: "closed"
6455
- });
6456
- }, 0);
6479
+ } else if (this.shouldReportStreamClose()) {
6480
+ const stream = this.stream;
6481
+ window.setTimeout(()=>{
6482
+ if (this.stream === stream) this.handleConnectionFailure({
6483
+ url2Connect,
6484
+ cause: "closed"
6485
+ });
6486
+ }, 0);
6487
+ }
6457
6488
  }
6458
6489
  shouldIgnoreStreamClose() {
6459
- return this.recordingTransportFailed || Boolean(this.unloaded) || this.validate();
6490
+ return this.recordingTransportFailed || Boolean(this.unloaded) || this.validate() || this.blocking && !this.mediaTransmissionStarted;
6491
+ }
6492
+ closeIdleConnection(reason) {
6493
+ if (this.mediaTransmissionStarted) return;
6494
+ this.options.logger.debug(string_summarize("Recorder: closing idle connection", {
6495
+ reason,
6496
+ connected: this.connected,
6497
+ connecting: this.connecting,
6498
+ visibilityState: document.visibilityState
6499
+ }));
6500
+ this.connected = this.connecting = this.reconnecting = false;
6501
+ this.clearConnectionTimeout();
6502
+ this.clearReconnectTimeout();
6503
+ this.stopPings();
6504
+ this.discardSocket();
6460
6505
  }
6461
6506
  handleStreamError(url2Connect, err) {
6462
6507
  if ("u" > typeof Event && err instanceof Event) this.lastSocketError = error_getEventDetails(err);
@@ -6465,28 +6510,20 @@ class RecorderConnection extends streamWriter {
6465
6510
  error: serializeError(err)
6466
6511
  });
6467
6512
  this.options.logger.debug(debugLine);
6468
- if (this.validate()) {
6513
+ if (this.shouldIgnoreStreamClose()) {
6469
6514
  this.connected = false;
6470
6515
  this.stopPings();
6471
6516
  this.discardSocket();
6472
6517
  return;
6473
6518
  }
6474
- if (!this.connected) return void this.handleConnectionFailure({
6519
+ if (this.mediaTransmissionStarted) return void this.failInterruptedRecording();
6520
+ this.reconnecting ||= this.canReconnectAfterStreamClose();
6521
+ this.connected = false;
6522
+ this.stopPings();
6523
+ this.handleConnectionFailure({
6475
6524
  url2Connect,
6476
6525
  cause: "error"
6477
6526
  });
6478
- const streamError = err instanceof Error ? err : new Error("WebSocket stream emitted an error event", {
6479
- cause: this.lastSocketError ?? err
6480
- });
6481
- if (this.mediaTransmissionStarted) return void this.failInterruptedRecording();
6482
- this.emit("ERROR", {
6483
- err: error_createError({
6484
- message: "WebSocket stream error",
6485
- explanation: `The WebSocket stream emitted an error event. Details: ${string_pretty(err)}`,
6486
- options: this.options,
6487
- exc: streamError
6488
- })
6489
- });
6490
6527
  }
6491
6528
  sendPings() {
6492
6529
  this.stopPings();
@@ -6546,6 +6583,7 @@ class RecorderConnection extends streamWriter {
6546
6583
  this.connectionFailed = true;
6547
6584
  this.connecting = false;
6548
6585
  this.reconnecting = false;
6586
+ const reconnectAttempts = this.reconnectAttempts;
6549
6587
  this.reconnectAttempts = 0;
6550
6588
  this.clearReconnectTimeout();
6551
6589
  this.clearConnectionTimeout();
@@ -6556,6 +6594,14 @@ class RecorderConnection extends streamWriter {
6556
6594
  const socketError = this.lastSocketError;
6557
6595
  const contents = {
6558
6596
  cause,
6597
+ transportPhase: void 0 === this.connectionOpenedAt ? "initial" : "idle",
6598
+ mediaTransmissionStarted: this.mediaTransmissionStarted,
6599
+ recordingActive: this.recordingActive,
6600
+ framesCount: this.framesCount,
6601
+ samplesCount: this.samplesCount,
6602
+ reconnectAttempts,
6603
+ connectionOpenedAt: this.connectionOpenedAt,
6604
+ automation: util_getAutomationSignals(),
6559
6605
  closeCode: closeEvent?.code,
6560
6606
  closeReason: closeEvent?.reason,
6561
6607
  elapsedMs,
@@ -6597,7 +6643,7 @@ class RecorderConnection extends streamWriter {
6597
6643
  });
6598
6644
  }
6599
6645
  shouldIgnoreConnectionFailure() {
6600
- return this.connectionFailed || this.connected || Boolean(this.unloaded) || this.validate();
6646
+ return this.connectionFailed || this.connected || Boolean(this.unloaded) || this.validate() || this.blocking && !this.mediaTransmissionStarted;
6601
6647
  }
6602
6648
  initSocket(cb) {
6603
6649
  if (this.connected) {
@@ -6607,7 +6653,8 @@ class RecorderConnection extends streamWriter {
6607
6653
  }
6608
6654
  if (this.connecting) return void this.options.logger.debug("Recorder: already **connecting**. Not going to initialize a new WebSocket connection.");
6609
6655
  this.clearReconnectTimeout();
6610
- if (util_isAutomatedUserAgent()) {
6656
+ const automation = util_getAutomationSignals();
6657
+ if (automation.userAgentMatched) {
6611
6658
  this.connecting = false;
6612
6659
  this.options.logger.debug("Recorder: skipping web socket connection for an automated crawler");
6613
6660
  return;
@@ -6641,6 +6688,7 @@ class RecorderConnection extends streamWriter {
6641
6688
  try {
6642
6689
  nativeSocket = new WebSocket(url2Connect);
6643
6690
  nativeSocket.addEventListener("close", (event)=>{
6691
+ if (this.stream?.socket !== nativeSocket) return;
6644
6692
  this.lastCloseEvent = {
6645
6693
  code: event.code,
6646
6694
  reason: event.reason,
@@ -6648,6 +6696,7 @@ class RecorderConnection extends streamWriter {
6648
6696
  };
6649
6697
  });
6650
6698
  nativeSocket.addEventListener("error", (event)=>{
6699
+ if (this.stream?.socket !== nativeSocket) return;
6651
6700
  this.lastSocketError = error_getEventDetails(event);
6652
6701
  });
6653
6702
  } catch (exc) {
@@ -6659,7 +6708,10 @@ class RecorderConnection extends streamWriter {
6659
6708
  message,
6660
6709
  explanation,
6661
6710
  options: this.options,
6662
- exc
6711
+ exc,
6712
+ cause: {
6713
+ automation
6714
+ }
6663
6715
  });
6664
6716
  this.emit("ERROR", {
6665
6717
  err
@@ -6697,14 +6749,16 @@ class RecorderConnection extends streamWriter {
6697
6749
  });
6698
6750
  }, connectionTimeoutMs);
6699
6751
  stream.on("close", ()=>{
6700
- this.handleStreamClose(url2Connect);
6752
+ if (this.stream === stream) this.handleStreamClose(url2Connect);
6701
6753
  });
6702
6754
  stream.on("connect", (args)=>{
6755
+ if (this.stream !== stream) return;
6703
6756
  this.options.logger.debug(`${PIPE_SYMBOL}Stream *connect* event emitted with args: ${string_pretty(args)}`);
6704
6757
  this.clearConnectionTimeout();
6705
- const isClosing = this.stream?.socket.readyState === WebSocket.CLOSING;
6758
+ const isClosing = stream.socket.readyState === WebSocket.CLOSING;
6706
6759
  if (!this.connected && !isClosing && !this.unloaded) {
6707
6760
  this.connected = true;
6761
+ this.connectionOpenedAt = Date.now();
6708
6762
  this.connecting = this.unloaded = false;
6709
6763
  this.reconnecting = false;
6710
6764
  this.reconnectAttempts = 0;
@@ -6714,6 +6768,7 @@ class RecorderConnection extends streamWriter {
6714
6768
  }
6715
6769
  });
6716
6770
  stream.on("data", (data)=>{
6771
+ if (this.stream !== stream) return;
6717
6772
  this.options.logger.debug(`${PIPE_SYMBOL}Stream *data* event emitted`);
6718
6773
  try {
6719
6774
  const command = JSON.parse(data.toString());
@@ -6732,10 +6787,10 @@ class RecorderConnection extends streamWriter {
6732
6787
  }
6733
6788
  });
6734
6789
  stream.on("error", (err)=>{
6735
- this.handleStreamError(url2Connect, err);
6790
+ if (this.stream === stream) this.handleStreamError(url2Connect, err);
6736
6791
  });
6737
6792
  stream.on("drain", ()=>{
6738
- this.handleStreamDrain(stream);
6793
+ if (this.stream === stream) this.handleStreamDrain(stream);
6739
6794
  });
6740
6795
  stream.on("preend", (args)=>{
6741
6796
  this.options.logger.debug(`${PIPE_SYMBOL}Stream *preend* event emitted with args: ${string_pretty(args)}`);
@@ -6789,7 +6844,7 @@ class RecorderConnection extends streamWriter {
6789
6844
  }
6790
6845
  }
6791
6846
  constructor(options){
6792
- super(options), connection_define_property(this, "reconnectTimeout", void 0), connection_define_property(this, "connectionTimeout", void 0), connection_define_property(this, "userMediaLoaded", void 0), connection_define_property(this, "userMediaLoading", false), connection_define_property(this, "unloaded", void 0), connection_define_property(this, "connectionFailed", false), connection_define_property(this, "reconnectAttempts", 0), connection_define_property(this, "blocking", false), connection_define_property(this, "connectingStartedAt", void 0), connection_define_property(this, "lastCloseEvent", void 0), connection_define_property(this, "lastSocketError", void 0), connection_define_property(this, "pingInterval", void 0);
6847
+ super(options), connection_define_property(this, "reconnectTimeout", void 0), connection_define_property(this, "connectionTimeout", void 0), connection_define_property(this, "userMediaLoaded", void 0), connection_define_property(this, "userMediaLoading", false), connection_define_property(this, "unloaded", void 0), connection_define_property(this, "connectionFailed", false), connection_define_property(this, "reconnectAttempts", 0), connection_define_property(this, "blocking", false), connection_define_property(this, "connectingStartedAt", void 0), connection_define_property(this, "lastCloseEvent", void 0), connection_define_property(this, "lastSocketError", void 0), connection_define_property(this, "pingInterval", void 0), connection_define_property(this, "connectionOpenedAt", void 0);
6793
6848
  }
6794
6849
  }
6795
6850
  const connection = RecorderConnection;
@@ -6851,6 +6906,7 @@ class RecorderMedia extends connection {
6851
6906
  const characteristics = this.userMedia?.getCharacteristics();
6852
6907
  this.options.logger.debug(`Recorder: userMediaErrorCallback(), name: ${err.name}, message: ${err.message} and Webcam characteristics: ${characteristics ? string_pretty(characteristics) : "none"}`);
6853
6908
  const errorListeners = this.getListeners("ERROR");
6909
+ if (err.name !== error_VideomailError.MEDIA_DEVICE_NOT_SUPPORTED) this.closeIdleConnection("camera-access-failed");
6854
6910
  if (errorListeners?.length) if (err.name !== error_VideomailError.MEDIA_DEVICE_NOT_SUPPORTED) {
6855
6911
  const videomailError = error_createError({
6856
6912
  err,
@@ -7332,6 +7388,9 @@ class RecorderCapture extends media {
7332
7388
  if (this.streamBackpressured) this.resumeAfterDrain = true;
7333
7389
  else loop.start();
7334
7390
  }
7391
+ resumeMediaForCountdown() {
7392
+ this.userMedia?.resume();
7393
+ }
7335
7394
  onFlushed(opts) {
7336
7395
  const frameNumber = opts.frameNumber;
7337
7396
  if (1 === frameNumber) this.emit("FIRST_FRAME_SENT");
@@ -8471,6 +8530,7 @@ class Recorder extends capture {
8471
8530
  this.cancelCameraRecovery();
8472
8531
  this.blocking = true;
8473
8532
  this.clearUserMediaTimeout();
8533
+ this.closeIdleConnection("blocking-error");
8474
8534
  });
8475
8535
  this.on("PREVIEW", ()=>{
8476
8536
  this.hide();
@@ -9009,21 +9069,36 @@ class Visuals extends util_Despot {
9009
9069
  this.clearCameraRecoveryNotice();
9010
9070
  return this.recorder.recoverCamera();
9011
9071
  }
9012
- resume() {
9072
+ canResumeCountdown(restored) {
9073
+ return restored && Boolean(this.recorderInsides.isCountingDown());
9074
+ }
9075
+ async resumeCountdown() {
9076
+ const restored = await this.recoverCamera();
9077
+ if (!this.canResumeCountdown(restored)) return;
9078
+ this.recorder.resumeMediaForCountdown();
9079
+ this.recorderInsides.resumeCountdown();
9080
+ this.recorderInsides.hidePause();
9081
+ }
9082
+ async resume() {
9013
9083
  this.clearCameraRecoveryNotice();
9014
- if (this.recorderInsides.isCountingDown()) {
9015
- this.recorderInsides.resumeCountdown();
9016
- this.recorderInsides.hidePause();
9017
- } else {
9084
+ if (this.recorderInsides.isCountingDown()) await this.resumeCountdown();
9085
+ else {
9018
9086
  this.recorder.resume();
9019
9087
  if (!this.recorder.isPaused()) this.recorderInsides.hidePause();
9020
9088
  }
9021
9089
  }
9090
+ resumeFromControl() {
9091
+ this.resume().catch((exc)=>{
9092
+ this.emit("ERROR", {
9093
+ exc
9094
+ });
9095
+ });
9096
+ }
9022
9097
  pauseOrResume() {
9023
- if (this.cameraRecoveryFailed && this.recorder.isPaused()) this.resume();
9098
+ if (this.cameraRecoveryFailed && this.recorder.isPaused()) this.resumeFromControl();
9024
9099
  else if (this.isRecordable()) {
9025
9100
  if (this.isRecording()) this.pause();
9026
- else if (this.recorder.isPaused()) this.resume();
9101
+ else if (this.recorder.isPaused()) this.resumeFromControl();
9027
9102
  else if (this.recorder.isReady()) this.recorder.record();
9028
9103
  }
9029
9104
  }
@@ -9052,8 +9127,8 @@ class Visuals extends util_Despot {
9052
9127
  isPaused() {
9053
9128
  return this.recorder.isPaused();
9054
9129
  }
9055
- error(err) {
9056
- this.notifier.error(err);
9130
+ error(err, blocking = true) {
9131
+ this.notifier.error(err, blocking);
9057
9132
  }
9058
9133
  hide() {
9059
9134
  if (this.visualsElement) {
@@ -9162,6 +9237,7 @@ function container_define_property(obj, key, value) {
9162
9237
  else obj[key] = value;
9163
9238
  return obj;
9164
9239
  }
9240
+ const INTERACTIVE_SPACE_TARGETS = "button, input, select, textarea, a[href], summary, label, audio[controls], video[controls], [contenteditable]:not([contenteditable='false']), [role='button'], [role='link'], [role='checkbox'], [role='radio'], [role='switch'], [role='menuitem'], [role='tab'], [tabindex]:not([tabindex='-1'])";
9165
9241
  class Container extends util_Despot {
9166
9242
  buildChildren(playerOnly = false, parentElement) {
9167
9243
  const parentElementInfo = parentElement ? `, parentElement="${string_pretty(parentElement)}"` : "";
@@ -9233,9 +9309,9 @@ class Container extends util_Despot {
9233
9309
  if (params.exc.stack) this.options.logger.error(params.exc.stack);
9234
9310
  else if (params.exc.message) this.options.logger.error(params.exc.message);
9235
9311
  } else this.options.logger.error(params.exc);
9236
- if (this.options.displayErrors && params.err) this.visuals.error(params.err);
9237
- else if (this.options.displayErrors && params.exc && params.exc instanceof error_VideomailError) this.visuals.error(params.exc);
9238
- else this.visuals.reset();
9312
+ if (this.options.displayErrors && params.err) this.visuals.error(params.err, !params.recoverable);
9313
+ else if (this.options.displayErrors && params.exc && params.exc instanceof error_VideomailError) this.visuals.error(params.exc, !params.recoverable);
9314
+ else if (!params.recoverable) this.visuals.reset();
9239
9315
  }
9240
9316
  initEvents(playerOnly = false) {
9241
9317
  this.options.logger.debug(`Container: initEvents (playerOnly = ${playerOnly})`);
@@ -9253,7 +9329,7 @@ class Container extends util_Despot {
9253
9329
  }
9254
9330
  });
9255
9331
  if (this.options.enableSpace) {
9256
- if (!playerOnly) window.addEventListener("keydown", this.handleKeyDown);
9332
+ if (!playerOnly) this.containerElement?.addEventListener("keydown", this.handleKeyDown);
9257
9333
  }
9258
9334
  this.on("ERROR", (params)=>{
9259
9335
  this.processError(params);
@@ -9293,7 +9369,7 @@ class Container extends util_Despot {
9293
9369
  removeBrowserEventListeners() {
9294
9370
  window.removeEventListener("resize", this.handleWindowResize);
9295
9371
  window.removeEventListener("beforeunload", this.handleBeforeUnload);
9296
- window.removeEventListener("keydown", this.handleKeyDown);
9372
+ this.containerElement?.removeEventListener("keydown", this.handleKeyDown);
9297
9373
  this.stopListeningToVisibility?.();
9298
9374
  this.stopListeningToVisibility = void 0;
9299
9375
  }
@@ -9532,6 +9608,7 @@ class Container extends util_Despot {
9532
9608
  async submitAll(formData, method, url, hasHostAction = false) {
9533
9609
  let response;
9534
9610
  let hostResponse;
9611
+ let submissionFailed = false;
9535
9612
  const videomailKey = formData[this.options.selectors.keyInputName];
9536
9613
  try {
9537
9614
  const hasVideomailKey = Boolean(videomailKey);
@@ -9542,7 +9619,7 @@ class Container extends util_Despot {
9542
9619
  ].filter(Boolean).join(": ");
9543
9620
  this.options.logger.debug(`Container: submitAll(${output})`);
9544
9621
  this.beginWaiting();
9545
- if (this.options.disableFormWhenSubmitting) this.disableForm(true);
9622
+ if (this.options.disableFormWhenSubmitting) this.form?.disableForSubmission();
9546
9623
  this.emit("SUBMITTING");
9547
9624
  if (hasVideomailKey) {
9548
9625
  response = this.getPendingHostSubmissionResponse(videomailKey) ?? await this.submitVideomail(formData, method);
@@ -9572,15 +9649,18 @@ class Container extends util_Despot {
9572
9649
  });
9573
9650
  }
9574
9651
  } catch (exc) {
9652
+ submissionFailed = true;
9575
9653
  const err = error_createError({
9576
9654
  exc,
9577
9655
  options: this.options
9578
9656
  });
9579
9657
  this.emit("ERROR", {
9580
- err
9658
+ err,
9659
+ recoverable: true
9581
9660
  });
9582
9661
  } finally{
9583
9662
  const finalResponse = hostResponse ?? response;
9663
+ if (submissionFailed && this.options.disableFormWhenSubmitting) this.form?.restoreAfterFailedSubmission();
9584
9664
  if (finalResponse?.text && "text/html" === finalResponse.type) document.body.innerHTML = dompurify.sanitize(finalResponse.text);
9585
9665
  this.endWaiting();
9586
9666
  }
@@ -9634,7 +9714,11 @@ class Container extends util_Despot {
9634
9714
  this.visuals.record();
9635
9715
  }
9636
9716
  resume() {
9637
- this.visuals.resume();
9717
+ this.visuals.resume().catch((exc)=>{
9718
+ this.emit("ERROR", {
9719
+ exc
9720
+ });
9721
+ });
9638
9722
  }
9639
9723
  stop() {
9640
9724
  this.visuals.stop();
@@ -9654,14 +9738,16 @@ class Container extends util_Despot {
9654
9738
  e
9655
9739
  });
9656
9740
  }), container_define_property(this, "handleKeyDown", (e)=>{
9657
- const element = e.target;
9658
- const tagName = element.tagName;
9659
- const isEditable = element.isContentEditable || "true" === element.contentEditable;
9660
- if (!isEditable && tagName && "INPUT" !== tagName.toUpperCase() && "TEXTAREA" !== tagName.toUpperCase() && "Space" === e.code) {
9661
- e.preventDefault();
9662
- if (this.options.enablePause) this.visuals.pauseOrResume();
9663
- else this.visuals.recordOrStop();
9664
- }
9741
+ const containerElement = this.containerElement;
9742
+ if (!containerElement) return;
9743
+ const eventPath = e.composedPath();
9744
+ const targetContainerIndex = eventPath.findIndex((eventTarget)=>eventTarget instanceof Element && eventTarget.classList.contains(this.options.selectors.containerClass));
9745
+ const targetContainer = eventPath[targetContainerIndex];
9746
+ const isInteractiveTarget = eventPath.slice(0, targetContainerIndex + 1).some((eventTarget)=>eventTarget instanceof Element && eventTarget.matches(INTERACTIVE_SPACE_TARGETS));
9747
+ if (!eventPath.includes(containerElement) || targetContainer !== containerElement || isInteractiveTarget || e.defaultPrevented || e.repeat || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || "Space" !== e.code) return;
9748
+ e.preventDefault();
9749
+ if (this.options.enablePause) this.visuals.pauseOrResume();
9750
+ else this.visuals.recordOrStop();
9665
9751
  });
9666
9752
  this.visuals = new wrappers_visuals(this, options);
9667
9753
  this.buttons = new buttons(this, options);
@@ -14,6 +14,7 @@ export interface VideomailCameraRecoveryFailedParams {
14
14
  export interface VideomailErrorParams {
15
15
  exc?: unknown;
16
16
  err?: VideomailError;
17
+ recoverable?: boolean | undefined;
17
18
  }
18
19
  export interface VideomailRecordingParams {
19
20
  framesCount: number;
@@ -0,0 +1,5 @@
1
+ declare function getAutomationSignals(): {
2
+ webdriver: boolean;
3
+ userAgentMatched: boolean;
4
+ };
5
+ export default getAutomationSignals;
@@ -18,6 +18,7 @@ declare class Form extends Despot {
18
18
  private submitButtonFormAttribute;
19
19
  private generatedFormId;
20
20
  private isSubmitting;
21
+ private readonly submissionDisabledStates;
21
22
  private readonly FORM_FIELDS;
22
23
  constructor(container: Container, formElement: HTMLFormElement, options: VideomailClientOptions);
23
24
  private readonly validate;
@@ -406,6 +407,8 @@ declare class Form extends Despot {
406
407
  private getRegisteredFormElements;
407
408
  disable(buttonsToo: boolean): void;
408
409
  enable(buttonsToo: boolean): void;
410
+ disableForSubmission(): void;
411
+ restoreAfterFailedSubmission(): void;
409
412
  build(): void;
410
413
  private removeAllInputListeners;
411
414
  private hideSubmitButton;
@@ -31,7 +31,7 @@ declare class Notifier extends Despot {
31
31
  private show;
32
32
  private runEntertainment;
33
33
  private cancelEntertainment;
34
- error(err: VideomailError): void;
34
+ error(err: VideomailError, blocking?: boolean): void;
35
35
  private getMessageElement;
36
36
  private setMessage;
37
37
  private setExplanation;
@@ -66,6 +66,7 @@ declare abstract class RecorderCapture extends RecorderMedia {
66
66
  validate(): boolean;
67
67
  pause(params?: PauseParams): void;
68
68
  resume(): void;
69
+ resumeMediaForCountdown(): void;
69
70
  private onFlushed;
70
71
  private draw;
71
72
  private createLoop;
@@ -20,6 +20,7 @@ declare abstract class RecorderConnection extends RecorderStreamWriter {
20
20
  } | undefined;
21
21
  protected lastSocketError?: Record<string, unknown> | undefined;
22
22
  protected pingInterval?: number | undefined;
23
+ private connectionOpenedAt?;
23
24
  protected abstract executeCommand(command: VideomailCommand): void;
24
25
  abstract isUserMediaLoaded(): boolean | undefined;
25
26
  abstract validate(): boolean;
@@ -28,6 +29,7 @@ declare abstract class RecorderConnection extends RecorderStreamWriter {
28
29
  private shouldReportStreamClose;
29
30
  private handleStreamClose;
30
31
  private shouldIgnoreStreamClose;
32
+ protected closeIdleConnection(reason: string): void;
31
33
  private handleStreamError;
32
34
  protected sendPings(): void;
33
35
  protected stopPings(): void;
@@ -40,7 +40,10 @@ declare class Visuals extends Despot {
40
40
  }): void;
41
41
  private clearCameraRecoveryNotice;
42
42
  recoverCamera(): Promise<boolean>;
43
- resume(): void;
43
+ private canResumeCountdown;
44
+ private resumeCountdown;
45
+ resume(): Promise<void>;
46
+ private resumeFromControl;
44
47
  pauseOrResume(): void;
45
48
  recordOrStop(): void;
46
49
  getRecorder(): Recorder;
@@ -52,7 +55,7 @@ declare class Visuals extends Despot {
52
55
  } | undefined;
53
56
  getAudioSampleRate(): number | undefined;
54
57
  isPaused(): boolean | undefined;
55
- error(err: VideomailError): void;
58
+ error(err: VideomailError, blocking?: boolean): void;
56
59
  hide(): void;
57
60
  isHidden(): boolean;
58
61
  showVisuals(): void;