├── .eslintignore
├── .gitignore
├── .prettierignore
├── CONTRIBUTING.md
├── NativescriptCapacitor.podspec
├── README.md
├── android
├── .gitignore
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── proguard-rules.pro
├── settings.gradle
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── getcapacitor
│ │ └── android
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── org
│ │ │ └── nativescript
│ │ │ └── capacitor
│ │ │ ├── NativeScriptCap.java
│ │ │ ├── NativeScriptCapPlugin.java
│ │ │ └── NativeScriptCapPluginListener.java
│ └── res
│ │ └── .gitkeep
│ └── test
│ └── java
│ └── com
│ └── getcapacitor
│ └── ExampleUnitTest.java
├── bin
├── build-nativescript.mjs
├── dev-nativescript.mjs
└── uninstall-nativescript.mjs
├── embed
├── android
│ ├── app-gradle-helpers
│ │ ├── AnalyticsCollector.gradle
│ │ ├── BuildToolTask.gradle
│ │ └── CustomExecutionLogger.gradle
│ ├── build-tools
│ │ ├── android-metadata-generator.jar
│ │ ├── dts-generator.jar
│ │ ├── jsparser
│ │ │ └── js_parser.js
│ │ └── static-binding-generator.jar
│ ├── debug
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── tns
│ │ │ │ ├── ErrorReport.java
│ │ │ │ ├── ErrorReportActivity.java
│ │ │ │ ├── NativeScriptSyncService.java
│ │ │ │ └── NativeScriptSyncServiceSocketImpl.java
│ │ └── res
│ │ │ └── layout
│ │ │ ├── error_activity.xml
│ │ │ ├── exception_tab.xml
│ │ │ └── logcat_tab.xml
│ ├── gradle-helpers
│ │ ├── paths.gradle
│ │ └── user_properties_reader.gradle
│ ├── internal
│ │ ├── livesync.js
│ │ └── ts_helpers.js
│ ├── libs
│ │ └── runtime-libs
│ │ │ ├── nativescript-optimized-with-inspector.aar
│ │ │ ├── nativescript-optimized.aar
│ │ │ └── nativescript-regular.aar
│ ├── main
│ │ └── java
│ │ │ └── com
│ │ │ └── tns
│ │ │ ├── AndroidJsV8Inspector.java
│ │ │ ├── DefaultExtractPolicy.java
│ │ │ ├── EqualityComparator.java
│ │ │ ├── LogcatLogger.java
│ │ │ ├── NativeScriptApplication.java
│ │ │ ├── NativeScriptUncaughtExceptionHandler.java
│ │ │ ├── RuntimeHelper.java
│ │ │ ├── Util.java
│ │ │ └── internal
│ │ │ ├── AppBuilderCallback.java
│ │ │ └── Plugin.java
│ ├── nativescript.build.gradle
│ └── nativescript.buildscript.gradle
└── ios
│ └── NativeScript
│ ├── App-Bridging-Header.h
│ ├── Runtime.h
│ └── Runtime.m
├── ios
├── Plugin.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ ├── Plugin.xcscheme
│ │ └── PluginTests.xcscheme
├── Plugin.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Plugin
│ ├── Info.plist
│ ├── NativeScriptCap.swift
│ ├── NativeScriptCapPlugin.h
│ ├── NativeScriptCapPlugin.m
│ └── NativeScriptCapPlugin.swift
├── PluginTests
│ ├── Info.plist
│ └── NativeScriptCapPluginTests.swift
├── Podfile
└── nativescript.rb
├── package.json
├── rollup.config.js
├── src
├── definitions.ts
├── index.mts
├── nativeapi.d.ts
├── nativescript
│ ├── examples
│ │ └── modal.ts
│ ├── index.ts
│ ├── package.json
│ ├── references.d.ts
│ └── tsconfig.json
├── postinstall.mts
└── web.ts
├── src_bridge
├── index.ts
├── legacy-ns-transform-native-classes.js
├── native-custom.d.ts
├── package.json
├── references.d.ts
├── tsconfig.json
├── webpack.config.js
└── webpack4.config.js
├── testing
├── browser.ts
├── nativeInterface.ts
├── test.htm
├── testingInterface.ts
└── tsconfig.json
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | build
2 | dist
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # node files
2 | dist
3 | node_modules
4 | *.tgz
5 | package-lock.json
6 | .npmrc
7 |
8 | # iOS files
9 | Pods
10 | Podfile.lock
11 | Build
12 | xcuserdata
13 |
14 | # macOS files
15 | .DS_Store
16 |
17 |
18 |
19 | # Based on Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
20 |
21 | # Built application files
22 | *.apk
23 | *.ap_
24 |
25 | # Files for the ART/Dalvik VM
26 | *.dex
27 |
28 | # Java class files
29 | *.class
30 |
31 | # Generated files
32 | gen
33 | out
34 | bridge
35 |
36 | # Gradle files
37 | .gradle
38 | build
39 |
40 | # Local configuration file (sdk path, etc)
41 | local.properties
42 |
43 | # Proguard folder generated by Eclipse
44 | proguard
45 |
46 | # Log Files
47 | *.log
48 |
49 | # Android Studio Navigation editor temp files
50 | .navigation
51 |
52 | # Android Studio captures folder
53 | captures
54 |
55 | # IntelliJ
56 | *.iml
57 | .idea
58 |
59 | # Keystore files
60 | # Uncomment the following line if you do not want to check your keystore files in.
61 | #*.jks
62 |
63 | # External native build folder generated in Android Studio 2.2 and later
64 | .externalNativeBuild
65 |
66 | # Executables
67 | !bin/build-nativescript.js
68 | !bin/dev-nativescript.js
69 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | build
2 | dist
3 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | This guide provides instructions for contributing to this Capacitor plugin.
4 |
5 | ## Developing
6 |
7 | ### Local Setup
8 |
9 | 1. Fork and clone the repo.
10 | 1. Setup the workspace.
11 |
12 | ```shell
13 | npm run setup
14 | ```
15 |
16 | 1. Install SwiftLint if you're on macOS.
17 |
18 | ```shell
19 | brew install swiftlint
20 | ```
21 |
22 | ### Scripts
23 |
24 | #### `npm run build`
25 |
26 | Build the plugin web assets and generate plugin API documentation using [`@capacitor/docgen`](https://github.com/ionic-team/capacitor-docgen).
27 |
28 | It will compile the TypeScript code from `src/` into ESM JavaScript in `dist/esm/`. These files are used in apps with bundlers when your plugin is imported.
29 |
30 | Then, Rollup will bundle the code into a single file at `dist/plugin.js`. This file is used in apps without bundlers by including it as a script in `index.html`.
31 |
32 | #### `npm run verify`
33 |
34 | Build and validate the web and native projects.
35 |
36 | This is useful to run in CI to verify that the plugin builds for all platforms.
37 |
38 | #### `npm run lint` / `npm run fmt`
39 |
40 | Check formatting and code quality, autoformat/autofix if possible.
41 |
42 | This template is integrated with ESLint, Prettier, and SwiftLint. Using these tools is completely optional, but the [Capacitor Community](https://github.com/capacitor-community/) strives to have consistent code style and structure for easier cooperation.
43 |
44 | ## Publishing
45 |
46 | There is a `prepublishOnly` hook in `package.json` which prepares the plugin before publishing, so all you need to do is run:
47 |
48 | ```shell
49 | npm publish
50 | ```
51 |
52 | > **Note**: The [`files`](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#files) array in `package.json` specifies which files get published. If you rename files/directories or add files elsewhere, you may need to update it.
53 |
--------------------------------------------------------------------------------
/NativescriptCapacitor.podspec:
--------------------------------------------------------------------------------
1 | require 'json'
2 |
3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4 |
5 | Pod::Spec.new do |s|
6 | s.name = 'NativescriptCapacitor'
7 | s.version = package['version']
8 | s.summary = package['description']
9 | s.license = package['license']
10 | s.homepage = package['repository']['url']
11 | s.author = package['author']
12 | s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13 | s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
14 | s.ios.deployment_target = '13.0'
15 | s.dependency 'Capacitor'
16 | s.dependency 'NativeScriptSDK', '~> 8.4.2'
17 | s.dependency 'NativeScriptUI', '~> 0.1.2'
18 | s.swift_version = '5.1'
19 | end
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @nativescript/capacitor
2 |
3 | NativeScript for Capacitor
4 |
5 | ## Install
6 |
7 | ```bash
8 | yarn add @nativescript/capacitor
9 | yarn build:mobile
10 | npx cap sync
11 | ```
12 |
13 | ## Docs
14 |
15 | See https://capacitor.nativescript.org/
16 |
17 | Contribute to docs [here](https://github.com/NativeScript/capacitor-docs)
18 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.12'
3 | androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.1.0'
4 | androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.1'
5 | androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.2.0'
6 | }
7 |
8 | buildscript {
9 | repositories {
10 | google()
11 | jcenter()
12 | }
13 | dependencies {
14 | classpath 'com.android.tools.build:gradle:4.1.1'
15 | }
16 | }
17 |
18 | apply plugin: 'com.android.library'
19 |
20 | android {
21 | compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 30
22 | defaultConfig {
23 | minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 21
24 | targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 30
25 | versionCode 1
26 | versionName "1.0"
27 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
28 | }
29 | buildTypes {
30 | release {
31 | minifyEnabled false
32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
33 | }
34 | }
35 | lintOptions {
36 | abortOnError false
37 | }
38 | compileOptions {
39 | sourceCompatibility JavaVersion.VERSION_1_8
40 | targetCompatibility JavaVersion.VERSION_1_8
41 | }
42 | }
43 |
44 | repositories {
45 | google()
46 | jcenter()
47 | mavenCentral()
48 | }
49 |
50 |
51 | dependencies {
52 | implementation fileTree(dir: 'libs', include: ['*.jar'])
53 | implementation project(':capacitor-android')
54 | implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
55 | testImplementation "junit:junit:$junitVersion"
56 | androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
57 | androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
58 | }
59 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
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 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 | # AndroidX package structure to make it clearer which packages are bundled with the
20 | # Android operating system, and which are packaged with your app's APK
21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
22 | android.useAndroidX=true
23 | # Automatically convert third-party libraries to use AndroidX
24 | android.enableJetifier=true
25 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NativeScript/capacitor/203b85f65af1c7e2a6bb16f3de2d369c9f4bda44/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto init
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto init
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :init
68 | @rem Get command-line arguments, handling Windows variants
69 |
70 | if not "%OS%" == "Windows_NT" goto win9xME_args
71 |
72 | :win9xME_args
73 | @rem Slurp the command line arguments.
74 | set CMD_LINE_ARGS=
75 | set _SKIP=2
76 |
77 | :win9xME_args_slurp
78 | if "x%~1" == "x" goto execute
79 |
80 | set CMD_LINE_ARGS=%*
81 |
82 | :execute
83 | @rem Setup the command line
84 |
85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
86 |
87 |
88 | @rem Execute Gradle
89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
90 |
91 | :end
92 | @rem End local scope for the variables with windows NT shell
93 | if "%ERRORLEVEL%"=="0" goto mainEnd
94 |
95 | :fail
96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
97 | rem the _cmd.exe /c_ return code!
98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
99 | exit /b 1
100 |
101 | :mainEnd
102 | if "%OS%"=="Windows_NT" endlocal
103 |
104 | :omega
105 |
--------------------------------------------------------------------------------
/android/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 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':capacitor'
2 | project(':capacitor').projectDir = file("/Users/teodordermendzhiev/workspace/capacitor-integration-demo/capacitor/android/capacitor")
--------------------------------------------------------------------------------
/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.getcapacitor.android;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import android.content.Context;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 | import androidx.test.platform.app.InstrumentationRegistry;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * @see Testing documentation
15 | */
16 | @RunWith(AndroidJUnit4.class)
17 | public class ExampleInstrumentedTest {
18 |
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
23 |
24 | assertEquals("com.getcapacitor.android", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/android/src/main/java/org/nativescript/capacitor/NativeScriptCap.java:
--------------------------------------------------------------------------------
1 | package org.nativescript.capacitor;
2 |
3 | public class NativeScriptCap { }
4 |
--------------------------------------------------------------------------------
/android/src/main/java/org/nativescript/capacitor/NativeScriptCapPlugin.java:
--------------------------------------------------------------------------------
1 | package org.nativescript.capacitor;
2 |
3 | import androidx.annotation.Nullable;
4 |
5 | import com.getcapacitor.Plugin;
6 | import com.getcapacitor.PluginCall;
7 | import com.getcapacitor.PluginMethod;
8 | import com.getcapacitor.annotation.CapacitorPlugin;
9 |
10 | @CapacitorPlugin(name = "NativeScriptCap")
11 | public class NativeScriptCapPlugin extends Plugin {
12 | @Nullable
13 | public static NativeScriptCapPluginListener listener = null;
14 |
15 | @Override
16 | public void load() {
17 | super.load();
18 | if (listener != null) {
19 | listener.setup(this);
20 | }
21 | }
22 |
23 | @PluginMethod(returnType = PluginMethod.RETURN_NONE)
24 | public void notify(PluginCall call) {
25 | if (listener != null) {
26 | listener.notify(call.getString("value"));
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/android/src/main/java/org/nativescript/capacitor/NativeScriptCapPluginListener.java:
--------------------------------------------------------------------------------
1 | package org.nativescript.capacitor;
2 |
3 | import androidx.annotation.Nullable;
4 |
5 | public interface NativeScriptCapPluginListener {
6 | void setup(NativeScriptCapPlugin instance);
7 | void notify(@Nullable String message);
8 | }
9 |
--------------------------------------------------------------------------------
/android/src/main/res/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NativeScript/capacitor/203b85f65af1c7e2a6bb16f3de2d369c9f4bda44/android/src/main/res/.gitkeep
--------------------------------------------------------------------------------
/android/src/test/java/com/getcapacitor/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.getcapacitor;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import org.junit.Test;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 |
14 | @Test
15 | public void addition_isCorrect() throws Exception {
16 | assertEquals(4, 2 + 2);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/bin/build-nativescript.mjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { join, relative, resolve, sep } from 'path';
3 | import { spawn } from 'child_process';
4 | import fs from 'fs';
5 | import stripJsonComments from 'strip-json-comments';
6 |
7 | const [,, ...args] = process.argv;
8 | // console.log('args:', args);
9 | // console.log('process.cwd', process.cwd());
10 | const installOnly = args && args[0] === 'install';
11 | const projectDir = process.cwd();
12 | const buildDir = join(
13 | projectDir,
14 | 'src',
15 | 'nativescript'
16 | );
17 | const capacitorConfigName = 'capacitor.config.json';
18 | const capacitorConfigPath = join(projectDir, capacitorConfigName);
19 | const capacitorConfigNameTS = 'capacitor.config.ts';
20 | const capacitorConfigTSPath = join(projectDir, capacitorConfigNameTS);
21 | let distFolder = 'www'; // default
22 | let isReact = false;
23 | let isVue = false;
24 | let isAngular = false;
25 | let webpackInfo;
26 |
27 | const checkFramework = () => {
28 | // default is webpack5 config
29 | webpackInfo = {
30 | name: 'webpack.config.js',
31 | version: 5
32 | };
33 |
34 | // Note: Left here in case other flavor integrations end up using older webpack
35 | // const packagePath = join(projectDir, 'package.json');
36 | // const packageContent = fs.readFileSync(packagePath, {
37 | // encoding: 'UTF-8',
38 | // });
39 | // if (packageContent) {
40 | // const packageJson = JSON.parse(
41 | // stripJsonComments(packageContent),
42 | // );
43 | // if (packageJson && packageJson.dependencies) {
44 | // isReact = !!packageJson.dependencies['react'];
45 | // isVue = !!packageJson.dependencies['vue'];
46 | // if (isVue) {
47 | // webpackInfo = {
48 | // name: 'webpack4.config.js',
49 | // version: 4
50 | // };
51 | // } else {
52 | // isAngular = true;
53 | // }
54 | // }
55 | // }
56 | };
57 |
58 | const buildNativeScript = () => {
59 | // console.log('buildDir:', resolve(buildDir));
60 |
61 | // console.log('using webpack config:', webpackInfo.name);
62 |
63 | // const configPath = import.meta.resolve(`@nativescript/capacitor/bridge/webpack.config.js`);
64 |
65 | // webpack 5 needs at least one platform set
66 | if (webpackInfo.version === 5) {
67 | if (args && !args.find(a => a.indexOf('env=platform') > -1)) {
68 | // use ios by default when no platform argument is present
69 | // won't matter for most users since they will often use conditional logic
70 | // but for power users that start using .ios and .android files they can split npm scripts to pass either platform for further optimized builds
71 | args.push(`--env=platform=ios`);
72 | }
73 | }
74 | // console.log('configPath:', resolve(configPath));
75 | // console.log('configPath:', configPath)
76 | const url = new URL(`../bridge/${webpackInfo.name}`, import.meta.url);
77 | const cmdArgs = ['webpack', `--config=${url.pathname}`, ...args];
78 | // console.log('cmdArgs:', cmdArgs);
79 | const child = spawn(`npx`, cmdArgs, {
80 | cwd: resolve(buildDir),
81 | stdio: 'inherit',
82 | shell: true,
83 | });
84 | child.on('error', (error) => {
85 | console.log('NativeScript build error:', error);
86 | });
87 | child.on('close', (res) => {
88 | console.log(`NativeScript build complete:`, `${distFolder}/nativescript/index.js`);
89 | child.kill();
90 | });
91 | };
92 |
93 | const installTsPatch = () => {
94 | // Ensure ts-patch is installed
95 | const child = spawn(`ts-patch`, ['install'], {
96 | cwd: resolve(buildDir),
97 | stdio: 'inherit',
98 | shell: true,
99 | });
100 | child.on('error', (error) => {
101 | console.log('NativeScript build error:', error);
102 | });
103 | child.on('close', (res) => {
104 | child.kill();
105 | if (!installOnly) {
106 | buildNativeScript();
107 | }
108 | });
109 | }
110 |
111 | const npmInstallTsPatch = () => {
112 | // Ensure ts-patch is installed
113 | const child = spawn(`npm`, ['install', '--legacy-peer-deps'], {
114 | cwd: resolve(buildDir),
115 | stdio: 'inherit',
116 | shell: true,
117 | });
118 | child.on('error', (error) => {
119 | console.log('NativeScript build error:', error);
120 | });
121 | child.on('close', (res) => {
122 | child.kill();
123 | installTsPatch();
124 | });
125 | }
126 |
127 | const npmInstall = () => {
128 |
129 | // Note: Left in case flavor integrations need any different dependencies in future
130 | // if (isReact) {
131 | // // Exception case: React needs ts-loader ^6.2.2 installed
132 | // const child = spawn(`npm`, ['install', 'ts-loader@6.2.2', '-D'], {
133 | // cwd: resolve(buildDir),
134 | // stdio: 'inherit',
135 | // shell: true,
136 | // });
137 | // child.on('error', (error) => {
138 | // console.log('NativeScript build error:', error);
139 | // });
140 | // child.on('close', (res) => {
141 | // child.kill();
142 | // installTsPatch();
143 | // });
144 | // } else {
145 | // Vue projects bring in ts-loader 6.2.2 through vue cli dependencies
146 | // Angular project use webpack5 and can use the latest with @nativescript/webpack
147 | // proceed as normal
148 | npmInstallTsPatch();
149 | // }
150 | }
151 |
152 | if (!installOnly) {
153 | // Determine configured build folder name
154 | if (fs.existsSync(capacitorConfigPath)) {
155 | const capacitorConfigContent = fs.readFileSync(capacitorConfigPath, {
156 | encoding: 'UTF-8',
157 | });
158 | if (capacitorConfigContent) {
159 | const capacitorConfigJson = JSON.parse(
160 | stripJsonComments(capacitorConfigContent),
161 | );
162 | if (capacitorConfigJson && capacitorConfigJson.webDir) {
163 | distFolder = capacitorConfigJson.webDir;
164 | }
165 | }
166 | } else if (fs.existsSync(capacitorConfigTSPath)) {
167 | const capacitorConfigTSContent = fs.readFileSync(capacitorConfigTSPath, {
168 | encoding: 'UTF-8',
169 | });
170 | if (capacitorConfigTSContent) {
171 | // need a AST parser here - ridumentary check for now
172 | // assuming 3 likely values: www, build or dist
173 | if (capacitorConfigTSContent.indexOf(`webDir: 'www'`) === -1) {
174 | // not using default, parse it out
175 | distFolder = capacitorConfigTSContent.split(/webDir:\s*["'`](\w+)["'`]/gim)[1];
176 | }
177 | }
178 | } else {
179 | console.error(`NativeScript Build Error: could not find ${capacitorConfigName}`);
180 | }
181 | if (args && !args.find(a => a.indexOf('env=distFolder') > -1)) {
182 | args.push(`--env=distFolder=${distFolder}`);
183 | }
184 | }
185 |
186 | // check frontend framework details
187 | checkFramework();
188 | // ensure various deps are installed and setup properly
189 | npmInstall();
190 |
191 | export function init() {
192 |
193 | }
--------------------------------------------------------------------------------
/bin/dev-nativescript.mjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import chokidar from 'chokidar';
3 | import { execSync } from 'child_process';
4 | import prompts from 'prompts';
5 | import path from 'path';
6 | import fs from 'fs';
7 |
8 | const projectDir = process.cwd();
9 | const buildDir = path.resolve(
10 | projectDir,
11 | 'src',
12 | 'nativescript'
13 | );
14 | const platform = process.argv[2];
15 |
16 |
17 | const getAvailableDevices = () => {
18 | if (platform !== 'ios' && platform !== 'android') {
19 | console.log("Please use valid platform (ios/android)")
20 | return;
21 | }
22 | let devices = [];
23 | const iosRegex = /(iOS \d+)/g;
24 | const androidRegex = /(API \d+)/g;
25 |
26 | const result = execSync(`npx cap run ${platform} --list`).toString()
27 | .split('\n')
28 | .filter(line => line.match(platform === 'ios' ? iosRegex : androidRegex));
29 |
30 | result.forEach(device => {
31 | const id = device.split(" ").pop();
32 | devices.push({
33 | title: device,
34 | value: id
35 | })
36 | })
37 | return devices;
38 | }
39 |
40 | const selectTargetDevice = async (devicesAvailable) => {
41 | if (devicesAvailable) {
42 | const targetDevice = await prompts({
43 | type: 'select',
44 | name: 'value',
45 | message: 'Please choose a target device',
46 | choices: devicesAvailable,
47 | initial: 0
48 | });
49 | return targetDevice.value;
50 | }
51 | }
52 |
53 | const buildApp = () => {
54 | execSync('npm run build:mobile', {stdio: 'inherit'});
55 | }
56 |
57 | const runOnTarget = (targetDevice) => {
58 | execSync(`npx cap run ${platform} --target ${targetDevice}`, {stdio: 'inherit'})
59 | }
60 |
61 | const watchFiles = (targetDevice) => {
62 | buildApp()
63 | runOnTarget(targetDevice);
64 | console.log("Watching for file changes...");
65 |
66 | chokidar.watch(`${buildDir}/**/*.ts`, {
67 | interval: 100,
68 | binaryInterval: 300,
69 | awaitWriteFinish: {
70 | stabilityThreshold: 2000,
71 | pollInterval: 100
72 | },
73 | }).on('change', (event, path) => {
74 | console.log("File change detected.");
75 | buildApp()
76 | runOnTarget(targetDevice);
77 | console.log("Watching for file changes...");
78 | });
79 | }
80 | (async () => {
81 |
82 | let targetDevice = await selectTargetDevice(getAvailableDevices());
83 | if (targetDevice) {
84 | watchFiles(targetDevice);
85 | }
86 |
87 | })();
88 |
89 | export function init() {
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/bin/uninstall-nativescript.mjs:
--------------------------------------------------------------------------------
1 | import { join, relative, resolve, sep } from 'path';
2 | import { spawn } from 'child_process';
3 |
4 | const projectDir = process.cwd();
5 | const cmdArgs = ['dist/esm/postinstall', `--action uninstall`];
6 | // console.log('cmdArgs:', cmdArgs);
7 | const child = spawn(`node`, cmdArgs, {
8 | cwd: resolve(join(projectDir, 'node_modules', '@nativescript', 'capacitor')),
9 | stdio: 'inherit',
10 | shell: true,
11 | });
12 | child.on('error', (error) => {
13 | console.log('NativeScript uninstall error:', error);
14 | });
15 | child.on('close', (res) => {
16 | console.log(`NativeScript uninstall complete.`);
17 | console.log(`\n`);
18 | console.log(`You can now 'npm uninstall @nativescript/capacitor'`);
19 | console.log(`\n`);
20 | child.kill();
21 | });
22 |
23 | export function init() {
24 |
25 | }
--------------------------------------------------------------------------------
/embed/android/app-gradle-helpers/AnalyticsCollector.gradle:
--------------------------------------------------------------------------------
1 | import groovy.json.JsonBuilder
2 |
3 | import java.nio.charset.StandardCharsets
4 | import java.nio.file.Files
5 | import java.nio.file.Paths
6 | import java.nio.file.Path
7 |
8 | class AnalyticsCollector{
9 |
10 | private final String analyticsFilePath
11 | private boolean hasUseKotlinPropertyInApp = false
12 | private boolean hasKotlinRuntimeClasses = false
13 |
14 | private AnalyticsCollector(String analyticsFilePath){
15 | this.analyticsFilePath = analyticsFilePath
16 | }
17 |
18 | static AnalyticsCollector withOutputPath(String analyticsFilePath){
19 | return new AnalyticsCollector(analyticsFilePath)
20 | }
21 |
22 | void markUseKotlinPropertyInApp(boolean useKotlin) {
23 | hasUseKotlinPropertyInApp = useKotlin
24 | }
25 |
26 | void writeAnalyticsFile() {
27 | def jsonBuilder = new JsonBuilder()
28 | def kotlinUsageData = new Object()
29 | kotlinUsageData.metaClass.hasUseKotlinPropertyInApp = hasUseKotlinPropertyInApp
30 | kotlinUsageData.metaClass.hasKotlinRuntimeClasses = hasKotlinRuntimeClasses
31 | jsonBuilder(kotlinUsage: kotlinUsageData)
32 | def prettyJson = jsonBuilder.toPrettyString()
33 |
34 |
35 |
36 | Path statisticsFilePath = Paths.get(analyticsFilePath)
37 |
38 | if (Files.notExists(statisticsFilePath)) {
39 | Files.createDirectories(statisticsFilePath.getParent())
40 | Files.createFile(statisticsFilePath)
41 | }
42 |
43 | Files.write(statisticsFilePath, prettyJson.getBytes(StandardCharsets.UTF_8))
44 |
45 | }
46 | }
47 |
48 | ext.AnalyticsCollector = AnalyticsCollector
49 |
--------------------------------------------------------------------------------
/embed/android/app-gradle-helpers/BuildToolTask.gradle:
--------------------------------------------------------------------------------
1 | import static org.gradle.internal.logging.text.StyledTextOutput.Style
2 |
3 | class BuildToolTask extends JavaExec {
4 | void setOutputs(def logger) {
5 | def logFile = new File("$workingDir/${name}.log")
6 | if(logFile.exists()) {
7 | logFile.delete()
8 | }
9 | standardOutput new FileOutputStream(logFile)
10 | errorOutput new FailureOutputStream(logger, logFile)
11 | }
12 | }
13 |
14 | class FailureOutputStream extends OutputStream {
15 | private logger
16 | private File logFile
17 | private currentLine = ""
18 | private firstWrite = true
19 | FailureOutputStream(inLogger, inLogFile) {
20 | logger = inLogger
21 | logFile = inLogFile
22 | }
23 |
24 | @Override
25 | void write(int i) throws IOException {
26 | if(firstWrite) {
27 | println ""
28 | firstWrite = false
29 | }
30 | currentLine += String.valueOf((char) i)
31 | }
32 |
33 | @Override
34 | void flush() {
35 | if(currentLine?.trim()) {
36 | logger.withStyle(Style.Failure).println currentLine.trim()
37 | currentLine = ""
38 | }
39 | }
40 |
41 | @Override
42 | void close() {
43 | if(!firstWrite && logFile.exists()) {
44 | logger.withStyle(Style.Info).println "Detailed log here: ${logFile.getAbsolutePath()}\n"
45 | }
46 | super.close()
47 | }
48 | }
49 |
50 | ext.BuildToolTask = BuildToolTask
--------------------------------------------------------------------------------
/embed/android/app-gradle-helpers/CustomExecutionLogger.gradle:
--------------------------------------------------------------------------------
1 | import org.gradle.internal.logging.text.StyledTextOutputFactory
2 |
3 | import static org.gradle.internal.logging.text.StyledTextOutput.Style
4 | def outLogger = services.get(StyledTextOutputFactory).create("colouredOutputLogger")
5 |
6 | class CustomExecutionLogger extends BuildAdapter implements TaskExecutionListener {
7 | private logger
8 | private failedTask
9 |
10 | CustomExecutionLogger(passedLogger) {
11 | logger = passedLogger
12 | }
13 |
14 | void buildStarted(Gradle gradle) {
15 | failedTask = null
16 | }
17 |
18 | void beforeExecute(Task task) {
19 | }
20 |
21 | void afterExecute(Task task, TaskState state) {
22 | def failure = state.getFailure()
23 | if(failure) {
24 | failedTask = task
25 | }
26 | }
27 |
28 | void buildFinished(BuildResult result) {
29 | def failure = result.getFailure()
30 | if(failure) {
31 | if(failedTask && (failedTask.getClass().getName().contains("BuildToolTask"))) {
32 | // the error from this task is already logged
33 | return
34 | }
35 |
36 | println ""
37 | logger.withStyle(Style.FailureHeader).println failure.getMessage()
38 |
39 | def causeException = failure.getCause()
40 | while (causeException != null) {
41 | failure = causeException
42 | causeException = failure.getCause()
43 | }
44 | if(failure != causeException) {
45 | logger.withStyle(Style.Failure).println failure.getMessage()
46 | }
47 | println ""
48 | }
49 | }
50 | }
51 |
52 | gradle.useLogger(new CustomExecutionLogger(outLogger))
--------------------------------------------------------------------------------
/embed/android/build-tools/android-metadata-generator.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NativeScript/capacitor/203b85f65af1c7e2a6bb16f3de2d369c9f4bda44/embed/android/build-tools/android-metadata-generator.jar
--------------------------------------------------------------------------------
/embed/android/build-tools/dts-generator.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NativeScript/capacitor/203b85f65af1c7e2a6bb16f3de2d369c9f4bda44/embed/android/build-tools/dts-generator.jar
--------------------------------------------------------------------------------
/embed/android/build-tools/static-binding-generator.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NativeScript/capacitor/203b85f65af1c7e2a6bb16f3de2d369c9f4bda44/embed/android/build-tools/static-binding-generator.jar
--------------------------------------------------------------------------------
/embed/android/debug/java/com/tns/ErrorReport.java:
--------------------------------------------------------------------------------
1 | package com.tns;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.ByteArrayOutputStream;
5 | import java.io.File;
6 | import java.io.FileOutputStream;
7 | import java.io.IOException;
8 | import java.io.InputStreamReader;
9 | import java.io.OutputStreamWriter;
10 | import java.io.PrintStream;
11 | import java.io.UnsupportedEncodingException;
12 | import java.lang.reflect.Method;
13 | import java.text.SimpleDateFormat;
14 | import java.util.Date;
15 |
16 | import android.Manifest;
17 | import android.app.Activity;
18 | import android.app.PendingIntent;
19 | import android.app.PendingIntent.CanceledException;
20 | import android.content.ClipData;
21 | import android.content.ClipboardManager;
22 | import android.content.Context;
23 | import android.content.Intent;
24 | import android.content.pm.PackageManager;
25 | import android.os.Build;
26 | import android.os.Bundle;
27 | import android.os.Environment;
28 | import com.google.android.material.tabs.TabLayout;
29 | import androidx.core.app.ActivityCompat;
30 | import androidx.fragment.app.Fragment;
31 | import androidx.fragment.app.FragmentManager;
32 | import androidx.fragment.app.FragmentStatePagerAdapter;
33 | import androidx.viewpager.widget.ViewPager;
34 | import androidx.appcompat.app.AppCompatActivity;
35 | import androidx.appcompat.widget.Toolbar;
36 | import android.text.method.ScrollingMovementMethod;
37 | import android.util.Log;
38 | import android.view.LayoutInflater;
39 | import android.view.View;
40 | import android.view.ViewGroup;
41 | import android.widget.Button;
42 | import android.widget.TextView;
43 | import android.widget.Toast;
44 |
45 |
46 | class ErrorReport implements TabLayout.OnTabSelectedListener {
47 | public static final String ERROR_FILE_NAME = "hasError";
48 | private static AppCompatActivity activity;
49 |
50 | private TabLayout tabLayout;
51 | private ViewPager viewPager;
52 | private Context context;
53 |
54 | private static String exceptionMsg;
55 | private static String logcatMsg;
56 |
57 | private static boolean checkingForPermissions = false;
58 |
59 | private final static String EXTRA_NATIVESCRIPT_ERROR_REPORT = "NativeScriptErrorMessage";
60 | private final static String EXTRA_ERROR_REPORT_MSG = "msg";
61 | private final static String EXTRA_PID = "pID";
62 | private final static int EXTRA_ERROR_REPORT_VALUE = 1;
63 |
64 | private static final int REQUEST_EXTERNAL_STORAGE = 1;
65 | private static String[] PERMISSIONS_STORAGE = {
66 | Manifest.permission.READ_EXTERNAL_STORAGE,
67 | Manifest.permission.WRITE_EXTERNAL_STORAGE
68 | };
69 |
70 | // Will prevent error activity from killing process if permission request dialog pops up
71 | public static boolean isCheckingForPermissions() {
72 | return checkingForPermissions;
73 | }
74 |
75 | public static void resetCheckingForPermissions() {
76 | checkingForPermissions = false;
77 | }
78 |
79 | // The following will not compile if uncommented with compileSdk lower than 23
80 | public static void verifyStoragePermissions(Activity activity) {
81 | // Check if we have write permission
82 | final int version = Build.VERSION.SDK_INT;
83 | if (version >= 23) {
84 | try {
85 | // Necessary to work around compile errors with compileSdk 22 and lower
86 | Method checkSelfPermissionMethod;
87 | try {
88 | checkSelfPermissionMethod = ActivityCompat.class.getMethod("checkSelfPermission", Context.class, String.class);
89 | } catch (NoSuchMethodException e) {
90 | // method wasn't found, so there is no need to handle permissions explicitly
91 | if (Util.isDebuggableApp(activity)) {
92 | e.printStackTrace();
93 | }
94 | return;
95 | }
96 |
97 | int permission = (int) checkSelfPermissionMethod.invoke(null, activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
98 |
99 | if (permission != PackageManager.PERMISSION_GRANTED) {
100 | // We don't have permission so prompt the user
101 | Method requestPermissionsMethod = ActivityCompat.class.getMethod("requestPermissions", Activity.class, PERMISSIONS_STORAGE.getClass(), int.class);
102 |
103 | checkingForPermissions = true;
104 |
105 | requestPermissionsMethod.invoke(null, activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
106 | }
107 | } catch (Exception e) {
108 | Toast.makeText(activity, "Couldn't resolve permissions", Toast.LENGTH_LONG).show();
109 | if (Util.isDebuggableApp(activity)) {
110 | e.printStackTrace();
111 | }
112 | return;
113 | }
114 | }
115 | }
116 |
117 | public ErrorReport(AppCompatActivity activity) {
118 | ErrorReport.activity = activity;
119 | this.context = activity.getApplicationContext();
120 | }
121 |
122 | static boolean startActivity(final Context context, String errorMessage) {
123 | final Intent intent = getIntent(context);
124 | if (intent == null) {
125 | return false; // (if in release mode) don't do anything
126 | }
127 |
128 | intent.putExtra(EXTRA_ERROR_REPORT_MSG, errorMessage);
129 |
130 | String PID = Integer.toString(android.os.Process.myPid());
131 | intent.putExtra(EXTRA_PID, PID);
132 |
133 | createErrorFile(context);
134 |
135 | try {
136 | startPendingErrorActivity(context, intent);
137 | } catch (CanceledException e) {
138 | Log.d("ErrorReport", "Couldn't send pending intent! Exception: " + e.getMessage());
139 | }
140 |
141 | killProcess(context);
142 |
143 | return true;
144 | }
145 |
146 | static void killProcess(Context context) {
147 | // finish current activity and all below it first
148 | if (context instanceof Activity) {
149 | ((Activity) context).finishAffinity();
150 | }
151 |
152 | // kill process
153 | android.os.Process.killProcess(android.os.Process.myPid());
154 | }
155 |
156 | static void startPendingErrorActivity(Context context, Intent intent) throws CanceledException {
157 | PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
158 |
159 | pendingIntent.send(context, 0, intent);
160 | }
161 |
162 | static String getErrorMessage(Throwable ex) {
163 | String content;
164 | PrintStream ps = null;
165 |
166 | try {
167 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
168 | ps = new PrintStream(baos);
169 | ex.printStackTrace(ps);
170 |
171 | try {
172 | content = baos.toString("UTF-8");
173 | } catch (UnsupportedEncodingException e) {
174 | content = e.getMessage();
175 | }
176 | } finally {
177 | if (ps != null) {
178 | ps.close();
179 | }
180 | }
181 |
182 | return content;
183 | }
184 |
185 | /*
186 | * Gets the process Id of the running app and filters all
187 | * output that doesn't belong to that process
188 | * */
189 | public static String getLogcat(String pId) {
190 | String content;
191 |
192 | try {
193 | String logcatCommand = "logcat -d";
194 | Process process = java.lang.Runtime.getRuntime().exec(logcatCommand);
195 |
196 | BufferedReader bufferedReader = new BufferedReader(
197 | new InputStreamReader(process.getInputStream()));
198 |
199 | StringBuilder log = new StringBuilder();
200 | String line = "";
201 | String lineSeparator = System.getProperty("line.separator");
202 | while ((line = bufferedReader.readLine()) != null) {
203 | if (line.contains(pId)) {
204 | log.append(line);
205 | log.append(lineSeparator);
206 | }
207 | }
208 |
209 | content = log.toString();
210 | } catch (IOException e) {
211 | content = "Failed to read logcat";
212 | Log.e("TNS.Android", content);
213 | }
214 |
215 | return content;
216 | }
217 |
218 | static Intent getIntent(Context context) {
219 | Class> errorActivityClass;
220 |
221 | if (Util.isDebuggableApp(context)) {
222 | errorActivityClass = ErrorReportActivity.class;
223 | } else {
224 | return null;
225 | }
226 |
227 | Intent intent = new Intent(context, errorActivityClass);
228 |
229 | intent.putExtra(EXTRA_NATIVESCRIPT_ERROR_REPORT, EXTRA_ERROR_REPORT_VALUE);
230 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
231 |
232 | return intent;
233 | }
234 |
235 | static boolean hasIntent(Intent intent) {
236 | int value = intent.getIntExtra(EXTRA_NATIVESCRIPT_ERROR_REPORT, 0);
237 |
238 | return value == EXTRA_ERROR_REPORT_VALUE;
239 | }
240 |
241 | void buildUI() {
242 | Intent intent = activity.getIntent();
243 |
244 | exceptionMsg = intent.getStringExtra(EXTRA_ERROR_REPORT_MSG);
245 |
246 | String processId = intent.getStringExtra(EXTRA_PID);
247 | logcatMsg = getLogcat(processId);
248 |
249 | int errActivityId = this.context.getResources().getIdentifier("error_activity", "layout", this.context.getPackageName());
250 |
251 | activity.setContentView(errActivityId);
252 |
253 | int toolBarId = this.context.getResources().getIdentifier("toolbar", "id", this.context.getPackageName());
254 |
255 | Toolbar toolbar = (Toolbar) activity.findViewById(toolBarId);
256 | activity.setSupportActionBar(toolbar);
257 |
258 | final int tabLayoutId = this.context.getResources().getIdentifier("tabLayout", "id", this.context.getPackageName());
259 |
260 | tabLayout = (TabLayout) activity.findViewById(tabLayoutId);
261 | tabLayout.addTab(tabLayout.newTab().setText("Exception"));
262 | tabLayout.addTab(tabLayout.newTab().setText("Logcat"));
263 | tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
264 | int pagerId = this.context.getResources().getIdentifier("pager", "id", this.context.getPackageName());
265 |
266 | viewPager = (ViewPager) activity.findViewById(pagerId);
267 |
268 | Pager adapter = new Pager(activity.getSupportFragmentManager(), tabLayout.getTabCount());
269 |
270 | viewPager.setAdapter(adapter);
271 | viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
272 | @Override
273 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
274 |
275 | }
276 |
277 | @Override
278 | public void onPageSelected(int position) {
279 | tabLayout.getTabAt(position).select();
280 | viewPager.setCurrentItem(position);
281 | }
282 |
283 | @Override
284 | public void onPageScrollStateChanged(int state) {
285 |
286 | }
287 | });
288 |
289 | this.addOnTabSelectedListener(tabLayout);
290 | }
291 |
292 | private void addOnTabSelectedListener(TabLayout tabLayout) {
293 | tabLayout.addOnTabSelectedListener(this);
294 | }
295 | private static void createErrorFile(final Context context) {
296 | try {
297 | File errFile = new File(context.getFilesDir(), ERROR_FILE_NAME);
298 | errFile.createNewFile();
299 | } catch (IOException e) {
300 | Log.d("ErrorReport", e.getMessage());
301 | }
302 | }
303 |
304 | @Override
305 | public void onTabSelected(TabLayout.Tab tab) {
306 | viewPager.setCurrentItem(tab.getPosition());
307 | }
308 |
309 | @Override
310 | public void onTabUnselected(TabLayout.Tab tab) {
311 |
312 | }
313 |
314 | @Override
315 | public void onTabReselected(TabLayout.Tab tab) {
316 | viewPager.setCurrentItem(tab.getPosition());
317 | }
318 |
319 | private class Pager extends FragmentStatePagerAdapter {
320 |
321 | int tabCount;
322 |
323 | @SuppressWarnings("deprecation")
324 | public Pager(FragmentManager fm, int tabCount) {
325 | super(fm);
326 | this.tabCount = tabCount;
327 | }
328 |
329 | @Override
330 | public Fragment getItem(int position) {
331 | switch (position) {
332 | case 0:
333 | return new ExceptionTab();
334 | case 1:
335 | return new LogcatTab();
336 | default:
337 | return null;
338 | }
339 | }
340 |
341 | @Override
342 | public int getCount() {
343 | return tabCount;
344 | }
345 | }
346 |
347 | public static class ExceptionTab extends Fragment {
348 | @Override
349 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
350 | int exceptionTabId = container.getContext().getResources().getIdentifier("exception_tab", "layout", container.getContext().getPackageName());
351 | View view = inflater.inflate(exceptionTabId, container, false);
352 |
353 | int txtViewId = container.getContext().getResources().getIdentifier("txtErrorMsg", "id", container.getContext().getPackageName());
354 | TextView txtErrorMsg = (TextView) view.findViewById(txtViewId);
355 | txtErrorMsg.setText(exceptionMsg);
356 | txtErrorMsg.setMovementMethod(new ScrollingMovementMethod());
357 |
358 | int btnCopyExceptionId = container.getContext().getResources().getIdentifier("btnCopyException", "id", container.getContext().getPackageName());
359 | Button copyToClipboard = (Button) view.findViewById(btnCopyExceptionId);
360 | copyToClipboard.setOnClickListener(new View.OnClickListener() {
361 | @Override
362 | public void onClick(View v) {
363 | ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
364 | ClipData clip = ClipData.newPlainText("nsError", exceptionMsg);
365 | clipboard.setPrimaryClip(clip);
366 | }
367 | });
368 |
369 | return view;
370 | }
371 | }
372 |
373 | public static class LogcatTab extends Fragment {
374 | @Override
375 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
376 | int logcatTabId = container.getContext().getResources().getIdentifier("logcat_tab", "layout", container.getContext().getPackageName());
377 | View view = inflater.inflate(logcatTabId, container, false);
378 |
379 | int textViewId = container.getContext().getResources().getIdentifier("logcatMsg", "id", container.getContext().getPackageName());
380 | TextView txtlogcatMsg = (TextView) view.findViewById(textViewId);
381 | txtlogcatMsg.setText(logcatMsg);
382 |
383 | txtlogcatMsg.setMovementMethod(new ScrollingMovementMethod());
384 |
385 | final String logName = "Log-" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
386 |
387 | int btnCopyLogcatId = container.getContext().getResources().getIdentifier("btnCopyLogcat", "id", container.getContext().getPackageName());
388 | Button copyToClipboard = (Button) view.findViewById(btnCopyLogcatId);
389 | copyToClipboard.setOnClickListener(new View.OnClickListener() {
390 | @Override
391 | public void onClick(View v) {
392 | verifyStoragePermissions(activity);
393 |
394 | if (!isCheckingForPermissions()) {
395 | try {
396 | File dir = new File(Environment.getExternalStorageDirectory().getPath() + "/logcat-reports/");
397 | dir.mkdirs();
398 |
399 | File logcatReportFile = new File(dir, logName);
400 | FileOutputStream stream = new FileOutputStream(logcatReportFile);
401 | OutputStreamWriter writer = new OutputStreamWriter(stream, "UTF-8");
402 | writer.write(logcatMsg);
403 | writer.close();
404 |
405 | String logPath = dir.getPath() + "/" + logName;
406 |
407 | ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
408 | ClipData clip = ClipData.newPlainText("logPath", logPath);
409 | clipboard.setPrimaryClip(clip);
410 |
411 | Toast.makeText(activity, "Path copied to clipboard: " + logPath, Toast.LENGTH_LONG).show();
412 | } catch (Exception e) {
413 | String err = "Could not write logcat report to sdcard. Make sure you have allowed access to external storage!";
414 | Toast.makeText(activity, err, Toast.LENGTH_LONG).show();
415 | if (Util.isDebuggableApp(container.getContext())) {
416 | e.printStackTrace();
417 | }
418 | }
419 | }
420 | }
421 | });
422 |
423 | return view;
424 | }
425 | }
426 | }
427 |
--------------------------------------------------------------------------------
/embed/android/debug/java/com/tns/ErrorReportActivity.java:
--------------------------------------------------------------------------------
1 | package com.tns;
2 |
3 | import android.app.Application;
4 | import android.os.Bundle;
5 | import androidx.annotation.NonNull;
6 | import androidx.appcompat.app.AppCompatActivity;
7 | import android.widget.Toast;
8 |
9 | import java.lang.reflect.Method;
10 |
11 | import static com.tns.ErrorReport.isCheckingForPermissions;
12 | import static com.tns.ErrorReport.resetCheckingForPermissions;
13 |
14 | public class ErrorReportActivity extends AppCompatActivity {
15 | public void onCreate(Bundle savedInstanceState) {
16 | setTheme(androidx.appcompat.R.style.Theme_AppCompat_NoActionBar);
17 |
18 | super.onCreate(savedInstanceState);
19 | Application app = this.getApplication();
20 | Logger logger = new LogcatLogger(app);
21 |
22 | RuntimeHelper.initLiveSync(null, logger, app);
23 |
24 | new ErrorReport(this).buildUI();
25 | }
26 |
27 | @Override
28 | protected void onUserLeaveHint() {
29 | super.onUserLeaveHint();
30 |
31 | if (!isCheckingForPermissions()) {
32 | ErrorReport.killProcess(this);
33 | }
34 | }
35 |
36 | // @Override
37 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
38 | try {
39 | Method onRequestPermissionsResultMethod = AppCompatActivity.class.getMethod("onRequestPermissionsResult", int.class, permissions.getClass(), grantResults.getClass());
40 | onRequestPermissionsResultMethod.invoke(new AppCompatActivity() /* never do this */, requestCode, permissions, grantResults);
41 |
42 | resetCheckingForPermissions();
43 | } catch (Exception e) {
44 | if (Util.isDebuggableApp(this)) {
45 | e.printStackTrace();
46 | }
47 | Toast.makeText(this, "Couldn't resolve permissions", Toast.LENGTH_LONG).show();
48 | resetCheckingForPermissions();
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/embed/android/debug/java/com/tns/NativeScriptSyncService.java:
--------------------------------------------------------------------------------
1 | package com.tns;
2 |
3 | import java.io.Closeable;
4 | import java.io.DataInputStream;
5 | import java.io.File;
6 | import java.io.FileFilter;
7 | import java.io.FileInputStream;
8 | import java.io.FileNotFoundException;
9 | import java.io.FileOutputStream;
10 | import java.io.IOException;
11 | import java.io.InputStream;
12 | import java.io.OutputStream;
13 |
14 | import android.content.Context;
15 | import android.content.pm.ApplicationInfo;
16 | import android.content.pm.PackageManager.NameNotFoundException;
17 | import android.net.LocalServerSocket;
18 | import android.net.LocalSocket;
19 | import android.util.Log;
20 |
21 | public class NativeScriptSyncService {
22 | private static String SYNC_ROOT_SOURCE_DIR = "/data/local/tmp/";
23 | private static final String SYNC_SOURCE_DIR = "/sync/";
24 | private static final String FULL_SYNC_SOURCE_DIR = "/fullsync/";
25 | private static final String REMOVED_SYNC_SOURCE_DIR = "/removedsync/";
26 |
27 | private final Runtime runtime;
28 | private static Logger logger;
29 | private final Context context;
30 |
31 | private final String syncPath;
32 | private final String fullSyncPath;
33 | private final String removedSyncPath;
34 | private final File fullSyncDir;
35 | private final File syncDir;
36 | private final File removedSyncDir;
37 |
38 | private LocalServerSocketThread localServerThread;
39 | private Thread localServerJavaThread;
40 |
41 | public NativeScriptSyncService(Runtime runtime, Logger logger, Context context) {
42 | this.runtime = runtime;
43 | NativeScriptSyncService.logger = logger;
44 | this.context = context;
45 |
46 | syncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + SYNC_SOURCE_DIR;
47 | fullSyncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + FULL_SYNC_SOURCE_DIR;
48 | removedSyncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + REMOVED_SYNC_SOURCE_DIR;
49 | fullSyncDir = new File(fullSyncPath);
50 | syncDir = new File(syncPath);
51 | removedSyncDir = new File(removedSyncPath);
52 | }
53 |
54 | public void sync() {
55 | if (logger != null && logger.isEnabled()) {
56 | logger.write("Sync is enabled:");
57 | logger.write("Sync path : " + syncPath);
58 | logger.write("Full sync path : " + fullSyncPath);
59 | logger.write("Removed files sync path: " + removedSyncPath);
60 | }
61 |
62 | if (fullSyncDir.exists()) {
63 | executeFullSync(context, fullSyncDir);
64 | return;
65 | }
66 |
67 | if (syncDir.exists()) {
68 | executePartialSync(context, syncDir);
69 | }
70 |
71 | if (removedSyncDir.exists()) {
72 | executeRemovedSync(context, removedSyncDir);
73 | }
74 | }
75 |
76 | private class LocalServerSocketThread implements Runnable {
77 | private volatile boolean running;
78 | private final String name;
79 |
80 | private ListenerWorker commThread;
81 | private LocalServerSocket serverSocket;
82 |
83 | public LocalServerSocketThread(String name) {
84 | this.name = name;
85 | this.running = false;
86 | }
87 |
88 | public void stop() {
89 | this.running = false;
90 | try {
91 | serverSocket.close();
92 | } catch (IOException e) {
93 | if (com.tns.Runtime.isDebuggable()) {
94 | e.printStackTrace();
95 | }
96 | }
97 | }
98 |
99 | public void run() {
100 | running = true;
101 | try {
102 | serverSocket = new LocalServerSocket(this.name);
103 | while (running) {
104 | LocalSocket socket = serverSocket.accept();
105 | commThread = new ListenerWorker(socket);
106 | new Thread(commThread).start();
107 | }
108 | } catch (IOException e) {
109 | if (com.tns.Runtime.isDebuggable()) {
110 | e.printStackTrace();
111 | }
112 | }
113 | }
114 | }
115 |
116 | private class ListenerWorker implements Runnable {
117 | private final DataInputStream input;
118 | private Closeable socket;
119 | private OutputStream output;
120 |
121 | public ListenerWorker(LocalSocket socket) throws IOException {
122 | this.socket = socket;
123 | input = new DataInputStream(socket.getInputStream());
124 | output = socket.getOutputStream();
125 | }
126 |
127 | public void run() {
128 | try {
129 | int length = input.readInt();
130 | input.readFully(new byte[length]); // ignore the payload
131 | executePartialSync(context, syncDir);
132 | executeRemovedSync(context, removedSyncDir);
133 |
134 | runtime.runScript(new File(NativeScriptSyncService.this.context.getFilesDir(), "internal/livesync.js"));
135 | try {
136 | output.write(1);
137 | } catch (IOException e) {
138 | if (com.tns.Runtime.isDebuggable()) {
139 | e.printStackTrace();
140 | }
141 | }
142 | socket.close();
143 | } catch (IOException e) {
144 | if (com.tns.Runtime.isDebuggable()) {
145 | e.printStackTrace();
146 | }
147 | }
148 | }
149 | }
150 |
151 | public void startServer() {
152 | localServerThread = new LocalServerSocketThread(context.getPackageName() + "-livesync");
153 | localServerJavaThread = new Thread(localServerThread);
154 | localServerJavaThread.start();
155 | }
156 |
157 | public static boolean isSyncEnabled(Context context) {
158 | int flags;
159 | boolean shouldExecuteSync = false;
160 | try {
161 | flags = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).applicationInfo.flags;
162 | shouldExecuteSync = ((flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
163 | } catch (NameNotFoundException e) {
164 | if (com.tns.Runtime.isDebuggable()) {
165 | e.printStackTrace();
166 | }
167 | return false;
168 | }
169 |
170 | return shouldExecuteSync;
171 | }
172 |
173 | final FileFilter deletingFilesFilter = new FileFilter() {
174 | @Override
175 | public boolean accept(File pathname) {
176 | if (pathname.isDirectory()) {
177 | return true;
178 | }
179 |
180 | boolean success = pathname.delete();
181 | if (!success) {
182 | logger.write("Syncing: file not deleted: " + pathname.getAbsolutePath().toString());
183 | }
184 | return false;
185 | }
186 | };
187 |
188 | private void deleteDir(File directory) {
189 | File[] subDirectories = directory.listFiles(deletingFilesFilter);
190 | if (subDirectories != null) {
191 | for (int i = 0; i < subDirectories.length; i++) {
192 | File subDir = subDirectories[i];
193 | deleteDir(subDir);
194 | }
195 | }
196 |
197 | boolean success = directory.delete();
198 | if (!success && directory.exists()) {
199 | logger.write("Syncing: directory not deleted: " + directory.getAbsolutePath().toString());
200 | }
201 | }
202 |
203 | private void moveFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) {
204 | File[] files = sourceDir.listFiles();
205 |
206 | if (files != null) {
207 | if (logger.isEnabled()) {
208 | logger.write("Syncing total number of fiiles: " + files.length);
209 | }
210 |
211 | for (int i = 0; i < files.length; i++) {
212 | File file = files[i];
213 | if (file.isFile()) {
214 | if (logger.isEnabled()) {
215 | logger.write("Syncing: " + file.getAbsolutePath().toString());
216 | }
217 |
218 | String targetFilePath = file.getAbsolutePath().replace(sourceRootAbsolutePath, targetRootAbsolutePath);
219 | File targetFileDir = new File(targetFilePath);
220 |
221 | File targetParent = targetFileDir.getParentFile();
222 | if (targetParent != null) {
223 | targetParent.mkdirs();
224 | }
225 |
226 | boolean success = copyFile(file.getAbsolutePath(), targetFilePath);
227 | if (!success) {
228 | logger.write("Sync failed: " + file.getAbsolutePath().toString());
229 | }
230 | } else {
231 | moveFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath);
232 | }
233 | }
234 | } else {
235 | if (logger.isEnabled()) {
236 | logger.write("Can't move files. Source is empty.");
237 | }
238 | }
239 | }
240 |
241 | // this removes only the app directory from the device to preserve
242 | // any existing files in /files directory on the device
243 | private void executeFullSync(Context context, final File sourceDir) {
244 | String appPath = context.getFilesDir().getAbsolutePath() + "/app";
245 | final File appDir = new File(appPath);
246 |
247 | if (appDir.exists()) {
248 | deleteDir(appDir);
249 | moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath());
250 | }
251 | }
252 |
253 | private void executePartialSync(Context context, File sourceDir) {
254 | String appPath = context.getFilesDir().getAbsolutePath() + "/app";
255 | final File appDir = new File(appPath);
256 |
257 | if (!appDir.exists()) {
258 | Log.e("TNS", "Application dir does not exists. Partial Sync failed. appDir: " + appPath);
259 | return;
260 | }
261 |
262 | if (logger.isEnabled()) {
263 | logger.write("Syncing sourceDir " + sourceDir.getAbsolutePath() + " with " + appDir.getAbsolutePath());
264 | }
265 |
266 | moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath());
267 | }
268 |
269 | private void deleteRemovedFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) {
270 | if (!sourceDir.exists()) {
271 | if (logger.isEnabled()) {
272 | logger.write("Directory does not exist: " + sourceDir.getAbsolutePath());
273 | }
274 | }
275 |
276 | File[] files = sourceDir.listFiles();
277 |
278 | if (files != null) {
279 | for (int i = 0; i < files.length; i++) {
280 | File file = files[i];
281 | String targetFilePath = file.getAbsolutePath().replace(sourceRootAbsolutePath, targetRootAbsolutePath);
282 | File targetFile = new File(targetFilePath);
283 | if (file.isFile()) {
284 | if (logger.isEnabled()) {
285 | logger.write("Syncing removed file: " + file.getAbsolutePath().toString());
286 | }
287 |
288 | targetFile.delete();
289 | } else {
290 | deleteRemovedFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath);
291 |
292 | // this is done so empty folders, if any, are deleted after we're don deleting files.
293 | if (targetFile.listFiles().length == 0) {
294 | targetFile.delete();
295 | }
296 | }
297 | }
298 | }
299 | }
300 |
301 | private void executeRemovedSync(final Context context, final File sourceDir) {
302 | String appPath = context.getFilesDir().getAbsolutePath() + "/app";
303 | deleteRemovedFiles(sourceDir, sourceDir.getAbsolutePath(), appPath);
304 | }
305 |
306 | private boolean copyFile(String sourceFile, String destinationFile) {
307 | FileInputStream fis = null;
308 | FileOutputStream fos = null;
309 |
310 | try {
311 | fis = new FileInputStream(sourceFile);
312 | fos = new FileOutputStream(destinationFile, false);
313 |
314 | byte[] buffer = new byte[4096];
315 | int read = 0;
316 |
317 | while ((read = fis.read(buffer)) != -1) {
318 | fos.write(buffer, 0, read);
319 | }
320 | } catch (FileNotFoundException e) {
321 | logger.write("Error copying file " + sourceFile);
322 | if (com.tns.Runtime.isDebuggable()) {
323 | e.printStackTrace();
324 | }
325 | return false;
326 | } catch (IOException e) {
327 | logger.write("Error copying file " + sourceFile);
328 | if (com.tns.Runtime.isDebuggable()) {
329 | e.printStackTrace();
330 | }
331 | return false;
332 | } finally {
333 | try {
334 | if (fis != null) {
335 | fis.close();
336 | }
337 | if (fos != null) {
338 | fos.close();
339 | }
340 | } catch (IOException e) {
341 | }
342 | }
343 |
344 | return true;
345 | }
346 | }
--------------------------------------------------------------------------------
/embed/android/debug/java/com/tns/NativeScriptSyncServiceSocketImpl.java:
--------------------------------------------------------------------------------
1 | package com.tns;
2 |
3 | import android.content.Context;
4 | import android.net.LocalServerSocket;
5 | import android.net.LocalSocket;
6 |
7 | import java.io.Closeable;
8 | import java.io.File;
9 | import java.io.FileOutputStream;
10 | import java.io.IOException;
11 | import java.security.DigestInputStream;
12 | import java.security.MessageDigest;
13 | import java.io.OutputStream;
14 | import java.util.Arrays;
15 |
16 | public class NativeScriptSyncServiceSocketImpl {
17 | private static String DEVICE_APP_DIR;
18 |
19 | private final Runtime runtime;
20 | private static Logger logger;
21 | private final Context context;
22 |
23 | private LocalServerSocketThread localServerThread;
24 | private Thread localServerJavaThread;
25 |
26 | public NativeScriptSyncServiceSocketImpl(Runtime runtime, Logger logger, Context context) {
27 | this.runtime = runtime;
28 | NativeScriptSyncServiceSocketImpl.logger = logger;
29 | this.context = context;
30 | DEVICE_APP_DIR = this.context.getFilesDir().getAbsolutePath() + "/app";
31 | }
32 |
33 | private class LocalServerSocketThread implements Runnable {
34 |
35 | private volatile boolean running;
36 | private final String name;
37 |
38 | private LiveSyncWorker livesyncWorker;
39 | private LocalServerSocket deviceSystemSocket;
40 |
41 | public LocalServerSocketThread(String name) {
42 | this.name = name;
43 | this.running = false;
44 | }
45 |
46 | public void stop() {
47 | this.running = false;
48 | try {
49 | deviceSystemSocket.close();
50 | } catch (IOException e) {
51 | if (com.tns.Runtime.isDebuggable()) {
52 | e.printStackTrace();
53 | }
54 | }
55 | }
56 |
57 | public void run() {
58 | running = true;
59 | try {
60 | deviceSystemSocket = new LocalServerSocket(this.name);
61 | while (running) {
62 | LocalSocket systemSocket = deviceSystemSocket.accept();
63 | livesyncWorker = new LiveSyncWorker(systemSocket);
64 | Thread liveSyncThread = setUpLivesyncThread();
65 | liveSyncThread.start();
66 | }
67 | } catch (IOException e) {
68 | if (com.tns.Runtime.isDebuggable()) {
69 | e.printStackTrace();
70 | }
71 | }
72 | catch (java.security.NoSuchAlgorithmException e) {
73 | if (com.tns.Runtime.isDebuggable()) {
74 | e.printStackTrace();
75 | }
76 | }
77 | }
78 |
79 | private Thread setUpLivesyncThread() {
80 | Thread livesyncThread = new Thread(livesyncWorker);
81 | livesyncThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
82 | @Override
83 | public void uncaughtException(Thread t, Throwable e) {
84 | logger.write(String.format("%s(%s): %s", t.getName(), t.getId(), e.toString()));
85 | }
86 | });
87 | livesyncThread.setName("Livesync Thread");
88 | return livesyncThread;
89 | }
90 |
91 | @Override
92 | protected void finalize() throws Throwable {
93 | deviceSystemSocket.close();
94 | }
95 | }
96 |
97 | public void startServer() {
98 | localServerThread = new LocalServerSocketThread(context.getPackageName() + "-livesync");
99 | localServerJavaThread = new Thread(localServerThread);
100 | localServerJavaThread.setName("Livesync Server Thread");
101 | localServerJavaThread.start();
102 | }
103 |
104 | private class LiveSyncWorker implements Runnable {
105 | public static final int OPERATION_BYTE_SIZE = 1;
106 | public static final int HASH_BYTE_SIZE = 16;
107 | public static final int LENGTH_BYTE_SIZE = 1;
108 | public static final int OPERATION_ID_BYTE_SIZE = 32;
109 | public static final int DELETE_FILE_OPERATION = 7;
110 | public static final int CREATE_FILE_OPERATION = 8;
111 | public static final int DO_SYNC_OPERATION = 9;
112 | public static final int ERROR_REPORT_CODE = 1;
113 | public static final int OPERATION_END_NO_REFRESH_REPORT_CODE = 3;
114 | public static final int OPERATION_END_REPORT_CODE = 2;
115 | public static final int REPORT_CODE_SIZE = 1;
116 | public static final int DO_REFRESH_LENGTH = 1;
117 | public static final int DO_REFRESH_VALUE = 1;
118 | public static final String FILE_NAME = "fileName";
119 | public static final String FILE_NAME_LENGTH = FILE_NAME + "Length";
120 | public static final String OPERATION = "operation";
121 | public static final String FILE_CONTENT = "fileContent";
122 | public static final String FILE_CONTENT_LENGTH = FILE_CONTENT + "Length";
123 | public static final int DEFAULT_OPERATION = -1;
124 | private static final String PROTOCOL_VERSION = "0.2.0";
125 | private byte[] handshakeMessage;
126 | private final DigestInputStream input;
127 | private Closeable livesyncSocket;
128 | private OutputStream output;
129 |
130 | public LiveSyncWorker(LocalSocket systemSocket) throws IOException, java.security.NoSuchAlgorithmException {
131 | this.livesyncSocket = systemSocket;
132 | MessageDigest md = MessageDigest.getInstance("MD5");
133 | input = new DigestInputStream(systemSocket.getInputStream(), md);
134 | output = systemSocket.getOutputStream();
135 | handshakeMessage = getHandshakeMessage();
136 | }
137 |
138 | public void run() {
139 | try {
140 | output.write(handshakeMessage);
141 | output.flush();
142 |
143 | } catch (IOException e) {
144 | logger.write(String.format("Error while LiveSyncing: Client socket might be closed!", e.toString()));
145 | if (com.tns.Runtime.isDebuggable()) {
146 | e.printStackTrace();
147 | }
148 | }
149 | try {
150 | do {
151 | int operation = getOperation();
152 |
153 | if (operation == DELETE_FILE_OPERATION) {
154 |
155 | String fileName = getFileName();
156 | validateData();
157 | deleteRecursive(new File(DEVICE_APP_DIR, fileName));
158 |
159 | } else if (operation == CREATE_FILE_OPERATION) {
160 |
161 | String fileName = getFileName();
162 | int contentLength = getFileContentLength(fileName);
163 | validateData();
164 | byte[] content = getFileContent(fileName, contentLength);
165 | validateData();
166 | createOrOverrideFile(fileName, content);
167 |
168 | } else if (operation == DO_SYNC_OPERATION) {
169 | byte[] operationUid = readNextBytes(OPERATION_ID_BYTE_SIZE);
170 | byte doRefresh = readNextBytes(DO_REFRESH_LENGTH)[0];
171 | int doRefreshInt = (int)doRefresh;
172 | int operationReportCode;
173 |
174 | validateData();
175 | if(runtime != null && doRefreshInt == DO_REFRESH_VALUE) {
176 | runtime.runScript(new File(NativeScriptSyncServiceSocketImpl.this.context.getFilesDir(), "internal/livesync.js"), false);
177 | operationReportCode = OPERATION_END_REPORT_CODE;
178 | } else {
179 | operationReportCode = OPERATION_END_NO_REFRESH_REPORT_CODE;
180 | }
181 |
182 | output.write(getReportMessageBytes(operationReportCode, operationUid));
183 | output.flush();
184 |
185 | } else if (operation == DEFAULT_OPERATION) {
186 | logger.write("LiveSync: input stream is empty!");
187 | break;
188 | } else {
189 | throw new IllegalArgumentException(String.format("\nLiveSync: Operation not recognised. Received operation is %s.", operation));
190 | }
191 |
192 | } while (true);
193 | } catch (Exception e) {
194 | String message = String.format("Error while LiveSyncing: %s", e.toString());
195 | this.closeWithError(message);
196 | } catch (Throwable e) {
197 | String message = String.format("%s(%s): Error while LiveSyncing.\nOriginal Exception: %s", Thread.currentThread().getName(), Thread.currentThread().getId(), e.toString());
198 | this.closeWithError(message);
199 | }
200 |
201 | }
202 |
203 | private byte[] getErrorMessageBytes(String message) {
204 | return this.getReportMessageBytes(ERROR_REPORT_CODE, message.getBytes());
205 | }
206 |
207 | private byte[] getReportMessageBytes(int reportType, byte[] messageBytes) {
208 | byte[] reportBytes = new byte[]{(byte)reportType};
209 | byte[] combined = new byte[messageBytes.length + REPORT_CODE_SIZE];
210 |
211 | System.arraycopy(reportBytes,0,combined, 0, REPORT_CODE_SIZE);
212 | System.arraycopy(messageBytes,0,combined, REPORT_CODE_SIZE, messageBytes.length);
213 |
214 | return combined;
215 | }
216 |
217 | private byte[] getHandshakeMessage() {
218 | byte[] protocolVersionBytes = PROTOCOL_VERSION.getBytes();
219 | byte[] versionLength = new byte[]{(byte)protocolVersionBytes.length};
220 | byte[] packageNameBytes = context.getPackageName().getBytes();
221 | byte[] combined = new byte[protocolVersionBytes.length + packageNameBytes.length + versionLength.length];
222 |
223 | System.arraycopy(versionLength,0,combined, 0, versionLength.length);
224 | System.arraycopy(protocolVersionBytes,0,combined, versionLength.length, protocolVersionBytes.length);
225 | System.arraycopy(packageNameBytes,0,combined,protocolVersionBytes.length + versionLength.length,packageNameBytes.length);
226 |
227 | return combined;
228 | }
229 |
230 | private void validateData() throws IOException {
231 | MessageDigest messageDigest = input.getMessageDigest();
232 | byte[] digest = messageDigest.digest();
233 | input.on(false);
234 | byte[] inputMD5 = readNextBytes(HASH_BYTE_SIZE);
235 | input.on(true);
236 |
237 |
238 | if(!Arrays.equals(digest, inputMD5)){
239 | throw new IllegalStateException(String.format("\nLiveSync: Validation of data failed.\nComputed hash: %s\nOriginal hash: %s ", Arrays.toString(digest), Arrays.toString(inputMD5)));
240 | }
241 | }
242 |
243 | /*
244 | * Tries to read operation input stream
245 | * If the stream is empty, method returns -1
246 | * */
247 | private int getOperation() {
248 | Integer operation;
249 | byte[] operationBuff = null;
250 | try {
251 | operationBuff = readNextBytes(OPERATION_BYTE_SIZE);
252 | if (operationBuff == null) {
253 | return DEFAULT_OPERATION;
254 | }
255 | operation = Integer.parseInt(new String(operationBuff));
256 |
257 | } catch (Exception e) {
258 | if(operationBuff == null){
259 | operationBuff = new byte[]{};
260 | }
261 | throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. Bytes read: $s %s\nOriginal Exception: %s", OPERATION, Arrays.toString(operationBuff), e.toString()));
262 | }
263 | return operation;
264 | }
265 |
266 | private String getFileName() {
267 | byte[] fileNameBuffer;
268 | int fileNameLength = -1;
269 |
270 | try {
271 | fileNameLength = getLength();
272 | } catch (Exception e) {
273 | throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. \nOriginal Exception: %s", FILE_NAME_LENGTH, e.toString()));
274 | }
275 |
276 | if(fileNameLength <= 0) {
277 | throw new IllegalStateException(String.format("\nLiveSync: File name length must be positive number or zero. Provided length: %s.", fileNameLength));
278 | }
279 |
280 | try {
281 | fileNameBuffer = readNextBytes(fileNameLength);
282 | } catch (Exception e) {
283 | throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s.\nOriginal Exception: %s", FILE_NAME, e.toString()));
284 | }
285 |
286 | if (fileNameBuffer == null) {
287 | throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes.", FILE_NAME));
288 | }
289 |
290 | String fileName = new String(fileNameBuffer);
291 | if (fileName.trim().length() < fileNameLength) {
292 | logger.write(String.format("WARNING: %s parsed length is less than %s. We read less information than you specified!", FILE_NAME, FILE_NAME_LENGTH));
293 | }
294 |
295 | return fileName.trim();
296 | }
297 |
298 | private int getFileContentLength(String fileName) throws IllegalStateException {
299 | int contentLength;
300 |
301 | try {
302 | contentLength = getLength();
303 | } catch (Exception e) {
304 | throw new IllegalStateException(String.format("\nLiveSync: failed to read %s for %s.\nOriginal Exception: %s", FILE_CONTENT_LENGTH, fileName, e.toString()));
305 | }
306 |
307 | if(contentLength < 0){
308 | throw new IllegalStateException(String.format("\nLiveSync: Content length must be positive number or zero. Provided content length: %s.", contentLength));
309 | }
310 |
311 | return contentLength;
312 | }
313 |
314 | private byte[] getFileContent(String fileName, int contentLength) throws IllegalStateException {
315 | byte[] contentBuff = null;
316 |
317 | try {
318 | if(contentLength > 0) {
319 | contentBuff = readNextBytes(contentLength);
320 | } else if(contentLength == 0){
321 | contentBuff = new byte[]{};
322 | }
323 | } catch (Exception e) {
324 | throw new IllegalStateException(String.format("\nLiveSync: failed to parse content of file: %s.\nOriginal Exception: %s", fileName, e.toString()));
325 | }
326 |
327 | if (contentLength != 0 && contentBuff == null) {
328 | throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes for file: %s. Did you send %s bytes?", FILE_CONTENT, fileName, contentLength));
329 | }
330 |
331 | return contentBuff;
332 | }
333 |
334 | private int getLength() {
335 | byte[] lengthBuffer;
336 | int lengthInt;
337 |
338 | try {
339 | byte lengthSize = readNextBytes(LENGTH_BYTE_SIZE)[0];
340 | //Cast signed byte to unsigned int
341 | int lengthSizeInt = ((int) lengthSize) & 0xFF;
342 | lengthBuffer = readNextBytes(lengthSizeInt);
343 |
344 | if (lengthBuffer == null) {
345 | throw new IllegalStateException(String.format("\nLiveSync: Missing size length bytes."));
346 | }
347 | } catch (Exception e) {
348 | throw new IllegalStateException(String.format("\nLiveSync: Failed to read size length. \nOriginal Exception: %s", e.toString()));
349 | }
350 |
351 | try {
352 | lengthInt = Integer.valueOf(new String(lengthBuffer));
353 | } catch (Exception e) {
354 | throw new IllegalStateException(String.format("\nLiveSync: Failed to parse size length. \nOriginal Exception: %s", e.toString()));
355 | }
356 |
357 | return lengthInt;
358 | }
359 |
360 | private void createOrOverrideFile(String fileName, byte[] content) throws IOException {
361 | File fileToCreate = prepareFile(fileName);
362 | try {
363 |
364 | fileToCreate.getParentFile().mkdirs();
365 | FileOutputStream fos = new FileOutputStream(fileToCreate.getCanonicalPath());
366 | if(runtime != null) {
367 | runtime.lock();
368 | }
369 | fos.write(content);
370 | fos.close();
371 |
372 | } catch (Exception e) {
373 | throw new IOException(String.format("\nLiveSync: failed to write file: %s\nOriginal Exception: %s", fileName, e.toString()));
374 | } finally {
375 | if(runtime != null) {
376 | runtime.unlock();
377 | }
378 | }
379 | }
380 |
381 | void deleteRecursive(File fileOrDirectory) {
382 | if (fileOrDirectory.isDirectory())
383 | for (File child : fileOrDirectory.listFiles()) {
384 | deleteRecursive(child);
385 | }
386 |
387 | fileOrDirectory.delete();
388 | }
389 |
390 | private File prepareFile(String fileName) {
391 | File fileToCreate = new File(DEVICE_APP_DIR, fileName);
392 | if (fileToCreate.exists()) {
393 | fileToCreate.delete();
394 | }
395 | return fileToCreate;
396 | }
397 |
398 | /*
399 | * Reads next bites from input stream. Bytes read depend on passed parameter.
400 | * */
401 | private byte[] readNextBytes(int size) throws IOException {
402 | byte[] buffer = new byte[size];
403 | int bytesRead = 0;
404 | int bufferWriteOffset = bytesRead;
405 | try {
406 | do {
407 |
408 | bytesRead = this.input.read(buffer, bufferWriteOffset, size);
409 | if (bytesRead == -1) {
410 | if (bufferWriteOffset == 0) {
411 | return null;
412 | }
413 | break;
414 | }
415 | size -= bytesRead;
416 | bufferWriteOffset += bytesRead;
417 | } while (size > 0);
418 | } catch (IOException e) {
419 | String message = e.getMessage();
420 | if (message != null && message.equals("Try again")) {
421 | throw new IllegalStateException("Error while LiveSyncing: Read operation timed out.");
422 | } else {
423 | throw e;
424 | }
425 | }
426 |
427 | return buffer;
428 | }
429 |
430 | private void closeWithError(String message) {
431 | try {
432 | output.write(getErrorMessageBytes(message));
433 | output.flush();
434 | logger.write(message);
435 | this.livesyncSocket.close();
436 | } catch (IOException e) {
437 | if (com.tns.Runtime.isDebuggable()) {
438 | e.printStackTrace();
439 | }
440 | }
441 | }
442 |
443 | @Override
444 | protected void finalize() throws Throwable {
445 | this.livesyncSocket.close();
446 | }
447 | }
448 | }
449 |
--------------------------------------------------------------------------------
/embed/android/debug/res/layout/error_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
22 |
23 |
24 |
25 |
39 |
40 |
--------------------------------------------------------------------------------
/embed/android/debug/res/layout/exception_tab.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
25 |
26 |
36 |
37 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/embed/android/debug/res/layout/logcat_tab.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
25 |
26 |
36 |
37 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/embed/android/gradle-helpers/paths.gradle:
--------------------------------------------------------------------------------
1 | import groovy.json.JsonSlurper
2 |
3 | ext.getAppPath = { userDir ->
4 | def relativePathToApp = "app"
5 | def nsConfigFile = file("$userDir/nsconfig.json")
6 | def nsConfig
7 |
8 | if (project.hasProperty("appPath")) {
9 | // when appPath is passed through -PappPath=/path/to/app
10 | // the path could be relative or absolute - either case will work
11 | relativePathToApp = appPath
12 | } else if (nsConfigFile.exists()) {
13 | nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8"))
14 | }
15 |
16 | if (nsConfig != null && nsConfig.appPath != null) {
17 | relativePathToApp = nsConfig.appPath
18 | }
19 |
20 | return java.nio.file.Paths.get(userDir).resolve(relativePathToApp).toAbsolutePath()
21 | }
22 |
23 | ext.getAppResourcesPath = { userDir ->
24 | def relativePathToAppResources
25 | def absolutePathToAppResources
26 | def nsConfigFile = file("$userDir/nsconfig.json")
27 | def nsConfig
28 |
29 | if (nsConfigFile.exists()) {
30 | nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8"))
31 | }
32 |
33 | if (project.hasProperty("appResourcesPath")) {
34 | // when appResourcesPath is passed through -PappResourcesPath=/path/to/App_Resources
35 | // the path could be relative or absolute - either case will work
36 | relativePathToAppResources = ext.appResourcesPath
37 | absolutePathToAppResources = java.nio.file.Paths.get(userDir).resolve(relativePathToAppResources).toAbsolutePath()
38 | } else if (nsConfig != null && nsConfig.appResourcesPath != null) {
39 | relativePathToAppResources = nsConfig.appResourcesPath
40 | absolutePathToAppResources = java.nio.file.Paths.get(userDir).resolve(relativePathToAppResources).toAbsolutePath()
41 | } else {
42 | absolutePathToAppResources = "${getAppPath(userDir)}/App_Resources"
43 | }
44 |
45 | return absolutePathToAppResources
46 | }
--------------------------------------------------------------------------------
/embed/android/gradle-helpers/user_properties_reader.gradle:
--------------------------------------------------------------------------------
1 | def GRADLE_PROPERTIES_FILENAME = "gradle.properties"
2 |
3 | def getFile = { dir, filename ->
4 | File file = new File("$dir$File.separator$filename")
5 | file?.exists() ? file : null
6 | }
7 |
8 | def getPropertyFile = { dir ->
9 | return getFile(dir, GRADLE_PROPERTIES_FILENAME)
10 | }
11 |
12 | ext.getUserProperties = { dir ->
13 | def file = getPropertyFile(dir)
14 | if (!file) {
15 | return null
16 | }
17 |
18 | Properties properties = new Properties()
19 | properties.load(file.newInputStream())
20 |
21 | return properties
22 | }
--------------------------------------------------------------------------------
/embed/android/internal/livesync.js:
--------------------------------------------------------------------------------
1 | if (global.__onLiveSync) {
2 | global.__onLiveSync();
3 | }
--------------------------------------------------------------------------------
/embed/android/internal/ts_helpers.js:
--------------------------------------------------------------------------------
1 | (function () {
2 |
3 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
4 | var c = arguments.length;
5 | var r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
6 |
7 | if (typeof global.Reflect === "object" && typeof global.Reflect.decorate === "function") {
8 | r = global.Reflect.decorate(decorators, target, key, desc);
9 | }
10 | else {
11 | for (var i = decorators.length - 1; i >= 0; i--) {
12 | if (d = decorators[i]) {
13 | r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
14 | }
15 | }
16 | }
17 | return c > 3 && r && Object.defineProperty(target, key, r), r;
18 | };
19 |
20 | // For backward compatibility.
21 | var __native = function (thiz) {
22 | // we are setting the __container__ property to the base class when the super method is called
23 | // if the constructor returns the __native(this) call we will use the old implementation
24 | // copying all the properties to the result
25 | // otherwise if we are using the result from the super() method call we won't need such logic
26 | // as thiz already contains the parent properties
27 | // this way we now support both implementations in typescript generated constructors:
28 | // 1: super(); return __native(this);
29 | // 2: return super() || this;
30 | if (thiz.__container__) {
31 | var result = thiz.__proto__;
32 |
33 | for (var prop in thiz) {
34 | if (thiz.hasOwnProperty(prop)) {
35 | thiz.__proto__[prop] = thiz[prop];
36 | delete thiz[prop];
37 | }
38 | }
39 |
40 | thiz.constructor = undefined;
41 | thiz.__proto__ = undefined;
42 | Object.freeze(thiz);
43 | Object.preventExtensions(thiz);
44 | return result;
45 | } else {
46 | return thiz;
47 | }
48 | };
49 |
50 | var __extends = function (Child, Parent) {
51 | var extendNativeClass = !!Parent.extend && (Parent.extend.toString().indexOf("[native code]") > -1);
52 | if (!extendNativeClass) {
53 | __extends_ts(Child, Parent);
54 | return;
55 | }
56 | if (Parent.__isPrototypeImplementationObject) {
57 | throw new Error("Can not extend an already extended native object.");
58 | }
59 |
60 | function extend(thiz) {
61 | var child = thiz.__proto__.__child;
62 | if (!child.__extended) {
63 | var parent = thiz.__proto__.__parent;
64 | child.__extended = parent.extend(child.name, child.prototype, true);
65 | // This will deal with "i instanceof child"
66 | child[Symbol.hasInstance] = function (instance) {
67 | return instance instanceof this.__extended;
68 | }
69 | }
70 | return child.__extended;
71 | };
72 |
73 | Parent.__activityExtend = function (parent, name, implementationObject) {
74 | __log("__activityExtend called");
75 | return parent.extend(name, implementationObject);
76 | };
77 |
78 | Parent.call = function (thiz) {
79 | var Extended = extend(thiz);
80 | thiz.__container__ = true;
81 | if (arguments.length > 1) {
82 | thiz.__proto__ = new (Function.prototype.bind.apply(Extended, [null].concat(Array.prototype.slice.call(arguments, 1))));
83 | }
84 | else {
85 | thiz.__proto__ = new Extended()
86 | }
87 | return thiz.__proto__;
88 | };
89 |
90 | Parent.apply = function (thiz, args) {
91 | var Extended = extend(thiz);
92 | thiz.__container__ = true;
93 | if (args && args.length > 0) {
94 | thiz.__proto__ = new (Function.prototype.bind.apply(Extended, [null].concat(args)));
95 | }
96 | else {
97 | thiz.__proto__ = new Extended();
98 | }
99 | return thiz.__proto__;
100 | };
101 | __extends_ns(Child, Parent);
102 | Child.__isPrototypeImplementationObject = true;
103 | Child.__proto__ = Parent;
104 | Child.prototype.__parent = Parent;
105 | Child.prototype.__child = Child;
106 | }
107 |
108 | var __extends_ts = function (child, parent) {
109 | extendStaticFunctions(child, parent);
110 | assignPrototypeFromParentToChild(parent, child);
111 | };
112 |
113 | var __extends_ns = function (child, parent) {
114 | if (!parent.extend) {
115 | assignPropertiesFromParentToChild(parent, child);
116 | }
117 |
118 | assignPrototypeFromParentToChild(parent, child);
119 | };
120 |
121 | var extendStaticFunctions =
122 | Object.setPrototypeOf
123 | || (hasInternalProtoProperty() && function (child, parent) { child.__proto__ = parent; })
124 | || assignPropertiesFromParentToChild;
125 |
126 | function hasInternalProtoProperty() {
127 | return { __proto__: [] } instanceof Array;
128 | }
129 |
130 | function assignPropertiesFromParentToChild(parent, child) {
131 | for (var property in parent) {
132 | if (parent.hasOwnProperty(property)) {
133 | child[property] = parent[property];
134 | }
135 | }
136 | }
137 |
138 | function assignPrototypeFromParentToChild(parent, child) {
139 | function __() {
140 | this.constructor = child;
141 | }
142 |
143 | if (parent === null) {
144 | child.prototype = Object.create(null);
145 | } else {
146 | __.prototype = parent.prototype;
147 | child.prototype = new __();
148 | }
149 | }
150 |
151 |
152 | function JavaProxy(className) {
153 | return function (target) {
154 | var extended = target.extend(className, target.prototype)
155 | extended.name = className;
156 | return extended;
157 | };
158 | }
159 |
160 | function Interfaces(interfacesArr) {
161 | return function (target) {
162 | if (interfacesArr instanceof Array) {
163 | // attach interfaces: [] to the object
164 | target.prototype.interfaces = interfacesArr;
165 | }
166 | }
167 | }
168 |
169 | Object.defineProperty(global, "__native", { value: __native });
170 | Object.defineProperty(global, "__extends", { value: __extends });
171 | Object.defineProperty(global, "__decorate", { value: __decorate });
172 |
173 | global.JavaProxy = JavaProxy;
174 | global.Interfaces = Interfaces;
175 | })()
--------------------------------------------------------------------------------
/embed/android/libs/runtime-libs/nativescript-optimized-with-inspector.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NativeScript/capacitor/203b85f65af1c7e2a6bb16f3de2d369c9f4bda44/embed/android/libs/runtime-libs/nativescript-optimized-with-inspector.aar
--------------------------------------------------------------------------------
/embed/android/libs/runtime-libs/nativescript-optimized.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NativeScript/capacitor/203b85f65af1c7e2a6bb16f3de2d369c9f4bda44/embed/android/libs/runtime-libs/nativescript-optimized.aar
--------------------------------------------------------------------------------
/embed/android/libs/runtime-libs/nativescript-regular.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NativeScript/capacitor/203b85f65af1c7e2a6bb16f3de2d369c9f4bda44/embed/android/libs/runtime-libs/nativescript-regular.aar
--------------------------------------------------------------------------------
/embed/android/main/java/com/tns/AndroidJsV8Inspector.java:
--------------------------------------------------------------------------------
1 | package com.tns;
2 |
3 | import android.os.Handler;
4 | import android.util.Log;
5 | import android.util.Pair;
6 | import android.webkit.MimeTypeMap;
7 |
8 | import org.json.JSONArray;
9 | import org.json.JSONException;
10 | import org.json.JSONObject;
11 |
12 | import java.io.File;
13 | import java.io.IOException;
14 | import java.lang.reflect.Array;
15 | import java.util.ArrayList;
16 | import java.util.LinkedList;
17 | import java.util.List;
18 | import java.util.Queue;
19 | import java.util.concurrent.LinkedBlockingQueue;
20 | import java.util.concurrent.atomic.AtomicBoolean;
21 |
22 | import fi.iki.elonen.NanoHTTPD;
23 | import fi.iki.elonen.NanoWSD;
24 |
25 | class AndroidJsV8Inspector {
26 | private JsV8InspectorServer server;
27 | private static String ApplicationDir;
28 | private String packageName;
29 |
30 | protected native final void init();
31 |
32 | protected native final void connect(Object connection);
33 |
34 | private native void scheduleBreak();
35 |
36 | protected static native void disconnect();
37 |
38 | protected native final void dispatchMessage(String message);
39 |
40 | private Handler mainHandler;
41 |
42 | private final Object debugBrkLock;
43 |
44 | private Logger currentRuntimeLogger;
45 |
46 | private static AtomicBoolean ReadyToProcessMessages = new AtomicBoolean(false);
47 |
48 | private LinkedBlockingQueue inspectorMessages = new LinkedBlockingQueue();
49 | private LinkedBlockingQueue pendingInspectorMessages = new LinkedBlockingQueue();
50 |
51 | AndroidJsV8Inspector(String filesDir, String packageName) {
52 | ApplicationDir = filesDir;
53 | this.packageName = packageName;
54 | this.debugBrkLock = new Object();
55 | }
56 |
57 | public void start() throws IOException {
58 | if (this.server == null) {
59 | Runtime currentRuntime = Runtime.getCurrentRuntime();
60 |
61 | mainHandler = currentRuntime.getHandler();
62 |
63 | currentRuntimeLogger = currentRuntime.getLogger();
64 |
65 | this.server = new JsV8InspectorServer(this.packageName + "-inspectorServer", currentRuntimeLogger);
66 | this.server.start(-1);
67 |
68 | if (currentRuntimeLogger.isEnabled()) {
69 | Log.d("V8Inspector", "start debugger ThreadId:" + Thread.currentThread().getId());
70 | }
71 |
72 | init();
73 | }
74 | }
75 |
76 | @RuntimeCallable
77 | private static void sendToDevToolsConsole(Object connection, String message, String level) {
78 | try {
79 | JSONObject consoleMessage = new JSONObject();
80 |
81 | JSONObject params = new JSONObject();
82 | params.put("type", level);
83 | params.put("executionContextId", 0);
84 | params.put("timestamp", 0.000000000000000);
85 |
86 | JSONArray args = new JSONArray();
87 | args.put(message);
88 | params.put("args", args);
89 |
90 | consoleMessage.put("method", "Runtime.consoleAPICalled");
91 | consoleMessage.put("params", params);
92 |
93 | String sendingText = consoleMessage.toString();
94 | AndroidJsV8Inspector.send(connection, sendingText);
95 |
96 | } catch (JSONException | IOException e) {
97 | if (Runtime.isDebuggable()) {
98 | e.printStackTrace();
99 | }
100 | }
101 | }
102 |
103 | @RuntimeCallable
104 | private static void send(Object connection, String payload) throws IOException {
105 | JsV8InspectorWebSocket socketConnection = (JsV8InspectorWebSocket) connection;
106 | if (socketConnection.isOpen()) {
107 | socketConnection.send(payload);
108 | }
109 | }
110 |
111 | @RuntimeCallable
112 | private static String getInspectorMessage(Object connection) {
113 | return ((JsV8InspectorWebSocket) connection).getInspectorMessage();
114 | }
115 |
116 | @RuntimeCallable
117 | public static Pair[] getPageResources() {
118 | // necessary to align the data dir returned by context (emulator) and that used by the v8 inspector
119 | if (ApplicationDir.startsWith("/data/user/0/")) {
120 | ApplicationDir = ApplicationDir.replaceFirst("/data/user/0/", "/data/data/");
121 | }
122 |
123 | String dataDir = ApplicationDir;
124 | File rootFilesDir = new File(dataDir, "app");
125 |
126 |
127 | List> resources = traverseResources(rootFilesDir);
128 |
129 | @SuppressWarnings("unchecked")
130 | Pair[] result = resources.toArray((Pair[]) Array.newInstance(resources.get(0).getClass(), resources.size()));
131 | return result;
132 | }
133 |
134 | private static List> traverseResources(File dir) {
135 | List> resources = new ArrayList<>();
136 |
137 | Queue directories = new LinkedList<>();
138 | directories.add(dir);
139 |
140 | while (!directories.isEmpty()) {
141 | File currentDir = directories.poll();
142 |
143 | File[] files = currentDir.listFiles();
144 | for (File file : files) {
145 | if (file.isDirectory()) {
146 | directories.add(file);
147 | } else {
148 | resources.add(new Pair<>("file://" + file.getAbsolutePath(), getMimeType(file.getAbsolutePath())));
149 | }
150 | }
151 | }
152 |
153 | return resources;
154 | }
155 |
156 | private static String getMimeType(String url) {
157 | String type = null;
158 | String extension = MimeTypeMap.getFileExtensionFromUrl(url);
159 | if (!extension.isEmpty()) {
160 | type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
161 |
162 | // getMimeType may sometime return incorrect results in the context of NativeScript
163 | // e.g. `.ts` returns video/MP2TS
164 | switch (extension) {
165 | case "js":
166 | type = "text/javascript";
167 | break;
168 | case "json":
169 | type = "application/json";
170 | break;
171 | case "css":
172 | type = "text/css";
173 | break;
174 | case "ts":
175 | type = "text/typescript";
176 | break;
177 | // handle shared libraries so they are marked properly and don't appear in the sources tab
178 | case "so":
179 | type = "application/binary";
180 | break;
181 | }
182 | }
183 |
184 | return type;
185 | }
186 |
187 | // pause the main thread for 30 seconds (30 * 1000 ms)
188 | // allowing the devtools frontend to establish connection with the inspector
189 | protected void waitForDebugger(boolean shouldBreak) {
190 | if (shouldBreak) {
191 | synchronized (this.debugBrkLock) {
192 | try {
193 | this.debugBrkLock.wait(1000 * 30);
194 | } catch (InterruptedException e) {
195 | if (Runtime.isDebuggable()) {
196 | e.printStackTrace();
197 | }
198 | } finally {
199 | AndroidJsV8Inspector.ReadyToProcessMessages.set(true);
200 | this.processDebugBreak();
201 | }
202 | }
203 | } else {
204 | AndroidJsV8Inspector.ReadyToProcessMessages.set(true);
205 | }
206 | }
207 |
208 | // process all messages coming from the frontend necessary to initialize the inspector backend
209 | // schedule a debug line break at first convenience
210 | private void processDebugBreak() {
211 | processDebugBreakMessages();
212 | scheduleBreak();
213 | }
214 |
215 | private void processDebugBreakMessages() {
216 | while (!pendingInspectorMessages.isEmpty()) {
217 | String inspectorMessage = pendingInspectorMessages.poll();
218 | dispatchMessage(inspectorMessage);
219 | }
220 | }
221 |
222 | private class JsV8InspectorServer extends NanoWSD {
223 | private Logger currentRuntimeLogger;
224 |
225 | JsV8InspectorServer(String name, Logger runtimeLogger) {
226 | super(name);
227 | currentRuntimeLogger = runtimeLogger;
228 | }
229 |
230 | @Override
231 | protected Response serveHttp(IHTTPSession session) {
232 | if (currentRuntimeLogger.isEnabled()) {
233 | Log.d("{N}.v8-inspector", "http request for " + session.getUri());
234 | }
235 | return super.serveHttp(session);
236 | }
237 |
238 | private JsV8InspectorWebSocket webSocket;
239 |
240 | @Override
241 | protected WebSocket openWebSocket(IHTTPSession handshake) {
242 | // close the previous webSocket
243 | if(this.webSocket != null) {
244 | try {
245 | this.webSocket.close(WebSocketFrame.CloseCode.NormalClosure, "New browser connection is open", false);
246 | } catch (IOException ioException) {
247 | if(this.webSocket.getState() != State.CLOSED) {
248 | Log.e("{N}.v8-inspector", "Error closing previous connection", ioException);
249 | }
250 | }
251 | }
252 | this.webSocket = new JsV8InspectorWebSocket(handshake, currentRuntimeLogger);
253 | return this.webSocket;
254 | }
255 | }
256 |
257 | private class JsV8InspectorWebSocket extends NanoWSD.WebSocket {
258 | private Logger currentRuntimeLogger;
259 |
260 | JsV8InspectorWebSocket(NanoHTTPD.IHTTPSession handshakeRequest, Logger runtimeLogger) {
261 | super(handshakeRequest);
262 | currentRuntimeLogger = runtimeLogger;
263 | }
264 |
265 | @Override
266 | protected void onOpen() {
267 | if (currentRuntimeLogger.isEnabled()) {
268 | Log.d("V8Inspector", "onOpen: ThreadID: " + Thread.currentThread().getId());
269 | }
270 |
271 | connect(JsV8InspectorWebSocket.this);
272 | }
273 |
274 | @Override
275 | protected void onClose(NanoWSD.WebSocketFrame.CloseCode code, String reason, boolean initiatedByRemote) {
276 | if (currentRuntimeLogger.isEnabled()) {
277 | Log.d("V8Inspector", "onClose");
278 | }
279 |
280 | mainHandler.post(new Runnable() {
281 | @Override
282 | public void run() {
283 | if (currentRuntimeLogger.isEnabled()) {
284 | Log.d("V8Inspector", "Disconnecting");
285 | }
286 | disconnect();
287 | }
288 | });
289 | }
290 |
291 | @Override
292 | protected void onMessage(final NanoWSD.WebSocketFrame message) {
293 | if (currentRuntimeLogger.isEnabled()) {
294 | Log.d("V8Inspector", "To dbg backend: " + message.getTextPayload() + " ThreadId:" + Thread.currentThread().getId());
295 | }
296 |
297 | inspectorMessages.offer(message.getTextPayload());
298 |
299 | if (!AndroidJsV8Inspector.ReadyToProcessMessages.get()) {
300 | String nextMessage = inspectorMessages.poll();
301 | while (nextMessage != null) {
302 | pendingInspectorMessages.offer(nextMessage);
303 | nextMessage = inspectorMessages.poll();
304 | }
305 |
306 | if (message.getTextPayload().contains("Debugger.enable")) {
307 | synchronized (debugBrkLock) {
308 | debugBrkLock.notify();
309 | }
310 | }
311 | } else {
312 | mainHandler.postAtFrontOfQueue(new Runnable() {
313 | @Override
314 | public void run() {
315 | String nextMessage = inspectorMessages.poll();
316 | while (nextMessage != null) {
317 | dispatchMessage(nextMessage);
318 | nextMessage = inspectorMessages.poll();
319 | }
320 | }
321 | });
322 | }
323 | }
324 |
325 | @Override
326 | public void send(String payload) throws IOException {
327 | if (currentRuntimeLogger.isEnabled()) {
328 | Log.d("V8Inspector", "To dbg client: " + payload);
329 | }
330 |
331 | super.send(payload);
332 | }
333 |
334 | public String getInspectorMessage() {
335 | try {
336 | return inspectorMessages.take();
337 | } catch (InterruptedException e) {
338 | if (Runtime.isDebuggable()) {
339 | e.printStackTrace();
340 | }
341 | }
342 |
343 | return null;
344 | }
345 |
346 | @Override
347 | protected void onPong(NanoWSD.WebSocketFrame pong) {
348 | }
349 |
350 | @Override
351 | protected void onException(IOException exception) {
352 | // when the chrome inspector is disconnected by closing the tab a "Broken pipe" exception is thrown which we don't need to log, only in verbose logging mode
353 | if(!exception.getMessage().equals("Broken pipe") || currentRuntimeLogger.isEnabled()) {
354 | if (Runtime.isDebuggable()) {
355 | exception.printStackTrace();
356 | }
357 | }
358 | disconnect();
359 | }
360 | }
361 | }
362 |
--------------------------------------------------------------------------------
/embed/android/main/java/com/tns/DefaultExtractPolicy.java:
--------------------------------------------------------------------------------
1 | package com.tns;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.BufferedWriter;
5 | import java.io.File;
6 | import java.io.FileInputStream;
7 | import java.io.FileNotFoundException;
8 | import java.io.FileOutputStream;
9 | import java.io.IOException;
10 | import java.io.InputStreamReader;
11 | import java.io.OutputStreamWriter;
12 |
13 | import android.content.Context;
14 | import android.content.pm.PackageInfo;
15 | import android.content.pm.PackageManager.NameNotFoundException;
16 |
17 | import androidx.core.content.pm.PackageInfoCompat;
18 |
19 | public class DefaultExtractPolicy implements ExtractPolicy {
20 | private final Logger logger;
21 |
22 | private final static String ASSETS_THUMB_FILENAME = "assetsThumb";
23 |
24 | public DefaultExtractPolicy(Logger logger) {
25 | this.logger = logger;
26 | }
27 |
28 | public boolean shouldExtract(Context context) {
29 | String assetsThumbFilePath = getFilesDir(context) + File.separatorChar + ASSETS_THUMB_FILENAME;
30 | String oldAssetsThumb = getCachedAssetsThumb(assetsThumbFilePath);
31 | if (oldAssetsThumb == null) {
32 | return true;
33 | } else {
34 | String currentThumb = getAssetsThumb(context);
35 |
36 | if (currentThumb != null && !currentThumb.equals(oldAssetsThumb)) {
37 | return true;
38 | }
39 | }
40 |
41 | return false;
42 | }
43 |
44 | public void setAssetsThumb(Context context) {
45 | String assetsThumb = generateAssetsThumb(context);
46 | if (assetsThumb != null) {
47 | String assetsThumbFilePath = getFilesDir(context) + File.separatorChar + ASSETS_THUMB_FILENAME;
48 | saveNewAssetsThumb(assetsThumb, assetsThumbFilePath);
49 | }
50 | }
51 |
52 | public boolean forceOverwrite() {
53 | return true;
54 | }
55 |
56 | public FileExtractor extractor() {
57 | return null;
58 | }
59 |
60 | public String getAssetsThumb(Context context) {
61 | return generateAssetsThumb(context);
62 | }
63 |
64 | private String generateAssetsThumb(Context context) {
65 | try {
66 | PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
67 | long code = PackageInfoCompat.getLongVersionCode(packageInfo);
68 | long updateTime = packageInfo.lastUpdateTime;
69 | return updateTime + "-" + code;
70 | } catch (NameNotFoundException e) {
71 | logger.write("Error while getting current assets thumb");
72 | if (com.tns.Runtime.isDebuggable()) {
73 | e.printStackTrace();
74 | }
75 | }
76 |
77 | return null;
78 | }
79 |
80 | private String getCachedAssetsThumb(String assetsThumbFilePath) {
81 | try {
82 | File cachedThumbFile = new File(assetsThumbFilePath);
83 | if (cachedThumbFile.exists()) {
84 | FileInputStream in = new FileInputStream(cachedThumbFile);
85 | BufferedReader reader = new BufferedReader(new InputStreamReader(in));
86 | String cachedThumb = reader.readLine();
87 | reader.close();
88 | in.close();
89 | return cachedThumb;
90 | }
91 | } catch (FileNotFoundException e) {
92 | logger.write("Error while getting current assets thumb");
93 | if (com.tns.Runtime.isDebuggable()) {
94 | e.printStackTrace();
95 | }
96 | } catch (IOException e) {
97 | logger.write("Error while getting current asstes thumb");
98 | if (com.tns.Runtime.isDebuggable()) {
99 | e.printStackTrace();
100 | }
101 | }
102 |
103 | return null;
104 | }
105 |
106 | private void saveNewAssetsThumb(String newThumb, String assetsThumbFile) {
107 | File cachedThumbFile = new File(assetsThumbFile);
108 | try {
109 | FileOutputStream out = new FileOutputStream(cachedThumbFile, false);
110 | BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
111 | try {
112 | writer.write(newThumb);
113 | writer.newLine();
114 | writer.flush();
115 | } finally {
116 | writer.close();
117 | out.close();
118 | }
119 | } catch (FileNotFoundException e) {
120 | logger.write("Error while writing current assets thumb");
121 | if (com.tns.Runtime.isDebuggable()) {
122 | e.printStackTrace();
123 | }
124 | } catch (IOException e) {
125 | logger.write("Error while writing current assets thumb");
126 | if (com.tns.Runtime.isDebuggable()) {
127 | e.printStackTrace();
128 | }
129 | }
130 | }
131 |
132 | /*
133 | Write assetsThumb file to a no-backup directory to prevent the thumb from being
134 | backed up on devices of API Level 23 and up.
135 | Devices Level 22 and lower don't support the Auto Backup feature,
136 | so it is safe to keep the thumb in the /files directory
137 | */
138 | private static String getFilesDir(Context context) {
139 | if (android.os.Build.VERSION.SDK_INT >= /* 21 */ android.os.Build.VERSION_CODES.LOLLIPOP) {
140 | return context.getNoBackupFilesDir().getPath();
141 | } else {
142 | return context.getFilesDir().getPath();
143 | }
144 | }
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/embed/android/main/java/com/tns/EqualityComparator.java:
--------------------------------------------------------------------------------
1 | package com.tns;
2 |
3 | public final class EqualityComparator {
4 | public static boolean areReferencesEqual(Object a, Object b) {
5 | return a == b;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/embed/android/main/java/com/tns/LogcatLogger.java:
--------------------------------------------------------------------------------
1 | package com.tns;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 |
6 | public final class LogcatLogger implements Logger {
7 | private final static String DEFAULT_LOG_TAG = "TNS.Java";
8 |
9 | private boolean enabled;
10 |
11 | public LogcatLogger(Context context) {
12 | this.initLogging(context);
13 | }
14 |
15 | public final boolean isEnabled() {
16 | return enabled;
17 | }
18 |
19 | public final void setEnabled(boolean isEnabled) {
20 | enabled = isEnabled;
21 | }
22 |
23 | public final void write(String msg) {
24 | if (this.enabled) {
25 | Log.d(DEFAULT_LOG_TAG, msg);
26 | }
27 | }
28 |
29 | public final void write(String tag, String msg) {
30 | if (this.enabled) {
31 | Log.d(tag, msg);
32 | }
33 | }
34 |
35 | private void initLogging(Context context) {
36 | boolean isDebuggableApp = Util.isDebuggableApp(context);
37 | if (isDebuggableApp) {
38 | String verboseLoggingProp = Util.readSystemProperty("nativescript.verbose.logging");
39 |
40 | if (Util.isPositive(verboseLoggingProp)) {
41 | setEnabled(true);
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/embed/android/main/java/com/tns/NativeScriptApplication.java:
--------------------------------------------------------------------------------
1 | package com.tns;
2 |
3 | import android.app.Application;
4 | import com.tns.Runtime;
5 | import com.tns.RuntimeHelper;
6 | import java.io.File;
7 |
8 | public class NativeScriptApplication extends Application {
9 | Runtime rt;
10 | private static NativeScriptApplication thiz;
11 |
12 | public NativeScriptApplication() {
13 | thiz = this;
14 | }
15 |
16 | public void onCreate() {
17 | super.onCreate();
18 | rt = RuntimeHelper.initRuntime(this);
19 | if(rt != null){
20 | File file = new File(getFilesDir(), "public/nativescript/index.js");
21 | rt.runModule(file);
22 | }
23 | }
24 |
25 | public static Application getInstance() {
26 | return thiz;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/embed/android/main/java/com/tns/NativeScriptUncaughtExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.tns;
2 |
3 | import java.lang.Thread.UncaughtExceptionHandler;
4 | import android.content.Context;
5 | import android.util.Log;
6 |
7 | public class NativeScriptUncaughtExceptionHandler implements UncaughtExceptionHandler {
8 | private final Context context;
9 |
10 | private final UncaughtExceptionHandler defaultHandler;
11 |
12 | private final Logger logger;
13 |
14 | public NativeScriptUncaughtExceptionHandler(Logger logger, Context context) {
15 | this.logger = logger;
16 | this.context = context;
17 | defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
18 | }
19 |
20 | @Override
21 | public void uncaughtException(Thread thread, Throwable ex) {
22 | String currentThreadMessage = String.format("An uncaught Exception occurred on \"%s\" thread.\n%s\n", thread.getName(), ex.getMessage());
23 | String stackTraceErrorMessage = Runtime.getStackTraceErrorMessage(ex);
24 | String errorMessage = String.format("%s\nStackTrace:\n%s", currentThreadMessage, stackTraceErrorMessage);
25 | if (Runtime.isInitialized()) {
26 | try {
27 | if (Util.isDebuggableApp(context)) {
28 | System.err.println(errorMessage);
29 | }
30 |
31 | Runtime runtime = Runtime.getCurrentRuntime();
32 |
33 | if (runtime != null) {
34 | runtime.passUncaughtExceptionToJs(ex, ex.getMessage(), stackTraceErrorMessage, Runtime.getJSStackTrace(ex));
35 | }
36 | } catch (Throwable t) {
37 | if (Util.isDebuggableApp(context)) {
38 | t.printStackTrace();
39 | }
40 | }
41 | }
42 |
43 | if (logger.isEnabled()) {
44 | logger.write("Uncaught Exception Message=" + errorMessage);
45 | }
46 |
47 | boolean res = false;
48 |
49 | if (Util.isDebuggableApp(context)) {
50 | try {
51 | Class> ErrReport = null;
52 | java.lang.reflect.Method startActivity = null;
53 |
54 | ErrReport = Class.forName("com.tns.ErrorReport");
55 |
56 | startActivity = ErrReport.getDeclaredMethod("startActivity", Context.class, String.class);
57 |
58 | res = (Boolean) startActivity.invoke(null, context, errorMessage);
59 | } catch (Exception e) {
60 | android.util.Log.v("Error", errorMessage);
61 | if (Util.isDebuggableApp(context)) {
62 | e.printStackTrace();
63 | };
64 | android.util.Log.v("Application Error", "ErrorActivity default implementation not found. Reinstall android platform to fix.");
65 | }
66 | }
67 |
68 | if (!res && defaultHandler != null) {
69 | defaultHandler.uncaughtException(thread, ex);
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/embed/android/main/java/com/tns/RuntimeHelper.java:
--------------------------------------------------------------------------------
1 | package com.tns;
2 |
3 | import android.app.Application;
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.IntentFilter;
8 | import android.content.SharedPreferences;
9 | import android.content.pm.PackageManager.NameNotFoundException;
10 | import android.os.Build;
11 | import android.preference.PreferenceManager;
12 | import android.util.Log;
13 |
14 | import java.io.File;
15 | import java.io.IOException;
16 | import java.lang.reflect.Constructor;
17 | import java.lang.reflect.Field;
18 | import java.lang.reflect.InvocationTargetException;
19 | import java.lang.reflect.Method;
20 | import java.util.TimeZone;
21 |
22 | public final class RuntimeHelper {
23 | private RuntimeHelper() {
24 | }
25 |
26 | private static AndroidJsV8Inspector v8Inspector;
27 |
28 | // hasErrorIntent tells you if there was an event (with an uncaught
29 | // exception) raised from ErrorReport
30 | private static boolean hasErrorIntent(Context context) {
31 | boolean hasErrorIntent = false;
32 |
33 | try {
34 | // empty file just to check if there was a raised uncaught error by
35 | // ErrorReport
36 | if (Util.isDebuggableApp(context)) {
37 | String fileName;
38 |
39 | try {
40 | Class> ErrReport = Class.forName("com.tns.ErrorReport");
41 | Field field = ErrReport.getDeclaredField("ERROR_FILE_NAME");
42 | fileName = (String) field.get(null);
43 | } catch (Exception e) {
44 | return false;
45 | }
46 |
47 | File errFile = new File(context.getFilesDir(), fileName);
48 |
49 | if (errFile.exists()) {
50 | errFile.delete();
51 | hasErrorIntent = true;
52 | }
53 | }
54 | } catch (Exception e) {
55 | Log.d(logTag, e.getMessage());
56 | }
57 |
58 | return hasErrorIntent;
59 | }
60 |
61 | public static Runtime initRuntime(Context context) {
62 | if (Runtime.isInitialized()) {
63 | return Runtime.getCurrentRuntime();
64 | }
65 |
66 | ManualInstrumentation.Frame frame = ManualInstrumentation.start("RuntimeHelper.initRuntime");
67 | try {
68 | ManualInstrumentation.Frame loadLibraryFrame = ManualInstrumentation.start("loadLibrary NativeScript");
69 | try {
70 | System.loadLibrary("NativeScript");
71 | } finally {
72 | loadLibraryFrame.close();
73 | }
74 |
75 | Logger logger = new LogcatLogger(context);
76 |
77 | Runtime.nativeLibraryLoaded = true;
78 | Runtime runtime = null;
79 | boolean showErrorIntent = hasErrorIntent(context);
80 | if (!showErrorIntent) {
81 | NativeScriptUncaughtExceptionHandler exHandler = new NativeScriptUncaughtExceptionHandler(logger, context);
82 |
83 | Thread.setDefaultUncaughtExceptionHandler(exHandler);
84 |
85 | DefaultExtractPolicy extractPolicy = new DefaultExtractPolicy(logger);
86 | boolean skipAssetExtraction = Util.runPlugin(logger, context);
87 |
88 | String appName = context.getPackageName();
89 | File rootDir = new File(context.getApplicationInfo().dataDir);
90 | File appDir = context.getFilesDir();
91 |
92 | try {
93 | appDir = appDir.getCanonicalFile();
94 | } catch (IOException e1) {
95 | e1.printStackTrace();
96 | }
97 |
98 | if (!skipAssetExtraction) {
99 | ManualInstrumentation.Frame extractionFrame = ManualInstrumentation.start("Extracting assets");
100 | try {
101 | if (logger.isEnabled()) {
102 | logger.write("Extracting assets...");
103 | }
104 |
105 | AssetExtractor aE = new AssetExtractor(null, logger);
106 |
107 | String outputDir = context.getFilesDir().getPath() + File.separator;
108 |
109 | // will force deletion of previously extracted files in app/files directories
110 | // see https://github.com/NativeScript/NativeScript/issues/4137 for reference
111 | boolean removePreviouslyInstalledAssets = true;
112 | File metadata = new File(outputDir + "metadata");
113 | if (metadata.exists()) {
114 | metadata.delete();
115 | }
116 | aE.extractAssets(context, "public", outputDir, extractPolicy, removePreviouslyInstalledAssets);
117 | aE.extractAssets(context, "internal", outputDir, extractPolicy, removePreviouslyInstalledAssets);
118 | aE.extractAssets(context, "metadata", outputDir, extractPolicy, false);
119 |
120 | boolean shouldExtractSnapshots = true;
121 |
122 | // will extract snapshot of the device appropriate architecture
123 | if (shouldExtractSnapshots) {
124 | if (logger.isEnabled()) {
125 | logger.write("Extracting snapshot blob");
126 | }
127 |
128 | @SuppressWarnings("deprecation")
129 | String cpu_abi = Build.CPU_ABI;
130 | aE.extractAssets(context, "snapshots/" + cpu_abi, outputDir, extractPolicy, removePreviouslyInstalledAssets);
131 | }
132 |
133 | extractPolicy.setAssetsThumb(context);
134 | } finally {
135 | extractionFrame.close();
136 | }
137 | }
138 |
139 | AppConfig appConfig = new AppConfig(appDir);
140 | ManualInstrumentation.setMode(appConfig.getProfilingMode());
141 |
142 | ClassLoader classLoader = context.getClassLoader();
143 | File dexDir = new File(rootDir, "code_cache/secondary-dexes");
144 | String dexThumb = null;
145 | try {
146 | dexThumb = Util.getDexThumb(context);
147 | } catch (NameNotFoundException e) {
148 | if (logger.isEnabled()) {
149 | logger.write("Error while getting current proxy thumb");
150 | }
151 | if (Util.isDebuggableApp(context)) {
152 | e.printStackTrace();
153 | }
154 | }
155 |
156 | String nativeLibDir = null;
157 | try {
158 | nativeLibDir = context.getPackageManager().getApplicationInfo(appName, 0).nativeLibraryDir;
159 | } catch (NameNotFoundException e) {
160 | if (Util.isDebuggableApp(context)) {
161 | e.printStackTrace();
162 | }
163 | }
164 |
165 | boolean isDebuggable = Util.isDebuggableApp(context);
166 | StaticConfiguration config = new StaticConfiguration(logger, appName, nativeLibDir, rootDir,
167 | appDir, classLoader, dexDir, dexThumb, appConfig, isDebuggable);
168 |
169 | runtime = Runtime.initializeRuntimeWithConfiguration(config);
170 | if (isDebuggable) {
171 | try {
172 | v8Inspector = new AndroidJsV8Inspector(context.getFilesDir().getAbsolutePath(), context.getPackageName());
173 | v8Inspector.start();
174 |
175 | // the following snippet is used as means to notify the VSCode extension
176 | // debugger that the debugger agent has started
177 | File debuggerStartedFile = new File("/data/local/tmp", context.getPackageName() + "-debugger-started");
178 | if (debuggerStartedFile.exists() && !debuggerStartedFile.isDirectory() && debuggerStartedFile.length() == 0) {
179 | java.io.FileWriter fileWriter = new java.io.FileWriter(debuggerStartedFile);
180 | fileWriter.write("started");
181 | fileWriter.close();
182 | }
183 |
184 | // check if --debug-brk flag has been set. If positive:
185 | // write to the file to invalidate the flag
186 | // inform the v8Inspector to pause the main thread
187 | File debugBreakFile = new File("/data/local/tmp", context.getPackageName() + "-debugbreak");
188 | boolean shouldBreak = false;
189 | if (debugBreakFile.exists() && !debugBreakFile.isDirectory() && debugBreakFile.length() == 0) {
190 | java.io.FileWriter fileWriter = new java.io.FileWriter(debugBreakFile);
191 | fileWriter.write("started");
192 | fileWriter.close();
193 |
194 | shouldBreak = true;
195 | }
196 |
197 | v8Inspector.waitForDebugger(shouldBreak);
198 | } catch (IOException e) {
199 | if (Util.isDebuggableApp(context)) {
200 | e.printStackTrace();
201 | }
202 | }
203 |
204 | // if app is in debuggable mode run livesync service
205 | // runtime needs to be initialized before the NativeScriptSyncService is enabled because it uses runtime.runScript(...)
206 | initLiveSync(runtime, logger, context);
207 |
208 | waitForLiveSync(context);
209 | }
210 |
211 | runtime.runScript(new File(appDir, "internal/ts_helpers.js"));
212 |
213 | File javaClassesModule = new File(appDir, "app/tns-java-classes.js");
214 | if (javaClassesModule.exists()) {
215 | runtime.runModule(javaClassesModule);
216 | }
217 |
218 | try {
219 | // put this call in a try/catch block because with the latest changes in the modules it is not granted that NativeScriptApplication is extended through JavaScript.
220 | JavaScriptImplementation jsImpl = context.getClass().getAnnotation(JavaScriptImplementation.class);
221 | if (jsImpl != null && !(context instanceof android.app.Service)) {
222 | Runtime.initInstance(context);
223 | }
224 | } catch (Exception e) {
225 | e.printStackTrace();
226 | if (logger.isEnabled()) {
227 | logger.write("Cannot initialize application instance.");
228 | }
229 | if (Util.isDebuggableApp(context)) {
230 | e.printStackTrace();
231 | }
232 | }
233 |
234 | if (appConfig.handleTimeZoneChanges()) {
235 | // If the user sets this flag, we will register a broadcast receiver
236 | // that will listen for the TIMEZONE_CHANGED event and update V8's cache
237 | // so that subsequent calls to "new Date()" return the new timezone
238 | registerTimezoneChangedListener(context, runtime);
239 | }
240 | }
241 | return runtime;
242 | }catch (Exception e){
243 | e.printStackTrace();
244 | return null;
245 | }finally {
246 | frame.close();
247 | }
248 | }
249 |
250 | private static void waitForLiveSync(Context context) {
251 | boolean needToWait = false;
252 |
253 | // CLI will create this file when initial sync is needed and then will remove it after syncing the fails and restarting the app
254 | File liveSyncFile = new File("/data/local/tmp/" + context.getPackageName() + "-livesync-in-progress");
255 | if (liveSyncFile.exists()) {
256 | needToWait = true;
257 | Long lastModified = liveSyncFile.lastModified();
258 | // we check for lastModified == 0 as this might happen if we cannot get the actual modified date
259 | if (lastModified > 0) {
260 | Long fileCreatedBeforeMillis = System.currentTimeMillis() - lastModified;
261 | // if last modified date is more than a minute before the current time discard the file as most probably this is a leftover
262 | if (fileCreatedBeforeMillis > 60000) {
263 | needToWait = false;
264 | }
265 | }
266 | }
267 |
268 | if (needToWait) {
269 | try {
270 | // wait for the livesync to complete and it should restart the app after deleting the livesync-in-progress file
271 | Thread.sleep(30000);
272 | } catch (Exception ex) {
273 | }
274 | }
275 | }
276 |
277 | private static void registerTimezoneChangedListener(Context context, final Runtime runtime) {
278 | IntentFilter timezoneFilter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
279 |
280 | BroadcastReceiver timezoneReceiver = new BroadcastReceiver() {
281 | @Override
282 | public void onReceive(Context context, Intent intent) {
283 | String action = intent.getAction();
284 | if (action == null || !action.equals(Intent.ACTION_TIMEZONE_CHANGED)) {
285 | return;
286 | }
287 |
288 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
289 |
290 | String oldTimezone = prefs.getString(PREF_TIMEZONE, null);
291 | String newTimezone = TimeZone.getDefault().getID();
292 | if (newTimezone == null) {
293 | newTimezone = "";
294 | }
295 |
296 | if (oldTimezone == null) {
297 | oldTimezone = "";
298 | }
299 |
300 | if (!oldTimezone.equals(newTimezone)) {
301 | prefs.edit().putString(PREF_TIMEZONE, newTimezone).commit();
302 | // Notify V8 for the timezone change
303 | runtime.ResetDateTimeConfigurationCache();
304 | }
305 | }
306 | };
307 |
308 | context.registerReceiver(timezoneReceiver, timezoneFilter);
309 | }
310 |
311 | public static void initLiveSync(Application app) {
312 | Runtime currentRuntime = Runtime.getCurrentRuntime();
313 | if (!currentRuntime.getIsLiveSyncStarted()) {
314 | initLiveSync(currentRuntime, currentRuntime.getLogger(), app);
315 | currentRuntime.setIsLiveSyncStarted(true);
316 | }
317 |
318 | }
319 |
320 | public static void initLiveSync(Runtime runtime, Logger logger, Context context) {
321 | boolean isDebuggable = Util.isDebuggableApp(context);
322 |
323 | if (!isDebuggable) {
324 | return;
325 | }
326 |
327 | // if app is in debuggable mode run livesync service
328 | // runtime needs to be initialized before the NativeScriptSyncService is enabled because it uses runtime.runScript(...)
329 | try {
330 | @SuppressWarnings("unchecked")
331 | Class> NativeScriptSyncService = Class.forName("com.tns.NativeScriptSyncServiceSocketImpl");
332 |
333 | @SuppressWarnings("unchecked")
334 | Constructor> cons = NativeScriptSyncService.getConstructor(new Class>[]{Runtime.class, Logger.class, Context.class});
335 | Object syncService = cons.newInstance(runtime, logger, context);
336 |
337 | @SuppressWarnings("unchecked")
338 | Method startServerMethod = NativeScriptSyncService.getMethod("startServer");
339 | startServerMethod.invoke(syncService);
340 | } catch (ClassNotFoundException e) {
341 | if (Util.isDebuggableApp(context)) {
342 | e.printStackTrace();
343 | }
344 | } catch (NoSuchMethodException e) {
345 | if (Util.isDebuggableApp(context)) {
346 | e.printStackTrace();
347 | }
348 | } catch (IllegalAccessException e) {
349 | if (Util.isDebuggableApp(context)) {
350 | e.printStackTrace();
351 | }
352 | } catch (InvocationTargetException e) {
353 | if (Util.isDebuggableApp(context)) {
354 | e.printStackTrace();
355 | }
356 | } catch (InstantiationException e) {
357 | if (Util.isDebuggableApp(context)) {
358 | e.printStackTrace();
359 | }
360 | }
361 | }
362 |
363 | private static final String logTag = "MyApp";
364 | private static final String PREF_TIMEZONE = "_android_runtime_pref_timezone_";
365 | }
366 |
--------------------------------------------------------------------------------
/embed/android/main/java/com/tns/Util.java:
--------------------------------------------------------------------------------
1 | package com.tns;
2 |
3 | import java.io.*;
4 |
5 | import com.tns.internal.Plugin;
6 |
7 | import android.content.Context;
8 | import android.content.pm.ApplicationInfo;
9 | import android.content.pm.PackageInfo;
10 | import android.content.pm.PackageManager;
11 | import android.content.pm.PackageManager.NameNotFoundException;
12 | import android.os.Bundle;
13 |
14 | import androidx.core.content.pm.PackageInfoCompat;
15 |
16 | public final class Util {
17 | private Util() {
18 | }
19 |
20 | public static String getDexThumb(Context context) throws NameNotFoundException {
21 | PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
22 | long code = PackageInfoCompat.getLongVersionCode(packageInfo);
23 | long updateTime = packageInfo.lastUpdateTime;
24 | return updateTime + "-" + code;
25 | }
26 |
27 | public static boolean isDebuggableApp(Context context) {
28 | int flags;
29 | try {
30 | flags = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).applicationInfo.flags;
31 | } catch (NameNotFoundException e) {
32 | flags = 0;
33 | if (Util.isDebuggableApp(context)) {
34 | e.printStackTrace();
35 | }
36 | }
37 |
38 | boolean isDebuggableApp = ((flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
39 | return isDebuggableApp;
40 | }
41 |
42 | static boolean runPlugin(Logger logger, Context context) {
43 | boolean success = false;
44 | String pluginClassName = "org.nativescript.livesync.LiveSyncPlugin";
45 | try {
46 | ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
47 | Bundle metadataBundle = ai.metaData;
48 | if (metadataBundle != null) {
49 | pluginClassName = metadataBundle.getString("com.tns.internal.Plugin");
50 | }
51 | } catch (Exception e) {
52 | if (Util.isDebuggableApp(context) && logger.isEnabled()) {
53 | e.printStackTrace();
54 | }
55 | }
56 |
57 | try {
58 | Class> liveSyncPluginClass = Class.forName(pluginClassName);
59 | Plugin p = (Plugin) liveSyncPluginClass.newInstance();
60 | success = p.execute(context);
61 | } catch (Exception e) {
62 | if (Util.isDebuggableApp(context) && logger.isEnabled()) {
63 | e.printStackTrace();
64 | }
65 | }
66 | return success;
67 | }
68 |
69 | public static String readSystemProperty(String name) {
70 | InputStreamReader in = null;
71 | BufferedReader reader = null;
72 | try {
73 | Process proc = java.lang.Runtime.getRuntime().exec(new String[] { "/system/bin/getprop", name });
74 | in = new InputStreamReader(proc.getInputStream());
75 | reader = new BufferedReader(in);
76 | return reader.readLine();
77 | } catch (IOException e) {
78 | return null;
79 | } finally {
80 | silentClose(in);
81 | silentClose(reader);
82 | }
83 | }
84 |
85 | private static void silentClose(Closeable closeable) {
86 | if (closeable == null) {
87 | return;
88 | }
89 | try {
90 | closeable.close();
91 | } catch (IOException ignored) {
92 | }
93 | }
94 |
95 | public static Boolean isPositive(String value) {
96 | if (value == null) {
97 | return false;
98 | }
99 |
100 | return (value.equals("true") || value.equals("TRUE") ||
101 | value.equals("yes") || value.equals("YES") ||
102 | value.equals("enabled") || value.equals("ENABLED"));
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/embed/android/main/java/com/tns/internal/AppBuilderCallback.java:
--------------------------------------------------------------------------------
1 | package com.tns.internal;
2 |
3 | import com.tns.ExtractPolicy;
4 |
5 | public interface AppBuilderCallback {
6 | void onConfigurationChanged(android.content.Context context, android.content.res.Configuration newConfig);
7 |
8 | void onCreate(android.content.Context context);
9 |
10 | void onLowMemory(android.content.Context context);
11 |
12 | void onTerminate(android.content.Context context);
13 |
14 | void onTrimMemory(android.content.Context context, int level);
15 |
16 | Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler();
17 |
18 | ExtractPolicy getExtractPolicy();
19 |
20 | boolean shouldEnableDebugging(android.content.Context context);
21 | }
22 |
--------------------------------------------------------------------------------
/embed/android/main/java/com/tns/internal/Plugin.java:
--------------------------------------------------------------------------------
1 | package com.tns.internal;
2 |
3 | public interface Plugin {
4 | boolean execute(android.content.Context context) throws Exception;
5 | }
6 |
--------------------------------------------------------------------------------
/embed/android/nativescript.buildscript.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 | }
5 | dependencies {
6 | classpath group: 'commons-io', name: 'commons-io', version: '2.8.0'
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/embed/ios/NativeScript/App-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // App-Bridging-Header.h
3 | // App
4 | //
5 | // Created by Osei Fortune on 08/07/2020.
6 | //
7 |
8 | #ifndef App_Bridging_Header_h
9 | #define App_Bridging_Header_h
10 | #import "Runtime.h"
11 | #import "NativeScript/NativeScript.h"
12 | #import
13 | static const struct mach_header_64 *mhExecHeaderPtr = &_mh_execute_header;
14 | #endif /* App_Bridging_Header_h */
15 |
16 |
--------------------------------------------------------------------------------
/embed/ios/NativeScript/Runtime.h:
--------------------------------------------------------------------------------
1 | //
2 | // Runtime.h
3 | // App
4 | //
5 | // Created by Osei Fortune on 08/07/2020.
6 | //
7 |
8 | #ifndef Runtime_h
9 | #define Runtime_h
10 | extern void* runtimeMeta();
11 | #endif /* Runtime_h */
12 |
13 |
--------------------------------------------------------------------------------
/embed/ios/NativeScript/Runtime.m:
--------------------------------------------------------------------------------
1 | //
2 | // Runtime.m
3 | // App
4 | //
5 | // Created by Osei Fortune on 08/07/2020.
6 | //
7 |
8 | #import
9 | #import
10 | #import
11 |
12 | static const struct mach_header_64 *mhExecHeaderPtr = &_mh_execute_header;
13 |
14 | extern void* runtimeMeta(){
15 | NSString *sectname = @"__TNSMetadata";
16 | NSString *segname = @"__DATA";
17 | unsigned long size;
18 | void* meta = getsectiondata(&_mh_execute_header, [segname cStringUsingEncoding: NSUTF8StringEncoding], [sectname cStringUsingEncoding: NSUTF8StringEncoding], &size);
19 | return meta;
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Plugin.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Plugin.xcodeproj/xcshareddata/xcschemes/Plugin.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
60 |
61 |
67 |
68 |
69 |
70 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/ios/Plugin.xcodeproj/xcshareddata/xcschemes/PluginTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
60 |
61 |
63 |
64 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/ios/Plugin.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Plugin/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/ios/Plugin/NativeScriptCap.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Capacitor
3 | import UIKit
4 | @objc public class NativeScriptCap: NSObject {
5 |
6 | }
--------------------------------------------------------------------------------
/ios/Plugin/NativeScriptCapPlugin.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | //! Project version number for Plugin.
4 | FOUNDATION_EXPORT double PluginVersionNumber;
5 |
6 | //! Project version string for Plugin.
7 | FOUNDATION_EXPORT const unsigned char PluginVersionString[];
8 |
9 | // In this header, you should import all the public headers of your framework using statements like #import
10 |
11 |
--------------------------------------------------------------------------------
/ios/Plugin/NativeScriptCapPlugin.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | // Define the plugin using the CAP_PLUGIN Macro, and
5 | // each method the plugin supports using the CAP_PLUGIN_METHOD macro.
6 | CAP_PLUGIN(NativeScriptCapPlugin, "NativeScriptCap",
7 | CAP_PLUGIN_METHOD(notify, CAPPluginReturnNone);
8 | )
9 |
--------------------------------------------------------------------------------
/ios/Plugin/NativeScriptCapPlugin.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Capacitor
3 |
4 | /**
5 | * Please read the Capacitor iOS Plugin Development Guide
6 | * here: https://capacitorjs.com/docs/plugins/ios
7 | */
8 | @objc(NativeScriptCapPlugin)
9 | public class NativeScriptCapPlugin: CAPPlugin {
10 | @objc public static var _callback: ((NativeScriptCapPlugin) -> Void)? = nil
11 | @objc public static var _notify: ((String) -> Void)? = nil
12 |
13 |
14 | @objc public static func setup(_ callback: @escaping (NativeScriptCapPlugin) -> Void, _ notify: @escaping (String) -> Void) {
15 | NativeScriptCapPlugin._callback = callback
16 | NativeScriptCapPlugin._notify = notify
17 | }
18 |
19 |
20 | @objc func notify(_ call: CAPPluginCall){
21 | guard let value = call.getString("value") else { return }
22 | NativeScriptCapPlugin._notify!(value)
23 |
24 | }
25 |
26 | @objc public override func load() {
27 | NativeScriptCapPlugin._callback!(self)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ios/PluginTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ios/PluginTests/NativeScriptCapPluginTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import Plugin
3 |
4 | class NativeScriptCapTests: XCTestCase {
5 | override func setUp() {
6 | super.setUp()
7 | // Put setup code here. This method is called before the invocation of each test method in the class.
8 | }
9 |
10 | override func tearDown() {
11 | // Put teardown code here. This method is called after the invocation of each test method in the class.
12 | super.tearDown()
13 | }
14 |
15 | func testEcho() {
16 | // This is an example of a functional test case for a plugin.
17 | // Use XCTAssert and related functions to verify your tests produce the correct results.
18 |
19 | let implementation = NativeScriptCap()
20 | let value = "Hello, World!"
21 | let result = implementation.echo(value)
22 |
23 | XCTAssertEqual(value, result)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '12.0'
2 |
3 | def capacitor_pods
4 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
5 | use_frameworks!
6 | pod 'Capacitor', :path => '../node_modules/@capacitor/ios'
7 | pod 'CapacitorCordova', :path => '../node_modules/@capacitor/ios'
8 | end
9 |
10 | target 'Plugin' do
11 | capacitor_pods
12 | end
13 |
14 | target 'PluginTests' do
15 | capacitor_pods
16 | end
17 |
--------------------------------------------------------------------------------
/ios/nativescript.rb:
--------------------------------------------------------------------------------
1 |
2 | @command = ARGV[0]
3 | @path = ARGV[1]
4 |
5 | @internal_dest = nil
6 |
7 | def addfiles (direc, current_group, main_target)
8 | Dir.glob(direc) do |item|
9 | next if item == '.' or item == '.DS_Store'
10 |
11 | if File.directory?(item)
12 | new_folder = File.basename(item)
13 | created_group = current_group.new_group(new_folder)
14 | addfiles("#{item}/*", created_group, main_target)
15 | else
16 | i = current_group.new_file(item)
17 | if item.include? ".m"
18 | main_target.add_file_references([i])
19 | end
20 | end
21 | end
22 | end
23 |
24 |
25 | def nativescript_post_install(installer)
26 |
27 | save_current_state(installer)
28 |
29 | pods_path = File.expand_path("..", installer.pods_project.path)
30 | internal_path = pods_path + "/NativeScriptSDK/resources"
31 | src_root = File.expand_path("..", pods_path)
32 | @internal_dest = File.expand_path("internal", src_root)
33 | FileUtils.copy_entry internal_path, @internal_dest
34 | main_target = nil
35 | installer.aggregate_targets.each do |target|
36 | user_project = target.user_project
37 | user_project.build_configurations.each do |config|
38 |
39 | config.build_settings["ENABLE_BITCODE"] = "NO"
40 | config.build_settings["CLANG_ENABLE_MODULES"] = "NO"
41 | config.build_settings["LD"] = "$SRCROOT/internal/nsld.sh"
42 | config.build_settings["LDPLUSPLUS"] = "$SRCROOT/internal/nsld.sh"
43 | config.build_settings["OTHER_LDFLAGS"] = '$(inherited) -framework WebKit$(inherited) -ObjC -sectcreate __DATA __TNSMetadata $(CONFIGURATION_BUILD_DIR)/metadata-$(CURRENT_ARCH).bin -F $(SRCROOT)/internal -licucore -lz -lc++ -framework Foundation -framework UIKit -framework CoreGraphics -framework MobileCoreServices -framework Security'
44 | end
45 | main_target = user_project.targets.first
46 |
47 | end
48 |
49 |
50 | sources_index = nil
51 | has_prebuild = false
52 | has_prelink = false
53 | main_target.build_phases.each_with_index do |phase, i|
54 | if phase.class.name == "Xcodeproj::Project::Object::PBXSourcesBuildPhase"
55 | sources_index = i
56 | end
57 | if phase.display_name == "NativeScript PreBuild"
58 | has_prebuild = true
59 | end
60 | if phase.display_name == "NativeScript PreLink"
61 | has_prelink = true
62 | end
63 | end
64 |
65 | if !has_prebuild
66 | pre_build = main_target.new_shell_script_build_phase("NativeScript PreBuild")
67 | pre_build.shell_script = "${SRCROOT}/internal/nativescript-pre-build"
68 |
69 | main_target.build_phases.move_from(main_target.build_phases.length-1, sources_index)
70 | end
71 |
72 |
73 | if !has_prelink
74 | link_index = nil
75 | main_target.build_phases.each_with_index do |phase, i|
76 | if phase.class.name == "Xcodeproj::Project::Object::PBXFrameworksBuildPhase"
77 | link_index = i
78 | end
79 | end
80 |
81 | pre_link = main_target.new_shell_script_build_phase("NativeScript PreLink")
82 | pre_link.shell_script = "${SRCROOT}/internal/nativescript-pre-link"
83 |
84 | main_target.build_phases.move_from(main_target.build_phases.length-1, link_index)
85 |
86 | end
87 |
88 |
89 |
90 | end
91 |
92 | def save_current_state(installer)
93 | @state = Hash.new
94 |
95 | main_target = nil
96 |
97 | user_project = nil
98 | installer.aggregate_targets.each do |target|
99 | user_project = target.user_project
100 |
101 | end
102 |
103 | user_project.build_configurations.each do |config|
104 | @state[config.name] = Hash.new
105 | @state[config.name]["settings"] = {
106 | "ENABLE_BITCODE": config.build_settings["ENABLE_BITCODE"],
107 | "CLANG_ENABLE_MODULES": config.build_settings["ENABLE_BITCODE"],
108 | "LD": config.build_settings["LD"],
109 | "LDPLUSPLUS": config.build_settings["LDPLUSPLUS"],
110 | "OTHER_LDFLAGS": config.build_settings["OTHER_LDFLAGS"],
111 | "USER_HEADER_SEARCH_PATHS": config.build_settings["USER_HEADER_SEARCH_PATHS"],
112 | "SWIFT_OBJC_BRIDGING_HEADER": config.build_settings["SWIFT_OBJC_BRIDGING_HEADER"]
113 | }
114 | end
115 |
116 | main_target = user_project.targets.first
117 | @state["main_target_build_phases"] = main_target.build_phases
118 | json = @state.to_json
119 |
120 | File.write('./pre-ns-state.json', json)
121 |
122 | end
123 |
124 | def nativescript_restore_state(installer)
125 | file = File.read('./pre-ns-state.json')
126 | state_hash = JSON.parse(file)
127 | #restore settings
128 | user_project = nil
129 | installer.aggregate_targets.each do |target|
130 | user_project = target.user_project
131 |
132 | end
133 |
134 | restore_state(user_project)
135 | end
136 |
137 | def get_appdelegate_path(user_project)
138 | src_root = File.expand_path("..", user_project.path)
139 | app_delegate_path = File.expand_path('./App/AppDelegate.swift', src_root)
140 | return app_delegate_path
141 | end
142 |
143 | def save_appdelegate(content, user_project)
144 | File.write(get_appdelegate_path(user_project), content)
145 | end
146 |
147 | def get_podfile_path(user_project)
148 | src_root = File.expand_path("..", user_project.path)
149 | podfile_path = File.expand_path('./Podfile', src_root)
150 | return podfile_path
151 | end
152 |
153 | def save_podfile(content, user_project)
154 | File.write(get_podfile_path(user_project), content)
155 | end
156 |
157 | def get_ns_state_path(user_project)
158 | src_root = File.expand_path("..", user_project.path)
159 | state_path = File.expand_path('./pre-ns-state.json', src_root)
160 | return state_path
161 | end
162 |
163 | def get_ns_state_hash(user_project)
164 | state_path = get_ns_state_path(user_project)
165 | file = File.read(state_path)
166 | state_hash = JSON.parse(file)
167 | return state_hash
168 | end
169 |
170 | def save_ns_state_hash(hash, user_project)
171 | json = hash.to_json
172 | state_path = get_ns_state_path(user_project)
173 | File.write(state_path, json)
174 | end
175 |
176 | def restore_state(user_project)
177 |
178 | state_hash = get_ns_state_hash(user_project)
179 | main_target = user_project.targets.first
180 |
181 | user_project.build_configurations.each do |config|
182 | state_hash[config.name]["settings"].each do |key, value|
183 | if value
184 | config.build_settings[key] = value
185 | puts "Restoring setting: " + key
186 | else
187 | puts "Deleting setting: " + key
188 | config.build_settings.delete(key)
189 | end
190 | end
191 | end
192 |
193 | main_target.build_phases.each do |phase|
194 | unless state_hash["main_target_build_phases"].include? phase.to_s
195 | puts "Removing build phase:"
196 | puts phase.to_s
197 | phase.remove_from_project
198 | end
199 | end
200 |
201 | groups = state_hash["added_groups"]
202 | if groups && groups.length > 0
203 | groups.each do |groupName|
204 | user_project.groups.each do |group|
205 | if group.name == groupName
206 | group.remove_from_project
207 | end
208 | end
209 | end
210 | end
211 |
212 | added_dirs = state_hash["added_dirs"]
213 | if added_dirs && added_dirs.length > 0
214 | added_dirs.each do |path|
215 | FileUtils.rm_rf(path)
216 | end
217 | end
218 |
219 | user_project.save
220 |
221 | podfile = File.read(get_podfile_path(user_project))
222 | podfile.slice! "require_relative '../../node_modules/@nativescript/capacitor/ios/nativescript.rb'"
223 | podfile.slice! "pod 'NativeScriptSDK', '~> 8.4.2'"
224 | podfile.slice! "pod 'NativeScriptUI', '~> 0.1.2'"
225 | podfile.slice! "nativescript_capacitor_post_install(installer)"
226 |
227 | save_podfile(podfile, user_project)
228 |
229 | appdelegate = File.read(get_appdelegate_path(user_project))
230 | puts appdelegate
231 | to_slice = [
232 | "var nativescript: NativeScript?",
233 | "// NativeScript init",
234 | "let nsConfig = Config.init()",
235 | "nsConfig.metadataPtr = runtimeMeta()",
236 | "// can turn off in production",
237 | "nsConfig.isDebug = true",
238 | "nsConfig.logToSystemConsole = nsConfig.isDebug",
239 | 'nsConfig.baseDir = URL(string: "public", relativeTo: Bundle.main.resourceURL)?.path',
240 | 'nsConfig.applicationPath = "nativescript"',
241 | "self.nativescript = NativeScript.init(config: nsConfig)",
242 | "self.nativescript?.runMainApplication()"
243 | ]
244 |
245 | to_slice.each do |line|
246 | appdelegate.slice! line
247 | end
248 |
249 | save_appdelegate(appdelegate, user_project)
250 | end
251 |
252 | def command_restore_state(project_path)
253 | puts "Restoring project state..."
254 | require 'xcodeproj'
255 | require 'json'
256 | project = Xcodeproj::Project.open(project_path)
257 | puts project
258 | restore_state(project)
259 | end
260 |
261 | def nativescript_capacitor_post_install(installer)
262 | nativescript_post_install(installer)
263 |
264 | main_target = nil
265 |
266 | user_project = nil
267 | installer.aggregate_targets.each do |target|
268 | user_project = target.user_project
269 | user_project.build_configurations.each do |config|
270 | config.build_settings["SWIFT_OBJC_BRIDGING_HEADER"] = "$(SRCROOT)/NativeScript/App-Bridging-Header.h"
271 | config.build_settings["USER_HEADER_SEARCH_PATHS"] = "$(SRCROOT)/NativeScript"
272 | end
273 | main_target = user_project.targets.first
274 |
275 | end
276 |
277 | state_hash = get_ns_state_hash(user_project)
278 |
279 | #Add NativeScript group
280 | pods_path = File.expand_path("..", installer.pods_project.path)
281 | nativescript_dir_path = File.expand_path("../../../../node_modules/@nativescript/capacitor/embed/ios/NativeScript", installer.pods_project.path)
282 | src_root = File.expand_path("..", pods_path)
283 | ns_dir_dest = File.expand_path("NativeScript", src_root)
284 |
285 | FileUtils.copy_entry(nativescript_dir_path, ns_dir_dest)
286 |
287 | state_hash['added_dirs'] = [ns_dir_dest]
288 |
289 | has_ns_group = false
290 | user_project.groups.each do |group|
291 | if group.name == 'NativeScript'
292 | has_ns_group = true
293 | end
294 | end
295 |
296 | if !has_ns_group
297 | ns_group = user_project.new_group('NativeScript')
298 | ns_path = ns_dir_dest
299 | addfiles("#{ns_path}/*", ns_group, main_target)
300 |
301 | state_hash['added_groups'] = ["NativeScript"]
302 |
303 | #don't save state if its not first time NS install
304 | save_ns_state_hash(state_hash, user_project)
305 |
306 | end
307 |
308 |
309 |
310 | user_project.save
311 |
312 | end
313 |
314 | if (@command == "clean")
315 | command_restore_state(@path)
316 | end
317 |
318 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nativescript/capacitor",
3 | "version": "5.0.2",
4 | "description": "NativeScript for Capacitor",
5 | "main": "dist/plugin.cjs.js",
6 | "module": "dist/esm/index.mjs",
7 | "types": "dist/esm/index.d.mts",
8 | "unpkg": "dist/plugin.js",
9 | "files": [
10 | "android/src/main/",
11 | "android/build.gradle",
12 | "bin/",
13 | "bridge/",
14 | "dist/",
15 | "ios/Plugin/",
16 | "ios/nativescript.rb",
17 | "NativescriptCapacitor.podspec",
18 | "embed/",
19 | "src/nativescript/"
20 | ],
21 | "bin": {
22 | "build-nativescript": "./bin/build-nativescript.mjs",
23 | "dev-nativescript": "./bin/dev-nativescript.mjs",
24 | "uninstall-nativescript": "./bin/uninstall-nativescript.mjs"
25 | },
26 | "author": "NativeScript TSC",
27 | "license": "Apache-2.0",
28 | "repository": {
29 | "type": "git",
30 | "url": "git+https://github.com/NativeScript/capacitor.git"
31 | },
32 | "bugs": {
33 | "url": "https://github.com/NativeScript/capacitor/issues"
34 | },
35 | "keywords": [
36 | "capacitor",
37 | "plugin",
38 | "native"
39 | ],
40 | "scripts": {
41 | "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
42 | "verify:ios": "cd ios && pod install && xcodebuild -workspace Plugin.xcworkspace -scheme Plugin && cd ..",
43 | "verify:android": "cd android && ./gradlew clean build test && cd ..",
44 | "verify:web": "npm run build",
45 | "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
46 | "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- autocorrect --format",
47 | "eslint": "eslint . --ext ts",
48 | "prettier": "prettier \"**/*.{css,html,ts,js,java}\"",
49 | "swiftlint": "node-swiftlint",
50 | "docgen": "docgen --api NativeScriptCapPlugin --output-readme README.md --output-json dist/docs.json",
51 | "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.js --bundleConfigAsCjs && npm run build:bridge",
52 | "build:bridge": "cd src_bridge && ts-patch install && tsc && cp package.json ../bridge/package.json && cp webpack.config.js ../bridge/webpack.config.js && cp webpack4.config.js ../bridge/webpack4.config.js && cp legacy-ns-transform-native-classes.js ../bridge/legacy-ns-transform-native-classes.js && cp native-custom.d.ts ../bridge/native-custom.d.ts && cp ../src/nativeapi.d.ts ../dist/esm/nativeapi.d.ts",
53 | "clean": "rimraf ./dist",
54 | "clean.all": "npx rimraf dist node_modules package-lock.json yarn.lock && npm run setup",
55 | "setup": "npm i --legacy-peer-deps --ignore-scripts && ts-patch install",
56 | "watch": "tsc --watch",
57 | "prepublishOnly": "npm run build",
58 | "postinstall": "node dist/esm/postinstall.mjs --action install"
59 | },
60 | "dependencies": {
61 | "@nativescript/webpack": "~5.0.0",
62 | "chokidar": "^3.5.2",
63 | "fs-extra": "^11.1.0",
64 | "nativescript-dev-xcode": "~0.5.0",
65 | "npm-run-all": "~4.1.5",
66 | "prompts": "^2.4.1",
67 | "strip-json-comments": "^5.0.0",
68 | "ts-loader": "^9.4.2",
69 | "ts-node": "^10.9.0",
70 | "ts-patch": "^2.1.0",
71 | "unzipper": "^0.10.11",
72 | "xmldom": "^0.6.0",
73 | "yargs": "17.6.0"
74 | },
75 | "devDependencies": {
76 | "@capacitor/android": "^5.3.0",
77 | "@capacitor/core": "^5.3.0",
78 | "@capacitor/docgen": "0.2.1",
79 | "@capacitor/ios": "^5.3.0",
80 | "@ionic/eslint-config": "^0.3.0",
81 | "@ionic/prettier-config": "^4.0.0",
82 | "@ionic/swiftlint-config": "^1.1.2",
83 | "@nativescript/core": "~8.5.0",
84 | "@nativescript/types": "~8.5.0",
85 | "@types/fs-extra": "^11.0.1",
86 | "@types/node": "^18.0.0",
87 | "@types/xmldom": "^0.1.30",
88 | "@types/yargs": "^17.0.24",
89 | "eslint": "^8.40.0",
90 | "prettier": "~2.8.8",
91 | "prettier-plugin-java": "~2.1.0",
92 | "rimraf": "^5.0.0",
93 | "rollup": "^3.21.5",
94 | "swiftlint": "^1.0.2",
95 | "typescript": "~5.1.6"
96 | },
97 | "prettier": "@ionic/prettier-config",
98 | "swiftlint": "@ionic/swiftlint-config",
99 | "eslintConfig": {
100 | "extends": "@ionic/eslint-config/recommended"
101 | },
102 | "capacitor": {
103 | "ios": {
104 | "src": "ios"
105 | },
106 | "android": {
107 | "src": "android"
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | input: 'dist/esm/index.mjs',
3 | output: [
4 | {
5 | file: 'dist/plugin.js',
6 | format: 'iife',
7 | name: 'capacitorNativeScriptCap',
8 | globals: {
9 | '@capacitor/core': 'capacitorExports',
10 | },
11 | sourcemap: true,
12 | inlineDynamicImports: true,
13 | },
14 | {
15 | file: 'dist/plugin.cjs.js',
16 | format: 'cjs',
17 | sourcemap: true,
18 | inlineDynamicImports: true,
19 | },
20 | ],
21 | external: ['@capacitor/core'],
22 | onwarn: function (warning) {
23 | if (warning.code === 'THIS_IS_UNDEFINED') {
24 | return;
25 | }
26 |
27 | // console.warn everything else
28 | console.warn(warning.message);
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/src/definitions.ts:
--------------------------------------------------------------------------------
1 | export interface NativeScriptCapPlugin {
2 | notify(options: {value: any}): void
3 | }
4 |
--------------------------------------------------------------------------------
/src/nativescript/examples/modal.ts:
--------------------------------------------------------------------------------
1 | import {
2 | iosRootViewController,
3 | androidCreateDialog,
4 | } from '@nativescript/capacitor/bridge';
5 |
6 | /**
7 | * Try calling from Ionic with:
8 | *
9 | * import { native } from '@nativescript/capacitor';
10 | *
11 | * native.openNativeModalView();
12 | *
13 | * Reference:
14 | * iOS: https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/index.html
15 | * Android: https://developer.android.com/reference/android/widget/LinearLayout
16 | */
17 |
18 | native.openNativeModalView = () => {
19 | if (native.isAndroid) {
20 | androidCreateDialog(() => {
21 | const activity = native.androidCapacitorActivity;
22 |
23 | const layout = new android.widget.LinearLayout(activity);
24 | layout.setGravity(android.view.Gravity.CENTER);
25 | layout.setOrientation(android.widget.LinearLayout.VERTICAL);
26 |
27 | const btn = new android.widget.Button(activity);
28 | btn.setText('Ionic');
29 | layout.addView(btn);
30 |
31 | const btn1 = new android.widget.Button(activity);
32 | btn1.setText('Capacitor');
33 | layout.addView(btn1);
34 |
35 | return layout;
36 | });
37 | } else {
38 | const vc = UIViewController.alloc().init();
39 | vc.view.backgroundColor = UIColor.blueColor;
40 | const label = UILabel.alloc().initWithFrame(
41 | CGRectMake(0, 30, UIScreen.mainScreen.bounds.size.width, 50),
42 | );
43 | label.text = `Well this is fun.`;
44 | label.textColor = UIColor.orangeColor;
45 | label.textAlignment = NSTextAlignment.Center;
46 | label.font = UIFont.systemFontOfSize(35);
47 | vc.view.addSubview(label);
48 | iosRootViewController().presentModalViewControllerAnimated(vc, true);
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/src/nativescript/index.ts:
--------------------------------------------------------------------------------
1 | // init - keep here.
2 | import '@nativescript/capacitor/bridge';
3 |
4 | /**
5 | * **** ****
6 | * ****** ****
7 | * ******** ****
8 | * ****** ***** ****** NativeScript
9 | * **** ********
10 | * **** ******
11 | * **** ****
12 | *
13 | * 🧠 Learn more: 👉 https://capacitor.nativescript.org/getting-started.html
14 | */
15 |
16 | // Example A: direct native calls
17 | const hello = `👋 🎉 ~ NativeScript Team`;
18 | if (native.isAndroid) {
19 | console.log(new java.lang.String(`Hello Android ${hello}`));
20 | } else {
21 | console.log(NSString.alloc().initWithString(`Hello iOS ${hello}`));
22 | }
23 |
24 | /**
25 | * In addition to calling platform iOS and Android api's directly,
26 | * you can write your own additional helpers here.
27 | */
28 |
29 | // Example B: opening a native modal
30 | import './examples/modal';
31 |
--------------------------------------------------------------------------------
/src/nativescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nativescript",
3 | "dependencies": {
4 | "@nativescript/core": "~8.5.0",
5 | "@nativescript/types": "~8.5.0"
6 | }
7 | }
--------------------------------------------------------------------------------
/src/nativescript/references.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
5 | import type { nativeCustom } from '../native-custom';
6 |
7 | declare global {
8 | var androidCapacitorActivity: android.app.Activity;
9 | var native: NodeJS.Global & typeof globalThis & nativeCustom;
10 | }
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/nativescript/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "baseUrl": "./",
6 | "outDir": "../../www/nativescript",
7 | "sourceMap": false,
8 | "declaration": false,
9 | "downlevelIteration": true,
10 | "experimentalDecorators": true,
11 | "module": "commonjs",
12 | "moduleResolution": "node",
13 | "importHelpers": true,
14 | "target": "es2015",
15 | "lib": [
16 | "es2018",
17 | "dom"
18 | ]
19 | },
20 | "files": [
21 | "references.d.ts",
22 | "index.ts"
23 | ]
24 | }
--------------------------------------------------------------------------------
/src/web.ts:
--------------------------------------------------------------------------------
1 | import { WebPlugin } from '@capacitor/core';
2 |
3 | import type { NativeScriptCapPlugin } from './definitions';
4 |
5 | export class NativeScriptCapWeb
6 | extends WebPlugin
7 | implements NativeScriptCapPlugin {
8 |
9 | notify(options: {value: any}): void {
10 | console.log('NOTIFY', options);
11 | // throw new Error('Method not implemented.');
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src_bridge/legacy-ns-transform-native-classes.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const ts = require("typescript");
4 | function default_1(ctx) {
5 | function isNativeClassExtension(node) {
6 | return (node.decorators &&
7 | node.decorators.filter((d) => {
8 | const fullText = d.getFullText().trim();
9 | return fullText.indexOf('@NativeClass') > -1;
10 | }).length > 0);
11 | }
12 | function visitNode(node) {
13 | if (ts.isClassDeclaration(node) && isNativeClassExtension(node)) {
14 | return createHelper(node);
15 | }
16 | return ts.visitEachChild(node, visitNode, ctx);
17 | }
18 | function createHelper(node) {
19 | // we remove the decorator for now!
20 | return ts.createIdentifier(ts.transpileModule(node.getText().replace(/@NativeClass(\((.|\n)*?\))?/gm, ''), {
21 | compilerOptions: { noEmitHelpers: true, module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES5 },
22 | }).outputText.replace(/(Object\.defineProperty\(.*?{.*?)(enumerable:\s*false)(.*?}\))/gs, '$1enumerable: true$3'));
23 | }
24 | return (source) => ts.updateSourceFileNode(source, ts.visitNodes(source.statements, visitNode));
25 | }
26 | exports.default = default_1;
27 | //# sourceMappingURL=ns-transform-native-classes.js.map
--------------------------------------------------------------------------------
/src_bridge/native-custom.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@nativescript/capacitor' {
2 | export interface customNativeAPI extends nativeCustom {}
3 | }
4 |
5 | /**
6 | * Define your own custom strongly typed native helpers here.
7 | */
8 | export interface nativeCustom {
9 | openNativeModalView: () => void;
10 | }
11 |
--------------------------------------------------------------------------------
/src_bridge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bridge",
3 | "main": "index.js",
4 | "types": "index.d.ts"
5 | }
--------------------------------------------------------------------------------
/src_bridge/references.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
5 | declare module NodeJS {
6 | interface Global {
7 | native?: NodeJS.Global & typeof globalThis;
8 | androidCapacitorActivity: android.app.Activity;
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/src_bridge/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowUnreachableCode": false,
4 | "declaration": true,
5 | "esModuleInterop": true,
6 | "lib": [
7 | "dom",
8 | "es2017"
9 | ],
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "experimentalDecorators": true,
13 | "noFallthroughCasesInSwitch": true,
14 | "outDir": "../bridge",
15 | "pretty": true,
16 | "sourceMap": true,
17 | "skipDefaultLibCheck": true,
18 | "skipLibCheck": true,
19 | "target": "es2017",
20 | "plugins": [
21 | {
22 | "transform": "@nativescript/webpack/dist/transformers/NativeClass/index",
23 | "type": "raw"
24 | }
25 | ]
26 | },
27 | "files": [
28 | "references.d.ts",
29 | "index.ts"
30 | ]
31 | }
--------------------------------------------------------------------------------
/src_bridge/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require("@nativescript/webpack");
2 | const { PlatformSuffixPlugin } = require("@nativescript/webpack/dist/plugins/PlatformSuffixPlugin");
3 | const TerserPlugin = require("terser-webpack-plugin");
4 | const { resolve } = require("path");
5 | module.exports = (env) => {
6 | const mode = env.production ? 'production' : 'development';
7 | const distFolder = env.distFolder || 'www';
8 | webpack.init(env);
9 | webpack.useConfig(false);
10 | webpack.chainWebpack((config) => {
11 | const platform = webpack.Utils.platform.getPlatformName();
12 | const projectDir = webpack.Utils.project.getProjectRootPath();
13 | const tsConfigPath = webpack.Utils.project.getProjectFilePath(
14 | "./tsconfig.json"
15 | );
16 | config.mode(mode);
17 | config.devtool(false);
18 | config.target('node');
19 | config.resolve.mainFields.prepend('main');
20 | config
21 | .entry("index")
22 | .add(
23 | webpack.Utils.project.getProjectFilePath("./index.ts")
24 | );
25 | config.output
26 | .path(webpack.Utils.project.getProjectFilePath(`../../${distFolder}/nativescript`))
27 | .pathinfo(false)
28 | .publicPath("")
29 | .libraryTarget("commonjs")
30 | .globalObject("global")
31 | .set("clean", true);
32 | // Set up Terser options
33 | config.optimization.minimizer("TerserPlugin").use(TerserPlugin, [
34 | {
35 | terserOptions: {
36 | compress: {
37 | collapse_vars: false,
38 | sequences: false,
39 | keep_infinity: true,
40 | drop_console: mode === "production",
41 | global_defs: {
42 | __UGLIFIED__: true,
43 | },
44 | },
45 | keep_fnames: true,
46 | keep_classnames: true,
47 | },
48 | },
49 | ]);
50 | config.plugin('PlatformSuffixPlugin').use(PlatformSuffixPlugin, [
51 | {
52 | platform,
53 | },
54 | ]);
55 | config.resolve.extensions.add(`.${platform}.ts`).add(".ts").add(`.${platform}.js`)
56 | .add('.js');
57 | config.resolve.alias.set('~', projectDir);
58 | // resolve symlinks
59 | config.resolve.symlinks(true);
60 | // resolve modules in project node_modules first
61 | // then fall-back to default node resolution (up the parent folder chain)
62 | config.resolve.modules
63 | .add(resolve(projectDir, `node_modules/@nativescript/core`))
64 | .add(resolve(projectDir, `node_modules`))
65 | .add("node_modules");
66 | // set up ts support
67 | config.module
68 | .rule("ts")
69 | .test([/\.ts$/])
70 | .use("ts-loader")
71 | .loader("ts-loader")
72 | .options({
73 | // todo: perhaps we can provide a default tsconfig
74 | // and use that if the project doesn't have one?
75 | configFile: tsConfigPath,
76 | transpileOnly: true,
77 | allowTsInNodeModules: true,
78 | compilerOptions: {
79 | sourceMap: false,
80 | declaration: false,
81 | },
82 | getCustomTransformers() {
83 | return {
84 | before: [require("@nativescript/webpack/dist/transformers/NativeClass").default],
85 | };
86 | },
87 | });
88 | });
89 | return webpack.resolveConfig();
90 | };
--------------------------------------------------------------------------------
/src_bridge/webpack4.config.js:
--------------------------------------------------------------------------------
1 | const { join, relative, resolve, sep } = require('path');
2 | const TerserPlugin = require('terser-webpack-plugin');
3 | const hashSalt = Date.now().toString();
4 | const projectDir = join(__dirname, '../../../../');
5 |
6 | module.exports = env => {
7 | env = env || {};
8 | const { distFolder, production, uglify } = env;
9 |
10 | const srcContext = resolve(join(projectDir, 'src', 'nativescript'));
11 | const tsConfigPath = resolve(join(srcContext, 'tsconfig.json'));
12 | const coreModulesPackageName = '@nativescript/core';
13 | const alias = env.alias || {};
14 | const dist = resolve(join(projectDir, distFolder || 'www', 'nativescript'));
15 |
16 | const config = {
17 | mode: production ? 'production' : 'development',
18 | context: srcContext,
19 | entry: {
20 | index: './index.ts',
21 | },
22 | output: {
23 | pathinfo: false,
24 | path: dist,
25 | libraryTarget: 'commonjs2',
26 | filename: '[name].js',
27 | globalObject: 'global',
28 | hashSalt,
29 | },
30 | resolve: {
31 | extensions: ['.ts', '.js'],
32 | // Resolve {N} system modules from @nativescript/core
33 | modules: [
34 | resolve(projectDir, `node_modules/${coreModulesPackageName}`),
35 | resolve(projectDir, 'node_modules'),
36 | `node_modules/${coreModulesPackageName}`,
37 | 'node_modules',
38 | ],
39 | alias,
40 | // resolve symlinks to symlinked modules
41 | symlinks: true,
42 | },
43 | resolveLoader: {
44 | // don't resolve symlinks to symlinked loaders
45 | symlinks: false,
46 | },
47 | node: {
48 | // Disable node shims that conflict with NativeScript
49 | http: false,
50 | timers: false,
51 | setImmediate: false,
52 | fs: 'empty',
53 | __dirname: false,
54 | },
55 | devtool: 'none',
56 | optimization: {
57 | noEmitOnErrors: true,
58 | minimize: !!uglify,
59 | minimizer: [
60 | new TerserPlugin({
61 | parallel: true,
62 | cache: false,
63 | sourceMap: false,
64 | terserOptions: {
65 | output: {
66 | comments: false,
67 | semicolons: false,
68 | },
69 | compress: {
70 | // The Android SBG has problems parsing the output
71 | // when these options are enabled
72 | collapse_vars: false,
73 | sequences: false,
74 | // For v8 Compatibility
75 | keep_infinity: true, // for V8
76 | reduce_funcs: false, // for V8
77 | // custom
78 | drop_console: !!production,
79 | drop_debugger: true,
80 | global_defs: {
81 | __UGLIFIED__: true,
82 | },
83 | },
84 | // Required for Element Level CSS, Observable Events, & Android Frame
85 | keep_classnames: true,
86 | },
87 | }),
88 | ],
89 | },
90 | module: {
91 | rules: [
92 | {
93 | test: /\.ts$/,
94 | use: {
95 | loader: 'ts-loader',
96 | options: {
97 | configFile: tsConfigPath,
98 | transpileOnly: true,
99 | allowTsInNodeModules: true,
100 | compilerOptions: {
101 | sourceMap: false,
102 | declaration: false,
103 | },
104 | getCustomTransformers: program => ({
105 | before: [
106 | require('./legacy-ns-transform-native-classes')
107 | .default,
108 | ],
109 | }),
110 | },
111 | },
112 | },
113 | ],
114 | },
115 | };
116 |
117 | return config;
118 | };
--------------------------------------------------------------------------------
/testing/test.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
24 |
25 | Testing...
26 |
27 |
28 |
--------------------------------------------------------------------------------
/testing/testingInterface.ts:
--------------------------------------------------------------------------------
1 | // This import is only used by this for Testing -- Copy everything after this chunk
2 | class test {
3 | public blah = "original";
4 | funcCall() {
5 | console.log("Called FuncCall");
6 | return 1;
7 | }
8 |
9 | funcPromCall(a,b) {
10 | console.log("a",a,b);
11 | return null;
12 | }
13 |
14 | funcCallback(func) {
15 | setTimeout(() => func(1), 1000);
16 | }
17 | }
18 |
19 | class NativeScriptCap {
20 | static listeners = {fromNativeScript: [], toNativeScript: []};
21 | static addListener(event, callback) {
22 | if (typeof this.listeners[event] === 'undefined') {
23 | this.listeners[event] = [];
24 | }
25 | this.listeners[event].push(callback);
26 | }
27 |
28 | static sendResponse(value, event="fromNativeScript") {
29 | for (let i=0;iwindow).test = new test();
70 | (window).NativeScriptCap = NativeScriptCap;
71 |
72 | // ---------------------------------------------------------
73 |
--------------------------------------------------------------------------------
/testing/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowUnreachableCode": false,
4 | "declaration": true,
5 | "esModuleInterop": true,
6 | "lib": ["dom", "es2017"],
7 | "module": "ES6",
8 | "moduleResolution": "classic",
9 | "noFallthroughCasesInSwitch": true,
10 | "noUnusedLocals": false,
11 | "noUnusedParameters": true,
12 | "pretty": true,
13 | "sourceMap": true,
14 | "skipDefaultLibCheck": true,
15 | "skipLibCheck": true,
16 | "target": "es5"
17 | },
18 | "files": ["./browser.ts", "./nativeInterface.ts", "./testingInterface.ts"]
19 | }
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowUnreachableCode": false,
4 | "declaration": true,
5 | "esModuleInterop": true,
6 | "lib": ["ESNext", "dom"],
7 | "module": "esnext",
8 | "target": "es2020",
9 | "moduleResolution": "node",
10 | "experimentalDecorators": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "removeComments": false,
13 | "outDir": "dist/esm",
14 | "pretty": true,
15 | "sourceMap": true,
16 | "plugins": [
17 | {
18 | "transform": "@nativescript/webpack/dist/transformers/NativeClass/index",
19 | "type": "raw"
20 | }
21 | ]
22 | },
23 | "files": [
24 | "src/index.mts",
25 | "src/postinstall.mts"
26 | ]
27 | }
--------------------------------------------------------------------------------