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 
16 let cert = requireInternal('security.cert');
17 let webview = requireInternal('web.webview');
18 let picker = requireNapi('file.picker');
19 let photoAccessHelper = requireNapi('file.photoAccessHelper');
20 let cameraPicker = requireNapi('multimedia.cameraPicker');
21 let camera = requireNapi('multimedia.camera');
22 let accessControl = requireNapi('abilityAccessCtrl');
23 let deviceinfo = requireInternal('deviceInfo');
24 const PARAM_CHECK_ERROR = 401;
25 
26 const ERROR_MSG_INVALID_PARAM = 'Invalid input parameter';
27 
28 let errMsgMap = new Map();
29 errMsgMap.set(PARAM_CHECK_ERROR, ERROR_MSG_INVALID_PARAM);
30 
31 class 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 
43 function 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 
61 function 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 
90 function 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 
111 function 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 
129 function 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 
166 function 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 
183 function 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 
199 function 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 
231 Object.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 
257 Object.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 
311 Object.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 
332 Object.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 
356 export default webview;
357