1import { 2 PermissionResponse, 3 PermissionStatus, 4 PermissionHookOptions, 5 createPermissionHook, 6 UnavailabilityError, 7} from 'expo-modules-core'; 8import * as React from 'react'; 9import { Platform, ViewProps } from 'react-native'; 10 11import ExpoBarCodeScannerModule from './ExpoBarCodeScannerModule'; 12import ExpoBarCodeScannerView from './ExpoBarCodeScannerView'; 13 14const { BarCodeType, Type } = ExpoBarCodeScannerModule; 15 16const EVENT_THROTTLE_MS = 500; 17 18// @needsAudit 19/** 20 * Those coordinates are represented in the coordinate space of the barcode source (e.g. when you 21 * are using the barcode scanner view, these values are adjusted to the dimensions of the view). 22 */ 23export type BarCodePoint = { 24 /** 25 * The `x` coordinate value. 26 */ 27 x: number; 28 /** 29 * The `y` coordinate value. 30 */ 31 y: number; 32}; 33 34// @needsAudit 35export type BarCodeSize = { 36 /** 37 * The height value. 38 */ 39 height: number; 40 /** 41 * The width value. 42 */ 43 width: number; 44}; 45 46// @needsAudit 47export type BarCodeBounds = { 48 /** 49 * The origin point of the bounding box. 50 */ 51 origin: BarCodePoint; 52 /** 53 * The size of the bounding box. 54 */ 55 size: BarCodeSize; 56}; 57 58// @needsAudit 59export type BarCodeScannerResult = { 60 /** 61 * The barcode type. 62 */ 63 type: string; 64 /** 65 * The information encoded in the bar code. 66 */ 67 data: string; 68 /** 69 * The [BarCodeBounds](#barcodebounds) object. 70 * `bounds` in some case will be representing an empty rectangle. 71 * Moreover, `bounds` doesn't have to bound the whole barcode. 72 * For some types, they will represent the area used by the scanner. 73 */ 74 bounds: BarCodeBounds; 75 /** 76 * Corner points of the bounding box. 77 * `cornerPoints` is not always available and may be empty. On iOS, for `code39` and `pdf417` 78 * you don't get this value. 79 */ 80 cornerPoints: BarCodePoint[]; 81}; 82 83// @docsMissing 84export type BarCodeEvent = BarCodeScannerResult & { 85 target?: number; 86}; 87 88// @docsMissing 89export type BarCodeEventCallbackArguments = { 90 nativeEvent: BarCodeEvent; 91}; 92 93// @docsMissing 94export type BarCodeScannedCallback = (params: BarCodeEvent) => void; 95 96// @needsAudit 97export type BarCodeScannerProps = ViewProps & { 98 /** 99 * Camera facing. Use one of `BarCodeScanner.Constants.Type`. Use either `Type.front` or `Type.back`. 100 * Same as `Camera.Constants.Type`. 101 * @default Type.back 102 */ 103 type?: 'front' | 'back' | number; 104 /** 105 * An array of bar code types. Usage: `BarCodeScanner.Constants.BarCodeType.<codeType>` where 106 * `codeType` is one of these [listed above](#supported-formats). Defaults to all supported bar 107 * code types. It is recommended to provide only the bar code formats you expect to scan to 108 * minimize battery usage. 109 * 110 * For example: `barCodeTypes={[BarCodeScanner.Constants.BarCodeType.qr]}`. 111 */ 112 barCodeTypes?: string[]; 113 /** 114 * A callback that is invoked when a bar code has been successfully scanned. The callback is 115 * provided with an [BarCodeScannerResult](#barcodescannerresult). 116 * > __Note:__ Passing `undefined` to the `onBarCodeScanned` prop will result in no scanning. This 117 * > can be used to effectively "pause" the scanner so that it doesn't continually scan even after 118 * > data has been retrieved. 119 */ 120 onBarCodeScanned?: BarCodeScannedCallback; 121}; 122 123export class BarCodeScanner extends React.Component<BarCodeScannerProps> { 124 lastEvents: { [key: string]: any } = {}; 125 lastEventsTimes: { [key: string]: any } = {}; 126 127 static Constants = { 128 BarCodeType, 129 Type, 130 }; 131 132 static ConversionTables = { 133 type: Type, 134 }; 135 136 static defaultProps = { 137 type: Type.back, 138 barCodeTypes: Object.values(BarCodeType), 139 }; 140 141 // @needsAudit 142 /** 143 * Checks user's permissions for accessing the camera. 144 * @return Return a promise that fulfills to an object of type [`PermissionResponse`](#permissionresponse). 145 */ 146 static async getPermissionsAsync(): Promise<PermissionResponse> { 147 return ExpoBarCodeScannerModule.getPermissionsAsync(); 148 } 149 150 // @needsAudit 151 /** 152 * Asks the user to grant permissions for accessing the camera. 153 * 154 * On iOS this will require apps to specify the `NSCameraUsageDescription` entry in the `Info.plist`. 155 * @return Return a promise that fulfills to an object of type [`PermissionResponse`](#permissionresponse). 156 */ 157 static async requestPermissionsAsync(): Promise<PermissionResponse> { 158 return ExpoBarCodeScannerModule.requestPermissionsAsync(); 159 } 160 161 // @needsAudit 162 /** 163 * Check or request permissions for the barcode scanner. 164 * This uses both `requestPermissionAsync` and `getPermissionsAsync` to interact with the permissions. 165 * 166 * @example 167 * ```ts 168 * const [permissionResponse, requestPermission] = BarCodeScanner.usePermissions(); 169 * ``` 170 */ 171 static usePermissions = createPermissionHook({ 172 getMethod: BarCodeScanner.getPermissionsAsync, 173 requestMethod: BarCodeScanner.requestPermissionsAsync, 174 }); 175 176 // @needsAudit 177 /** 178 * Scan bar codes from the image given by the URL. 179 * @param url URL to get the image from. 180 * @param barCodeTypes An array of bar code types. Defaults to all supported bar code types on 181 * the platform. 182 * > __Note:__ Only QR codes are supported on iOS. 183 * @return A possibly empty array of objects of the `BarCodeScannerResult` shape, where the type 184 * refers to the bar code type that was scanned and the data is the information encoded in the bar 185 * code. 186 */ 187 static async scanFromURLAsync( 188 url: string, 189 barCodeTypes: string[] = Object.values(BarCodeType) 190 ): Promise<BarCodeScannerResult[]> { 191 if (!ExpoBarCodeScannerModule.scanFromURLAsync) { 192 throw new UnavailabilityError('expo-barcode-scanner', 'scanFromURLAsync'); 193 } 194 if (Array.isArray(barCodeTypes) && !barCodeTypes.length) { 195 throw new Error('No barCodeTypes specified; provide at least one barCodeType for scanner'); 196 } 197 198 if (Platform.OS === 'ios') { 199 if (Array.isArray(barCodeTypes) && !barCodeTypes.includes(BarCodeType.qr)) { 200 // Only QR type is supported on iOS, fail if one tries to use other types 201 throw new Error('Only QR type is supported by scanFromURLAsync() on iOS'); 202 } 203 // on iOS use only supported QR type 204 return await ExpoBarCodeScannerModule.scanFromURLAsync(url, [BarCodeType.qr]); 205 } 206 207 // On other platforms, if barCodeTypes is not provided, use all available types 208 return await ExpoBarCodeScannerModule.scanFromURLAsync(url, barCodeTypes); 209 } 210 211 render() { 212 const nativeProps = this.convertNativeProps(this.props); 213 const { onBarCodeScanned } = this.props; 214 return ( 215 <ExpoBarCodeScannerView 216 {...nativeProps} 217 onBarCodeScanned={this.onObjectDetected(onBarCodeScanned)} 218 /> 219 ); 220 } 221 222 /** 223 * @hidden 224 */ 225 onObjectDetected = 226 (callback?: BarCodeScannedCallback) => 227 ({ nativeEvent }: BarCodeEventCallbackArguments) => { 228 const { type } = nativeEvent; 229 if ( 230 this.lastEvents[type] && 231 this.lastEventsTimes[type] && 232 JSON.stringify(nativeEvent) === this.lastEvents[type] && 233 Date.now() - this.lastEventsTimes[type] < EVENT_THROTTLE_MS 234 ) { 235 return; 236 } 237 238 if (callback) { 239 callback(nativeEvent); 240 this.lastEventsTimes[type] = new Date(); 241 this.lastEvents[type] = JSON.stringify(nativeEvent); 242 } 243 }; 244 245 /** 246 * @hidden 247 */ 248 convertNativeProps(props: BarCodeScannerProps) { 249 const nativeProps: BarCodeScannerProps = {}; 250 251 for (const [key, value] of Object.entries(props)) { 252 if (typeof value === 'string' && BarCodeScanner.ConversionTables[key]) { 253 nativeProps[key] = BarCodeScanner.ConversionTables[key][value]; 254 } else { 255 nativeProps[key] = value; 256 } 257 } 258 259 return nativeProps; 260 } 261} 262 263export { PermissionResponse, PermissionStatus, PermissionHookOptions }; 264export const { Constants, getPermissionsAsync, requestPermissionsAsync, scanFromURLAsync } = 265 BarCodeScanner; 266