@livx.cc/appwrap 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +20 -0
- package/package.json +46 -0
- package/runtime/App_Resources/Android/app.gradle +34 -0
- package/runtime/App_Resources/Android/before-plugins.gradle +15 -0
- package/runtime/App_Resources/Android/src/main/AndroidManifest.xml +76 -0
- package/runtime/App_Resources/Android/src/main/res/drawable/ic_launcher_foreground.xml +15 -0
- package/runtime/App_Resources/Android/src/main/res/drawable-hdpi/background.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/drawable-hdpi/logo.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/drawable-ldpi/background.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/drawable-ldpi/logo.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/drawable-mdpi/background.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/drawable-mdpi/logo.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml +8 -0
- package/runtime/App_Resources/Android/src/main/res/drawable-xhdpi/background.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
- package/runtime/App_Resources/Android/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/runtime/App_Resources/Android/src/main/res/values/colors.xml +14 -0
- package/runtime/App_Resources/Android/src/main/res/values/ic_launcher_background.xml +4 -0
- package/runtime/App_Resources/Android/src/main/res/values/styles.xml +42 -0
- package/runtime/App_Resources/Android/src/main/res/values-v21/colors.xml +5 -0
- package/runtime/App_Resources/Android/src/main/res/values-v21/styles.xml +32 -0
- package/runtime/App_Resources/Android/src/main/res/values-v29/styles.xml +18 -0
- package/runtime/App_Resources/Android/src/main/res/xml/file_paths.xml +5 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/Contents.json +6 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json +23 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@3x.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json +23 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png +0 -0
- package/runtime/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@3x.png +0 -0
- package/runtime/App_Resources/iOS/Info.plist +60 -0
- package/runtime/App_Resources/iOS/LaunchScreen.storyboard +53 -0
- package/runtime/App_Resources/iOS/Podfile +13 -0
- package/runtime/App_Resources/iOS/PrivacyInfo.xcprivacy +59 -0
- package/runtime/App_Resources/iOS/build.xcconfig +7 -0
- package/runtime/app/app.ts +58 -0
- package/runtime/app/main-page.ts +116 -0
- package/runtime/app/main-page.xml +10 -0
- package/runtime/app/shell/active-modules.generated.ts +9 -0
- package/runtime/app/shell/android-helpers.ts +76 -0
- package/runtime/app/shell/bridge.ts +99 -0
- package/runtime/app/shell/capabilities.manifest.ts +207 -0
- package/runtime/app/shell/config.ts +35 -0
- package/runtime/app/shell/custom-webview.android.ts +270 -0
- package/runtime/app/shell/custom-webview.ios.ts +258 -0
- package/runtime/app/shell/custom-webview.ts +17 -0
- package/runtime/app/shell/env.ts +81 -0
- package/runtime/app/shell/events.ts +100 -0
- package/runtime/app/shell/fcm-bootstrap.generated.ts +1 -0
- package/runtime/app/shell/fcm-firebase.d.ts +16 -0
- package/runtime/app/shell/fcm-service.android.ts +40 -0
- package/runtime/app/shell/fcm-service.ios.ts +3 -0
- package/runtime/app/shell/handlers-android.ts +417 -0
- package/runtime/app/shell/handlers-billing.ts +197 -0
- package/runtime/app/shell/handlers-extended.ts +405 -0
- package/runtime/app/shell/handlers-health.ts +134 -0
- package/runtime/app/shell/handlers-media.ts +126 -0
- package/runtime/app/shell/handlers-oauth.ts +69 -0
- package/runtime/app/shell/handlers-parity.ts +257 -0
- package/runtime/app/shell/handlers-push.ts +189 -0
- package/runtime/app/shell/handlers-system.ts +70 -0
- package/runtime/app/shell/handlers.ts +149 -0
- package/runtime/app/shell/ios-frameworks.d.ts +36 -0
- package/runtime/app/shell/ios-image.ts +15 -0
- package/runtime/app/shell/ios-ui-delegate.ts +131 -0
- package/runtime/app/shell/mime.ts +29 -0
- package/runtime/app/shell/native-log.ts +22 -0
- package/runtime/app/shell/optional-handlers.generated.ts +10 -0
- package/runtime/app/shell/orientation.ts +43 -0
- package/runtime/app/shell/status-bar.ts +129 -0
- package/runtime/app/shell/toast.ts +67 -0
- package/runtime/app/shell/web-quirks.ts +131 -0
- package/runtime/modules-native/health/App_Resources/Android/src/main/java/cc/livx/appwrap/HealthConnectBridge.kt +74 -0
- package/runtime/nativescript.config.ts +15 -0
- package/runtime/package.json +21 -0
- package/runtime/references.d.ts +2 -0
- package/runtime/tsconfig.json +19 -0
- package/runtime/webpack.config.js +27 -0
- package/scripts/stage-assets.mjs +21 -0
- package/src/cli.ts +1328 -0
- package/templates/ci/fastlane/Fastfile +43 -0
- package/templates/ci/fastlane/Matchfile +3 -0
- package/templates/ci/github/workflows/appwrap-pr.yml +28 -0
- package/templates/ci/github/workflows/appwrap-release-android.yml +51 -0
- package/templates/ci/github/workflows/appwrap-release-ios.yml +41 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"images" : [
|
|
3
|
+
{
|
|
4
|
+
"idiom" : "universal",
|
|
5
|
+
"filename" : "LaunchScreen-Center.png",
|
|
6
|
+
"scale" : "1x"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"idiom" : "universal",
|
|
10
|
+
"filename" : "LaunchScreen-Center@2x.png",
|
|
11
|
+
"scale" : "2x"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"idiom" : "universal",
|
|
15
|
+
"filename" : "LaunchScreen-Center@3x.png",
|
|
16
|
+
"scale" : "3x"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"info" : {
|
|
20
|
+
"version" : 1,
|
|
21
|
+
"author" : "xcode"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
6
|
+
<string>en</string>
|
|
7
|
+
<key>CFBundleDisplayName</key>
|
|
8
|
+
<string>${PRODUCT_NAME}</string>
|
|
9
|
+
<key>CFBundleExecutable</key>
|
|
10
|
+
<string>${EXECUTABLE_NAME}</string>
|
|
11
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
12
|
+
<string>6.0</string>
|
|
13
|
+
<key>CFBundleName</key>
|
|
14
|
+
<string>${PRODUCT_NAME}</string>
|
|
15
|
+
<key>CFBundlePackageType</key>
|
|
16
|
+
<string>APPL</string>
|
|
17
|
+
<key>CFBundleShortVersionString</key>
|
|
18
|
+
<string>1.0.3</string>
|
|
19
|
+
<key>CFBundleSignature</key>
|
|
20
|
+
<string>????</string>
|
|
21
|
+
<key>CFBundleVersion</key>
|
|
22
|
+
<string>1.0.3</string>
|
|
23
|
+
<key>LSRequiresIPhoneOS</key>
|
|
24
|
+
<true/>
|
|
25
|
+
<key>UILaunchStoryboardName</key>
|
|
26
|
+
<string>LaunchScreen</string>
|
|
27
|
+
<key>UIRequiresFullScreen</key>
|
|
28
|
+
<true/>
|
|
29
|
+
<key>UIRequiredDeviceCapabilities</key>
|
|
30
|
+
<array>
|
|
31
|
+
<string>armv7</string>
|
|
32
|
+
</array>
|
|
33
|
+
<key>UISupportedInterfaceOrientations</key>
|
|
34
|
+
<array>
|
|
35
|
+
<string>UIInterfaceOrientationPortrait</string>
|
|
36
|
+
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
37
|
+
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
38
|
+
</array>
|
|
39
|
+
<key>UISupportedInterfaceOrientations~ipad</key>
|
|
40
|
+
<array>
|
|
41
|
+
<string>UIInterfaceOrientationPortrait</string>
|
|
42
|
+
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
43
|
+
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
44
|
+
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
45
|
+
</array>
|
|
46
|
+
<!-- URL scheme + permission usage strings are stamped per-app by `appwrap init`
|
|
47
|
+
from appwrap.json (urlScheme / permissions) — do not hardcode them here. -->
|
|
48
|
+
<key>NSAppTransportSecurity</key>
|
|
49
|
+
<dict>
|
|
50
|
+
<key>NSAllowsLocalNetworking</key>
|
|
51
|
+
<true/>
|
|
52
|
+
</dict>
|
|
53
|
+
<!-- Background audio so music-player PWAs keep playing when backgrounded /
|
|
54
|
+
screen-locked. Remove for apps that never play audio in the background. -->
|
|
55
|
+
<key>UIBackgroundModes</key>
|
|
56
|
+
<array>
|
|
57
|
+
<string>audio</string>
|
|
58
|
+
</array>
|
|
59
|
+
</dict>
|
|
60
|
+
</plist>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
|
3
|
+
<device id="retina5_9" orientation="portrait">
|
|
4
|
+
<adaptation id="fullscreen"/>
|
|
5
|
+
</device>
|
|
6
|
+
<dependencies>
|
|
7
|
+
<deployment identifier="iOS"/>
|
|
8
|
+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
|
9
|
+
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
10
|
+
</dependencies>
|
|
11
|
+
<scenes>
|
|
12
|
+
<!--View Controller-->
|
|
13
|
+
<scene sceneID="EHf-IW-A2E">
|
|
14
|
+
<objects>
|
|
15
|
+
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
|
16
|
+
<layoutGuides>
|
|
17
|
+
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
|
|
18
|
+
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
|
|
19
|
+
</layoutGuides>
|
|
20
|
+
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
|
21
|
+
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
|
22
|
+
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
23
|
+
<subviews>
|
|
24
|
+
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="LaunchScreen.AspectFill" translatesAutoresizingMaskIntoConstraints="NO" id="wtH-rr-YfP">
|
|
25
|
+
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
|
26
|
+
</imageView>
|
|
27
|
+
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="LaunchScreen.Center" translatesAutoresizingMaskIntoConstraints="NO" id="s1z-aa-wYv">
|
|
28
|
+
<rect key="frame" x="0.0" y="150" width="375" height="512"/>
|
|
29
|
+
</imageView>
|
|
30
|
+
</subviews>
|
|
31
|
+
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
|
32
|
+
<constraints>
|
|
33
|
+
<constraint firstAttribute="trailing" secondItem="s1z-aa-wYv" secondAttribute="trailing" id="4Rf-kf-vql"/>
|
|
34
|
+
<constraint firstAttribute="bottom" secondItem="wtH-rr-YfP" secondAttribute="bottom" id="5CS-At-4ka"/>
|
|
35
|
+
<constraint firstItem="wtH-rr-YfP" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="Awn-b8-xf1"/>
|
|
36
|
+
<constraint firstItem="s1z-aa-wYv" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="BpJ-vb-eKA"/>
|
|
37
|
+
<constraint firstItem="s1z-aa-wYv" firstAttribute="trailing" secondItem="wtH-rr-YfP" secondAttribute="trailing" id="QIM-7J-OZz"/>
|
|
38
|
+
<constraint firstAttribute="trailing" secondItem="wtH-rr-YfP" secondAttribute="trailing" id="RXg-rW-UK8"/>
|
|
39
|
+
<constraint firstItem="s1z-aa-wYv" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="Rcp-LG-48z"/>
|
|
40
|
+
<constraint firstItem="wtH-rr-YfP" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="cch-8E-tYu"/>
|
|
41
|
+
</constraints>
|
|
42
|
+
</view>
|
|
43
|
+
</viewController>
|
|
44
|
+
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
45
|
+
</objects>
|
|
46
|
+
<point key="canvasLocation" x="509.60000000000002" y="237.93103448275863"/>
|
|
47
|
+
</scene>
|
|
48
|
+
</scenes>
|
|
49
|
+
<resources>
|
|
50
|
+
<image name="LaunchScreen.AspectFill" width="768" height="1024"/>
|
|
51
|
+
<image name="LaunchScreen.Center" width="384" height="512"/>
|
|
52
|
+
</resources>
|
|
53
|
+
</document>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Silence CocoaPods' "no platform specified" warning; matches the post_install target below.
|
|
2
|
+
platform :ios, '13.0'
|
|
3
|
+
|
|
4
|
+
# Bump every pod's deployment target. Xcode 15+ removed `libarclite`, so pods that
|
|
5
|
+
# declare an old IPHONEOS_DEPLOYMENT_TARGET (e.g. SAMKeychain via @nativescript/
|
|
6
|
+
# secure-storage) fail device builds with "SDK does not contain 'libarclite'".
|
|
7
|
+
post_install do |installer|
|
|
8
|
+
installer.pods_project.targets.each do |target|
|
|
9
|
+
target.build_configurations.each do |config|
|
|
10
|
+
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<!--
|
|
4
|
+
Apple Privacy Manifest. REQUIRED for App Store upload since May 2024 (hard reject otherwise).
|
|
5
|
+
Declares the "required-reason APIs" the appwrap shell + NativeScript runtime touch, none of which
|
|
6
|
+
are used for tracking:
|
|
7
|
+
- UserDefaults (CA92.1) — kit.storage / ApplicationSettings (NSUserDefaults)
|
|
8
|
+
- FileTimestamp (C617.1) — NativeScript reads bundle/app file metadata at runtime
|
|
9
|
+
- SystemBootTime (35F9.1) — NativeScript timers/uptime
|
|
10
|
+
- DiskSpace (E174.1) — NativeScript file I/O
|
|
11
|
+
NSPrivacyTracking is false and no data types are collected BY THE SHELL. If your PWA collects user
|
|
12
|
+
data (account, analytics, location) or uses tracking, add the matching NSPrivacyCollectedDataTypes /
|
|
13
|
+
NSPrivacyTracking entries here — see tracking-analytics + store-readiness.
|
|
14
|
+
-->
|
|
15
|
+
<plist version="1.0">
|
|
16
|
+
<dict>
|
|
17
|
+
<key>NSPrivacyTracking</key>
|
|
18
|
+
<false/>
|
|
19
|
+
<key>NSPrivacyTrackingDomains</key>
|
|
20
|
+
<array/>
|
|
21
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
|
22
|
+
<array/>
|
|
23
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
24
|
+
<array>
|
|
25
|
+
<dict>
|
|
26
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
27
|
+
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
|
28
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
29
|
+
<array>
|
|
30
|
+
<string>CA92.1</string>
|
|
31
|
+
</array>
|
|
32
|
+
</dict>
|
|
33
|
+
<dict>
|
|
34
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
35
|
+
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
|
36
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
37
|
+
<array>
|
|
38
|
+
<string>C617.1</string>
|
|
39
|
+
</array>
|
|
40
|
+
</dict>
|
|
41
|
+
<dict>
|
|
42
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
43
|
+
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
|
44
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
45
|
+
<array>
|
|
46
|
+
<string>35F9.1</string>
|
|
47
|
+
</array>
|
|
48
|
+
</dict>
|
|
49
|
+
<dict>
|
|
50
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
51
|
+
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
|
52
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
53
|
+
<array>
|
|
54
|
+
<string>E174.1</string>
|
|
55
|
+
</array>
|
|
56
|
+
</dict>
|
|
57
|
+
</array>
|
|
58
|
+
</dict>
|
|
59
|
+
</plist>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// You can add custom settings here
|
|
2
|
+
// for example you can uncomment the following line to force distribution code signing
|
|
3
|
+
// CODE_SIGN_IDENTITY = iPhone Distribution
|
|
4
|
+
// To build for device with XCode you need to specify your development team.
|
|
5
|
+
// DEVELOPMENT_TEAM is stamped by appwrap init/sync from appwrap.json.teamId
|
|
6
|
+
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
7
|
+
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Application, isIOS } from '@nativescript/core';
|
|
2
|
+
import { onDeepLink } from './shell/events';
|
|
3
|
+
import { iosOrientationMask } from './shell/orientation';
|
|
4
|
+
import { installForegroundNotificationDelegate } from './shell/handlers-extended';
|
|
5
|
+
import { onApnsToken, onApnsError, onRemoteMessage } from './shell/handlers-push';
|
|
6
|
+
|
|
7
|
+
/** APNs device token (NSData) → lowercase hex string the push backend expects. */
|
|
8
|
+
function apnsTokenToHex(deviceToken: NSData): string {
|
|
9
|
+
const bytes = new Uint8Array(interop.bufferFromData(deviceToken));
|
|
10
|
+
let hex = '';
|
|
11
|
+
for (let i = 0; i < bytes.length; i++) hex += bytes[i].toString(16).padStart(2, '0');
|
|
12
|
+
return hex;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (isIOS) {
|
|
16
|
+
// Custom delegate for URL-scheme opens. NativeScript tracks lifecycle via
|
|
17
|
+
// UIApplication notifications, so adding a delegate only extends behavior.
|
|
18
|
+
const DelegateClass = (UIResponder as any).extend(
|
|
19
|
+
{
|
|
20
|
+
applicationDidFinishLaunchingWithOptions(_app: UIApplication, launchOptions: NSDictionary<string, any>) {
|
|
21
|
+
// Set the notification delegate NOW so a cold-launch tap's deep link is delivered.
|
|
22
|
+
installForegroundNotificationDelegate();
|
|
23
|
+
const url = launchOptions?.objectForKey(UIApplicationLaunchOptionsURLKey);
|
|
24
|
+
if (url) onDeepLink(url.absoluteString ?? String(url));
|
|
25
|
+
return true;
|
|
26
|
+
},
|
|
27
|
+
applicationOpenURLOptions(_app: UIApplication, url: NSURL, _options: NSDictionary<string, any>) {
|
|
28
|
+
console.log('AppWrap: openURL', url.absoluteString);
|
|
29
|
+
onDeepLink(url.absoluteString);
|
|
30
|
+
return true;
|
|
31
|
+
},
|
|
32
|
+
// Drives `kit.screen.orientation.lock` — UIKit reads this on every rotation.
|
|
33
|
+
applicationSupportedInterfaceOrientationsForWindow(_app: UIApplication, _window: any) {
|
|
34
|
+
return iosOrientationMask();
|
|
35
|
+
},
|
|
36
|
+
// ── Remote push (APNs) — delivered to the AppDelegate, bridged to kit.push. ──
|
|
37
|
+
applicationDidRegisterForRemoteNotificationsWithDeviceToken(_app: UIApplication, deviceToken: NSData) {
|
|
38
|
+
onApnsToken(apnsTokenToHex(deviceToken));
|
|
39
|
+
},
|
|
40
|
+
applicationDidFailToRegisterForRemoteNotificationsWithError(_app: UIApplication, error: NSError) {
|
|
41
|
+
onApnsError(error?.localizedDescription ?? 'APNs registration failed');
|
|
42
|
+
},
|
|
43
|
+
// Background/silent + content-available pushes (a tapped alert routes via the UN delegate).
|
|
44
|
+
applicationDidReceiveRemoteNotificationFetchCompletionHandler(
|
|
45
|
+
_app: UIApplication,
|
|
46
|
+
userInfo: NSDictionary<any, any>,
|
|
47
|
+
completionHandler: (result: UIBackgroundFetchResult) => void
|
|
48
|
+
) {
|
|
49
|
+
onRemoteMessage(userInfo, false);
|
|
50
|
+
completionHandler(UIBackgroundFetchResult.NewData);
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{ protocols: [UIApplicationDelegate] }
|
|
54
|
+
);
|
|
55
|
+
Application.ios.delegate = DelegateClass;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Application.run({ moduleName: 'main-page' });
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Application, AndroidApplication, EventData, Page, isAndroid, isIOS, knownFolders, path } from '@nativescript/core';
|
|
2
|
+
import { bridge } from './shell/bridge';
|
|
3
|
+
import { registerHandlers } from './shell/handlers';
|
|
4
|
+
import { registerExtendedHandlers } from './shell/handlers-extended';
|
|
5
|
+
import { registerParityHandlers } from './shell/handlers-parity';
|
|
6
|
+
import { registerSystemHandlers } from './shell/handlers-system';
|
|
7
|
+
import { registerBillingHandlers } from './shell/handlers-billing';
|
|
8
|
+
import { registerMediaHandlers } from './shell/handlers-media';
|
|
9
|
+
import { registerPushHandlers } from './shell/handlers-push';
|
|
10
|
+
import { registerAndroidHandlers } from './shell/handlers-android';
|
|
11
|
+
import { registerOptionalHandlers } from './shell/optional-handlers.generated';
|
|
12
|
+
import './shell/fcm-bootstrap.generated'; // side-effect: registers the FCM service when push is wired
|
|
13
|
+
import { startEventForwarding } from './shell/events';
|
|
14
|
+
import { SHELL_CONFIG } from './shell/config';
|
|
15
|
+
import { bindStatusBarPage, setStatusBarStyle, enableAndroidEdgeToEdge, wireAndroidSafeArea } from './shell/status-bar';
|
|
16
|
+
import { CustomWebView } from './shell/custom-webview';
|
|
17
|
+
|
|
18
|
+
let initialized = false;
|
|
19
|
+
|
|
20
|
+
export function onPageLoaded(args: EventData): void {
|
|
21
|
+
const page = args.object as Page;
|
|
22
|
+
page.bindingContext = { backgroundColor: SHELL_CONFIG.backgroundColor };
|
|
23
|
+
bindStatusBarPage(page);
|
|
24
|
+
if (isAndroid) enableAndroidEdgeToEdge();
|
|
25
|
+
setStatusBarStyle(SHELL_CONFIG.statusBarStyle);
|
|
26
|
+
|
|
27
|
+
if (initialized) return;
|
|
28
|
+
initialized = true;
|
|
29
|
+
|
|
30
|
+
// Debug mode: keep the screen awake (no auto-lock while foreground) so the dev/inspect session
|
|
31
|
+
// and the iterate loop stay alive. iOS via the idle timer; Android via WebView keepScreenOn.
|
|
32
|
+
if (isIOS && SHELL_CONFIG.debug) {
|
|
33
|
+
try { UIApplication.sharedApplication.idleTimerDisabled = true; } catch (e) { /* no-op */ }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
registerHandlers();
|
|
37
|
+
registerExtendedHandlers();
|
|
38
|
+
registerParityHandlers();
|
|
39
|
+
registerSystemHandlers();
|
|
40
|
+
registerBillingHandlers();
|
|
41
|
+
registerMediaHandlers();
|
|
42
|
+
registerPushHandlers();
|
|
43
|
+
// Last on purpose: overrides the iOS-only placeholders with Android impls
|
|
44
|
+
if (isAndroid) registerAndroidHandlers();
|
|
45
|
+
// Opt-in modules that own their own handler file (health, …) — generated to only the active set.
|
|
46
|
+
registerOptionalHandlers();
|
|
47
|
+
|
|
48
|
+
const webView = page.getViewById<CustomWebView>('webview');
|
|
49
|
+
bridge.attach(webView);
|
|
50
|
+
if (isAndroid) wireAndroidSafeArea(webView); // experimental edge-to-edge (no-op unless config on)
|
|
51
|
+
startEventForwarding();
|
|
52
|
+
loadBundle(webView);
|
|
53
|
+
|
|
54
|
+
// Halt the WebView render + JS-timer pipeline while backgrounded so a page running a continuous
|
|
55
|
+
// animation (Android doesn't auto-pause rAF off-screen) stops burning CPU/battery. No-op on iOS.
|
|
56
|
+
Application.on(Application.suspendEvent, () => webView.setRenderingActive(false));
|
|
57
|
+
Application.on(Application.resumeEvent, () => webView.setRenderingActive(true));
|
|
58
|
+
|
|
59
|
+
// Android system back → WebView history (iOS gets this via the edge-swipe
|
|
60
|
+
// gesture). Only swallow the press when there's history to pop; otherwise let
|
|
61
|
+
// the OS default through so the app can still be backgrounded/exited.
|
|
62
|
+
if (isAndroid) {
|
|
63
|
+
Application.android.on(AndroidApplication.activityBackPressedEvent, (data: any) => {
|
|
64
|
+
const wk = webView.android as android.webkit.WebView;
|
|
65
|
+
if (wk?.canGoBack()) {
|
|
66
|
+
data.cancel = true;
|
|
67
|
+
wk.goBack();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function loadBundle(webView: CustomWebView): void {
|
|
74
|
+
const wwwPath = path.join(knownFolders.currentApp().path, 'www');
|
|
75
|
+
const entryPath = path.join(wwwPath, SHELL_CONFIG.entry);
|
|
76
|
+
|
|
77
|
+
if (isIOS) {
|
|
78
|
+
// Wait for the native WKWebView to exist, then load. Default: app:// custom
|
|
79
|
+
// scheme (stable origin, ES modules work). 'file' loader kept for debugging.
|
|
80
|
+
const tryLoad = () => {
|
|
81
|
+
const wk = webView.ios as WKWebView;
|
|
82
|
+
if (!wk) {
|
|
83
|
+
setTimeout(tryLoad, 50);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (SHELL_CONFIG.loader === 'server' && SHELL_CONFIG.serverUrl) {
|
|
87
|
+
// Live URL (dev HMR / deployed). Bridge (WKUserScript + message handler) still injects.
|
|
88
|
+
const url = NSURL.URLWithString(SHELL_CONFIG.serverUrl);
|
|
89
|
+
wk.loadRequest(NSURLRequest.requestWithURL(url));
|
|
90
|
+
} else if (SHELL_CONFIG.loader === 'file') {
|
|
91
|
+
const entryURL = NSURL.fileURLWithPath(entryPath);
|
|
92
|
+
const readAccessURL = NSURL.fileURLWithPathIsDirectory(wwwPath, true);
|
|
93
|
+
wk.loadFileURLAllowingReadAccessToURL(entryURL, readAccessURL);
|
|
94
|
+
} else {
|
|
95
|
+
const url = NSURL.URLWithString(`app://local/${SHELL_CONFIG.entry}`);
|
|
96
|
+
wk.loadRequest(NSURLRequest.requestWithURL(url));
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
tryLoad();
|
|
100
|
+
} else if (isAndroid) {
|
|
101
|
+
// Settings, transport (onJsPrompt) and the appwrap.local asset interceptor
|
|
102
|
+
// are wired in CustomWebView.initNativeView (custom-webview.android.ts).
|
|
103
|
+
const tryLoad = () => {
|
|
104
|
+
if (!webView.android) {
|
|
105
|
+
setTimeout(tryLoad, 50);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
webView.src = SHELL_CONFIG.loader === 'server' && SHELL_CONFIG.serverUrl
|
|
109
|
+
? SHELL_CONFIG.serverUrl
|
|
110
|
+
: SHELL_CONFIG.loader === 'file'
|
|
111
|
+
? `file://${entryPath}`
|
|
112
|
+
: `https://appwrap.local/${SHELL_CONFIG.entry}`;
|
|
113
|
+
};
|
|
114
|
+
tryLoad();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<Page
|
|
2
|
+
xmlns="http://schemas.nativescript.org/tns.xsd"
|
|
3
|
+
xmlns:shell="shell/custom-webview"
|
|
4
|
+
actionBarHidden="true"
|
|
5
|
+
loaded="onPageLoaded"
|
|
6
|
+
backgroundColor="{{ backgroundColor }}">
|
|
7
|
+
<GridLayout rows="*">
|
|
8
|
+
<shell:CustomWebView id="webview" row="0" />
|
|
9
|
+
</GridLayout>
|
|
10
|
+
</Page>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated by `appwrap init`/`sync` from appwrap.json `modules`. Do not edit.
|
|
3
|
+
* Committed DEFAULT = every opt-in capability active (back-compat: matches pre-modules behavior).
|
|
4
|
+
* The CLI overwrites this with the resolved active set for the app being built.
|
|
5
|
+
*/
|
|
6
|
+
export const ACTIVE_MODULE_NAMES: string[] = [
|
|
7
|
+
'notifications', 'biometrics', 'geo', 'photos', 'camera', 'media',
|
|
8
|
+
'motion', 'contacts', 'calendar', 'reviews', 'billing',
|
|
9
|
+
];
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Application, Utils } from '@nativescript/core';
|
|
2
|
+
|
|
3
|
+
declare const android: any, java: any;
|
|
4
|
+
|
|
5
|
+
let nextCode = 7300;
|
|
6
|
+
|
|
7
|
+
function foregroundActivity(): any {
|
|
8
|
+
return Application.android.foregroundActivity ?? Application.android.startActivity;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Request runtime permissions; resolves true when ALL are granted. Pre-granted short-circuits. */
|
|
12
|
+
export function requestPermissions(permissions: string[]): Promise<boolean> {
|
|
13
|
+
const context = Utils.android.getApplicationContext();
|
|
14
|
+
const granted = (p: string) =>
|
|
15
|
+
context.checkSelfPermission(p) === android.content.pm.PackageManager.PERMISSION_GRANTED;
|
|
16
|
+
if (permissions.every(granted)) return Promise.resolve(true);
|
|
17
|
+
|
|
18
|
+
const activity = foregroundActivity();
|
|
19
|
+
if (!activity) return Promise.resolve(false);
|
|
20
|
+
|
|
21
|
+
const code = nextCode++;
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
const onResult = (args: any) => {
|
|
24
|
+
if (args.requestCode !== code) return;
|
|
25
|
+
Application.android.off(Application.android.activityRequestPermissionsEvent as any, onResult);
|
|
26
|
+
const results: number[] = Array.from(args.grantResults ?? []);
|
|
27
|
+
resolve(results.length > 0 && results.every((r) => r === android.content.pm.PackageManager.PERMISSION_GRANTED));
|
|
28
|
+
};
|
|
29
|
+
Application.android.on(Application.android.activityRequestPermissionsEvent as any, onResult);
|
|
30
|
+
activity.requestPermissions(permissions, code);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Decode a content Uri (downscaled to `maxSize` longest edge) into a JPEG data URL. */
|
|
35
|
+
export function uriToDataUrl(uri: any, maxSize: number): string | null {
|
|
36
|
+
const cr = Utils.android.getApplicationContext().getContentResolver();
|
|
37
|
+
const bounds = new android.graphics.BitmapFactory.Options();
|
|
38
|
+
bounds.inJustDecodeBounds = true;
|
|
39
|
+
let stream = cr.openInputStream(uri);
|
|
40
|
+
android.graphics.BitmapFactory.decodeStream(stream, null, bounds);
|
|
41
|
+
stream.close();
|
|
42
|
+
|
|
43
|
+
const opts = new android.graphics.BitmapFactory.Options();
|
|
44
|
+
let sample = 1;
|
|
45
|
+
while (Math.max(bounds.outWidth, bounds.outHeight) / sample > maxSize) sample *= 2;
|
|
46
|
+
opts.inSampleSize = sample;
|
|
47
|
+
stream = cr.openInputStream(uri);
|
|
48
|
+
const bmp = android.graphics.BitmapFactory.decodeStream(stream, null, opts);
|
|
49
|
+
stream.close();
|
|
50
|
+
return bmp ? bitmapToDataUrl(bmp) : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Compress an Android Bitmap to a JPEG data URL. */
|
|
54
|
+
export function bitmapToDataUrl(bmp: any): string {
|
|
55
|
+
const baos = new java.io.ByteArrayOutputStream();
|
|
56
|
+
bmp.compress(android.graphics.Bitmap.CompressFormat.JPEG, 85, baos);
|
|
57
|
+
const b64 = android.util.Base64.encodeToString(baos.toByteArray(), android.util.Base64.NO_WRAP);
|
|
58
|
+
return `data:image/jpeg;base64,${b64}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Launch an intent and resolve with its activity result. */
|
|
62
|
+
export function startActivityForResult(intent: any): Promise<{ resultCode: number; intent: any }> {
|
|
63
|
+
const activity = foregroundActivity();
|
|
64
|
+
if (!activity) return Promise.reject(Object.assign(new Error('no foreground activity'), { code: 'NOT_READY' }));
|
|
65
|
+
|
|
66
|
+
const code = nextCode++;
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const onResult = (args: any) => {
|
|
69
|
+
if (args.requestCode !== code) return;
|
|
70
|
+
Application.android.off(Application.android.activityResultEvent as any, onResult);
|
|
71
|
+
resolve({ resultCode: args.resultCode, intent: args.intent });
|
|
72
|
+
};
|
|
73
|
+
Application.android.on(Application.android.activityResultEvent as any, onResult);
|
|
74
|
+
activity.startActivityForResult(intent, code);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { isAndroid, isIOS } from '@nativescript/core';
|
|
2
|
+
import { CustomWebView } from './custom-webview';
|
|
3
|
+
|
|
4
|
+
export type HandlerFn = (params: any) => unknown | Promise<unknown>;
|
|
5
|
+
|
|
6
|
+
interface RequestEnvelope {
|
|
7
|
+
v: 1;
|
|
8
|
+
id: string;
|
|
9
|
+
kind: 'request';
|
|
10
|
+
method: string;
|
|
11
|
+
params?: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* AppWrap bridge — protocol v1 dispatcher.
|
|
16
|
+
* Handlers are registered by method name ('haptics.impact'); requests arrive as
|
|
17
|
+
* JSON strings, responses/events go back via `window.__appwrapDeliver(json)`.
|
|
18
|
+
*/
|
|
19
|
+
export class Bridge {
|
|
20
|
+
private handlers = new Map<string, HandlerFn>();
|
|
21
|
+
private webView: CustomWebView | null = null;
|
|
22
|
+
|
|
23
|
+
register(method: string, handler: HandlerFn): void {
|
|
24
|
+
this.handlers.set(method, handler);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Both platforms push envelopes in: iOS via WKScriptMessageHandler 'appwrap',
|
|
29
|
+
* Android via the prompt() tunnel intercepted in WebChromeClient.onJsPrompt
|
|
30
|
+
* (see custom-webview.android.ts) — no polling on either side.
|
|
31
|
+
*/
|
|
32
|
+
attach(webView: CustomWebView): void {
|
|
33
|
+
this.webView = webView;
|
|
34
|
+
webView.onAppwrapMessage = (json) => this.onMessage(json);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
detach(): void {
|
|
38
|
+
if (this.webView) this.webView.onAppwrapMessage = null;
|
|
39
|
+
this.webView = null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
emit(event: string, payload?: unknown): void {
|
|
43
|
+
this.deliver(JSON.stringify({ v: 1, kind: 'event', event, payload }));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private async onMessage(json: string): Promise<void> {
|
|
47
|
+
let req: RequestEnvelope;
|
|
48
|
+
try {
|
|
49
|
+
req = JSON.parse(json);
|
|
50
|
+
} catch {
|
|
51
|
+
console.error('Bridge: unparseable envelope', json?.slice(0, 200));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (req.kind !== 'request' || !req.id || !req.method) return;
|
|
55
|
+
|
|
56
|
+
const handler = this.handlers.get(req.method);
|
|
57
|
+
if (!handler) {
|
|
58
|
+
this.respond(req.id, undefined, { code: 'UNSUPPORTED', message: `No handler for ${req.method}` });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const result = await handler(req.params ?? {});
|
|
63
|
+
this.respond(req.id, result);
|
|
64
|
+
} catch (e: any) {
|
|
65
|
+
this.respond(req.id, undefined, { code: e?.code ?? 'NATIVE_ERROR', message: e?.message ?? String(e) });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private respond(id: string, result?: unknown, error?: { code: string; message: string }): void {
|
|
70
|
+
this.deliver(JSON.stringify({ v: 1, id, kind: 'response', result, error }));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private deliver(envelopeJson: string): void {
|
|
74
|
+
const js = `window.__appwrapDeliver && window.__appwrapDeliver(${JSON.stringify(envelopeJson)})`;
|
|
75
|
+
this.evalJs(js).catch((e) => console.error('Bridge: deliver failed', e));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private evalJs(script: string): Promise<any> {
|
|
79
|
+
const wv = this.webView;
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
if (!wv) return reject(new Error('no webview'));
|
|
82
|
+
if (isIOS && wv.ios) {
|
|
83
|
+
(wv.ios as WKWebView).evaluateJavaScriptCompletionHandler(script, (result, error) => {
|
|
84
|
+
if (error) reject(new Error(error.localizedDescription));
|
|
85
|
+
else resolve(result);
|
|
86
|
+
});
|
|
87
|
+
} else if (isAndroid && wv.android) {
|
|
88
|
+
wv.android.evaluateJavascript(
|
|
89
|
+
script,
|
|
90
|
+
new android.webkit.ValueCallback({ onReceiveValue: (r: string) => resolve(r ? JSON.parse(r) : null) })
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
reject(new Error('webview not ready'));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const bridge = new Bridge();
|