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