├── framework ├── consumer-rules.txt ├── src │ ├── npm │ │ ├── .npmignore │ │ ├── index.js │ │ ├── package.json │ │ └── index.d.ts │ └── main │ │ ├── module.json5 │ │ ├── resources │ │ ├── rawfile │ │ │ ├── config.json │ │ │ ├── CloakClient.js │ │ │ └── www │ │ │ │ └── index.html │ │ ├── zh │ │ │ └── element │ │ │ │ └── string.json │ │ ├── zh_CN │ │ │ └── element │ │ │ │ └── string.json │ │ └── base │ │ │ └── element │ │ │ └── string.json │ │ └── ets │ │ ├── types.ets │ │ ├── Config.ets │ │ ├── Utils.ets │ │ ├── DialogUtils.ets │ │ ├── plugin │ │ ├── CloakGlobalObject.ets │ │ ├── PluginManager.ets │ │ └── CloakPlugin.ets │ │ ├── logger.ets │ │ ├── Cloak.ets │ │ └── Webview.ets ├── .ohpmignore ├── .gitignore ├── hvigorfile.ts ├── Index.ets ├── build-profile.json5 ├── oh-package-lock.json5 ├── oh-package.json5 └── obfuscation-rules.txt ├── entry ├── src │ ├── mock │ │ └── mock-config.json5 │ ├── main │ │ ├── resources │ │ │ ├── base │ │ │ │ ├── profile │ │ │ │ │ ├── backup_config.json │ │ │ │ │ └── main_pages.json │ │ │ │ ├── media │ │ │ │ │ ├── background.png │ │ │ │ │ ├── foreground.png │ │ │ │ │ ├── startIcon.png │ │ │ │ │ └── layered_image.json │ │ │ │ └── element │ │ │ │ │ ├── color.json │ │ │ │ │ └── string.json │ │ │ ├── dark │ │ │ │ └── element │ │ │ │ │ └── color.json │ │ │ ├── rawfile │ │ │ │ ├── config.json │ │ │ │ └── www │ │ │ │ │ ├── css │ │ │ │ │ └── demo.css │ │ │ │ │ ├── index.html │ │ │ │ │ └── js │ │ │ │ │ └── demo.js │ │ │ ├── zh │ │ │ │ └── element │ │ │ │ │ └── string.json │ │ │ └── zh_CN │ │ │ │ └── element │ │ │ │ └── string.json │ │ ├── ets │ │ │ ├── pages │ │ │ │ └── Index.ets │ │ │ ├── entrybackupability │ │ │ │ └── EntryBackupAbility.ets │ │ │ └── entryability │ │ │ │ └── EntryAbility.ets │ │ └── module.json5 │ ├── test │ │ ├── List.test.ets │ │ └── LocalUnit.test.ets │ └── ohosTest │ │ ├── ets │ │ └── test │ │ │ ├── List.test.ets │ │ │ └── Ability.test.ets │ │ └── module.json5 ├── .gitignore ├── hvigorfile.ts ├── build-profile.json5 ├── oh-package.json5 ├── obfuscation-rules.txt └── oh-package-lock.json5 ├── plugins ├── CloakPluginDevice │ ├── consumer-rules.txt │ ├── .gitignore │ ├── Index.ets │ ├── src │ │ └── main │ │ │ ├── module.json5 │ │ │ └── ets │ │ │ └── Plugin.ets │ ├── hvigorfile.ts │ ├── oh-package.json5 │ ├── build-profile.json5 │ ├── oh-package-lock.json5 │ └── obfuscation-rules.txt ├── CloakPluginGeolocation │ ├── consumer-rules.txt │ ├── .gitignore │ ├── Index.ets │ ├── src │ │ └── main │ │ │ ├── module.json5 │ │ │ └── ets │ │ │ └── Plugin.ts.ets │ ├── hvigorfile.ts │ ├── oh-package.json5 │ ├── build-profile.json5 │ ├── oh-package-lock.json5 │ └── obfuscation-rules.txt └── README.md ├── scripts ├── sync-readme.sh ├── publish.sh ├── remote-debug.sh └── build_har.sh ├── AppScope ├── resources │ ├── base │ │ ├── media │ │ │ └── app_icon.png │ │ └── element │ │ │ └── string.json │ ├── zh │ │ └── element │ │ │ └── string.json │ └── zh_CN │ │ └── element │ │ └── string.json └── app.json5 ├── hvigorfile.ts ├── code-linter.json5 ├── oh-package.json5 ├── LICENSE ├── CHANGELOG.md ├── oh-package-lock.json5 ├── CHANGELOG-EN.md ├── hvigor └── hvigor-config.json5 ├── README.md ├── .gitignore └── README-EN.md /framework/consumer-rules.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /framework/src/npm/.npmignore: -------------------------------------------------------------------------------- 1 | *.tgz -------------------------------------------------------------------------------- /entry/src/mock/mock-config.json5: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /plugins/CloakPluginDevice/consumer-rules.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/CloakPluginGeolocation/consumer-rules.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /framework/src/npm/index.js: -------------------------------------------------------------------------------- 1 | const Cloak = window.Cloak; 2 | 3 | export { Cloak }; 4 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/profile/backup_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowToBackupRestore": true 3 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/profile/main_pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": [ 3 | "pages/Index" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /plugins/CloakPluginDevice/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /plugins/CloakPluginGeolocation/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | This is a temporary directory for testing plugin development and will be deleted eventually. 2 | -------------------------------------------------------------------------------- /entry/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test 7 | 8 | src/main/resources/rawfile/www2 -------------------------------------------------------------------------------- /scripts/sync-readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cp ./README*.md ./framework && cp ./CHANGELOG*.md ./framework && cp ./LICENSE ./framework -------------------------------------------------------------------------------- /plugins/CloakPluginDevice/Index.ets: -------------------------------------------------------------------------------- 1 | import CloakPluginDevice from './src/main/ets/Plugin' 2 | 3 | 4 | export { CloakPluginDevice } 5 | -------------------------------------------------------------------------------- /AppScope/resources/base/media/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisdomGardenInc/Cloak/HEAD/AppScope/resources/base/media/app_icon.png -------------------------------------------------------------------------------- /entry/src/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import localUnitTest from './LocalUnit.test'; 2 | 3 | export default function testsuite() { 4 | localUnitTest(); 5 | } -------------------------------------------------------------------------------- /AppScope/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "app_name", 5 | "value": "Cloak" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /AppScope/resources/zh/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "app_name", 5 | "value": "Cloak" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisdomGardenInc/Cloak/HEAD/entry/src/main/resources/base/media/background.png -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisdomGardenInc/Cloak/HEAD/entry/src/main/resources/base/media/foreground.png -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/startIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisdomGardenInc/Cloak/HEAD/entry/src/main/resources/base/media/startIcon.png -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import abilityTest from './Ability.test'; 2 | 3 | export default function testsuite() { 4 | abilityTest(); 5 | } -------------------------------------------------------------------------------- /plugins/CloakPluginGeolocation/Index.ets: -------------------------------------------------------------------------------- 1 | import CloakPluginGeolocation from './src/main/ets/Plugin.ts' 2 | 3 | 4 | export { CloakPluginGeolocation } 5 | -------------------------------------------------------------------------------- /AppScope/resources/zh_CN/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "app_name", 5 | "value": "Cloak" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /framework/.ohpmignore: -------------------------------------------------------------------------------- 1 | /src/npm 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | Thumbs.db 6 | Thumbs.db:encryptable 7 | ehthumbs.db 8 | ehthumbs_vista.db 9 | .gitignore.bak -------------------------------------------------------------------------------- /entry/src/main/resources/base/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "start_window_background", 5 | "value": "#FFFFFF" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /entry/src/main/resources/dark/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "start_window_background", 5 | "value": "#000000" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cp framework/build/default/outputs/default/framework.har local_hars/ 4 | ohpm publish framework/build/default/outputs/default/framework.har -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/layered_image.json: -------------------------------------------------------------------------------- 1 | { 2 | "layered-image": 3 | { 4 | "background" : "$media:background", 5 | "foreground" : "$media:foreground" 6 | } 7 | } -------------------------------------------------------------------------------- /framework/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test 7 | .gitignore.bak 8 | LICENSE 9 | CHANGELOG-EN.md 10 | CHANGELOG.md 11 | README-EN.md 12 | README.md -------------------------------------------------------------------------------- /framework/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "framework", 4 | "type": "har", 5 | "deviceTypes": [ 6 | "default", 7 | "tablet", 8 | "2in1", 9 | "car" 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/Index.ets: -------------------------------------------------------------------------------- 1 | import { CloakWebview } from "@wisdomgarden/cloak" 2 | 3 | 4 | @Entry 5 | @ComponentV2 6 | struct CloakIndex { 7 | build() { 8 | Column() { 9 | CloakWebview() 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /plugins/CloakPluginDevice/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "CloakPluginDevice", 4 | "type": "har", 5 | "deviceTypes": [ 6 | "default", 7 | "tablet", 8 | "2in1", 9 | "car" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { appTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /AppScope/app.json5: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "bundleName": "com.wisdomgarden.cloak", 4 | "vendor": "WisdomGarden", 5 | "versionCode": 1, 6 | "versionName": "1.0.0", 7 | "icon": "$media:app_icon", 8 | "label": "$string:app_name" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /entry/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { hapTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /plugins/CloakPluginGeolocation/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "CloakPluginGeolocation", 4 | "type": "har", 5 | "deviceTypes": [ 6 | "default", 7 | "tablet", 8 | "2in1", 9 | "car" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/rawfile/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "APP_FOLDER": "www", 3 | "APP_HOST": "http://localhost", 4 | "APP_USE_REAL_HOST_RESOURCE": false, 5 | "APP_IDENTITY_USER_AGENT": "CloakDemo/HarmonyOS", 6 | "IS_DEBUG": true, 7 | "WEB_VIEW_USE_APP_PERMISSION": true 8 | } -------------------------------------------------------------------------------- /framework/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { harTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /framework/src/main/resources/rawfile/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "APP_FOLDER": "www", 3 | "APP_HOST": "http://localhost", 4 | "APP_USE_REAL_HOST_RESOURCE": false, 5 | "APP_IDENTITY_USER_AGENT": "Cloak/HarmonyOS", 6 | "IS_DEBUG": false, 7 | "WEB_VIEW_USE_APP_PERMISSION": true 8 | } -------------------------------------------------------------------------------- /plugins/CloakPluginDevice/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { harTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /plugins/CloakPluginGeolocation/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { harTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /entry/src/ohosTest/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "entry_test", 4 | "type": "feature", 5 | "deviceTypes": [ 6 | "phone", 7 | "tablet", 8 | "2in1", 9 | "car" 10 | ], 11 | "deliveryWithInstall": true, 12 | "installationFree": false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /plugins/CloakPluginDevice/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wisdomgarden/cloak-plugin-device", 3 | "version": "1.0.0", 4 | "description": "Please describe the basic information.", 5 | "main": "Index.ets", 6 | "author": "WisdomGarden", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "@wisdomgarden/cloak": "file:../../framework" 10 | } 11 | } -------------------------------------------------------------------------------- /plugins/CloakPluginGeolocation/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wisdomgarden/cloak-plugin-geolocation", 3 | "version": "1.0.0", 4 | "description": "Please describe the basic information.", 5 | "main": "Index.ets", 6 | "author": "WisdomGarden", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "@wisdomgarden/cloak": "file:../../framework" 10 | } 11 | } -------------------------------------------------------------------------------- /framework/src/main/ets/types.ets: -------------------------------------------------------------------------------- 1 | import { common } from '@kit.AbilityKit'; 2 | import { webview } from '@kit.ArkWeb'; 3 | 4 | export interface IChannelMessage { 5 | payload: ESObject, 6 | pluginName: string, 7 | } 8 | 9 | export interface ICloakContext { 10 | applicationContext: common.ApplicationContext; 11 | uiContext: UIContext; 12 | webviewController: WebviewController; 13 | channelCloakPort: webview.WebMessagePort 14 | } -------------------------------------------------------------------------------- /code-linter.json5: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "**/*.ets" 4 | ], 5 | "ignore": [ 6 | "**/src/ohosTest/**/*", 7 | "**/src/test/**/*", 8 | "**/src/mock/**/*", 9 | "**/node_modules/**/*", 10 | "**/oh_modules/**/*", 11 | "**/build/**/*", 12 | "**/.preview/**/*" 13 | ], 14 | "ruleSet": [ 15 | "plugin:@performance/recommended", 16 | "plugin:@typescript-eslint/recommended" 17 | ], 18 | "rules": { 19 | } 20 | } -------------------------------------------------------------------------------- /framework/Index.ets: -------------------------------------------------------------------------------- 1 | import CloakWebview from './src/main/ets/Webview' 2 | import Cloak from './src/main/ets/Cloak' 3 | import { CloakPlugin, PluginMethod, PluginMetadata, ICloakPlugin } from './src/main/ets/plugin/CloakPlugin' 4 | import PluginManager from './src/main/ets/plugin/PluginManager'; 5 | import { getLogger } from './src/main/ets/logger' 6 | 7 | 8 | export { Cloak, CloakWebview, CloakPlugin, ICloakPlugin, PluginMethod, PluginMetadata, PluginManager, getLogger } 9 | -------------------------------------------------------------------------------- /entry/src/main/ets/entrybackupability/EntryBackupAbility.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; 3 | 4 | export default class EntryBackupAbility extends BackupExtensionAbility { 5 | async onBackup() { 6 | hilog.info(0x0000, 'testTag', 'onBackup ok'); 7 | } 8 | 9 | async onRestore(bundleVersion: BundleVersion) { 10 | hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); 11 | } 12 | } -------------------------------------------------------------------------------- /entry/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | }, 5 | "buildOptionSet": [ 6 | { 7 | "name": "release", 8 | "arkOptions": { 9 | "obfuscation": { 10 | "ruleOptions": { 11 | "enable": false, 12 | "files": [ 13 | "./obfuscation-rules.txt" 14 | ] 15 | } 16 | } 17 | } 18 | }, 19 | ], 20 | "targets": [ 21 | { 22 | "name": "default" 23 | }, 24 | { 25 | "name": "ohosTest", 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "modelVersion": "5.0.1", 3 | "description": "Cloak - A Hybrid Development Framework for HarmonyOS", 4 | "author": "WisdomGarden", 5 | "repository": "https://github.com/WisdomGardenInc/Cloak", 6 | "homepage": "https://tronclass.com.cn/", 7 | "license": "Apache-2.0", 8 | "scripts": { 9 | "prepack": "cp ./README*.md ./framework && cp ./CHANGELOG*.md ./framework && cp ./LICENSE ./framework" 10 | }, 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "@ohos/hypium": "1.0.19", 14 | "@ohos/hamock": "1.0.0" 15 | }, 16 | "dynamicDependencies": {}, 17 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 WisdomGarden 2 | 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. -------------------------------------------------------------------------------- /plugins/CloakPluginGeolocation/src/main/ets/Plugin.ts.ets: -------------------------------------------------------------------------------- 1 | import { CloakPlugin, PluginMethod } from '@wisdomgarden/cloak'; 2 | import { geoLocationManager } from '@kit.LocationKit'; 3 | 4 | 5 | export default class Geolocation extends CloakPlugin { 6 | protected _name = "Geolocation"; 7 | protected _version = "1.0.0"; 8 | protected _description = '' 9 | 10 | @PluginMethod() 11 | public async getLocation(): Promise { 12 | const result = await geoLocationManager.getCurrentLocation() 13 | console.log(result.latitude.toString(), result.longitude.toString()); 14 | return result; 15 | } 16 | } -------------------------------------------------------------------------------- /framework/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | "arkOptions": { 5 | "byteCodeHar": false, 6 | } 7 | }, 8 | "buildOptionSet": [ 9 | { 10 | "name": "release", 11 | "arkOptions": { 12 | "obfuscation": { 13 | "ruleOptions": { 14 | "enable": false, 15 | "files": [ 16 | "./obfuscation-rules.txt" 17 | ] 18 | }, 19 | "consumerFiles": [ 20 | "./consumer-rules.txt" 21 | ] 22 | } 23 | }, 24 | }, 25 | ], 26 | "targets": [ 27 | { 28 | "name": "default" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /framework/oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": true 4 | }, 5 | "lockfileVersion": 3, 6 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 7 | "specifiers": { 8 | "mime@^3.0.0": "mime@3.0.0" 9 | }, 10 | "packages": { 11 | "mime@3.0.0": { 12 | "name": "mime", 13 | "version": "3.0.0", 14 | "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", 15 | "resolved": "https://ohpm.openharmony.cn/ohpm/mime/-/mime-3.0.0.tgz", 16 | "shasum": "b374550dca3a0c18443b0c950a6a58f1931cf7a7", 17 | "registryType": "ohpm" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /framework/src/main/ets/Config.ets: -------------------------------------------------------------------------------- 1 | import BuildProfile from '../../../BuildProfile'; 2 | 3 | export interface APPConfig { 4 | APP_FOLDER: string; 5 | APP_HOST: string; 6 | APP_IDENTITY_USER_AGENT: string; 7 | CLOAK_IDENTITY_USER_AGENT: string; 8 | IS_DEBUG: boolean; 9 | WEB_VIEW_USE_APP_PERMISSION: boolean; 10 | APP_USE_REAL_HOST_RESOURCE: boolean; 11 | } 12 | 13 | const config: APPConfig = { 14 | APP_FOLDER: 'www', 15 | APP_HOST: 'http://localhost', 16 | APP_IDENTITY_USER_AGENT: 'Cloak/HarmonyOS', 17 | CLOAK_IDENTITY_USER_AGENT: `Cloak/${BuildProfile.HAR_VERSION}`, 18 | WEB_VIEW_USE_APP_PERMISSION: true, 19 | IS_DEBUG: false, 20 | APP_USE_REAL_HOST_RESOURCE: false, 21 | } 22 | 23 | 24 | export default config -------------------------------------------------------------------------------- /framework/src/npm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wisdomgarden/cloak", 3 | "version": "0.0.3", 4 | "description": "Cloak is a hybrid development framework for HarmonyOS, enabling quick packaging of H5 applications and native capability access", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "prepack": "cp ../../../README*.md . && cp ../../../LICENSE ." 9 | }, 10 | "author": "WisdomGarden", 11 | "repository": "https://github.com/WisdomGardenInc/Cloak", 12 | "homepage": "https://tronclass.com.cn/", 13 | "license": "Apache-2.0", 14 | "keywords": [ 15 | "Cloak", 16 | "HarmonyOS", 17 | "Cordova", 18 | "Capacitor", 19 | "Hybrid", 20 | "H5", 21 | "Webview" 22 | ] 23 | } -------------------------------------------------------------------------------- /plugins/CloakPluginDevice/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | "arkOptions": { 5 | "byteCodeHar": false, 6 | } 7 | }, 8 | "buildOptionSet": [ 9 | { 10 | "name": "release", 11 | "arkOptions": { 12 | "obfuscation": { 13 | "ruleOptions": { 14 | "enable": false, 15 | "files": [ 16 | "./obfuscation-rules.txt" 17 | ] 18 | }, 19 | "consumerFiles": [ 20 | "./consumer-rules.txt" 21 | ] 22 | } 23 | }, 24 | }, 25 | ], 26 | "targets": [ 27 | { 28 | "name": "default" 29 | }, 30 | { 31 | "name": "ohosTest" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /plugins/CloakPluginGeolocation/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | "arkOptions": { 5 | "byteCodeHar": false, 6 | } 7 | }, 8 | "buildOptionSet": [ 9 | { 10 | "name": "release", 11 | "arkOptions": { 12 | "obfuscation": { 13 | "ruleOptions": { 14 | "enable": false, 15 | "files": [ 16 | "./obfuscation-rules.txt" 17 | ] 18 | }, 19 | "consumerFiles": [ 20 | "./consumer-rules.txt" 21 | ] 22 | } 23 | }, 24 | }, 25 | ], 26 | "targets": [ 27 | { 28 | "name": "default" 29 | }, 30 | { 31 | "name": "ohosTest" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | [**English Version**](./CHANGELOG-EN.md) | [中文版](./CHANGELOG.md) 2 | 3 | # 更新日志 4 | 5 | ## 1.0.5 (2025-04-11) 6 | - 增加静态属性 `Cloak.CONFIG`,用于获取当前配置 7 | - 改变静态属性 `Cloak.APP_CONTEXT`,为 `Cloak.APP_ABILITY` 原 context 可通过 `Cloak.getAbility().context` 获得 8 | - 增加静态方法 `Cloak.isStartupCompleted()`,用于判断应用是否启动完成 9 | - `CloakPlugin` 增加生命周期函数 `beforeRegister`, `afterRegister`, `onAttach` 10 | - 增加日志模块 11 | 12 | 13 | ## 1.0.4 (2025-03-13) 14 | - 支持事件机制、消息机制 15 | 16 | ## 1.0.3 (2025-02-27) 17 | - 配置文件增加 `APP_USE_REAL_HOST_RESOURCE`,控制是否加载真实 `APP_HOST` 所配置的远程资源,否则用 `APP_FOLDER` 配置的本地资源目录 18 | - 若请求路径不存在静态资源,则自动回退到加载 index.html 19 | 20 | ## 1.0.2 (2025-02-26) 21 | - 配置文件增加 `WEB_VIEW_USE_APP_PERMISSION`,控制 WebView 是否使用应用权限,不再出现授权提示 22 | 23 | ## 1.0.1 (2025-02-25) 24 | - 增加默认 index.html 页,快速启动项目 25 | 26 | ## 1.0.0 (2025-02-25) 27 | - 实现 JavaScript 与 HarmonyOS 原生通信机制 28 | - 建立基础插件开发体系 29 | -------------------------------------------------------------------------------- /framework/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wisdomgarden/cloak", 3 | "version": "1.0.5", 4 | "description": "Cloak is a hybrid development framework for HarmonyOS, enabling quick packaging of H5 applications and native capability access", 5 | "main": "Index.ets", 6 | "author": "WisdomGarden", 7 | "repository": "https://github.com/WisdomGardenInc/Cloak", 8 | "homepage": "https://tronclass.com.cn/", 9 | "license": "Apache-2.0", 10 | "scripts": { 11 | "prepack": "cp ../README*.md . && cp ../CHANGELOG*.md . && cp ../LICENSE ." 12 | }, 13 | "hooks": { 14 | "preInstall": "cp ../README*.md . && cp ../CHANGELOG*.md . && cp ../LICENSE ." 15 | }, 16 | "dependencies": { 17 | "mime": "^3.0.0" 18 | }, 19 | "keywords": [ 20 | "Cloak", 21 | "HarmonyOS", 22 | "Cordova", 23 | "Capacitor", 24 | "Hybrid", 25 | "H5", 26 | "Webview", 27 | ] 28 | } -------------------------------------------------------------------------------- /entry/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloak", 3 | "version": "1.0.0", 4 | "description": "This is a demo application of the Cloak framework.", 5 | "main": "entry", 6 | "author": "WisdomGarden", 7 | "repository": "https://github.com/WisdomGardenInc/Cloak", 8 | "homepage": "https://tronclass.com.cn/", 9 | "license": "Apache-2.0", 10 | "dependencies": { 11 | "@wisdomgarden/cloak": "file:../framework", 12 | "@wisdomgarden/cloak-plugin-device": "file:../plugins/CloakPluginDevice", 13 | "@wisdomgarden/cloak-plugin-geolocation": "file:../plugins/CloakPluginGeolocation", 14 | "@wisdomgarden/cloak-plugin-permission": "^1.0.3", 15 | "@wisdomgarden/cloak-plugin-inappbrowser": "^1.0.0", 16 | "@wisdomgarden/cloak-plugin-http": "^1.0.0", 17 | "@wisdomgarden/cloak-plugin-open-native-settings": "^1.0.0", 18 | }, 19 | "devDependencies": {}, 20 | "dynamicDependencies": {} 21 | } -------------------------------------------------------------------------------- /framework/src/main/ets/Utils.ets: -------------------------------------------------------------------------------- 1 | import mime from 'mime'; 2 | import Config from './Config'; 3 | 4 | interface IResourceInfo { 5 | resourcePath: string, 6 | extension: string 7 | } 8 | 9 | export const getResourceInfo = (pathName: string): IResourceInfo => { 10 | // Simple handling, check the extension rather than checking if the file exists. 11 | const filename = pathName.split('/').pop() || ''; 12 | const extensionInfo = filename.split('.'); 13 | let extension = extensionInfo.length > 1 ? (extensionInfo.pop()?.toLowerCase() || '') : ''; 14 | 15 | let resourcePath = `${Config.APP_FOLDER}${pathName}`; 16 | if (extension === '') { 17 | resourcePath = `${Config.APP_FOLDER}/index.html`; 18 | extension = 'html'; 19 | } 20 | 21 | return { resourcePath, extension }; 22 | } 23 | 24 | export const getMimeType = (extension: string): string => { 25 | return mime.getType(extension); 26 | } -------------------------------------------------------------------------------- /plugins/CloakPluginDevice/src/main/ets/Plugin.ets: -------------------------------------------------------------------------------- 1 | import { CloakPlugin, PluginMethod } from '@wisdomgarden/cloak'; 2 | 3 | interface DeviceInfoResult { 4 | platform: string, 5 | } 6 | 7 | // class TestInnerObj { 8 | // methodNameListForJsProxy = ["getData"] 9 | // 10 | // public getData() { 11 | // return "TestInnerObj data" 12 | // } 13 | // } 14 | 15 | export default class Device extends CloakPlugin { 16 | protected _name = "Device"; 17 | protected _version = "1.0.0"; 18 | protected _description = '' 19 | 20 | // @PluginMethod() 21 | // public getInnerObj(): ESObject { 22 | // return new TestInnerObj(); 23 | // }; 24 | 25 | @PluginMethod() 26 | public async getDeviceInfo(): Promise { 27 | 28 | return { 29 | platform: "HarmonyOS" 30 | }; 31 | } 32 | 33 | @PluginMethod() 34 | public sendTestEvent() { 35 | this._sendEvent("test", { data: "test~~~~"}) 36 | } 37 | } -------------------------------------------------------------------------------- /oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": true 4 | }, 5 | "lockfileVersion": 3, 6 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 7 | "specifiers": { 8 | "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", 9 | "@ohos/hypium@1.0.19": "@ohos/hypium@1.0.19" 10 | }, 11 | "packages": { 12 | "@ohos/hamock@1.0.0": { 13 | "name": "@ohos/hamock", 14 | "version": "1.0.0", 15 | "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", 16 | "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har", 17 | "registryType": "ohpm" 18 | }, 19 | "@ohos/hypium@1.0.19": { 20 | "name": "@ohos/hypium", 21 | "version": "1.0.19", 22 | "integrity": "sha512-cEjDgLFCm3cWZDeRXk7agBUkPqjWxUo6AQeiu0gEkb3J8ESqlduQLSIXeo3cCsm8U/asL7iKjF85ZyOuufAGSQ==", 23 | "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.19.har", 24 | "registryType": "ohpm" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /plugins/CloakPluginDevice/oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": true 4 | }, 5 | "lockfileVersion": 3, 6 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 7 | "specifiers": { 8 | "@wisdomgarden/cloak@../../framework": "@wisdomgarden/cloak@../../framework", 9 | "mime@^3.0.0": "mime@3.0.0" 10 | }, 11 | "packages": { 12 | "@wisdomgarden/cloak@../../framework": { 13 | "name": "@wisdomgarden/cloak", 14 | "version": "1.0.5", 15 | "resolved": "../../framework", 16 | "registryType": "local", 17 | "dependencies": { 18 | "mime": "^3.0.0" 19 | } 20 | }, 21 | "mime@3.0.0": { 22 | "name": "mime", 23 | "version": "3.0.0", 24 | "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", 25 | "resolved": "https://ohpm.openharmony.cn/ohpm/mime/-/mime-3.0.0.tgz", 26 | "shasum": "b374550dca3a0c18443b0c950a6a58f1931cf7a7", 27 | "registryType": "ohpm" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /plugins/CloakPluginGeolocation/oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": true 4 | }, 5 | "lockfileVersion": 3, 6 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 7 | "specifiers": { 8 | "@wisdomgarden/cloak@../../framework": "@wisdomgarden/cloak@../../framework", 9 | "mime@^3.0.0": "mime@3.0.0" 10 | }, 11 | "packages": { 12 | "@wisdomgarden/cloak@../../framework": { 13 | "name": "@wisdomgarden/cloak", 14 | "version": "1.0.5", 15 | "resolved": "../../framework", 16 | "registryType": "local", 17 | "dependencies": { 18 | "mime": "^3.0.0" 19 | } 20 | }, 21 | "mime@3.0.0": { 22 | "name": "mime", 23 | "version": "3.0.0", 24 | "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", 25 | "resolved": "https://ohpm.openharmony.cn/ohpm/mime/-/mime-3.0.0.tgz", 26 | "shasum": "b374550dca3a0c18443b0c950a6a58f1931cf7a7", 27 | "registryType": "ohpm" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /entry/obfuscation-rules.txt: -------------------------------------------------------------------------------- 1 | # Define project specific obfuscation rules here. 2 | # You can include the obfuscation configuration files in the current module's build-profile.json5. 3 | # 4 | # For more details, see 5 | # https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 6 | 7 | # Obfuscation options: 8 | # -disable-obfuscation: disable all obfuscations 9 | # -enable-property-obfuscation: obfuscate the property names 10 | # -enable-toplevel-obfuscation: obfuscate the names in the global scope 11 | # -compact: remove unnecessary blank spaces and all line feeds 12 | # -remove-log: remove all console.* statements 13 | # -print-namecache: print the name cache that contains the mapping from the old names to new names 14 | # -apply-namecache: reuse the given cache file 15 | 16 | # Keep options: 17 | # -keep-property-name: specifies property names that you want to keep 18 | # -keep-global-name: specifies names that you want to keep in the global scope 19 | 20 | -enable-property-obfuscation 21 | -enable-toplevel-obfuscation 22 | -enable-filename-obfuscation 23 | -enable-export-obfuscation -------------------------------------------------------------------------------- /framework/obfuscation-rules.txt: -------------------------------------------------------------------------------- 1 | # Define project specific obfuscation rules here. 2 | # You can include the obfuscation configuration files in the current module's build-profile.json5. 3 | # 4 | # For more details, see 5 | # https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 6 | 7 | # Obfuscation options: 8 | # -disable-obfuscation: disable all obfuscations 9 | # -enable-property-obfuscation: obfuscate the property names 10 | # -enable-toplevel-obfuscation: obfuscate the names in the global scope 11 | # -compact: remove unnecessary blank spaces and all line feeds 12 | # -remove-log: remove all console.* statements 13 | # -print-namecache: print the name cache that contains the mapping from the old names to new names 14 | # -apply-namecache: reuse the given cache file 15 | 16 | # Keep options: 17 | # -keep-property-name: specifies property names that you want to keep 18 | # -keep-global-name: specifies names that you want to keep in the global scope 19 | 20 | -enable-property-obfuscation 21 | -enable-toplevel-obfuscation 22 | -enable-filename-obfuscation 23 | -enable-export-obfuscation -------------------------------------------------------------------------------- /plugins/CloakPluginDevice/obfuscation-rules.txt: -------------------------------------------------------------------------------- 1 | # Define project specific obfuscation rules here. 2 | # You can include the obfuscation configuration files in the current module's build-profile.json5. 3 | # 4 | # For more details, see 5 | # https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 6 | 7 | # Obfuscation options: 8 | # -disable-obfuscation: disable all obfuscations 9 | # -enable-property-obfuscation: obfuscate the property names 10 | # -enable-toplevel-obfuscation: obfuscate the names in the global scope 11 | # -compact: remove unnecessary blank spaces and all line feeds 12 | # -remove-log: remove all console.* statements 13 | # -print-namecache: print the name cache that contains the mapping from the old names to new names 14 | # -apply-namecache: reuse the given cache file 15 | 16 | # Keep options: 17 | # -keep-property-name: specifies property names that you want to keep 18 | # -keep-global-name: specifies names that you want to keep in the global scope 19 | 20 | -enable-property-obfuscation 21 | -enable-toplevel-obfuscation 22 | -enable-filename-obfuscation 23 | -enable-export-obfuscation -------------------------------------------------------------------------------- /plugins/CloakPluginGeolocation/obfuscation-rules.txt: -------------------------------------------------------------------------------- 1 | # Define project specific obfuscation rules here. 2 | # You can include the obfuscation configuration files in the current module's build-profile.json5. 3 | # 4 | # For more details, see 5 | # https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 6 | 7 | # Obfuscation options: 8 | # -disable-obfuscation: disable all obfuscations 9 | # -enable-property-obfuscation: obfuscate the property names 10 | # -enable-toplevel-obfuscation: obfuscate the names in the global scope 11 | # -compact: remove unnecessary blank spaces and all line feeds 12 | # -remove-log: remove all console.* statements 13 | # -print-namecache: print the name cache that contains the mapping from the old names to new names 14 | # -apply-namecache: reuse the given cache file 15 | 16 | # Keep options: 17 | # -keep-property-name: specifies property names that you want to keep 18 | # -keep-global-name: specifies names that you want to keep in the global scope 19 | 20 | -enable-property-obfuscation 21 | -enable-toplevel-obfuscation 22 | -enable-filename-obfuscation 23 | -enable-export-obfuscation -------------------------------------------------------------------------------- /framework/src/main/resources/zh/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "permission_location_reason", 5 | "value": "我們需要存取您的位置以提供個性化的簽到服務,讓您無論身在何處都能輕鬆簽到。" 6 | }, 7 | { 8 | "name": "permission_network_reason", 9 | "value": "為了確保最佳的服務質量,我們請求監控您的網絡狀態。這有助於我們根據您的連接類型優化您的體驗。" 10 | }, 11 | { 12 | "name": "permission_internet_reason", 13 | "value": "我們的應用程式需要互聯網連接以提供最新的信息和服務。您的隱私和安全是我們的首要任務。" 14 | }, 15 | { 16 | "name": "permission_camera_reason", 17 | "value": "允許相機存取,以便您可以使用掃描二維碼或在應用程式內直接拍攝照片等視覺功能。" 18 | }, 19 | { 20 | "name": "permission_microphone_reason", 21 | "value": "授予麥克風存取權限以啟用語音交互功能,讓您在使用應用程式時享受免提體驗。" 22 | }, 23 | { 24 | "name": "common_ui_dialog_request_permission_title", 25 | "value": "請求權限" 26 | }, 27 | { 28 | "name": "common_ui_dialog_webview_request_permission_title", 29 | "value": "Webview 請求權限" 30 | }, 31 | { 32 | "name": "common_ui_dialog_confirm", 33 | "value": "確認" 34 | }, 35 | { 36 | "name": "common_ui_dialog_ok", 37 | "value": "確定" 38 | }, 39 | { 40 | "name": "common_ui_dialog_cancel", 41 | "value": "取消" 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /framework/src/main/resources/zh_CN/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "permission_location_reason", 5 | "value": "我们需要访问您的位置以提供个性化的考勤服务,使您无论身在何处都能轻松签到。" 6 | }, 7 | { 8 | "name": "permission_network_reason", 9 | "value": "为确保最佳服务质量,我们请求监控您的网络状态。这有助于我们根据您的连接类型优化您的体验。" 10 | }, 11 | { 12 | "name": "permission_internet_reason", 13 | "value": "我们的应用需要互联网访问以提供最新的信息和服务。您的隐私和安全是我们的首要任务。" 14 | }, 15 | { 16 | "name": "permission_camera_reason", 17 | "value": "允许相机访问,以便您可以使用扫描二维码或在应用内直接拍摄照片等视觉功能。" 18 | }, 19 | { 20 | "name": "permission_microphone_reason", 21 | "value": "授予麦克风访问权限以启用语音交互功能,使您在使用应用时能够享受免提体验。" 22 | }, 23 | { 24 | "name": "common_ui_dialog_request_permission_title", 25 | "value": "请求权限" 26 | }, 27 | { 28 | "name": "common_ui_dialog_webview_request_permission_title", 29 | "value": "Webview 请求权限" 30 | }, 31 | { 32 | "name": "common_ui_dialog_confirm", 33 | "value": "确认" 34 | }, 35 | { 36 | "name": "common_ui_dialog_ok", 37 | "value": "确定" 38 | }, 39 | { 40 | "name": "common_ui_dialog_cancel", 41 | "value": "取消" 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /framework/src/main/ets/DialogUtils.ets: -------------------------------------------------------------------------------- 1 | export const showAlert = 2 | (message: string | Resource, gotResult: (success: boolean, message: string | null,) => void, 3 | title: string | Resource = "") => { 4 | AlertDialog.show({ 5 | title, 6 | message, 7 | autoCancel: false, 8 | isModal: true, 9 | cancel: () => { 10 | gotResult(false, null) 11 | }, 12 | confirm: { 13 | value: $r('app.string.common_ui_dialog_ok'), 14 | action: () => { 15 | gotResult(true, null) 16 | } 17 | } 18 | }) 19 | } 20 | 21 | 22 | export const showConfirm = 23 | (message: string | Resource, gotResult: (success: boolean, message: string | null) => void, 24 | title: string | Resource = "") => { 25 | AlertDialog.show({ 26 | title, 27 | message, 28 | autoCancel: false, 29 | primaryButton: { 30 | value: $r('app.string.common_ui_dialog_confirm'), 31 | action: () => { 32 | gotResult(true, null) 33 | } 34 | }, 35 | secondaryButton: { 36 | value: $r('app.string.common_ui_dialog_cancel'), 37 | action: () => { 38 | gotResult(false, null) 39 | } 40 | }, 41 | cancel: () => { 42 | gotResult(false, null) 43 | } 44 | }) 45 | } -------------------------------------------------------------------------------- /framework/src/main/ets/plugin/CloakGlobalObject.ets: -------------------------------------------------------------------------------- 1 | import BuildProfile from '../../../../BuildProfile'; 2 | import { CloakPlugin, PluginMetadata } from './CloakPlugin'; 3 | 4 | interface CloakGlobalObjectMetadata extends PluginMetadata { 5 | platform: string; 6 | } 7 | 8 | export default class CloakGlobalObject extends CloakPlugin { 9 | protected _name = "Cloak"; 10 | protected _version = BuildProfile.HAR_VERSION; 11 | protected _description = "Cloak is a Hybrid Framework for HarmonyOS"; 12 | 13 | /** 14 | * window.Cloak { 15 | * plugins: { 16 | * xxxx: { 17 | * metadata: { 18 | * name: '', 19 | * version: '', 20 | * description: '', 21 | * methods: [], 22 | * TSType: 'npm library url', 23 | * permission: {} 24 | * } 25 | * exposed method1 ..... 26 | * exposed method2 ..... 27 | * } 28 | * } 29 | * } 30 | * 31 | * 32 | **/ 33 | 34 | 35 | public getMetadata(): CloakGlobalObjectMetadata { 36 | const superMetaData = super.getMetadata(); 37 | 38 | return { 39 | name: superMetaData.name, 40 | version: superMetaData.version, 41 | description: superMetaData.description, 42 | methods: superMetaData.methods, 43 | platform: "HarmonyOS", 44 | }; 45 | } 46 | } -------------------------------------------------------------------------------- /CHANGELOG-EN.md: -------------------------------------------------------------------------------- 1 | [**中文版**](./CHANGELOG.md) | [English Version](./CHANGELOG-EN.md) 2 | 3 | # Changelog 4 | 5 | ## 1.0.5 (2025-04-11) 6 | - Added static property `Cloak.CONFIG` for retrieving the current configuration 7 | - Changed static property `Cloak.APP_CONTEXT` to `Cloak.APP_ABILITY`; the original context can now be accessed via `Cloak.getAbility().context` 8 | - Added static method `Cloak.isStartupCompleted()` to determine whether the application has completed startup 9 | - Added lifecycle methods `beforeRegister`, `afterRegister`, and `onAttach` to `CloakPlugin` 10 | - Introduced a logging module 11 | 12 | ## 1.0.4 (2025-03-13) 13 | - Support event listeners and message handlers 14 | 15 | ## 1.0.3 (2025-02-27) 16 | - Added configuration option `APP_USE_REAL_HOST_RESOURCE` to control whether to load remote resources configured by `APP_HOST`, otherwise use the local resource directory configured by `APP_FOLDER` 17 | - Fallback to index.html if requested static resource does not exist 18 | 19 | ## 1.0.2 (2025-02-26) 20 | - Added configuration option `WEB_VIEW_USE_APP_PERMISSION` to control whether WebView uses app permissions, eliminating authorization prompts 21 | 22 | ## 1.0.1 (2025-02-25) 23 | - Added default index.html page for quick project startup 24 | 25 | ## 1.0.0 (2025-02-25) 26 | - Implemented JavaScript ↔ HarmonyOS native communication 27 | - Established base plugin architecture -------------------------------------------------------------------------------- /hvigor/hvigor-config.json5: -------------------------------------------------------------------------------- 1 | { 2 | "modelVersion": "5.0.1", 3 | "dependencies": { 4 | }, 5 | "execution": { 6 | // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ 7 | // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ 8 | // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ 9 | // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ 10 | // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ 11 | }, 12 | "logging": { 13 | // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ 14 | }, 15 | "debugging": { 16 | // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ 17 | }, 18 | "nodeOptions": { 19 | // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ 20 | // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /framework/src/main/ets/logger.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from "@kit.PerformanceAnalysisKit"; 2 | 3 | export const LOG_DOMAIN = 0xC000; 4 | 5 | export const LOG_TAG = 'CloakFramework'; 6 | 7 | 8 | export interface ILogger { 9 | debug: (format: string, ...args: (string | number | boolean)[]) => void; 10 | info: (format: string, ...args: (string | number | boolean)[]) => void; 11 | warn: (format: string, ...args: (string | number | boolean)[]) => void; 12 | error: (format: string, ...args: (string | number | boolean)[]) => void; 13 | fatal: (format: string, ...args: (string | number | boolean)[]) => void; 14 | } 15 | 16 | export const getLogger = (domain: number, tag: string): ILogger => { 17 | 18 | let tagInFormat = `[${tag}] `; 19 | 20 | const logger: ILogger = { 21 | debug: (format: string, ...args: (string | number | boolean)[]) => { 22 | hilog.debug(domain, tag, tagInFormat + format, ...args); 23 | }, 24 | info: (format: string, ...args: (string | number | boolean)[]) => { 25 | hilog.info(domain, tag, tagInFormat + format, ...args); 26 | }, 27 | warn: (format: string, ...args: (string | number | boolean)[]) => { 28 | hilog.warn(domain, tag, tagInFormat + format, ...args); 29 | }, 30 | error: (format: string, ...args: (string | number | boolean)[]) => { 31 | hilog.error(domain, tag, tagInFormat + format, ...args); 32 | }, 33 | fatal: (format: string, ...args: (string | number | boolean)[]) => { 34 | hilog.debug(domain, tag, tagInFormat + format, ...args); 35 | } 36 | }; 37 | 38 | return logger; 39 | } 40 | 41 | export default getLogger(LOG_DOMAIN, LOG_TAG); -------------------------------------------------------------------------------- /entry/src/main/resources/zh/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "module_desc", 5 | "value": "Cloak Frame 演示" 6 | }, 7 | { 8 | "name": "EntryAbility_desc", 9 | "value": "Cloak 應用程式" 10 | }, 11 | { 12 | "name": "EntryAbility_label", 13 | "value": "Cloak" 14 | }, 15 | { 16 | "name": "permission_location_reason", 17 | "value": "我們需要存取您的位置以提供個性化的簽到服務,讓您無論身在何處都能輕鬆簽到。" 18 | }, 19 | { 20 | "name": "permission_network_reason", 21 | "value": "為了確保最佳的服務質量,我們請求監控您的網絡狀態。這有助於我們根據您的連接類型優化您的體驗。" 22 | }, 23 | { 24 | "name": "permission_internet_reason", 25 | "value": "我們的應用程式需要互聯網連接以提供最新的信息和服務。您的隱私和安全是我們的首要任務。" 26 | }, 27 | { 28 | "name": "permission_camera_reason", 29 | "value": "允許相機存取,以便您可以使用掃描二維碼或在應用程式內直接拍攝照片等視覺功能。" 30 | }, 31 | { 32 | "name": "permission_microphone_reason", 33 | "value": "授予麥克風存取權限以啟用語音交互功能,讓您在使用應用程式時享受免提體驗。" 34 | }, 35 | { 36 | "name": "permission_vibrate_reason", 37 | "value": "我們請求使用振動功能,以提供通知和應用內操作的觸覺反饋,增強您的互動體驗,同時不會打擾周圍的人。" 38 | }, 39 | { 40 | "name": "common_ui_dialog_request_permission_title", 41 | "value": "請求權限" 42 | }, 43 | { 44 | "name": "common_ui_dialog_webview_request_permission_title", 45 | "value": "Webview 請求權限" 46 | }, 47 | { 48 | "name": "common_ui_dialog_confirm", 49 | "value": "確認" 50 | }, 51 | { 52 | "name": "common_ui_dialog_ok", 53 | "value": "確定" 54 | }, 55 | { 56 | "name": "common_ui_dialog_cancel", 57 | "value": "取消" 58 | } 59 | ] 60 | } -------------------------------------------------------------------------------- /entry/src/main/resources/zh_CN/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "module_desc", 5 | "value": "Cloak Frame 演示" 6 | }, 7 | { 8 | "name": "EntryAbility_desc", 9 | "value": "Cloak 应用" 10 | }, 11 | { 12 | "name": "EntryAbility_label", 13 | "value": "Cloak" 14 | }, 15 | { 16 | "name": "permission_location_reason", 17 | "value": "我们需要访问您的位置以提供个性化的考勤服务,使您无论身在何处都能轻松签到。" 18 | }, 19 | { 20 | "name": "permission_network_reason", 21 | "value": "为确保最佳服务质量,我们请求监控您的网络状态。这有助于我们根据您的连接类型优化您的体验。" 22 | }, 23 | { 24 | "name": "permission_internet_reason", 25 | "value": "我们的应用需要互联网访问以提供最新的信息和服务。您的隐私和安全是我们的首要任务。" 26 | }, 27 | { 28 | "name": "permission_camera_reason", 29 | "value": "允许相机访问,以便您可以使用扫描二维码或在应用内直接拍摄照片等视觉功能。" 30 | }, 31 | { 32 | "name": "permission_microphone_reason", 33 | "value": "授予麦克风访问权限以启用语音交互功能,使您在使用应用时能够享受免提体验。" 34 | }, 35 | { 36 | "name": "permission_vibrate_reason", 37 | "value": "我们请求使用振动功能,以便为通知和应用内操作提供触觉反馈,增强您的互动体验,同时不会打扰周围的人。" 38 | }, 39 | { 40 | "name": "common_ui_dialog_request_permission_title", 41 | "value": "请求权限" 42 | }, 43 | { 44 | "name": "common_ui_dialog_webview_request_permission_title", 45 | "value": "Webview 请求权限" 46 | }, 47 | { 48 | "name": "common_ui_dialog_confirm", 49 | "value": "确认" 50 | }, 51 | { 52 | "name": "common_ui_dialog_ok", 53 | "value": "确定" 54 | }, 55 | { 56 | "name": "common_ui_dialog_cancel", 57 | "value": "取消" 58 | } 59 | ] 60 | } -------------------------------------------------------------------------------- /framework/src/npm/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@wisdomgarden/cloak" { 2 | export interface ESObject { 3 | [key: string]: number | boolean | string | null | undefined | ESObject; 4 | } 5 | 6 | export type NormalType = ESObject | boolean | string | null | undefined | ESObject; 7 | 8 | export interface MessagePort { 9 | postMessage(message: string): void; 10 | onmessage: (event: { data: string }) => void; 11 | } 12 | 13 | export interface PluginMetadata { 14 | name: string; 15 | version: string; 16 | description: string; 17 | methods: string[]; 18 | } 19 | 20 | export interface PluginBase { 21 | name: string; 22 | // messageHandler?: (message: MessagePayload) => void; // private 23 | setMessageHandler(callback: (message: NormalType) => void): void; 24 | onMessage(message: NormalType): void; 25 | sendMessage(message: NormalType): void; 26 | register(plugin: Plugin): void; 27 | } 28 | 29 | export interface Plugin extends PluginBase { 30 | metadata: PluginMetadata; 31 | getMetadata: () => PluginMetadata; 32 | [key: string]: (...args: any) => NormalType | Promise; 33 | } 34 | 35 | export interface CloakPlugins { 36 | [pluginName: string]: Plugin; 37 | } 38 | 39 | export interface Cloak { 40 | channel: MessagePort; 41 | plugins: CloakPlugins; 42 | metadata: PluginMetadata; 43 | platform: "HarmonyOS"; 44 | getMetadata(): PluginMetadata; 45 | } 46 | 47 | export const Cloak: Cloak; 48 | } 49 | 50 | declare global { 51 | interface WindowEventMap { 52 | CloakReady: Event; 53 | } 54 | 55 | interface Window { 56 | Cloak: Cloak; 57 | } 58 | } 59 | 60 | export {}; 61 | -------------------------------------------------------------------------------- /framework/src/main/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "permission_location_reason", 5 | "value": "We need access to your location to offer personalized attendance services, making it easier for you to check in wherever you are." 6 | }, 7 | { 8 | "name": "permission_network_reason", 9 | "value": "To ensure the best service quality, we request permission to monitor your network status. This helps us optimize your experience based on your connection type." 10 | }, 11 | { 12 | "name": "permission_internet_reason", 13 | "value": "Our app requires internet access to deliver up-to-date information and services. Your privacy and security are our priority." 14 | }, 15 | { 16 | "name": "permission_camera_reason", 17 | "value": "Allow camera access so you can use visual features such as scanning QR codes or capturing photos directly within the app." 18 | }, 19 | { 20 | "name": "permission_microphone_reason", 21 | "value": "Grant microphone access to enable voice interaction features, allowing for a hands-free experience when using the app." 22 | }, 23 | { 24 | "name": "common_ui_dialog_request_permission_title", 25 | "value": "Request Permission" 26 | }, 27 | { 28 | "name": "common_ui_dialog_webview_request_permission_title", 29 | "value": "Webview Request Permission" 30 | }, 31 | { 32 | "name": "common_ui_dialog_confirm", 33 | "value": "Confirm" 34 | }, 35 | { 36 | "name": "common_ui_dialog_ok", 37 | "value": "OK" 38 | }, 39 | { 40 | "name": "common_ui_dialog_cancel", 41 | "value": "Cancel" 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /scripts/remote-debug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get current fport rule list 4 | CURRENT_FPORT_LIST=$(hdc fport ls) 5 | 6 | # Delete the existing fport rule one by one 7 | while IFS= read -r line; do 8 | # Extract the taskline 9 | IFS=' ' read -ra parts <<< "$line" 10 | taskline="${parts[1]} ${parts[2]}" 11 | 12 | # Delete the corresponding fport rule 13 | echo "Removing forward rule for $taskline" 14 | hdc fport rm $taskline 15 | result=$? 16 | 17 | if [ $result -eq 0 ]; then 18 | echo "Remove forward rule success, taskline:$taskline" 19 | else 20 | echo "Failed to remove forward rule, taskline:$taskline" 21 | fi 22 | 23 | done <<< "$CURRENT_FPORT_LIST" 24 | 25 | # Initial port number 26 | INITIAL_PORT=9222 27 | 28 | # Get the current port number, use initial port number if not set previously 29 | CURRENT_PORT=${PORT:-$INITIAL_PORT} 30 | 31 | # Get the list of all PIDs that match the condition 32 | PID_LIST=$(hdc shell cat /proc/net/unix | grep webview_devtools_remote_ | awk -F '_' '{print $NF}') 33 | 34 | if [ -z "$PID_LIST" ]; then 35 | echo "Failed to retrieve PID from the device" 36 | exit 1 37 | fi 38 | 39 | # Increment the port number 40 | PORT=$CURRENT_PORT 41 | 42 | # Forward ports for each application one by one 43 | for PID in $PID_LIST; do 44 | # Increment the port number 45 | PORT=$((PORT + 1)) 46 | 47 | # Execute the hdc fport command 48 | hdc fport tcp:$PORT localabstract:webview_devtools_remote_$PID 49 | 50 | # Check if the command executed successfully 51 | if [ $? -ne 0 ]; then 52 | echo "Failed to execute hdc fport command" 53 | exit 1 54 | fi 55 | done 56 | 57 | # List all forwarded ports 58 | hdc fport ls -------------------------------------------------------------------------------- /entry/src/test/LocalUnit.test.ets: -------------------------------------------------------------------------------- 1 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 2 | 3 | export default function localUnitTest() { 4 | describe('localUnitTest', () => { 5 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 6 | beforeAll(() => { 7 | // Presets an action, which is performed only once before all test cases of the test suite start. 8 | // This API supports only one parameter: preset action function. 9 | }); 10 | beforeEach(() => { 11 | // Presets an action, which is performed before each unit test case starts. 12 | // The number of execution times is the same as the number of test cases defined by **it**. 13 | // This API supports only one parameter: preset action function. 14 | }); 15 | afterEach(() => { 16 | // Presets a clear action, which is performed after each unit test case ends. 17 | // The number of execution times is the same as the number of test cases defined by **it**. 18 | // This API supports only one parameter: clear action function. 19 | }); 20 | afterAll(() => { 21 | // Presets a clear action, which is performed after all test cases of the test suite end. 22 | // This API supports only one parameter: clear action function. 23 | }); 24 | it('assertContain', 0, () => { 25 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 26 | let a = 'abc'; 27 | let b = 'b'; 28 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 29 | expect(a).assertContain(b); 30 | expect(a).assertEqual(a); 31 | }); 32 | }); 33 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/test/Ability.test.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 3 | 4 | export default function abilityTest() { 5 | describe('ActsAbilityTest', () => { 6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 7 | beforeAll(() => { 8 | // Presets an action, which is performed only once before all test cases of the test suite start. 9 | // This API supports only one parameter: preset action function. 10 | }) 11 | beforeEach(() => { 12 | // Presets an action, which is performed before each unit test case starts. 13 | // The number of execution times is the same as the number of test cases defined by **it**. 14 | // This API supports only one parameter: preset action function. 15 | }) 16 | afterEach(() => { 17 | // Presets a clear action, which is performed after each unit test case ends. 18 | // The number of execution times is the same as the number of test cases defined by **it**. 19 | // This API supports only one parameter: clear action function. 20 | }) 21 | afterAll(() => { 22 | // Presets a clear action, which is performed after all test cases of the test suite end. 23 | // This API supports only one parameter: clear action function. 24 | }) 25 | it('assertContain', 0, () => { 26 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 27 | hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); 28 | let a = 'abc'; 29 | let b = 'b'; 30 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 31 | expect(a).assertContain(b); 32 | expect(a).assertEqual(a); 33 | }) 34 | }) 35 | } -------------------------------------------------------------------------------- /framework/src/main/ets/Cloak.ets: -------------------------------------------------------------------------------- 1 | import { CloakPlugin } from './plugin/CloakPlugin'; 2 | import PluginManager from './plugin/PluginManager'; 3 | import { UIAbility } from '@kit.AbilityKit'; 4 | import { util } from '@kit.ArkTS'; 5 | import Config, { APPConfig } from './Config'; 6 | 7 | export default class Cloak { 8 | private static APP_ABILITY: UIAbility; 9 | public static SAFE_TOP = 32; 10 | public static SAFE_BOTTOM = 28; 11 | private static CONFIG: APPConfig; 12 | private static IS_STARTUP_COMPLETED = false; 13 | 14 | constructor(uiAbility: UIAbility, plugins?: CloakPlugin[]) { 15 | Cloak.APP_ABILITY = uiAbility; 16 | this.parseConfig(); 17 | if (plugins) { 18 | this.addPlugins(plugins); 19 | } 20 | } 21 | 22 | public static getAbility() { 23 | return Cloak.APP_ABILITY; 24 | } 25 | 26 | public static getConfig() { 27 | return Cloak.CONFIG; 28 | } 29 | 30 | public static setStartupCompleted() { 31 | Cloak.IS_STARTUP_COMPLETED = true; 32 | } 33 | 34 | public static isStartupCompleted() { 35 | return Cloak.IS_STARTUP_COMPLETED; 36 | } 37 | 38 | public addPlugins(plugins: CloakPlugin[]) { 39 | plugins.forEach(plugin => { 40 | PluginManager.addPlugin(plugin) 41 | }) 42 | } 43 | 44 | private parseConfig() { 45 | const content = Cloak.APP_ABILITY.context.getApplicationContext() 46 | .resourceManager 47 | .getRawFileContentSync('config.json'); 48 | 49 | const textDecoder = util.TextDecoder.create('utf-8', { 50 | ignoreBOM: true 51 | }); 52 | const config = JSON.parse(textDecoder.decodeToString(content)) as Partial 53 | 54 | Config.APP_FOLDER = config.APP_FOLDER ?? Config.APP_FOLDER; 55 | Config.APP_HOST = config.APP_HOST ?? Config.APP_HOST; 56 | Config.APP_IDENTITY_USER_AGENT = config.APP_IDENTITY_USER_AGENT ?? Config.APP_IDENTITY_USER_AGENT; 57 | Config.IS_DEBUG = config.IS_DEBUG ?? Config.IS_DEBUG; 58 | Config.WEB_VIEW_USE_APP_PERMISSION = config.WEB_VIEW_USE_APP_PERMISSION ?? Config.WEB_VIEW_USE_APP_PERMISSION; 59 | Config.APP_USE_REAL_HOST_RESOURCE = config.APP_USE_REAL_HOST_RESOURCE ?? Config.APP_USE_REAL_HOST_RESOURCE; 60 | 61 | Cloak.CONFIG = Config; 62 | } 63 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "module_desc", 5 | "value": "Cloak Frame demo" 6 | }, 7 | { 8 | "name": "EntryAbility_desc", 9 | "value": "Cloak APP" 10 | }, 11 | { 12 | "name": "EntryAbility_label", 13 | "value": "Cloak" 14 | }, 15 | { 16 | "name": "permission_location_reason", 17 | "value": "We need access to your location to offer personalized attendance services, making it easier for you to check in wherever you are." 18 | }, 19 | { 20 | "name": "permission_network_reason", 21 | "value": "To ensure the best service quality, we request permission to monitor your network status. This helps us optimize your experience based on your connection type." 22 | }, 23 | { 24 | "name": "permission_internet_reason", 25 | "value": "Our app requires internet access to deliver up-to-date information and services. Your privacy and security are our priority." 26 | }, 27 | { 28 | "name": "permission_camera_reason", 29 | "value": "Allow camera access so you can use visual features such as scanning QR codes or capturing photos directly within the app." 30 | }, 31 | { 32 | "name": "permission_microphone_reason", 33 | "value": "Grant microphone access to enable voice interaction features, allowing for a hands-free experience when using the app." 34 | }, 35 | { 36 | "name": "permission_vibrate_reason", 37 | "value": "We request permission to use vibration features to provide tactile feedback for notifications and in-app actions, enhancing your interactive experience without disturbing others around you." 38 | }, 39 | { 40 | "name": "common_ui_dialog_request_permission_title", 41 | "value": "Request Permission" 42 | }, 43 | { 44 | "name": "common_ui_dialog_webview_request_permission_title", 45 | "value": "Webview Request Permission" 46 | }, 47 | { 48 | "name": "common_ui_dialog_confirm", 49 | "value": "Confirm" 50 | }, 51 | { 52 | "name": "common_ui_dialog_ok", 53 | "value": "OK" 54 | }, 55 | { 56 | "name": "common_ui_dialog_cancel", 57 | "value": "Cancel" 58 | } 59 | ] 60 | } -------------------------------------------------------------------------------- /scripts/build_har.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function init_context() { 4 | HARMONYOS_PROJECT_DIR=$(dirname $(dirname "$0")) 5 | HARMONYOS_PROJECT_DIR=$(cd "$HARMONYOS_PROJECT_DIR" && pwd) 6 | ohpm install --all --registry https://ohpm.openharmony.cn/ohpm/ --strict_ssl true 7 | hvigorw clean --no-daemon 8 | } 9 | 10 | function init_JDK() { 11 | OLD_JAVA_HOME=$JAVA_HOME 12 | OLD_PATH=$PATH 13 | 14 | export JAVA_HOME=$JAVA_HOME_17 15 | export PATH=$JAVA_HOME/bin:$PATH 16 | } 17 | 18 | function restore_JDK() { 19 | export JAVA_HOME=$OLD_JAVA_HOME 20 | export PATH=$OLD_PATH 21 | # echo "Restored to original JAVA_HOME: $JAVA_HOME" 22 | } 23 | 24 | function build_har() { 25 | # Backup and modify .gitignore 26 | local gitignore_file="framework/.gitignore" 27 | local gitignore_backup="framework/.gitignore.bak" 28 | 29 | cp "$gitignore_file" "$gitignore_backup" 30 | 31 | sed -i '' '/^LICENSE$/d' "$gitignore_file" 32 | sed -i '' '/^CHANGELOG-EN.md$/d' "$gitignore_file" 33 | sed -i '' '/^CHANGELOG.md$/d' "$gitignore_file" 34 | sed -i '' '/^README-EN.md$/d' "$gitignore_file" 35 | sed -i '' '/^README.md$/d' "$gitignore_file" 36 | 37 | local module="framework" 38 | hvigorw assembleHar --mode module -p product=default -p module=$module@default -p buildMode=release -p debuggable=false --analyze=normal --parallel --incremental --no-daemon 39 | 40 | # Restore .gitignore 41 | mv "$gitignore_backup" "$gitignore_file" 42 | 43 | local har_file="${module}/build/default/outputs/default/${module}.har" 44 | if [ ! -f "$har_file" ]; then 45 | echo "Build failed. File $har_file not found!" 46 | exit 1 47 | fi 48 | 49 | local size=$(ls -lh "$har_file" | awk '{print $5}') 50 | local date=$(ls -lh "$har_file" | awk '{print $6, $7, $8}') 51 | printf "%-30s %-10s %s\n" "$module.har" "$size" "$date" 52 | echo "----------------------------------------" 53 | } 54 | 55 | main() { 56 | init_context 57 | 58 | local startTime=$(date '+%s') 59 | 60 | init_JDK 61 | build_har 62 | restore_JDK 63 | 64 | local endTime=$(date '+%s') 65 | local elapsedTime=$(expr $endTime - $startTime) 66 | printf "\nbuild success in ${elapsedTime}s..." 67 | } 68 | 69 | main 70 | -------------------------------------------------------------------------------- /entry/src/main/resources/rawfile/www/css/demo.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | html, body { 7 | width: 100vw; 8 | color: black; 9 | font-size: 16px; 10 | } 11 | 12 | body { 13 | height: 100vh; 14 | scroll: auto; 15 | padding: 1rem; 16 | box-sizing: border-box; 17 | } 18 | 19 | section { 20 | margin: 1.5rem 0; 21 | padding: 1rem; 22 | border: 1px solid #ccc; 23 | border-radius: 8px; 24 | scroll: auto; 25 | } 26 | 27 | h2 { 28 | margin-bottom: 1rem; 29 | } 30 | 31 | button { 32 | margin: 0.5rem; 33 | padding: 0.5rem 1rem; 34 | background-color: #4CAF50; 35 | color: white; 36 | border: none; 37 | border-radius: 4px; 38 | cursor: pointer; 39 | } 40 | 41 | button:hover { 42 | background-color: #45a049; 43 | } 44 | 45 | #mediaPreview { 46 | margin-top: 1rem; 47 | max-width: 100%; 48 | } 49 | 50 | #mediaPreview img, 51 | #mediaPreview video, 52 | #mediaPreview audio { 53 | max-width: 100%; 54 | margin-top: 0.5rem; 55 | } 56 | 57 | #locationInfo, #qrResult { 58 | margin-top: 1rem; 59 | padding: 0.5rem; 60 | background-color: #f0f0f0; 61 | overflow: scroll; 62 | word-wrap: break-word; 63 | line-break: auto; 64 | width: 100%; 65 | height: auto; 66 | } 67 | 68 | #qrVideo { 69 | width: 100%; 70 | position: relative; 71 | } 72 | 73 | #notesList { 74 | margin-top: 1rem; 75 | } 76 | 77 | .note-item { 78 | padding: 0.5rem; 79 | margin: 0.5rem 0; 80 | background-color: #f0f0f0; 81 | display: flex; 82 | justify-content: space-between; 83 | align-items: center; 84 | } 85 | 86 | .demo { 87 | background-color: green; 88 | margin: 1rem 0; 89 | } 90 | 91 | .plugin-item { 92 | margin: 1rem 0; 93 | padding: 1rem; 94 | border: 1px solid #ccc; 95 | border-radius: 8px; 96 | background-color: #f9f9f9; 97 | } 98 | 99 | .plugin-item h3 { 100 | margin-bottom: 0.5rem; 101 | color: #2196F3; 102 | font-family: 'monospace'; 103 | } 104 | 105 | .plugin-item p { 106 | margin: 0.5rem 0; 107 | white-space: pre-wrap; 108 | word-wrap: break-word; 109 | word-break: break-word; 110 | } -------------------------------------------------------------------------------- /framework/src/main/resources/rawfile/CloakClient.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | class PluginBase { 3 | constructor(name) { 4 | this.name = name; 5 | } 6 | 7 | setMessageHandler(callback) { 8 | this.messageHandler = callback; 9 | } 10 | 11 | onMessage(message) { 12 | if (this.messageHandler) { 13 | this.messageHandler(message); 14 | } 15 | } 16 | 17 | sendMessage(message) { 18 | Cloak.channel.postMessage( 19 | JSON.stringify({ 20 | pluginName: this.name, 21 | payload: message, 22 | }) 23 | ); 24 | } 25 | 26 | register() { 27 | if(this.registered) { 28 | return; 29 | } 30 | const pluginRegister = window.__CloakPluginsRegister && window.__CloakPluginsRegister[this.name]; 31 | if(pluginRegister){ 32 | pluginRegister(this); 33 | } 34 | } 35 | } 36 | 37 | // IChannelMessage 38 | window.addEventListener( 39 | "message", 40 | function (event) { 41 | if (event.data === "Cloak__init_port__") { 42 | if (event.ports[0] !== null) { 43 | Cloak.channel = event.ports[0]; 44 | 45 | Cloak.channel.onmessage = function (event) { 46 | const data = JSON.parse(event.data); 47 | 48 | const plugin = Cloak.plugins[data.pluginName]; 49 | plugin.onMessage(data.payload); 50 | }; 51 | window.document.dispatchEvent(new Event("CloakReady")); 52 | } 53 | } 54 | }, 55 | { once: true } 56 | ); 57 | 58 | const PLUGIN_PREFIX = "_cloak_plugin_"; 59 | const cloakPlugins = Object.keys(window).filter((key) => key.startsWith(PLUGIN_PREFIX)); 60 | Cloak.plugins = {}; 61 | Cloak.metadata = Cloak.getMetadata(); 62 | Cloak.name = "Cloak"; 63 | Cloak.registered = true; 64 | Object.setPrototypeOf(Cloak, PluginBase.prototype); 65 | 66 | cloakPlugins.forEach((pluginName) => { 67 | const newName = pluginName.substring(PLUGIN_PREFIX.length); 68 | 69 | const plugin = window[pluginName]; 70 | plugin.name = newName; 71 | plugin.registered = false; 72 | Cloak.plugins[newName] = plugin; 73 | 74 | plugin.metadata = Cloak.plugins[newName].getMetadata(); 75 | 76 | Object.setPrototypeOf(plugin, PluginBase.prototype); 77 | 78 | plugin.register(); 79 | }); 80 | })(); 81 | -------------------------------------------------------------------------------- /entry/src/main/ets/entryability/EntryAbility.ets: -------------------------------------------------------------------------------- 1 | import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; 2 | import { hilog } from '@kit.PerformanceAnalysisKit'; 3 | import { window } from '@kit.ArkUI'; 4 | import { Cloak } from '@wisdomgarden/cloak'; 5 | import { CloakPluginDevice } from '@wisdomgarden/cloak-plugin-device'; 6 | import { CloakPluginPermission } from '@wisdomgarden/cloak-plugin-permission'; 7 | import { CloakPluginGeolocation } from '@wisdomgarden/cloak-plugin-geolocation'; 8 | import { CloakPluginInAppBrowser } from '@wisdomgarden/cloak-plugin-inappbrowser'; 9 | 10 | export default class EntryAbility extends UIAbility { 11 | onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 12 | this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); 13 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); 14 | 15 | const cloak = new Cloak(this) 16 | cloak.addPlugins([ 17 | new CloakPluginPermission(), 18 | new CloakPluginDevice(), 19 | new CloakPluginGeolocation(), 20 | new CloakPluginInAppBrowser(), 21 | ]) 22 | } 23 | 24 | onDestroy(): void { 25 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); 26 | } 27 | 28 | onWindowStageCreate(windowStage: window.WindowStage): void { 29 | // Main window is created, set main page for this ability 30 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); 31 | 32 | windowStage.loadContent('pages/Index', (err) => { 33 | if (err.code) { 34 | hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); 35 | return; 36 | } 37 | hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); 38 | }); 39 | } 40 | 41 | onWindowStageDestroy(): void { 42 | // Main window is destroyed, release UI related resources 43 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); 44 | } 45 | 46 | onForeground(): void { 47 | // Ability has brought to foreground 48 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); 49 | } 50 | 51 | onBackground(): void { 52 | // Ability has back to background 53 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); 54 | } 55 | } -------------------------------------------------------------------------------- /framework/src/main/ets/plugin/PluginManager.ets: -------------------------------------------------------------------------------- 1 | import CloakGlobalObject from './CloakGlobalObject'; 2 | import { CloakPlugin } from './CloakPlugin'; 3 | import { common } from '@kit.AbilityKit'; 4 | import { util } from '@kit.ArkTS'; 5 | import { IChannelMessage, ICloakContext } from '../types'; 6 | import { webview } from '@kit.ArkWeb'; 7 | import { Cloak } from '../../../../Index'; 8 | 9 | 10 | export default class PluginManager { 11 | private static _plugins = new Map(); 12 | private static _applicationContext: common.ApplicationContext; 13 | private static _uiContext: UIContext; 14 | private static _webviewController: WebviewController; 15 | private static _channelCloakPort: webview.WebMessagePort; 16 | private static cloakClientJS: string; 17 | 18 | public static setContext(context: ICloakContext) { 19 | PluginManager._applicationContext = context.applicationContext; 20 | PluginManager._uiContext = context.uiContext; 21 | PluginManager._webviewController = context.webviewController; 22 | PluginManager._channelCloakPort = context.channelCloakPort; 23 | 24 | CloakPlugin.setContext(context) 25 | } 26 | 27 | public static updateContext(channelCloakPort: webview.WebMessagePort) { 28 | PluginManager._channelCloakPort = channelCloakPort; 29 | CloakPlugin.updateContext(channelCloakPort) 30 | } 31 | 32 | public static get applicationContext(): common.ApplicationContext { 33 | return PluginManager._applicationContext; 34 | } 35 | 36 | public static get uiContext(): UIContext { 37 | return PluginManager._uiContext; 38 | } 39 | 40 | public static get webviewController(): WebviewController { 41 | return PluginManager._webviewController; 42 | } 43 | 44 | public static get channelCloakPort(): webview.WebMessagePort { 45 | return PluginManager._channelCloakPort; 46 | } 47 | 48 | public static addPlugin(plugin: CloakPlugin) { 49 | PluginManager._plugins.set(plugin.name, plugin) 50 | } 51 | 52 | public static registerPlugins() { 53 | PluginManager._plugins.forEach(plugin => { 54 | plugin.beforeRegister(); 55 | plugin.register(); 56 | plugin.afterRegister(); 57 | }) 58 | 59 | // process global Cloak 60 | const cloakGlobalObject = new CloakGlobalObject(); 61 | PluginManager._webviewController.registerJavaScriptProxy(cloakGlobalObject, "Cloak", 62 | ["getMetadata"]) 63 | } 64 | 65 | public static async initCloakClient() { 66 | 67 | if (!PluginManager.cloakClientJS) { 68 | const content = PluginManager._applicationContext.resourceManager.getRawFileContentSync('CloakClient.js'); 69 | 70 | const textDecoder = util.TextDecoder.create('utf-8', { 71 | ignoreBOM: true 72 | }); 73 | const contentStr = textDecoder.decodeToString(content) 74 | 75 | PluginManager.cloakClientJS = contentStr; 76 | } 77 | 78 | if (PluginManager.cloakClientJS) { 79 | // window.event `CloakReady` will be triggered 80 | await PluginManager._webviewController.runJavaScript(PluginManager.cloakClientJS); 81 | } 82 | 83 | 84 | PluginManager._plugins.forEach(plugin => { 85 | plugin.onAttach(); 86 | }) 87 | Cloak.setStartupCompleted() 88 | } 89 | 90 | public static onMessage(message: IChannelMessage) { 91 | const plugin = PluginManager._plugins.get(message.pluginName); 92 | plugin?.onMessage(message?.payload) 93 | } 94 | } -------------------------------------------------------------------------------- /entry/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "entry", 4 | "type": "entry", 5 | "description": "$string:module_desc", 6 | "mainElement": "EntryAbility", 7 | "deviceTypes": [ 8 | "phone", 9 | "tablet", 10 | "2in1", 11 | "car" 12 | ], 13 | "deliveryWithInstall": true, 14 | "installationFree": false, 15 | "pages": "$profile:main_pages", 16 | "abilities": [ 17 | { 18 | "name": "EntryAbility", 19 | "srcEntry": "./ets/entryability/EntryAbility.ets", 20 | "description": "$string:EntryAbility_desc", 21 | "icon": "$media:layered_image", 22 | "label": "$string:EntryAbility_label", 23 | "startWindowIcon": "$media:startIcon", 24 | "startWindowBackground": "$color:start_window_background", 25 | "exported": true, 26 | "supportWindowMode": ["fullscreen"], 27 | "skills": [ 28 | { 29 | "entities": [ 30 | "entity.system.home" 31 | ], 32 | "actions": [ 33 | "action.system.home" 34 | ] 35 | } 36 | ] 37 | } 38 | ], 39 | "extensionAbilities": [ 40 | { 41 | "name": "EntryBackupAbility", 42 | "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", 43 | "type": "backup", 44 | "exported": false, 45 | "metadata": [ 46 | { 47 | "name": "ohos.extension.backup", 48 | "resource": "$profile:backup_config" 49 | } 50 | ], 51 | } 52 | ], 53 | "requestPermissions": [ 54 | // system grant 55 | { 56 | "name": "ohos.permission.INTERNET", 57 | "reason": "$string:permission_internet_reason", 58 | "usedScene": { 59 | "abilities": [ 60 | "EntryAbility" 61 | ], 62 | } 63 | }, 64 | { 65 | "name": "ohos.permission.GET_NETWORK_INFO", 66 | "reason": "$string:permission_network_reason", 67 | "usedScene": { 68 | "abilities": [ 69 | "EntryAbility" 70 | ], 71 | } 72 | }, 73 | { 74 | name: "ohos.permission.GET_WIFI_INFO", 75 | "usedScene": { 76 | "abilities": [ 77 | "EntryAbility" 78 | ], 79 | "when": "inuse" 80 | } 81 | }, 82 | { 83 | name: "ohos.permission.GET_BUNDLE_INFO", 84 | "usedScene": { 85 | "abilities": [ 86 | "EntryAbility" 87 | ], 88 | "when": "inuse" 89 | } 90 | }, 91 | { 92 | name: "ohos.permission.VIBRATE", 93 | "usedScene": { 94 | "abilities": [ 95 | "EntryAbility" 96 | ], 97 | "when": "inuse" 98 | } 99 | }, 100 | { 101 | name: "ohos.permission.MODIFY_AUDIO_SETTINGS", 102 | "usedScene": { 103 | "abilities": [ 104 | "EntryAbility" 105 | ], 106 | "when": "inuse" 107 | } 108 | }, 109 | { 110 | name: "ohos.permission.STORE_PERSISTENT_DATA", 111 | "usedScene": { 112 | "abilities": [ 113 | "EntryAbility" 114 | ], 115 | "when": "inuse" 116 | } 117 | }, 118 | // user grant 119 | { 120 | "name": "ohos.permission.LOCATION", 121 | "reason": "$string:permission_location_reason", 122 | "usedScene": { 123 | "abilities": [ 124 | "EntryAbility" 125 | ], 126 | "when": "inuse" 127 | } 128 | }, 129 | { 130 | "name": "ohos.permission.APPROXIMATELY_LOCATION", 131 | "reason": "$string:permission_location_reason", 132 | "usedScene": { 133 | "abilities": [ 134 | "EntryAbility" 135 | ], 136 | "when": "inuse" 137 | } 138 | }, 139 | { 140 | "name": "ohos.permission.CAMERA", 141 | "reason": "$string:permission_camera_reason", 142 | "usedScene": { 143 | "abilities": [ 144 | "EntryAbility" 145 | ], 146 | "when": "inuse" 147 | } 148 | }, 149 | { 150 | "name": "ohos.permission.MICROPHONE", 151 | "reason": "$string:permission_microphone_reason", 152 | "usedScene": { 153 | "abilities": [ 154 | "EntryAbility" 155 | ], 156 | "when": "inuse" 157 | } 158 | }, 159 | ] 160 | } 161 | } -------------------------------------------------------------------------------- /entry/oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": true 4 | }, 5 | "lockfileVersion": 3, 6 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 7 | "specifiers": { 8 | "@wisdomgarden/cloak-plugin-device@../plugins/CloakPluginDevice": "@wisdomgarden/cloak-plugin-device@../plugins/CloakPluginDevice", 9 | "@wisdomgarden/cloak-plugin-geolocation@../plugins/CloakPluginGeolocation": "@wisdomgarden/cloak-plugin-geolocation@../plugins/CloakPluginGeolocation", 10 | "@wisdomgarden/cloak-plugin-http@^1.0.0": "@wisdomgarden/cloak-plugin-http@1.0.0", 11 | "@wisdomgarden/cloak-plugin-inappbrowser@^1.0.0": "@wisdomgarden/cloak-plugin-inappbrowser@1.0.0", 12 | "@wisdomgarden/cloak-plugin-open-native-settings@^1.0.0": "@wisdomgarden/cloak-plugin-open-native-settings@1.0.0", 13 | "@wisdomgarden/cloak-plugin-permission@^1.0.3": "@wisdomgarden/cloak-plugin-permission@1.0.3", 14 | "@wisdomgarden/cloak@../framework": "@wisdomgarden/cloak@../framework", 15 | "@wisdomgarden/cloak@1.0.4": "@wisdomgarden/cloak@../framework", 16 | "mime@^3.0.0": "mime@3.0.0" 17 | }, 18 | "packages": { 19 | "@wisdomgarden/cloak-plugin-device@../plugins/CloakPluginDevice": { 20 | "name": "@wisdomgarden/cloak-plugin-device", 21 | "version": "1.0.0", 22 | "resolved": "../plugins/CloakPluginDevice", 23 | "registryType": "local", 24 | "dependencies": { 25 | "@wisdomgarden/cloak": "file:../../framework" 26 | } 27 | }, 28 | "@wisdomgarden/cloak-plugin-geolocation@../plugins/CloakPluginGeolocation": { 29 | "name": "@wisdomgarden/cloak-plugin-geolocation", 30 | "version": "1.0.0", 31 | "resolved": "../plugins/CloakPluginGeolocation", 32 | "registryType": "local", 33 | "dependencies": { 34 | "@wisdomgarden/cloak": "file:../../framework" 35 | } 36 | }, 37 | "@wisdomgarden/cloak-plugin-http@1.0.0": { 38 | "name": "@wisdomgarden/cloak-plugin-http", 39 | "version": "1.0.0", 40 | "integrity": "sha512-0ck6WbX6zDUG3X5kGS9n1Ru6kjvM/sN67QIAogNdANHPE1fgELYeOS2zTjzzSE20vF1hUDPuQN63w0peMl8P8w==", 41 | "resolved": "https://ohpm.openharmony.cn/ohpm/@wisdomgarden/cloak-plugin-http/-/cloak-plugin-http-1.0.0.har", 42 | "registryType": "ohpm", 43 | "dependencies": { 44 | "@wisdomgarden/cloak": "1.0.4" 45 | } 46 | }, 47 | "@wisdomgarden/cloak-plugin-inappbrowser@1.0.0": { 48 | "name": "@wisdomgarden/cloak-plugin-inappbrowser", 49 | "version": "1.0.0", 50 | "integrity": "sha512-uQD2Ioo7PbCJojlkCBIfQ60HuPYcRNzSNYiabwm5x5mYjPuEWPwyIUAxFeddsB8uLxX7OfkW88IqGX0k+lsljQ==", 51 | "resolved": "https://ohpm.openharmony.cn/ohpm/@wisdomgarden/cloak-plugin-inappbrowser/-/cloak-plugin-inappbrowser-1.0.0.har", 52 | "registryType": "ohpm", 53 | "dependencies": { 54 | "@wisdomgarden/cloak": "1.0.4" 55 | } 56 | }, 57 | "@wisdomgarden/cloak-plugin-open-native-settings@1.0.0": { 58 | "name": "@wisdomgarden/cloak-plugin-open-native-settings", 59 | "version": "1.0.0", 60 | "integrity": "sha512-ycsvVhW2xS5wWpp7JN3Xx8jWGd6DBY2NE1/2y17tTCEpOTanOSrDSdsvYXuUyfWEqA2EOVrw1z61C0t6F0f4DA==", 61 | "resolved": "https://ohpm.openharmony.cn/ohpm/@wisdomgarden/cloak-plugin-open-native-settings/-/cloak-plugin-open-native-settings-1.0.0.har", 62 | "registryType": "ohpm", 63 | "dependencies": { 64 | "@wisdomgarden/cloak": "1.0.4" 65 | } 66 | }, 67 | "@wisdomgarden/cloak-plugin-permission@1.0.3": { 68 | "name": "@wisdomgarden/cloak-plugin-permission", 69 | "version": "1.0.3", 70 | "integrity": "sha512-oHNRqp36ItKJ0h7/XG3InQ0QMQR6R8u1gcFdbFd/RroC0CiFQ5d2CUtu9CDBs/FPkvx0SqHoPnDafLr4GgJPDg==", 71 | "resolved": "https://ohpm.openharmony.cn/ohpm/@wisdomgarden/cloak-plugin-permission/-/cloak-plugin-permission-1.0.3.har", 72 | "registryType": "ohpm", 73 | "dependencies": { 74 | "@wisdomgarden/cloak": "1.0.4" 75 | } 76 | }, 77 | "@wisdomgarden/cloak@../framework": { 78 | "name": "@wisdomgarden/cloak", 79 | "version": "1.0.5", 80 | "resolved": "../framework", 81 | "registryType": "local", 82 | "dependencies": { 83 | "mime": "^3.0.0" 84 | } 85 | }, 86 | "mime@3.0.0": { 87 | "name": "mime", 88 | "version": "3.0.0", 89 | "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", 90 | "resolved": "https://ohpm.openharmony.cn/ohpm/mime/-/mime-3.0.0.tgz", 91 | "shasum": "b374550dca3a0c18443b0c950a6a58f1931cf7a7", 92 | "registryType": "ohpm" 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /entry/src/main/resources/rawfile/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | Cloak 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |

HTML5 Feature Demo

20 |
21 |

Installed Plugins

22 |
23 |
24 |
25 |
26 |
27 | 28 |
29 |

Common Operations

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 44 |
45 | 46 |
47 |

Media Operations

48 | 49 | 50 | 51 |
52 | 53 | 54 | 55 |
56 | 57 | 58 | 59 | 60 |
61 |
62 | 63 |
64 |

Location Services

65 | 66 | 67 | 68 |
69 | 70 |
71 |
72 | 73 |
74 |

QR Code Scan

75 | 76 | 77 | 78 | 79 |
80 |
81 | 82 |
83 |

Other Features

84 | 85 |
86 | 87 |
88 |

IndexedDB Demo

89 |
90 | 91 | 92 |
93 |
94 |
95 |
96 | 97 | 98 | -------------------------------------------------------------------------------- /framework/src/main/resources/rawfile/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | Cloak Framework 10 | 85 | 86 | 148 | 149 | 150 | 151 |
152 |

Cloak Framework

153 |
154 |

Framework Information

155 |
156 |
157 |
158 |

UserAgent

159 |
160 |
161 |
162 |

Installed Plugins

163 |
164 |
165 |
166 |

Common Operations

167 | 168 | 169 |
170 |
171 | 172 | 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [**English Version**](./README-EN.md) | [中文版](./README.md) 2 | 3 | # **Cloak** - HarmonyOS 混合开发框架 4 | 5 | **Cloak** 是专为 **HarmonyOS** 设计的混合开发框架,类似 [Cordova](https://cordova.apache.org/) 和 [Capacitor](https://capacitorjs.com/),但具备 **更轻量**、**更高性能** 的特性。 6 | 7 | 该框架可将 Web 应用快速转换为原生应用,同时通过插件机制访问 HarmonyOS 原生能力。 8 | 9 | --- 10 | 11 | ## 核心特性 12 | 13 | - **快速打包**:将 H5 应用快速编译为 HarmonyOS 应用。 14 | - **原生能力访问**:通过插件机制调用原生接口。 15 | - **WebView 支持**:提供高性能 WebView 容器,确保 H5 应用流畅运行。 16 | - **插件开发**:支持开发者自定义插件以扩展原生功能。 17 | 18 | --- 19 | 20 | ## 提示 21 | 22 | - **兼容性说明**:与现有 Cordova 或 Capacitor 插件 **不兼容**,所有插件需基于 HarmonyOS 原生能力 **重新开发**。 23 | 24 | ### 现有插件 25 | 每个插件还有配套的同名 `npm` 包,方便 `Typescript` 和前端**扩展**。 26 | 27 | - **[CloakPluginPermission](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginPermission/README.md)** 28 | 29 | 用于检查、请求 HarmonyOS 权限。 30 | 31 | ```bash 32 | ohpm i @wisdomgarden/cloak-plugin-permission 33 | npm i @wisdomgarden/cloak-plugin-permission # optional 34 | ``` 35 | 36 | - **[CloakPluginHttp](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginHttp/README.md)** 37 | 38 | 用于在 Cloak 应用中进行 Native HTTP 请求。 39 | 40 | ```bash 41 | ohpm i @wisdomgarden/cloak-plugin-http 42 | npm i @wisdomgarden/cloak-plugin-http # optional 43 | ``` 44 | 45 | - **[CloakPluginInAppBrowser](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginInAppBrowser/README.md)** 46 | 47 | 用于在 Cloak 应用中打再开内部浏览器,执行操作。 48 | 49 | ```bash 50 | ohpm i @wisdomgarden/cloak-plugin-inappbrowser 51 | npm i @wisdomgarden/cloak-plugin-inappbrowser # optional 52 | ``` 53 | 54 | - **[CloakPluginOpenNativeSettings](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginOpenNativeSettings/README.md)** 55 | 56 | 用于在 Cloak 应用中打开原生设置页面。 57 | 58 | ```bash 59 | ohpm i @wisdomgarden/cloak-plugin-open-native-settings 60 | npm i @wisdomgarden/cloak-plugin-open-native-settings # optional 61 | ``` 62 | 63 | - **[CloakPluginJpush](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginJpush/README.md)** 64 | 65 | 用于在 Cloak 应用中集成极光推送,实现消息推送功能。 66 | 67 | ```bash 68 | ohpm install @wisdomgarden/cloak-plugin-jpush 69 | npm install @wisdomgarden/cloak-plugin-jpush # optional 70 | ``` 71 | 72 | - **[CloakPluginCodeScanner](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginCodeScanner/README.md)** 73 | 74 | 用于在 Cloak 应用中进行二维码扫描。 75 | 76 | ```bash 77 | ohpm install @wisdomgarden/cloak-plugin-code-scanner 78 | npm install @wisdomgarden/cloak-plugin-code-scanner # optional 79 | ``` 80 | 81 | 82 | ***✨✨✨ 更多插件即将推出,敬请期待。 ✨✨✨*** 83 | 84 | --- 85 | 86 | ## 使用方法 87 | 88 | ### 运行示例应用 89 | 1. **创建 EmptyAbility 应用** 90 | 91 | 参考华为官方文档:[构建第一个ArkTS应用(Stage模型)](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/start-with-ets-stage-V5) 92 | 93 | 2. **安装 Cloak 框架** 94 | ```bash 95 | ohpm install @wisdomgarden/cloak 96 | ``` 97 | 安装完成后即可运行内置示例应用。 98 | 99 | 3. **引入框架** 100 | 101 | 修改 `entry/src/main/ets/entryability/EntryAbility.ets` 的 `onCreate` 方法,添加以下代码: 102 | 103 | ```typescript 104 | import { Cloak } from '@wisdomgarden/cloak'; 105 | 106 | onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 107 | // ... 108 | 109 | const cloak = new Cloak(this) 110 | // cloak.addPlugins([ 111 | // new CloakPluginPermission(), 112 | // new CloakPluginDevice(), 113 | // new CloakPluginGeolocation(), 114 | // new CloakPluginInAppBrowser(), 115 | // ]) 116 | } 117 | ``` 118 | 119 | 修改 entry/src/main/ets/pages/Index.ets, 展示 webview 120 | 121 | ```typescript 122 | import { CloakWebview } from "@wisdomgarden/cloak" 123 | 124 | @Entry 125 | @Component 126 | struct CloakIndex { 127 | build() { 128 | Column() { 129 | CloakWebview() 130 | } 131 | } 132 | } 133 | ``` 134 | 135 | --- 136 | 137 | ### 集成你的 H5 应用 138 | 139 | 4. **创建配置文件** 140 | 在资源目录新建 `entry/src/main/resources/rawfile/config.json`: 141 | ```json 142 | { 143 | "APP_FOLDER": "www", 144 | "APP_HOST": "http://localhost", 145 | "APP_IDENTITY_USER_AGENT": "YourAppName/HarmonyOS", 146 | "IS_DEBUG": false, 147 | "WEB_VIEW_USE_APP_PERMISSION": true, 148 | "APP_USE_REAL_HOST_RESOURCE": false 149 | } 150 | ``` 151 | 152 | 153 | 5. **部署 H5 资源** 154 | 155 | 将 H5 应用文件(**以 index.html 为入口**)复制至 `entry/src/main/resources/rawfile/www` 156 | 157 | 158 | 6. **调试与运行** 159 | 160 | 通过 DevEco Studio 进行编译和实时调试。 161 | 162 | 163 | 7. **适配 H5 能力** 164 | 165 | 至此,配合 **[CloakPluginPermission](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginPermission/README.md)** 获得系统权限, 166 | Cloak 已经可以适配绝大多数 H5 应用所需的能力。比如 `navigator.mediaDevices`, `input (capture, file)`, `navigator.geolocation`, `indexedDB` 等。可参见 [Demo](https://github.com/WisdomGardenInc/Cloak/tree/master/entry/src/main/resources/rawfile/www) 167 | 168 | 169 | 8. **插件开发** 170 | 171 | 根据需求开发自定义插件,或通过[社区](https://ohpm.openharmony.cn)获取适配 HarmonyOS 的插件。 172 | 173 | ## 示例 174 | 175 | 完成[使用方法](#使用方法)步骤,其他可参照: https://github.com/WisdomGardenInc/Cloak -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig 2 | # Created by https://www.toptal.com/developers/gitignore/api/linux,macos,node,python,windows 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=linux,macos,node,python,windows 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### macOS ### 21 | # General 22 | .DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must end with two \r 27 | Icon 28 | 29 | 30 | # Thumbnails 31 | ._* 32 | 33 | # Files that might appear in the root of a volume 34 | .DocumentRevisions-V100 35 | .fseventsd 36 | .Spotlight-V100 37 | .TemporaryItems 38 | .Trashes 39 | .VolumeIcon.icns 40 | .com.apple.timemachine.donotpresent 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | ### macOS Patch ### 50 | # iCloud generated files 51 | *.icloud 52 | 53 | ### VisualStudioCode ### 54 | .vscode/* 55 | !.vscode/settings.json 56 | !.vscode/tasks.json 57 | !.vscode/launch.json 58 | !.vscode/extensions.json 59 | !.vscode/*.code-snippets 60 | 61 | # Local History for Visual Studio Code 62 | .history/ 63 | 64 | # Built Visual Studio Code Extensions 65 | *.vsix 66 | 67 | ### VisualStudioCode Patch ### 68 | # Ignore all local history of files 69 | .history 70 | .ionide 71 | 72 | ### Windows ### 73 | # Windows thumbnail cache files 74 | Thumbs.db 75 | Thumbs.db:encryptable 76 | ehthumbs.db 77 | ehthumbs_vista.db 78 | 79 | # Dump file 80 | *.stackdump 81 | 82 | # Folder config file 83 | [Dd]esktop.ini 84 | 85 | # Recycle Bin used on file shares 86 | $RECYCLE.BIN/ 87 | 88 | # Windows Installer files 89 | *.cab 90 | *.msi 91 | *.msix 92 | *.msm 93 | *.msp 94 | 95 | # Windows shortcuts 96 | *.lnk 97 | 98 | # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,windows 99 | 100 | ### Node ### 101 | # Logs 102 | logs 103 | *.log 104 | npm-debug.log* 105 | yarn-debug.log* 106 | yarn-error.log* 107 | lerna-debug.log* 108 | .pnpm-debug.log* 109 | 110 | # Diagnostic reports (https://nodejs.org/api/report.html) 111 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 112 | 113 | # Runtime data 114 | pids 115 | *.pid 116 | *.seed 117 | *.pid.lock 118 | 119 | # Directory for instrumented libs generated by jscoverage/JSCover 120 | lib-cov 121 | 122 | # Coverage directory used by tools like istanbul 123 | coverage 124 | *.lcov 125 | 126 | # nyc test coverage 127 | .nyc_output 128 | 129 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 130 | .grunt 131 | 132 | # Bower dependency directory (https://bower.io/) 133 | bower_components 134 | 135 | # node-waf configuration 136 | .lock-wscript 137 | 138 | # Compiled binary addons (https://nodejs.org/api/addons.html) 139 | build/Release 140 | 141 | # Dependency directories 142 | node_modules/ 143 | jspm_packages/ 144 | 145 | # Snowpack dependency directory (https://snowpack.dev/) 146 | web_modules/ 147 | 148 | # TypeScript cache 149 | *.tsbuildinfo 150 | 151 | # Optional npm cache directory 152 | .npm 153 | 154 | # Optional eslint cache 155 | .eslintcache 156 | 157 | # Optional stylelint cache 158 | .stylelintcache 159 | 160 | # Microbundle cache 161 | .rpt2_cache/ 162 | .rts2_cache_cjs/ 163 | .rts2_cache_es/ 164 | .rts2_cache_umd/ 165 | 166 | # Optional REPL history 167 | .node_repl_history 168 | 169 | # Output of 'npm pack' 170 | *.tgz 171 | 172 | # Yarn Integrity file 173 | .yarn-integrity 174 | 175 | # dotenv environment variable files 176 | .env 177 | .env.development.local 178 | .env.test.local 179 | .env.production.local 180 | .env.local 181 | 182 | # parcel-bundler cache (https://parceljs.org/) 183 | .cache 184 | .parcel-cache 185 | 186 | # Next.js build output 187 | .next 188 | out 189 | 190 | # Nuxt.js build / generate output 191 | .nuxt 192 | dist 193 | 194 | # Gatsby files 195 | .cache/ 196 | # Comment in the public line in if your project uses Gatsby and not Next.js 197 | # https://nextjs.org/blog/next-9-1#public-directory-support 198 | # public 199 | 200 | # vuepress build output 201 | .vuepress/dist 202 | 203 | # vuepress v2.x temp and cache directory 204 | .temp 205 | 206 | # Docusaurus cache and generated files 207 | .docusaurus 208 | 209 | # Serverless directories 210 | .serverless/ 211 | 212 | # FuseBox cache 213 | .fusebox/ 214 | 215 | # DynamoDB Local files 216 | .dynamodb/ 217 | 218 | # TernJS port file 219 | .tern-port 220 | 221 | # Stores VSCode versions used for testing VSCode extensions 222 | .vscode-test 223 | 224 | # yarn v2 225 | .yarn/cache 226 | .yarn/unplugged 227 | .yarn/build-state.yml 228 | .yarn/install-state.gz 229 | .pnp.* 230 | 231 | ### Node Patch ### 232 | # Serverless Webpack directories 233 | .webpack/ 234 | 235 | # Optional stylelint cache 236 | 237 | # SvelteKit build / generate output 238 | .svelte-kit 239 | 240 | # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) 241 | 242 | /node_modules 243 | /oh_modules 244 | /local.properties 245 | /.idea 246 | **/build 247 | /.hvigor 248 | .cxx 249 | /.clangd 250 | /.clang-format 251 | /.clang-tidy 252 | **/.test 253 | /.appanalyzer 254 | 255 | /signing 256 | BuildProfile.ets 257 | 258 | **/src/npm/LICENSE 259 | **/src/npm/README.md 260 | **/src/npm/README-EN.md 261 | 262 | /local_hars -------------------------------------------------------------------------------- /README-EN.md: -------------------------------------------------------------------------------- 1 | [**中文版**](./README.md) | [English Version](./README-EN.md) 2 | 3 | # **Cloak** - Hybrid Development Framework for HarmonyOS 4 | 5 | **Cloak** is a lightweight hybrid development framework for **HarmonyOS**, inspired by [Cordova](https://cordova.apache.org/) and [Capacitor](https://capacitorjs.com/), but with **simpler implementation** and **better performance**. 6 | 7 | Enables rapid conversion of web applications to native HarmonyOS apps with plugin-based native API access. 8 | 9 | --- 10 | 11 | ## Core Features 12 | 13 | - **Quick Packaging** 14 | Compile H5/web apps into HarmonyOS applications within minutes 15 | 16 | - **Native API Access** 17 | Extend functionality through HarmonyOS native plugins 18 | 19 | - **Optimized WebView** 20 | High-performance WebView container with hardware acceleration 21 | 22 | - **Plugin Development** 23 | Easily create HarmonyOS native plugins using TypeScript/ArkTS 24 | 25 | --- 26 | 27 | ## Important Notes 28 | 29 | - **No Compatibility** with existing Cordova/Capacitor plugins 30 | All plugins must be **redeveloped** using HarmonyOS native APIs 31 | 32 | ### Existing Plugins 33 | 34 | Each plugin also has a corresponding `npm` package with the same name, making it convenient for `TypeScript` and frontend **extensions**. 35 | 36 | - **[CloakPluginPermission](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginPermission/README-EN.md)** 37 | 38 | Used to check and request HarmonyOS permissions. 39 | 40 | ```bash 41 | ohpm i @wisdomgarden/cloak-plugin-permission 42 | npm i @wisdomgarden/cloak-plugin-permission # optional 43 | ``` 44 | 45 | - **[CloakPluginHttp](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginHttp/README-EN.md)** 46 | 47 | Used to make Native HTTP requests within Cloak applications. 48 | 49 | ```bash 50 | ohpm i @wisdomgarden/cloak-plugin-http 51 | npm i @wisdomgarden/cloak-plugin-http # optional 52 | ``` 53 | 54 | - **[CloakPluginInAppBrowser](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginInAppBrowser/README-EN.md)** 55 | 56 | Used to open internal browsers in Cloak applications and perform operations. 57 | 58 | ```bash 59 | ohpm i @wisdomgarden/cloak-plugin-inappbrowser 60 | npm i @wisdomgarden/cloak-plugin-inappbrowser # optional 61 | ``` 62 | 63 | - **[CloakPluginOpenNativeSettings](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginOpenNativeSettings/README-EN.md)** 64 | 65 | used for opening native settings pages within Cloak applications 66 | 67 | ```bash 68 | ohpm i @wisdomgarden/cloak-plugin-open-native-settings 69 | npm i @wisdomgarden/cloak-plugin-open-native-settings # optional 70 | ``` 71 | 72 | - **[CloakPluginJpush](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginJpush/README-EN.md)** 73 | 74 | Used to integrate JPush in Cloak applications to receive notifications. 75 | 76 | ```bash 77 | ohpm install @wisdomgarden/cloak-plugin-jpush 78 | npm install @wisdomgarden/cloak-plugin-jpush # optional 79 | ``` 80 | 81 | 82 | - **[CloakPluginCodeScanner](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginCodeScanner/README-EN.md)** 83 | 84 | Used for QR code scanning in Cloak applications. 85 | 86 | ```bash 87 | ohpm install @wisdomgarden/cloak-plugin-code-scanner 88 | npm install @wisdomgarden/cloak-plugin-code-scanner # optional 89 | ``` 90 | 91 | ***✨✨✨ More plugins are coming soon, stay tuned. ✨✨✨*** 92 | 93 | --- 94 | 95 | ## Getting Started 96 | 97 | ### Run Demo App 98 | 1. **Create EmptyAbility Project** 99 | 100 | Follow official guide: [Building the First ArkTS Application in Stage Model](https://developer.huawei.com/consumer/en/doc/harmonyos-guides-V5/start-with-ets-stage-V5) 101 | 102 | 103 | 2. **Install Cloak Framework** 104 | ```bash 105 | ohpm install @wisdomgarden/cloak 106 | ``` 107 | The built-in demo will be automatically available. 108 | 109 | --- 110 | 111 | ### Integrate Your Web App 112 | 3. **Create Config File** 113 | Add `entry/src/main/resources/rawfile/config.json`: 114 | ```json 115 | { 116 | "APP_FOLDER": "www", 117 | "APP_HOST": "http://localhost", 118 | "APP_IDENTITY_USER_AGENT": "YourAppName/HarmonyOS", 119 | "IS_DEBUG": false, 120 | "WEB_VIEW_USE_APP_PERMISSION": true, 121 | "APP_USE_REAL_HOST_RESOURCE": false 122 | } 123 | ``` 124 | 125 | 126 | 4. **Deploy Web Assets** 127 | 128 | Copy your web app (must contain **index.html**) to `entry/src/main/resources/rawfile/www` 129 | 130 | 131 | 5. **Initialize Framework** 132 | 133 | Modify `entry/src/main/ets/entryability/EntryAbility.ets`, add following code to `onCreate` method: 134 | 135 | ```typescript 136 | import { Cloak } from '@wisdomgarden/cloak'; 137 | 138 | onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 139 | // ... 140 | 141 | const cloak = new Cloak(this) 142 | // cloak.addPlugins([ 143 | // new CloakPluginPermission(), 144 | // new CloakPluginDevice(), 145 | // new CloakPluginGeolocation(), 146 | // new CloakPluginInAppBrowser(), 147 | // ]) 148 | } 149 | ``` 150 | 151 | Modify `entry/src/main/ets/pages/Index.ets`, show the webview: 152 | 153 | ```typescript 154 | import { CloakWebview } from "@wisdomgarden/cloak" 155 | 156 | @Entry 157 | @Component 158 | struct CloakIndex { 159 | build() { 160 | Column() { 161 | CloakWebview() 162 | } 163 | } 164 | } 165 | ``` 166 | 167 | 168 | 6. **Debug & Run** 169 | 170 | Use DevEco Studio for real-time debugging 171 | 172 | 173 | 7. **Adapting H5 Capabilities** 174 | 175 | At this point, with the help of **[CloakPluginPermission](https://github.com/WisdomGardenInc/CloakPlugins/blob/master/plugins/CloakPluginPermission/README-EN.md)** to obtain system permissions, Cloak can now adapt to the majority of capabilities required by H5 applications. For example, `navigator.mediaDevices`, `input (capture, file)`, `navigator.geolocation`, `indexedDB`, etc. Please refer to the [Demo](https://github.com/WisdomGardenInc/Cloak/tree/master/entry/src/main/resources/rawfile/www) for more details. 176 | 177 | 178 | 8. **Plugin Development** 179 | 180 | Develop custom plugins or find community plugins via [OHPM registry](https://ohpm.openharmony.cn) 181 | 182 | ## Example 183 | 184 | Complete the [Getting Started](#getting-started) steps, and refer to: https://github.com/WisdomGardenInc/Cloak for more details. -------------------------------------------------------------------------------- /framework/src/main/ets/plugin/CloakPlugin.ets: -------------------------------------------------------------------------------- 1 | import { common } from '@kit.AbilityKit'; 2 | import { webview } from '@kit.ArkWeb'; 3 | import { ICloakContext } from '../types'; 4 | import { util } from '@kit.ArkTS'; 5 | import { BusinessError } from '@kit.BasicServicesKit'; 6 | import logger from '../logger'; 7 | 8 | 9 | export interface PluginMetadata { 10 | name: string; 11 | version: string; 12 | description: string; 13 | methods: string[]; 14 | } 15 | 16 | // Decorator 17 | export function PluginMethod() { 18 | return (target: CloakPlugin, propertyName: string, descriptor: PropertyDescriptor) => { 19 | target.addPluginMethodName(propertyName); 20 | } 21 | } 22 | 23 | 24 | export interface ICloakPlugin { 25 | name: string; 26 | 27 | pluginMethods(): string[]; 28 | 29 | getMetadata(): PluginMetadata; 30 | 31 | register(context: common.ApplicationContext, uiContext: UIContext, webviewController: WebviewController): void; 32 | } 33 | 34 | export abstract class CloakPlugin implements ICloakPlugin { 35 | private static pluginNamePrefix = "_cloak_plugin_" 36 | protected static applicationContext: common.ApplicationContext; 37 | protected static webviewController: WebviewController; 38 | protected static uiContext: UIContext; 39 | private static pluginsMethodsMap = new Map(); 40 | protected static channelCloakPort: webview.WebMessagePort; 41 | protected _name: string = this.constructor.name; 42 | protected abstract _version: string; 43 | protected _description: string = ''; 44 | private _pluginMethods: string[]; 45 | protected browserEventsHandlers = new Map void>>(); 46 | protected browserMessages = new Set(); 47 | protected isAttached = false; 48 | 49 | public addPluginMethodName(methodName: string) { 50 | // Decorator before class instance constructor 51 | if (!CloakPlugin.pluginsMethodsMap.has(this.constructor.name)) { 52 | CloakPlugin.pluginsMethodsMap.set(this.constructor.name, [methodName]); 53 | } else { 54 | CloakPlugin.pluginsMethodsMap.get(this.constructor.name)!.push(methodName); 55 | } 56 | } 57 | 58 | constructor() { 59 | this._pluginMethods = CloakPlugin.pluginsMethodsMap.get(this.constructor.name) ?? []; 60 | } 61 | 62 | public get name() { 63 | return this._name; 64 | } 65 | 66 | 67 | public pluginMethods(): string[] { 68 | return this._pluginMethods.slice(); 69 | } 70 | 71 | 72 | public static setContext(context: ICloakContext) { 73 | CloakPlugin.applicationContext = context.applicationContext; 74 | CloakPlugin.uiContext = context.uiContext; 75 | CloakPlugin.webviewController = context.webviewController; 76 | CloakPlugin.channelCloakPort = context.channelCloakPort; 77 | } 78 | 79 | public static updateContext(channelCloakPort: webview.WebMessagePort) { 80 | CloakPlugin.channelCloakPort = channelCloakPort; 81 | } 82 | 83 | public register() { 84 | try { 85 | CloakPlugin.webviewController.registerJavaScriptProxy(this, `${CloakPlugin.pluginNamePrefix}${this._name}`, 86 | this._pluginMethods.concat('getMetadata', 'addEventListener', 'removeEventListener', 'clearAllEvents', 87 | 'addMessage', 'removeMessage', 'clearAllMessages')) 88 | } catch (error) { 89 | logger.error( 90 | 'registerJavaScriptProxy error. ErrorCode: %{public}s, Message: %{public}s', 91 | (error as BusinessError).code, 92 | (error as BusinessError).message, 93 | ); 94 | } 95 | } 96 | 97 | public getMetadata(): PluginMetadata { 98 | return { 99 | name: this._name, 100 | version: this._version, 101 | description: this._description, 102 | methods: this._pluginMethods 103 | } 104 | } 105 | 106 | public addEventListener(event: string, handler: (event: ESObject) => void) { 107 | if (!handler) { 108 | return; 109 | } 110 | if (!this.browserEventsHandlers.has(event)) { 111 | this.browserEventsHandlers.set(event, new Map()); 112 | } 113 | // let handlerId = Reflect.get(handler, 'handlerId') as string; 114 | // if (!handlerId?.length) { 115 | // handlerId = util.generateRandomUUID(); 116 | // } 117 | 118 | const handlerId = util.generateRandomUUID(); 119 | 120 | if (this.browserEventsHandlers.get(event)?.has(handlerId)) { 121 | return; 122 | } 123 | 124 | this.browserEventsHandlers.get(event)?.set(handlerId, handler) 125 | 126 | return handlerId; 127 | } 128 | 129 | public removeEventListener(event: string, handlerId?: string) { 130 | const handlers = this.browserEventsHandlers.get(event); 131 | if (!handlers?.size) { 132 | return; 133 | } 134 | 135 | if (!handlerId) { 136 | this.browserEventsHandlers.delete(event); 137 | return; 138 | } 139 | 140 | this.browserEventsHandlers.get(event)?.delete(handlerId) 141 | } 142 | 143 | public clearAllEvents() { 144 | this.browserEventsHandlers.clear(); 145 | } 146 | 147 | protected hasEvent(event: string): boolean { 148 | return this.browserEventsHandlers.has(event) 149 | } 150 | 151 | protected _sendEvent(eventType: string, payload: ESObject) { 152 | if (!this.hasEvent(eventType)) { 153 | return; 154 | } 155 | const handlers = this.browserEventsHandlers.get(eventType); 156 | handlers?.forEach((handler) => { 157 | handler(payload) 158 | }) 159 | } 160 | 161 | public addMessage(messageType: string) { 162 | if (!messageType) { 163 | return; 164 | } 165 | this.browserMessages.add(messageType) 166 | } 167 | 168 | public removeMessage(messageType: string) { 169 | this.browserMessages.delete(messageType) 170 | } 171 | 172 | public clearAllMessages() { 173 | this.browserMessages.clear(); 174 | } 175 | 176 | public hasMessage(messageType: string) { 177 | return this.browserMessages.has(messageType) 178 | } 179 | 180 | public onMessage(payload: ESObject) { 181 | // 182 | } 183 | 184 | protected wrapMessage(payload: ESObject) { 185 | return JSON.stringify({ 186 | pluginName: this._name, 187 | payload: payload 188 | }) 189 | } 190 | 191 | protected _sendMessage(messageType: string, payload: ESObject) { 192 | if (!this.hasMessage(messageType)) { 193 | return; 194 | } 195 | CloakPlugin.channelCloakPort.postMessageEvent(this.wrapMessage(payload)); 196 | } 197 | 198 | public beforeRegister() { 199 | } 200 | 201 | public afterRegister() { 202 | } 203 | 204 | protected _onAttach() { 205 | } 206 | 207 | public onAttach() { 208 | if (this.isAttached) { 209 | return; 210 | } 211 | this.isAttached = true; 212 | this._onAttach(); 213 | } 214 | } -------------------------------------------------------------------------------- /framework/src/main/ets/Webview.ets: -------------------------------------------------------------------------------- 1 | import { webview } from '@kit.ArkWeb'; 2 | import { BusinessError } from '@kit.BasicServicesKit'; 3 | import { url } from '@kit.ArkTS'; 4 | import Config from './Config'; 5 | import * as DialogUtils from './DialogUtils'; 6 | import * as Utils from './Utils'; 7 | import PluginManager from './plugin/PluginManager'; 8 | import { IChannelMessage } from './types'; 9 | import { window } from '@kit.ArkUI'; 10 | import { common } from '@kit.AbilityKit'; 11 | import Cloak from './Cloak'; 12 | import logger from './logger'; 13 | 14 | 15 | // https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V13/ts-basic-components-web-V13 16 | // https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-webview-V5 17 | 18 | @Component 19 | export default struct CloakWebview { 20 | webviewController = new webview.WebviewController(); 21 | channelCloakPort?: webview.WebMessagePort; 22 | channelClientPort?: webview.WebMessagePort; 23 | 24 | aboutToAppear(): void { 25 | if (Config.IS_DEBUG) { 26 | try { 27 | webview.WebviewController.setWebDebuggingAccess(Config.IS_DEBUG); 28 | } catch (error) { 29 | logger.warn( 30 | 'setWebDebuggingAccess error. ErrorCode: %{public}s, Message: %{public}s', 31 | (error as BusinessError).code, 32 | (error as BusinessError).message, 33 | ); 34 | } 35 | } 36 | 37 | this.initSafeArea(getContext() as common.UIAbilityContext) 38 | } 39 | 40 | private async initSafeArea(context: common.UIAbilityContext): Promise { 41 | try { 42 | const mainWindow = await window.getLastWindow(context); 43 | 44 | const avoidAreaTop = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); 45 | Cloak.SAFE_TOP = px2vp(avoidAreaTop.topRect.height); 46 | 47 | const avoidAreaBottom = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR); 48 | Cloak.SAFE_BOTTOM = px2vp(avoidAreaBottom.bottomRect.height); 49 | } catch (error) { 50 | logger.error( 51 | 'getWindowAvoidArea error. ErrorCode: %{public}s, Message: %{public}s', 52 | (error as BusinessError).code, 53 | (error as BusinessError).message, 54 | ); 55 | } 56 | } 57 | 58 | initMessageChannel() { 59 | try { 60 | const ports = this.webviewController.createWebMessagePorts(); 61 | this.channelClientPort = ports[0]; 62 | this.channelCloakPort = ports[1]; 63 | 64 | this.channelCloakPort.onMessageEvent((result: webview.WebMessage) => { 65 | 66 | if (typeof result !== 'string') { 67 | return; 68 | } 69 | 70 | const message: IChannelMessage = JSON.parse(result); 71 | 72 | PluginManager.onMessage(message); 73 | }) 74 | } catch (error) { 75 | logger.fatal( 76 | 'initMessageChannel error. ErrorCode: %{public}s, Message: %{public}s', 77 | (error as BusinessError).code, 78 | (error as BusinessError).message, 79 | ); 80 | } 81 | } 82 | 83 | build() { 84 | Column() { 85 | Web({ 86 | src: Config.APP_HOST, 87 | controller: this.webviewController, 88 | renderMode: RenderMode.ASYNC_RENDER 89 | }) 90 | .layoutMode(WebLayoutMode.NONE) 91 | .overScrollMode(OverScrollMode.NEVER) 92 | .nestedScroll({ 93 | scrollForward: NestedScrollMode.SELF_ONLY, 94 | scrollBackward: NestedScrollMode.SELF_ONLY, 95 | }) 96 | .mixedMode(MixedMode.All) 97 | .javaScriptAccess(true) 98 | .fileAccess(true) 99 | .onlineImageAccess(true) 100 | .domStorageAccess(true) 101 | .imageAccess(true) 102 | .databaseAccess(true) 103 | .geolocationAccess(true) 104 | .onGeolocationShow(event => { 105 | if (Config.WEB_VIEW_USE_APP_PERMISSION || !event) { 106 | event?.geolocation.invoke(event.origin, true, true); 107 | } else { 108 | DialogUtils.showConfirm($r('app.string.permission_location_reason'), (isConfirmed) => { 109 | if (isConfirmed) { 110 | event.geolocation.invoke(event.origin, true, true); 111 | } else { 112 | event.geolocation.invoke(event.origin, false, true); 113 | } 114 | 115 | }, $r('app.string.common_ui_dialog_webview_request_permission_title')) 116 | } 117 | 118 | }) 119 | .onPermissionRequest(event => { 120 | if (Config.WEB_VIEW_USE_APP_PERMISSION || !event) { 121 | event?.request.grant(event.request.getAccessibleResource()); 122 | } else { 123 | const accessible = event.request.getAccessibleResource(); 124 | let message: string | Resource = `request permisson ${accessible.join(", ")}` 125 | 126 | if (accessible.includes("TYPE_VIDEO_CAPTURE")) { 127 | message = $r('app.string.permission_camera_reason') 128 | } else if (accessible.includes("TYPE_AUDIO_CAPTURE")) { 129 | message = $r('app.string.permission_microphone_reason') 130 | } 131 | 132 | DialogUtils.showConfirm(message, (isConfirmed) => { 133 | if (isConfirmed) { 134 | event.request.grant(accessible); 135 | } else { 136 | event.request.deny(); 137 | } 138 | }, $r('app.string.common_ui_dialog_webview_request_permission_title')) 139 | } 140 | }) 141 | .zoomAccess(false) 142 | .overviewModeAccess(false) 143 | .mediaPlayGestureAccess(true) 144 | .multiWindowAccess(false) 145 | .allowWindowOpenMethod(false) 146 | .horizontalScrollBarAccess(false) 147 | .verticalScrollBarAccess(false)// .keyboardAvoidMode() 148 | .cacheMode(!Config.IS_DEBUG ? CacheMode.Default : CacheMode.Online) 149 | .copyOptions(CopyOptions.LocalDevice) 150 | .blockNetwork(false) 151 | .horizontalScrollBarAccess(false) 152 | .verticalScrollBarAccess(false) 153 | .mediaOptions({ 154 | resumeInterval: 0, audioExclusive: true 155 | }) 156 | .onAlert((event) => { 157 | event && event.message && DialogUtils.showAlert(event.message, () => { 158 | event.result.handleConfirm(); 159 | }); 160 | return true; 161 | }) 162 | .onConfirm((event) => { 163 | event && event.message && DialogUtils.showConfirm(event.message, (success: boolean) => { 164 | if (success) { 165 | event.result.handleConfirm(); 166 | } else { 167 | event.result.handleCancel(); 168 | } 169 | }); 170 | return true; 171 | }) 172 | .onPrompt(() => { 173 | return false; 174 | }) 175 | .onInterceptRequest((event) => { 176 | if (!event) { 177 | return null; 178 | } 179 | if (Config.APP_USE_REAL_HOST_RESOURCE) { 180 | return null; 181 | } 182 | const requestUrl = event.request.getRequestUrl() 183 | 184 | 185 | if (!requestUrl.startsWith(Config.APP_HOST)) { 186 | return null; 187 | } 188 | 189 | try { 190 | const pathname = url.URL.parseURL(requestUrl, Config.APP_HOST).pathname 191 | const resourceInfo = Utils.getResourceInfo(pathname) 192 | const mimeType = Utils.getMimeType(resourceInfo.extension) 193 | 194 | let response = new WebResourceResponse() 195 | response.setResponseData($rawfile(resourceInfo.resourcePath)); 196 | response.setResponseEncoding('utf-8') 197 | response.setResponseMimeType(mimeType) 198 | response.setResponseCode(200) 199 | response.setReasonMessage('OK') 200 | response.setResponseIsReady(true) 201 | return response 202 | } catch (error) { 203 | logger.warn( 204 | 'Loading h5 resource error. URL: %{public}s, ErrorCode: %{public}s, Message: %{public}s', 205 | requestUrl, 206 | (error as BusinessError).code, 207 | (error as BusinessError).message, 208 | ); 209 | return null 210 | } 211 | }) 212 | .onControllerAttached(() => { 213 | // user agent 214 | try { 215 | const custom_agent_items = [Config.CLOAK_IDENTITY_USER_AGENT] 216 | if (Config.APP_IDENTITY_USER_AGENT) { 217 | custom_agent_items.push(Config.APP_IDENTITY_USER_AGENT); 218 | } 219 | let userAgent = this.webviewController.getUserAgent() + " " + custom_agent_items.join(" "); 220 | this.webviewController.setCustomUserAgent(userAgent); 221 | } catch (error) { 222 | logger.error( 223 | 'onControllerAttached set userAgent error. ErrorCode: %{public}s, Message: %{public}s', 224 | (error as BusinessError).code, 225 | (error as BusinessError).message, 226 | ); 227 | } 228 | 229 | this.initMessageChannel(); 230 | 231 | if (!this.channelCloakPort) { 232 | logger.fatal('Cloak Webview init failed'); 233 | return; 234 | } 235 | 236 | PluginManager.setContext({ 237 | applicationContext: getContext().getApplicationContext(), 238 | uiContext: this.getUIContext(), 239 | webviewController: this.webviewController, 240 | channelCloakPort: this.channelCloakPort, 241 | }); 242 | PluginManager.registerPlugins(); 243 | 244 | // in onControllerAttached function don't need refresh 245 | // this.webviewController.refresh(); 246 | logger.info('Cloak Webview init success') 247 | }) 248 | .onPageBegin((event) => { 249 | if (this.channelCloakPort) { 250 | this.channelCloakPort.close(); 251 | this.channelCloakPort = undefined; 252 | this.channelClientPort = undefined; 253 | } 254 | }) 255 | .onPageEnd((event) => { 256 | // when page reload post message will not work, so must re-ini it again 257 | PluginManager.initCloakClient().then(() => { 258 | if (!this.channelCloakPort) { 259 | this.initMessageChannel(); 260 | if (this.channelCloakPort) { 261 | PluginManager.updateContext(this.channelCloakPort) 262 | } 263 | } 264 | try { 265 | this.webviewController.postMessage('Cloak__init_port__', [this.channelClientPort], '*'); 266 | logger.info('Cloak Webview initCloakClient success') 267 | } catch (error) { 268 | logger.fatal( 269 | 'initCloakClient error. errorCode: %{public}s, Message: %{public}s', 270 | (error as BusinessError).code, 271 | (error as BusinessError).message, 272 | ); 273 | 274 | } 275 | 276 | }); 277 | }) 278 | } 279 | .width("100%") 280 | .height("100%") 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /entry/src/main/resources/rawfile/www/js/demo.js: -------------------------------------------------------------------------------- 1 | window.document.addEventListener( 2 | "CloakReady", 3 | () => { 4 | displayPluginsInfo(); 5 | }, 6 | { once: true } 7 | ); 8 | 9 | window.onload = function () { 10 | document.querySelector("div.demo").innerText = window.location.href + "\n" + window.navigator.userAgent; 11 | initDB(); 12 | }; 13 | 14 | const onAlert = () => { 15 | const result = alert("alert content"); 16 | console.log(result); 17 | }; 18 | 19 | const onConfirm = () => { 20 | const result = confirm("confirm content"); 21 | console.log(result); 22 | }; 23 | 24 | // Media Capacity 25 | const takePhoto = async () => { 26 | try { 27 | const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "user" } }); 28 | const video = document.createElement("video"); 29 | video.srcObject = stream; 30 | await video.play(); 31 | 32 | const canvas = document.createElement("canvas"); 33 | canvas.width = video.videoWidth; 34 | canvas.height = video.videoHeight; 35 | canvas.getContext("2d").drawImage(video, 0, 0); 36 | 37 | const image = document.createElement("img"); 38 | image.src = canvas.toDataURL("image/jpeg"); 39 | document.getElementById("mediaPreview").innerHTML = ""; 40 | document.getElementById("mediaPreview").appendChild(image); 41 | 42 | stream.getTracks().forEach((track) => track.stop()); 43 | } catch (error) { 44 | console.error("take photo error:", error); 45 | } 46 | }; 47 | 48 | const recordVideo = async () => { 49 | try { 50 | const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" }, audio: true }); 51 | const mediaRecorder = new MediaRecorder(stream); 52 | const chunks = []; 53 | 54 | mediaRecorder.ondataavailable = (e) => chunks.push(e.data); 55 | mediaRecorder.onstop = () => { 56 | const blob = new Blob(chunks, { type: "video/webm" }); 57 | const video = document.createElement("video"); 58 | video.src = URL.createObjectURL(blob); 59 | video.controls = true; 60 | document.getElementById("mediaPreview").innerHTML = ""; 61 | document.getElementById("mediaPreview").appendChild(video); 62 | }; 63 | 64 | mediaRecorder.start(); 65 | setTimeout(() => { 66 | mediaRecorder.stop(); 67 | stream.getTracks().forEach((track) => track.stop()); 68 | }, 5000); // capture 5 seconds 69 | } catch (error) { 70 | console.error("record video error", error); 71 | } 72 | }; 73 | 74 | const recordAudio = async () => { 75 | try { 76 | const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); 77 | const mediaRecorder = new MediaRecorder(stream); 78 | const chunks = []; 79 | 80 | mediaRecorder.ondataavailable = (e) => chunks.push(e.data); 81 | mediaRecorder.onstop = () => { 82 | const blob = new Blob(chunks, { type: "audio/webm" }); 83 | const audio = document.createElement("audio"); 84 | audio.src = URL.createObjectURL(blob); 85 | audio.controls = true; 86 | document.getElementById("mediaPreview").innerHTML = ""; 87 | document.getElementById("mediaPreview").appendChild(audio); 88 | }; 89 | 90 | mediaRecorder.start(); 91 | setTimeout(() => { 92 | mediaRecorder.stop(); 93 | stream.getTracks().forEach((track) => track.stop()); 94 | }, 5000); // capture 5 seconds 95 | } catch (error) { 96 | console.error("record audio error:", error); 97 | } 98 | }; 99 | 100 | const previewFile = (file) => { 101 | let node = null; 102 | if (file.type.startsWith("image/")) { 103 | node = document.createElement("img"); 104 | node.src = URL.createObjectURL(file); 105 | } else if (file.type.startsWith("video/")) { 106 | node = document.createElement("video"); 107 | node.src = URL.createObjectURL(file); 108 | node.controls = true; 109 | } else if (file.type.startsWith("audio/")) { 110 | node = document.createElement("audio"); 111 | node.src = URL.createObjectURL(file); 112 | node.controls = true; 113 | } else if (file.type.startsWith("application/pdf")) { 114 | node = document.createElement("embed"); 115 | node.src = URL.createObjectURL(file); 116 | node.width = "100%"; 117 | node.height = "500px"; 118 | } else { 119 | node = document.createElement("div"); 120 | node.textContent = `Selected file: ${file.name} (${file.type})`; 121 | } 122 | document.getElementById("mediaPreview").innerHTML = ""; 123 | document.getElementById("mediaPreview").appendChild(node); 124 | }; 125 | 126 | const selectFile = (accept = "") => { 127 | const input = document.createElement("input"); 128 | input.type = "file"; 129 | input.accept = accept; 130 | input.multiple = true; 131 | input.onchange = (e) => { 132 | previewFile(e.target.files[0]); 133 | }; 134 | input.click(); 135 | }; 136 | 137 | const takeMedia = (accept = "") => { 138 | const input = document.createElement("input"); 139 | input.type = "file"; 140 | input.accept = accept; 141 | input.capture = "user"; 142 | input.onchange = (e) => { 143 | previewFile(e.target.files[0]); 144 | }; 145 | input.click(); 146 | }; 147 | 148 | // Geolocation Capacity 149 | let watchId = null; 150 | 151 | const getLocation = () => { 152 | if (navigator.geolocation) { 153 | navigator.geolocation.getCurrentPosition( 154 | (position) => { 155 | document.getElementById( 156 | "locationInfo" 157 | ).textContent = `Latitude: ${position.coords.latitude}, Longitude: ${position.coords.longitude}`; 158 | }, 159 | (error) => { 160 | console.error("Get location error:", error); 161 | }, 162 | { 163 | enableHighAccuracy: true, 164 | maximumAge: 0, 165 | timeout: 5000, 166 | } 167 | ); 168 | } 169 | }; 170 | 171 | const getLocationNative = async () => { 172 | if (Cloak?.plugins?.Geolocation) { 173 | const result = await Cloak.plugins.Geolocation.getLocation(); 174 | document.getElementById("locationInfo").textContent = JSON.stringify(result, null, 2); 175 | } 176 | }; 177 | 178 | const startWatchLocation = () => { 179 | if (navigator.geolocation && !watchId) { 180 | watchId = navigator.geolocation.watchPosition( 181 | (position) => { 182 | document.getElementById( 183 | "locationInfo" 184 | ).textContent = `Real-time location - Latitude: ${position.coords.latitude}, Longitude: ${position.coords.longitude}`; 185 | }, 186 | (error) => { 187 | console.error("Watch location error:", error); 188 | } 189 | ); 190 | } 191 | }; 192 | 193 | const stopWatchLocation = () => { 194 | if (watchId) { 195 | navigator.geolocation.clearWatch(watchId); 196 | watchId = null; 197 | document.getElementById("locationInfo").textContent = "Location watch stopped"; 198 | } 199 | }; 200 | 201 | // Scan Qrcode Capacity 202 | let qrCodevideoStream = null; 203 | 204 | const startQRScan = async () => { 205 | try { 206 | const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }); 207 | qrCodevideoStream = stream; 208 | const video = document.getElementById("qrVideo"); 209 | video.srcObject = stream; 210 | video.style.display = "block"; 211 | await video.play(); 212 | 213 | // QR code parsing library is needed here to achieve full functionality 214 | // For demonstration, we only show video preview 215 | document.getElementById("qrResult").textContent = 216 | "Camera is on (QR code parsing library needed for full functionality)"; 217 | } catch (error) { 218 | console.error("QR scan error:", error); 219 | } 220 | }; 221 | 222 | const stopQRScan = async () => { 223 | if (qrCodevideoStream) { 224 | qrCodevideoStream.getTracks().forEach((track) => track.stop()); 225 | qrCodevideoStream = null; 226 | const video = document.getElementById("qrVideo"); 227 | video.style.display = "none"; 228 | document.getElementById("qrResult").textContent = "QR scan stopped"; 229 | } 230 | }; 231 | 232 | // Vibration function 233 | const vibrate = () => { 234 | if (navigator.vibrate) { 235 | navigator.vibrate([200, 100, 200]); // Vibration pattern: 200ms on - 100ms off - 200ms on 236 | } 237 | }; 238 | 239 | // IndexedDB operations 240 | // IndexedDB initialization 241 | let db; 242 | const initDB = () => { 243 | const request = indexedDB.open("NotesDB", 1); 244 | 245 | request.onerror = (event) => { 246 | console.error("Database error:", event.target.error); 247 | }; 248 | 249 | request.onupgradeneeded = (event) => { 250 | db = event.target.result; 251 | if (!db.objectStoreNames.contains("notes")) { 252 | db.createObjectStore("notes", { keyPath: "id", autoIncrement: true }); 253 | } 254 | }; 255 | 256 | request.onsuccess = (event) => { 257 | db = event.target.result; 258 | displayNotes(); 259 | }; 260 | }; 261 | 262 | const addNote = () => { 263 | const noteInput = document.getElementById("noteInput"); 264 | const note = { 265 | content: noteInput.value, 266 | timestamp: new Date().toISOString(), 267 | }; 268 | 269 | const transaction = db.transaction(["notes"], "readwrite"); 270 | const store = transaction.objectStore("notes"); 271 | store.add(note); 272 | 273 | transaction.oncomplete = () => { 274 | noteInput.value = ""; 275 | displayNotes(); 276 | }; 277 | }; 278 | 279 | const displayNotes = () => { 280 | const transaction = db.transaction(["notes"], "readonly"); 281 | const store = transaction.objectStore("notes"); 282 | const request = store.getAll(); 283 | 284 | request.onsuccess = () => { 285 | const notes = request.result; 286 | const notesList = document.getElementById("notesList"); 287 | notesList.innerHTML = ""; 288 | 289 | notes.forEach((note) => { 290 | const div = document.createElement("div"); 291 | div.className = "note-item"; 292 | div.innerHTML = ` 293 | ${note.content} 294 | 295 | `; 296 | notesList.appendChild(div); 297 | }); 298 | }; 299 | }; 300 | 301 | const deleteNote = (id) => { 302 | const transaction = db.transaction(["notes"], "readwrite"); 303 | const store = transaction.objectStore("notes"); 304 | store.delete(id); 305 | 306 | transaction.oncomplete = () => { 307 | displayNotes(); 308 | }; 309 | }; 310 | 311 | const displayPluginsInfo = () => { 312 | const pluginsInfoDiv = document.getElementById("pluginsInfo"); 313 | pluginsInfoDiv.innerHTML = ""; 314 | 315 | if (window.Cloak && window.Cloak.plugins) { 316 | const plugins = window.Cloak.plugins; 317 | for (const pluginName in plugins) { 318 | if (plugins.hasOwnProperty(pluginName)) { 319 | const plugin = plugins[pluginName]; 320 | const metadata = plugin.getMetadata(); 321 | const pluginInfo = document.createElement("div"); 322 | pluginInfo.className = "plugin-item"; 323 | pluginInfo.innerHTML = ` 324 |

📦 ${metadata.name}

325 |

Version: ${metadata.version}

326 |

Methods: ${metadata.methods.join(", ")}

327 |

${metadata.description}

328 | `; 329 | pluginsInfoDiv.appendChild(pluginInfo); 330 | } 331 | } 332 | } else { 333 | pluginsInfoDiv.textContent = "No plugins installed."; 334 | } 335 | }; 336 | 337 | // permission 338 | const queryPermissions = async (permissions) => { 339 | const result = await Cloak.plugins.Permission.query(permissions); 340 | alert(JSON.stringify(result, null, 2)); 341 | }; 342 | 343 | const requestPermissions = async (permissions) => { 344 | const result = await Cloak.plugins.Permission.request(permissions); 345 | alert(JSON.stringify(result, null, 2)); 346 | }; 347 | 348 | const postMessageObject = () => { 349 | Cloak.plugins.Device.sendMessage({ aaa: "test" }); 350 | }; 351 | 352 | const onOpenUrl = async (url) => { 353 | const browser = Cloak.plugins.InAppBrowser.create(url, "_blank", { clearcache: true, footer: true }); 354 | 355 | browser.open(); 356 | 357 | browser.addEventListener('loadstart', function (event) { 358 | alert("addEventListener loadstart: " + event.url); 359 | }); 360 | 361 | browser.on("loadstart").subscribe(({ url }) => { 362 | alert("on loadstart: " + url); 363 | }); 364 | 365 | 366 | browser.on("loadstop").subscribe(({ url }) => { 367 | browser.executeScript({ code: "document.querySelector('.core-card .card-title').innerText = '和 Wisdom Garden 一起开启 OpenHarmony 之旅吧!';document.querySelector('.core-card .card-title').style.fontSize = '2rem';document.querySelector('.core-card .card-title').style.color = 'red';" }); 368 | browser.insertCSS({ code: ".card-summary {color: purple !important;}" }); 369 | alert("on loadstop: " + url); 370 | }); 371 | 372 | browser.on("exit").subscribe(() => { 373 | alert("closed"); 374 | }); 375 | }; 376 | 377 | 378 | 379 | const callback = (event) => { 380 | alert("addEventListener alert: " + event.data); 381 | } 382 | let handlerId = null; 383 | 384 | const addEvent = () => { 385 | handlerId = Cloak.plugins.Device.addEventListener("test", callback); 386 | } 387 | 388 | const removeEvent = () => { 389 | Cloak.plugins.Device.removeEventListener("test", handlerId); 390 | } 391 | 392 | const removeAllEvents = () => { 393 | Cloak.plugins.Device.clearAllEvents(); 394 | } 395 | 396 | const triggerEvent = () => { 397 | Cloak.plugins.Device.sendTestEvent(); 398 | } --------------------------------------------------------------------------------