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