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 } catch (error) { 159 console.log('selectFile error: ' + + JSON.stringify(error)); 160 return documentSelectOptions; 161 } 162 return documentSelectOptions; 163} 164 165function isContainImageMimeType(acceptTypes) { 166 if (!(acceptTypes instanceof Array) || acceptTypes.length < 1) { 167 return false; 168 } 169 170 let imageTypes = ['tif', 'xbm', 'tiff', 'pjp', 'jfif', 'bmp', 'avif', 'apng', 'ico', 171 'webp', 'svg', 'gif', 'svgz', 'jpg', 'jpeg', 'png', 'pjpeg']; 172 for (let i = 0; i < acceptTypes.length; i++) { 173 for (let j = 0; j < imageTypes.length; j++) { 174 if (acceptTypes[i].includes(imageTypes[j])) { 175 return true; 176 } 177 } 178 } 179 return false; 180} 181 182function isContainVideoMimeType(acceptTypes) { 183 if (!(acceptTypes instanceof Array) || acceptTypes.length < 1) { 184 return false; 185 } 186 187 let videoTypes = ['ogm', 'ogv', 'mpg', 'mp4', 'mpeg', 'm4v', 'webm']; 188 for (let i = 0; i < acceptTypes.length; i++) { 189 for (let j = 0; j < videoTypes.length; j++) { 190 if (acceptTypes[i].includes(videoTypes[j])) { 191 return true; 192 } 193 } 194 } 195 return false; 196} 197 198function selectPicture(param, selectResult) { 199 try { 200 let photoResultArray = []; 201 let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions(); 202 if (param.getMode() === FileSelectorMode.FileOpenMode) { 203 console.log('allow select single photo or video'); 204 photoSelectOptions.maxSelectNumber = 1; 205 } 206 let acceptTypes = param.getAcceptType(); 207 photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE; 208 if (isContainImageMimeType(acceptTypes) && !isContainVideoMimeType(acceptTypes)) { 209 photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; 210 } 211 if (!isContainImageMimeType(acceptTypes) && isContainVideoMimeType(acceptTypes)) { 212 photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE; 213 } 214 215 let photoPicker = new photoAccessHelper.PhotoViewPicker(); 216 photoPicker.select(photoSelectOptions).then((photoSelectResult) => { 217 if (photoSelectResult.photoUris.length <= 0) { 218 return; 219 } 220 for (let i = 0; i < photoSelectResult.photoUris.length; i++) { 221 photoResultArray.push(photoSelectResult.photoUris[i]); 222 } 223 selectResult.handleFileList(photoResultArray); 224 }); 225 } catch (error) { 226 console.log('selectPicture error' + JSON.stringify(error)); 227 } 228} 229 230Object.defineProperty(webview.WebviewController.prototype, 'getCertificate', { 231 value: function (callback) { 232 if (arguments.length !== 0 && arguments.length !== 1) { 233 throw new BusinessError(PARAM_CHECK_ERROR, 234 'BusinessError 401: Parameter error. The number of params must be zero or one.'); 235 } 236 237 let certChainData = this.innerGetCertificate(); 238 if (callback === undefined) { 239 console.log('get certificate promise'); 240 return getCertificatePromise(certChainData); 241 } else { 242 console.log('get certificate async callback'); 243 if (typeof callback !== 'function') { 244 throw new BusinessError(PARAM_CHECK_ERROR, 245 'BusinessError 401: Parameter error. The type of "callback" must be function.' ); 246 } 247 return getCertificatePromise(certChainData).then(x509CertArray => { 248 callback(undefined, x509CertArray); 249 }).catch(error => { 250 callback(error, undefined); 251 }); 252 } 253 } 254}); 255 256Object.defineProperty(webview.WebviewController.prototype, 'fileSelectorShowFromUserWeb', { 257 value: function (callback) { 258 let currentDevice = deviceinfo.deviceType.toLowerCase(); 259 if (needShowDialog(callback.fileparam)) { 260 ActionSheet.show({ 261 title: '选择上传', 262 autoCancel: true, 263 confirm: { 264 defaultFocus: true, 265 value: '取消', 266 style: DialogButtonStyle.DEFAULT, 267 action: () => { 268 console.log('Get Alert Dialog handled'); 269 } 270 }, 271 cancel: () => { 272 console.log('actionSheet canceled'); 273 }, 274 alignment: DialogAlignment.Bottom, 275 offset: { dx: 0, dy: -10 }, 276 sheets: [ 277 { 278 icon: $r('sys.media.ohos_ic_public_albums'), 279 title: '图片', 280 action: () => { 281 selectPicture(callback.fileparam, callback.fileresult); 282 } 283 }, 284 { 285 icon: $r('sys.media.ohos_ic_public_camera'), 286 title: '拍照', 287 action: () => { 288 takePhoto(callback.fileparam, callback.fileresult); 289 } 290 }, 291 { 292 icon: $r('sys.media.ohos_ic_public_email'), 293 title: '文件', 294 action: () => { 295 selectFile(callback.fileparam, callback.fileresult); 296 } 297 } 298 ] 299 }); 300 } else if (currentDevice === 'phone' && callback.fileparam.isCapture()) { 301 console.log('take photo will be directly invoked due to the capture property'); 302 takePhoto(callback.fileparam, callback.fileresult); 303 } else { 304 console.log('selectFile will be invoked by web'); 305 selectFile(callback.fileparam, callback.fileresult); 306 } 307 } 308}); 309 310Object.defineProperty(webview.WebviewController.prototype, 'requestPermissionsFromUserWeb', { 311 value: function (callback) { 312 let accessManger = accessControl.createAtManager(); 313 let abilityContext = getContext(this); 314 accessManger.requestPermissionsFromUser(abilityContext, ['ohos.permission.READ_PASTEBOARD']) 315 .then((PermissionRequestResult) => { 316 if (PermissionRequestResult.authResults[0] === 0) { 317 console.log('requestPermissionsFromUserWeb is allowed'); 318 callback.request.grant(callback.request.getAccessibleResource()); 319 } 320 else { 321 console.log('requestPermissionsFromUserWeb is refused'); 322 callback.request.deny(); 323 } 324 }) 325 .catch((error) => { 326 callback.request.deny(); 327 }); 328 } 329}); 330 331Object.defineProperty(webview.WebviewController.prototype, 'openAppLink', { 332 value: function (callback) { 333 let abilityContext = getContext(this); 334 try { 335 let option = { 336 appLinkingOnly: true 337 }; 338 console.log('begin openAppLink'); 339 abilityContext.openLink(callback.url, option, null) 340 .then(() => { 341 console.log('applink success openLink'); 342 callback.result.cancelLoad(); 343 }) 344 .catch((error) => { 345 console.log(`applink openLink ErrorCode: ${error.code}, Message: ${error.message}`); 346 callback.result.continueLoad(); 347 }); 348 } catch (err) { 349 console.log(`applink openLink ErrorCode: ${err.code}, Message: ${err.message}`); 350 callback.result.continueLoad(); 351 } 352 } 353}); 354 355export default webview; 356