@privateid/ultra-web-sdk-alpha
Version:
CryptoNets WebAssembly SDK
573 lines • 26.6 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { UAParser } from 'ua-parser-js';
import { CameraFaceMode, FacingMode } from './camera.types';
import { PORTRAIT, LANDSCAPE, BACK_CAMERA_WORDS, CAMERA_HEIGHT, CAMERA_WIDTH, CAMERA_LOW_RES_WIDTH, } from './camera.constants';
import { printLogs } from '../../global/shared.utils';
const cameraLowResHeight = 480;
const cameraHeight = 1440;
const cameraWidth = 2560;
const mobileCameraResWidth = 1200;
const mobileCameraResHeight = 1440;
export function getScreenOrientation() {
if (window.innerHeight > window.innerWidth) {
return PORTRAIT;
}
return LANDSCAPE;
}
export function getUserAgent() {
const userAgent = typeof window !== 'undefined' && navigator && window.navigator.userAgent;
return userAgent || undefined;
}
export function parseUserAgent() {
// This maybe evaluated in next.js Node rather than in Browser
if (!getUserAgent()) {
return undefined;
}
const parser = new UAParser();
return parser.getResult();
}
export function isPortrait() {
return getScreenOrientation() === PORTRAIT;
}
// TODO: Refactor this to use the FacingMode enum
export function isBackCameraAndPortrait(faceMode) {
return isPortrait();
}
// TODO: Refactor these `isSomethingUA` functions to be one.
export function isMobileUA() {
const result = parseUserAgent();
if (!result || !result.device || !result.device.type) {
return false;
}
return ['mobile', 'tablet'].includes(result.device.type);
}
export function isFirefoxUA() {
var _a;
const result = parseUserAgent();
return ((_a = result === null || result === void 0 ? void 0 : result.browser) === null || _a === void 0 ? void 0 : _a.name) === 'Firefox';
}
export function isSafariUA() {
var _a;
const result = parseUserAgent();
return ((_a = result === null || result === void 0 ? void 0 : result.browser) === null || _a === void 0 ? void 0 : _a.name) === 'Safari';
}
export function isChromeUA() {
var _a;
const result = parseUserAgent();
return ((_a = result === null || result === void 0 ? void 0 : result.browser) === null || _a === void 0 ? void 0 : _a.name) === 'Chrome';
}
export function isSamsungUA() {
var _a;
const result = parseUserAgent();
return ((_a = result === null || result === void 0 ? void 0 : result.device) === null || _a === void 0 ? void 0 : _a.vendor) === 'Samsung';
}
export function isAndroid() {
var _a;
const result = parseUserAgent();
return ((_a = result === null || result === void 0 ? void 0 : result.os) === null || _a === void 0 ? void 0 : _a.name) === 'Android';
}
/**
* See https://developer.chrome.com/docs/multidevice/user-agent/#webview_user_agent
* This onloy detects the WebView after Android 4.4. It'd return false for older
* Android version.
*/
export function isAndroidWebView() {
var _a, _b;
const result = parseUserAgent();
// If the user-agent string contains "wv", 'ua-parser-js' will return the
// string "WebView" as part of its browser name.
return ((_a = result === null || result === void 0 ? void 0 : result.os) === null || _a === void 0 ? void 0 : _a.name) === 'Android' && /WebView/.test(String((_b = result === null || result === void 0 ? void 0 : result.browser) === null || _b === void 0 ? void 0 : _b.name));
}
export function isAndroid12OrAbove() {
return getMajorAndroidVersion() >= 12;
}
export function isIOSUA() {
var _a;
const result = parseUserAgent();
return ((_a = result === null || result === void 0 ? void 0 : result.os) === null || _a === void 0 ? void 0 : _a.name) === 'iOS';
}
export function getMajorAndroidVersion() {
var _a, _b;
const result = parseUserAgent();
const matched = ((_a = result === null || result === void 0 ? void 0 : result.os) === null || _a === void 0 ? void 0 : _a.name) === 'Android' ? String(((_b = result === null || result === void 0 ? void 0 : result.os) === null || _b === void 0 ? void 0 : _b.version) || '').match(/^\d+/) : null;
return matched ? parseInt(matched[0], 10) : NaN;
}
export function getMajorSafariVersion() {
var _a, _b;
const result = parseUserAgent();
const name = (_a = result === null || result === void 0 ? void 0 : result.browser) === null || _a === void 0 ? void 0 : _a.name;
if (name === 'Safari' || name === 'Mobile Safari') {
return parseInt(((_b = result === null || result === void 0 ? void 0 : result.browser) === null || _b === void 0 ? void 0 : _b.version) || '', 10);
}
return NaN;
}
export function getMajorChromeVersion() {
var _a, _b, _c;
const result = parseUserAgent();
return ((_b = (_a = result === null || result === void 0 ? void 0 : result.browser) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.includes('Chrome')) ? parseInt(((_c = result === null || result === void 0 ? void 0 : result.browser) === null || _c === void 0 ? void 0 : _c.version) || '', 10) : NaN;
}
export function getMajorIOSVersion() {
var _a, _b;
const result = parseUserAgent();
const matched = ((_a = result === null || result === void 0 ? void 0 : result.os) === null || _a === void 0 ? void 0 : _a.name) === 'iOS' ? String(((_b = result === null || result === void 0 ? void 0 : result.os) === null || _b === void 0 ? void 0 : _b.version) || '').match(/^\d+/) : null;
return matched ? parseInt(matched[0], 10) : NaN;
}
export function getMajorMinorOsVersion() {
var _a;
const result = parseUserAgent();
const version = String(((_a = result === null || result === void 0 ? void 0 : result.os) === null || _a === void 0 ? void 0 : _a.version) || '');
const matched = version.match(/(^\d+)(\.\d+)?/);
return (matched && matched[0]) || 'unknown';
}
export function isWindows() {
var _a, _b;
return getPlatform() === 'desktop' && ((_b = (_a = parseUserAgent()) === null || _a === void 0 ? void 0 : _a.os) === null || _b === void 0 ? void 0 : _b.name) === 'Windows';
}
export function isMac() {
var _a, _b;
return getPlatform() === 'desktop' && ((_b = (_a = parseUserAgent()) === null || _a === void 0 ? void 0 : _a.os) === null || _b === void 0 ? void 0 : _b.name) === 'Mac OS';
}
export function isLinux() {
var _a;
const ua = parseUserAgent();
return ((_a = ua === null || ua === void 0 ? void 0 : ua.os) === null || _a === void 0 ? void 0 : _a.name) === 'Linux';
}
// This returns the "platform"- iphone vs android vs desktop used for reporting metrics.
// Do not make this into something with high cardinality- that will impact SignalFX.
// If you need to alter/add to these values, please make sure to update the relevant
// SignalFX metrics as well.
export function getPlatform() {
if (isAndroid()) {
return 'android';
}
if (isIOSUA()) {
return 'iphone';
}
return 'desktop';
}
export function isMobileDevice() {
return isMobileUA() || isAndroidDesktop() || isIOSDesktop();
}
// Whether it's an IOS browser requesting desktop site.
export function isIOSDesktop() {
// IOS browser => Request Desktop Site.
return isMac() && window.navigator.maxTouchPoints > 1;
}
export function isIOS() {
return isIOSUA() || isIOSDesktop();
}
export function isIOS15OrGreater() {
if (isIOSDesktop()) {
// In the past, the version of Safari has been in sync with the version of
// iOS. Therefore, we could use Safari's version as a proxy to determine
// the version of iOS. However, it's not possible to determine the iOS
// version using other types of browsers.
return getMajorSafariVersion() >= 15;
}
// iOS Mobile.
return getMajorIOSVersion() >= 15;
}
// Whether it's an Android browser requesting desktop site.
export function isAndroidDesktop() {
// Android browser => Request Desktop Site.
return isLinux() && window.navigator.maxTouchPoints > 1;
}
const isBackCameraLabel = (label) => {
const lowerCase = (label === null || label === void 0 ? void 0 : label.toLowerCase()) || '';
return BACK_CAMERA_WORDS.some((keyword) => lowerCase === null || lowerCase === void 0 ? void 0 : lowerCase.includes(keyword));
};
export const isMobileBackCameraPortrait = ({ label, facingMode }) => {
const isBackCamera = isBackCameraLabel(label) || (facingMode === null || facingMode === void 0 ? void 0 : facingMode.includes(FacingMode.environment));
return isMobileDevice() && isBackCamera && isPortrait();
};
export function isFaceTimeCamera(label, isDocumentScan) {
return isMac() && label.includes('FaceTime') && isDocumentScan;
}
export const getCameraList = (backOnly) => __awaiter(void 0, void 0, void 0, function* () {
try {
if (!navigator.mediaDevices) {
return [];
}
let cameraList = yield navigator.mediaDevices.enumerateDevices().then((devices) => {
const filteredDevices = devices.filter((device) => {
let filteredLabels = true;
if (!backOnly) {
filteredLabels = !isBackCameraLabel(device.label);
}
return device.kind === 'videoinput' && filteredLabels;
});
return filteredDevices.map((device) => {
// @ts-ignore
if (device === null || device === void 0 ? void 0 : device.getCapabilities) {
// @ts-ignore
return Object.assign(Object.assign({}, device === null || device === void 0 ? void 0 : device.getCapabilities()), { deviceId: device.deviceId, label: device.label });
}
return { deviceId: device.deviceId, label: device.label };
});
});
if (isAndroid()) {
cameraList = cameraList.sort((a, b) => (a.label < b.label ? -1 : 1));
}
return cameraList;
}
catch (error) {
// handleException(error, 'error listing cameras');
return [];
}
});
export const getVideoConstraints = (availableDevices, faceMode, requireHD, isDocumentScan, canvasResolution, hasError = false) => {
// Use 1.5 ratio. US DL are 1.58, passport are 1.42, so take something in the middle
let deviceId = availableDevices === null || availableDevices === void 0 ? void 0 : availableDevices[0];
if (isIOS() && isDocumentScan && availableDevices && (availableDevices === null || availableDevices === void 0 ? void 0 : availableDevices.length) > 2) {
const ultraWideDevice = availableDevices.find(({ label }) => label.includes('Dual Wide') || label.includes('Back Triple'));
deviceId = ultraWideDevice || availableDevices[0];
}
let resizeMode = 'crop-and-scale';
if (isMobileDevice()) {
// Known cases that 'crop-and-scale' does not work.
if (
// Chrome 107+ on Android shows broken video in 'crop-and-scale'.
(isAndroid() && getMajorChromeVersion() >= 107) ||
// Samsung video driver has a bug when using resizeMode
(isSamsungUA() && !isAndroid12OrAbove())) {
resizeMode = 'none';
}
let videoWidth;
let videoHeight;
let aspectRatio;
if ((canvasResolution === null || canvasResolution === void 0 ? void 0 : canvasResolution.width) && (canvasResolution === null || canvasResolution === void 0 ? void 0 : canvasResolution.height)) {
videoWidth = canvasResolution.width;
videoHeight = canvasResolution.height;
aspectRatio = 1.7777777778;
}
else if (faceMode) {
videoWidth = isBackCameraAndPortrait(faceMode)
? // ? { min: cameraLowResHeight, max: mobileCameraResHeight }
{ ideal: mobileCameraResHeight }
: { ideal: CAMERA_LOW_RES_WIDTH };
videoHeight = isBackCameraAndPortrait(faceMode)
? // ? { min: cameraLowResHeight, max: mobileCameraResHeight }
{ ideal: mobileCameraResHeight }
: undefined;
aspectRatio = isBackCameraAndPortrait(faceMode) ? 1 : 1.7777777778;
}
return {
audio: false,
video: {
facingMode: faceMode,
resizeMode,
width: videoWidth,
height: videoHeight,
deviceId: deviceId ? deviceId.deviceId : undefined,
aspectRatio,
focusMode: 'continuous',
},
};
}
return {
audio: false,
video: {
aspectRatio: 1.5,
// deviceId: deviceId ? deviceId.deviceId : undefined,
height: { min: cameraLowResHeight },
resizeMode,
},
};
};
export function scaleImageData(imageData, scaleFactor) {
const { width, height, data } = imageData;
// Create a new canvas and context for temporary scaling
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
// Set the dimensions of the temporary canvas
tempCanvas.width = width * scaleFactor;
tempCanvas.height = height * scaleFactor;
// Draw the original image data onto the temporary canvas
tempCtx === null || tempCtx === void 0 ? void 0 : tempCtx.putImageData(imageData, 0, 0);
// Get the scaled image data from the temporary canvas
const scaledImageData = tempCtx === null || tempCtx === void 0 ? void 0 : tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
return scaledImageData;
}
export function zoomInCanvasImage(canvas, scaleFactor) {
// Get the current width and height of the canvas
const originalWidth = canvas.width;
const originalHeight = canvas.height;
// Calculate the new dimensions after applying the scale factor
const newWidth = Math.round(originalWidth / scaleFactor);
const newHeight = Math.round(originalHeight / scaleFactor);
// Calculate the amount of width and height to be cropped
const cropWidth = originalWidth - newWidth;
const cropHeight = originalHeight - newHeight;
// Create an offscreen canvas to hold the cropped image
const offscreenCanvas = document.createElement('canvas');
const offscreenCtx = offscreenCanvas.getContext('2d');
// Set the dimensions for the offscreen canvas
offscreenCanvas.width = originalWidth;
offscreenCanvas.height = originalHeight;
const centerX = originalWidth / 2;
const centerY = originalHeight / 2;
const sx = centerX - newWidth / (2 * scaleFactor);
const sy = centerY - newHeight / (2 * scaleFactor);
offscreenCtx === null || offscreenCtx === void 0 ? void 0 : offscreenCtx.drawImage(canvas, sx, sy, newWidth / scaleFactor, newHeight / scaleFactor,
// cropWidth / 2,
// cropHeight / 2, // sourceX, sourceY
// originalWidth - cropWidth,
// originalHeight - cropHeight, // sourceWidth, sourceHeight
0, 0, // destinationX, destinationY
// changing this for testing
newWidth, newHeight);
// Get the image data from the offscreen canvas
const imageData = offscreenCtx === null || offscreenCtx === void 0 ? void 0 : offscreenCtx.getImageData(0, 0, newWidth, newHeight);
// Return the image data
return imageData;
}
export const checkDeviceIsVirtualCamera = (deviceLabel) => {
const virtualCameraKeywords = ['virtual', 'obs', 'software', 'manycam'];
return virtualCameraKeywords.some((keyword) => deviceLabel.toLowerCase().includes(keyword));
};
export function getFilteredVideoDevices() {
return __awaiter(this, void 0, void 0, function* () {
const permission = yield navigator.permissions.query({ name: 'camera' });
if (permission.state === 'denied') {
printLogs('Camera permission is denied', '', 'WARN');
return [];
}
// Request camera permission first to ensure device labels are available
try {
const stream = yield navigator.mediaDevices.getUserMedia({ video: true, audio: false });
stream.getTracks().forEach((track) => track.stop());
}
catch (error) {
printLogs('Camera permission denied or not available', error, 'WARN');
}
const devices = yield navigator.mediaDevices.enumerateDevices();
const filtered = devices.filter((d) => d.kind === 'videoinput' && !checkDeviceIsVirtualCamera(d.label));
return filtered;
});
}
export function getVideoInputDevices() {
return __awaiter(this, void 0, void 0, function* () {
const devices = yield navigator.mediaDevices.enumerateDevices();
return devices.filter((d) => d.kind === 'videoinput');
});
}
export function filterDevicesByPattern(devices, pattern) {
return devices.filter((d) => pattern.test(d.label));
}
export function getExternalDevice(devices) {
return devices.length > 1 ? devices.find((device) => !device.label.includes('FaceTime')) : devices[0];
}
export function isWindowsDevice() {
return ['windows', 'win16', 'win32', 'wince'].includes(navigator.platform.toUpperCase());
}
export function getDefaultDevice(devices, deviceId) {
return devices.reduce((acc, val) => {
if (val.deviceId === deviceId) {
// @ts-ignore
if (val === null || val === void 0 ? void 0 : val.getCapabilities) {
// @ts-ignore
acc.push(Object.assign(Object.assign({}, val.getCapabilities()), { label: val.label }));
}
else {
acc.push(val);
}
}
return acc;
}, []);
}
export function getBestResolution(isMacFaceTimeCamera, deviceCapabilites, canvasResolution) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
let resolution = (canvasResolution === null || canvasResolution === void 0 ? void 0 : canvasResolution.width) || Math.min(((_b = (_a = deviceCapabilites[0]) === null || _a === void 0 ? void 0 : _a.width) === null || _b === void 0 ? void 0 : _b.max) || CAMERA_HEIGHT, CAMERA_WIDTH);
let hasError = true;
let constraints = {
audio: false,
video: {
deviceId: deviceCapabilites[0].deviceId,
width: { ideal: resolution },
height: (canvasResolution === null || canvasResolution === void 0 ? void 0 : canvasResolution.height) ? { ideal: canvasResolution === null || canvasResolution === void 0 ? void 0 : canvasResolution.height } : { ideal: CAMERA_HEIGHT },
aspectRatio: isMacFaceTimeCamera ? 1 : 1.777777778,
resizeMode: 'none',
facingMode: isWindowsDevice() ? 'user' : undefined,
},
};
let stream;
while (hasError) {
try {
stream = yield navigator.mediaDevices.getUserMedia(constraints);
if (stream)
hasError = false;
}
catch (e) {
resolution = getTheNextResolutionAvailable(resolution);
constraints = Object.assign(Object.assign({}, constraints), { video: Object.assign(Object.assign({}, constraints.video), { width: { ideal: resolution } }) });
}
}
return stream;
});
}
/**
* This function open camera, and returns the stream, current faceMode and the list of available media devices
* @category Face
* @param domElement id of the video tag
*/
export function getTheNextResolutionAvailable(currentResolution) {
printLogs(`getTheNextResolutionAvailable `, currentResolution);
const resolutions = [2560, 1920, 1600, 1552, 1440, 1280, 1024, 960, 800, 720, 704, 640].sort((a, b) => b - a);
return resolutions.find((e) => e < currentResolution) || 640;
}
export function startVideo(videoElement, stream) {
return __awaiter(this, void 0, void 0, function* () {
const element = document.getElementById(videoElement);
element.srcObject = stream;
element.play();
yield new Promise((resolve) => (element.onplaying = resolve));
enableMutationObserver(element, stream);
});
}
export function enableMutationObserver(videoElement, stream) {
const videoMutationObserver = new MutationObserver(() => {
if (videoElement.srcObject !== stream) {
printLogs('Unauthorized video source change detected! Resetting to correct camera.', '', 'WARN');
// eslint-disable-next-line no-param-reassign
videoElement.srcObject = stream;
}
});
videoMutationObserver.observe(videoElement, { attributes: true, attributeFilter: ['srcObject'] });
}
export function getDevicesWithCapabilities(devices) {
return devices.map((device) => {
// @ts-ignore
if (device.getCapabilities) {
// @ts-ignore
const capabilities = device.getCapabilities();
return Object.assign(Object.assign({}, capabilities), { label: device.label });
}
return device;
});
}
export function calculateWidth(canvasResolution, isPortraitMobileCamera, resolution) {
if (canvasResolution === null || canvasResolution === void 0 ? void 0 : canvasResolution.width) {
return resolution;
}
if (isPortraitMobileCamera) {
return CAMERA_HEIGHT;
}
return resolution;
}
export function calculateHeight(canvasResolution) {
if (canvasResolution === null || canvasResolution === void 0 ? void 0 : canvasResolution.height) {
return canvasResolution.height;
}
return CAMERA_HEIGHT;
}
export function calculateAspectRatio(canvasResolution, isPortraitMobileCamera) {
if (canvasResolution === null || canvasResolution === void 0 ? void 0 : canvasResolution.width) {
return 1.7777777778;
}
if (isPortraitMobileCamera) {
return 1;
}
return 1.7777777778;
}
export function createBasicVideoConstraints(options) {
const videoConstraints = {};
if (options.deviceId)
videoConstraints.deviceId = options.deviceId;
if (options.width)
videoConstraints.width = { ideal: options.width };
if (options.height)
videoConstraints.height = { ideal: options.height };
if (options.aspectRatio)
videoConstraints.aspectRatio = options.aspectRatio;
if (options.facingMode)
videoConstraints.facingMode = options.facingMode;
if (options.focusMode)
videoConstraints.focusMode = options.focusMode;
if (options.resizeMode)
videoConstraints.resizeMode = options.resizeMode;
return {
audio: false,
video: videoConstraints,
};
}
export function createFirefoxVideoConstraints(faceMode, canvasResolution) {
return createBasicVideoConstraints({
width: (canvasResolution === null || canvasResolution === void 0 ? void 0 : canvasResolution.width) || CAMERA_LOW_RES_WIDTH,
height: (canvasResolution === null || canvasResolution === void 0 ? void 0 : canvasResolution.height) || 1080,
aspectRatio: 1.7777777778,
focusMode: 'continuous',
facingMode: faceMode === CameraFaceMode.back ? 'environment' : 'user',
});
}
export function createMacSafariVideoConstraints(deviceId, resolution, canvasResolution) {
return createBasicVideoConstraints({
deviceId,
width: resolution,
height: canvasResolution === null || canvasResolution === void 0 ? void 0 : canvasResolution.height,
});
}
export function createBestResolutionStream(deviceCapabilites, canvasResolution) {
var _a, _b, _c, _d;
return __awaiter(this, void 0, void 0, function* () {
let resolution = (canvasResolution === null || canvasResolution === void 0 ? void 0 : canvasResolution.width) || Math.min(((_b = (_a = deviceCapabilites[0]) === null || _a === void 0 ? void 0 : _a.width) === null || _b === void 0 ? void 0 : _b.max) || CAMERA_HEIGHT, CAMERA_HEIGHT);
const isPortraitMobileCamera = isMobileBackCameraPortrait(deviceCapabilites[0]);
let hasError = true;
const videoConstraints = {
deviceId: deviceCapabilites[0].deviceId,
width: { ideal: calculateWidth(canvasResolution, isPortraitMobileCamera, resolution) },
height: { ideal: calculateHeight(canvasResolution) },
facingMode: ((_d = (_c = deviceCapabilites[0]) === null || _c === void 0 ? void 0 : _c.facingMode) === null || _d === void 0 ? void 0 : _d[0]) || undefined,
focusMode: 'continuous',
aspectRatio: calculateAspectRatio(canvasResolution, isPortraitMobileCamera),
resizeMode: 'none',
};
const constraints = {
audio: false,
video: videoConstraints,
};
let stream;
while (hasError) {
try {
stream = yield navigator.mediaDevices.getUserMedia(constraints);
hasError = false;
}
catch (e) {
resolution = getTheNextResolutionAvailable(resolution);
constraints.video = Object.assign(Object.assign({}, constraints.video), { width: { ideal: resolution } });
}
}
printLogs(`bestconstraints: `, constraints);
return stream;
});
}
export function prepareCameraResult(stream, devices, faceMode, videoElement) {
return __awaiter(this, void 0, void 0, function* () {
const track = stream.getVideoTracks()[0];
const capabilities = track.getCapabilities ? track.getCapabilities() : null;
const settings = track.getSettings();
printLogs('settings: ', settings);
if (capabilities) {
printLogs('capabilities: ', capabilities);
}
yield startVideo(videoElement, stream);
return {
status: true,
stream,
devices,
faceMode,
settings,
capabilities,
errorMessage: null,
};
});
}
//# sourceMappingURL=camera.utils.js.map