1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16let cert = requireInternal('security.cert');
17let webview = requireInternal('web.webview');
18let picker = requireNapi('file.picker');
19let photoAccessHelper = requireNapi('file.photoAccessHelper');
20let cameraPicker = requireNapi('multimedia.cameraPicker');
21let camera = requireNapi('multimedia.camera');
22let accessControl = requireNapi('abilityAccessCtrl');
23let deviceinfo = requireInternal('deviceInfo');
24const PARAM_CHECK_ERROR = 401;
25
26const ERROR_MSG_INVALID_PARAM = 'Invalid input parameter';
27
28let errMsgMap = new Map();
29errMsgMap.set(PARAM_CHECK_ERROR, ERROR_MSG_INVALID_PARAM);
30
31class BusinessError extends Error {
32  constructor(code, errorMsg = 'undefined') {
33    if (errorMsg === 'undefined') {
34      let msg = errMsgMap.get(code);
35      super(msg);
36    } else {
37      super(errorMsg);
38    }
39    this.code = code;
40  }
41}
42
43function getCertificatePromise(certChainData) {
44  let x509CertArray = [];
45  if (!(certChainData instanceof Array)) {
46    console.log('failed, cert chain data type is not array');
47    return Promise.all(x509CertArray);
48  }
49
50  for (let i = 0; i < certChainData.length; i++) {
51    let encodeBlobData = {
52      data: certChainData[i],
53      encodingFormat: cert.EncodingFormat.FORMAT_DER
54    };
55    x509CertArray[i] = cert.createX509Cert(encodeBlobData);
56  }
57
58  return Promise.all(x509CertArray);
59}
60
61function takePhoto(param, selectResult) {
62  try {
63    let pickerProfileOptions = {
64      'cameraPosition': camera.CameraPosition.CAMERA_POSITION_BACK,
65    };
66    let acceptTypes = param.getAcceptType();
67    let mediaType = [];
68    if (isContainImageMimeType(acceptTypes) && !isContainVideoMimeType(acceptTypes)) {
69      mediaType.push(cameraPicker.PickerMediaType.PHOTO);
70    } else if (!isContainImageMimeType(acceptTypes) && isContainVideoMimeType(acceptTypes)) {
71      mediaType.push(cameraPicker.PickerMediaType.VIDEO);
72    } else {
73      mediaType.push(cameraPicker.PickerMediaType.PHOTO);
74      mediaType.push(cameraPicker.PickerMediaType.VIDEO);
75    }
76    cameraPicker.pick(getContext(this), mediaType, pickerProfileOptions)
77    .then((pickerResult) => {
78      if (pickerResult.resultCode === 0) {
79        selectResult.handleFileList([pickerResult.resultUri]);
80      }
81    }).catch((error) => {
82      console.log('selectFile error:' + JSON.stringify(error));
83    });
84
85  } catch (error) {
86    console.log('the pick call failed, error code' + JSON.stringify(error));
87  }
88}
89
90function needShowDialog(params) {
91  let result = false;
92  try {
93    let currentDevice = deviceinfo.deviceType.toLowerCase();
94    if (currentDevice !== 'phone') {
95      return false;
96    }
97    if (params.isCapture()) {
98      console.log('input element contain capture tag, not show dialog');
99      return false;
100    }
101    let acceptTypes = params.getAcceptType();
102    if (isContainImageMimeType(acceptTypes) || isContainVideoMimeType(acceptTypes)) {
103      result = true;
104    }
105  } catch (error) {
106    console.log('show dialog happend error:' + JSON.stringify(error));
107  }
108  return result;
109}
110
111function selectFile(param, result) {
112  try {
113    let documentSelectOptions = createDocumentSelectionOptions(param);
114    let documentPicker = new picker.DocumentViewPicker();
115    documentPicker.select(documentSelectOptions)
116      .then((documentSelectResult) => {
117        if (documentSelectResult && documentSelectResult.length > 0) {
118          let filePath = documentSelectResult;
119          result.handleFileList(filePath);
120        }
121      }).catch((error) => {
122        console.log('selectFile error: ' + JSON.stringify(error));
123      });
124  } catch (error) {
125    console.log('picker error: ' + JSON.stringify(error));
126  }
127}
128
129function createDocumentSelectionOptions(param) {
130  let documentSelectOptions = new picker.DocumentSelectOptions();
131  try {
132    let defaultSelectNumber = 500;
133    let defaultSelectMode = picker.DocumentSelectMode.MIXED;
134    documentSelectOptions.maxSelectNumber = defaultSelectNumber;
135    documentSelectOptions.selectMode = defaultSelectMode;
136    let mode = param.getMode();
137    switch (mode) {
138      case FileSelectorMode.FileOpenMode:
139        documentSelectOptions.maxSelectNumber = 1;
140        documentSelectOptions.selectMode = picker.DocumentSelectMode.FILE;
141        break;
142      case FileSelectorMode.FileOpenMultipleMode:
143        documentSelectOptions.selectMode = picker.DocumentSelectMode.FILE;
144        break;
145      case FileSelectorMode.FileOpenFolderMode:
146        documentSelectOptions.selectMode = picker.DocumentSelectMode.FOLDER;
147        break;
148      case FileSelectorMode.FileSaveMode:
149        break;
150      default:
151        break;
152    }
153    documentSelectOptions.fileSuffixFilters = [];
154    let suffix = param.getAcceptType().join(',');
155    if (suffix) {
156      documentSelectOptions.fileSuffixFilters.push(suffix);
157    }
158    documentSelectOptions.fileSuffixFilters.push('.*');
159 } catch (error) {
160    console.log('selectFile error: ' + + JSON.stringify(error));
161    return documentSelectOptions;
162 }
163  return documentSelectOptions;
164}
165
166function isContainImageMimeType(acceptTypes) {
167  if (!(acceptTypes instanceof Array) || acceptTypes.length < 1) {
168    return false;
169  }
170
171  let imageTypes = ['tif', 'xbm', 'tiff', 'pjp', 'jfif', 'bmp', 'avif', 'apng', 'ico',
172                    'webp', 'svg', 'gif', 'svgz', 'jpg', 'jpeg', 'png', 'pjpeg'];
173  for (let i = 0; i < acceptTypes.length; i++) {
174    for (let j = 0; j < imageTypes.length; j++) {
175      if (acceptTypes[i].includes(imageTypes[j])) {
176        return true;
177      }
178    }
179  }
180  return false;
181}
182
183function isContainVideoMimeType(acceptTypes) {
184  if (!(acceptTypes instanceof Array) || acceptTypes.length < 1) {
185    return false;
186  }
187
188  let videoTypes = ['ogm', 'ogv', 'mpg', 'mp4', 'mpeg', 'm4v', 'webm'];
189  for (let i = 0; i < acceptTypes.length; i++) {
190    for (let j = 0; j < videoTypes.length; j++) {
191      if (acceptTypes[i].includes(videoTypes[j])) {
192        return true;
193      }
194    }
195  }
196  return false;
197}
198
199function selectPicture(param, selectResult) {
200  try {
201    let photoResultArray = [];
202    let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
203    if (param.getMode() === FileSelectorMode.FileOpenMode) {
204      console.log('allow select single photo or video');
205      photoSelectOptions.maxSelectNumber = 1;
206    }
207    let acceptTypes = param.getAcceptType();
208    photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE;
209    if (isContainImageMimeType(acceptTypes) && !isContainVideoMimeType(acceptTypes)) {
210      photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
211    }
212    if (!isContainImageMimeType(acceptTypes) && isContainVideoMimeType(acceptTypes)) {
213      photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE;
214    }
215
216    let photoPicker = new photoAccessHelper.PhotoViewPicker();
217    photoPicker.select(photoSelectOptions).then((photoSelectResult) => {
218      if (photoSelectResult.photoUris.length <= 0) {
219        return;
220      }
221      for (let i = 0; i < photoSelectResult.photoUris.length; i++) {
222        photoResultArray.push(photoSelectResult.photoUris[i]);
223      }
224      selectResult.handleFileList(photoResultArray);
225    });
226  } catch (error) {
227    console.log('selectPicture error' + JSON.stringify(error));
228  }
229}
230
231Object.defineProperty(webview.WebviewController.prototype, 'getCertificate', {
232  value: function (callback) {
233    if (arguments.length !== 0 && arguments.length !== 1) {
234      throw new BusinessError(PARAM_CHECK_ERROR,
235        'BusinessError 401: Parameter error. The number of params must be zero or one.');
236    }
237
238    let certChainData = this.innerGetCertificate();
239    if (callback === undefined) {
240      console.log('get certificate promise');
241      return getCertificatePromise(certChainData);
242    } else {
243      console.log('get certificate async callback');
244      if (typeof callback !== 'function') {
245        throw new BusinessError(PARAM_CHECK_ERROR,
246          'BusinessError 401: Parameter error. The type of 'callback' must be function.' );
247      }
248      getCertificatePromise(certChainData).then(x509CertArray => {
249        callback(undefined, x509CertArray);
250      }).catch(error => {
251        callback(error, undefined);
252      });
253    }
254  }
255});
256
257Object.defineProperty(webview.WebviewController.prototype, 'fileSelectorShowFromUserWeb', {
258  value:  function (callback) {
259    let currentDevice = deviceinfo.deviceType.toLowerCase();
260    if (needShowDialog(callback.fileparam)) {
261      ActionSheet.show({
262        title: '选择上传',
263        autoCancel: true,
264        confirm: {
265          defaultFocus: true,
266          value: '取消',
267          style: DialogButtonStyle.DEFAULT,
268          action: () => {
269            console.log('Get Alert Dialog handled');
270          }
271        },
272        cancel: () => {
273          console.log('actionSheet canceled');
274        },
275        alignment: DialogAlignment.Bottom,
276        offset: { dx: 0, dy: -10 },
277        sheets: [
278          {
279            icon: $r('sys.media.ohos_ic_public_albums'),
280            title: '图片',
281            action: () => {
282              selectPicture(callback.fileparam, callback.fileresult);
283            }
284          },
285          {
286            icon: $r('sys.media.ohos_ic_public_camera'),
287            title: '拍照',
288            action: () => {
289              takePhoto(callback.fileparam, callback.fileresult);
290             }
291          },
292          {
293            icon: $r('sys.media.ohos_ic_public_email'),
294            title: '文件',
295            action: () => {
296              selectFile(callback.fileparam, callback.fileresult);
297            }
298          }
299        ]
300      });
301    } else if (currentDevice === 'phone' && callback.fileparam.isCapture()) {
302      console.log('take photo will be directly invoked due to the capture property');
303      takePhoto(callback.fileparam, callback.fileresult);
304    } else {
305      console.log('selectFile will be invoked by web');
306      selectFile(callback.fileparam, callback.fileresult);
307    }
308  }
309});
310
311Object.defineProperty(webview.WebviewController.prototype, 'requestPermissionsFromUserWeb', {
312  value:  function (callback) {
313    let accessManger = accessControl.createAtManager();
314    let abilityContext = getContext(this);
315    accessManger.requestPermissionsFromUser(abilityContext, ['ohos.permission.READ_PASTEBOARD'])
316      .then((PermissionRequestResult) => {
317        if (PermissionRequestResult.authResults[0] === 0) {
318          console.log('requestPermissionsFromUserWeb is allowed');
319          callback.request.grant(callback.request.getAccessibleResource());
320        }
321        else {
322          console.log('requestPermissionsFromUserWeb is refused');
323          callback.request.deny();
324        }
325      })
326      .catch((error) => {
327        callback.request.deny();
328      });
329  }
330});
331
332Object.defineProperty(webview.WebviewController.prototype, 'openAppLink', {
333  value:  function (callback) {
334    let abilityContext = getContext(this);
335    try {
336      let option = {
337        appLinkingOnly: true
338      };
339      console.log('begin openAppLink');
340      abilityContext.openLink(callback.url, option, null)
341        .then(() => {
342          console.log('applink success openLink');
343          callback.result.cancelLoad();
344        })
345        .catch((error) => {
346          console.log(`applink openLink ErrorCode: ${error.code},  Message: ${error.message}`);
347          callback.result.continueLoad();
348        });
349    } catch (err) {
350      console.log(`applink openLink ErrorCode: ${err.code},  Message: ${err.message}`);
351      callback.result.continueLoad();
352    }
353  }
354});
355
356export default webview;
357