scandoc-ai-components 0.1.17 → 0.1.18

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 (2) hide show
  1. package/dist/index.js +176 -36
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11170,6 +11170,7 @@ __webpack_require__.r(__webpack_exports__);
11170
11170
 
11171
11171
  const DOCUMENT_DETECTOR = new _ai_detect_document__WEBPACK_IMPORTED_MODULE_0__["default"]();
11172
11172
  class ExtractorVideo {
11173
+ static DEBUG = true;
11173
11174
  static FREQUENCY_MS = 10;
11174
11175
  static VALIDATION_BATCH_SIZE = 1;
11175
11176
  static VALIDATION_IMG_WIDTH = 384;
@@ -11197,8 +11198,48 @@ class ExtractorVideo {
11197
11198
  this.waitingForSecondSide = false;
11198
11199
  this.hasShownTurnMessage = false;
11199
11200
  this.turnMessageTimer = null;
11201
+ this.debugLog = [];
11200
11202
  this.onExtractedResults = onExtractedResults;
11201
11203
  }
11204
+ logDebug(event, data = {}) {
11205
+ if (!ExtractorVideo.DEBUG) return;
11206
+ const entry = {
11207
+ ts: new Date().toISOString(),
11208
+ event,
11209
+ data
11210
+ };
11211
+ this.debugLog.push(entry);
11212
+ try {
11213
+ console.log("[ScanDocAI]", event, data);
11214
+ } catch (_) {}
11215
+ }
11216
+ getDebugDump() {
11217
+ return {
11218
+ userAgent: navigator.userAgent,
11219
+ secureContext: window.isSecureContext,
11220
+ location: window.location.href,
11221
+ videoPresent: !!this.video,
11222
+ videoReadyState: this.video?.readyState,
11223
+ videoNetworkState: this.video?.networkState,
11224
+ videoPaused: this.video?.paused,
11225
+ videoMuted: this.video?.muted,
11226
+ videoAutoplay: this.video?.autoplay,
11227
+ videoPlaysInline: this.video?.playsInline,
11228
+ videoDimensions: {
11229
+ videoWidth: this.video?.videoWidth || 0,
11230
+ videoHeight: this.video?.videoHeight || 0,
11231
+ clientWidth: this.video?.clientWidth || 0,
11232
+ clientHeight: this.video?.clientHeight || 0
11233
+ },
11234
+ streamTracks: this.video?.srcObject ? this.video.srcObject.getTracks().map(t => ({
11235
+ kind: t.kind,
11236
+ label: t.label,
11237
+ readyState: t.readyState,
11238
+ enabled: t.enabled
11239
+ })) : [],
11240
+ log: this.debugLog
11241
+ };
11242
+ }
11202
11243
  reset() {
11203
11244
  this.stopVideo();
11204
11245
  this.candidateImages = [];
@@ -11215,12 +11256,16 @@ class ExtractorVideo {
11215
11256
  this.hasShownTurnMessage = false;
11216
11257
  clearTimeout(this.turnMessageTimer);
11217
11258
  this.turnMessageTimer = null;
11259
+ this.debugLog = [];
11218
11260
  }
11219
11261
  async analyzeVideoStream() {
11220
11262
  if (!this.isRunning) return;
11221
11263
  const now = Date.now();
11222
11264
  const cfgValues = (0,_config__WEBPACK_IMPORTED_MODULE_1__.getScanDocAIConfigValues)();
11223
11265
  if (this.scanStartTime && now - this.scanStartTime > cfgValues.MAX_SCAN_DURATION_MS) {
11266
+ this.logDebug("scan_timeout", {
11267
+ elapsedMs: now - this.scanStartTime
11268
+ });
11224
11269
  this.stopVideo();
11225
11270
  this.onExtractedResults({
11226
11271
  success: false,
@@ -11231,7 +11276,16 @@ class ExtractorVideo {
11231
11276
  }
11232
11277
  const canvas = document.createElement("canvas");
11233
11278
  const video = document.getElementById("ScanDocAIVideoElement");
11234
- if (video && video.videoWidth < video.videoHeight) {
11279
+ if (!video) {
11280
+ this.logDebug("analyze_no_video_element");
11281
+ setTimeout(() => this.analyzeVideoStream(), ExtractorVideo.FREQUENCY_MS);
11282
+ return;
11283
+ }
11284
+ if (video.videoWidth < video.videoHeight) {
11285
+ this.logDebug("portrait_video_detected", {
11286
+ videoWidth: video.videoWidth,
11287
+ videoHeight: video.videoHeight
11288
+ });
11235
11289
  this.showMessage("Please rotate your device to landscape mode.");
11236
11290
  this.candidateImages = [];
11237
11291
  setTimeout(() => this.analyzeVideoStream(), ExtractorVideo.FREQUENCY_MS);
@@ -11250,6 +11304,9 @@ class ExtractorVideo {
11250
11304
  const [isValidationOk, response] = await (0,_requests_validation__WEBPACK_IMPORTED_MODULE_3__["default"])(images.map(e => e.validationImg), this.pastBlurValues, {});
11251
11305
  if (!isValidationOk) {
11252
11306
  const msg = Array.isArray(response) ? response.join(", ") : String(response || "");
11307
+ this.logDebug("validation_failed", {
11308
+ response: msg
11309
+ });
11253
11310
  if (msg.toLowerCase().includes("token expired/invalid")) {
11254
11311
  this.stopVideo();
11255
11312
  this.showMessage("Token expired/invalid. Please provide a new token.", true);
@@ -11264,6 +11321,7 @@ class ExtractorVideo {
11264
11321
  return;
11265
11322
  }
11266
11323
  if (response?.InfoCode === "1006" && response?.TransactionID) {
11324
+ this.logDebug("unsupported_document", response);
11267
11325
  const frontImage = this.candidateImages[this.candidateImages.length - 1].fullImg;
11268
11326
  await (0,_requests_error_logging__WEBPACK_IMPORTED_MODULE_4__["default"])(frontImage, "", response.TransactionID, response.OS, response.Device, response.Browser);
11269
11327
  this.showMessage("Document not supported.", true);
@@ -11345,6 +11403,10 @@ class ExtractorVideo {
11345
11403
  return;
11346
11404
  }
11347
11405
  this.candidateImages = [];
11406
+ }).catch(err => {
11407
+ this.logDebug("document_detector_error", {
11408
+ message: err?.message || String(err)
11409
+ });
11348
11410
  }).finally(() => {
11349
11411
  setTimeout(() => this.analyzeVideoStream(), ExtractorVideo.FREQUENCY_MS);
11350
11412
  });
@@ -11452,21 +11514,15 @@ class ExtractorVideo {
11452
11514
  };
11453
11515
  }
11454
11516
  }
11455
- if (supported.width) {
11456
- video.width = {
11457
- ideal: width
11458
- };
11459
- }
11460
- if (supported.height) {
11461
- video.height = {
11462
- ideal: height
11463
- };
11464
- }
11465
- if (supported.aspectRatio) {
11466
- video.aspectRatio = {
11467
- ideal: width / height
11468
- };
11469
- }
11517
+ if (supported.width) video.width = {
11518
+ ideal: width
11519
+ };
11520
+ if (supported.height) video.height = {
11521
+ ideal: height
11522
+ };
11523
+ if (supported.aspectRatio) video.aspectRatio = {
11524
+ ideal: width / height
11525
+ };
11470
11526
  return {
11471
11527
  video,
11472
11528
  audio: false
@@ -11485,12 +11541,14 @@ class ExtractorVideo {
11485
11541
  async tryEnableDocumentFocus(stream) {
11486
11542
  const track = stream?.getVideoTracks?.()[0];
11487
11543
  if (!track) {
11544
+ this.logDebug("focus_no_track");
11488
11545
  return {
11489
11546
  ok: false,
11490
11547
  reason: "No video track found"
11491
11548
  };
11492
11549
  }
11493
11550
  if (!track.getCapabilities || !track.applyConstraints) {
11551
+ this.logDebug("focus_api_unavailable");
11494
11552
  return {
11495
11553
  ok: false,
11496
11554
  reason: "Focus APIs unavailable on this browser/device",
@@ -11539,11 +11597,13 @@ class ExtractorVideo {
11539
11597
  result.reason = "No supported focus controls were exposed";
11540
11598
  }
11541
11599
  result.settingsAfter = track.getSettings ? track.getSettings() : null;
11600
+ this.logDebug("focus_result", result);
11542
11601
  return result;
11543
11602
  } catch (err) {
11544
11603
  result.ok = false;
11545
11604
  result.error = err.message || String(err);
11546
11605
  result.settingsAfter = track.getSettings ? track.getSettings() : null;
11606
+ this.logDebug("focus_error", result);
11547
11607
  return result;
11548
11608
  }
11549
11609
  }
@@ -11553,12 +11613,23 @@ class ExtractorVideo {
11553
11613
  width,
11554
11614
  height
11555
11615
  }) {
11556
- return navigator.mediaDevices.getUserMedia(this.buildVideoConstraints({
11616
+ const constraints = this.buildVideoConstraints({
11557
11617
  width,
11558
11618
  height,
11559
11619
  mode,
11560
11620
  deviceId
11561
- }));
11621
+ });
11622
+ this.logDebug("getUserMedia_attempt", {
11623
+ constraints
11624
+ });
11625
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
11626
+ this.logDebug("getUserMedia_success", {
11627
+ deviceId,
11628
+ width,
11629
+ height,
11630
+ trackLabels: stream.getTracks().map(t => t.label)
11631
+ });
11632
+ return stream;
11562
11633
  }
11563
11634
  async testDeviceAtPreferredResolutions(device, mode) {
11564
11635
  const attempts = [];
@@ -11595,6 +11666,13 @@ class ExtractorVideo {
11595
11666
  aspectRatio,
11596
11667
  label
11597
11668
  });
11669
+ this.logDebug("device_test_success", {
11670
+ deviceLabel: device?.label,
11671
+ deviceId: device?.deviceId,
11672
+ requested: size,
11673
+ settings,
11674
+ label
11675
+ });
11598
11676
  return {
11599
11677
  result,
11600
11678
  attempts
@@ -11605,6 +11683,13 @@ class ExtractorVideo {
11605
11683
  requested: size,
11606
11684
  error: err.message || String(err)
11607
11685
  });
11686
+ this.logDebug("device_test_error", {
11687
+ deviceLabel: device?.label,
11688
+ deviceId: device?.deviceId,
11689
+ requested: size,
11690
+ error: err?.message || String(err),
11691
+ name: err?.name
11692
+ });
11608
11693
  await this.stopMediaStream(stream);
11609
11694
  }
11610
11695
  }
@@ -11618,12 +11703,10 @@ class ExtractorVideo {
11618
11703
  mode,
11619
11704
  tested: []
11620
11705
  };
11621
-
11622
- // First try a warmup stream to unlock labels
11623
11706
  let warmupStream = null;
11624
11707
  let warmupResult = null;
11625
11708
  try {
11626
- const warmupSize = ExtractorVideo.VIDEO_CANDIDATE_SETTINGS[2]; // 1280x720
11709
+ const warmupSize = ExtractorVideo.VIDEO_CANDIDATE_SETTINGS[2];
11627
11710
  warmupStream = await this.openCandidateStream({
11628
11711
  mode,
11629
11712
  width: warmupSize.width,
@@ -11644,13 +11727,32 @@ class ExtractorVideo {
11644
11727
  }),
11645
11728
  requested: warmupSize
11646
11729
  };
11647
- } catch (_) {
11730
+ this.logDebug("warmup_success", {
11731
+ label,
11732
+ settings
11733
+ });
11734
+ } catch (err) {
11735
+ this.logDebug("warmup_error", {
11736
+ name: err?.name,
11737
+ message: err?.message || String(err)
11738
+ });
11648
11739
  warmupStream = null;
11649
11740
  }
11650
11741
  let devices = [];
11651
11742
  try {
11652
11743
  devices = await navigator.mediaDevices.enumerateDevices();
11653
- } catch (_) {
11744
+ this.logDebug("enumerate_devices", {
11745
+ devices: devices.map(d => ({
11746
+ kind: d.kind,
11747
+ label: d.label,
11748
+ deviceId: d.deviceId
11749
+ }))
11750
+ });
11751
+ } catch (err) {
11752
+ this.logDebug("enumerate_devices_error", {
11753
+ name: err?.name,
11754
+ message: err?.message || String(err)
11755
+ });
11654
11756
  devices = [];
11655
11757
  }
11656
11758
  const videoInputs = devices.filter(d => d.kind === "videoinput");
@@ -11704,13 +11806,12 @@ class ExtractorVideo {
11704
11806
  requested: chosen.requested,
11705
11807
  source: "device-test"
11706
11808
  };
11809
+ this.logDebug("camera_chosen", debug.chosen);
11707
11810
  return {
11708
11811
  chosen,
11709
11812
  debug
11710
11813
  };
11711
11814
  }
11712
-
11713
- // Final fallback: simple facingMode-based opens in descending priority
11714
11815
  for (const size of ExtractorVideo.VIDEO_CANDIDATE_SETTINGS) {
11715
11816
  try {
11716
11817
  const stream = await this.openCandidateStream({
@@ -11746,16 +11847,24 @@ class ExtractorVideo {
11746
11847
  requested: size,
11747
11848
  source: "fallback-facingMode"
11748
11849
  };
11850
+ this.logDebug("camera_chosen_fallback", debug.chosen);
11749
11851
  return {
11750
11852
  chosen,
11751
11853
  debug
11752
11854
  };
11753
- } catch (_) {}
11855
+ } catch (err) {
11856
+ this.logDebug("fallback_open_error", {
11857
+ requested: size,
11858
+ name: err?.name,
11859
+ message: err?.message || String(err)
11860
+ });
11861
+ }
11754
11862
  }
11755
11863
  throw new Error("Could not start a suitable camera.");
11756
11864
  }
11757
11865
  async startVideo() {
11758
11866
  try {
11867
+ this.logDebug("startVideo_begin");
11759
11868
  const serviceConfig = (0,_config__WEBPACK_IMPORTED_MODULE_1__.getScanDocAIConfig)();
11760
11869
  await serviceConfig.getAccessToken(false);
11761
11870
  const videoElem = document.getElementById("ScanDocAIVideoElement");
@@ -11772,38 +11881,75 @@ class ExtractorVideo {
11772
11881
  debug
11773
11882
  } = await this.pickBestPhoneCamera(mode);
11774
11883
  this.video.srcObject = chosen.stream;
11775
- await this.video.play().catch(e => {
11776
- console.warn(`Error on video play: ${e}`);
11884
+ this.logDebug("srcObject_assigned", {
11885
+ label: chosen.label,
11886
+ settings: chosen.settings
11777
11887
  });
11888
+ try {
11889
+ await this.video.play();
11890
+ this.logDebug("video_play_success", {
11891
+ readyState: this.video.readyState,
11892
+ videoWidth: this.video.videoWidth,
11893
+ videoHeight: this.video.videoHeight
11894
+ });
11895
+ } catch (e) {
11896
+ this.logDebug("video_play_error", {
11897
+ name: e?.name,
11898
+ message: e?.message || String(e)
11899
+ });
11900
+ throw e;
11901
+ }
11778
11902
  this.focusDebug = await this.tryEnableDocumentFocus(chosen.stream);
11779
11903
  this.cameraDebug = debug;
11780
11904
  this.video.addEventListener("loadeddata", () => {
11905
+ this.logDebug("video_loadeddata", {
11906
+ videoWidth: this.video.videoWidth,
11907
+ videoHeight: this.video.videoHeight
11908
+ });
11781
11909
  this.adjustOverlayPosition();
11782
11910
  });
11911
+ this.video.addEventListener("error", () => {
11912
+ this.logDebug("video_element_error", {
11913
+ error: this.video?.error ? {
11914
+ code: this.video.error.code,
11915
+ message: this.video.error.message
11916
+ } : null
11917
+ });
11918
+ });
11783
11919
  window.addEventListener("resize", () => this.adjustOverlayPosition());
11784
11920
  setTimeout(() => this.adjustOverlayPosition(), 500);
11785
11921
  this.scanStartTime = Date.now();
11786
11922
  setTimeout(() => this.analyzeVideoStream(), ExtractorVideo.FREQUENCY_MS);
11787
11923
  this.showMessage("Starting scanning");
11924
+ this.logDebug("startVideo_success", this.getDebugDump());
11788
11925
  return true;
11789
11926
  } catch (error) {
11790
11927
  console.error("startVideo failed:", error);
11928
+ this.logDebug("startVideo_failed", {
11929
+ name: error?.name,
11930
+ message: error?.message || String(error),
11931
+ dump: this.getDebugDump()
11932
+ });
11791
11933
  this.isRunning = false;
11792
11934
  this.stopVideo();
11793
11935
  this.onExtractedResults({
11794
11936
  success: false,
11795
11937
  code: error.status === 401 || error.status === 403 ? "004" : "005",
11796
- info: error.status === 401 || error.status === 403 ? "Authentication failed: Invalid API key/token." : "Startup failed: " + (error.message || "Unknown error")
11938
+ info: error.status === 401 || error.status === 403 ? "Authentication failed: Invalid API key/token." : "Startup failed: " + (error.message || "Unknown error"),
11939
+ debug: ExtractorVideo.DEBUG ? this.getDebugDump() : undefined
11797
11940
  });
11798
11941
  return false;
11799
11942
  }
11800
11943
  }
11801
11944
  stopVideo() {
11802
11945
  this.isRunning = false;
11946
+ this.logDebug("stopVideo_called");
11803
11947
  if (this.video) {
11804
11948
  this.video.pause();
11805
11949
  if (this.video.srcObject !== undefined && this.video.srcObject !== null) {
11806
- this.video.srcObject.getTracks().forEach(t => t.stop());
11950
+ try {
11951
+ this.video.srcObject.getTracks().forEach(t => t.stop());
11952
+ } catch (_) {}
11807
11953
  }
11808
11954
  this.video.srcObject = null;
11809
11955
  }
@@ -11924,13 +12070,11 @@ class ExtractorVideo {
11924
12070
  margin-left: 20%;
11925
12071
  margin-right: 20%;
11926
12072
  }
11927
-
11928
12073
  .desktopVideoHolder {
11929
12074
  position: relative;
11930
12075
  width: 100%;
11931
12076
  overflow: hidden;
11932
12077
  }
11933
-
11934
12078
  .desktopVideo {
11935
12079
  width: 100%;
11936
12080
  height: auto;
@@ -11939,7 +12083,6 @@ class ExtractorVideo {
11939
12083
  max-width: 100vw;
11940
12084
  max-height: 100vh;
11941
12085
  }
11942
-
11943
12086
  .centerGuideDot {
11944
12087
  position: absolute;
11945
12088
  width: 16px;
@@ -11952,12 +12095,10 @@ class ExtractorVideo {
11952
12095
  pointer-events: none;
11953
12096
  display: none;
11954
12097
  }
11955
-
11956
12098
  @keyframes flicker {
11957
12099
  0%, 100% { opacity: 1; }
11958
12100
  50% { opacity: 0.3; }
11959
12101
  }
11960
-
11961
12102
  .backgroundOverlay {
11962
12103
  position: absolute;
11963
12104
  top: 0;
@@ -11968,7 +12109,6 @@ class ExtractorVideo {
11968
12109
  z-index: 2;
11969
12110
  pointer-events: none;
11970
12111
  }
11971
-
11972
12112
  .desktopFeedback {
11973
12113
  position: relative;
11974
12114
  display: flex;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "scandoc-ai-components",
3
3
  "author": "ScanDoc-AI",
4
- "version": "0.1.17",
4
+ "version": "0.1.18",
5
5
  "private": false,
6
6
  "description": "Pure JavaScript package for integrating ScanDoc-AI services.",
7
7
  "keywords": [