videomail-client 16.0.1 → 16.0.3

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/README.md CHANGED
@@ -260,25 +260,7 @@ You can also retrieve this data with `videomailClient.getByKey()`.
260
260
 
261
261
  ## Form Submissions
262
262
 
263
- With `enableAutoSubmission` enabled (the default), a recorded video and an explicit form `action` produce two requests: the client sends Videomail first, then POSTs the original form fields to that action after success. Without a recorded video, the client POSTs the form directly to its action.
264
-
265
- For first-party Videomail forms without an explicit `action`, a recorded video is sent in one request to the Videomail API; listen for `SUBMITTED` to handle the response. Set `enableAutoSubmission: false` when a host framework owns form submission, then let that integration resume its submission after `SUBMITTED`.
266
-
267
- If this does not work, verify that the configured selectors identify the form and its submit button:
268
-
269
- ```ts
270
- selectors: {
271
- formId: undefined,
272
- submitButtonId: undefined,
273
- submitButtonSelector: undefined,
274
- }
275
- ```
276
-
277
- When these values are `undefined` (the defaults), the client detects the nearest form and a button with `type="submit"` automatically.
278
-
279
- ### Include videomail meta data in Form Submissions
280
-
281
- When the client sends the second, host-form request, enable `submitWithVideomail` to include full videomail metadata under the `videomail` key. Otherwise the original form fields include the videomail alias, which can later be resolved with `videomailClient.getByAlias(alias)`.
263
+ How to configure, inject meta data and test form submissions with the Videomail client can be found in [Form Submissions ✏️](./docs/FORM_SUBMISSIONS.md).
282
264
 
283
265
  ## Privacy and error reporting
284
266
 
@@ -11115,7 +11115,7 @@ var __webpack_exports__ = {};
11115
11115
  var client = __webpack_require__("Q");
11116
11116
  var client_default = /*#__PURE__*/ __webpack_require__.n(client);
11117
11117
  var package_namespaceObject = {
11118
- rE: "16.0.1"
11118
+ rE: "16.0.3"
11119
11119
  };
11120
11120
  function isAudioEnabled(options) {
11121
11121
  return Boolean(options.audio.enabled);
@@ -15271,13 +15271,35 @@ var __webpack_exports__ = {};
15271
15271
  if ((null == type ? void 0 : type.toLowerCase()) === "hidden") inputElement.value = "";
15272
15272
  }
15273
15273
  }
15274
- startListeningToSubmitEvents() {
15274
+ connectSubmitButton() {
15275
15275
  const submitButton = this.container.getSubmitButton();
15276
- if (submitButton) submitButton.onclick = this.doTheSubmit.bind(this);
15276
+ if (!submitButton) return;
15277
+ if (submitButton.form === this.formElement) return;
15278
+ if (!this.formElement.id) {
15279
+ let formId;
15280
+ do formId = `videomail-form-${Form.nextFormId++}`;
15281
+ while (document.getElementById(formId))
15282
+ this.formElement.id = formId;
15283
+ this.generatedFormId = formId;
15284
+ }
15285
+ this.submitButton = submitButton;
15286
+ this.submitButtonFormAttribute = submitButton.getAttribute("form");
15287
+ submitButton.setAttribute("form", this.formElement.id);
15288
+ }
15289
+ startListeningToSubmitEvents() {
15290
+ this.connectSubmitButton();
15291
+ this.formElement.addEventListener("submit", this.handleSubmit);
15277
15292
  }
15278
15293
  stopListeningToSubmitEvents() {
15279
- const submitButton = this.container.getSubmitButton();
15280
- if (submitButton) submitButton.onclick = null;
15294
+ this.formElement.removeEventListener("submit", this.handleSubmit);
15295
+ if (this.submitButton) {
15296
+ if (null === this.submitButtonFormAttribute) this.submitButton.removeAttribute("form");
15297
+ else this.submitButton.setAttribute("form", this.submitButtonFormAttribute);
15298
+ this.submitButton = void 0;
15299
+ this.submitButtonFormAttribute = null;
15300
+ }
15301
+ if (this.generatedFormId === this.formElement.id) this.formElement.removeAttribute("id");
15302
+ this.generatedFormId = void 0;
15281
15303
  }
15282
15304
  doTheSubmit(e) {
15283
15305
  return _async_to_generator(function*() {
@@ -15286,6 +15308,7 @@ var __webpack_exports__ = {};
15286
15308
  this.options.logger.debug(`Form: doTheSubmit(${string_pretty(e)})`);
15287
15309
  e.preventDefault();
15288
15310
  } else this.options.logger.debug("Form: doTheSubmit()");
15311
+ if (this.isSubmitting) return false;
15289
15312
  const hasHostAction = this.formElement.hasAttribute("action");
15290
15313
  const url = null != (_this_formElement_getAttribute = this.formElement.getAttribute("action")) ? _this_formElement_getAttribute : this.options.apiUrl;
15291
15314
  const method = this.formElement.getAttribute("method");
@@ -15300,7 +15323,14 @@ var __webpack_exports__ = {};
15300
15323
  default:
15301
15324
  chosenMethod = FormMethod.POST;
15302
15325
  }
15303
- if (this.container.hasElement()) yield this.container.submitAll(this.getData(), chosenMethod, url, hasHostAction);
15326
+ if (this.container.hasElement()) {
15327
+ this.isSubmitting = true;
15328
+ try {
15329
+ yield this.container.submitAll(this.getData(), chosenMethod, url, hasHostAction);
15330
+ } finally{
15331
+ this.isSubmitting = false;
15332
+ }
15333
+ }
15304
15334
  return false;
15305
15335
  }).call(this);
15306
15336
  }
@@ -15322,9 +15352,11 @@ var __webpack_exports__ = {};
15322
15352
  html_showElement(this.formElement);
15323
15353
  }
15324
15354
  constructor(container, formElement, options){
15325
- super("Form", options), form_define_property(this, "container", void 0), form_define_property(this, "formElement", void 0), form_define_property(this, "validate", (event)=>{
15355
+ 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)=>{
15326
15356
  this.container.validate(event);
15327
- }), form_define_property(this, "keyInput", void 0), form_define_property(this, "FORM_FIELDS", {});
15357
+ }), form_define_property(this, "handleSubmit", (event)=>{
15358
+ if (!event.defaultPrevented) this.doTheSubmit(event);
15359
+ });
15328
15360
  this.container = container;
15329
15361
  this.formElement = formElement;
15330
15362
  this.FORM_FIELDS = {
@@ -15340,6 +15372,7 @@ var __webpack_exports__ = {};
15340
15372
  };
15341
15373
  }
15342
15374
  }
15375
+ form_define_property(Form, "nextFormId", 0);
15343
15376
  const wrappers_form = Form;
15344
15377
  function resource_asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
15345
15378
  try {
@@ -18025,7 +18058,7 @@ var __webpack_exports__ = {};
18025
18058
  html_disableElement(radioButtonElement);
18026
18059
  return radioButtonElement;
18027
18060
  }
18028
- makeButton(buttonClass, text, clickHandler, show, id, type, selector, disabled = true) {
18061
+ makeButton(buttonClass, text, clickHandler, show, id, type = "button", selector, disabled = true) {
18029
18062
  let buttonElement;
18030
18063
  if (id) buttonElement = document.querySelector(`#${id}`);
18031
18064
  else if (selector) buttonElement = document.querySelector(selector);
@@ -20096,6 +20129,7 @@ var __webpack_exports__ = {};
20096
20129
  this.options.logger.debug(debugLine);
20097
20130
  const canReconnect = this.canReconnectAfterStreamClose();
20098
20131
  this.connected = false;
20132
+ this.stopPings();
20099
20133
  if (this.shouldIgnoreStreamClose()) return;
20100
20134
  if (this.mediaTransmissionStarted) this.failInterruptedRecording();
20101
20135
  else if (canReconnect) {
@@ -20112,7 +20146,38 @@ var __webpack_exports__ = {};
20112
20146
  }, 0);
20113
20147
  }
20114
20148
  shouldIgnoreStreamClose() {
20115
- return this.recordingTransportFailed || Boolean(this.unloaded);
20149
+ return this.recordingTransportFailed || Boolean(this.unloaded) || this.validate();
20150
+ }
20151
+ handleStreamError(url2Connect, err) {
20152
+ var _this_lastSocketError;
20153
+ if ("u" > typeof Event && err instanceof Event) this.lastSocketError = error_getEventDetails(err);
20154
+ else this.lastSocketError = serializeError(err);
20155
+ const debugLine = string_summarize(`${PIPE_SYMBOL}Stream *error* event emitted:`, {
20156
+ error: serializeError(err)
20157
+ });
20158
+ this.options.logger.debug(debugLine);
20159
+ if (this.validate()) {
20160
+ this.connected = false;
20161
+ this.stopPings();
20162
+ this.discardSocket();
20163
+ return;
20164
+ }
20165
+ if (!this.connected) return void this.handleConnectionFailure({
20166
+ url2Connect,
20167
+ cause: "error"
20168
+ });
20169
+ const streamError = err instanceof Error ? err : new Error("WebSocket stream emitted an error event", {
20170
+ cause: null != (_this_lastSocketError = this.lastSocketError) ? _this_lastSocketError : err
20171
+ });
20172
+ if (this.mediaTransmissionStarted) return void this.failInterruptedRecording();
20173
+ this.emit("ERROR", {
20174
+ err: error_createError({
20175
+ message: "WebSocket stream error",
20176
+ explanation: `The WebSocket stream emitted an error event. Details: ${string_pretty(err)}`,
20177
+ options: this.options,
20178
+ exc: streamError
20179
+ })
20180
+ });
20116
20181
  }
20117
20182
  sendPings() {
20118
20183
  this.stopPings();
@@ -20123,6 +20188,7 @@ var __webpack_exports__ = {};
20123
20188
  }
20124
20189
  stopPings() {
20125
20190
  if (void 0 === this.pingInterval) return;
20191
+ this.options.logger.debug("Recorder: stopping pings...");
20126
20192
  window.clearInterval(this.pingInterval);
20127
20193
  this.pingInterval = void 0;
20128
20194
  }
@@ -20275,7 +20341,7 @@ var __webpack_exports__ = {};
20275
20341
  });
20276
20342
  }
20277
20343
  shouldIgnoreConnectionFailure() {
20278
- return this.connectionFailed || this.connected || Boolean(this.unloaded);
20344
+ return this.connectionFailed || this.connected || Boolean(this.unloaded) || this.validate();
20279
20345
  }
20280
20346
  calculateFrameProgress() {
20281
20347
  return `${(this.confirmedFrameNumber / (this.framesCount || 1) * 100).toFixed(2)}%`;
@@ -20483,29 +20549,7 @@ var __webpack_exports__ = {};
20483
20549
  }
20484
20550
  });
20485
20551
  stream.on("error", (err)=>{
20486
- var _this_lastSocketError;
20487
- if ("u" > typeof Event && err instanceof Event) this.lastSocketError = error_getEventDetails(err);
20488
- else this.lastSocketError = serializeError(err);
20489
- const debugLine = string_summarize(`${PIPE_SYMBOL}Stream *error* event emitted:`, {
20490
- error: serializeError(err)
20491
- });
20492
- this.options.logger.debug(debugLine);
20493
- if (!this.connected) return void this.handleConnectionFailure({
20494
- url2Connect,
20495
- cause: "error"
20496
- });
20497
- const streamError = err instanceof Error ? err : new Error("WebSocket stream emitted an error event", {
20498
- cause: null != (_this_lastSocketError = this.lastSocketError) ? _this_lastSocketError : err
20499
- });
20500
- if (this.mediaTransmissionStarted) return void this.failInterruptedRecording();
20501
- this.emit("ERROR", {
20502
- err: error_createError({
20503
- message: "WebSocket stream error",
20504
- explanation: `The WebSocket stream emitted an error event. Details: ${string_pretty(err)}`,
20505
- options: this.options,
20506
- exc: streamError
20507
- })
20508
- });
20552
+ this.handleStreamError(url2Connect, err);
20509
20553
  });
20510
20554
  stream.on("drain", ()=>{
20511
20555
  this.handleStreamDrain(stream);
@@ -20853,6 +20897,7 @@ var __webpack_exports__ = {};
20853
20897
  }
20854
20898
  back(cb) {
20855
20899
  this.emit("GOING_BACK");
20900
+ this.key = void 0;
20856
20901
  this.recordingDimensions = void 0;
20857
20902
  this.unloaded = false;
20858
20903
  this.show();
@@ -20919,7 +20964,7 @@ var __webpack_exports__ = {};
20919
20964
  this.stopTimeout = void 0;
20920
20965
  }
20921
20966
  validate() {
20922
- return this.connected && void 0 === this.canvas;
20967
+ return Boolean(this.key) && void 0 === this.canvas;
20923
20968
  }
20924
20969
  isReady() {
20925
20970
  var _this_userMedia;
package/dist/esm/index.js CHANGED
@@ -3620,7 +3620,7 @@ const constants = {
3620
3620
  }
3621
3621
  };
3622
3622
  var package_namespaceObject = {
3623
- rE: "16.0.1"
3623
+ rE: "16.0.3"
3624
3624
  };
3625
3625
  function isAudioEnabled(options) {
3626
3626
  return Boolean(options.audio.enabled);
@@ -4368,19 +4368,42 @@ class Form extends util_Despot {
4368
4368
  if (type?.toLowerCase() === "hidden") inputElement.value = "";
4369
4369
  }
4370
4370
  }
4371
- startListeningToSubmitEvents() {
4371
+ connectSubmitButton() {
4372
4372
  const submitButton = this.container.getSubmitButton();
4373
- if (submitButton) submitButton.onclick = this.doTheSubmit.bind(this);
4373
+ if (!submitButton) return;
4374
+ if (submitButton.form === this.formElement) return;
4375
+ if (!this.formElement.id) {
4376
+ let formId;
4377
+ do formId = `videomail-form-${Form.nextFormId++}`;
4378
+ while (document.getElementById(formId))
4379
+ this.formElement.id = formId;
4380
+ this.generatedFormId = formId;
4381
+ }
4382
+ this.submitButton = submitButton;
4383
+ this.submitButtonFormAttribute = submitButton.getAttribute("form");
4384
+ submitButton.setAttribute("form", this.formElement.id);
4385
+ }
4386
+ startListeningToSubmitEvents() {
4387
+ this.connectSubmitButton();
4388
+ this.formElement.addEventListener("submit", this.handleSubmit);
4374
4389
  }
4375
4390
  stopListeningToSubmitEvents() {
4376
- const submitButton = this.container.getSubmitButton();
4377
- if (submitButton) submitButton.onclick = null;
4391
+ this.formElement.removeEventListener("submit", this.handleSubmit);
4392
+ if (this.submitButton) {
4393
+ if (null === this.submitButtonFormAttribute) this.submitButton.removeAttribute("form");
4394
+ else this.submitButton.setAttribute("form", this.submitButtonFormAttribute);
4395
+ this.submitButton = void 0;
4396
+ this.submitButtonFormAttribute = null;
4397
+ }
4398
+ if (this.generatedFormId === this.formElement.id) this.formElement.removeAttribute("id");
4399
+ this.generatedFormId = void 0;
4378
4400
  }
4379
4401
  async doTheSubmit(e) {
4380
4402
  if (e) {
4381
4403
  this.options.logger.debug(`Form: doTheSubmit(${string_pretty(e)})`);
4382
4404
  e.preventDefault();
4383
4405
  } else this.options.logger.debug("Form: doTheSubmit()");
4406
+ if (this.isSubmitting) return false;
4384
4407
  const hasHostAction = this.formElement.hasAttribute("action");
4385
4408
  const url = this.formElement.getAttribute("action") ?? this.options.apiUrl;
4386
4409
  const method = this.formElement.getAttribute("method");
@@ -4395,7 +4418,14 @@ class Form extends util_Despot {
4395
4418
  default:
4396
4419
  chosenMethod = FormMethod.POST;
4397
4420
  }
4398
- if (this.container.hasElement()) await this.container.submitAll(this.getData(), chosenMethod, url, hasHostAction);
4421
+ if (this.container.hasElement()) {
4422
+ this.isSubmitting = true;
4423
+ try {
4424
+ await this.container.submitAll(this.getData(), chosenMethod, url, hasHostAction);
4425
+ } finally{
4426
+ this.isSubmitting = false;
4427
+ }
4428
+ }
4399
4429
  return false;
4400
4430
  }
4401
4431
  getInvalidElement() {
@@ -4416,9 +4446,11 @@ class Form extends util_Despot {
4416
4446
  html_showElement(this.formElement);
4417
4447
  }
4418
4448
  constructor(container, formElement, options){
4419
- super("Form", options), form_define_property(this, "container", void 0), form_define_property(this, "formElement", void 0), form_define_property(this, "validate", (event)=>{
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)=>{
4420
4450
  this.container.validate(event);
4421
- }), form_define_property(this, "keyInput", void 0), form_define_property(this, "FORM_FIELDS", {});
4451
+ }), form_define_property(this, "handleSubmit", (event)=>{
4452
+ if (!event.defaultPrevented) this.doTheSubmit(event);
4453
+ });
4422
4454
  this.container = container;
4423
4455
  this.formElement = formElement;
4424
4456
  this.FORM_FIELDS = {
@@ -4434,6 +4466,7 @@ class Form extends util_Despot {
4434
4466
  };
4435
4467
  }
4436
4468
  }
4469
+ form_define_property(Form, "nextFormId", 0);
4437
4470
  const wrappers_form = Form;
4438
4471
  function resource_define_property(obj, key, value) {
4439
4472
  if (key in obj) Object.defineProperty(obj, key, {
@@ -4970,7 +5003,7 @@ class Buttons extends util_Despot {
4970
5003
  html_disableElement(radioButtonElement);
4971
5004
  return radioButtonElement;
4972
5005
  }
4973
- makeButton(buttonClass, text, clickHandler, show, id, type, selector, disabled = true) {
5006
+ makeButton(buttonClass, text, clickHandler, show, id, type = "button", selector, disabled = true) {
4974
5007
  let buttonElement;
4975
5008
  buttonElement = id ? document.querySelector(`#${id}`) : selector ? document.querySelector(selector) : this.buttonsElement?.querySelector(`.${buttonClass}`);
4976
5009
  if (buttonElement) buttonElement = html_adjustButton(buttonElement, show, type, disabled);
@@ -6875,6 +6908,7 @@ class Recorder extends util_Despot {
6875
6908
  this.options.logger.debug(debugLine);
6876
6909
  const canReconnect = this.canReconnectAfterStreamClose();
6877
6910
  this.connected = false;
6911
+ this.stopPings();
6878
6912
  if (this.shouldIgnoreStreamClose()) return;
6879
6913
  if (this.mediaTransmissionStarted) this.failInterruptedRecording();
6880
6914
  else if (canReconnect) {
@@ -6891,7 +6925,37 @@ class Recorder extends util_Despot {
6891
6925
  }, 0);
6892
6926
  }
6893
6927
  shouldIgnoreStreamClose() {
6894
- return this.recordingTransportFailed || Boolean(this.unloaded);
6928
+ return this.recordingTransportFailed || Boolean(this.unloaded) || this.validate();
6929
+ }
6930
+ handleStreamError(url2Connect, err) {
6931
+ if ("u" > typeof Event && err instanceof Event) this.lastSocketError = error_getEventDetails(err);
6932
+ else this.lastSocketError = serializeError(err);
6933
+ const debugLine = string_summarize(`${PIPE_SYMBOL}Stream *error* event emitted:`, {
6934
+ error: serializeError(err)
6935
+ });
6936
+ this.options.logger.debug(debugLine);
6937
+ if (this.validate()) {
6938
+ this.connected = false;
6939
+ this.stopPings();
6940
+ this.discardSocket();
6941
+ return;
6942
+ }
6943
+ if (!this.connected) return void this.handleConnectionFailure({
6944
+ url2Connect,
6945
+ cause: "error"
6946
+ });
6947
+ const streamError = err instanceof Error ? err : new Error("WebSocket stream emitted an error event", {
6948
+ cause: this.lastSocketError ?? err
6949
+ });
6950
+ if (this.mediaTransmissionStarted) return void this.failInterruptedRecording();
6951
+ this.emit("ERROR", {
6952
+ err: error_createError({
6953
+ message: "WebSocket stream error",
6954
+ explanation: `The WebSocket stream emitted an error event. Details: ${string_pretty(err)}`,
6955
+ options: this.options,
6956
+ exc: streamError
6957
+ })
6958
+ });
6895
6959
  }
6896
6960
  sendPings() {
6897
6961
  this.stopPings();
@@ -6902,6 +6966,7 @@ class Recorder extends util_Despot {
6902
6966
  }
6903
6967
  stopPings() {
6904
6968
  if (void 0 === this.pingInterval) return;
6969
+ this.options.logger.debug("Recorder: stopping pings...");
6905
6970
  window.clearInterval(this.pingInterval);
6906
6971
  this.pingInterval = void 0;
6907
6972
  }
@@ -7053,7 +7118,7 @@ class Recorder extends util_Despot {
7053
7118
  });
7054
7119
  }
7055
7120
  shouldIgnoreConnectionFailure() {
7056
- return this.connectionFailed || this.connected || Boolean(this.unloaded);
7121
+ return this.connectionFailed || this.connected || Boolean(this.unloaded) || this.validate();
7057
7122
  }
7058
7123
  calculateFrameProgress() {
7059
7124
  return `${(this.confirmedFrameNumber / (this.framesCount || 1) * 100).toFixed(2)}%`;
@@ -7259,28 +7324,7 @@ class Recorder extends util_Despot {
7259
7324
  }
7260
7325
  });
7261
7326
  stream.on("error", (err)=>{
7262
- if ("u" > typeof Event && err instanceof Event) this.lastSocketError = error_getEventDetails(err);
7263
- else this.lastSocketError = serializeError(err);
7264
- const debugLine = string_summarize(`${PIPE_SYMBOL}Stream *error* event emitted:`, {
7265
- error: serializeError(err)
7266
- });
7267
- this.options.logger.debug(debugLine);
7268
- if (!this.connected) return void this.handleConnectionFailure({
7269
- url2Connect,
7270
- cause: "error"
7271
- });
7272
- const streamError = err instanceof Error ? err : new Error("WebSocket stream emitted an error event", {
7273
- cause: this.lastSocketError ?? err
7274
- });
7275
- if (this.mediaTransmissionStarted) return void this.failInterruptedRecording();
7276
- this.emit("ERROR", {
7277
- err: error_createError({
7278
- message: "WebSocket stream error",
7279
- explanation: `The WebSocket stream emitted an error event. Details: ${string_pretty(err)}`,
7280
- options: this.options,
7281
- exc: streamError
7282
- })
7283
- });
7327
+ this.handleStreamError(url2Connect, err);
7284
7328
  });
7285
7329
  stream.on("drain", ()=>{
7286
7330
  this.handleStreamDrain(stream);
@@ -7621,6 +7665,7 @@ class Recorder extends util_Despot {
7621
7665
  }
7622
7666
  back(cb) {
7623
7667
  this.emit("GOING_BACK");
7668
+ this.key = void 0;
7624
7669
  this.recordingDimensions = void 0;
7625
7670
  this.unloaded = false;
7626
7671
  this.show();
@@ -7685,7 +7730,7 @@ class Recorder extends util_Despot {
7685
7730
  this.stopTimeout = void 0;
7686
7731
  }
7687
7732
  validate() {
7688
- return this.connected && void 0 === this.canvas;
7733
+ return Boolean(this.key) && void 0 === this.canvas;
7689
7734
  }
7690
7735
  isReady() {
7691
7736
  return this.userMedia?.isReady();
@@ -10,12 +10,18 @@ export declare const FormMethod: {
10
10
  };
11
11
  export type FormMethodType = (typeof FormMethod)[keyof typeof FormMethod];
12
12
  declare class Form extends Despot {
13
+ private static nextFormId;
13
14
  private readonly container;
14
15
  private readonly formElement;
15
- private readonly validate;
16
16
  private keyInput?;
17
+ private submitButton;
18
+ private submitButtonFormAttribute;
19
+ private generatedFormId;
20
+ private isSubmitting;
17
21
  private readonly FORM_FIELDS;
18
22
  constructor(container: Container, formElement: HTMLFormElement, options: VideomailClientOptions);
23
+ private readonly validate;
24
+ private readonly handleSubmit;
19
25
  private getData;
20
26
  transformFormData(formInputs: FormInputs): {
21
27
  url?: string;
@@ -405,9 +411,10 @@ declare class Form extends Despot {
405
411
  private hideSubmitButton;
406
412
  unload(): void;
407
413
  private resetForm;
414
+ private connectSubmitButton;
408
415
  private startListeningToSubmitEvents;
409
416
  private stopListeningToSubmitEvents;
410
- doTheSubmit(e?: any): Promise<boolean>;
417
+ doTheSubmit(e?: Event): Promise<boolean>;
411
418
  getInvalidElement(): Element | null;
412
419
  findSubmitButton(): HTMLButtonElement | null;
413
420
  hide(): void;
@@ -83,6 +83,7 @@ declare class Recorder extends Despot {
83
83
  private shouldReportStreamClose;
84
84
  private handleStreamClose;
85
85
  private shouldIgnoreStreamClose;
86
+ private handleStreamError;
86
87
  private sendPings;
87
88
  private stopPings;
88
89
  private onAudioSample;
package/dist/umd/index.js CHANGED
@@ -11121,7 +11121,7 @@
11121
11121
  var client = __webpack_require__("Q");
11122
11122
  var client_default = /*#__PURE__*/ __webpack_require__.n(client);
11123
11123
  var package_namespaceObject = {
11124
- rE: "16.0.1"
11124
+ rE: "16.0.3"
11125
11125
  };
11126
11126
  function isAudioEnabled(options) {
11127
11127
  return Boolean(options.audio.enabled);
@@ -15029,12 +15029,14 @@
15029
15029
  GET: "get"
15030
15030
  };
15031
15031
  class Form extends util_Despot {
15032
+ static nextFormId = 0;
15032
15033
  container;
15033
15034
  formElement;
15034
- validate = (event)=>{
15035
- this.container.validate(event);
15036
- };
15037
15035
  keyInput;
15036
+ submitButton;
15037
+ submitButtonFormAttribute = null;
15038
+ generatedFormId;
15039
+ isSubmitting = false;
15038
15040
  FORM_FIELDS = {};
15039
15041
  constructor(container, formElement, options){
15040
15042
  super("Form", options);
@@ -15052,6 +15054,12 @@
15052
15054
  sendCopy: options.selectors.sendCopyInputName
15053
15055
  };
15054
15056
  }
15057
+ validate = (event)=>{
15058
+ this.container.validate(event);
15059
+ };
15060
+ handleSubmit = (event)=>{
15061
+ if (!event.defaultPrevented) this.doTheSubmit(event);
15062
+ };
15055
15063
  getData() {
15056
15064
  return getFormData(this.formElement, {
15057
15065
  includeDisabled: true
@@ -15235,19 +15243,42 @@
15235
15243
  if (type?.toLowerCase() === "hidden") inputElement.value = "";
15236
15244
  }
15237
15245
  }
15238
- startListeningToSubmitEvents() {
15246
+ connectSubmitButton() {
15239
15247
  const submitButton = this.container.getSubmitButton();
15240
- if (submitButton) submitButton.onclick = this.doTheSubmit.bind(this);
15248
+ if (!submitButton) return;
15249
+ if (submitButton.form === this.formElement) return;
15250
+ if (!this.formElement.id) {
15251
+ let formId;
15252
+ do formId = `videomail-form-${Form.nextFormId++}`;
15253
+ while (document.getElementById(formId))
15254
+ this.formElement.id = formId;
15255
+ this.generatedFormId = formId;
15256
+ }
15257
+ this.submitButton = submitButton;
15258
+ this.submitButtonFormAttribute = submitButton.getAttribute("form");
15259
+ submitButton.setAttribute("form", this.formElement.id);
15260
+ }
15261
+ startListeningToSubmitEvents() {
15262
+ this.connectSubmitButton();
15263
+ this.formElement.addEventListener("submit", this.handleSubmit);
15241
15264
  }
15242
15265
  stopListeningToSubmitEvents() {
15243
- const submitButton = this.container.getSubmitButton();
15244
- if (submitButton) submitButton.onclick = null;
15266
+ this.formElement.removeEventListener("submit", this.handleSubmit);
15267
+ if (this.submitButton) {
15268
+ if (null === this.submitButtonFormAttribute) this.submitButton.removeAttribute("form");
15269
+ else this.submitButton.setAttribute("form", this.submitButtonFormAttribute);
15270
+ this.submitButton = void 0;
15271
+ this.submitButtonFormAttribute = null;
15272
+ }
15273
+ if (this.generatedFormId === this.formElement.id) this.formElement.removeAttribute("id");
15274
+ this.generatedFormId = void 0;
15245
15275
  }
15246
15276
  async doTheSubmit(e) {
15247
15277
  if (e) {
15248
15278
  this.options.logger.debug(`Form: doTheSubmit(${string_pretty(e)})`);
15249
15279
  e.preventDefault();
15250
15280
  } else this.options.logger.debug("Form: doTheSubmit()");
15281
+ if (this.isSubmitting) return false;
15251
15282
  const hasHostAction = this.formElement.hasAttribute("action");
15252
15283
  const url = this.formElement.getAttribute("action") ?? this.options.apiUrl;
15253
15284
  const method = this.formElement.getAttribute("method");
@@ -15262,7 +15293,14 @@
15262
15293
  default:
15263
15294
  chosenMethod = FormMethod.POST;
15264
15295
  }
15265
- if (this.container.hasElement()) await this.container.submitAll(this.getData(), chosenMethod, url, hasHostAction);
15296
+ if (this.container.hasElement()) {
15297
+ this.isSubmitting = true;
15298
+ try {
15299
+ await this.container.submitAll(this.getData(), chosenMethod, url, hasHostAction);
15300
+ } finally{
15301
+ this.isSubmitting = false;
15302
+ }
15303
+ }
15266
15304
  return false;
15267
15305
  }
15268
15306
  getInvalidElement() {
@@ -17888,7 +17926,7 @@
17888
17926
  html_disableElement(radioButtonElement);
17889
17927
  return radioButtonElement;
17890
17928
  }
17891
- makeButton(buttonClass, text, clickHandler, show, id, type, selector, disabled = true) {
17929
+ makeButton(buttonClass, text, clickHandler, show, id, type = "button", selector, disabled = true) {
17892
17930
  let buttonElement;
17893
17931
  buttonElement = id ? document.querySelector(`#${id}`) : selector ? document.querySelector(selector) : this.buttonsElement?.querySelector(`.${buttonClass}`);
17894
17932
  if (buttonElement) buttonElement = html_adjustButton(buttonElement, show, type, disabled);
@@ -19852,6 +19890,7 @@
19852
19890
  this.options.logger.debug(debugLine);
19853
19891
  const canReconnect = this.canReconnectAfterStreamClose();
19854
19892
  this.connected = false;
19893
+ this.stopPings();
19855
19894
  if (this.shouldIgnoreStreamClose()) return;
19856
19895
  if (this.mediaTransmissionStarted) this.failInterruptedRecording();
19857
19896
  else if (canReconnect) {
@@ -19868,7 +19907,37 @@
19868
19907
  }, 0);
19869
19908
  }
19870
19909
  shouldIgnoreStreamClose() {
19871
- return this.recordingTransportFailed || Boolean(this.unloaded);
19910
+ return this.recordingTransportFailed || Boolean(this.unloaded) || this.validate();
19911
+ }
19912
+ handleStreamError(url2Connect, err) {
19913
+ if ("u" > typeof Event && err instanceof Event) this.lastSocketError = error_getEventDetails(err);
19914
+ else this.lastSocketError = serializeError(err);
19915
+ const debugLine = string_summarize(`${PIPE_SYMBOL}Stream *error* event emitted:`, {
19916
+ error: serializeError(err)
19917
+ });
19918
+ this.options.logger.debug(debugLine);
19919
+ if (this.validate()) {
19920
+ this.connected = false;
19921
+ this.stopPings();
19922
+ this.discardSocket();
19923
+ return;
19924
+ }
19925
+ if (!this.connected) return void this.handleConnectionFailure({
19926
+ url2Connect,
19927
+ cause: "error"
19928
+ });
19929
+ const streamError = err instanceof Error ? err : new Error("WebSocket stream emitted an error event", {
19930
+ cause: this.lastSocketError ?? err
19931
+ });
19932
+ if (this.mediaTransmissionStarted) return void this.failInterruptedRecording();
19933
+ this.emit("ERROR", {
19934
+ err: error_createError({
19935
+ message: "WebSocket stream error",
19936
+ explanation: `The WebSocket stream emitted an error event. Details: ${string_pretty(err)}`,
19937
+ options: this.options,
19938
+ exc: streamError
19939
+ })
19940
+ });
19872
19941
  }
19873
19942
  sendPings() {
19874
19943
  this.stopPings();
@@ -19879,6 +19948,7 @@
19879
19948
  }
19880
19949
  stopPings() {
19881
19950
  if (void 0 === this.pingInterval) return;
19951
+ this.options.logger.debug("Recorder: stopping pings...");
19882
19952
  window.clearInterval(this.pingInterval);
19883
19953
  this.pingInterval = void 0;
19884
19954
  }
@@ -20030,7 +20100,7 @@
20030
20100
  });
20031
20101
  }
20032
20102
  shouldIgnoreConnectionFailure() {
20033
- return this.connectionFailed || this.connected || Boolean(this.unloaded);
20103
+ return this.connectionFailed || this.connected || Boolean(this.unloaded) || this.validate();
20034
20104
  }
20035
20105
  calculateFrameProgress() {
20036
20106
  return `${(this.confirmedFrameNumber / (this.framesCount || 1) * 100).toFixed(2)}%`;
@@ -20236,28 +20306,7 @@
20236
20306
  }
20237
20307
  });
20238
20308
  stream.on("error", (err)=>{
20239
- if ("u" > typeof Event && err instanceof Event) this.lastSocketError = error_getEventDetails(err);
20240
- else this.lastSocketError = serializeError(err);
20241
- const debugLine = string_summarize(`${PIPE_SYMBOL}Stream *error* event emitted:`, {
20242
- error: serializeError(err)
20243
- });
20244
- this.options.logger.debug(debugLine);
20245
- if (!this.connected) return void this.handleConnectionFailure({
20246
- url2Connect,
20247
- cause: "error"
20248
- });
20249
- const streamError = err instanceof Error ? err : new Error("WebSocket stream emitted an error event", {
20250
- cause: this.lastSocketError ?? err
20251
- });
20252
- if (this.mediaTransmissionStarted) return void this.failInterruptedRecording();
20253
- this.emit("ERROR", {
20254
- err: error_createError({
20255
- message: "WebSocket stream error",
20256
- explanation: `The WebSocket stream emitted an error event. Details: ${string_pretty(err)}`,
20257
- options: this.options,
20258
- exc: streamError
20259
- })
20260
- });
20309
+ this.handleStreamError(url2Connect, err);
20261
20310
  });
20262
20311
  stream.on("drain", ()=>{
20263
20312
  this.handleStreamDrain(stream);
@@ -20598,6 +20647,7 @@
20598
20647
  }
20599
20648
  back(cb) {
20600
20649
  this.emit("GOING_BACK");
20650
+ this.key = void 0;
20601
20651
  this.recordingDimensions = void 0;
20602
20652
  this.unloaded = false;
20603
20653
  this.show();
@@ -20662,7 +20712,7 @@
20662
20712
  this.stopTimeout = void 0;
20663
20713
  }
20664
20714
  validate() {
20665
- return this.connected && void 0 === this.canvas;
20715
+ return Boolean(this.key) && void 0 === this.canvas;
20666
20716
  }
20667
20717
  isReady() {
20668
20718
  return this.userMedia?.isReady();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "videomail-client",
3
- "version": "16.0.1",
3
+ "version": "16.0.3",
4
4
  "description": "A wicked npm package to record videos directly in the browser, for Deaf and Sign Language!",
5
5
  "keywords": [
6
6
  "webcam",
@@ -53,7 +53,7 @@
53
53
  "prettier:fix": "prettier --write ./etc ./src ./.storybook *.ts *.js",
54
54
  "quality": "npm run test && npm run prettier && npm run lint && npm run types && npm run audit",
55
55
  "release": "release-it --only-version --config ./etc/release-it.ts",
56
- "storybook": "cross-env BROWSER=chromium storybook dev -p 8443 --https --ssl-cert ./etc/ssl-certs/localhost.crt --ssl-key ./etc/ssl-certs/localhost.key",
56
+ "storybook": "cross-env BROWSER=chromium storybook dev -p 8443 -h localhost --https --ssl-cert ./etc/ssl-certs/localhost.crt --ssl-key ./etc/ssl-certs/localhost.key",
57
57
  "test": "cross-env ENVIRON=test vitest --watch=false",
58
58
  "types": "tsc --noEmit",
59
59
  "types:config": "tsc --showConfig"
@@ -113,7 +113,7 @@
113
113
  "eslint-plugin-simple-import-sort": "14.0.0",
114
114
  "globals": "17.13.0",
115
115
  "jsdom": "30.1.3",
116
- "msw": "2.15.0",
116
+ "msw": "3.0.3",
117
117
  "msw-storybook-addon": "3.0.3",
118
118
  "prettier": "3.9.10",
119
119
  "prettier-plugin-curly": "0.4.1",