@arkyc/widget 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/dist/ArkycWidget.d.mts +50 -0
- package/dist/ArkycWidget.d.mts.map +1 -0
- package/dist/ArkycWidget.mjs +80 -0
- package/dist/ArkycWidget.mjs.map +1 -0
- package/dist/WidgetHandler.d.mts +24 -0
- package/dist/WidgetHandler.d.mts.map +1 -0
- package/dist/WidgetHandler.mjs +28 -0
- package/dist/WidgetHandler.mjs.map +1 -0
- package/dist/_virtual/_virtual_arkyc-theme-css.mjs +4 -0
- package/dist/arkyc-widget.iife.global.js +670 -0
- package/dist/arkyc-widget.iife.global.js.map +1 -0
- package/dist/capture.d.mts +73 -0
- package/dist/capture.d.mts.map +1 -0
- package/dist/capture.mjs +126 -0
- package/dist/capture.mjs.map +1 -0
- package/dist/client.d.mts +152 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +120 -0
- package/dist/client.mjs.map +1 -0
- package/dist/controller.d.mts +126 -0
- package/dist/controller.d.mts.map +1 -0
- package/dist/controller.mjs +582 -0
- package/dist/controller.mjs.map +1 -0
- package/dist/countries.mjs +967 -0
- package/dist/countries.mjs.map +1 -0
- package/dist/device.mjs +17 -0
- package/dist/device.mjs.map +1 -0
- package/dist/document.d.mts +108 -0
- package/dist/document.d.mts.map +1 -0
- package/dist/document.mjs +227 -0
- package/dist/document.mjs.map +1 -0
- package/dist/face.d.mts +82 -0
- package/dist/face.d.mts.map +1 -0
- package/dist/face.mjs +230 -0
- package/dist/face.mjs.map +1 -0
- package/dist/flow.d.mts +74 -0
- package/dist/flow.d.mts.map +1 -0
- package/dist/flow.mjs +132 -0
- package/dist/flow.mjs.map +1 -0
- package/dist/index.d.mts +19 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +16 -0
- package/dist/index.mjs.map +1 -0
- package/dist/qr.mjs +22 -0
- package/dist/qr.mjs.map +1 -0
- package/dist/realtime.d.mts +29 -0
- package/dist/realtime.d.mts.map +1 -0
- package/dist/realtime.mjs +107 -0
- package/dist/realtime.mjs.map +1 -0
- package/dist/theme.d.mts +42 -0
- package/dist/theme.d.mts.map +1 -0
- package/dist/theme.mjs +77 -0
- package/dist/theme.mjs.map +1 -0
- package/dist/types.d.mts +153 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/ui.mjs +931 -0
- package/dist/ui.mjs.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"countries.mjs","names":[],"sources":["../src/countries.ts"],"sourcesContent":["export const countries = [\n {\n name: 'United States',\n iso2: 'US',\n flag: '๐บ๐ธ',\n },\n {\n name: 'Canada',\n iso2: 'CA',\n flag: '๐จ๐ฆ',\n },\n {\n name: 'Brazil',\n iso2: 'BR',\n flag: '๐ง๐ท',\n },\n {\n name: 'United Kingdom',\n iso2: 'GB',\n flag: '๐ฌ๐ง',\n },\n {\n name: 'Germany',\n iso2: 'DE',\n flag: '๐ฉ๐ช',\n },\n {\n name: 'France',\n iso2: 'FR',\n flag: '๐ซ๐ท',\n },\n {\n name: 'China',\n iso2: 'CN',\n flag: '๐จ๐ณ',\n },\n {\n name: 'Japan',\n iso2: 'JP',\n flag: '๐ฏ๐ต',\n },\n {\n name: 'India',\n iso2: 'IN',\n flag: '๐ฎ๐ณ',\n },\n {\n name: 'Australia',\n iso2: 'AU',\n flag: '๐ฆ๐บ',\n },\n {\n name: 'South Africa',\n iso2: 'ZA',\n flag: '๐ฟ๐ฆ',\n },\n {\n name: 'Russia',\n iso2: 'RU',\n flag: '๐ท๐บ',\n },\n {\n name: 'Saudi Arabia',\n iso2: 'SA',\n flag: '๐ธ๐ฆ',\n },\n {\n name: 'Afghanistan',\n iso2: 'AF',\n flag: '๐ฆ๐ซ',\n },\n {\n name: 'Albania',\n iso2: 'AL',\n flag: '๐ฆ๐ฑ',\n },\n {\n name: 'Algeria',\n iso2: 'DZ',\n flag: '๐ฉ๐ฟ',\n },\n {\n name: 'Andorra',\n iso2: 'AD',\n flag: '๐ฆ๐ฉ',\n },\n {\n name: 'Angola',\n iso2: 'AO',\n flag: '๐ฆ๐ด',\n },\n {\n name: 'Antigua and Barbuda',\n iso2: 'AG',\n flag: '๐ฆ๐ฌ',\n },\n {\n name: 'Argentina',\n iso2: 'AR',\n flag: '๐ฆ๐ท',\n },\n {\n name: 'Armenia',\n iso2: 'AM',\n flag: '๐ฆ๐ฒ',\n },\n {\n name: 'Austria',\n iso2: 'AT',\n flag: '๐ฆ๐น',\n },\n {\n name: 'Azerbaijan',\n iso2: 'AZ',\n flag: '๐ฆ๐ฟ',\n },\n {\n name: 'Bahamas',\n iso2: 'BS',\n flag: '๐ง๐ธ',\n },\n {\n name: 'Bahrain',\n iso2: 'BH',\n flag: '๐ง๐ญ',\n },\n {\n name: 'Bangladesh',\n iso2: 'BD',\n flag: '๐ง๐ฉ',\n },\n {\n name: 'Barbados',\n iso2: 'BB',\n flag: '๐ง๐ง',\n },\n {\n name: 'Belarus',\n iso2: 'BY',\n flag: '๐ง๐พ',\n },\n {\n name: 'Belgium',\n iso2: 'BE',\n flag: '๐ง๐ช',\n },\n {\n name: 'Belize',\n iso2: 'BZ',\n flag: '๐ง๐ฟ',\n },\n {\n name: 'Benin',\n iso2: 'BJ',\n flag: '๐ง๐ฏ',\n },\n {\n name: 'Bhutan',\n iso2: 'BT',\n flag: '๐ง๐น',\n },\n {\n name: 'Bolivia',\n iso2: 'BO',\n flag: '๐ง๐ด',\n },\n {\n name: 'Bosnia and Herzegovina',\n iso2: 'BA',\n flag: '๐ง๐ฆ',\n },\n {\n name: 'Botswana',\n iso2: 'BW',\n flag: '๐ง๐ผ',\n },\n {\n name: 'Brunei',\n iso2: 'BN',\n flag: '๐ง๐ณ',\n },\n {\n name: 'Bulgaria',\n iso2: 'BG',\n flag: '๐ง๐ฌ',\n },\n {\n name: 'Burkina Faso',\n iso2: 'BF',\n flag: '๐ง๐ซ',\n },\n {\n name: 'Burundi',\n iso2: 'BI',\n flag: '๐ง๐ฎ',\n },\n {\n name: 'Cabo Verde',\n iso2: 'CV',\n flag: '๐จ๐ป',\n },\n {\n name: 'Cambodia',\n iso2: 'KH',\n flag: '๐ฐ๐ญ',\n },\n {\n name: 'Cameroon',\n iso2: 'CM',\n flag: '๐จ๐ฒ',\n },\n {\n name: 'Central African Republic',\n iso2: 'CF',\n flag: '๐จ๐ซ',\n },\n {\n name: 'Chad',\n iso2: 'TD',\n flag: '๐น๐ฉ',\n },\n {\n name: 'Chile',\n iso2: 'CL',\n flag: '๐จ๐ฑ',\n },\n {\n name: 'Colombia',\n iso2: 'CO',\n flag: '๐จ๐ด',\n },\n {\n name: 'Comoros',\n iso2: 'KM',\n flag: '๐ฐ๐ฒ',\n },\n {\n name: 'Congo (Congo-Brazzaville)',\n iso2: 'CG',\n flag: '๐จ๐ฌ',\n },\n {\n name: 'Congo (Congo-Kinshasa)',\n iso2: 'CD',\n flag: '๐จ๐ฉ',\n },\n {\n name: 'Costa Rica',\n iso2: 'CR',\n flag: '๐จ๐ท',\n },\n {\n name: 'Croatia',\n iso2: 'HR',\n flag: '๐ญ๐ท',\n },\n {\n name: 'Cuba',\n iso2: 'CU',\n flag: '๐จ๐บ',\n },\n {\n name: 'Cyprus',\n iso2: 'CY',\n flag: '๐จ๐พ',\n },\n {\n name: 'Czech Republic',\n iso2: 'CZ',\n flag: '๐จ๐ฟ',\n },\n {\n name: 'Denmark',\n iso2: 'DK',\n flag: '๐ฉ๐ฐ',\n },\n {\n name: 'Djibouti',\n iso2: 'DJ',\n flag: '๐ฉ๐ฏ',\n },\n {\n name: 'Dominica',\n iso2: 'DM',\n flag: '๐ฉ๐ฒ',\n },\n {\n name: 'Dominican Republic',\n iso2: 'DO',\n flag: '๐ฉ๐ด',\n },\n {\n name: 'Ecuador',\n iso2: 'EC',\n flag: '๐ช๐จ',\n },\n {\n name: 'Egypt',\n iso2: 'EG',\n flag: '๐ช๐ฌ',\n },\n {\n name: 'El Salvador',\n iso2: 'SV',\n flag: '๐ธ๐ป',\n },\n {\n name: 'Equatorial Guinea',\n iso2: 'GQ',\n flag: '๐ฌ๐ถ',\n },\n {\n name: 'Eritrea',\n iso2: 'ER',\n flag: '๐ช๐ท',\n },\n {\n name: 'Estonia',\n iso2: 'EE',\n flag: '๐ช๐ช',\n },\n {\n name: 'Eswatini',\n iso2: 'SZ',\n flag: '๐ธ๐ฟ',\n },\n {\n name: 'Ethiopia',\n iso2: 'ET',\n flag: '๐ช๐น',\n },\n {\n name: 'Fiji',\n iso2: 'FJ',\n flag: '๐ซ๐ฏ',\n },\n {\n name: 'Finland',\n iso2: 'FI',\n flag: '๐ซ๐ฎ',\n },\n {\n name: 'Gabon',\n iso2: 'GA',\n flag: '๐ฌ๐ฆ',\n },\n {\n name: 'Gambia',\n iso2: 'GM',\n flag: '๐ฌ๐ฒ',\n },\n {\n name: 'Georgia',\n iso2: 'GE',\n flag: '๐ฌ๐ช',\n },\n {\n name: 'Ghana',\n iso2: 'GH',\n flag: '๐ฌ๐ญ',\n },\n {\n name: 'Greece',\n iso2: 'GR',\n flag: '๐ฌ๐ท',\n },\n {\n name: 'Grenada',\n iso2: 'GD',\n flag: '๐ฌ๐ฉ',\n },\n {\n name: 'Guatemala',\n iso2: 'GT',\n flag: '๐ฌ๐น',\n },\n {\n name: 'Guinea',\n iso2: 'GN',\n flag: '๐ฌ๐ณ',\n },\n {\n name: 'Guinea-Bissau',\n iso2: 'GW',\n flag: '๐ฌ๐ผ',\n },\n {\n name: 'Guyana',\n iso2: 'GY',\n flag: '๐ฌ๐พ',\n },\n {\n name: 'Haiti',\n iso2: 'HT',\n flag: '๐ญ๐น',\n },\n {\n name: 'Honduras',\n iso2: 'HN',\n flag: '๐ญ๐ณ',\n },\n {\n name: 'Hungary',\n iso2: 'HU',\n flag: '๐ญ๐บ',\n },\n {\n name: 'Iceland',\n iso2: 'IS',\n flag: '๐ฎ๐ธ',\n },\n {\n name: 'Indonesia',\n iso2: 'ID',\n flag: '๐ฎ๐ฉ',\n },\n {\n name: 'Iran',\n iso2: 'IR',\n flag: '๐ฎ๐ท',\n },\n {\n name: 'Iraq',\n iso2: 'IQ',\n flag: '๐ฎ๐ถ',\n },\n {\n name: 'Ireland',\n iso2: 'IE',\n flag: '๐ฎ๐ช',\n },\n {\n name: 'Israel',\n iso2: 'IL',\n flag: '๐ฎ๐ฑ',\n },\n {\n name: 'Italy',\n iso2: 'IT',\n flag: '๐ฎ๐น',\n },\n {\n name: 'Jamaica',\n iso2: 'JM',\n flag: '๐ฏ๐ฒ',\n },\n {\n name: 'Jordan',\n iso2: 'JO',\n flag: '๐ฏ๐ด',\n },\n {\n name: 'Kazakhstan',\n iso2: 'KZ',\n flag: '๐ฐ๐ฟ',\n },\n {\n name: 'Kenya',\n iso2: 'KE',\n flag: '๐ฐ๐ช',\n },\n {\n name: 'Kiribati',\n iso2: 'KI',\n flag: '๐ฐ๐ฎ',\n },\n {\n name: 'Kuwait',\n iso2: 'KW',\n flag: '๐ฐ๐ผ',\n },\n {\n name: 'Kyrgyzstan',\n iso2: 'KG',\n flag: '๐ฐ๐ฌ',\n },\n {\n name: 'Laos',\n iso2: 'LA',\n flag: '๐ฑ๐ฆ',\n },\n {\n name: 'Latvia',\n iso2: 'LV',\n flag: '๐ฑ๐ป',\n },\n {\n name: 'Lebanon',\n iso2: 'LB',\n flag: '๐ฑ๐ง',\n },\n {\n name: 'Lesotho',\n iso2: 'LS',\n flag: '๐ฑ๐ธ',\n },\n {\n name: 'Liberia',\n iso2: 'LR',\n flag: '๐ฑ๐ท',\n },\n {\n name: 'Libya',\n iso2: 'LY',\n flag: '๐ฑ๐พ',\n },\n {\n name: 'Liechtenstein',\n iso2: 'LI',\n flag: '๐ฑ๐ฎ',\n },\n {\n name: 'Lithuania',\n iso2: 'LT',\n flag: '๐ฑ๐น',\n },\n {\n name: 'Luxembourg',\n iso2: 'LU',\n flag: '๐ฑ๐บ',\n },\n {\n name: 'Madagascar',\n iso2: 'MG',\n flag: '๐ฒ๐ฌ',\n },\n {\n name: 'Malawi',\n iso2: 'MW',\n flag: '๐ฒ๐ผ',\n },\n {\n name: 'Malaysia',\n iso2: 'MY',\n flag: '๐ฒ๐พ',\n },\n {\n name: 'Maldives',\n iso2: 'MV',\n flag: '๐ฒ๐ป',\n },\n {\n name: 'Mali',\n iso2: 'ML',\n flag: '๐ฒ๐ฑ',\n },\n {\n name: 'Malta',\n iso2: 'MT',\n flag: '๐ฒ๐น',\n },\n {\n name: 'Marshall Islands',\n iso2: 'MH',\n flag: '๐ฒ๐ญ',\n },\n {\n name: 'Mauritania',\n iso2: 'MR',\n flag: '๐ฒ๐ท',\n },\n {\n name: 'Mauritius',\n iso2: 'MU',\n flag: '๐ฒ๐บ',\n },\n {\n name: 'Mexico',\n iso2: 'MX',\n flag: '๐ฒ๐ฝ',\n },\n {\n name: 'Micronesia',\n iso2: 'FM',\n flag: '๐ซ๐ฒ',\n },\n {\n name: 'Moldova',\n iso2: 'MD',\n flag: '๐ฒ๐ฉ',\n },\n {\n name: 'Monaco',\n iso2: 'MC',\n flag: '๐ฒ๐จ',\n },\n {\n name: 'Mongolia',\n iso2: 'MN',\n flag: '๐ฒ๐ณ',\n },\n {\n name: 'Montenegro',\n iso2: 'ME',\n flag: '๐ฒ๐ช',\n },\n {\n name: 'Morocco',\n iso2: 'MA',\n flag: '๐ฒ๐ฆ',\n },\n {\n name: 'Mozambique',\n iso2: 'MZ',\n flag: '๐ฒ๐ฟ',\n },\n {\n name: 'Myanmar',\n iso2: 'MM',\n flag: '๐ฒ๐ฒ',\n },\n {\n name: 'Namibia',\n iso2: 'NA',\n flag: '๐ณ๐ฆ',\n },\n {\n name: 'Nauru',\n iso2: 'NR',\n flag: '๐ณ๐ท',\n },\n {\n name: 'Nepal',\n iso2: 'NP',\n flag: '๐ณ๐ต',\n },\n {\n name: 'Netherlands',\n iso2: 'NL',\n flag: '๐ณ๐ฑ',\n },\n {\n name: 'New Zealand',\n iso2: 'NZ',\n flag: '๐ณ๐ฟ',\n },\n {\n name: 'Nicaragua',\n iso2: 'NI',\n flag: '๐ณ๐ฎ',\n },\n {\n name: 'Niger',\n iso2: 'NE',\n flag: '๐ณ๐ช',\n },\n {\n name: 'Nigeria',\n iso2: 'NG',\n flag: '๐ณ๐ฌ',\n },\n {\n name: 'North Korea',\n iso2: 'KP',\n flag: '๐ฐ๐ต',\n },\n {\n name: 'North Macedonia',\n iso2: 'MK',\n flag: '๐ฒ๐ฐ',\n },\n {\n name: 'Norway',\n iso2: 'NO',\n flag: '๐ณ๐ด',\n },\n {\n name: 'Oman',\n iso2: 'OM',\n flag: '๐ด๐ฒ',\n },\n {\n name: 'Pakistan',\n iso2: 'PK',\n flag: '๐ต๐ฐ',\n },\n {\n name: 'Palau',\n iso2: 'PW',\n flag: '๐ต๐ผ',\n },\n {\n name: 'Panama',\n iso2: 'PA',\n flag: '๐ต๐ฆ',\n },\n {\n name: 'Papua New Guinea',\n iso2: 'PG',\n flag: '๐ต๐ฌ',\n },\n {\n name: 'Paraguay',\n iso2: 'PY',\n flag: '๐ต๐พ',\n },\n {\n name: 'Peru',\n iso2: 'PE',\n flag: '๐ต๐ช',\n },\n {\n name: 'Philippines',\n iso2: 'PH',\n flag: '๐ต๐ญ',\n },\n {\n name: 'Poland',\n iso2: 'PL',\n flag: '๐ต๐ฑ',\n },\n {\n name: 'Portugal',\n iso2: 'PT',\n flag: '๐ต๐น',\n },\n {\n name: 'Qatar',\n iso2: 'QA',\n flag: '๐ถ๐ฆ',\n },\n {\n name: 'Romania',\n iso2: 'RO',\n flag: '๐ท๐ด',\n },\n {\n name: 'Rwanda',\n iso2: 'RW',\n flag: '๐ท๐ผ',\n },\n {\n name: 'Saint Kitts and Nevis',\n iso2: 'KN',\n flag: '๐ฐ๐ณ',\n },\n {\n name: 'Saint Lucia',\n iso2: 'LC',\n flag: '๐ฑ๐จ',\n },\n {\n name: 'Saint Vincent and the Grenadines',\n iso2: 'VC',\n flag: '๐ป๐จ',\n },\n {\n name: 'Samoa',\n iso2: 'WS',\n flag: '๐ผ๐ธ',\n },\n {\n name: 'San Marino',\n iso2: 'SM',\n flag: '๐ธ๐ฒ',\n },\n {\n name: 'Sao Tome and Principe',\n iso2: 'ST',\n flag: '๐ธ๐น',\n },\n {\n name: 'Senegal',\n iso2: 'SN',\n flag: '๐ธ๐ณ',\n },\n {\n name: 'Serbia',\n iso2: 'RS',\n flag: '๐ท๐ธ',\n },\n {\n name: 'Seychelles',\n iso2: 'SC',\n flag: '๐ธ๐จ',\n },\n {\n name: 'Sierra Leone',\n iso2: 'SL',\n flag: '๐ธ๐ฑ',\n },\n {\n name: 'Singapore',\n iso2: 'SG',\n flag: '๐ธ๐ฌ',\n },\n {\n name: 'Slovakia',\n iso2: 'SK',\n flag: '๐ธ๐ฐ',\n },\n {\n name: 'Slovenia',\n iso2: 'SI',\n flag: '๐ธ๐ฎ',\n },\n {\n name: 'Solomon Islands',\n iso2: 'SB',\n flag: '๐ธ๐ง',\n },\n {\n name: 'Somalia',\n iso2: 'SO',\n flag: '๐ธ๐ด',\n },\n {\n name: 'South Korea',\n iso2: 'KR',\n flag: '๐ฐ๐ท',\n },\n {\n name: 'South Sudan',\n iso2: 'SS',\n flag: '๐ธ๐ธ',\n },\n {\n name: 'Spain',\n iso2: 'ES',\n flag: '๐ช๐ธ',\n },\n {\n name: 'Sri Lanka',\n iso2: 'LK',\n flag: '๐ฑ๐ฐ',\n },\n {\n name: 'Sudan',\n iso2: 'SD',\n flag: '๐ธ๐ฉ',\n },\n {\n name: 'Suriname',\n iso2: 'SR',\n flag: '๐ธ๐ท',\n },\n {\n name: 'Sweden',\n iso2: 'SE',\n flag: '๐ธ๐ช',\n },\n {\n name: 'Switzerland',\n iso2: 'CH',\n flag: '๐จ๐ญ',\n },\n {\n name: 'Syria',\n iso2: 'SY',\n flag: '๐ธ๐พ',\n },\n {\n name: 'Tajikistan',\n iso2: 'TJ',\n flag: '๐น๐ฏ',\n },\n {\n name: 'Tanzania',\n iso2: 'TZ',\n flag: '๐น๐ฟ',\n },\n {\n name: 'Thailand',\n iso2: 'TH',\n flag: '๐น๐ญ',\n },\n {\n name: 'Togo',\n iso2: 'TG',\n flag: '๐น๐ฌ',\n },\n {\n name: 'Tonga',\n iso2: 'TO',\n flag: '๐น๐ด',\n },\n {\n name: 'Trinidad and Tobago',\n iso2: 'TT',\n flag: '๐น๐น',\n },\n {\n name: 'Tunisia',\n iso2: 'TN',\n flag: '๐น๐ณ',\n },\n {\n name: 'Turkey',\n iso2: 'TR',\n flag: '๐น๐ท',\n },\n {\n name: 'Turkmenistan',\n iso2: 'TM',\n flag: '๐น๐ฒ',\n },\n {\n name: 'Tuvalu',\n iso2: 'TV',\n flag: '๐น๐ป',\n },\n {\n name: 'Uganda',\n iso2: 'UG',\n flag: '๐บ๐ฌ',\n },\n {\n name: 'Ukraine',\n iso2: 'UA',\n flag: '๐บ๐ฆ',\n },\n {\n name: 'United Arab Emirates',\n iso2: 'AE',\n flag: '๐ฆ๐ช',\n },\n {\n name: 'Uruguay',\n iso2: 'UY',\n flag: '๐บ๐พ',\n },\n {\n name: 'Uzbekistan',\n iso2: 'UZ',\n flag: '๐บ๐ฟ',\n },\n {\n name: 'Vanuatu',\n iso2: 'VU',\n flag: '๐ป๐บ',\n },\n {\n name: 'Venezuela',\n iso2: 'VE',\n flag: '๐ป๐ช',\n },\n {\n name: 'Vietnam',\n iso2: 'VN',\n flag: '๐ป๐ณ',\n },\n {\n name: 'Yemen',\n iso2: 'YE',\n flag: '๐พ๐ช',\n },\n {\n name: 'Zambia',\n iso2: 'ZM',\n flag: '๐ฟ๐ฒ',\n },\n {\n name: 'Zimbabwe',\n iso2: 'ZW',\n flag: '๐ฟ๐ผ',\n },\n {\n name: 'Timor-Leste',\n iso2: 'TL',\n flag: '๐น๐ฑ',\n },\n] as const\n"],"mappings":";AAAA,MAAa,YAAY;CACvB;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;CACA;EACE,MAAM;EACN,MAAM;EACN,MAAM;CACR;AACF"}
|
package/dist/device.mjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/device.ts
|
|
2
|
+
/**
|
|
3
|
+
* Coarse desktop/mobile detection used to decide whether to offer cross-device
|
|
4
|
+
* handoff. Handoff is most useful when the user starts on a desktop (no good
|
|
5
|
+
* camera) and continues on a phone, so projects can restrict it to desktops.
|
|
6
|
+
*/
|
|
7
|
+
function isDesktopDevice(nav) {
|
|
8
|
+
const ua = nav.userAgent || "";
|
|
9
|
+
if (!ua) return false;
|
|
10
|
+
const mobile = /Android|iPhone|iPad|iPod|Opera Mini|IEMobile|Mobile|BlackBerry|Windows Phone/i.test(ua);
|
|
11
|
+
const iPadOS = nav.platform === "MacIntel" && (nav.maxTouchPoints ?? 0) > 1;
|
|
12
|
+
return !mobile && !iPadOS;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { isDesktopDevice };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=device.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.mjs","names":[],"sources":["../src/device.ts"],"sourcesContent":["/**\n * Coarse desktop/mobile detection used to decide whether to offer cross-device\n * handoff. Handoff is most useful when the user starts on a desktop (no good\n * camera) and continues on a phone, so projects can restrict it to desktops.\n */\nexport function isDesktopDevice(nav: Navigator): boolean {\n const ua = nav.userAgent || ''\n // Require a positive desktop signal: an unknown/empty UA is treated as\n // non-desktop so the QR-first handoff flow never triggers on uncertainty.\n if (!ua) return false\n const mobile = /Android|iPhone|iPad|iPod|Opera Mini|IEMobile|Mobile|BlackBerry|Windows Phone/i.test(ua)\n // iPadOS 13+ masquerades as desktop Safari but reports touch points.\n const iPadOS = nav.platform === 'MacIntel' && (nav.maxTouchPoints ?? 0) > 1\n return !mobile && !iPadOS\n}\n"],"mappings":";;;;;;AAKA,SAAgB,gBAAgB,KAAyB;CACvD,MAAM,KAAK,IAAI,aAAa;CAG5B,IAAI,CAAC,IAAI,OAAO;CAChB,MAAM,SAAS,gFAAgF,KAAK,EAAE;CAEtG,MAAM,SAAS,IAAI,aAAa,eAAe,IAAI,kBAAkB,KAAK;CAC1E,OAAO,CAAC,UAAU,CAAC;AACrB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
//#region src/document.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* In-browser document detection for the capture screens. Pure JavaScript, no
|
|
4
|
+
* dependency: it draws the current frame to a small canvas and runs Sobel edge
|
|
5
|
+
* projection to locate the document's four borders, then judges whether a real,
|
|
6
|
+
* well-framed, in-focus document is present before auto-capturing.
|
|
7
|
+
*
|
|
8
|
+
* Like the face analyzer it is feature-detected and wrapped so that on any
|
|
9
|
+
* failure (no 2D canvas, a non-browser/test host) `ready()` resolves `false` and
|
|
10
|
+
* the caller falls back to the simpler brightness heuristic. The detection core
|
|
11
|
+
* (`analyzeDocumentGray` / `documentGuidance`) is a set of pure functions over a
|
|
12
|
+
* grayscale buffer, so it is unit-testable without a real canvas or camera.
|
|
13
|
+
*/
|
|
14
|
+
/** Detected document rectangle, normalized 0โ1 within the frame. */
|
|
15
|
+
interface DocRect {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
w: number;
|
|
19
|
+
h: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A normalized read of the current frame's document candidate.
|
|
23
|
+
*/
|
|
24
|
+
interface DocumentSample {
|
|
25
|
+
/** A plausible rectangular document border was found. */
|
|
26
|
+
present: boolean;
|
|
27
|
+
/** The detected border rectangle (normalized 0โ1). */
|
|
28
|
+
rect: DocRect;
|
|
29
|
+
/** Rectangle area as a fraction of the frame (proxy for distance). */
|
|
30
|
+
fill: number;
|
|
31
|
+
centerX: number;
|
|
32
|
+
centerY: number;
|
|
33
|
+
/** Weakest of the four border edges, 0โ1 (how convincingly bordered it is). */
|
|
34
|
+
edgeStrength: number;
|
|
35
|
+
/** Variance of the Laplacian over the frame โ a focus/blur proxy (higher = sharper). */
|
|
36
|
+
sharpness: number;
|
|
37
|
+
/** Mean luminance, 0โ1. */
|
|
38
|
+
brightness: number;
|
|
39
|
+
}
|
|
40
|
+
interface DocumentAnalyzer {
|
|
41
|
+
/** Prepare the canvas. Resolves `false` if unavailable โ callers go manual. */
|
|
42
|
+
ready(): Promise<boolean>;
|
|
43
|
+
/** Analyze the current video frame, or `null` if it can't be read. */
|
|
44
|
+
analyze(video: HTMLVideoElement): DocumentSample | null;
|
|
45
|
+
/** Release resources. */
|
|
46
|
+
close(): void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Detection thresholds. Tunable per-widget via `documentTuning`. Like the face
|
|
50
|
+
* tuning, the geometric/strength knobs benefit from calibration against real
|
|
51
|
+
* devices and lighting.
|
|
52
|
+
*/
|
|
53
|
+
interface DocumentTuning {
|
|
54
|
+
/** Width (px) the frame is downscaled to before analysis. */
|
|
55
|
+
sampleWidth: number;
|
|
56
|
+
/** Min / max rectangle area (fraction of frame) โ too small = far, too big = cropped. */
|
|
57
|
+
minFill: number;
|
|
58
|
+
maxFill: number;
|
|
59
|
+
/** Max distance of the document centre from the frame centre (0โ1). */
|
|
60
|
+
centerTol: number;
|
|
61
|
+
/** Min weakest-border strength to count as a real document edge (0โ1). */
|
|
62
|
+
minEdgeStrength: number;
|
|
63
|
+
/** Min Laplacian variance to count as in-focus. */
|
|
64
|
+
minSharpness: number;
|
|
65
|
+
/** Each border must sit at least this far (0โ1) inside the frame (not cropped). */
|
|
66
|
+
borderMargin: number;
|
|
67
|
+
/** Below this mean luminance the frame is treated as too dark. */
|
|
68
|
+
minBrightness: number;
|
|
69
|
+
/** Consecutive "ready" frames before auto-capture fires. */
|
|
70
|
+
hold: number;
|
|
71
|
+
}
|
|
72
|
+
declare const DEFAULT_DOCUMENT_TUNING: DocumentTuning;
|
|
73
|
+
/**
|
|
74
|
+
* Locate the dominant rectangle in a grayscale frame via Sobel edge projection,
|
|
75
|
+
* and measure focus and brightness. Pure โ operates on a grayscale buffer.
|
|
76
|
+
*
|
|
77
|
+
* @param tuning
|
|
78
|
+
* @returns
|
|
79
|
+
*/
|
|
80
|
+
declare function analyzeDocumentGray(gray: ArrayLike<number>, w: number, h: number): DocumentSample;
|
|
81
|
+
/**
|
|
82
|
+
* Whether a document is present, well-framed and in focus, with a guidance hint.
|
|
83
|
+
*
|
|
84
|
+
* @param tuning
|
|
85
|
+
* @returns
|
|
86
|
+
*/
|
|
87
|
+
declare function documentGuidance(s: DocumentSample, t?: DocumentTuning): {
|
|
88
|
+
ready: boolean;
|
|
89
|
+
hint: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Whether the sample is a well-framed, in-focus document ready to capture.
|
|
93
|
+
*
|
|
94
|
+
* @param s
|
|
95
|
+
* @param t
|
|
96
|
+
* @returns
|
|
97
|
+
*/
|
|
98
|
+
declare function isDocumentReady(s: DocumentSample, t?: DocumentTuning): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* The default (canvas edge-projection) document analyzer.
|
|
101
|
+
*
|
|
102
|
+
* @param tuning
|
|
103
|
+
* @returns
|
|
104
|
+
*/
|
|
105
|
+
declare function createDefaultDocumentAnalyzer(tuning?: DocumentTuning): DocumentAnalyzer;
|
|
106
|
+
//#endregion
|
|
107
|
+
export { DEFAULT_DOCUMENT_TUNING, DocRect, DocumentAnalyzer, DocumentSample, DocumentTuning, analyzeDocumentGray, createDefaultDocumentAnalyzer, documentGuidance, isDocumentReady };
|
|
108
|
+
//# sourceMappingURL=document.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.mts","names":[],"sources":["../src/document.ts"],"mappings":";;AAcA;;;;;;;;;AAIG;AAMH;;UAViB,OAAA;EACf,CAAA;EACA,CAAA;EACA,CAAA;EACA,CAAA;AAAA;;;;UAMe,cAAA;EAcf;EAZA,OAAA;EAYU;EAVV,IAAA,EAAM,OAAO;EAakB;EAX/B,IAAA;EACA,OAAA;EACA,OAAA;EAakC;EAXlC,YAAA;EAWgD;EAThD,SAAA;EAOS;EALT,UAAA;AAAA;AAAA,UAGe,gBAAA;EAImB;EAFlC,KAAA,IAAS,OAAA;EAIJ;EAFL,OAAA,CAAQ,KAAA,EAAO,gBAAA,GAAmB,cAAA;EAUnB;EARf,KAAA;AAAA;;;;;;UAQe,cAAA;EAWf;EATA,WAAA;EAaA;EAXA,OAAA;EACA,OAAA;EAYI;EAVJ,SAAA;EA0BD;EAxBC,eAAA;EAwBD;EAtBC,YAAA;EAsDc;EApDd,YAAA;;EAEA,aAAA;EAkDwC;EAhDxC,IAAA;AAAA;AAAA,cAGW,uBAAA,EAAyB,cAarC;;;AAgCiG;AAgGlG;;;;iBAhGgB,mBAAA,CAAoB,IAAA,EAAM,SAAA,UAAmB,CAAA,UAAW,CAAA,WAAY,cAAc;;;;;;;iBAgGlF,gBAAA,CACd,CAAA,EAAG,cAAA,EACH,CAAA,GAAG,cAAwC;EACxC,KAAA;EAAgB,IAAA;AAAA;;;;;;;AAuByE;iBAA9E,eAAA,CAAgB,CAAA,EAAG,cAAA,EAAgB,CAAA,GAAG,cAAwC;;;;;;;iBAwD9E,6BAAA,CAA8B,MAAA,GAAQ,cAAA,GAA2C,gBAAgB"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
//#region src/document.ts
|
|
2
|
+
const DEFAULT_DOCUMENT_TUNING = {
|
|
3
|
+
sampleWidth: 320,
|
|
4
|
+
minFill: .18,
|
|
5
|
+
maxFill: .97,
|
|
6
|
+
centerTol: .22,
|
|
7
|
+
minEdgeStrength: .1,
|
|
8
|
+
minSharpness: 8,
|
|
9
|
+
borderMargin: .012,
|
|
10
|
+
minBrightness: .18,
|
|
11
|
+
hold: 5
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The four border insets of a rect from the frame edges (top, right, bottom, left).
|
|
15
|
+
*
|
|
16
|
+
* @param rect
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
function margins(rect) {
|
|
20
|
+
return [
|
|
21
|
+
rect.y,
|
|
22
|
+
1 - (rect.x + rect.w),
|
|
23
|
+
1 - (rect.y + rect.h),
|
|
24
|
+
rect.x
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
function peak(arr, lo, hi) {
|
|
28
|
+
let bi = lo;
|
|
29
|
+
let bv = -1;
|
|
30
|
+
for (let i = lo; i < hi; i += 1) {
|
|
31
|
+
const v = arr[i];
|
|
32
|
+
if (v > bv) {
|
|
33
|
+
bv = v;
|
|
34
|
+
bi = i;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
i: bi,
|
|
39
|
+
v: bv
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Locate the dominant rectangle in a grayscale frame via Sobel edge projection,
|
|
44
|
+
* and measure focus and brightness. Pure โ operates on a grayscale buffer.
|
|
45
|
+
*
|
|
46
|
+
* @param tuning
|
|
47
|
+
* @returns
|
|
48
|
+
*/
|
|
49
|
+
function analyzeDocumentGray(gray, w, h) {
|
|
50
|
+
const empty = {
|
|
51
|
+
present: false,
|
|
52
|
+
rect: {
|
|
53
|
+
x: 0,
|
|
54
|
+
y: 0,
|
|
55
|
+
w: 0,
|
|
56
|
+
h: 0
|
|
57
|
+
},
|
|
58
|
+
fill: 0,
|
|
59
|
+
centerX: .5,
|
|
60
|
+
centerY: .5,
|
|
61
|
+
edgeStrength: 0,
|
|
62
|
+
sharpness: 0,
|
|
63
|
+
brightness: 0
|
|
64
|
+
};
|
|
65
|
+
if (w < 8 || h < 8 || gray.length < w * h) return empty;
|
|
66
|
+
const rowH = new Float64Array(h);
|
|
67
|
+
const colV = new Float64Array(w);
|
|
68
|
+
let lapSum = 0;
|
|
69
|
+
let lapSumSq = 0;
|
|
70
|
+
let lapN = 0;
|
|
71
|
+
let lumSum = 0;
|
|
72
|
+
for (let i = 0; i < w * h; i += 1) lumSum += gray[i];
|
|
73
|
+
const brightness = lumSum / (w * h * 255);
|
|
74
|
+
for (let y = 1; y < h - 1; y += 1) for (let x = 1; x < w - 1; x += 1) {
|
|
75
|
+
const i = y * w + x;
|
|
76
|
+
const tl = gray[i - w - 1];
|
|
77
|
+
const tc = gray[i - w];
|
|
78
|
+
const tr = gray[i - w + 1];
|
|
79
|
+
const ml = gray[i - 1];
|
|
80
|
+
const c = gray[i];
|
|
81
|
+
const mr = gray[i + 1];
|
|
82
|
+
const bl = gray[i + w - 1];
|
|
83
|
+
const bc = gray[i + w];
|
|
84
|
+
const br = gray[i + w + 1];
|
|
85
|
+
const gx = tr + 2 * mr + br - (tl + 2 * ml + bl);
|
|
86
|
+
const gy = bl + 2 * bc + br - (tl + 2 * tc + tr);
|
|
87
|
+
rowH[y] += Math.abs(gy);
|
|
88
|
+
colV[x] += Math.abs(gx);
|
|
89
|
+
const lap = 4 * c - ml - mr - tc - bc;
|
|
90
|
+
lapSum += lap;
|
|
91
|
+
lapSumSq += lap * lap;
|
|
92
|
+
lapN += 1;
|
|
93
|
+
}
|
|
94
|
+
const sharpness = lapN ? lapSumSq / lapN - (lapSum / lapN) ** 2 : 0;
|
|
95
|
+
const bandH = Math.max(1, Math.floor(h * .05));
|
|
96
|
+
const bandW = Math.max(1, Math.floor(w * .05));
|
|
97
|
+
const midY = Math.floor(h / 2);
|
|
98
|
+
const midX = Math.floor(w / 2);
|
|
99
|
+
const top = peak(rowH, bandH, midY);
|
|
100
|
+
const bottom = peak(rowH, midY, h - bandH);
|
|
101
|
+
const left = peak(colV, bandW, midX);
|
|
102
|
+
const right = peak(colV, midX, w - bandW);
|
|
103
|
+
const x0 = left.i / w;
|
|
104
|
+
const x1 = right.i / w;
|
|
105
|
+
const y0 = top.i / h;
|
|
106
|
+
const y1 = bottom.i / h;
|
|
107
|
+
const rect = {
|
|
108
|
+
x: x0,
|
|
109
|
+
y: y0,
|
|
110
|
+
w: Math.max(0, x1 - x0),
|
|
111
|
+
h: Math.max(0, y1 - y0)
|
|
112
|
+
};
|
|
113
|
+
const topLenPx = Math.max(1, rect.w * w);
|
|
114
|
+
const sideLenPx = Math.max(1, rect.h * h);
|
|
115
|
+
const edgeStrength = Math.min(top.v / (topLenPx * 1020), bottom.v / (topLenPx * 1020), left.v / (sideLenPx * 1020), right.v / (sideLenPx * 1020));
|
|
116
|
+
return {
|
|
117
|
+
present: rect.w > .12 && rect.h > .12 && edgeStrength > .04,
|
|
118
|
+
rect,
|
|
119
|
+
fill: rect.w * rect.h,
|
|
120
|
+
centerX: (x0 + x1) / 2,
|
|
121
|
+
centerY: (y0 + y1) / 2,
|
|
122
|
+
edgeStrength,
|
|
123
|
+
sharpness,
|
|
124
|
+
brightness
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Whether a document is present, well-framed and in focus, with a guidance hint.
|
|
129
|
+
*
|
|
130
|
+
* @param tuning
|
|
131
|
+
* @returns
|
|
132
|
+
*/
|
|
133
|
+
function documentGuidance(s, t = DEFAULT_DOCUMENT_TUNING) {
|
|
134
|
+
if (s.brightness < t.minBrightness) return {
|
|
135
|
+
ready: false,
|
|
136
|
+
hint: "Too dark โ find better lighting"
|
|
137
|
+
};
|
|
138
|
+
if (!s.present || s.edgeStrength < t.minEdgeStrength) return {
|
|
139
|
+
ready: false,
|
|
140
|
+
hint: "Fit your document inside the frame"
|
|
141
|
+
};
|
|
142
|
+
if (s.fill > t.maxFill || Math.min(...margins(s.rect)) < t.borderMargin) return {
|
|
143
|
+
ready: false,
|
|
144
|
+
hint: "Move back a little โ fit the whole document"
|
|
145
|
+
};
|
|
146
|
+
if (s.fill < t.minFill) return {
|
|
147
|
+
ready: false,
|
|
148
|
+
hint: "Move closer"
|
|
149
|
+
};
|
|
150
|
+
if (Math.abs(s.centerX - .5) > t.centerTol || Math.abs(s.centerY - .5) > t.centerTol) return {
|
|
151
|
+
ready: false,
|
|
152
|
+
hint: "Center the document"
|
|
153
|
+
};
|
|
154
|
+
if (s.sharpness < t.minSharpness) return {
|
|
155
|
+
ready: false,
|
|
156
|
+
hint: "Hold steady to focus"
|
|
157
|
+
};
|
|
158
|
+
return {
|
|
159
|
+
ready: true,
|
|
160
|
+
hint: "Looks good โ hold steady"
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Whether the sample is a well-framed, in-focus document ready to capture.
|
|
165
|
+
*
|
|
166
|
+
* @param s
|
|
167
|
+
* @param t
|
|
168
|
+
* @returns
|
|
169
|
+
*/
|
|
170
|
+
function isDocumentReady(s, t = DEFAULT_DOCUMENT_TUNING) {
|
|
171
|
+
return documentGuidance(s, t).ready;
|
|
172
|
+
}
|
|
173
|
+
var CanvasDocumentAnalyzer = class {
|
|
174
|
+
tuning;
|
|
175
|
+
canvas = null;
|
|
176
|
+
ctx = null;
|
|
177
|
+
constructor(tuning) {
|
|
178
|
+
this.tuning = tuning;
|
|
179
|
+
}
|
|
180
|
+
ready() {
|
|
181
|
+
try {
|
|
182
|
+
if (typeof document === "undefined") return Promise.resolve(false);
|
|
183
|
+
const canvas = document.createElement("canvas");
|
|
184
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
185
|
+
if (!ctx) return Promise.resolve(false);
|
|
186
|
+
this.canvas = canvas;
|
|
187
|
+
this.ctx = ctx;
|
|
188
|
+
return Promise.resolve(true);
|
|
189
|
+
} catch {
|
|
190
|
+
return Promise.resolve(false);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
analyze(video) {
|
|
194
|
+
if (!this.ctx || !this.canvas || !video.videoWidth) return null;
|
|
195
|
+
const tw = this.tuning.sampleWidth;
|
|
196
|
+
const th = Math.max(1, Math.round(tw * video.videoHeight / video.videoWidth));
|
|
197
|
+
this.canvas.width = tw;
|
|
198
|
+
this.canvas.height = th;
|
|
199
|
+
let data;
|
|
200
|
+
try {
|
|
201
|
+
this.ctx.drawImage(video, 0, 0, tw, th);
|
|
202
|
+
data = this.ctx.getImageData(0, 0, tw, th).data;
|
|
203
|
+
} catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const gray = new Float32Array(tw * th);
|
|
207
|
+
for (let i = 0, p = 0; i < gray.length; i += 1, p += 4) gray[i] = .299 * data[p] + .587 * data[p + 1] + .114 * data[p + 2];
|
|
208
|
+
return analyzeDocumentGray(gray, tw, th);
|
|
209
|
+
}
|
|
210
|
+
close() {
|
|
211
|
+
this.canvas = null;
|
|
212
|
+
this.ctx = null;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* The default (canvas edge-projection) document analyzer.
|
|
217
|
+
*
|
|
218
|
+
* @param tuning
|
|
219
|
+
* @returns
|
|
220
|
+
*/
|
|
221
|
+
function createDefaultDocumentAnalyzer(tuning = DEFAULT_DOCUMENT_TUNING) {
|
|
222
|
+
return new CanvasDocumentAnalyzer(tuning);
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
export { DEFAULT_DOCUMENT_TUNING, analyzeDocumentGray, createDefaultDocumentAnalyzer, documentGuidance, isDocumentReady };
|
|
226
|
+
|
|
227
|
+
//# sourceMappingURL=document.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.mjs","names":[],"sources":["../src/document.ts"],"sourcesContent":["/**\n * In-browser document detection for the capture screens. Pure JavaScript, no\n * dependency: it draws the current frame to a small canvas and runs Sobel edge\n * projection to locate the document's four borders, then judges whether a real,\n * well-framed, in-focus document is present before auto-capturing.\n *\n * Like the face analyzer it is feature-detected and wrapped so that on any\n * failure (no 2D canvas, a non-browser/test host) `ready()` resolves `false` and\n * the caller falls back to the simpler brightness heuristic. The detection core\n * (`analyzeDocumentGray` / `documentGuidance`) is a set of pure functions over a\n * grayscale buffer, so it is unit-testable without a real canvas or camera.\n */\n\n/** Detected document rectangle, normalized 0โ1 within the frame. */\nexport interface DocRect {\n x: number\n y: number\n w: number\n h: number\n}\n\n/**\n * A normalized read of the current frame's document candidate.\n */\nexport interface DocumentSample {\n /** A plausible rectangular document border was found. */\n present: boolean\n /** The detected border rectangle (normalized 0โ1). */\n rect: DocRect\n /** Rectangle area as a fraction of the frame (proxy for distance). */\n fill: number\n centerX: number\n centerY: number\n /** Weakest of the four border edges, 0โ1 (how convincingly bordered it is). */\n edgeStrength: number\n /** Variance of the Laplacian over the frame โ a focus/blur proxy (higher = sharper). */\n sharpness: number\n /** Mean luminance, 0โ1. */\n brightness: number\n}\n\nexport interface DocumentAnalyzer {\n /** Prepare the canvas. Resolves `false` if unavailable โ callers go manual. */\n ready(): Promise<boolean>\n /** Analyze the current video frame, or `null` if it can't be read. */\n analyze(video: HTMLVideoElement): DocumentSample | null\n /** Release resources. */\n close(): void\n}\n\n/**\n * Detection thresholds. Tunable per-widget via `documentTuning`. Like the face\n * tuning, the geometric/strength knobs benefit from calibration against real\n * devices and lighting.\n */\nexport interface DocumentTuning {\n /** Width (px) the frame is downscaled to before analysis. */\n sampleWidth: number\n /** Min / max rectangle area (fraction of frame) โ too small = far, too big = cropped. */\n minFill: number\n maxFill: number\n /** Max distance of the document centre from the frame centre (0โ1). */\n centerTol: number\n /** Min weakest-border strength to count as a real document edge (0โ1). */\n minEdgeStrength: number\n /** Min Laplacian variance to count as in-focus. */\n minSharpness: number\n /** Each border must sit at least this far (0โ1) inside the frame (not cropped). */\n borderMargin: number\n /** Below this mean luminance the frame is treated as too dark. */\n minBrightness: number\n /** Consecutive \"ready\" frames before auto-capture fires. */\n hold: number\n}\n\nexport const DEFAULT_DOCUMENT_TUNING: DocumentTuning = {\n sampleWidth: 320,\n // Edge-projection underestimates the document rectangle when the strongest\n // borders sit inside the card, so a demanding minFill reads as a constant\n // \"move closer\". Keep it modest; framing is still gated by maxFill + margins.\n minFill: 0.18,\n maxFill: 0.97,\n centerTol: 0.22,\n minEdgeStrength: 0.1,\n minSharpness: 8,\n borderMargin: 0.012,\n minBrightness: 0.18,\n hold: 5,\n}\n\n/**\n * The four border insets of a rect from the frame edges (top, right, bottom, left).\n *\n * @param rect\n * @returns\n */\nfunction margins(rect: DocRect): [number, number, number, number] {\n return [rect.y, 1 - (rect.x + rect.w), 1 - (rect.y + rect.h), rect.x]\n}\n\nfunction peak(arr: Float64Array, lo: number, hi: number): { i: number; v: number } {\n let bi = lo\n let bv = -1\n for (let i = lo; i < hi; i += 1) {\n const v = arr[i]!\n if (v > bv) {\n bv = v\n bi = i\n }\n }\n return { i: bi, v: bv }\n}\n\n/**\n * Locate the dominant rectangle in a grayscale frame via Sobel edge projection,\n * and measure focus and brightness. Pure โ operates on a grayscale buffer.\n *\n * @param tuning\n * @returns\n */\nexport function analyzeDocumentGray(gray: ArrayLike<number>, w: number, h: number): DocumentSample {\n const empty: DocumentSample = {\n present: false,\n rect: { x: 0, y: 0, w: 0, h: 0 },\n fill: 0,\n centerX: 0.5,\n centerY: 0.5,\n edgeStrength: 0,\n sharpness: 0,\n brightness: 0,\n }\n if (w < 8 || h < 8 || gray.length < w * h) return empty\n\n // Per-row horizontal-edge energy (|gy|) and per-column vertical-edge energy (|gx|).\n const rowH = new Float64Array(h)\n const colV = new Float64Array(w)\n let lapSum = 0\n let lapSumSq = 0\n let lapN = 0\n let lumSum = 0\n for (let i = 0; i < w * h; i += 1) lumSum += gray[i]!\n const brightness = lumSum / (w * h * 255)\n\n for (let y = 1; y < h - 1; y += 1) {\n for (let x = 1; x < w - 1; x += 1) {\n const i = y * w + x\n const tl = gray[i - w - 1]!\n const tc = gray[i - w]!\n const tr = gray[i - w + 1]!\n const ml = gray[i - 1]!\n const c = gray[i]!\n const mr = gray[i + 1]!\n const bl = gray[i + w - 1]!\n const bc = gray[i + w]!\n const br = gray[i + w + 1]!\n const gx = tr + 2 * mr + br - (tl + 2 * ml + bl)\n const gy = bl + 2 * bc + br - (tl + 2 * tc + tr)\n rowH[y]! += Math.abs(gy)\n colV[x]! += Math.abs(gx)\n const lap = 4 * c - ml - mr - tc - bc\n lapSum += lap\n lapSumSq += lap * lap\n lapN += 1\n }\n }\n const sharpness = lapN ? lapSumSq / lapN - (lapSum / lapN) ** 2 : 0\n\n const bandH = Math.max(1, Math.floor(h * 0.05))\n const bandW = Math.max(1, Math.floor(w * 0.05))\n const midY = Math.floor(h / 2)\n const midX = Math.floor(w / 2)\n\n // Top/bottom borders are the strongest horizontal edges in the upper/lower half;\n // left/right borders the strongest vertical edges in the left/right half.\n const top = peak(rowH, bandH, midY)\n const bottom = peak(rowH, midY, h - bandH)\n const left = peak(colV, bandW, midX)\n const right = peak(colV, midX, w - bandW)\n\n const x0 = left.i / w\n const x1 = right.i / w\n const y0 = top.i / h\n const y1 = bottom.i / h\n const rect: DocRect = { x: x0, y: y0, w: Math.max(0, x1 - x0), h: Math.max(0, y1 - y0) }\n\n // Normalize each border by its own length (max |sobel| โ 4ยท255), so strength\n // measures how cleanly bordered the edge is, independent of document size โ\n // size is judged separately by `fill`.\n const topLenPx = Math.max(1, rect.w * w)\n const sideLenPx = Math.max(1, rect.h * h)\n const edgeStrength = Math.min(\n top.v / (topLenPx * 1020),\n bottom.v / (topLenPx * 1020),\n left.v / (sideLenPx * 1020),\n right.v / (sideLenPx * 1020),\n )\n const present = rect.w > 0.12 && rect.h > 0.12 && edgeStrength > 0.04\n\n return {\n present,\n rect,\n fill: rect.w * rect.h,\n centerX: (x0 + x1) / 2,\n centerY: (y0 + y1) / 2,\n edgeStrength,\n sharpness,\n brightness,\n }\n}\n\n/**\n * Whether a document is present, well-framed and in focus, with a guidance hint.\n *\n * @param tuning\n * @returns\n */\nexport function documentGuidance(\n s: DocumentSample,\n t: DocumentTuning = DEFAULT_DOCUMENT_TUNING,\n): { ready: boolean; hint: string } {\n if (s.brightness < t.minBrightness) return { ready: false, hint: 'Too dark โ find better lighting' }\n if (!s.present || s.edgeStrength < t.minEdgeStrength) {\n return { ready: false, hint: 'Fit your document inside the frame' }\n }\n if (s.fill > t.maxFill || Math.min(...margins(s.rect)) < t.borderMargin) {\n return { ready: false, hint: 'Move back a little โ fit the whole document' }\n }\n if (s.fill < t.minFill) return { ready: false, hint: 'Move closer' }\n if (Math.abs(s.centerX - 0.5) > t.centerTol || Math.abs(s.centerY - 0.5) > t.centerTol) {\n return { ready: false, hint: 'Center the document' }\n }\n if (s.sharpness < t.minSharpness) return { ready: false, hint: 'Hold steady to focus' }\n return { ready: true, hint: 'Looks good โ hold steady' }\n}\n\n/**\n * Whether the sample is a well-framed, in-focus document ready to capture.\n *\n * @param s\n * @param t\n * @returns\n */\nexport function isDocumentReady(s: DocumentSample, t: DocumentTuning = DEFAULT_DOCUMENT_TUNING): boolean {\n return documentGuidance(s, t).ready\n}\n\nclass CanvasDocumentAnalyzer implements DocumentAnalyzer {\n private canvas: HTMLCanvasElement | null = null\n private ctx: CanvasRenderingContext2D | null = null\n\n constructor(private readonly tuning: DocumentTuning) {}\n\n ready(): Promise<boolean> {\n try {\n if (typeof document === 'undefined') return Promise.resolve(false)\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d', { willReadFrequently: true })\n if (!ctx) return Promise.resolve(false)\n this.canvas = canvas\n this.ctx = ctx\n return Promise.resolve(true)\n } catch {\n return Promise.resolve(false)\n }\n }\n\n analyze(video: HTMLVideoElement): DocumentSample | null {\n if (!this.ctx || !this.canvas || !video.videoWidth) return null\n const tw = this.tuning.sampleWidth\n const th = Math.max(1, Math.round((tw * video.videoHeight) / video.videoWidth))\n this.canvas.width = tw\n this.canvas.height = th\n let data: Uint8ClampedArray\n try {\n this.ctx.drawImage(video, 0, 0, tw, th)\n data = this.ctx.getImageData(0, 0, tw, th).data\n } catch {\n return null\n }\n const gray = new Float32Array(tw * th)\n for (let i = 0, p = 0; i < gray.length; i += 1, p += 4) {\n gray[i] = 0.299 * data[p]! + 0.587 * data[p + 1]! + 0.114 * data[p + 2]!\n }\n return analyzeDocumentGray(gray, tw, th)\n }\n\n close(): void {\n this.canvas = null\n this.ctx = null\n }\n}\n\n/**\n * The default (canvas edge-projection) document analyzer.\n *\n * @param tuning\n * @returns\n */\nexport function createDefaultDocumentAnalyzer(tuning: DocumentTuning = DEFAULT_DOCUMENT_TUNING): DocumentAnalyzer {\n return new CanvasDocumentAnalyzer(tuning)\n}\n"],"mappings":"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"}
|
package/dist/face.d.mts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { LivenessChallenge } from "@arkyc/types";
|
|
2
|
+
|
|
3
|
+
//#region src/face.d.ts
|
|
4
|
+
/** A normalized read of the current frame's face. */
|
|
5
|
+
interface FaceSample {
|
|
6
|
+
present: boolean;
|
|
7
|
+
/** Face bounding-box centre, normalized 0โ1. */
|
|
8
|
+
centerX: number;
|
|
9
|
+
centerY: number;
|
|
10
|
+
/** Face bounding-box height, normalized 0โ1 (proxy for distance). */
|
|
11
|
+
scale: number;
|
|
12
|
+
/** Head turn: <0 toward one side, >0 the other (approx, from landmark asymmetry). */
|
|
13
|
+
turn: number;
|
|
14
|
+
/** Head pitch proxy: nose-to-eye offset over face height (relative changes matter). */
|
|
15
|
+
pitch: number;
|
|
16
|
+
/** Eyes-closed score 0โ1 (blendshape). */
|
|
17
|
+
blink: number;
|
|
18
|
+
/** Smile score 0โ1 (blendshape). */
|
|
19
|
+
smile: number;
|
|
20
|
+
}
|
|
21
|
+
interface FaceAnalyzer {
|
|
22
|
+
/** Load models. Resolves `false` if unavailable โ callers then go manual. */
|
|
23
|
+
ready(): Promise<boolean>;
|
|
24
|
+
/** Analyze the current video frame, or `null` if it can't be read. */
|
|
25
|
+
analyze(video: HTMLVideoElement): FaceSample | null;
|
|
26
|
+
/** Release resources. */
|
|
27
|
+
close(): void;
|
|
28
|
+
}
|
|
29
|
+
/** The default (real, MediaPipe-backed) analyzer. */
|
|
30
|
+
declare function createDefaultFaceAnalyzer(): FaceAnalyzer;
|
|
31
|
+
/** A challenge detector: fed frame samples, returns `true` once satisfied. */
|
|
32
|
+
interface ChallengeDetector {
|
|
33
|
+
feed(sample: FaceSample): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* 0โ1 progress toward satisfying the challenge, updated on each `feed` โ drives
|
|
36
|
+
* the UI's hold-progress ring. Multi-stage gestures (blink/nod) step through
|
|
37
|
+
* intermediate values; sustained gestures ramp with the hold streak.
|
|
38
|
+
*/
|
|
39
|
+
readonly progress: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Detection thresholds. These are the knobs to tune against a real camera โ
|
|
43
|
+
* use the calibration harness (`playground/calibration.html`) to read live
|
|
44
|
+
* signal values and adjust. All are overridable per-widget via `faceTuning`.
|
|
45
|
+
*/
|
|
46
|
+
interface FaceTuning {
|
|
47
|
+
/** |turn| asymmetry needed to count as a head turn. */
|
|
48
|
+
turn: number;
|
|
49
|
+
/** Mouth-smile blendshape score needed to count as a smile. */
|
|
50
|
+
smile: number;
|
|
51
|
+
/** Eye-blink score above which the eyes count as closed. */
|
|
52
|
+
blinkClosed: number;
|
|
53
|
+
/** Eye-blink score below which they count as re-opened. */
|
|
54
|
+
blinkOpen: number;
|
|
55
|
+
/** Consecutive frames a condition must hold before it fires. */
|
|
56
|
+
hold: number;
|
|
57
|
+
/** Pitch increase (looking down) needed to arm a nod. */
|
|
58
|
+
nodDown: number;
|
|
59
|
+
/** Pitch return delta (back up) that completes a nod. */
|
|
60
|
+
nodReturn: number;
|
|
61
|
+
/** Face must grow past `baseline * closerFactor` for move-closer. */
|
|
62
|
+
closerFactor: number;
|
|
63
|
+
/** Selfie framing tolerances (distance from frame centre / size window). */
|
|
64
|
+
selfieCenterTol: number;
|
|
65
|
+
selfieCenterYTol: number;
|
|
66
|
+
selfieMinScale: number;
|
|
67
|
+
selfieMaxScale: number;
|
|
68
|
+
}
|
|
69
|
+
declare const DEFAULT_TUNING: FaceTuning;
|
|
70
|
+
/**
|
|
71
|
+
* Build a stateful detector for a single active-liveness challenge.
|
|
72
|
+
*
|
|
73
|
+
* @param challenge
|
|
74
|
+
* @param t
|
|
75
|
+
* @returns
|
|
76
|
+
*/
|
|
77
|
+
declare function makeChallengeDetector(challenge: LivenessChallenge, t?: FaceTuning): ChallengeDetector;
|
|
78
|
+
/** Whether a face is well-framed for a selfie (present, centred, right distance). */
|
|
79
|
+
declare function isSelfieReady(s: FaceSample, t?: FaceTuning): boolean;
|
|
80
|
+
//#endregion
|
|
81
|
+
export { ChallengeDetector, DEFAULT_TUNING, FaceAnalyzer, FaceSample, FaceTuning, createDefaultFaceAnalyzer, isSelfieReady, makeChallengeDetector };
|
|
82
|
+
//# sourceMappingURL=face.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"face.d.mts","names":[],"sources":["../src/face.ts"],"mappings":";;;;UAaiB,UAAA;EACf,OAAA;EAaA;EAXA,OAAA;EACA,OAAA;EAae;EAXf,KAAA;;EAEA,IAAA;EAae;EAXf,KAAA;EAW4C;EAT5C,KAAA;EAOA;EALA,KAAA;AAAA;AAAA,UAGe,YAAA;EAIP;EAFR,KAAA,IAAS,OAAA;EAIT;EAFA,OAAA,CAAQ,KAAA,EAAO,gBAAA,GAAmB,UAAA;EAE7B;EAAL,KAAA;AAAA;;iBAqJc,yBAAA,IAA6B,YAAY;AAAA;AAAA,UAKxC,iBAAA;EACf,IAAA,CAAK,MAAA,EAAQ,UAAU;;;;;;WAMd,QAAA;AAAA;AAAQ;AAQnB;;;;AARmB,UAQF,UAAA;EAIf;EAFA,IAAA;EAMA;EAJA,KAAA;EAQA;EANA,WAAA;EAUA;EARA,SAAA;EAWA;EATA,IAAA;EAWA;EATA,OAAA;EASc;EAPd,SAAA;EAuBD;EArBC,YAAA;EAqBD;EAnBC,eAAA;EACA,gBAAA;EACA,cAAA;EACA,cAAA;AAAA;AAAA,cAGW,cAAA,EAAgB,UAa5B;;;;;;;;iBASe,qBAAA,CAAsB,SAAA,EAAW,iBAAA,EAAmB,CAAA,GAAG,UAAA,GAA8B,iBAAA;;iBAsGrF,aAAA,CAAc,CAAA,EAAG,UAAA,EAAY,CAAA,GAAG,UAA2B"}
|