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