@sekyro/web 0.0.0-stage → 0.1.0-rc.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/CHANGELOG.md +17 -0
- package/LICENSE +20 -0
- package/README.md +92 -2
- package/dist/capture-constraints-BujkZcfb.d.cts +21 -0
- package/dist/capture-constraints-BujkZcfb.d.ts +21 -0
- package/dist/capture-session-v3-BJ8ciaWl.d.ts +457 -0
- package/dist/capture-session-v3-CAoWM8iT.d.cts +457 -0
- package/dist/chunk-BPYVZUJA.js +852 -0
- package/dist/chunk-BPYVZUJA.js.map +1 -0
- package/dist/chunk-LOTAJDRE.js +1094 -0
- package/dist/chunk-LOTAJDRE.js.map +1 -0
- package/dist/chunk-MWNFSNMP.js +52 -0
- package/dist/chunk-MWNFSNMP.js.map +1 -0
- package/dist/chunk-NILNKLK6.js +50 -0
- package/dist/chunk-NILNKLK6.js.map +1 -0
- package/dist/chunk-NWF46H7K.js +359 -0
- package/dist/chunk-NWF46H7K.js.map +1 -0
- package/dist/chunk-WY6U7AIK.js +193 -0
- package/dist/chunk-WY6U7AIK.js.map +1 -0
- package/dist/chunk-YVFF4Q4G.js +327 -0
- package/dist/chunk-YVFF4Q4G.js.map +1 -0
- package/dist/client/index.cjs +357 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +39 -0
- package/dist/client/index.d.ts +39 -0
- package/dist/client/index.js +15 -0
- package/dist/client/index.js.map +1 -0
- package/dist/core/index.cjs +1605 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +87 -0
- package/dist/core/index.d.ts +87 -0
- package/dist/core/index.js +139 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/v3/detect-worker.cjs +392 -0
- package/dist/core/v3/detect-worker.cjs.map +1 -0
- package/dist/core/v3/detect-worker.d.cts +1 -0
- package/dist/core/v3/detect-worker.d.ts +1 -0
- package/dist/core/v3/detect-worker.js +13 -0
- package/dist/core/v3/detect-worker.js.map +1 -0
- package/dist/create-verification-CjLnJ0w6.d.ts +348 -0
- package/dist/create-verification-EnZ13uY4.d.cts +348 -0
- package/dist/detect-worker-CKXHlXx9.d.cts +105 -0
- package/dist/detect-worker-CKXHlXx9.d.ts +105 -0
- package/dist/detector-scanic-4C6IEW2K.js +787 -0
- package/dist/detector-scanic-4C6IEW2K.js.map +1 -0
- package/dist/hosted-client-BpvBqu9f.d.cts +408 -0
- package/dist/hosted-client-BpvBqu9f.d.ts +408 -0
- package/dist/index.cjs +2636 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/index.cjs +4166 -0
- package/dist/internal/index.cjs.map +1 -0
- package/dist/internal/index.d.cts +431 -0
- package/dist/internal/index.d.ts +431 -0
- package/dist/internal/index.js +647 -0
- package/dist/internal/index.js.map +1 -0
- package/dist/popup/index.cjs +76 -0
- package/dist/popup/index.cjs.map +1 -0
- package/dist/popup/index.d.cts +66 -0
- package/dist/popup/index.d.ts +66 -0
- package/dist/popup/index.js +11 -0
- package/dist/popup/index.js.map +1 -0
- package/dist/scanic-ml-stub-S2OZ6HEF.js +10 -0
- package/dist/scanic-ml-stub-S2OZ6HEF.js.map +1 -0
- package/package.json +118 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/doc/mrz-band.ts","../../src/core/doc/frame-analysis.ts","../../src/core/doc/feedback-aggregator.ts","../../src/core/doc/capture-session-doc.ts"],"sourcesContent":["// MRZ-band detector (Task 37 round 3) — the passport path's detection signal.\n//\n// WHY: edge/contour detection (scanic) fails on passport data pages — an open\n// booklet's page edges are white-on-white, so quads lock late or onto the\n// wrong rectangle (operator field finding, twice). The industry-standard\n// approach (Scanbot/Regula/Dynamsoft; PyImageSearch's classic blackhat\n// pipeline; YOLO-MRZ) is to detect the MRZ ITSELF: two 44-char lines of\n// high-contrast OCR-B strokes — the one feature of a passport that is\n// reliably detectable in any framing. This is the pure-JS analog of the\n// blackhat+gradient pipeline: per-row horizontal-stroke density with a\n// column-coverage requirement, then the tallest qualifying row run.\n//\n// Deterministic, allocation-light, no wasm: ~2ms on a 480px frame in node —\n// cheap enough to run at full doc cadence on LOW devices.\n\nexport interface MrzBand {\n x: number;\n y: number;\n width: number;\n height: number;\n /** Mean qualifying-row stroke density (diagnostics/telemetry). */\n density: number;\n}\n\nexport interface MrzBandThresholds {\n /** Luma step that counts as a text stroke edge. */\n EDGE_LUMA_DELTA: number;\n /** Min fraction of a row's pixels that are stroke edges to qualify. */\n MIN_ROW_DENSITY: number;\n /** Row must have stroke edges spread across ≥ this fraction of 12 columns. */\n MIN_COLUMN_COVERAGE: number;\n /** Band width (stroke x-extent) as a fraction of frame width. */\n MIN_WIDTH_FRAC: number;\n /** Band height bounds as fractions of frame height (2–3 OCR-B lines). */\n MIN_HEIGHT_FRAC: number;\n MAX_HEIGHT_FRAC: number;\n /** Rows this close together merge into one band (inter-line gap). */\n ROW_MERGE_GAP: number;\n}\n\n/** Frozen defaults (goldens in test/doc/mrz-band.test.ts — retune together). */\nexport const MRZ_BAND_DEFAULTS: Readonly<MrzBandThresholds> = Object.freeze({\n EDGE_LUMA_DELTA: 40,\n MIN_ROW_DENSITY: 0.08,\n MIN_COLUMN_COVERAGE: 0.6,\n MIN_WIDTH_FRAC: 0.55,\n MIN_HEIGHT_FRAC: 0.02,\n MAX_HEIGHT_FRAC: 0.16,\n ROW_MERGE_GAP: 18,\n});\n\nconst COLUMN_BUCKETS = 12;\n\n/**\n * Detect the MRZ stroke band on a (≤480px) grayscale-able frame. Returns the\n * tight band rect in frame pixels, or null when no qualifying band exists\n * (no MRZ in view, or only a partial/narrow slice of one).\n */\nexport function detectMrzBand(\n frame: ImageData,\n overrides: Partial<MrzBandThresholds> = {},\n): MrzBand | null {\n const t = { ...MRZ_BAND_DEFAULTS, ...overrides };\n const { data, width, height } = frame;\n if (width < 32 || height < 32) return null;\n\n // Per-row: stroke-edge count + column coverage + x-extent.\n const qualifies = new Array<boolean>(height).fill(false);\n const density = new Float32Array(height);\n const minX = new Int32Array(height).fill(width);\n const maxX = new Int32Array(height).fill(-1);\n\n const bucketWidth = width / COLUMN_BUCKETS;\n const buckets = new Array<boolean>(COLUMN_BUCKETS);\n for (let y = 0; y < height; y++) {\n let edges = 0;\n buckets.fill(false);\n const rowOffset = y * width * 4;\n let previousLuma =\n 0.299 * (data[rowOffset] ?? 0) +\n 0.587 * (data[rowOffset + 1] ?? 0) +\n 0.114 * (data[rowOffset + 2] ?? 0);\n for (let x = 1; x < width; x++) {\n const i = rowOffset + x * 4;\n const luma =\n 0.299 * (data[i] ?? 0) + 0.587 * (data[i + 1] ?? 0) + 0.114 * (data[i + 2] ?? 0);\n if (Math.abs(luma - previousLuma) >= t.EDGE_LUMA_DELTA) {\n edges += 1;\n buckets[Math.min(COLUMN_BUCKETS - 1, Math.floor(x / bucketWidth))] = true;\n if (x < (minX[y] as number)) minX[y] = x;\n if (x > (maxX[y] as number)) maxX[y] = x;\n }\n previousLuma = luma;\n }\n density[y] = edges / width;\n const coverage = buckets.filter(Boolean).length / COLUMN_BUCKETS;\n qualifies[y] =\n density[y]! >= t.MIN_ROW_DENSITY &&\n coverage >= t.MIN_COLUMN_COVERAGE &&\n (maxX[y] as number) - (minX[y] as number) >= t.MIN_WIDTH_FRAC * width;\n }\n\n // Merge qualifying rows across the inter-line gap; keep the best band.\n let best: { top: number; bottom: number; rows: number } | null = null;\n let runTop = -1;\n let lastQualifying = -1;\n let runRows = 0;\n const flush = (bottom: number) => {\n if (runTop < 0) return;\n const bandHeight = bottom - runTop + 1;\n if (\n bandHeight >= t.MIN_HEIGHT_FRAC * height &&\n bandHeight <= t.MAX_HEIGHT_FRAC * height &&\n runRows >= 4 && // at least a few real stroke rows, not a lone edge\n (best === null || runRows > best.rows)\n ) {\n best = { top: runTop, bottom, rows: runRows };\n }\n runTop = -1;\n runRows = 0;\n };\n for (let y = 0; y < height; y++) {\n if (qualifies[y]) {\n if (runTop < 0) runTop = y;\n else if (y - lastQualifying > t.ROW_MERGE_GAP) {\n flush(lastQualifying);\n runTop = y;\n }\n lastQualifying = y;\n runRows += 1;\n }\n }\n flush(lastQualifying);\n if (best === null) return null;\n const band = best as { top: number; bottom: number; rows: number };\n\n // Tight x-extent + mean density over the band's qualifying rows.\n let x1 = width;\n let x2 = -1;\n let densitySum = 0;\n let densityRows = 0;\n for (let y = band.top; y <= band.bottom; y++) {\n if (!qualifies[y]) continue;\n if ((minX[y] as number) < x1) x1 = minX[y] as number;\n if ((maxX[y] as number) > x2) x2 = maxX[y] as number;\n densitySum += density[y] as number;\n densityRows += 1;\n }\n if (x2 - x1 < t.MIN_WIDTH_FRAC * width) return null;\n return {\n x: x1,\n y: band.top,\n width: x2 - x1,\n height: band.bottom - band.top + 1,\n density: densityRows > 0 ? densitySum / densityRows : 0,\n };\n}\n","// Doc frame analysis (capture v2, Task-37 round 4) — the BlinkID-inspired\n// PER-FRAME status vector: independent framing / lighting / blur / glare /\n// tilt verdicts instead of a single collapsed guidance key. Pure and\n// deterministic: inputs are the detection (quad or MRZ band rect-as-quad),\n// the doc-region crop's quality measures, and static thresholds — no clocks,\n// no I/O. The FeedbackAggregator votes over these statuses; DocGateV1's\n// integrator/stability logic consumes `framingOk`/`qualityOk`.\nimport { EXPOSURE_BAND } from \"../v3/frame-quality\";\nimport { aspectBand, type DocFormat } from \"./doc-catalog\";\nimport {\n DOC_GATE_V1_DEFAULTS,\n type DocGateThresholds,\n type DocQuad,\n quadAreaFrac,\n quadBounds,\n} from \"./doc-gate-v1\";\n\nexport type FramingStatus =\n | \"no_document\"\n | \"too_far\"\n | \"too_close\"\n | \"edge_clipped\"\n | \"wrong_shape\"\n | \"tilted\"\n | \"ok\";\n\nexport type LightingStatus = \"too_dark\" | \"too_bright\" | \"ok\";\nexport type BlurStatus = \"blurry\" | \"ok\";\nexport type GlareStatus = \"glare\" | \"ok\";\n\n/** The per-frame analysis vector (every axis judged independently). */\nexport interface DocFrameAnalysis {\n framing: FramingStatus;\n lighting: LightingStatus;\n blur: BlurStatus;\n glare: GlareStatus;\n /** All-clear: every axis ok — the gate's integrator/stability input. */\n acceptable: boolean;\n}\n\nexport interface DocFrameMeasures {\n /** frame-quality sharpness on the doc-region crop (0..1). */\n sharpness: number;\n /** frame-quality exposure on the doc-region crop (0..1). */\n exposure: number;\n /** Mean crop luma (0..255) — picks too_dark vs too_bright. */\n meanLuma: number;\n /** Fraction of crop pixels at/above the specular ceiling (0..1). */\n specularFraction: number;\n}\n\n/** Analysis thresholds beyond the gate's — frozen defaults (round 4). */\nexport const DOC_ANALYSIS_DEFAULTS = Object.freeze({\n /** Doc area fraction above which the camera is too close (capture would crop). */\n MAX_AREA_FRAC: 0.92,\n /** Opposite-side length ratio beyond which the view is too oblique. */\n TILT_MAX_SIDE_RATIO: 1.3,\n /** Specular-pixel fraction at/above which the frame reads as glare. */\n GLARE_MAX_SPECULAR_FRAC: 0.025,\n});\n\n/** Fraction of crop pixels with luma ≥ 252 — cheap specular-highlight measure. */\nexport function specularFraction(crop: ImageData): number {\n const { data, width, height } = crop;\n const pixels = width * height;\n if (pixels === 0) return 0;\n let hot = 0;\n for (let i = 0; i < pixels; i++) {\n const o = i * 4;\n // Rec.601 luma, integer-ish — a specular blowout saturates all channels.\n const luma =\n 0.299 * (data[o] ?? 0) + 0.587 * (data[o + 1] ?? 0) + 0.114 * (data[o + 2] ?? 0);\n if (luma >= 252) hot++;\n }\n return hot / pixels;\n}\n\n/** Opposite-side length ratio of the quad (1 = fronto-parallel). */\nexport function quadTiltRatio(quad: DocQuad): number {\n const len = (a: { x: number; y: number }, b: { x: number; y: number }) =>\n Math.hypot(b.x - a.x, b.y - a.y);\n const top = len(quad.topLeft, quad.topRight);\n const bottom = len(quad.bottomLeft, quad.bottomRight);\n const left = len(quad.topLeft, quad.bottomLeft);\n const right = len(quad.topRight, quad.bottomRight);\n const h = Math.max(top, bottom) / Math.max(1e-6, Math.min(top, bottom));\n const v = Math.max(left, right) / Math.max(1e-6, Math.min(left, right));\n return Math.max(h, v);\n}\n\n/**\n * Judge one frame. `detectionMode` mirrors the gate: ID-1 quads carry\n * area/aspect/tilt semantics; ID-3 MRZ bands carry width-fraction semantics\n * (band rects are axis-aligned, so tilt/aspect checks are band-specific).\n */\nexport function analyzeDocFrame(input: {\n format: DocFormat;\n quad: DocQuad | null;\n frameWidth: number;\n frameHeight: number;\n measures: DocFrameMeasures;\n thresholds?: Partial<DocGateThresholds>;\n}): DocFrameAnalysis {\n const t = { ...DOC_GATE_V1_DEFAULTS, ...input.thresholds };\n const a = DOC_ANALYSIS_DEFAULTS;\n const { quad, frameWidth, frameHeight, measures } = input;\n const bandMode = input.format === \"ID-3\";\n\n let framing: FramingStatus = \"ok\";\n if (quad === null) {\n framing = \"no_document\";\n } else if (bandMode) {\n const bounds = quadBounds(quad);\n const widthFrac = frameWidth > 0 ? bounds.width / frameWidth : 0;\n const aspect = bounds.height > 0 ? bounds.width / bounds.height : 0;\n if (widthFrac < t.ID3_BAND_MIN_WIDTH_FRAC) framing = \"too_far\";\n else if (edgeClipped(quad, frameWidth, frameHeight, t.EDGE_CLIP_MARGIN_PX)) {\n framing = \"edge_clipped\";\n } else if (aspect < t.ID3_BAND_ASPECT_MIN || aspect > t.ID3_BAND_ASPECT_MAX) {\n framing = \"wrong_shape\";\n }\n } else {\n const areaFrac = quadAreaFrac(quad, frameWidth, frameHeight);\n const band = aspectBand(input.format);\n const bounds = quadBounds(quad);\n const aspect =\n Math.max(bounds.width, bounds.height) / Math.max(1e-6, Math.min(bounds.width, bounds.height));\n if (areaFrac < t.MIN_AREA_FRAC) framing = \"too_far\";\n else if (areaFrac > a.MAX_AREA_FRAC) framing = \"too_close\";\n else if (edgeClipped(quad, frameWidth, frameHeight, t.EDGE_CLIP_MARGIN_PX)) {\n framing = \"edge_clipped\";\n } else if (Math.abs(aspect - band.center) > band.tolerance) framing = \"wrong_shape\";\n else if (quadTiltRatio(quad) > a.TILT_MAX_SIDE_RATIO) framing = \"tilted\";\n }\n\n const lighting: LightingStatus =\n measures.exposure >= t.GUIDE_EXPOSURE\n ? \"ok\"\n : measures.meanLuma < (EXPOSURE_BAND[0] + EXPOSURE_BAND[1]) / 2\n ? \"too_dark\"\n : \"too_bright\";\n const blur: BlurStatus = measures.sharpness >= t.GUIDE_SHARPNESS ? \"ok\" : \"blurry\";\n const glare: GlareStatus =\n measures.specularFraction >= a.GLARE_MAX_SPECULAR_FRAC ? \"glare\" : \"ok\";\n\n return {\n framing,\n lighting,\n blur,\n glare,\n acceptable: framing === \"ok\" && lighting === \"ok\" && blur === \"ok\" && glare === \"ok\",\n };\n}\n\nfunction edgeClipped(\n quad: DocQuad,\n frameWidth: number,\n frameHeight: number,\n margin: number,\n): boolean {\n const pts = [quad.topLeft, quad.topRight, quad.bottomRight, quad.bottomLeft];\n return pts.some(\n (p) => p.x <= margin || p.y <= margin || p.x >= frameWidth - margin || p.y >= frameHeight - margin,\n );\n}\n","// Feedback aggregator (capture v2, Task-37 round 4) — the BlinkID-inspired\n// anti-flicker layer between per-frame analysis and the UI: each frame VOTES\n// for a feedback state; votes decay exponentially over a sliding window and\n// the DOMINANT state wins, but never before the current state's minimum\n// display duration has elapsed. Success states bypass voting entirely.\n//\n// 100% deterministic: the clock is an input (`nowMs`), the decay math is\n// closed-form — golden-testable tick sequences, no timers.\nimport type { DocFrameAnalysis } from \"./frame-analysis\";\n\n/** Feedback vocabulary (dcv2.* message keys on the hosted side). */\nexport type DocFeedbackKey =\n | \"sensing\"\n | \"too_far\"\n | \"too_close\"\n | \"edge_clipped\"\n | \"wrong_shape\"\n | \"tilted\"\n | \"too_dark\"\n | \"too_bright\"\n | \"blurry\"\n | \"glare\"\n | \"hold_still\"\n | \"processing\"\n | \"side_captured\"\n | \"captured\";\n\n/** Reticle rendering mode per feedback state (the stage maps to visuals). */\nexport type ReticleType = \"searching\" | \"coaching\" | \"locking\" | \"processing\" | \"flip\" | \"done\";\n\nexport interface DocFeedbackState {\n key: DocFeedbackKey;\n reticle: ReticleType;\n /** The state holds the UI at least this long (ms) before a switch. */\n minDurationMs: number;\n}\n\nexport const DOC_FEEDBACK_STATES: Readonly<Record<DocFeedbackKey, DocFeedbackState>> =\n Object.freeze({\n sensing: { key: \"sensing\", reticle: \"searching\", minDurationMs: 900 },\n too_far: { key: \"too_far\", reticle: \"coaching\", minDurationMs: 1400 },\n too_close: { key: \"too_close\", reticle: \"coaching\", minDurationMs: 1400 },\n edge_clipped: { key: \"edge_clipped\", reticle: \"coaching\", minDurationMs: 1400 },\n wrong_shape: { key: \"wrong_shape\", reticle: \"coaching\", minDurationMs: 1400 },\n tilted: { key: \"tilted\", reticle: \"coaching\", minDurationMs: 1400 },\n too_dark: { key: \"too_dark\", reticle: \"coaching\", minDurationMs: 1400 },\n too_bright: { key: \"too_bright\", reticle: \"coaching\", minDurationMs: 1400 },\n blurry: { key: \"blurry\", reticle: \"coaching\", minDurationMs: 1400 },\n glare: { key: \"glare\", reticle: \"coaching\", minDurationMs: 1400 },\n hold_still: { key: \"hold_still\", reticle: \"locking\", minDurationMs: 900 },\n processing: { key: \"processing\", reticle: \"processing\", minDurationMs: 800 },\n side_captured: { key: \"side_captured\", reticle: \"flip\", minDurationMs: 2600 },\n captured: { key: \"captured\", reticle: \"done\", minDurationMs: 800 },\n });\n\n/** Frame analysis → the feedback vote it casts (worst axis speaks). */\nexport function feedbackVoteOf(analysis: DocFrameAnalysis): DocFeedbackKey {\n if (analysis.framing === \"no_document\") return \"sensing\";\n if (analysis.framing === \"too_far\") return \"too_far\";\n if (analysis.framing === \"too_close\") return \"too_close\";\n if (analysis.framing === \"edge_clipped\") return \"edge_clipped\";\n if (analysis.framing === \"wrong_shape\") return \"wrong_shape\";\n if (analysis.framing === \"tilted\") return \"tilted\";\n if (analysis.lighting === \"too_dark\") return \"too_dark\";\n if (analysis.lighting === \"too_bright\") return \"too_bright\";\n if (analysis.glare === \"glare\") return \"glare\";\n if (analysis.blur === \"blurry\") return \"blurry\";\n return \"hold_still\";\n}\n\ninterface Vote {\n key: DocFeedbackKey;\n atMs: number;\n}\n\n/** Sliding vote window + exponential decay + per-state minimum hold. */\nexport class DocFeedbackAggregator {\n private votes: Vote[] = [];\n private current: DocFeedbackState = DOC_FEEDBACK_STATES.sensing;\n private currentSinceMs: number | null = null;\n\n constructor(\n private readonly windowMs = 2600,\n /** Per-second decay factor — recent frames outvote stale ones. */\n private readonly decayPerSecond = 0.35,\n ) {}\n\n reset(): void {\n this.votes = [];\n this.current = DOC_FEEDBACK_STATES.sensing;\n this.currentSinceMs = null;\n }\n\n /** Force a terminal/choreography state (capture fired, side flip, upload). */\n force(key: Extract<DocFeedbackKey, \"processing\" | \"side_captured\" | \"captured\">, nowMs: number): DocFeedbackState {\n this.votes = [];\n this.current = DOC_FEEDBACK_STATES[key];\n this.currentSinceMs = nowMs;\n return this.current;\n }\n\n /** Cast this frame's vote and return the (possibly held) winning state. */\n tick(vote: DocFeedbackKey, nowMs: number): DocFeedbackState {\n if (this.currentSinceMs === null) this.currentSinceMs = nowMs;\n this.votes.push({ key: vote, atMs: nowMs });\n this.votes = this.votes.filter((v) => nowMs - v.atMs <= this.windowMs);\n\n // Hold the current state through its minimum display duration.\n if (nowMs - this.currentSinceMs < this.current.minDurationMs) return this.current;\n\n const scores = new Map<DocFeedbackKey, number>();\n for (const v of this.votes) {\n const ageSeconds = (nowMs - v.atMs) / 1000;\n const weight = this.decayPerSecond ** ageSeconds; // 1 now → decays with age\n scores.set(v.key, (scores.get(v.key) ?? 0) + weight);\n }\n let winner: DocFeedbackKey = this.current.key;\n let best = -1;\n for (const [key, score] of scores) {\n if (score > best) {\n best = score;\n winner = key;\n }\n }\n if (winner !== this.current.key) {\n this.current = DOC_FEEDBACK_STATES[winner];\n this.currentSinceMs = nowMs;\n }\n return this.current;\n }\n}\n","// CaptureSessionDoc (Task 34) — the THIN document twin of CaptureSessionV3:\n// environment-facing camera, a 480px detect snapshot at the device profile's\n// cadence, DocGateV1 ticks, and on `ready` an UNCROPPED full-resolution still\n// (gate round 2: client-side quad cropping cut the MRZ when corners were off;\n// kyc-docs-v2 runs its own detector/warp — the client's job is timing, not\n// geometry). Same event contract SHAPE as CaptureSessionV3\n// (`statechange | checks | guidance | artifacts | error`) so hosts bind it\n// with the same hook pattern. Every effect is seam-injectable for tests; the\n// gate itself stays 100% deterministic (clock = tick input).\nimport { mapGetUserMediaError } from \"../media\";\nimport type { CaptureArtifacts, CaptureErrorKind, CaptureState } from \"../types\";\nimport { classifyDevice, type DeviceClass, probeDevice } from \"../v3/device-profile\";\nimport { exposure, meanLuma, sharpness } from \"../v3/frame-quality\";\nimport {\n DocFeedbackAggregator,\n type DocFeedbackState,\n feedbackVoteOf,\n} from \"./feedback-aggregator\";\nimport { analyzeDocFrame, type DocFrameAnalysis, specularFraction } from \"./frame-analysis\";\nimport type { DocFormat, DocSide } from \"./doc-catalog\";\nimport {\n DOC_DETECT_WIDTH,\n DocGateV1,\n type DocGateThresholds,\n type DocGateTick,\n type DocQuad,\n} from \"./doc-gate-v1\";\nimport { detectMrzBand } from \"./mrz-band\";\n\n/**\n * LAZY scanic load (route-budget contract): the vendored detector is ~39KiB gz\n * and must NOT ride the host's session-shell bundle — it is dynamically\n * imported on the FIRST detect tick (the camera-permission prompt gives the\n * fetch plenty of cover). Tests inject `opts.detect` and never touch this.\n */\nlet scanicDetect: ((image: ImageData) => Promise<DocQuad | null>) | null = null;\nasync function lazyScanicDetect(image: ImageData): Promise<DocQuad | null> {\n if (scanicDetect === null) {\n const module_ = await import(\"./detector-scanic\");\n scanicDetect = module_.detectQuad;\n }\n return scanicDetect(image);\n}\n\n/**\n * ID-3 detection (round 3): the MRZ stroke band, NOT page-edge quads — an\n * open booklet's page edges are white-on-white and defeat contour detection\n * (operator field finding, twice). The band rect becomes the gate's \"quad\"\n * (ID-3 band-mode geometry). Pure JS — no scanic on the passport path.\n */\nasync function mrzBandDetect(image: ImageData): Promise<DocQuad | null> {\n const band = detectMrzBand(image);\n if (band === null) return null;\n return {\n topLeft: { x: band.x, y: band.y },\n topRight: { x: band.x + band.width, y: band.y },\n bottomRight: { x: band.x + band.width, y: band.y + band.height },\n bottomLeft: { x: band.x, y: band.y + band.height },\n };\n}\n\n/**\n * Pre-fetch + cache the scanic chunk (call when the scan surface MOUNTS, before\n * the camera is even granted) so the first detect tick pays no chunk-fetch\n * latency. Fire-and-forget; failures fall back to the lazy path.\n */\nexport function warmDocDetector(): void {\n if (scanicDetect !== null) return;\n void import(\"./detector-scanic\")\n .then((module_) => {\n scanicDetect = module_.detectQuad;\n })\n .catch(() => undefined);\n}\n\n/** Doc detect cadence per device class — scanic @480px is ~3–8× a face tick,\n * so the doc loop paces slower than the face profiles (findings §1: 2–5 Hz). */\nexport const DOC_CADENCE_HZ: Readonly<Record<DeviceClass, number>> = Object.freeze({\n LOW: 3,\n MID: 5,\n HIGH: 6,\n});\n\nexport interface DocChecksPayload {\n engine: \"docv1\";\n /** Gate integrator 0..1 — the overlay ring. */\n p: number;\n plausible: boolean;\n stableTicks: number;\n quad: DocQuad | null;\n /** Detect-frame size the quad coordinates live in (overlay scaling). */\n frameWidth: number;\n frameHeight: number;\n /** Capture-v2 per-frame status vector (framing/lighting/blur/glare). */\n analysis: DocFrameAnalysis;\n}\n\n/** Artifacts: `keyframe` = the full-res still of the framed side (JPEG). */\nexport type DocArtifacts = CaptureArtifacts & { side?: DocSide };\n\nexport interface CaptureSessionDocEvents {\n statechange: { state: CaptureState };\n checks: DocChecksPayload;\n /** dcv1.* message KEY — hosts translate; never prose. Deduped. */\n guidance: { key: string };\n /** Capture-v2 AGGREGATED feedback (time-decay voted, min-duration held) —\n * the stage's message + reticle channel. Deduped on state key. */\n feedback: DocFeedbackState;\n artifacts: DocArtifacts;\n error: { kind: CaptureErrorKind; retryable: boolean };\n}\n\nexport interface CaptureSessionDocOptions {\n /** Host-owned <video playsInline muted>. */\n video: HTMLVideoElement;\n format?: DocFormat;\n /** Side label stamped on the artifacts event (recto→verso sequencing). */\n side?: DocSide;\n thresholds?: Partial<DocGateThresholds>;\n deviceClass?: DeviceClass;\n // ── seams (production defaults in parens) ─────────────────────────────────\n /** (navigator.mediaDevices.getUserMedia — environment camera) */\n getUserMedia?: (constraints: MediaStreamConstraints) => Promise<MediaStream>;\n /** (vendored scanic detectQuad) */\n detect?: (image: ImageData) => Promise<DocQuad | null>;\n /** (canvas downscale of the live video to the 480px detect frame) */\n grabDetectFrame?: (video: HTMLVideoElement, targetWidth: number) => ImageData | null;\n /** (full-res canvas still → JPEG blob + preview; quad passed for telemetry) */\n captureStill?: (\n video: HTMLVideoElement,\n quad: DocQuad,\n detectSize: { width: number; height: number },\n ) => Promise<{ blob: Blob; previewDataUrl: string }>;\n /** (performance.now) */\n now?: () => number;\n /** (setTimeout/clearTimeout) — the cadence pacer. */\n setTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;\n clearTimer?: (id: ReturnType<typeof setTimeout>) => void;\n}\n\ntype Handler<K extends keyof CaptureSessionDocEvents> = (\n payload: CaptureSessionDocEvents[K],\n) => void;\n\nexport class CaptureSessionDoc {\n private readonly opts: CaptureSessionDocOptions;\n private readonly gate: DocGateV1;\n private readonly cadenceMs: number;\n private readonly handlers = new Map<string, Set<(payload: never) => void>>();\n private stream: MediaStream | null = null;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private state: CaptureState = \"idle\";\n private lastGuidance: string | null = null;\n private attempt = 0;\n private capturing = false;\n private readonly feedback = new DocFeedbackAggregator();\n private lastFeedbackKey: string | null = null;\n\n constructor(opts: CaptureSessionDocOptions) {\n this.opts = opts;\n this.gate = new DocGateV1(opts.format ?? \"ID-1\", opts.thresholds);\n const deviceClass = opts.deviceClass ?? classifyDevice(probeDevice());\n this.cadenceMs = 1000 / DOC_CADENCE_HZ[deviceClass];\n }\n\n on<K extends keyof CaptureSessionDocEvents>(event: K, handler: Handler<K>): () => void {\n let set = this.handlers.get(event);\n if (!set) {\n set = new Set();\n this.handlers.set(event, set);\n }\n set.add(handler as (payload: never) => void);\n return () => set?.delete(handler as (payload: never) => void);\n }\n\n private emit<K extends keyof CaptureSessionDocEvents>(\n event: K,\n payload: CaptureSessionDocEvents[K],\n ): void {\n for (const handler of [...(this.handlers.get(event) ?? [])]) {\n (handler as Handler<K>)(payload);\n }\n }\n\n async start(): Promise<void> {\n const attempt = ++this.attempt;\n this.gate.reset();\n this.feedback.reset();\n this.lastFeedbackKey = null;\n this.lastGuidance = null;\n this.capturing = false;\n this.setState(\"preflight\");\n try {\n const getUserMedia =\n this.opts.getUserMedia ??\n ((constraints: MediaStreamConstraints) =>\n navigator.mediaDevices.getUserMedia(constraints));\n this.setState(\"acquiring\");\n this.stream = await getUserMedia({\n audio: false,\n video: {\n facingMode: { ideal: \"environment\" },\n width: { ideal: 1920 },\n height: { ideal: 1080 },\n },\n });\n if (attempt !== this.attempt) {\n for (const track of this.stream.getTracks()) track.stop();\n return;\n }\n this.opts.video.srcObject = this.stream;\n await this.opts.video.play().catch(() => undefined);\n this.setState(\"positioning\");\n this.scheduleTick(attempt);\n } catch (err) {\n const kind = mapGetUserMediaError(err);\n this.setState(\"error\");\n this.emit(\"error\", { kind, retryable: true });\n }\n }\n\n /** Restart the gate for another side / a retake (same stream). */\n rearm(side?: DocSide): void {\n if (side !== undefined) this.opts.side = side;\n this.gate.reset();\n this.feedback.reset();\n this.lastFeedbackKey = null;\n this.lastGuidance = null;\n this.capturing = false;\n if (this.state === \"captured\") {\n this.setState(\"positioning\");\n this.scheduleTick(this.attempt);\n }\n }\n\n destroy(): void {\n this.attempt += 1;\n if (this.timer !== null) (this.opts.clearTimer ?? clearTimeout)(this.timer);\n this.timer = null;\n if (this.stream) for (const track of this.stream.getTracks()) track.stop();\n this.stream = null;\n this.opts.video.srcObject = null;\n }\n\n private setState(state: CaptureState): void {\n this.state = state;\n this.emit(\"statechange\", { state });\n }\n\n private scheduleTick(attempt: number): void {\n const setTimer = this.opts.setTimer ?? setTimeout;\n this.timer = setTimer(() => void this.tick(attempt), this.cadenceMs);\n }\n\n private async tick(attempt: number): Promise<void> {\n if (attempt !== this.attempt || this.state !== \"positioning\" || this.capturing) return;\n const now = this.opts.now ?? (() => performance.now());\n const grab = this.opts.grabDetectFrame ?? defaultGrabDetectFrame;\n const detect =\n this.opts.detect ??\n ((this.opts.format ?? \"ID-1\") === \"ID-3\" ? mrzBandDetect : lazyScanicDetect);\n\n const frame = grab(this.opts.video, DOC_DETECT_WIDTH);\n if (frame === null) {\n this.scheduleTick(attempt);\n return;\n }\n let quad: DocQuad | null = null;\n try {\n quad = await detect(frame);\n } catch {\n quad = null; // a detector hiccup is a no-doc tick, never a crash\n }\n if (attempt !== this.attempt) return;\n\n const crop = quad !== null ? cropQuadRegion(frame, quad) : frame;\n const atMs = now();\n const measures = {\n sharpness: sharpness(crop),\n exposure: exposure(crop),\n meanLuma: meanLuma(crop),\n specularFraction: specularFraction(crop),\n };\n const analysis = analyzeDocFrame({\n format: this.opts.format ?? \"ID-1\",\n quad,\n frameWidth: frame.width,\n frameHeight: frame.height,\n measures,\n ...(this.opts.thresholds !== undefined ? { thresholds: this.opts.thresholds } : {}),\n });\n const tick = this.gate.tick({\n timestampMs: atMs,\n quad,\n quality: measures,\n frameWidth: frame.width,\n frameHeight: frame.height,\n });\n this.emit(\"checks\", {\n engine: \"docv1\",\n p: tick.p,\n plausible: tick.plausible,\n stableTicks: tick.stableTicks,\n quad: tick.quad,\n frameWidth: frame.width,\n frameHeight: frame.height,\n analysis,\n });\n // Capture-v2 feedback: time-decay voted + min-duration held (anti-flicker).\n const feedbackState = this.feedback.tick(feedbackVoteOf(analysis), atMs);\n if (feedbackState.key !== this.lastFeedbackKey) {\n this.lastFeedbackKey = feedbackState.key;\n this.emit(\"feedback\", feedbackState);\n }\n if (tick.guidanceKey !== this.lastGuidance) {\n this.lastGuidance = tick.guidanceKey;\n this.emit(\"guidance\", { key: tick.guidanceKey });\n }\n\n if (tick.ready && quad !== null) {\n const processing = this.feedback.force(\"processing\", atMs);\n this.lastFeedbackKey = processing.key;\n this.emit(\"feedback\", processing);\n await this.fire(attempt, quad, {\n width: frame.width,\n height: frame.height,\n tickSharpness: measures.sharpness,\n });\n return;\n }\n this.scheduleTick(attempt);\n }\n\n /**\n * BEST-FRAME capture window (capture-v2): the first ready tick captures a\n * candidate immediately, then up to CAPTURE_EXTRA_CANDIDATES more stills are\n * taken at CAPTURE_CANDIDATE_SPACING_MS while the user holds — each scored\n * by the live detect-tick sharpness at its capture moment — and the SHARPEST\n * wins. Beats first-acceptable-frame capture without decoding stills.\n */\n private async fire(\n attempt: number,\n quad: DocQuad,\n detectSize: { width: number; height: number; tickSharpness: number },\n ): Promise<void> {\n this.capturing = true;\n const capture = this.opts.captureStill ?? defaultCaptureStill;\n const grab = this.opts.grabDetectFrame ?? defaultGrabDetectFrame;\n const setTimer = this.opts.setTimer ?? setTimeout;\n const size = { width: detectSize.width, height: detectSize.height };\n try {\n let best = {\n still: await capture(this.opts.video, quad, size),\n sharpness: detectSize.tickSharpness,\n };\n for (let i = 0; i < CAPTURE_EXTRA_CANDIDATES; i++) {\n await new Promise<void>((resolve) => {\n setTimer(() => resolve(), CAPTURE_CANDIDATE_SPACING_MS);\n });\n if (attempt !== this.attempt) return;\n const frame = grab(this.opts.video, DOC_DETECT_WIDTH);\n if (frame === null) continue;\n const candidateSharpness = sharpness(cropQuadRegion(frame, quad));\n if (candidateSharpness > best.sharpness * CAPTURE_IMPROVEMENT_MIN_RATIO) {\n const still = await capture(this.opts.video, quad, size);\n if (attempt !== this.attempt) return;\n best = { still, sharpness: candidateSharpness };\n }\n }\n if (attempt !== this.attempt) return;\n this.setState(\"captured\");\n this.emit(\"artifacts\", {\n keyframe: best.still.blob,\n previewDataUrl: best.still.previewDataUrl,\n ...(this.opts.side !== undefined ? { side: this.opts.side } : {}),\n });\n } catch {\n if (attempt !== this.attempt) return;\n // Still capture failed (canvas/encode) — rearm the gate and keep coaching.\n this.capturing = false;\n this.gate.reset();\n this.scheduleTick(attempt);\n }\n }\n}\n\n/** Best-frame window: 2 extra candidates, 260ms apart, ≥8% sharper to replace. */\nexport const CAPTURE_EXTRA_CANDIDATES = 2;\nexport const CAPTURE_CANDIDATE_SPACING_MS = 260;\nconst CAPTURE_IMPROVEMENT_MIN_RATIO = 1.08;\n\n/** Downscale the live video to the detect width (aspect preserved). */\nfunction defaultGrabDetectFrame(video: HTMLVideoElement, targetWidth: number): ImageData | null {\n const vw = video.videoWidth;\n const vh = video.videoHeight;\n if (vw === 0 || vh === 0) return null;\n const scale = Math.min(1, targetWidth / vw);\n const width = Math.round(vw * scale);\n const height = Math.round(vh * scale);\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const context = canvas.getContext(\"2d\", { willReadFrequently: true });\n if (context === null) return null;\n context.drawImage(video, 0, 0, width, height);\n return context.getImageData(0, 0, width, height);\n}\n\n/**\n * Capture the FULL-RES frame (gate-timed, UNCROPPED — operator gate round 2):\n * client-side quad cropping cut the MRZ whenever the detected corners were\n * slightly off, and kyc-docs-v2 runs its OWN detector/warp on the server —\n * the client's job is TIMING (fire when framed/steady), not geometry. The\n * quad/detectSize params stay in the seam signature for telemetry/tests.\n */\nasync function defaultCaptureStill(\n video: HTMLVideoElement,\n _quad: DocQuad,\n _detectSize: { width: number; height: number },\n): Promise<{ blob: Blob; previewDataUrl: string }> {\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, video.videoWidth);\n canvas.height = Math.max(1, video.videoHeight);\n const context = canvas.getContext(\"2d\");\n if (context === null) throw new Error(\"2d context unavailable\");\n context.drawImage(video, 0, 0, canvas.width, canvas.height);\n const blob = await new Promise<Blob>((resolve, reject) => {\n canvas.toBlob((b) => (b ? resolve(b) : reject(new Error(\"toBlob failed\"))), \"image/jpeg\", 0.92);\n });\n return { blob, previewDataUrl: canvas.toDataURL(\"image/jpeg\", 0.85) };\n}\n\n/** Quad-bbox crop of the DETECT frame — the quality measures run on this. */\nfunction cropQuadRegion(frame: ImageData, quad: DocQuad): ImageData {\n const xs = [quad.topLeft.x, quad.topRight.x, quad.bottomRight.x, quad.bottomLeft.x];\n const ys = [quad.topLeft.y, quad.topRight.y, quad.bottomRight.y, quad.bottomLeft.y];\n const x1 = Math.max(0, Math.floor(Math.min(...xs)));\n const y1 = Math.max(0, Math.floor(Math.min(...ys)));\n const x2 = Math.min(frame.width, Math.ceil(Math.max(...xs)));\n const y2 = Math.min(frame.height, Math.ceil(Math.max(...ys)));\n const width = Math.max(1, x2 - x1);\n const height = Math.max(1, y2 - y1);\n const data = new Uint8ClampedArray(width * height * 4);\n for (let row = 0; row < height; row++) {\n const src = ((y1 + row) * frame.width + x1) * 4;\n data.set(frame.data.subarray(src, src + width * 4), row * width * 4);\n }\n return { data, width, height, colorSpace: frame.colorSpace } as ImageData;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/popup/index.ts
|
|
21
|
+
var popup_exports = {};
|
|
22
|
+
__export(popup_exports, {
|
|
23
|
+
SEKYRO_EVENTS: () => SEKYRO_EVENTS,
|
|
24
|
+
open: () => open,
|
|
25
|
+
openHosted: () => openHosted
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(popup_exports);
|
|
28
|
+
var SEKYRO_EVENTS = [
|
|
29
|
+
"session.completed",
|
|
30
|
+
"session.failed",
|
|
31
|
+
"session.review_pending",
|
|
32
|
+
"session.expired",
|
|
33
|
+
"session.cancelled"
|
|
34
|
+
];
|
|
35
|
+
var WINDOW_NAME = "sekyro-verify";
|
|
36
|
+
var WINDOW_FEATURES = "width=480,height=720,noopener=false";
|
|
37
|
+
var EVENT_NAMES = new Set(SEKYRO_EVENTS);
|
|
38
|
+
function isSekyroEvent(data) {
|
|
39
|
+
if (data === null || typeof data !== "object") return false;
|
|
40
|
+
const d = data;
|
|
41
|
+
return d.source === "sekyro" && d.version === 1 && typeof d.sessionId === "string" && typeof d.event === "string" && EVENT_NAMES.has(d.event);
|
|
42
|
+
}
|
|
43
|
+
function open(opts) {
|
|
44
|
+
const origin = new URL(opts.hostedUrl).origin;
|
|
45
|
+
const popup = window.open(opts.hostedUrl, WINDOW_NAME, WINDOW_FEATURES);
|
|
46
|
+
if (!popup) return { error: "popup_blocked" };
|
|
47
|
+
const listener = (ev) => {
|
|
48
|
+
if (ev.source !== popup) return;
|
|
49
|
+
if (ev.origin !== origin) return;
|
|
50
|
+
if (!isSekyroEvent(ev.data)) return;
|
|
51
|
+
const { source, version, event, sessionId } = ev.data;
|
|
52
|
+
opts.onEvent({ source, version, event, sessionId });
|
|
53
|
+
};
|
|
54
|
+
window.addEventListener("message", listener);
|
|
55
|
+
return {
|
|
56
|
+
close() {
|
|
57
|
+
window.removeEventListener("message", listener);
|
|
58
|
+
popup.close();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function openHosted(hostedUrl, opts) {
|
|
63
|
+
if (opts.mode === "redirect") {
|
|
64
|
+
window.location.assign(hostedUrl);
|
|
65
|
+
return { close() {
|
|
66
|
+
} };
|
|
67
|
+
}
|
|
68
|
+
return open({ hostedUrl, onEvent: opts.onEvent ?? (() => void 0) });
|
|
69
|
+
}
|
|
70
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
71
|
+
0 && (module.exports = {
|
|
72
|
+
SEKYRO_EVENTS,
|
|
73
|
+
open,
|
|
74
|
+
openHosted
|
|
75
|
+
});
|
|
76
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/popup/index.ts"],"sourcesContent":["// @sekyro/web/popup — the partner-facing popup embed wrapper\n// (Plan 6 Task 12). Framework-free browser code.\n//\n// REBRAND-CRITICAL WIRE CONTRACT (frozen on first partner integration):\n// - `source: 'sekyro'`, `version: 1`, window name `'sekyro-verify'`;\n// - event names mirror the webhook catalog (`session.completed` /\n// `session.failed` / `session.review_pending` / `session.expired`) plus the\n// page-only `session.cancelled` (user closed the popup mid-flow);\n// - payload is the event name + sessionId ONLY — never scores, PII, or\n// outcome detail (partners fetch the verdict server-side via API/webhooks).\n//\n// Security posture: the hosted page posts to the org's allowlisted origin\n// (backend `sdkAllowedOrigins`, surfaced as `HostedSessionView.sdkOrigin`) and\n// this listener double-checks BOTH the sender origin (must be the hosted URL's\n// origin) AND the message shape before forwarding anything to the partner.\n\n/** The frozen SDK event-name vocabulary, in catalog order. */\nexport const SEKYRO_EVENTS = [\n \"session.completed\",\n \"session.failed\",\n \"session.review_pending\",\n \"session.expired\",\n \"session.cancelled\",\n] as const;\n\n/** One SDK event name — webhook-catalog terminal names + `session.cancelled`. */\nexport type SekyroEventName = (typeof SEKYRO_EVENTS)[number];\n\n/** The frozen postMessage payload: event name + sessionId ONLY. */\nexport interface SekyroEvent {\n source: \"sekyro\";\n version: 1;\n event: SekyroEventName;\n sessionId: string;\n}\n\nexport interface OpenOptions {\n /** The `hosted_url` returned by `POST /api/v2/sessions` (create result). */\n hostedUrl: string;\n /** Called for every allowlist-validated event posted by the hosted page. */\n onEvent: (event: SekyroEvent) => void;\n}\n\n/** Live popup handle — `close()` detaches the listener and closes the window. */\nexport interface PopupHandle {\n close(): void;\n}\n\n/** `window.open` returned null: blocked by the browser (or a popup blocker). */\nexport interface PopupBlocked {\n error: \"popup_blocked\";\n}\n\n/** FROZEN window name — partners may target it (e.g. to re-focus the popup). */\nconst WINDOW_NAME = \"sekyro-verify\";\n\n/** Popup geometry; `noopener=false` keeps `window.opener` alive for the page's postMessage. */\nconst WINDOW_FEATURES = \"width=480,height=720,noopener=false\";\n\nconst EVENT_NAMES: ReadonlySet<string> = new Set(SEKYRO_EVENTS);\n\n/** True when the payload matches the frozen {@link SekyroEvent} shape exactly. */\nfunction isSekyroEvent(data: unknown): data is SekyroEvent {\n if (data === null || typeof data !== \"object\") return false;\n const d = data as Partial<SekyroEvent>;\n return (\n d.source === \"sekyro\" &&\n d.version === 1 &&\n typeof d.sessionId === \"string\" &&\n typeof d.event === \"string\" &&\n EVENT_NAMES.has(d.event)\n );\n}\n\n/**\n * Open the hosted verification flow in a popup and subscribe to its lifecycle\n * events. Incoming messages are dropped unless ALL hold:\n * - the sender window (`ev.source`) is THIS popup — closes the spoof path of\n * a same-origin iframe/window posting on the hosted origin's behalf\n * (`ev.source` is identity-stable across popup navigations);\n * - the sender origin is exactly the hosted URL's origin;\n * - the payload matches the frozen {@link SekyroEvent} shape (allowlisted\n * event name, string sessionId).\n *\n * Returns `{error: 'popup_blocked'}` when the browser refuses the popup —\n * callers should fall back to a full redirect to `hostedUrl`.\n *\n * Usage notes:\n * - ONE active popup per page: the window name is shared and every `open()`\n * installs a fresh listener, so `close()` the previous handle before\n * reopening (a reused named window would otherwise feed stale listeners).\n * - Events are at-least-once HINTS, not a system of record: a popup reload\n * mid-flow fires `session.cancelled`, and a reload after a terminal state\n * re-posts the terminal event — partners MUST confirm outcomes server-side\n * via webhooks or the sessions API.\n * - The popup `features` geometry is currently fixed ({@link WINDOW_FEATURES});\n * an options field for it is reserved for a later version.\n */\nexport function open(opts: OpenOptions): PopupHandle | PopupBlocked {\n const origin = new URL(opts.hostedUrl).origin;\n const popup = window.open(opts.hostedUrl, WINDOW_NAME, WINDOW_FEATURES);\n if (!popup) return { error: \"popup_blocked\" };\n\n const listener = (ev: MessageEvent): void => {\n if (ev.source !== popup) return;\n if (ev.origin !== origin) return;\n if (!isSekyroEvent(ev.data)) return;\n const { source, version, event, sessionId } = ev.data;\n // Re-project onto the frozen shape so extra properties never reach the partner.\n opts.onEvent({ source, version, event, sessionId });\n };\n window.addEventListener(\"message\", listener);\n\n return {\n close(): void {\n window.removeEventListener(\"message\", listener);\n popup.close();\n },\n };\n}\n\nexport interface OpenHostedOptions {\n /** `'popup'` opens a named window (the `open()` embed); `'redirect'` navigates this tab. */\n mode: \"popup\" | \"redirect\";\n /** Popup mode only — redirect mode never returns to this page. */\n onEvent?: (event: SekyroEvent) => void;\n}\n\n/**\n * Spec §3.1 entry: `openHosted(hostedUrl, { mode, onEvent })`. Popup mode is\n * the `open()` embed; a blocked popup returns `{error: 'popup_blocked'}` so the\n * caller can fall back to `mode: 'redirect'` (never done implicitly — leaving\n * the partner page is their call). Redirect mode navigates and returns a\n * no-op handle.\n */\nexport function openHosted(hostedUrl: string, opts: OpenHostedOptions): PopupHandle | PopupBlocked {\n if (opts.mode === \"redirect\") {\n window.location.assign(hostedUrl);\n return { close(): void {} };\n }\n return open({ hostedUrl, onEvent: opts.onEvent ?? (() => undefined) });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA+BA,IAAM,cAAc;AAGpB,IAAM,kBAAkB;AAExB,IAAM,cAAmC,IAAI,IAAI,aAAa;AAG9D,SAAS,cAAc,MAAoC;AACzD,MAAI,SAAS,QAAQ,OAAO,SAAS,SAAU,QAAO;AACtD,QAAM,IAAI;AACV,SACE,EAAE,WAAW,YACb,EAAE,YAAY,KACd,OAAO,EAAE,cAAc,YACvB,OAAO,EAAE,UAAU,YACnB,YAAY,IAAI,EAAE,KAAK;AAE3B;AA0BO,SAAS,KAAK,MAA+C;AAClE,QAAM,SAAS,IAAI,IAAI,KAAK,SAAS,EAAE;AACvC,QAAM,QAAQ,OAAO,KAAK,KAAK,WAAW,aAAa,eAAe;AACtE,MAAI,CAAC,MAAO,QAAO,EAAE,OAAO,gBAAgB;AAE5C,QAAM,WAAW,CAAC,OAA2B;AAC3C,QAAI,GAAG,WAAW,MAAO;AACzB,QAAI,GAAG,WAAW,OAAQ;AAC1B,QAAI,CAAC,cAAc,GAAG,IAAI,EAAG;AAC7B,UAAM,EAAE,QAAQ,SAAS,OAAO,UAAU,IAAI,GAAG;AAEjD,SAAK,QAAQ,EAAE,QAAQ,SAAS,OAAO,UAAU,CAAC;AAAA,EACpD;AACA,SAAO,iBAAiB,WAAW,QAAQ;AAE3C,SAAO;AAAA,IACL,QAAc;AACZ,aAAO,oBAAoB,WAAW,QAAQ;AAC9C,YAAM,MAAM;AAAA,IACd;AAAA,EACF;AACF;AAgBO,SAAS,WAAW,WAAmB,MAAqD;AACjG,MAAI,KAAK,SAAS,YAAY;AAC5B,WAAO,SAAS,OAAO,SAAS;AAChC,WAAO,EAAE,QAAc;AAAA,IAAC,EAAE;AAAA,EAC5B;AACA,SAAO,KAAK,EAAE,WAAW,SAAS,KAAK,YAAY,MAAM,QAAW,CAAC;AACvE;","names":[]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** The frozen SDK event-name vocabulary, in catalog order. */
|
|
2
|
+
declare const SEKYRO_EVENTS: readonly ["session.completed", "session.failed", "session.review_pending", "session.expired", "session.cancelled"];
|
|
3
|
+
/** One SDK event name — webhook-catalog terminal names + `session.cancelled`. */
|
|
4
|
+
type SekyroEventName = (typeof SEKYRO_EVENTS)[number];
|
|
5
|
+
/** The frozen postMessage payload: event name + sessionId ONLY. */
|
|
6
|
+
interface SekyroEvent {
|
|
7
|
+
source: "sekyro";
|
|
8
|
+
version: 1;
|
|
9
|
+
event: SekyroEventName;
|
|
10
|
+
sessionId: string;
|
|
11
|
+
}
|
|
12
|
+
interface OpenOptions {
|
|
13
|
+
/** The `hosted_url` returned by `POST /api/v2/sessions` (create result). */
|
|
14
|
+
hostedUrl: string;
|
|
15
|
+
/** Called for every allowlist-validated event posted by the hosted page. */
|
|
16
|
+
onEvent: (event: SekyroEvent) => void;
|
|
17
|
+
}
|
|
18
|
+
/** Live popup handle — `close()` detaches the listener and closes the window. */
|
|
19
|
+
interface PopupHandle {
|
|
20
|
+
close(): void;
|
|
21
|
+
}
|
|
22
|
+
/** `window.open` returned null: blocked by the browser (or a popup blocker). */
|
|
23
|
+
interface PopupBlocked {
|
|
24
|
+
error: "popup_blocked";
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Open the hosted verification flow in a popup and subscribe to its lifecycle
|
|
28
|
+
* events. Incoming messages are dropped unless ALL hold:
|
|
29
|
+
* - the sender window (`ev.source`) is THIS popup — closes the spoof path of
|
|
30
|
+
* a same-origin iframe/window posting on the hosted origin's behalf
|
|
31
|
+
* (`ev.source` is identity-stable across popup navigations);
|
|
32
|
+
* - the sender origin is exactly the hosted URL's origin;
|
|
33
|
+
* - the payload matches the frozen {@link SekyroEvent} shape (allowlisted
|
|
34
|
+
* event name, string sessionId).
|
|
35
|
+
*
|
|
36
|
+
* Returns `{error: 'popup_blocked'}` when the browser refuses the popup —
|
|
37
|
+
* callers should fall back to a full redirect to `hostedUrl`.
|
|
38
|
+
*
|
|
39
|
+
* Usage notes:
|
|
40
|
+
* - ONE active popup per page: the window name is shared and every `open()`
|
|
41
|
+
* installs a fresh listener, so `close()` the previous handle before
|
|
42
|
+
* reopening (a reused named window would otherwise feed stale listeners).
|
|
43
|
+
* - Events are at-least-once HINTS, not a system of record: a popup reload
|
|
44
|
+
* mid-flow fires `session.cancelled`, and a reload after a terminal state
|
|
45
|
+
* re-posts the terminal event — partners MUST confirm outcomes server-side
|
|
46
|
+
* via webhooks or the sessions API.
|
|
47
|
+
* - The popup `features` geometry is currently fixed ({@link WINDOW_FEATURES});
|
|
48
|
+
* an options field for it is reserved for a later version.
|
|
49
|
+
*/
|
|
50
|
+
declare function open(opts: OpenOptions): PopupHandle | PopupBlocked;
|
|
51
|
+
interface OpenHostedOptions {
|
|
52
|
+
/** `'popup'` opens a named window (the `open()` embed); `'redirect'` navigates this tab. */
|
|
53
|
+
mode: "popup" | "redirect";
|
|
54
|
+
/** Popup mode only — redirect mode never returns to this page. */
|
|
55
|
+
onEvent?: (event: SekyroEvent) => void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Spec §3.1 entry: `openHosted(hostedUrl, { mode, onEvent })`. Popup mode is
|
|
59
|
+
* the `open()` embed; a blocked popup returns `{error: 'popup_blocked'}` so the
|
|
60
|
+
* caller can fall back to `mode: 'redirect'` (never done implicitly — leaving
|
|
61
|
+
* the partner page is their call). Redirect mode navigates and returns a
|
|
62
|
+
* no-op handle.
|
|
63
|
+
*/
|
|
64
|
+
declare function openHosted(hostedUrl: string, opts: OpenHostedOptions): PopupHandle | PopupBlocked;
|
|
65
|
+
|
|
66
|
+
export { type OpenHostedOptions, type OpenOptions, type PopupBlocked, type PopupHandle, SEKYRO_EVENTS, type SekyroEvent, type SekyroEventName, open, openHosted };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** The frozen SDK event-name vocabulary, in catalog order. */
|
|
2
|
+
declare const SEKYRO_EVENTS: readonly ["session.completed", "session.failed", "session.review_pending", "session.expired", "session.cancelled"];
|
|
3
|
+
/** One SDK event name — webhook-catalog terminal names + `session.cancelled`. */
|
|
4
|
+
type SekyroEventName = (typeof SEKYRO_EVENTS)[number];
|
|
5
|
+
/** The frozen postMessage payload: event name + sessionId ONLY. */
|
|
6
|
+
interface SekyroEvent {
|
|
7
|
+
source: "sekyro";
|
|
8
|
+
version: 1;
|
|
9
|
+
event: SekyroEventName;
|
|
10
|
+
sessionId: string;
|
|
11
|
+
}
|
|
12
|
+
interface OpenOptions {
|
|
13
|
+
/** The `hosted_url` returned by `POST /api/v2/sessions` (create result). */
|
|
14
|
+
hostedUrl: string;
|
|
15
|
+
/** Called for every allowlist-validated event posted by the hosted page. */
|
|
16
|
+
onEvent: (event: SekyroEvent) => void;
|
|
17
|
+
}
|
|
18
|
+
/** Live popup handle — `close()` detaches the listener and closes the window. */
|
|
19
|
+
interface PopupHandle {
|
|
20
|
+
close(): void;
|
|
21
|
+
}
|
|
22
|
+
/** `window.open` returned null: blocked by the browser (or a popup blocker). */
|
|
23
|
+
interface PopupBlocked {
|
|
24
|
+
error: "popup_blocked";
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Open the hosted verification flow in a popup and subscribe to its lifecycle
|
|
28
|
+
* events. Incoming messages are dropped unless ALL hold:
|
|
29
|
+
* - the sender window (`ev.source`) is THIS popup — closes the spoof path of
|
|
30
|
+
* a same-origin iframe/window posting on the hosted origin's behalf
|
|
31
|
+
* (`ev.source` is identity-stable across popup navigations);
|
|
32
|
+
* - the sender origin is exactly the hosted URL's origin;
|
|
33
|
+
* - the payload matches the frozen {@link SekyroEvent} shape (allowlisted
|
|
34
|
+
* event name, string sessionId).
|
|
35
|
+
*
|
|
36
|
+
* Returns `{error: 'popup_blocked'}` when the browser refuses the popup —
|
|
37
|
+
* callers should fall back to a full redirect to `hostedUrl`.
|
|
38
|
+
*
|
|
39
|
+
* Usage notes:
|
|
40
|
+
* - ONE active popup per page: the window name is shared and every `open()`
|
|
41
|
+
* installs a fresh listener, so `close()` the previous handle before
|
|
42
|
+
* reopening (a reused named window would otherwise feed stale listeners).
|
|
43
|
+
* - Events are at-least-once HINTS, not a system of record: a popup reload
|
|
44
|
+
* mid-flow fires `session.cancelled`, and a reload after a terminal state
|
|
45
|
+
* re-posts the terminal event — partners MUST confirm outcomes server-side
|
|
46
|
+
* via webhooks or the sessions API.
|
|
47
|
+
* - The popup `features` geometry is currently fixed ({@link WINDOW_FEATURES});
|
|
48
|
+
* an options field for it is reserved for a later version.
|
|
49
|
+
*/
|
|
50
|
+
declare function open(opts: OpenOptions): PopupHandle | PopupBlocked;
|
|
51
|
+
interface OpenHostedOptions {
|
|
52
|
+
/** `'popup'` opens a named window (the `open()` embed); `'redirect'` navigates this tab. */
|
|
53
|
+
mode: "popup" | "redirect";
|
|
54
|
+
/** Popup mode only — redirect mode never returns to this page. */
|
|
55
|
+
onEvent?: (event: SekyroEvent) => void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Spec §3.1 entry: `openHosted(hostedUrl, { mode, onEvent })`. Popup mode is
|
|
59
|
+
* the `open()` embed; a blocked popup returns `{error: 'popup_blocked'}` so the
|
|
60
|
+
* caller can fall back to `mode: 'redirect'` (never done implicitly — leaving
|
|
61
|
+
* the partner page is their call). Redirect mode navigates and returns a
|
|
62
|
+
* no-op handle.
|
|
63
|
+
*/
|
|
64
|
+
declare function openHosted(hostedUrl: string, opts: OpenHostedOptions): PopupHandle | PopupBlocked;
|
|
65
|
+
|
|
66
|
+
export { type OpenHostedOptions, type OpenOptions, type PopupBlocked, type PopupHandle, SEKYRO_EVENTS, type SekyroEvent, type SekyroEventName, open, openHosted };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// scanic-ml-stub:scanic-ml-stub
|
|
2
|
+
function initMlDetector() {
|
|
3
|
+
throw new Error("scanic ML detector is not vendored (classical pipeline only)");
|
|
4
|
+
}
|
|
5
|
+
var scanic_ml_stub_default = {};
|
|
6
|
+
export {
|
|
7
|
+
scanic_ml_stub_default as default,
|
|
8
|
+
initMlDetector
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=scanic-ml-stub-S2OZ6HEF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["scanic-ml-stub:scanic-ml-stub"],"sourcesContent":["export function initMlDetector(){throw new Error(\"scanic ML detector is not vendored (classical pipeline only)\");}\nexport default {};\n"],"mappings":";AAAO,SAAS,iBAAgB;AAAC,QAAM,IAAI,MAAM,8DAA8D;AAAE;AACjH,IAAO,yBAAQ,CAAC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,120 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sekyro/web",
|
|
3
|
-
"version": "0.
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-rc.2",
|
|
4
|
+
"description": "Sekyro web SDK — browser liveness capture, hosted-verification client and popup embed for Sekyro identity verification",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/IBTIKAR-Technologies/sekyro-sdks.git",
|
|
9
|
+
"directory": "packages/sekyro-web"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/IBTIKAR-Technologies/sekyro-sdks/tree/main/packages/sekyro-web#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/IBTIKAR-Technologies/sekyro-sdks/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"sekyro",
|
|
17
|
+
"kyc",
|
|
18
|
+
"identity-verification",
|
|
19
|
+
"liveness",
|
|
20
|
+
"face-capture",
|
|
21
|
+
"mauritania",
|
|
22
|
+
"sdk",
|
|
23
|
+
"browser"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"type": "module",
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"import": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"default": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"require": {
|
|
40
|
+
"types": "./dist/index.d.cts",
|
|
41
|
+
"default": "./dist/index.cjs"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"./core": {
|
|
45
|
+
"import": {
|
|
46
|
+
"types": "./dist/core/index.d.ts",
|
|
47
|
+
"default": "./dist/core/index.js"
|
|
48
|
+
},
|
|
49
|
+
"require": {
|
|
50
|
+
"types": "./dist/core/index.d.cts",
|
|
51
|
+
"default": "./dist/core/index.cjs"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"./core/v3/detect-worker": {
|
|
55
|
+
"import": {
|
|
56
|
+
"types": "./dist/core/v3/detect-worker.d.ts",
|
|
57
|
+
"default": "./dist/core/v3/detect-worker.js"
|
|
58
|
+
},
|
|
59
|
+
"require": {
|
|
60
|
+
"types": "./dist/core/v3/detect-worker.d.cts",
|
|
61
|
+
"default": "./dist/core/v3/detect-worker.cjs"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"./client": {
|
|
65
|
+
"import": {
|
|
66
|
+
"types": "./dist/client/index.d.ts",
|
|
67
|
+
"default": "./dist/client/index.js"
|
|
68
|
+
},
|
|
69
|
+
"require": {
|
|
70
|
+
"types": "./dist/client/index.d.cts",
|
|
71
|
+
"default": "./dist/client/index.cjs"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"./popup": {
|
|
75
|
+
"import": {
|
|
76
|
+
"types": "./dist/popup/index.d.ts",
|
|
77
|
+
"default": "./dist/popup/index.js"
|
|
78
|
+
},
|
|
79
|
+
"require": {
|
|
80
|
+
"types": "./dist/popup/index.d.cts",
|
|
81
|
+
"default": "./dist/popup/index.cjs"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"./internal": {
|
|
85
|
+
"import": {
|
|
86
|
+
"types": "./dist/internal/index.d.ts",
|
|
87
|
+
"default": "./dist/internal/index.js"
|
|
88
|
+
},
|
|
89
|
+
"require": {
|
|
90
|
+
"types": "./dist/internal/index.d.cts",
|
|
91
|
+
"default": "./dist/internal/index.cjs"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"files": [
|
|
96
|
+
"dist",
|
|
97
|
+
"README.md",
|
|
98
|
+
"CHANGELOG.md",
|
|
99
|
+
"LICENSE"
|
|
100
|
+
],
|
|
101
|
+
"scripts": {
|
|
102
|
+
"build": "tsup",
|
|
103
|
+
"test": "vitest run",
|
|
104
|
+
"typecheck": "tsc --noEmit",
|
|
105
|
+
"api:report": "api-extractor run --local",
|
|
106
|
+
"api:check": "api-extractor run"
|
|
107
|
+
},
|
|
108
|
+
"dependencies": {
|
|
109
|
+
"@mediapipe/tasks-vision": "0.10.35"
|
|
110
|
+
},
|
|
111
|
+
"devDependencies": {
|
|
112
|
+
"@microsoft/api-extractor": "^7.59.4",
|
|
113
|
+
"@types/node": "^22",
|
|
114
|
+
"@vitest/coverage-v8": "^3",
|
|
115
|
+
"jsdom": "^25",
|
|
116
|
+
"tsup": "^8",
|
|
117
|
+
"typescript": "^5.7",
|
|
118
|
+
"vitest": "^3"
|
|
119
|
+
}
|
|
120
|
+
}
|