1import { PermissionResponse, PermissionStatus, UnavailabilityError } from 'expo-modules-core';
2
3function getUserMedia(constraints: MediaStreamConstraints): Promise<MediaStream> {
4  if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
5    return navigator.mediaDevices.getUserMedia(constraints);
6  }
7
8  // Some browsers partially implement mediaDevices. We can't just assign an object
9  // with getUserMedia as it would overwrite existing properties.
10  // Here, we will just add the getUserMedia property if it's missing.
11
12  // First get ahold of the legacy getUserMedia, if present
13  const getUserMedia =
14    // TODO: this method is deprecated, migrate to https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
15    navigator.getUserMedia ||
16    navigator.webkitGetUserMedia ||
17    navigator.mozGetUserMedia ||
18    function () {
19      const error: any = new Error('Permission unimplemented');
20      error.code = 0;
21      error.name = 'NotAllowedError';
22      throw error;
23    };
24
25  return new Promise((resolve, reject) => {
26    getUserMedia.call(navigator, constraints, resolve, reject);
27  });
28}
29
30function handleGetUserMediaError({ message }: { message: string }): PermissionResponse {
31  // name: NotAllowedError
32  // code: 0
33  if (message === 'Permission dismissed') {
34    return {
35      status: PermissionStatus.UNDETERMINED,
36      expires: 'never',
37      canAskAgain: true,
38      granted: false,
39    };
40  } else {
41    return {
42      status: PermissionStatus.DENIED,
43      expires: 'never',
44      canAskAgain: true,
45      granted: false,
46    };
47  }
48}
49
50async function handleRequestPermissionsAsync(): Promise<PermissionResponse> {
51  try {
52    await getUserMedia({
53      video: true,
54    });
55    return {
56      status: PermissionStatus.GRANTED,
57      expires: 'never',
58      canAskAgain: true,
59      granted: true,
60    };
61  } catch ({ message }) {
62    return handleGetUserMediaError({ message });
63  }
64}
65
66async function handlePermissionsQueryAsync(): Promise<PermissionResponse> {
67  if (!navigator?.permissions?.query) {
68    throw new UnavailabilityError(
69      'expo-barcode-scanner',
70      'navigator.permissions API is not available'
71    );
72  }
73
74  const { state } = await navigator.permissions.query({ name: 'camera' });
75  switch (state) {
76    case 'prompt':
77      return {
78        status: PermissionStatus.UNDETERMINED,
79        expires: 'never',
80        canAskAgain: true,
81        granted: false,
82      };
83    case 'granted':
84      return {
85        status: PermissionStatus.GRANTED,
86        expires: 'never',
87        canAskAgain: true,
88        granted: true,
89      };
90    case 'denied':
91      return {
92        status: PermissionStatus.DENIED,
93        expires: 'never',
94        canAskAgain: true,
95        granted: false,
96      };
97  }
98}
99
100export default {
101  get name(): string {
102    return 'ExpoBarCodeScannerModule';
103  },
104  get BarCodeType() {
105    return {
106      code39mod43: 'code39mod43',
107      code138: 'code138',
108      interleaved2of5: 'interleaved2of5',
109      aztec: 'aztec',
110      ean13: 'ean13',
111      ean8: 'ean8',
112      qr: 'qr',
113      pdf417: 'pdf417',
114      upc_e: 'upc_e',
115      datamatrix: 'datamatrix',
116      code39: 'code39',
117      code93: 'code93',
118      itf14: 'itf14',
119      codabar: 'codabar',
120      code128: 'code128',
121      upc_a: 'upc_a',
122    };
123  },
124  get Type() {
125    return { front: 'front', back: 'back' };
126  },
127  async requestPermissionsAsync(): Promise<PermissionResponse> {
128    return handleRequestPermissionsAsync();
129  },
130  async getPermissionsAsync(): Promise<PermissionResponse> {
131    return handlePermissionsQueryAsync();
132  },
133};
134