1import { UnavailabilityError } from 'expo-errors'; 2import mapValues from 'lodash.mapvalues'; 3import PropTypes from 'prop-types'; 4import React from 'react'; 5import { Platform, ViewProps, ViewPropTypes } from 'react-native'; 6 7import ExpoBarCodeScannerModule from './ExpoBarCodeScannerModule'; 8import ExpoBarCodeScannerView from './ExpoBarCodeScannerView'; 9 10const { BarCodeType, Type } = ExpoBarCodeScannerModule; 11 12const EVENT_THROTTLE_MS = 500; 13 14type BarCodeEvent = { 15 type: string; 16 data: string; 17 [key: string]: any; 18}; 19 20export type BarCodeEventCallbackArguments = { 21 nativeEvent: BarCodeEvent; 22}; 23 24export type BarCodeScannedCallback = (params: BarCodeEvent) => void; 25 26export interface BarCodeScannerProps extends ViewProps { 27 type?: 'front' | 'back' | number; 28 barCodeTypes?: string[]; 29 onBarCodeScanned: BarCodeScannedCallback; 30} 31 32export class BarCodeScanner extends React.Component<BarCodeScannerProps> { 33 lastEvents: { [key: string]: any } = {}; 34 lastEventsTimes: { [key: string]: any } = {}; 35 36 static Constants = { 37 BarCodeType, 38 Type, 39 }; 40 41 static ConversionTables = { 42 type: Type, 43 }; 44 45 static propTypes = { 46 ...ViewPropTypes, 47 onBarCodeScanned: PropTypes.func, 48 barCodeTypes: PropTypes.array, 49 type: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 50 }; 51 52 static defaultProps = { 53 type: Type.back, 54 barCodeTypes: Object.values(BarCodeType), 55 }; 56 57 static async scanFromURLAsync( 58 url: string, 59 barCodeTypes: string[] = Object.values(BarCodeType) 60 ): Promise<{ type: string; data: string }> { 61 if (!ExpoBarCodeScannerModule.scanFromURLAsync) { 62 throw new UnavailabilityError('expo-barcode-scanner', 'scanFromURLAsync'); 63 } 64 if (Array.isArray(barCodeTypes) && !barCodeTypes.length) { 65 throw new Error('No barCodeTypes specified; provide at least one barCodeType for scanner'); 66 } 67 68 if (Platform.OS === 'ios') { 69 if (Array.isArray(barCodeTypes) && !barCodeTypes.includes(BarCodeType.qr)) { 70 // Only QR type is supported on iOS, fail if one tries to use other types 71 throw new Error('Only QR type is supported by scanFromURLAsync() on iOS'); 72 } 73 // on iOS use only supported QR type 74 return await ExpoBarCodeScannerModule.scanFromURLAsync(url, [BarCodeType.qr]); 75 } 76 77 // On other platforms, if barCodeTypes is not provided, use all available types 78 return await ExpoBarCodeScannerModule.scanFromURLAsync(url, barCodeTypes); 79 } 80 81 render() { 82 const nativeProps = this.convertNativeProps(this.props); 83 const { onBarCodeScanned } = this.props; 84 return ( 85 <ExpoBarCodeScannerView 86 {...nativeProps} 87 onBarCodeScanned={this.onObjectDetected(onBarCodeScanned)} 88 /> 89 ); 90 } 91 92 // coordinates of cornerPoints and boundingBox are represented in DP (Display-Indepent Points) unit 93 // React Native is using the same unit 94 onObjectDetected = (callback?: BarCodeScannedCallback) => ({ 95 nativeEvent, 96 }: BarCodeEventCallbackArguments) => { 97 const { type } = nativeEvent; 98 if ( 99 this.lastEvents[type] && 100 this.lastEventsTimes[type] && 101 JSON.stringify(nativeEvent) === this.lastEvents[type] && 102 Date.now() - this.lastEventsTimes[type] < EVENT_THROTTLE_MS 103 ) { 104 return; 105 } 106 107 if (callback) { 108 callback(nativeEvent); 109 this.lastEventsTimes[type] = new Date(); 110 this.lastEvents[type] = JSON.stringify(nativeEvent); 111 } 112 }; 113 114 convertNativeProps(props: BarCodeScannerProps) { 115 const newProps = mapValues(props, this.convertProp); 116 return newProps; 117 } 118 119 convertProp(value: any, key: string): any { 120 if (typeof value === 'string' && BarCodeScanner.ConversionTables[key]) { 121 return BarCodeScanner.ConversionTables[key][value]; 122 } 123 return value; 124 } 125} 126 127export const { Constants } = BarCodeScanner; 128