├── harness ├── __init__.py ├── command_handler │ ├── __init__.py │ └── base.py └── __main__.py ├── noticeChecker ├── __init__.py ├── copyright.txt ├── test_path_matcher.py └── overwrite.py ├── captivityHarness ├── ready.json └── __main__.py ├── forApple ├── Headless │ ├── Headless │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ └── AppDelegate.swift │ └── readme.md ├── Skeleton │ └── Skeleton │ │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Skeleton.entitlements │ │ ├── MainViewController.swift │ │ ├── Info.plist │ │ ├── AppDelegate.swift │ │ └── Base.lproj │ │ └── LaunchScreen.storyboard ├── Captivity │ └── Captivity │ │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Captivity.entitlements │ │ ├── SecondaryViewController.swift │ │ ├── SpinnerViewController.swift │ │ ├── MainViewController.swift │ │ ├── Info.plist │ │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ │ └── AppDelegate.swift ├── FetchTest │ └── FetchTest │ │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── FetchTest.entitlements │ │ ├── MainViewController.swift │ │ ├── Info.plist │ │ ├── AppDelegate.swift │ │ └── Base.lproj │ │ └── LaunchScreen.storyboard ├── CaptiveCrypto │ └── CaptiveCrypto │ │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Array+inserting.swift │ │ ├── CaptiveCrypto.entitlements │ │ ├── AppDelegate.swift │ │ ├── StoredKey │ │ ├── Storage.swift │ │ ├── Generation.swift │ │ ├── Deletion.swift │ │ ├── GenericPasswordConvertible.swift │ │ ├── StoredKey.swift │ │ ├── Error.swift │ │ ├── keysWithName.swift │ │ └── Decipher.swift │ │ ├── AnyEncodable.swift │ │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ │ └── Info.plist ├── MacSkeleton │ └── MacSkeleton │ │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── ViewController.swift │ │ ├── AppDelegate.swift │ │ ├── MacSkeleton.entitlements │ │ └── Info.plist ├── .gitignore ├── MacSkeletonLocal.xcworkspace │ └── contents.xcworkspacedata └── CaptiveWebView.xcworkspace │ └── contents.xcworkspacedata ├── forAndroid ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── Headless │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── styles.xml │ │ │ │ └── colors.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── AndroidManifest.xml │ ├── readme.md │ ├── proguard-rules.pro │ └── build.gradle ├── Skeleton │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── skeleton │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ ├── proguard-rules.pro │ └── build.gradle ├── Captivity │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── captivity │ │ │ │ ├── SecondaryActivity.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ └── SpinnerActivity.kt │ │ │ └── AndroidManifest.xml │ ├── proguard-rules.pro │ └── build.gradle ├── FetchTest │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── fetchtest │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ ├── proguard-rules.pro │ └── build.gradle ├── CaptiveCrypto │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── captivecrypto │ │ │ └── storedkey │ │ │ ├── KeysDeletion.kt │ │ │ ├── KeyInfoPurposeStrings.kt │ │ │ └── StoredKey.kt │ ├── proguard-rules.pro │ └── build.gradle ├── captivewebview │ ├── src │ │ ├── documentation │ │ │ ├── extra.md │ │ │ └── readme.md │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── captivewebview │ │ │ ├── DefaultActivity.kt │ │ │ ├── Activity.kt │ │ │ ├── JSONExtensions.kt │ │ │ └── FetchException.kt │ ├── proguard-rules.pro │ └── build.gradle ├── .gitignore ├── settings.gradle ├── build.gradle ├── gradle.properties └── gradlew.bat ├── Tests ├── LinuxMain.swift └── CaptiveWebViewTests │ ├── XCTestManifests.swift │ └── CaptiveWebViewTests.swift ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── NOTICE.txt ├── WebResources ├── CaptiveCrypto │ └── UserInterface │ │ ├── KeyStore.html │ │ ├── Main.html │ │ ├── captivecrypto.js │ │ └── keystore.css ├── Captivity │ └── UserInterface │ │ ├── Secondary.html │ │ ├── speech.html │ │ ├── Main.html │ │ ├── secondary.js │ │ ├── speechcontrols.js │ │ ├── embeddedSVG.html │ │ └── three.html ├── FetchTest │ └── UserInterface │ │ └── Main.html ├── Skeleton │ └── UserInterface │ │ ├── Main.html │ │ └── main.js └── Headless │ └── WebResources │ └── Headless.html ├── notices.ignore ├── Sources └── CaptiveWebView │ ├── CaptiveWebView.h │ ├── CaptiveWebView │ ├── CaptiveWebViewCommandHandler.swift │ ├── ApplicationDelegate.swift │ └── Constrain.swift │ ├── URL+Changing.swift │ ├── CaptiveWebViewError.swift │ └── ViewController │ ├── ViewController+handleCommand.swift │ └── ViewController.swift ├── .gitignore ├── here.code-workspace ├── CaptiveWebView.podspec ├── Package.swift ├── LICENSE.txt ├── contributing.md └── readme.md /harness/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /noticeChecker/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /harness/command_handler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /captivityHarness/ready.json: -------------------------------------------------------------------------------- 1 | { 2 | "from": "file" 3 | } -------------------------------------------------------------------------------- /noticeChecker/copyright.txt: -------------------------------------------------------------------------------- 1 | Copyright %Y Omnissa, LLC. 2 | SPDX-License-Identifier: BSD-2-Clause -------------------------------------------------------------------------------- /forApple/Headless/Headless/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /forApple/Skeleton/Skeleton/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /forApple/Captivity/Captivity/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /forApple/FetchTest/FetchTest/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /forApple/MacSkeleton/MacSkeleton/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /forAndroid/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Headless/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Headless/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Skeleton/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Skeleton/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Captivity/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Captivity/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Captivity/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/FetchTest/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/FetchTest/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/FetchTest/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Headless/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Headless/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Skeleton/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Skeleton/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/CaptiveCrypto/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/CaptiveCrypto/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Captivity/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Captivity/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/FetchTest/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/FetchTest/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Headless/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Skeleton/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/CaptiveCrypto/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/CaptiveCrypto/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Captivity/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Captivity/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/FetchTest/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/FetchTest/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Headless/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Headless/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Headless/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Skeleton/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Skeleton/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Skeleton/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/CaptiveCrypto/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Captivity/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Captivity/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Captivity/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/FetchTest/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/FetchTest/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/FetchTest/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Headless/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Headless/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Skeleton/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/Skeleton/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/CaptiveCrypto/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/CaptiveCrypto/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/CaptiveCrypto/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/CaptiveCrypto/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/captive-web-view/main/forAndroid/CaptiveCrypto/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /forAndroid/captivewebview/src/documentation/extra.md: -------------------------------------------------------------------------------- 1 | # Appendix dummy 2 | This appendix doesn't get included anywhere yet. 3 | 4 | Legal 5 | ===== 6 | Copyright 2019 Omnissa, LLC. 7 | SPDX-License-Identifier: BSD-2-Clause 8 | -------------------------------------------------------------------------------- /forAndroid/captivewebview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | Captivity 7 | 8 | -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | FetchTest 7 | 8 | -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | Headless 7 | 8 | -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | Skeleton 7 | 8 | -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | Cryptography 7 | 8 | -------------------------------------------------------------------------------- /forAndroid/.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Omnissa, LLC. 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | *.iml 5 | .gradle 6 | /local.properties 7 | /.idea/ 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | 12 | /*/build/ 13 | -------------------------------------------------------------------------------- /forAndroid/captivewebview/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | Captive Web View Library 7 | 8 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import XCTest 5 | 6 | import captive_web_viewTests 7 | 8 | var tests = [XCTestCaseEntry]() 9 | tests += CaptiveWebViewTests.allTests() 10 | XCTMain(tests) 11 | -------------------------------------------------------------------------------- /forAndroid/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Oct 23 16:10:16 BST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip 7 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 6 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /forApple/Headless/readme.md: -------------------------------------------------------------------------------- 1 | Headless Sample 2 | =============== 3 | This directory has the Headless sample application for iOS. For details, see the 4 | [../documentation/headless.md](../documentation/headless.md) file. 5 | 6 | Legal 7 | ===== 8 | Copyright 2023 Omnissa, LLC. 9 | SPDX-License-Identifier: BSD-2-Clause 10 | -------------------------------------------------------------------------------- /forApple/.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Omnissa, LLC. 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | # Xcode project files 5 | */*.xcodeproj/project.xcworkspace/ 6 | */*.xcodeproj/xcshareddata/ 7 | */*.xcodeproj/xcuserdata/ 8 | 9 | # Xcode workspace files 10 | *.xcworkspace/xcshareddata/ 11 | *.xcworkspace/xcuserdata/ 12 | -------------------------------------------------------------------------------- /Tests/CaptiveWebViewTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import XCTest 5 | 6 | #if !canImport(ObjectiveC) 7 | public func allTests() -> [XCTestCaseEntry] { 8 | return [ 9 | testCase(CaptiveWebViewTests.allTests), 10 | ] 11 | } 12 | #endif 13 | -------------------------------------------------------------------------------- /forAndroid/Headless/readme.md: -------------------------------------------------------------------------------- 1 | Headless Sample 2 | =============== 3 | This directory has the Headless sample application for Android. For details, see 4 | the [../../documentation/headless.md](../../documentation/headless.md) file. 5 | 6 | Legal 7 | ===== 8 | Copyright 2019 Omnissa, LLC. 9 | SPDX-License-Identifier: BSD-2-Clause 10 | -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /forAndroid/captivewebview/src/documentation/readme.md: -------------------------------------------------------------------------------- 1 | # Module localwebview 2 | Placeholder for the preamble for the whole Local Web View module. 3 | 4 | # Package captivewebview 5 | Placeholder for the preamble for the package. 6 | 7 | Legal 8 | ===== 9 | Copyright 2019 Omnissa, LLC. 10 | SPDX-License-Identifier: BSD-2-Clause 11 | -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /forAndroid/settings.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | [ 5 | // Library: 6 | ':captivewebview', 7 | 8 | // Sample applications: 9 | ':Captivity', ':Headless', ':Skeleton', ':CaptiveCrypto', ':FetchTest' 10 | ].each({include it}) 11 | 12 | // TOTH: https://stackoverflow.com/a/43497244/7657675 13 | -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | #008577 8 | #00574B 9 | #D81B60 10 | 11 | -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | #008577 8 | #00574B 9 | #D81B60 10 | 11 | -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | #008577 8 | #00574B 9 | #D81B60 10 | 11 | -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | #008577 8 | #00574B 9 | #D81B60 10 | 11 | -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | #008577 8 | #00574B 9 | #D81B60 10 | 11 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/Array+inserting.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | 6 | extension Array { 7 | func inserting(_ element:Element, at index:Int) -> Array { 8 | var inserted = self 9 | inserted.insert(element, at: index) 10 | return inserted 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/CaptiveWebViewTests/CaptiveWebViewTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import XCTest 5 | @testable import CaptiveWebView 6 | 7 | final class CaptiveWebViewTests: XCTestCase { 8 | func testExample() { 9 | // TODO add Swift unit tests 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /forApple/MacSkeletonLocal.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 6 | 8 | 10 | 11 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /forApple/MacSkeleton/MacSkeleton/ViewController.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Cocoa 5 | import WebKit 6 | 7 | import CaptiveWebView 8 | 9 | class MainViewController: CaptiveWebView.DefaultViewController { 10 | 11 | override var representedObject: Any? { 12 | didSet { 13 | // Update the view, if already loaded. 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /forApple/Skeleton/Skeleton/Skeleton.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | com.apple.security.app-sandbox 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /forApple/Captivity/Captivity/Captivity.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | com.apple.security.app-sandbox 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /forApple/FetchTest/FetchTest/FetchTest.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | com.apple.security.app-sandbox 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Captive Web View 2 | Copyright (c) 2019 Omnissa, LLC. All Rights Reserved. 3 | 4 | This product is licensed to you under the BSD-2 license (the "License"). You may not use this product except in compliance with the BSD-2 License. 5 | 6 | This product may include a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. 7 | 8 | -------------------------------------------------------------------------------- /WebResources/CaptiveCrypto/UserInterface/KeyStore.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 |

Loading ...

17 | 18 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/CaptiveCrypto.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | com.apple.security.app-sandbox 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /notices.ignore: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Omnissa, LLC. 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | # Specifies files for the copyright notice checker to ignore in this 5 | # repository. Same format as .gitignore files. 6 | 7 | # Files from an Android Studio project template that haven't been edited. 8 | **/proguard-rules.pro 9 | forAndroid/**/ic_launcher*.xml 10 | 11 | # File from the three.js project. 12 | WebResources/Captivity/UserInterface/three.html 13 | 14 | # Files that are supposed to be empty. 15 | **/__init__.py 16 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import UIKit 5 | import CaptiveWebView 6 | 7 | @UIApplicationMain 8 | class AppDelegate: CaptiveWebView.ApplicationDelegate { 9 | 10 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 11 | 12 | self.launch(MainViewController.self) 13 | 14 | return true 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Sources/CaptiveWebView/CaptiveWebView.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #import 5 | 6 | //! Project version number for CaptiveWebView. 7 | FOUNDATION_EXPORT double CaptiveWebViewVersionNumber; 8 | 9 | //! Project version string for CaptiveWebView. 10 | FOUNDATION_EXPORT const unsigned char CaptiveWebViewVersionString[]; 11 | 12 | // In this header, you should import all the public headers of your framework using statements like #import 13 | -------------------------------------------------------------------------------- /WebResources/CaptiveCrypto/UserInterface/Main.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 13 |

Loading ...

20 | 21 | -------------------------------------------------------------------------------- /WebResources/Captivity/UserInterface/Secondary.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 13 |

Loading ...

20 | 21 | -------------------------------------------------------------------------------- /forApple/MacSkeleton/MacSkeleton/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Cocoa 5 | 6 | @NSApplicationMain 7 | class AppDelegate: NSObject, NSApplicationDelegate { 8 | 9 | 10 | 11 | func applicationDidFinishLaunching(_ aNotification: Notification) { 12 | // Insert code here to initialize your application 13 | } 14 | 15 | func applicationWillTerminate(_ aNotification: Notification) { 16 | // Insert code here to tear down your application 17 | } 18 | 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /forApple/MacSkeleton/MacSkeleton/MacSkeleton.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | com.apple.security.app-sandbox 10 | 11 | com.apple.security.files.user-selected.read-only 12 | 13 | com.apple.security.network.client 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/java/com/example/captivity/SecondaryActivity.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | package com.example.captivity 5 | 6 | import org.json.JSONObject 7 | 8 | class SecondaryActivity: com.example.captivewebview.DefaultActivity() { 9 | 10 | override fun commandResponse( 11 | command: String?, 12 | jsonObject: JSONObject 13 | ): JSONObject { 14 | return when(command) { 15 | "ready" -> jsonObject 16 | else -> super.commandResponse(command, jsonObject) 17 | } 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/java/com/example/fetchtest/MainActivity.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | package com.example.fetchtest 5 | 6 | import org.json.JSONObject 7 | import java.lang.Exception 8 | 9 | class MainActivity: com.example.captivewebview.DefaultActivity() { 10 | 11 | override fun commandResponse( 12 | command: String?, 13 | jsonObject: JSONObject 14 | ): JSONObject { 15 | return when(command) { 16 | "ready" -> jsonObject 17 | else -> super.commandResponse(command, jsonObject) 18 | } 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/java/com/example/skeleton/MainActivity.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | package com.example.skeleton 5 | 6 | import org.json.JSONObject 7 | import java.lang.Exception 8 | 9 | class MainActivity: com.example.captivewebview.DefaultActivity() { 10 | 11 | override fun commandResponse( 12 | command: String?, 13 | jsonObject: JSONObject 14 | ): JSONObject { 15 | return when(command) { 16 | "ready" -> jsonObject 17 | else -> super.commandResponse(command, jsonObject) 18 | } 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /forApple/Captivity/Captivity/SecondaryViewController.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import UIKit 5 | 6 | import CaptiveWebView 7 | 8 | class SecondaryViewController: CaptiveWebView.DefaultViewController { 9 | 10 | override func response( 11 | to command: String, 12 | in commandDictionary: Dictionary 13 | ) throws -> Dictionary 14 | { 15 | switch command { 16 | case "ready": 17 | return [:] 18 | default: 19 | return try super.response(to: command, in: commandDictionary) 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /harness/__main__.py: -------------------------------------------------------------------------------- 1 | # Run with Python 3 2 | # Copyright 2022 Omnissa, LLC. 3 | # SPDX-License-Identifier: BSD-2-Clause 4 | 5 | # This file makes harness a runnable module. To get the command line usage, run 6 | # it like this. 7 | # 8 | # cd /path/where/you/cloned/captive-web-view/ 9 | # python3 -m harness --help 10 | 11 | # 12 | # Standard library imports, in alphabetic order. 13 | # 14 | # Module for the operating system interface. 15 | # https://docs.python.org/3/library/sys.html 16 | from sys import argv, stderr, exit 17 | # 18 | # Local imports. 19 | # 20 | # The HTTP server harness. 21 | from harness import server 22 | 23 | exit(server.Main("python3 -m harness", None, argv)()) 24 | -------------------------------------------------------------------------------- /forApple/CaptiveWebView.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 6 | 8 | 10 | 11 | 13 | 14 | 16 | 17 | 19 | 20 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Omnissa, LLC. 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | # That macOS file it puts everywhere. 5 | .DS_Store 6 | 7 | # Publishing location for the AAR file. 8 | /m2repository 9 | 10 | # Material design minified or original files have to be included by hand. 11 | material.min.css 12 | material.min.js 13 | material.css 14 | material.js 15 | 16 | # File with secrets like go-rest token. 17 | WebResources/Headless/WebResources/secrets.js 18 | 19 | # Python that was cached by importation 20 | __pycache__/ 21 | 22 | # Directory with the three.js framework shouldn't be added to our repository. 23 | WebResources/Captivity/UserInterface/js/ 24 | 25 | # Swift Package Manager 26 | /.build 27 | /Packages 28 | /*.xcodeproj 29 | xcuserdata/ -------------------------------------------------------------------------------- /Sources/CaptiveWebView/CaptiveWebView/CaptiveWebViewCommandHandler.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | import os.log 6 | 7 | // A protocol can't be nested in a struct. 8 | 9 | public protocol CaptiveWebViewCommandHandler { 10 | func handleCommand(_ command:Dictionary) 11 | -> Dictionary 12 | func logCaptiveWebViewCommandHandler(_ message:String) 13 | } 14 | 15 | extension CaptiveWebViewCommandHandler { 16 | public func logCaptiveWebViewCommandHandler(_ message:String) { 17 | if #available(macOS 10.12, *) { 18 | os_log("%@", message) 19 | } else { 20 | NSLog("%@", message) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /WebResources/FetchTest/UserInterface/Main.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 19 | 20 |

Loading ...

27 | -------------------------------------------------------------------------------- /WebResources/Skeleton/UserInterface/Main.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 19 | 20 |

Loading ...

27 | -------------------------------------------------------------------------------- /here.code-workspace: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | // This is a workspace declaration to facilitate use of VSCode and VSCodium to 5 | // edit the readme and other documentation under this directory. 6 | // See https://code.visualstudio.com/docs/editor/multi-root-workspaces 7 | { 8 | "folders": [ 9 | { "path": ".", "name": "Captive Web View" } 10 | ], 11 | "settings": { 12 | // Variable names are documented in the VSCod[e|ium] Settings. 13 | // Search for "window title". 14 | "window.title": "${activeEditorShort}${separator}${folderName}" 15 | }, 16 | "extensions": { 17 | "recommendations": [ 18 | // Word wrapping for Markdown and comments. 19 | "stkb.rewrap" 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /WebResources/Headless/WebResources/Headless.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 20 | 21 |

Loading ...

28 | -------------------------------------------------------------------------------- /forApple/Captivity/Captivity/SpinnerViewController.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import UIKit 5 | 6 | import CaptiveWebView 7 | 8 | class SpinnerViewController: CaptiveWebView.DefaultViewController { 9 | 10 | var polls = 0; 11 | 12 | override func response( 13 | to command: String, 14 | in commandDictionary: Dictionary 15 | ) throws -> Dictionary 16 | { 17 | switch command { 18 | case "getStatus": 19 | polls = (polls + 1) % 30 20 | return ["message":"Dummy status \(polls)."] 21 | case "ready": 22 | return ["showLog":false] 23 | default: 24 | return try super.response(to: command, in: commandDictionary) 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /WebResources/CaptiveCrypto/UserInterface/captivecrypto.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import PageBuilder from "./pagebuilder.js"; 5 | 6 | class CaptiveCrypto { 7 | constructor(bridge) { 8 | const loading = document.getElementById('loading'); 9 | this._bridge = bridge; 10 | 11 | const builder = new PageBuilder('div', undefined, document.body); 12 | builder.add_anchor("KeyStore.html", "Native Key Store"); 13 | builder.add_anchor( 14 | "SubtleCrypto.html", "HTML 5 Subtle Crypto Scratch Code"); 15 | 16 | loading.firstChild.textContent = "Captive Cryptography"; 17 | loading.classList.add('loaded'); 18 | } 19 | } 20 | 21 | export default function(bridge) { 22 | new CaptiveCrypto(bridge); 23 | return null; 24 | } 25 | -------------------------------------------------------------------------------- /forAndroid/Captivity/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /forAndroid/FetchTest/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /forAndroid/Headless/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /forAndroid/Skeleton/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /forAndroid/captivewebview/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -------------------------------------------------------------------------------- /forApple/Captivity/Captivity/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import UIKit 5 | import CaptiveWebView 6 | 7 | class MainViewController: CaptiveWebView.DefaultViewController { 8 | 9 | // Implicit raw values, see: 10 | // https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html#ID535 11 | private enum Command: String { 12 | case ready 13 | } 14 | 15 | override func response( 16 | to command: String, 17 | in commandDictionary: Dictionary 18 | ) throws -> Dictionary 19 | { 20 | switch Command(rawValue: command) { 21 | case .ready: 22 | return [:] 23 | default: 24 | return try super.response(to: command, in: commandDictionary) 25 | } 26 | } 27 | 28 | } 29 | 30 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/StoredKey/Storage.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | 6 | extension StoredKey { 7 | // Enumeration for different storage of keys supported by this class, either 8 | // as generic passwords in the keychain, or as keys in the keychain. The 9 | // keychain only stores private keys as keys, so symmetric keys must be 10 | // stored as generic passwords. 11 | internal enum Storage: String, CaseIterable { 12 | case generic, key 13 | 14 | var secClass:CFString { switch self { 15 | case .generic: return kSecClassGenericPassword 16 | case .key: return kSecClassKey 17 | } } 18 | 19 | var kSecReturn:CFString { switch self { 20 | case .generic: return kSecReturnData 21 | case .key: return kSecReturnRef 22 | } } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /forAndroid/build.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 5 | 6 | buildscript { 7 | ext.kotlin_version = '1.7.10' 8 | repositories { 9 | google() 10 | mavenCentral() 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:8.0.2' 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | // NOTE: Do not place your application dependencies here; they belong 16 | // in the individual module build.gradle files 17 | } 18 | } 19 | 20 | allprojects { 21 | repositories { 22 | google() 23 | mavenCentral() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | 31 | ext { 32 | // Version number of the Captive Web View library for Android. 33 | captiveWebViewVersion = '8.1.0' 34 | } -------------------------------------------------------------------------------- /forAndroid/gradle.properties: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Omnissa, LLC. 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | # 4 | # Project-wide Gradle settings. 5 | # IDE (e.g. Android Studio) users: 6 | # Gradle settings configured through the IDE *will override* 7 | # any settings specified in this file. 8 | # For more details on how to configure your build environment visit 9 | # http://www.gradle.org/docs/current/userguide/build_environment.html 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | # When configured, Gradle will run in incubating parallel mode. 14 | # This option should only be used with decoupled projects. More details, visit 15 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 16 | # org.gradle.parallel=true 17 | # Kotlin code style for this project: "official" or "obsolete": 18 | kotlin.code.style=official 19 | 20 | android.useAndroidX=true 21 | 22 | android.defaults.buildfeatures.buildconfig=true 23 | -------------------------------------------------------------------------------- /CaptiveWebView.podspec: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Omnissa, LLC. 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | # 4 | # Be sure to run `pod lib lint CaptiveWebView.podspec' to ensure this is a 5 | # valid spec before submitting. 6 | # 7 | # Any lines starting with a # are optional, but their use is encouraged 8 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 9 | 10 | Pod::Spec.new do |s| 11 | s.name = 'CaptiveWebView' 12 | s.version = '8.0.0' 13 | s.summary = 'The Captive Web View library facilitates use of web technologies in mobile applications.' 14 | 15 | s.homepage = 'https://github.com/omnissa-archive/captive-web-view' 16 | s.license = { :type => 'BSD-2', :file => 'LICENSE.txt' } 17 | s.author = 'omnissa' 18 | s.source = { :git => 'https://github.com/omnissa-archive/captive-web-view.git', :tag => "#{s.version}" } 19 | 20 | 21 | s.ios.deployment_target = '14.0' 22 | 23 | s.source_files = '**/Sources/**/*' 24 | s.resources = "**/Sources/CaptiveWebView/Resources/*" 25 | end 26 | -------------------------------------------------------------------------------- /WebResources/Skeleton/UserInterface/main.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | class Main { 5 | constructor(bridge) { 6 | const loading = document.getElementById('loading'); 7 | 8 | this._transcript = document.createElement('div'); 9 | document.body.append(this._transcript); 10 | 11 | bridge.receiveObjectCallback = command => { 12 | this._transcribe(command); 13 | return Object.assign(command, {"confirm": "Main"}); 14 | }; 15 | 16 | loading.firstChild.textContent = "Skeleton"; 17 | 18 | bridge.sendObject({"command": "ready"}) 19 | .then(response => this._transcribe(response)) 20 | .catch(error => this._transcribe(error)); 21 | } 22 | 23 | _transcribe(message) { 24 | const pre = document.createElement('pre'); 25 | pre.append(JSON.stringify(message, undefined, 4)); 26 | this._transcript.append(pre); 27 | } 28 | } 29 | 30 | export default function(bridge) { 31 | new Main(bridge); 32 | return null; 33 | } 34 | -------------------------------------------------------------------------------- /Sources/CaptiveWebView/URL+Changing.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | 6 | public extension URL { 7 | func changing(lastPathComponent:String) -> URL { 8 | return deletingLastPathComponent().appendingPathComponent( 9 | lastPathComponent) 10 | } 11 | 12 | func changing(scheme:String) -> URL { 13 | var changed:URLComponents = URLComponents( 14 | url: self, resolvingAgainstBaseURL: false)! 15 | changed.scheme = scheme 16 | return changed.url! 17 | } 18 | 19 | func appending(pathComponents:[String]?) -> URL { 20 | var appended = self 21 | pathComponents?.forEach() { 22 | appended.appendPathComponent($0) 23 | } 24 | return appended 25 | } 26 | 27 | func appending(pathComponents:URL?) -> URL { 28 | // Extract the array of path components from the URL. 29 | // It's OK to pass nil. 30 | return self.appending(pathComponents: pathComponents?.pathComponents) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /WebResources/Captivity/UserInterface/speech.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 22 | 23 |

Speech

36 | 37 | -------------------------------------------------------------------------------- /noticeChecker/test_path_matcher.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Omnissa, LLC. 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | # Run with Python 3.9 or later. 5 | """File in the noticeChecker module.""" 6 | # 7 | # Local imports 8 | # 9 | from noticeChecker.path_matcher import matches_transcript 10 | 11 | def path_matcher_tests(): 12 | for expected, *parameters in ( 13 | (True, 'a/b/c', 'a/b/c'), 14 | (False, 'b/c', 'a/b/c'), 15 | (False, 'b/c', 'a/**/b/c'), 16 | (True, 'a/b/c/d/e.txt', 'd/e.*'), 17 | (True, 'b/c', '**/b/c'), 18 | (True, 'b/c', '**/**/b/c'), 19 | (False, 'a/b/c/d/e.txt', '/d/e.*'), 20 | (True, 'a/b/c/d/e.txt', '**/d/e.*'), 21 | (True, 'a/b/c/d/e.txt', 'a/b/**/e.*'), 22 | (True, 'a/b/c/d/e.txt', 'a/**/**/e.*'), 23 | (True, 'a/b/c/d/e.txt', '**/d/**/e.*'), 24 | (True, 'a/b/c/d/e.txt', 'a/**/d/**/e.*') 25 | ): 26 | matched, transcript = matches_transcript(*parameters) 27 | print("Pass" if matched == expected else "Fail", matched, parameters) 28 | for line in transcript: 29 | print(" " + line) 30 | -------------------------------------------------------------------------------- /Sources/CaptiveWebView/CaptiveWebViewError.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | 6 | // Swift seems to have made it rather difficult to create a throw-able that 7 | // has a message that can be retrieved in the catch. So, Captive Web View 8 | // has its own custom class here. 9 | // 10 | // Having created a custom class anyway, it seemed like a code-saver to pack 11 | // it with convenience initialisers for an array of strings, variadic 12 | // strings, and CFString. 13 | 14 | public class CaptiveWebViewError: Error { 15 | let message:String 16 | 17 | public init(_ message:String) { 18 | self.message = message 19 | } 20 | public convenience init(_ message:[String]) { 21 | self.init(message.joined()) 22 | } 23 | public convenience init(_ message:String...) { 24 | self.init(message) 25 | } 26 | public convenience init(_ message:CFString) { 27 | self.init(NSString(string: message) as String) 28 | } 29 | 30 | var localizedDescription: String { self.message } 31 | 32 | var description: String { self.message } 33 | } 34 | -------------------------------------------------------------------------------- /forAndroid/captivewebview/src/main/java/com/example/captivewebview/DefaultActivity.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | package com.example.captivewebview 5 | 6 | import android.annotation.SuppressLint 7 | 8 | // This class is intended to be used as a base class only, so no need to warn 9 | // about it not being registered in the manifest. 10 | @SuppressLint("Registered") 11 | open class DefaultActivity: 12 | com.example.captivewebview.Activity(), 13 | DefaultActivityMixIn 14 | { 15 | // There's almost no code! This class is only here to pick up the 16 | // DefaultActivityMixIn code and apply it to the Local Web View Activity 17 | // base class. 18 | 19 | val activityMap = DefaultActivityMixIn.activityMap 20 | fun addToActivityMap(key:String, activityClassJava: Any) { 21 | DefaultActivityMixIn.addToActivityMap(key, activityClassJava) 22 | } 23 | fun addToActivityMap(map: Map) { 24 | DefaultActivityMixIn.addToActivityMap(map) 25 | } 26 | fun addToActivityMap(vararg pairs: Pair) { 27 | DefaultActivityMixIn.addToActivityMap(*pairs) 28 | } 29 | } -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/StoredKey/Generation.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | 6 | extension StoredKey { 7 | public enum GenerationSentinelResult:String { 8 | case passed, failed, multipleKeys 9 | } 10 | 11 | internal static func generationSentinel( 12 | _ basis:StoredKeyBasis, _ alias:String 13 | ) throws -> GenerationSentinelResult 14 | { 15 | let keys = try self.keysWithName(alias) 16 | if keys.count == 1 { 17 | let storedKey = basis.storedKey() 18 | let sentinel = "InMemorySentinel" 19 | let enciphered = try storedKey.encipher(sentinel) 20 | let deciphered = try self.decipher( 21 | enciphered, withFirstKeyNamed: alias) 22 | return sentinel == deciphered ? .passed : .failed 23 | } 24 | else { 25 | return .multipleKeys 26 | } 27 | } 28 | 29 | struct KeyGeneration:Encodable { 30 | let deletedFirst:Bool 31 | let sentinelCheck:String 32 | let summary:[String] 33 | let attributes:[String:AnyEncodable] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /forApple/MacSkeleton/MacSkeleton/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/StoredKey/Deletion.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | 6 | extension StoredKey { 7 | 8 | 9 | struct Deletion: Encodable { 10 | let deleted: [String] 11 | let notDeleted: [String:String] 12 | } 13 | 14 | // Clears the keychain and returns a summary of what storage types were 15 | // deleted or not deleted because of an error. 16 | static func deleteAll() -> Deletion { 17 | var deleted:[String] = [] 18 | var notDeleted:[String:String] = [:] 19 | 20 | for storage in Storage.allCases { 21 | // Query to find all items of this security class. 22 | let query: [CFString: Any] = [kSecClass: storage.secClass] 23 | let status = SecItemDelete(query as CFDictionary) 24 | if status == errSecSuccess || status == errSecItemNotFound { 25 | deleted.append(storage.rawValue) 26 | } 27 | else { 28 | notDeleted[storage.rawValue] = status.secErrorMessage 29 | } 30 | } 31 | 32 | return Deletion(deleted: deleted, notDeleted: notDeleted) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/StoredKey/GenericPasswordConvertible.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | import CryptoKit 6 | 7 | // General approach to storing symmetric keys in the keychain, and code 8 | // snippets, are from here: 9 | // https://developer.apple.com/documentation/cryptokit/storing_cryptokit_keys_in_the_keychain 10 | 11 | // Declare protocol. 12 | protocol GenericPasswordConvertible: CustomStringConvertible { 13 | /// Creates a key from a raw representation. 14 | init(rawRepresentation data: D) throws where D: ContiguousBytes 15 | 16 | /// A raw representation of the key. 17 | var rawRepresentation: Data { get } 18 | } 19 | 20 | // Add extension that makes CryptoKey SymmetricKey satisfy the protocol. 21 | extension SymmetricKey: GenericPasswordConvertible { 22 | public var description: String { 23 | return "symmetrically" 24 | } 25 | 26 | init(rawRepresentation data: D) throws where D: ContiguousBytes { 27 | self.init(data: data) 28 | } 29 | 30 | var rawRepresentation: Data { 31 | return withUnsafeBytes{Data($0)} 32 | } 33 | } 34 | // End of first code to support storing CryptoKit symmetric key in the keychain. 35 | -------------------------------------------------------------------------------- /forAndroid/Captivity/build.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | plugins { 5 | id 'com.android.application' 6 | id 'kotlin-android' 7 | } 8 | 9 | android { 10 | namespace 'com.example.captivity' 11 | compileSdk 33 12 | 13 | sourceSets { 14 | main.assets.srcDirs += new File(new File( 15 | rootDir.getParent(), "WebResources"), "Captivity") 16 | } 17 | 18 | defaultConfig { 19 | applicationId "com.example.captivity" 20 | minSdk 29 21 | targetSdk 33 22 | versionCode 1 23 | versionName "1.0" 24 | 25 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 26 | } 27 | 28 | buildTypes { 29 | release { 30 | minifyEnabled false 31 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 32 | } 33 | } 34 | 35 | } 36 | 37 | dependencies { 38 | def appcompat_version = "1.6.1" 39 | 40 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 41 | implementation "androidx.appcompat:appcompat:$appcompat_version" 42 | implementation "androidx.appcompat:appcompat-resources:$appcompat_version" 43 | 44 | implementation project(':captivewebview') 45 | } 46 | -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/java/com/example/captivecrypto/storedkey/KeysDeletion.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | package com.example.captivecrypto.storedkey 5 | 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | data class KeysDeletion( 10 | val deleted:List, val notDeleted:Map 11 | ) 12 | 13 | fun deleteAllStoredKeys( 14 | providerName: String = KEY.AndroidKeyStore.name 15 | ): KeysDeletion 16 | { 17 | val keyStore = loadKeyStore(providerName) 18 | val deleted = mutableListOf() 19 | val notDeleted = mutableMapOf() 20 | // Next part could maybe be done like this: 21 | // 22 | // keyStore.aliases().toList().forEach { ... } 23 | // 24 | // However, it seems hazardous to delete from the key store in scope 25 | // of an iterator across the key store. So there's a separate 26 | // variable instead. 27 | val deleting = keyStore.aliases().toList() 28 | deleting.forEach { 29 | try { 30 | keyStore.deleteEntry(it) 31 | deleted.add(it) 32 | } 33 | catch (exception: Exception) { 34 | notDeleted[it] = exception.toString() 35 | } 36 | } 37 | return KeysDeletion(deleted.toList(), notDeleted) 38 | } 39 | -------------------------------------------------------------------------------- /forApple/MacSkeleton/MacSkeleton/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSHumanReadableCopyright 6 | Copyright 2020 Omnissa, LLC. 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | NSSupportsAutomaticTermination 32 | 33 | NSSupportsSuddenTermination 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /forAndroid/FetchTest/build.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | plugins { 5 | id 'com.android.application' 6 | id 'kotlin-android' 7 | } 8 | 9 | android { 10 | namespace 'com.example.fetchtest' 11 | compileSdk 33 12 | 13 | sourceSets { 14 | main.assets.srcDirs += new File(new File( 15 | rootDir.getParent(), "WebResources"), "FetchTest") 16 | } 17 | 18 | defaultConfig { 19 | applicationId "com.example.fetchtest" 20 | minSdk 29 21 | targetSdk 33 22 | versionCode 1 23 | versionName "1.0" 24 | 25 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 26 | } 27 | 28 | buildTypes { 29 | release { 30 | minifyEnabled false 31 | proguardFiles getDefaultProguardFile( 32 | 'proguard-android-optimize.txt'), 'proguard-rules.pro' 33 | } 34 | } 35 | 36 | } 37 | 38 | dependencies { 39 | def appcompat_version = "1.6.1" 40 | 41 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 42 | implementation "androidx.appcompat:appcompat:$appcompat_version" 43 | implementation "androidx.appcompat:appcompat-resources:$appcompat_version" 44 | 45 | implementation project(':captivewebview') 46 | } 47 | -------------------------------------------------------------------------------- /forAndroid/Skeleton/build.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | plugins { 5 | id 'com.android.application' 6 | id 'kotlin-android' 7 | } 8 | 9 | android { 10 | namespace 'com.example.skeleton' 11 | compileSdk 33 12 | 13 | sourceSets { 14 | main.assets.srcDirs += new File(new File( 15 | rootDir.getParent(), "WebResources"), "Skeleton") 16 | } 17 | 18 | defaultConfig { 19 | applicationId "com.example.skeleton" 20 | minSdk 29 21 | targetSdk 33 22 | versionCode 1 23 | versionName "1.0" 24 | 25 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 26 | } 27 | 28 | buildTypes { 29 | release { 30 | minifyEnabled false 31 | proguardFiles getDefaultProguardFile( 32 | 'proguard-android-optimize.txt'), 'proguard-rules.pro' 33 | } 34 | } 35 | 36 | } 37 | 38 | dependencies { 39 | def appcompat_version = "1.6.1" 40 | 41 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 42 | implementation "androidx.appcompat:appcompat:$appcompat_version" 43 | implementation "androidx.appcompat:appcompat-resources:$appcompat_version" 44 | 45 | implementation project(':captivewebview') 46 | } 47 | -------------------------------------------------------------------------------- /forAndroid/captivewebview/src/main/java/com/example/captivewebview/Activity.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | package com.example.captivewebview 5 | 6 | import android.app.Activity 7 | import android.os.Bundle 8 | 9 | open class Activity : Activity(), ActivityMixIn { 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | onCreateMixIn() 13 | } 14 | 15 | // Following code would implement saving and restoring of the WebView but 16 | // that seems to be an inferior solution to configuration change handling 17 | // declaration. See the warnMissingDeclaration() method for details. 18 | // override fun onSaveInstanceState(outState: Bundle?) { 19 | // val webView = 20 | // findViewById(WEB_VIEW_ID) 21 | // webView?.saveState(outState) 22 | // super.onSaveInstanceState(outState) 23 | // } 24 | // 25 | // override fun onRestoreInstanceState(savedInstanceState: Bundle?) { 26 | // val webView = 27 | // findViewById(WEB_VIEW_ID) 28 | // webView?.restoreState(savedInstanceState) 29 | // super.onRestoreInstanceState(savedInstanceState) 30 | // } 31 | 32 | } -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/java/com/example/captivity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | package com.example.captivity 5 | 6 | import android.os.Bundle 7 | import org.json.JSONObject 8 | 9 | class MainActivity: com.example.captivewebview.DefaultActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | addToActivityMap( 14 | "Secondary" to SecondaryActivity::class.java, 15 | "Spinner" to SpinnerActivity::class.java 16 | ) 17 | } 18 | 19 | // Android Studio warns that `ready` should start with a capital letter but 20 | // it shouldn't because it has to match what gets sent from the JS layer. 21 | private enum class Command { 22 | ready, UNKNOWN; 23 | 24 | companion object { 25 | fun matching(string: String?): Command? { 26 | return if (string == null) null 27 | else try { valueOf(string) } 28 | catch (exception: Exception) { UNKNOWN } 29 | } 30 | } 31 | } 32 | 33 | override fun commandResponse(command: String?, jsonObject: JSONObject) = 34 | when(Command.matching(command)) { 35 | Command.ready -> jsonObject 36 | else -> super.commandResponse(command, jsonObject) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | // Copyright 2023 Omnissa, LLC. 5 | // SPDX-License-Identifier: BSD-2-Clause 6 | 7 | import PackageDescription 8 | 9 | let package = Package( 10 | name: "CaptiveWebView", 11 | platforms: [ 12 | .macOS(.v10_11), .iOS(.v13) 13 | ], 14 | products: [ 15 | // Products define the executables and libraries a package produces, and make them visible to other packages. 16 | .library( 17 | name: "CaptiveWebView", 18 | targets: ["CaptiveWebView"]), 19 | ], 20 | dependencies: [ 21 | // Dependencies declare other packages that this package depends on. 22 | // .package(url: /* package url */, from: "1.0.0"), 23 | ], 24 | targets: [ 25 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 26 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 27 | .target( 28 | name: "CaptiveWebView", 29 | dependencies: [], 30 | resources:[ 31 | .copy("Resources/library"), 32 | ]), 33 | .testTarget( 34 | name: "CaptiveWebViewTests", 35 | dependencies: ["CaptiveWebView"]), 36 | ] 37 | ) 38 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/StoredKey/StoredKey.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | import CryptoKit 6 | 7 | class StoredKey { 8 | // Properties and methods of a StoredKey instance. It isn't necessary to use 9 | // StoredKey instances externally. The static methods, like 10 | // encypt(message withFirstKeyNamed:) for example, can be used instead. 11 | internal let _storage:Storage 12 | let secKey:SecKey? 13 | let symmetricKey:SymmetricKey? 14 | 15 | var storage:String {_storage.rawValue} 16 | 17 | // Symmetric key constructor. 18 | init(_ symmetricKey:SymmetricKey) { 19 | _storage = .generic 20 | secKey = nil 21 | self.symmetricKey = symmetricKey 22 | } 23 | 24 | // Key pair constructor. The SecKey will be the private key. The 25 | // corresponding public key will be generated as needed in either of the 26 | // encipher or decipher methods. 27 | init(_ secKey:SecKey) { 28 | _storage = .key 29 | self.secKey = secKey 30 | symmetricKey = nil 31 | } 32 | 33 | } 34 | 35 | protocol StoredKeyBasis { 36 | func storedKey() -> StoredKey 37 | } 38 | extension SecKey: StoredKeyBasis { 39 | func storedKey() -> StoredKey { return StoredKey(self) } 40 | } 41 | extension SymmetricKey: StoredKeyBasis { 42 | func storedKey() -> StoredKey { return StoredKey(self) } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /forApple/FetchTest/FetchTest/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import UIKit 5 | import CaptiveWebView 6 | 7 | import os.log 8 | 9 | class MainViewController: CaptiveWebView.DefaultViewController { 10 | 11 | var sent = false 12 | 13 | override func response( 14 | to command: String, 15 | in commandDictionary: Dictionary 16 | ) throws -> Dictionary 17 | { 18 | // First time a command is received, send a dummy object to the 19 | // JavaScript layer, just for demonstration purposes. 20 | if !sent { 21 | _ = timedSend(seconds: 1) 22 | sent = true 23 | } 24 | switch command { 25 | case "ready": 26 | return [:] 27 | default: 28 | return try super.response(to: command, in: commandDictionary) 29 | } 30 | } 31 | 32 | private func timedSend(seconds:TimeInterval) -> Timer { 33 | return Timer.scheduledTimer(withTimeInterval: seconds, repeats: false) { 34 | (timer:Timer) in 35 | self.sendObject(["fireDate":"\(timer.fireDate)"]) { 36 | (result:Any?, error:Error?) in 37 | os_log("sendObject result: %@, error: %@", 38 | String(describing: result), String(describing: error) 39 | ) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /forApple/Skeleton/Skeleton/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import UIKit 5 | import CaptiveWebView 6 | 7 | import os.log 8 | 9 | class MainViewController: CaptiveWebView.DefaultViewController { 10 | 11 | var sent = false 12 | 13 | override func response( 14 | to command: String, 15 | in commandDictionary: Dictionary 16 | ) throws -> Dictionary 17 | { 18 | // First time a command is received, send a dummy object to the 19 | // JavaScript layer, just for demonstration purposes. 20 | if !sent { 21 | _ = timedSend(seconds: 1) 22 | sent = true 23 | } 24 | switch command { 25 | case "ready": 26 | return [:] 27 | default: 28 | return try super.response(to: command, in: commandDictionary) 29 | } 30 | } 31 | 32 | private func timedSend(seconds:TimeInterval) -> Timer { 33 | return Timer.scheduledTimer(withTimeInterval: seconds, repeats: false) { 34 | (timer:Timer) in 35 | self.sendObject(["fireDate":"\(timer.fireDate)"]) { 36 | (result:Any?, error:Error?) in 37 | os_log("sendObject result: %@, error: %@", 38 | String(describing: result), String(describing: error) 39 | ) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/AnyEncodable.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | 6 | // Dummy type to wrap any Encodable value. 7 | struct AnyEncodable:Encodable { 8 | let encodable:Encodable 9 | 10 | init(_ encodable:Encodable) { 11 | self.encodable = encodable 12 | } 13 | 14 | func encode(to encoder: Encoder) throws { 15 | try encodable.encode(to: encoder) 16 | } 17 | } 18 | // This is here because the following doesn't compile: 19 | // 20 | // public struct Description:Encodable { 21 | // let storage:String 22 | // let name:String 23 | // let type:String 24 | // let attributes:[String:Encodable] // This line is an error. 25 | // } 26 | // 27 | // It appears that there has to be an enum, struct or class wrapped around 28 | // the object that is Encodable. 29 | // 30 | 31 | 32 | // Extensions to make some pre-Swift classes conform to Encodable. 33 | extension NSNumber: Encodable { 34 | public func encode(to encoder: Encoder) throws { 35 | try Int(exactly: self).encode(to: encoder) 36 | } 37 | } 38 | 39 | extension CFNumber: Encodable { 40 | public func encode(to encoder: Encoder) throws { 41 | try (self as NSNumber).encode(to: encoder) 42 | } 43 | } 44 | extension CFString: Encodable { 45 | public func encode(to encoder: Encoder) throws { 46 | try (self as String).encode(to: encoder) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/build.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | plugins { 5 | id 'com.android.application' 6 | id 'kotlin-android' 7 | 8 | id 'org.jetbrains.kotlin.plugin.serialization' version '1.4.10' 9 | } 10 | 11 | android { 12 | namespace 'com.example.captivecrypto' 13 | compileSdk 33 14 | 15 | sourceSets { 16 | main.assets.srcDirs += new File(new File( 17 | rootDir.getParent(), "WebResources"), "CaptiveCrypto") 18 | } 19 | 20 | defaultConfig { 21 | applicationId "com.example.captivecrypto" 22 | minSdk 29 23 | targetSdk 33 24 | versionCode 1 25 | versionName "1.0" 26 | 27 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 28 | } 29 | 30 | buildTypes { 31 | release { 32 | minifyEnabled false 33 | proguardFiles getDefaultProguardFile( 34 | 'proguard-android-optimize.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | 38 | } 39 | 40 | dependencies { 41 | def appcompat_version = "1.6.1" 42 | 43 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 44 | implementation "androidx.appcompat:appcompat:$appcompat_version" 45 | implementation "androidx.appcompat:appcompat-resources:$appcompat_version" 46 | 47 | implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0-RC2" 48 | 49 | implementation project(':captivewebview') 50 | } 51 | -------------------------------------------------------------------------------- /forAndroid/Headless/build.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | plugins { 5 | id 'com.android.application' 6 | id 'kotlin-android' 7 | } 8 | 9 | android { 10 | namespace 'com.example.headless' 11 | compileSdk 33 12 | 13 | sourceSets { 14 | main.assets.srcDirs += new RelativePath( 15 | false, "WebResources", "Headless", "WebResources" 16 | ).getFile(new File(rootDir.getParent())) 17 | } 18 | 19 | defaultConfig { 20 | applicationId "com.example.headless" 21 | minSdk 29 22 | targetSdk 33 23 | versionCode 1 24 | versionName "1.0" 25 | 26 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 27 | } 28 | 29 | buildTypes { 30 | release { 31 | minifyEnabled false 32 | proguardFiles getDefaultProguardFile( 33 | 'proguard-android-optimize.txt'), 'proguard-rules.pro' 34 | } 35 | } 36 | 37 | } 38 | 39 | dependencies { 40 | def appcompat_version = "1.6.1" 41 | def constraintLayout_version = "2.1.4" 42 | 43 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 44 | implementation "androidx.appcompat:appcompat:$appcompat_version" 45 | implementation "androidx.appcompat:appcompat-resources:$appcompat_version" 46 | implementation( 47 | "androidx.constraintlayout:constraintlayout:$constraintLayout_version") 48 | 49 | implementation project(':captivewebview') 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Captive Web View 2 | Copyright (c) 2020 Omnissa, LLC. All rights reserved 3 | 4 | The BSD-2 license (the "License") set forth below applies to all parts of the Captive Web View project. You may not use this file except in compliance with the License. 5 | 6 | BSD-2 License 7 | 8 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | 12 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/java/com/example/captivity/SpinnerActivity.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | package com.example.captivity 5 | 6 | import com.example.captivewebview.put 7 | import org.json.JSONObject 8 | import java.lang.Exception 9 | 10 | class SpinnerActivity : com.example.captivewebview.DefaultActivity() { 11 | 12 | // Android Studio warns that these should start with capital letters but 13 | // they shouldn't because they have to match what gets sent from the JS 14 | // layer. 15 | private enum class Command { 16 | getStatus, ready, UNKNOWN; 17 | 18 | companion object { 19 | fun matching(string: String?): Command? { 20 | return if (string == null) null 21 | else try { 22 | Command.valueOf(string) 23 | } 24 | catch (exception: Exception) { UNKNOWN } 25 | } 26 | } 27 | } 28 | 29 | enum class Key { showLog, message } 30 | 31 | var polls = 0 32 | 33 | override fun commandResponse( 34 | command: String?, 35 | jsonObject: JSONObject 36 | ): JSONObject { 37 | return when(Command.matching(command)) { 38 | Command.getStatus -> { 39 | polls = (polls + 1) % 30 40 | jsonObject.put(Key.message, "Dummy status ${polls}.") 41 | } 42 | Command.ready -> jsonObject.put(Key.showLog, false) 43 | else -> super.commandResponse(command, jsonObject) 44 | } 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /captivityHarness/__main__.py: -------------------------------------------------------------------------------- 1 | # Run with Python 3 2 | # Copyright 2022 Omnissa, LLC. 3 | # SPDX-License-Identifier: BSD-2-Clause 4 | """\ 5 | HTTP server that can be used as a back end for the Captivity application during 6 | development.""" 7 | 8 | # To get the command line usage, run it like this. 9 | # 10 | # cd /path/where/you/cloned/captive-web-view/ 11 | # python3 -m captivityHarness --help 12 | 13 | # 14 | # Standard library imports, in alphabetic order. 15 | # 16 | # Module for OO path handling. 17 | # https://docs.python.org/3/library/pathlib.html 18 | from pathlib import Path 19 | # 20 | # Module for the operating system interface. 21 | # https://docs.python.org/3/library/sys.html 22 | from sys import argv, exit 23 | # 24 | # Local imports. 25 | # 26 | # Harness HTTP server base class. 27 | from harness.server import Main 28 | # 29 | # Command handlers. 30 | from harness.command_handler.base import JSONFileCommandHandler 31 | from harness.command_handler.fetch import FetchCommandHandler 32 | 33 | class Captivity(Main): 34 | # Override. 35 | def command_handlers(self): 36 | yield JSONFileCommandHandler(__file__) 37 | yield FetchCommandHandler() 38 | yield from super().command_handlers() 39 | 40 | # Override. 41 | def server_directories(self): 42 | # Add path with the Captivity HTML/CSS/JavaScript files. 43 | yield Path(__file__).parents[1].joinpath( 44 | 'WebResources', 'Captivity', 'UserInterface') 45 | yield from super().server_directories() 46 | 47 | exit(Captivity("python3 -m captivityHarness", __doc__, argv)()) 48 | -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 13 | 16 | 17 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /forApple/FetchTest/FetchTest/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSHumanReadableCopyright 6 | Copyright 2023 Omnissa, LLC. 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /forApple/Skeleton/Skeleton/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSHumanReadableCopyright 6 | Copyright 2023 Omnissa, LLC. 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /WebResources/Captivity/UserInterface/Main.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 48 | 49 |

Loading ...

56 | 57 | -------------------------------------------------------------------------------- /forAndroid/captivewebview/src/main/java/com/example/captivewebview/JSONExtensions.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | package com.example.captivewebview 5 | 6 | import org.json.JSONObject 7 | 8 | // The extensions in this file make the members of an enum class usable as 9 | // JSONObject keys and as String values in the left sides of `to` mappings. They 10 | // can be imported individually. 11 | // 12 | // Code that attempts to use an enum member as a JSONObject key without 13 | // importing will generate a build-time error, which is good. Fix by adding 14 | // individual import statements like these. 15 | // 16 | // import com.example.captivewebview.opt 17 | // import com.example.captivewebview.put 18 | // 19 | // Code that attempts to use an enum member as the String left side of a `to` 20 | // mapping might only generate a run-time error, which is difficult to 21 | // troubleshoot. Fix by adding an individual import statement like this. 22 | // 23 | // import com.example.captivewebview.to 24 | 25 | inline fun > JSONObject.opt(key: T) 26 | : Any? = opt(key.name) 27 | 28 | inline fun > JSONObject.put(key: T, value: Any?) 29 | : JSONObject = put(key.name, value) 30 | 31 | inline fun > JSONObject.putOpt(key: T, value: Any?) 32 | : JSONObject = putOpt(key.name, value) 33 | 34 | inline fun > JSONObject.remove(key: T) 35 | : Any? = remove(key.name) 36 | 37 | // Enables members of the any enumeration to be used as keys in mappings 38 | // from String to any, for example as mapOf() parameters. 39 | // Also blocks direct use of the enum in mappings. 40 | inline infix fun , VALUE> T.to(that: VALUE) 41 | : Pair = this.name to that 42 | -------------------------------------------------------------------------------- /forApple/Captivity/Captivity/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | NSHumanReadableCopyright 10 | Copyright 2023 Omnissa, LLC. 11 | CFBundleDevelopmentRegion 12 | $(DEVELOPMENT_LANGUAGE) 13 | CFBundleExecutable 14 | $(EXECUTABLE_NAME) 15 | CFBundleIdentifier 16 | $(PRODUCT_BUNDLE_IDENTIFIER) 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | $(PRODUCT_NAME) 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 1.0 25 | CFBundleVersion 26 | 1 27 | LSRequiresIPhoneOS 28 | 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Sources/CaptiveWebView/CaptiveWebView/ApplicationDelegate.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #if os(iOS) 5 | import Foundation 6 | import UIKit 7 | 8 | extension CaptiveWebView.ApplicationDelegate { 9 | 10 | /** 11 | Launch a view controller as the user interface without using a storyboard. 12 | 13 | - Parameter ViewController: Class of the view controller. Use syntax like 14 | `ViewControllerClassName.self` in the calling code. 15 | - Parameter screen: UIScreen to fill with the view. By default, the main 16 | screen of the device. 17 | 18 | */ 19 | // This function uses Swift Generics syntax with type constraints. See: 20 | // https://docs.swift.org/swift-book/LanguageGuide/Generics.html 21 | // By the way, UIViewController is a class, not an interface. 22 | // 23 | // The extension doesn't have to be declared as public, because the class is 24 | // public. However, the method here does have to be declared as public. 25 | public func launch(_ ViewController:V.Type, 26 | screen:UIScreen = UIScreen.main) 27 | { 28 | // TOTH: https://stackoverflow.com/questions/24046898/how-do-i-create-a-new-swift-project-without-using-storyboards#25482567 29 | let uiWindow: UIWindow = UIWindow(frame: screen.bounds) 30 | 31 | // It seems that it isn't allowed to set the `window` property, 32 | // except as it's done here, by putting the code to set it in a 33 | // subclass. 34 | self.window = uiWindow 35 | 36 | // Instantiating from a parameter requires explicit init() call. 37 | uiWindow.rootViewController = ViewController.init() 38 | uiWindow.makeKeyAndVisible() 39 | } 40 | 41 | } 42 | #endif 43 | -------------------------------------------------------------------------------- /WebResources/Captivity/UserInterface/secondary.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import PageBuilder from "./pagebuilder.js"; 5 | 6 | class Secondary { 7 | constructor(bridge) { 8 | this._bridge = bridge; 9 | const loading = document.getElementById('loading'); 10 | 11 | const builder = new PageBuilder('div', undefined, document.body); 12 | builder.add_node( 13 | 'div', "This page loads in a separate Activity or ViewController."); 14 | builder.add_node( 15 | 'div', "Return by tapping Close, or the Android back button."); 16 | const buttonClose = builder.add_button("Close"); 17 | this._transcript = builder.add_transcript(false); 18 | 19 | bridge.receiveObjectCallback = command => { 20 | this._transcribe(command); 21 | return Object.assign(command, {"confirm": "Secondary"}); 22 | }; 23 | 24 | buttonClose.addEventListener('click', () => this._send({ 25 | "command": "close"})); 26 | 27 | loading.firstChild.textContent = "Secondary"; 28 | 29 | this._send({"command": "ready"}); 30 | } 31 | 32 | _transcribe(message) { 33 | this._transcript.add(JSON.stringify(message, undefined, 4)); 34 | } 35 | 36 | _send(object_) { 37 | return ( 38 | this._bridge ? 39 | this._bridge.sendObject(object_) : 40 | Promise.reject(new Error("No Bridge!")) 41 | ).then(response => { 42 | this._transcribe(response); 43 | return response; 44 | }) 45 | .catch(error => { 46 | this._transcribe(error); 47 | return error; 48 | }); 49 | } 50 | } 51 | 52 | export default function(bridge) { 53 | new Secondary(bridge); 54 | return null; 55 | } 56 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSHumanReadableCopyright 6 | Copyright 2020 Omnissa, LLC. 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportsDocumentBrowser 38 | 39 | LSSupportsOpeningDocumentsInPlace 40 | 41 | UIFileSharingEnabled 42 | 43 | UISupportedInterfaceOrientations~ipad 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationPortraitUpsideDown 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/java/com/example/captivecrypto/storedkey/KeyInfoPurposeStrings.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | package com.example.captivecrypto.storedkey 5 | 6 | import android.security.keystore.KeyInfo 7 | import android.security.keystore.KeyProperties 8 | 9 | val KeyInfo.purposeStrings:List 10 | get() = KeyPurpose.purposeStrings(this) 11 | 12 | private enum class KeyPurpose(val mask:Int) { 13 | Encrypt(KeyProperties.PURPOSE_ENCRYPT), 14 | Decrypt(KeyProperties.PURPOSE_DECRYPT), 15 | Sign(KeyProperties.PURPOSE_SIGN), 16 | Verify(KeyProperties.PURPOSE_VERIFY); 17 | 18 | // Next purpose requires API 28 19 | // wrap(KeyProperties.PURPOSE_WRAP_KEY) 20 | 21 | companion object { 22 | // Utility function to take the `purposes` property bit map and turn 23 | // it into a list of strings. 24 | fun purposeStrings(keyInfo: KeyInfo):List { 25 | var mask:Int = 0 26 | val returning = mutableListOf() 27 | values().forEach { 28 | if (keyInfo.purposes and it.mask != 0) { 29 | mask = mask or it.mask 30 | returning.add(it.name) 31 | } 32 | } 33 | 34 | // Check if there's anything in the purposes map that isn't in 35 | // the purposesList. If there is, add an explanatory message to 36 | // the purposes strings. 37 | if (keyInfo.purposes != mask) { 38 | returning.add(listOf( 39 | "Unmatched info:", 40 | keyInfo.purposes.toString(2).padStart(8, '0') 41 | , " returning:", 42 | mask.toString(2).padStart(8, '0') 43 | ).joinToString("")) 44 | } 45 | 46 | return returning 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /harness/command_handler/base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Omnissa, LLC. 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | """\ 4 | Captive Web View python harness command handler base class and JSON handler. 5 | 6 | Import and use it like the ../../captivityHarness/__main__.py server does.""" 7 | # Standard library imports, in alphabetic order. 8 | # 9 | # JSON module. 10 | # https://docs.python.org/3/library/json.html 11 | import json 12 | # 13 | # Module for OO path handling. 14 | # https://docs.python.org/3/library/pathlib.html 15 | from pathlib import Path 16 | 17 | class CommandHandler: 18 | 19 | @staticmethod 20 | def parseCommandObject(commandObject): 21 | try: 22 | command = commandObject['command'] 23 | except KeyError: 24 | command = None 25 | 26 | try: 27 | parameters = commandObject['parameters'] 28 | except KeyError: 29 | parameters = None 30 | 31 | return command, parameters 32 | 33 | def __call__(self, commandObject, httpHandler): 34 | return None 35 | 36 | class JSONFileCommandHandler(CommandHandler): 37 | 38 | def __init__(self, pathSpecifier=None): 39 | path = ( 40 | Path() if pathSpecifier is None else Path(pathSpecifier)).resolve() 41 | self._path = path.parent.resolve() if path.is_file() else path 42 | super().__init__() 43 | 44 | # Override. 45 | def __call__(self, commandObject, httpHandler): 46 | command, _ = self.parseCommandObject(commandObject) 47 | 48 | if command is None: 49 | return None 50 | 51 | commandPath = Path(self._path, command).with_suffix(".json").resolve() 52 | if commandPath.exists(): 53 | httpHandler.log_message( 54 | "%s", f'Loading response from "{commandPath}".') 55 | with commandPath.open() as file: 56 | return json.load(file) 57 | 58 | httpHandler.log_message("%s", f'No response object "{commandPath}".') 59 | return None 60 | -------------------------------------------------------------------------------- /forApple/Captivity/Captivity/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /forApple/FetchTest/FetchTest/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /forApple/Headless/Headless/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /forApple/Skeleton/Skeleton/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /forApple/Headless/Headless/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSHumanReadableCopyright 6 | Copyright 2023 Omnissa, LLC. 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | NSAppTransportSecurity 47 | 48 | NSExceptionDomains 49 | 50 | swapi.dev 51 | 52 | NSExceptionAllowsInsecureHTTPLoads 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /forAndroid/captivewebview/src/main/java/com/example/captivewebview/FetchException.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | package com.example.captivewebview 5 | 6 | import org.json.JSONObject 7 | import java.net.SocketTimeoutException 8 | 9 | // Android Kotlin has many Exception subclasses but they don't seem to 10 | // have properties for details. So a custom Exception is used that has a 11 | // JSONObject for storing properties. 12 | // In case an error is encountered by one of the component functions of 13 | // builtInFetch they can throw an instance of the custom Exception and 14 | // add details to it. If the error is from an Exception thrown by 15 | // something that the component function called, that exception gets set 16 | // as the cause of the custom Exception. 17 | // 18 | // TOTH custom Exception: https://stackoverflow.com/a/68775013/7657675 19 | class FetchException( 20 | message: String? = null, cause: Throwable? = null 21 | ) : Exception(message, cause) { 22 | private val details = JSONObject() 23 | 24 | constructor(cause: Throwable) : this(null, cause) 25 | 26 | enum class Key { 27 | message, bytesTransferred 28 | } 29 | 30 | init { 31 | details.putOpt(Key.message, cause?.localizedMessage) 32 | .putOpt( 33 | Key.bytesTransferred.name, 34 | (cause as? SocketTimeoutException)?.bytesTransferred 35 | ) 36 | } 37 | 38 | fun put(vararg pairs: Pair) = 39 | pairs.forEach { 40 | details.put(it.first, it.second ?: JSONObject.NULL) 41 | }.let { this } 42 | 43 | fun toJSON(status: Int?): JSONObject = JSONObject() 44 | .put(FetchKey.ok, false) 45 | .putOpt(FetchKey.status, status) 46 | .putOpt( 47 | FetchKey.statusText, 48 | message ?: cause?.let { it::class.java.simpleName } 49 | ) 50 | .putOpt(FetchKey.headers, details) 51 | .put(FetchKey.text, JSONObject.NULL) 52 | .put(FetchKey.json, JSONObject.NULL) 53 | .put(FetchKey.peerCertificate, JSONObject.NULL) 54 | } 55 | -------------------------------------------------------------------------------- /forAndroid/Captivity/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /forAndroid/FetchTest/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /forAndroid/Headless/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /forAndroid/Skeleton/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/StoredKey/Error.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | 6 | // Swift seems to have made it rather difficult to create a throw-able that 7 | // has a message that can be retrieved in the catch. So, there's a custom 8 | // class here. 9 | // 10 | // Having created a custom class anyway, it seemed like a code-saver to pack 11 | // it with convenience initialisers for an array of strings, variadic 12 | // strings, CFString, and OSStatus. 13 | 14 | public class StoredKeyError: Error, CustomStringConvertible { 15 | let _message:String 16 | 17 | public init(_ message:String) { 18 | self._message = message 19 | } 20 | public convenience init(_ message:[String]) { 21 | self.init(message.joined()) 22 | } 23 | public convenience init(_ message:String...) { 24 | self.init(message) 25 | } 26 | public convenience init(_ message:CFString) { 27 | self.init(NSString(string: message) as String) 28 | } 29 | public convenience init(_ osStatus:OSStatus, _ details:String...) { 30 | self.init(details.inserting(osStatus.secErrorMessage, at: 0)) 31 | } 32 | 33 | public var message: String { 34 | return self._message 35 | } 36 | 37 | public var localizedDescription: String { 38 | return self._message 39 | } 40 | 41 | public var description: String { 42 | return self._message 43 | } 44 | } 45 | 46 | // Handy extension to get an error message from an OSStatus. 47 | public extension OSStatus { 48 | var secErrorMessage: String { 49 | return (SecCopyErrorMessageString(self, nil) as String?) ?? "\(self)" 50 | } 51 | } 52 | 53 | func check( 54 | _ itemRef:CFTypeRef?, from source:String, isTypeID typeID:CFTypeID) throws 55 | { 56 | guard CFGetTypeID(itemRef) == typeID else { 57 | let description 58 | = CFCopyTypeIDDescription(CFGetTypeID(itemRef)) as String 59 | let expected = CFCopyTypeIDDescription(typeID) as String 60 | throw StoredKeyError( 61 | "Unexpected type \(description) from \(source).", 62 | " Expected type is \(expected).") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /forApple/FetchTest/FetchTest/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import UIKit 5 | import CaptiveWebView 6 | 7 | @UIApplicationMain 8 | class AppDelegate: CaptiveWebView.ApplicationDelegate { 9 | 10 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 11 | 12 | self.launch(MainViewController.self) 13 | 14 | return true 15 | } 16 | 17 | func applicationWillResignActive(_ application: UIApplication) { 18 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 19 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 20 | } 21 | 22 | func applicationDidEnterBackground(_ application: UIApplication) { 23 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 24 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 25 | } 26 | 27 | func applicationWillEnterForeground(_ application: UIApplication) { 28 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 29 | } 30 | 31 | func applicationDidBecomeActive(_ application: UIApplication) { 32 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 33 | } 34 | 35 | func applicationWillTerminate(_ application: UIApplication) { 36 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 37 | } 38 | 39 | 40 | } 41 | 42 | -------------------------------------------------------------------------------- /forApple/Skeleton/Skeleton/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import UIKit 5 | import CaptiveWebView 6 | 7 | @UIApplicationMain 8 | class AppDelegate: CaptiveWebView.ApplicationDelegate { 9 | 10 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 11 | 12 | self.launch(MainViewController.self) 13 | 14 | return true 15 | } 16 | 17 | func applicationWillResignActive(_ application: UIApplication) { 18 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 19 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 20 | } 21 | 22 | func applicationDidEnterBackground(_ application: UIApplication) { 23 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 24 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 25 | } 26 | 27 | func applicationWillEnterForeground(_ application: UIApplication) { 28 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 29 | } 30 | 31 | func applicationDidBecomeActive(_ application: UIApplication) { 32 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 33 | } 34 | 35 | func applicationWillTerminate(_ application: UIApplication) { 36 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 37 | } 38 | 39 | 40 | } 41 | 42 | -------------------------------------------------------------------------------- /forApple/Captivity/Captivity/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /forApple/FetchTest/FetchTest/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /forApple/Headless/Headless/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /forApple/Skeleton/Skeleton/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /forApple/Headless/Headless/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import UIKit 5 | 6 | @UIApplicationMain 7 | class AppDelegate: UIResponder, UIApplicationDelegate { 8 | 9 | var window: UIWindow? 10 | 11 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 12 | // Override point for customization after application launch. 13 | return true 14 | } 15 | 16 | func applicationWillResignActive(_ application: UIApplication) { 17 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 18 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 19 | } 20 | 21 | func applicationDidEnterBackground(_ application: UIApplication) { 22 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 23 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 24 | } 25 | 26 | func applicationWillEnterForeground(_ application: UIApplication) { 27 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 28 | } 29 | 30 | func applicationDidBecomeActive(_ application: UIApplication) { 31 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 32 | } 33 | 34 | func applicationWillTerminate(_ application: UIApplication) { 35 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/StoredKey/keysWithName.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | import CryptoKit 6 | 7 | extension StoredKey { 8 | 9 | static func keysWithName(_ alias:String) throws -> [StoredKey] { 10 | return try Storage.allCases.flatMap {storage -> [StoredKey] in 11 | let query = [ 12 | kSecClass: storage.secClass, 13 | kSecAttrLabel: alias, 14 | kSecMatchLimit: kSecMatchLimitAll, 15 | storage.kSecReturn: true 16 | ] as CFDictionary 17 | 18 | var result: CFTypeRef? 19 | let status = SecItemCopyMatching(query, &result) 20 | 21 | // Set items to an NSArray of the return value, or an empty NSArray 22 | // in case of errSecItemNotFound. 23 | let items:NSArray 24 | if status == errSecSuccess { 25 | guard let nsArray = result as? NSArray else { 26 | throw StoredKeyError( 27 | "Couldn't cast result \(String(describing: result)) to", 28 | " NSArray. Result was returned by", 29 | "SecItemCopyMatching(\(query),)." 30 | ) 31 | } 32 | items = nsArray 33 | } 34 | else if status == errSecItemNotFound { items = NSArray() } 35 | else { throw StoredKeyError( 36 | status, " Returned by SecItemCopyMatching(\(query),).") } 37 | 38 | return try items.map({item in 39 | switch(storage) { 40 | case .generic: 41 | guard let data = item as? Data else { throw StoredKeyError( 42 | "Couldn't initialise StoredKey SymmetricKey with name", 43 | " \"\(alias)\".", 44 | " Couldn't cast \(String(describing: item)) to Data.", 45 | " Item was returned in SecItemCopyMatching(\(query),)", 46 | " array." 47 | ) } 48 | return StoredKey(SymmetricKey(data: data)) 49 | 50 | case .key: return StoredKey(item as! SecKey) 51 | // Jim couldn't find a way to do that other than as! cast. 52 | } 53 | }) 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /WebResources/CaptiveCrypto/UserInterface/keystore.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Omnissa, LLC. 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | */ 4 | 5 | /* Style names make use of the Block Element Modifier convention, see: 6 | * https://en.bem.info/methodology/naming-convention/ 7 | */ 8 | 9 | .kst__key-store-message { 10 | display: none; 11 | } 12 | .kst__key-store-message_empty { 13 | border-bottom: var(--border); 14 | margin-bottom: 0.5em; 15 | padding-bottom: 0.5em; 16 | display: inherit; 17 | text-align: center; 18 | font-weight: bold; 19 | } 20 | 21 | .kst__key-label { 22 | padding-right: 0.25em; 23 | } 24 | 25 | .kst__key-alias { 26 | display: inline-block; 27 | } 28 | 29 | .kst__key-controls { 30 | display: inherit; 31 | } 32 | .kst__key-controls_collapsed { 33 | display: none; 34 | } 35 | 36 | .kst__key-controls .cwv-input { 37 | display: inline-block; 38 | } 39 | 40 | .kst__button-panel { 41 | padding-left: 0.5em; 42 | padding-right: 0.5em; 43 | display: flex; 44 | flex-wrap: wrap; 45 | justify-content: start; 46 | } 47 | 48 | .kst__button-panel > * { 49 | flex: none; 50 | margin-top: 0.5em; 51 | margin-right: 0.5em; 52 | } 53 | 54 | .kst__button-panel > .cwv-button + .cwv-button { 55 | margin-left: 0px; 56 | } 57 | 58 | /* These buttons get a chevron prefix. There are a number of candidates for the 59 | * chevron character. 60 | * 61 | * - Canadian syllabics Po: "\1433" 62 | * Looks nice but formally isn't a direction indicator. Bit big. 63 | * - Arrowhead: "\02C3" 64 | * Small, and isn't centred so it looks funny when it rotates. 65 | * - Another arrowhead: "\02F2" 66 | * Small, and isn't centred so it looks funny when it rotates. 67 | * - Angle quote: "\203A" 68 | * Small but could be boosted with a scale() transform for example. Seems to 69 | * be smaller on iOS though. 70 | * - Plain greater than: ">" 71 | */ 72 | .kst__key-button::before { 73 | content: ">"; 74 | margin-right: 0.5em; 75 | /* TOTH for how to rotate a pseudo-element: 76 | * https://stackoverflow.com/a/9782047/7657675 77 | */ 78 | display: inline-block; 79 | transform: rotate(90deg); 80 | transition: transform 0.25s linear; 81 | } 82 | .kst__key-button_collapsed::before { 83 | transform: rotate(0deg); 84 | } 85 | 86 | textarea { 87 | font-family: monospace; 88 | background-color: var(--background-colour); 89 | color: var(--line-colour); 90 | } -------------------------------------------------------------------------------- /noticeChecker/overwrite.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Omnissa, LLC. 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | # Run with Python 3.9 or later. 5 | """File in the noticeChecker module.""" 6 | # 7 | # Standard library imports, in alphabetic order. 8 | # 9 | # Sequence comparison module. 10 | # https://docs.python.org/3/library/difflib.html#difflib.context_diff 11 | from difflib import context_diff 12 | # 13 | # Module for file and directory handling. 14 | # https://docs.python.org/3.5/library/shutil.html 15 | import shutil 16 | 17 | class Overwrite: 18 | 19 | def __init__(self, automaticResponse=None): 20 | self._automaticResponse = automaticResponse 21 | 22 | def prompt(self, originalPath, editedPath): 23 | if self._automaticResponse is not None: 24 | if self._automaticResponse: shutil.copy(editedPath, originalPath) 25 | return self._automaticResponse 26 | 27 | with ( 28 | originalPath.open('r') as originalFile, 29 | editedPath.open('r') as editedFile 30 | ): 31 | diff = "".join(context_diff( 32 | originalFile.readlines(), editedFile.readlines(), 33 | fromfile=str(originalPath), tofile="Edited" 34 | )) 35 | 36 | print() 37 | if diff.strip() == "": #return False 38 | raise RuntimeError("Editor made no changes.", originalPath) 39 | print(diff) 40 | 41 | while True: 42 | response = input(' Overwrite? (Y/y*/n/n*/?)').lower() 43 | if response == "" or response.startswith("y"): 44 | print('Overwriting.') 45 | shutil.copy(editedPath, originalPath) 46 | if response.endswith("*"): 47 | self._automaticResponse = True 48 | return True 49 | elif response.startswith("n"): 50 | print('Keeping') 51 | if response.endswith("*"): 52 | self._automaticResponse = False 53 | return False 54 | elif response == "?": 55 | print(diff) 56 | print() 57 | print("y to overwrite, the default.") 58 | print("n to keep and not overwrite.") 59 | print("Append * to make that response to all future prompts.") 60 | print("Ctrl-c to quit.") 61 | print() 62 | else: 63 | print( 64 | f'Unrecognised "{response}". Ctrl-C to quit or ? for help.') 65 | -------------------------------------------------------------------------------- /Sources/CaptiveWebView/ViewController/ViewController+handleCommand.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | 6 | private enum KEY: String { 7 | // Common keys. 8 | case command, confirm, failed, secure 9 | } 10 | 11 | // Convenience extension to facilitate use of the KEY enumeration as keys in a 12 | // dictionary. TOTH for the setter: 13 | // https://www.avanderlee.com/swift/custom-subscripts/#making-a-read-and-write-subscript 14 | extension Dictionary where Key == String { 15 | fileprivate subscript(_ key:KEY) -> Value? { 16 | get { 17 | self[key.rawValue] 18 | } 19 | set { 20 | self[key.rawValue] = newValue 21 | } 22 | } 23 | } 24 | 25 | // Clunky but can be used to create a dictionary with String keys from a 26 | // dictionary literal with KEY keys. 27 | extension Dictionary where Key == KEY { 28 | func withStringKeys() -> [String: Value] { 29 | return Dictionary(uniqueKeysWithValues: self.map { 30 | ($0.rawValue, $1) 31 | }) 32 | } 33 | } 34 | 35 | extension CaptiveWebView.ViewController { 36 | static func handleCommand( 37 | _ viewController: CaptiveWebView.ViewController, 38 | _ command: Dictionary 39 | ) -> Dictionary 40 | { 41 | var returning = command as [String:Any?] 42 | do { 43 | let commandAny:Any = command[.command] ?? "" 44 | guard let commandString = commandAny as? String else { 45 | throw CaptiveWebViewError( 46 | "Command isn't String: " + String(describing: commandAny)) 47 | } 48 | 49 | let responded = try viewController.response(to:commandString, 50 | in:command) 51 | returning.merge(responded) {(_, new) in new} 52 | // Confirmation message starts with the class name of self. 53 | // TOTH: https://stackoverflow.com/a/34878224 54 | returning[.confirm] = 55 | String(describing: type(of: viewController)) + " bridge OK." 56 | returning[.secure] = viewController.webView.hasOnlySecureContent 57 | } 58 | catch let error as CaptiveWebViewError { 59 | returning[.failed] = error.localizedDescription 60 | } 61 | catch { 62 | returning[.failed] = error.localizedDescription 63 | } 64 | return returning 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /forAndroid/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /WebResources/Captivity/UserInterface/speechcontrols.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | // 4 | // Original speech synthesis code copied from this article and the linked 5 | // repository: 6 | // https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis 7 | // License is: CC0 1.0 Universal. 8 | // 9 | // The repository is licensed under Creative Commons Zero v1.0 Universal and so 10 | // unencumbered by copyright, see: 11 | // https://github.com/mdn/web-speech-api/blob/master/LICENSE 12 | 13 | import PageBuilder from "./pagebuilder.js"; 14 | import Speech from "./speech.js"; 15 | 16 | class SpeechControls { 17 | constructor(bridge) { 18 | this._bridge = bridge; 19 | this._speech = new Speech() 20 | } 21 | 22 | load(rootID) { 23 | const root = document.getElementById(rootID); 24 | if (root === null) { 25 | return; 26 | } 27 | const builder = new PageBuilder(root); 28 | // const form = builder.add_node('form'); 29 | const say = builder.add_input('input-text', "Say:"); 30 | 31 | this._voiceSelect = new PageBuilder(builder.add_node('select')); 32 | 33 | const speakButton = builder.add_button("Speak"); 34 | speakButton.setAttribute('disabled', true); 35 | speakButton.addEventListener('click', () => { 36 | const voiceIndex = this._voiceSelect.node.selectedIndex; 37 | this._speech.speak(say.value, voiceIndex); 38 | }); 39 | 40 | this._speech.initialise(speech => { 41 | this._voiceSelect.remove_childs(); 42 | speech.voices.forEach(voice => { 43 | // console.log(voice.lang, voice.default, voice.name); 44 | this._voiceSelect.add_node( 45 | 'option', `${voice.name} (${voice.lang})`); 46 | }); 47 | speakButton.removeAttribute('disabled'); 48 | }); 49 | 50 | // var inputForm = document.querySelector('form'); 51 | // var inputTxt = document.querySelector('.txt'); 52 | // // var voiceSelect = document.querySelector('select'); 53 | 54 | // var pitch = document.querySelector('#pitch'); 55 | // var pitchValue = document.querySelector('.pitch-value'); 56 | // var rate = document.querySelector('#rate'); 57 | // var rateValue = document.querySelector('.rate-value'); 58 | } 59 | 60 | } 61 | 62 | export default function(bridge) { 63 | const speechControls = new SpeechControls(bridge); 64 | speechControls.load("user-interface"); 65 | return speechControls; 66 | } 67 | -------------------------------------------------------------------------------- /Sources/CaptiveWebView/ViewController/ViewController.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | import WebKit 6 | import os.log 7 | 8 | extension CaptiveWebView.ViewController { 9 | 10 | internal static let nameSuffix = "ViewController" 11 | 12 | // It seems that the first time a WKWebView is loaded, it will appear 13 | // as a white rectangle. The appearance can be very brief, like a white 14 | // flash. That's a problem in dark mode because the appearance before 15 | // and after will be of a black screen. The fix is to hide the web view 16 | // for half a second. 17 | // The hiding takes place in the loadView, above, for example. The showing 18 | // is scheduled from here, in the navigation didCommit callback. That's 19 | // actually the only reason this ViewController subclass is also a 20 | // WKNavigationDelegate. 21 | public func webView( 22 | _ webView: WKWebView, didCommit navigation: WKNavigation! 23 | ) { 24 | webView.isHidden = true 25 | if let interval = loadVisibilityTimeOutSeconds { 26 | Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { 27 | (timer:Timer) in webView.isHidden = false 28 | } 29 | } 30 | else { 31 | webView.isHidden = false 32 | } 33 | } 34 | 35 | public func loadCustom(scheme:String = CaptiveWebView.scheme, 36 | file:String = "index.html") -> URL 37 | { 38 | return CaptiveWebView.load(in: webView, scheme:scheme, file:file) 39 | } 40 | 41 | public func loadMainHTML() -> URL { 42 | // Next line has a reference to the mainHTML property, which is declared 43 | // in the CaptiveWebView.swift file, in the class placeholder. It can't 44 | // be declared here in the extension. 45 | return self.loadCustom(file: mainHTML) 46 | } 47 | 48 | static func mainHTML(from viewController:WebViewController) -> String { 49 | var subClass = String(describing:type(of: viewController)) 50 | if subClass.hasSuffix(CaptiveWebView.ViewController.nameSuffix) { 51 | subClass.removeLast( 52 | CaptiveWebView.ViewController.nameSuffix.count) 53 | } 54 | subClass.append(".html") 55 | return subClass 56 | } 57 | 58 | public func sendObject( 59 | _ command:Dictionary, 60 | _ completionHandler:((Any?, Error?) -> Void)? = nil) 61 | { 62 | CaptiveWebView.sendObject(to: webView, command, completionHandler) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Captive Web View 2 | The Captive Web View project team welcomes contributions from the community. 3 | 4 | ## Contribution Flow 5 | This is a rough outline of what a contributor's workflow looks like: 6 | 7 | - Create a topic branch from where you want to base your work 8 | - Make commits of logical units 9 | - Make sure your commit messages are in the proper format (see below) 10 | - Push your changes to a topic branch in your fork of the repository 11 | - Submit a pull request 12 | 13 | Example: 14 | 15 | ``` shell 16 | git remote add upstream https://github.com/omnissa-archive/captive-web-view.git 17 | git checkout -b my-new-feature master 18 | git commit -a 19 | git push origin my-new-feature 20 | ``` 21 | 22 | ### Staying In Sync With Upstream 23 | 24 | When your branch gets out of sync with the omnissa/master branch, use the following to update: 25 | 26 | ``` shell 27 | git checkout my-new-feature 28 | git fetch -a 29 | git pull --rebase upstream master 30 | git push --force-with-lease origin my-new-feature 31 | ``` 32 | 33 | ### Updating pull requests 34 | 35 | If your PR fails to pass CI or needs changes based on code review, you'll most likely want to squash these changes into 36 | existing commits. 37 | 38 | If your pull request contains a single commit or your changes are related to the most recent commit, you can simply 39 | amend the commit. 40 | 41 | ``` shell 42 | git add . 43 | git commit --amend 44 | git push --force-with-lease origin my-new-feature 45 | ``` 46 | 47 | If you need to squash changes into an earlier commit, you can use: 48 | 49 | ``` shell 50 | git add . 51 | git commit --fixup 52 | git rebase -i --autosquash master 53 | git push --force-with-lease origin my-new-feature 54 | ``` 55 | 56 | Be sure to add a comment to the PR indicating your new changes are ready to review, as GitHub does not generate a 57 | notification when you git push. 58 | 59 | ### Code Style 60 | The main code of Captive Web View is written in Kotlin, Swift, and ES6 61 | JavaScript. There is also an HTTP server, based on the version 3 Python module. 62 | The Python code makes use of the PEP 8 style guidelines. 63 | 64 | ### Formatting Commit Messages 65 | 66 | We follow the conventions on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/). 67 | 68 | Be sure to include any related GitHub issue references in the commit message. See 69 | [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) for referencing issues 70 | and commits. 71 | 72 | ## Reporting Bugs and Creating Issues 73 | 74 | When opening a new issue, try to roughly follow the commit message format conventions above. 75 | -------------------------------------------------------------------------------- /forApple/Captivity/Captivity/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import UIKit 5 | import CaptiveWebView 6 | 7 | @UIApplicationMain 8 | class AppDelegate: CaptiveWebView.ApplicationDelegate { 9 | 10 | // var window: UIWindow? 11 | 12 | 13 | func application( 14 | _ application: UIApplication, 15 | didFinishLaunchingWithOptions launchOptions: 16 | [UIApplication.LaunchOptionsKey: Any]? 17 | ) -> Bool 18 | { 19 | CaptiveWebView.DefaultViewController 20 | .viewControllerMap.merge([ 21 | "Secondary": SecondaryViewController.self, 22 | "Spinner": SpinnerViewController.self 23 | ], uniquingKeysWith: {(first, _) in first}) 24 | self.launch(MainViewController.self) 25 | // Override point for customization after application launch. 26 | return true 27 | } 28 | 29 | func applicationWillResignActive(_ application: UIApplication) { 30 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 31 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 32 | } 33 | 34 | func applicationDidEnterBackground(_ application: UIApplication) { 35 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 36 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 37 | } 38 | 39 | func applicationWillEnterForeground(_ application: UIApplication) { 40 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 41 | } 42 | 43 | func applicationDidBecomeActive(_ application: UIApplication) { 44 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 45 | } 46 | 47 | func applicationWillTerminate(_ application: UIApplication) { 48 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /forAndroid/captivewebview/build.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | plugins { 5 | id 'com.android.library' 6 | id 'kotlin-android' 7 | id 'maven-publish' 8 | 9 | // The dokka plugin requires a version or it won't be found. 10 | // Adds the documentation/dokka task. 11 | // id 'org.jetbrains.dokka' version '0.9.18' 12 | id 'org.jetbrains.dokka-android' version '0.9.18' 13 | } 14 | 15 | android { 16 | namespace 'com.example.captivewebview' 17 | compileSdk 33 18 | 19 | sourceSets { 20 | main.assets.srcDirs += new RelativePath( 21 | false, "Sources", "CaptiveWebView", "Resources" 22 | ).getFile(buildscript.sourceFile.parentFile.parentFile.parentFile) 23 | } 24 | 25 | defaultConfig { 26 | minSdk 29 27 | targetSdk 33 28 | versionCode 1 29 | versionName captiveWebViewVersion 30 | } 31 | 32 | buildTypes { 33 | release { 34 | minifyEnabled false 35 | proguardFiles getDefaultProguardFile( 36 | 'proguard-android-optimize.txt'), 'proguard-rules.pro' 37 | } 38 | } 39 | 40 | } 41 | 42 | dependencies { 43 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 44 | implementation 'androidx.webkit:webkit:1.7.0' 45 | } 46 | 47 | // Reference for Maven publication of Android library. 48 | // https://developer.android.com/studio/publish-library/upload-library 49 | // 50 | // The `publish` Gradle task seems to do what the old `uploadArchives` task did. 51 | publishing { 52 | publications { 53 | release(MavenPublication) { 54 | groupId = 'com.example.captivewebview' 55 | artifactId = 'captivewebview' 56 | version = captiveWebViewVersion 57 | afterEvaluate { 58 | from components.release 59 | } 60 | } 61 | } 62 | repositories { 63 | maven { 64 | url = uri(new File(rootDir, '../m2repository')) 65 | } 66 | } 67 | } 68 | 69 | // https://www.kotlinresources.com/library/dokka/ 70 | // https://github.com/Kotlin/dokka/issues/224#issuecomment-383886215 71 | 72 | task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaAndroidTask) { 73 | outputFormat = 'javadoc' 74 | outputDirectory = "$buildDir/dokkaJavadoc" 75 | includes = ['src/documentation/readme.md', 'src/documentation/extra.md'] 76 | } 77 | 78 | task dokkaHTML(type: org.jetbrains.dokka.gradle.DokkaAndroidTask) { 79 | outputFormat = 'html' 80 | outputDirectory = "$buildDir/dokkaHTML" 81 | includes = ['src/documentation/readme.md', 'src/documentation/extra.md'] 82 | } 83 | -------------------------------------------------------------------------------- /WebResources/Captivity/UserInterface/embeddedSVG.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 36 | 37 | 38 |

Embedded SVG Images

The image is from the Material Design icon set:
https://material.io/tools/icons/?icon=visibility&style=baseline

Back to Captivity 63 | -------------------------------------------------------------------------------- /WebResources/Captivity/UserInterface/three.html: -------------------------------------------------------------------------------- 1 | 2 | 32 | 33 | 34 | My first three.js app 35 | 41 | 42 | 43 | 44 | 70 | Back to Captivity 73 | 74 | -------------------------------------------------------------------------------- /forAndroid/CaptiveCrypto/src/main/java/com/example/captivecrypto/storedkey/StoredKey.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | package com.example.captivecrypto.storedkey 5 | 6 | import android.content.Context 7 | import android.os.Build 8 | import android.security.keystore.KeyProperties 9 | import java.security.Key 10 | import java.security.KeyStore 11 | import java.text.SimpleDateFormat 12 | import java.util.* 13 | 14 | // Single API object for convenience import. 15 | object StoredKey { 16 | fun capabilities(context: Context) = deviceCapabilities(context) 17 | fun describeAll() = describeAllStoredKeys() 18 | fun describeAll(providerName: String) = describeAllStoredKeys(providerName) 19 | fun deleteAll() = deleteAllStoredKeys() 20 | fun deleteAll(providerName: String) = deleteAllStoredKeys(providerName) 21 | fun generateKeyNamed(alias: String) = generateStoredKeyNamed(alias) 22 | fun generateKeyPairNamed(alias: String) = generateStoredKeyPairNamed(alias) 23 | fun describeKeyNamed(alias: String) = describeStoredKeyNamed(alias) 24 | fun encipherWithKeyNamed(plaintext:String, alias: String) = 25 | encipherWithStoredKey(plaintext, alias) 26 | fun decipherWithKeyNamed(ciphertext:EncipheredMessage, alias: String) = 27 | decipherWithStoredKey(ciphertext, alias) 28 | } 29 | // It'd be nicer to have single functions with optional parameters for 30 | // `providerName` but that seemed to generate errors like this. 31 | // 32 | // java.lang.NoSuchMethodError: No virtual method ... 33 | // 34 | 35 | // Common code used by more than one file in the package. 36 | 37 | enum class KEY { 38 | AndroidKeyStore; 39 | } 40 | 41 | fun loadKeyStore(name: String): KeyStore = 42 | KeyStore.getInstance(name).apply { load(null) } 43 | 44 | fun cipherSpecifier(key: Key): String = when (key.algorithm) { 45 | // For the "AES/CBC/PKCS5PADDING" magic, TOTH: 46 | // https://developer.android.com/guide/topics/security/cryptography#encrypt-message 47 | // KeyProperties.KEY_ALGORITHM_AES -> "AES/CBC/PKCS5PADDING" 48 | KeyProperties.KEY_ALGORITHM_AES -> "AES/GCM/NoPADDING" 49 | 50 | KeyProperties.KEY_ALGORITHM_RSA -> "RSA/ECB/OAEPPadding" 51 | 52 | else -> key.algorithm 53 | } 54 | 55 | fun unavailableMessage(requiredVersion: Int) = listOf( 56 | "Unavailable at build version ${Build.VERSION.SDK_INT}.", 57 | "Minimum build version ${requiredVersion}." 58 | ).joinToString(" ") 59 | 60 | fun formattedDate(date: Date, withZone:Boolean) = 61 | listOf("dd", "MMM", "yyyy HH:MM", " z") 62 | .filter { withZone || it.trim() != "z"} 63 | .map { SimpleDateFormat(it, Locale.getDefault()) } 64 | .map { it.format(date).run { 65 | if (it.toPattern() == "MMM") lowercase(Locale.getDefault()) 66 | else this 67 | } } 68 | .joinToString("") 69 | -------------------------------------------------------------------------------- /forApple/CaptiveCrypto/CaptiveCrypto/StoredKey/Decipher.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | import CryptoKit 6 | 7 | extension StoredKey { 8 | // Instance methods. 9 | func decipher(_ enciphered:Data) throws -> String { 10 | switch _storage { 11 | case .key: 12 | return try decipherWithPrivateKey(enciphered as CFData) 13 | case .generic: 14 | return try decipherWithSymmetricKey(enciphered) 15 | } 16 | } 17 | func decipher(_ enciphered:Enciphered) throws -> String { 18 | return try decipher(enciphered.message) 19 | } 20 | 21 | private func decipherWithSymmetricKey(_ enciphered:Data) throws -> String { 22 | let sealed = try AES.GCM.SealedBox(combined: enciphered) 23 | guard let key = symmetricKey else { throw StoredKeyError( 24 | "StoredKey instance isn't a symmetric key.") 25 | } 26 | let decipheredData = try AES.GCM.open(sealed, using: key) 27 | let message = 28 | String(data: decipheredData, encoding: .utf8) ?? "\(decipheredData)" 29 | return message 30 | } 31 | 32 | private func decipherWithPrivateKey(_ enciphered:CFData) throws -> String { 33 | guard let privateKey = secKey else { throw StoredKeyError( 34 | "StoredKey instance isn't a key pair.") 35 | } 36 | guard let publicKey = SecKeyCopyPublicKey(privateKey) else { 37 | throw StoredKeyError("No public key.") 38 | } 39 | guard let algorithm = StoredKey.algorithms.first( 40 | where: { SecKeyIsAlgorithmSupported(publicKey, .encrypt, $0)} 41 | ) else 42 | { 43 | throw StoredKeyError("No algorithms supported.") 44 | } 45 | 46 | var error: Unmanaged? 47 | guard let decipheredBytes = SecKeyCreateDecryptedData( 48 | privateKey, algorithm, enciphered, &error) else { 49 | throw error?.takeRetainedValue() as? Error ?? StoredKeyError( 50 | "SecKeyCreateDecryptedData(\(privateKey),", 51 | " \(algorithm), \(enciphered),)", 52 | " returned null and set error \(String(describing: error)).") 53 | } 54 | 55 | let message = String( 56 | data: decipheredBytes as Data, encoding: .utf8) 57 | ?? "\(decipheredBytes)" 58 | return message 59 | } 60 | 61 | // Static methods that work with a key alias instead of a StoredKey 62 | // instance. 63 | static func decipher( 64 | _ enciphered:Enciphered, withFirstKeyNamed alias:String 65 | ) throws -> String 66 | { 67 | guard let key = try keysWithName(alias).first else { 68 | throw StoredKeyError(errSecItemNotFound) 69 | } 70 | return try key.decipher(enciphered) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/CaptiveWebView/CaptiveWebView/Constrain.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Omnissa, LLC. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | import Foundation 5 | import WebKit 6 | 7 | extension CaptiveWebView { 8 | #if os(macOS) 9 | 10 | // macOS version, takes two NSView parameters. 11 | public static func constrain( 12 | view left: NSView, to right: NSView, leftSide:Bool = false 13 | ) { 14 | left.translatesAutoresizingMaskIntoConstraints = false 15 | left.topAnchor.constraint( 16 | equalTo: right.topAnchor).isActive = true 17 | left.bottomAnchor.constraint( 18 | equalTo: right.bottomAnchor).isActive = true 19 | left.leftAnchor.constraint( 20 | equalTo: right.leftAnchor).isActive = true 21 | left.rightAnchor.constraint( 22 | equalTo: leftSide ? right.centerXAnchor : right.rightAnchor 23 | ).isActive = true 24 | } 25 | // TOTH: 26 | // https://github.com/dasher-project/redash/blob/master/Keyboard/foriOS/DasherApp/Keyboard/KeyboardViewController.swift#L129 27 | 28 | #else 29 | 30 | // iOS version, takes either of the following: 31 | // 32 | // - Two UIView parameters. 33 | // - One UIView and one UILayoutGuide. 34 | // 35 | // The UIView.safeAreaLayoutGuide property is a UILayoutGuide. 36 | public static func constrain( 37 | view left: UIView, to right: UIView, leftHalf:Bool = false 38 | ) { 39 | setAnchors(of: left, 40 | top: right.topAnchor, 41 | left: right.leftAnchor, 42 | bottom: right.bottomAnchor, 43 | right: leftHalf ? right.centerXAnchor : right.rightAnchor) 44 | } 45 | 46 | public static func constrain( 47 | view: UIView, to guide: UILayoutGuide, leftHalf:Bool = false 48 | ) { 49 | setAnchors(of: view, 50 | top: guide.topAnchor, 51 | left: guide.leftAnchor, 52 | bottom: guide.bottomAnchor, 53 | right: leftHalf ? guide.centerXAnchor : guide.rightAnchor) 54 | } 55 | 56 | public static func setAnchors( 57 | of view: UIView, 58 | top: NSLayoutYAxisAnchor, 59 | left:NSLayoutXAxisAnchor, 60 | bottom: NSLayoutYAxisAnchor, 61 | right: NSLayoutXAxisAnchor 62 | ) { 63 | view.translatesAutoresizingMaskIntoConstraints = false 64 | view.topAnchor.constraint(equalTo: top).isActive = true 65 | view.leftAnchor.constraint(equalTo: left).isActive = true 66 | view.bottomAnchor.constraint(equalTo: bottom).isActive = true 67 | view.rightAnchor.constraint(equalTo: right).isActive = true 68 | } 69 | // TOTH: 70 | // https://github.com/dasher-project/redash/blob/master/Keyboard/foriOS/DasherApp/Keyboard/KeyboardViewController.swift#L129 71 | #endif 72 | 73 | } 74 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Captive Web View 2 | This repository holds the Captive Web View library code and sample applications. 3 | 4 | # Overview 5 | The Captive Web View library facilitates use of web technologies in mobile 6 | applications. It has the following features. 7 | 8 | - Web technologies support. 9 | 10 | The library facilitates use of Web View controls as the container for any of 11 | the following. 12 | 13 | - Whole application. 14 | - Whole user interface. 15 | - Part of user interface. 16 | - Headless application code. 17 | 18 | The user interface, and any other code running in a Web View, would be 19 | written in HTML5, CSS, and JavaScript. 20 | 21 | The Android and iOS versions of a Captive Web View application can share the 22 | same HTML5, CSS, and JavaScript code. 23 | 24 | - Object bridge. 25 | 26 | The library implements a simple bridge between JavaScript, running in the 27 | Web View, and Kotlin or Swift code, running natively. 28 | 29 | The bridge can be invoked from either the native end or the JavaScript end, 30 | and supports responses. 31 | 32 | The JavaScript ends of the bridge interface use JavaScript objects. The 33 | native ends use either JSONObject, for Android, or Dictionary, for iOS. 34 | 35 | - Native user interface division. 36 | 37 | The library can be used with applications that divide their user interface 38 | into multiple Activity or ViewController classes. A different HTML file can 39 | be associated with each native class. 40 | 41 | - Modern standards from built-in controls. 42 | 43 | The library utilises the built-in WebView, for Android, and WKWebView, for 44 | iOS. These classes support the latest web standards, such as HTML5 and ES6 45 | JavaScript. Support is maintained by the respective developer teams, i.e. 46 | the Chromium and WebKit projects. 47 | 48 | The library for Android is written in Kotlin; the library for iOS is written in 49 | Swift. There is also a small amount of JavaScript code in the library. 50 | 51 | Captive Web View can be seen as a simple version of platforms like Apache 52 | Cordova and Electron. 53 | 54 | # Usage 55 | - For Android, see the [forAndroid sub-directory](/forAndroid/). 56 | - For iOS, Catalyst, and native macOS, see the 57 | [forApple sub-directory](/forApple/). 58 | 59 | # Learn More 60 | - Reference documentation is in the 61 | [documentation/reference.md](documentation/reference.md) file. 62 | 63 | ## Contributing 64 | The Captive Web View project team welcomes contributions from the community. 65 | For more detailed information, refer to the 66 | [contributing.md](contributing.md) file. 67 | 68 | Check the [documentation/backlog.md](documentation/backlog.md) file for a list 69 | of work to be done. 70 | 71 | License 72 | ======= 73 | Captive Web View, is: 74 | Copyright 2020 Omnissa, LLC. 75 | And licensed under a two-clause BSD license. 76 | SPDX-License-Identifier: BSD-2-Clause 77 | --------------------------------------------------------------------------------