diff --git a/.gitignore b/.gitignore index 5cfcb26..e48d043 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ publish/src publish/package demo/report/report.html demo/report/stats.json +src/platforms/android/nativescript_nativechat.aar diff --git a/README.md b/README.md index 1591652..1b55442 100644 --- a/README.md +++ b/README.md @@ -2,27 +2,26 @@ ## Prerequisites / Requirements -Follow the [instructions]() in our documentation to enable a mobile channel for your bot. +Follow the [instructions](https://docs.nativechat.com/docs/1.0/publishing/mobile) in our documentation to enable a mobile channel for your bot. ## Installation Run the following command from the root of your project: -``` +```bash tns plugin add @progress-nativechat/nativescript-nativechat ``` ## Usage -### JavaScript - -#### How to add the plugin using XML and binding +### JavaScript: How to add the plugin using XML and binding ```xml ``` + ```javascript exports.pageLoaded = function (args) { var page = args.object; @@ -47,7 +46,7 @@ exports.pageLoaded = function (args) { }; ``` -#### How to add the plugin directly through code +### JavaScript: How to add the plugin directly through code ```javascript var plugin = require('@progress-nativechat/nativescript-nativechat'); @@ -76,9 +75,7 @@ exports.pageLoaded = function (args) { }; ``` -### TypeScript - -#### How to add the plugin using XML and binding +### TypeScript: How to add the plugin using XML and binding ```xml ``` + ```typescript import { EventData, fromObject } from 'tns-core-modules/data/observable'; import { Page } from 'tns-core-modules/ui/page'; @@ -112,7 +110,8 @@ export function pageLoaded(args: EventData) { }); } ``` -#### How to add the plugin directly through code + +### TypeScript: How to add the plugin directly through code ```typescript import { EventData } from 'tns-core-modules/data/observable'; @@ -214,7 +213,6 @@ The *config* property should conform to the **NativeChatConfig** interface. | session | [Session](#session) | optional | Information about the user session. | | gtmId | string | optional | Google Tag Manager ID. Used in combination with the tracking property to track completed conversations. Check [here](https://docs.nativechat.com/docs/1.0/publishing/web/#gtmid-optional) for more information.| - #### User | Property | Type | | Description | @@ -230,6 +228,74 @@ The *config* property should conform to the **NativeChatConfig** interface. | context | object | optional | A JSON object containing entities to be merged with the conversation context. They can be used as any other entity within the cognitive flow. Be careful to not override other entities used in the cognitive flow. | | userMessage | string | optional | Used to send a message on the user's behalf if the session is cleared. | +## Enable Platform Functionality + +### Android: File Picker + +You have to extend the application activity following [this](https://docs.nativescript.org/angular/core-concepts/android-runtime/advanced-topics/extend-application-activity#extending-activity) guide. Your activity should implement the *IUploadFileActivity* interface since the NativeChat plugin sets the `uploadFileCallback` activity property. Finally, add the following code to the *onActivityResult* method: + +```typescript +import { NativeChat, IUploadFileActivity } from "@progress-nativechat/nativescript-nativechat"; +const ACCESS_FINE_LOCATION = (android as any).Manifest.permission.ACCESS_FINE_LOCATION; + +@JavaProxy("org.myApp.MainActivity") +class Activity extends android.app.Activity implements IUploadFileActivity { + + public uploadFileCallback: android.webkit.ValueCallback; + + protected onCreate(savedInstanceState: android.os.Bundle): void { + if (!this._callbacks) { + setActivityCallbacks(this); + } + + this._callbacks.onCreate(this, savedInstanceState, super.onCreate); + this.uploadFileCallback = null; + } + + protected onActivityResult(requestCode: number, resultCode: number, data: android.content.Intent): void { + this._callbacks.onActivityResult(this, requestCode, resultCode, data, super.onActivityResult); + if (requestCode === NativeChat.platform.android.SELECT_FILE_RESULT_CODE) { + this.upload(resultCode, data); + } + } + + private upload(resultCode: number, data: android.content.Intent) { + if (!this.uploadFileCallback) { + return; + } + + let uri = null; + if (resultCode == android.app.Activity.RESULT_OK && data) { + uri = Array.create(android.net.Uri, 1); + uri[0] = android.net.Uri.parse(data.getDataString()); + } + + this.uploadFileCallback.onReceiveValue(uri); + this.uploadFileCallback = null; + } + + // the rest of the activity methods... +} +``` + +The default value of `NativeChat.platform.android.SELECT_FILE_RESULT_CODE` is `100`, but you can change it if there are collisions with another activity result code in your app. + +### iOS: Location Picker + +You have to request authorization from the user to use his location. Add *NSLocationWhenInUseUsageDescription* key in the *app/App_Resources/iOS/Info.plist* file. + +```xml + + + + + ... + NSLocationWhenInUseUsageDescription + Can I use your location? + + +``` + ## License Apache License Version 2.0, January 2004 diff --git a/demo/app/App_Resources/Android/AndroidManifest.xml b/demo/app/App_Resources/Android/AndroidManifest.xml index 9db8321..020ed5f 100644 --- a/demo/app/App_Resources/Android/AndroidManifest.xml +++ b/demo/app/App_Resources/Android/AndroidManifest.xml @@ -17,6 +17,7 @@ + + android:theme="@style/LaunchScreenTheme" + android:windowSoftInputMode="adjustResize"> diff --git a/demo/app/App_Resources/iOS/Info.plist b/demo/app/App_Resources/iOS/Info.plist index ea3e3ea..3efaecc 100644 --- a/demo/app/App_Resources/iOS/Info.plist +++ b/demo/app/App_Resources/iOS/Info.plist @@ -43,5 +43,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSLocationWhenInUseUsageDescription + Can I use your location? diff --git a/demo/app/activity.android.ts b/demo/app/activity.android.ts new file mode 100644 index 0000000..a3ee216 --- /dev/null +++ b/demo/app/activity.android.ts @@ -0,0 +1,86 @@ +import { setActivityCallbacks, AndroidActivityCallbacks } from "tns-core-modules/ui/frame"; +import { NativeChat, IUploadFileActivity, IGeolocationActivity } from "@progress-nativechat/nativescript-nativechat"; + +const ACCESS_FINE_LOCATION = (android as any).Manifest.permission.ACCESS_FINE_LOCATION; + + +@JavaProxy("org.myApp.MainActivity") +class Activity extends android.app.Activity implements IUploadFileActivity, IGeolocationActivity { + private _callbacks: AndroidActivityCallbacks; + + public geolocationCallback: android.webkit.GeolocationPermissions.ICallback; + public geolocationOrigin: string; + + public uploadFileCallback: android.webkit.ValueCallback; + + protected onCreate(savedInstanceState: android.os.Bundle): void { + if (!this._callbacks) { + setActivityCallbacks(this); + } + + this._callbacks.onCreate(this, savedInstanceState, super.onCreate); + this.uploadFileCallback = null; + } + + protected onSaveInstanceState(outState: android.os.Bundle): void { + this._callbacks.onSaveInstanceState(this, outState, super.onSaveInstanceState); + } + + protected onStart(): void { + this._callbacks.onStart(this, super.onStart); + } + + protected onStop(): void { + this._callbacks.onStop(this, super.onStop); + } + + protected onDestroy(): void { + this._callbacks.onDestroy(this, super.onDestroy); + } + + public onBackPressed(): void { + this._callbacks.onBackPressed(this, super.onBackPressed); + } + + public onRequestPermissionsResult(requestCode: number, permissions: Array, grantResults: Array): void { + this._callbacks.onRequestPermissionsResult(this, requestCode, permissions, grantResults, undefined /*TODO: Enable if needed*/); + + if (requestCode === NativeChat.platform.android.LOCATION_REQUEST_CODE && + this.geolocationCallback && this.geolocationOrigin) { + + for (let index = 0; index < permissions.length; index++) { + if (permissions[index] === ACCESS_FINE_LOCATION) { + if (grantResults[index] === android.content.pm.PackageManager.PERMISSION_GRANTED) { + this.geolocationCallback.invoke(this.geolocationOrigin, true, true); + } + + this.geolocationCallback = null; + this.geolocationOrigin = null; + break; + } + } + } + } + + protected onActivityResult(requestCode: number, resultCode: number, data: android.content.Intent): void { + this._callbacks.onActivityResult(this, requestCode, resultCode, data, super.onActivityResult); + if (requestCode === NativeChat.platform.android.SELECT_FILE_RESULT_CODE) { + this.upload(resultCode, data); + } + } + + private upload(resultCode: number, data: android.content.Intent) { + if (!this.uploadFileCallback) { + return; + } + + let uri = null; + if (resultCode == android.app.Activity.RESULT_OK && data) { + uri = Array.create(android.net.Uri, 1); + uri[0] = android.net.Uri.parse(data.getDataString()); + } + + this.uploadFileCallback.onReceiveValue(uri); + this.uploadFileCallback = null; + } +} diff --git a/demo/package-lock.json b/demo/package-lock.json index e2ef61d..30704ef 100644 --- a/demo/package-lock.json +++ b/demo/package-lock.json @@ -25,56 +25,56 @@ "version": "6.0.5", "bundled": true, "requires": { - "tslib": "1.9.0" + "tslib": "^1.9.0" } }, "@angular/compiler": { "version": "6.0.5", "bundled": true, "requires": { - "tslib": "1.9.0" + "tslib": "^1.9.0" } }, "@angular/core": { "version": "6.0.5", "bundled": true, "requires": { - "tslib": "1.9.0" + "tslib": "^1.9.0" } }, "@angular/forms": { "version": "6.0.5", "bundled": true, "requires": { - "tslib": "1.9.0" + "tslib": "^1.9.0" } }, "@angular/http": { "version": "6.0.5", "bundled": true, "requires": { - "tslib": "1.9.0" + "tslib": "^1.9.0" } }, "@angular/platform-browser": { "version": "6.0.5", "bundled": true, "requires": { - "tslib": "1.9.0" + "tslib": "^1.9.0" } }, "@angular/platform-browser-dynamic": { "version": "6.0.5", "bundled": true, "requires": { - "tslib": "1.9.0" + "tslib": "^1.9.0" } }, "@angular/router": { "version": "6.0.5", "bundled": true, "requires": { - "tslib": "1.9.0" + "tslib": "^1.9.0" } }, "ansi-regex": { @@ -89,7 +89,7 @@ "version": "1.0.10", "bundled": true, "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "async": { @@ -100,20 +100,20 @@ "version": "6.26.0", "bundled": true, "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" }, "dependencies": { "chalk": { "version": "1.1.3", "bundled": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } } } @@ -126,7 +126,7 @@ "version": "1.1.11", "bundled": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -138,23 +138,23 @@ "version": "2.3.2", "bundled": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "dependencies": { "ansi-styles": { "version": "3.2.1", "bundled": true, "requires": { - "color-convert": "1.9.1" + "color-convert": "^1.9.0" } }, "supports-color": { "version": "5.3.0", "bundled": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -163,7 +163,7 @@ "version": "1.9.1", "bundled": true, "requires": { - "color-name": "1.1.3" + "color-name": "^1.1.1" } }, "color-name": { @@ -218,19 +218,19 @@ "version": "7.1.2", "bundled": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "has-ansi": { "version": "2.0.0", "bundled": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "has-flag": { @@ -245,8 +245,8 @@ "version": "1.0.6", "bundled": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -265,15 +265,15 @@ "version": "3.11.0", "bundled": true, "requires": { - "argparse": "1.0.10", - "esprima": "4.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "minimatch": { "version": "3.0.4", "bundled": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -295,8 +295,8 @@ "version": "6.0.0", "bundled": true, "requires": { - "nativescript-intl": "3.0.0", - "reflect-metadata": "0.1.12" + "nativescript-intl": "^3.0.0", + "reflect-metadata": "^0.1.8" } }, "nativescript-intl": { @@ -311,7 +311,7 @@ "version": "1.4.0", "bundled": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "path-is-absolute": { @@ -330,19 +330,19 @@ "version": "1.0.0", "bundled": true, "requires": { - "colors": "1.2.1", - "pkginfo": "0.4.1", - "read": "1.0.7", - "revalidator": "0.1.8", - "utile": "0.3.0", - "winston": "2.1.1" + "colors": "^1.1.2", + "pkginfo": "0.x.x", + "read": "1.0.x", + "revalidator": "0.1.x", + "utile": "0.3.x", + "winston": "2.1.x" } }, "read": { "version": "1.0.7", "bundled": true, "requires": { - "mute-stream": "0.0.7" + "mute-stream": "~0.0.4" } }, "reflect-metadata": { @@ -353,7 +353,7 @@ "version": "1.6.0", "bundled": true, "requires": { - "path-parse": "1.0.5" + "path-parse": "^1.0.5" } }, "revalidator": { @@ -364,14 +364,14 @@ "version": "2.6.2", "bundled": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "rxjs": { "version": "6.0.0", "bundled": true, "requires": { - "tslib": "1.9.0" + "tslib": "^1.9.0" } }, "semver": { @@ -390,7 +390,7 @@ "version": "3.0.1", "bundled": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "supports-color": { @@ -420,25 +420,25 @@ "version": "5.9.1", "bundled": true, "requires": { - "babel-code-frame": "6.26.0", - "builtin-modules": "1.1.1", - "chalk": "2.3.2", - "commander": "2.15.1", - "diff": "3.5.0", - "glob": "7.1.2", - "js-yaml": "3.11.0", - "minimatch": "3.0.4", - "resolve": "1.6.0", - "semver": "5.5.0", - "tslib": "1.9.0", - "tsutils": "2.26.0" + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.12.1" } }, "tsutils": { "version": "2.26.0", "bundled": true, "requires": { - "tslib": "1.9.0" + "tslib": "^1.8.1" } }, "typescript": { @@ -449,25 +449,25 @@ "version": "0.3.0", "bundled": true, "requires": { - "async": "0.9.2", - "deep-equal": "0.2.2", - "i": "0.3.6", - "mkdirp": "0.5.1", - "ncp": "1.0.1", - "rimraf": "2.6.2" + "async": "~0.9.0", + "deep-equal": "~0.2.1", + "i": "0.3.x", + "mkdirp": "0.x.x", + "ncp": "1.0.x", + "rimraf": "2.x.x" } }, "winston": { "version": "2.1.1", "bundled": true, "requires": { - "async": "1.0.0", - "colors": "1.0.3", - "cycle": "1.0.3", - "eyes": "0.1.8", - "isstream": "0.1.2", - "pkginfo": "0.3.1", - "stack-trace": "0.0.10" + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "pkginfo": "0.3.x", + "stack-trace": "0.0.x" }, "dependencies": { "async": { diff --git a/publish/package-lock.json b/publish/package-lock.json new file mode 100644 index 0000000..8431a8c --- /dev/null +++ b/publish/package-lock.json @@ -0,0 +1,111 @@ +{ + "name": "nativescript-publish", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/src/android/geolocation-activity.d.ts b/src/android/geolocation-activity.d.ts new file mode 100644 index 0000000..945f25b --- /dev/null +++ b/src/android/geolocation-activity.d.ts @@ -0,0 +1,4 @@ +export interface IGeolocationActivity { + geolocationCallback: android.webkit.GeolocationPermissions.ICallback; + geolocationOrigin: string; +} diff --git a/src/android/geolocation-activity.ts b/src/android/geolocation-activity.ts new file mode 100644 index 0000000..945f25b --- /dev/null +++ b/src/android/geolocation-activity.ts @@ -0,0 +1,4 @@ +export interface IGeolocationActivity { + geolocationCallback: android.webkit.GeolocationPermissions.ICallback; + geolocationOrigin: string; +} diff --git a/src/android/index.d.ts b/src/android/index.d.ts new file mode 100644 index 0000000..4dcf523 --- /dev/null +++ b/src/android/index.d.ts @@ -0,0 +1,2 @@ +export { IGeolocationActivity } from './geolocation-activity'; +export { IUploadFileActivity } from './upload-file-activity'; diff --git a/src/android/index.ts b/src/android/index.ts new file mode 100644 index 0000000..eea8a5f --- /dev/null +++ b/src/android/index.ts @@ -0,0 +1,2 @@ +export { IGeolocationActivity } from './geolocation-activity'; +export { IUploadFileActivity } from './upload-file-activity'; \ No newline at end of file diff --git a/src/android/nativechat-web-chrome-client.d.ts b/src/android/nativechat-web-chrome-client.d.ts new file mode 100644 index 0000000..91cf8a3 --- /dev/null +++ b/src/android/nativechat-web-chrome-client.d.ts @@ -0,0 +1,4 @@ +export declare class NativeChatWebChromeClient extends android.webkit.WebChromeClient { + onGeolocationPermissionsShowPrompt(origin: string, callback: android.webkit.GeolocationPermissions.ICallback): void; + onShowFileChooser(webview: android.webkit.WebView, filePathCallback: android.webkit.ValueCallback, fileChooserParams: any): boolean; +} diff --git a/src/android/nativechat-web-chrome-client.ts b/src/android/nativechat-web-chrome-client.ts new file mode 100644 index 0000000..026212a --- /dev/null +++ b/src/android/nativechat-web-chrome-client.ts @@ -0,0 +1,56 @@ +import * as application from 'tns-core-modules/application'; +import { NativeChat } from '../nativechat'; + +const PERMISSION_GRANTED = android.content.pm.PackageManager.PERMISSION_GRANTED; +const ACCESS_FINE_LOCATION = (android as any).Manifest.permission.ACCESS_FINE_LOCATION; + +export class NativeChatWebChromeClient extends android.webkit.WebChromeClient { + onGeolocationPermissionsShowPrompt(origin: string, callback: android.webkit.GeolocationPermissions.ICallback) { + const context = application.android.currentContext; + + context.geolocationCallback = null; + context.geolocationOrigin = null; + + const fineLocationPermission = context.checkSelfPermission(ACCESS_FINE_LOCATION); + if (fineLocationPermission !== PERMISSION_GRANTED) { + context.geolocationCallback = callback; + context.geolocationOrigin = origin; + try { + context.requestPermissions([ACCESS_FINE_LOCATION], NativeChat.platform.android.LOCATION_REQUEST_CODE); + } catch (e) { + context.geolocationCallback = null; + context.geolocationOrigin = null; + android.widget.Toast.makeText(context, 'Cannot request location permissions.', android.widget.Toast.LENGTH_LONG).show(); + } + } else { + callback.invoke(origin, true, true); + } + } + + onShowFileChooser( + webview: android.webkit.WebView, + filePathCallback: android.webkit.ValueCallback, + fileChooserParams + ): boolean { + const context = application.android.currentContext; + + if (context.uploadFileCallback != null) { + context.uploadFileCallback.onReceiveValue(null); + context.uploadFileCallback = null; + } + + context.uploadFileCallback = filePathCallback; + + try { + const intent = fileChooserParams.createIntent(); + context.startActivityForResult(intent, NativeChat.platform.android.SELECT_FILE_RESULT_CODE); + } catch (e) { + context.uploadFileCallback = null; + android.widget.Toast.makeText(context, 'Cannot open file chooser', android.widget.Toast.LENGTH_LONG).show(); + + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/src/android/nativechat-web-view-client.d.ts b/src/android/nativechat-web-view-client.d.ts new file mode 100644 index 0000000..11fb02d --- /dev/null +++ b/src/android/nativechat-web-view-client.d.ts @@ -0,0 +1,3 @@ +export declare class NativeChatWebViewClient extends android.webkit.WebViewClient { + shouldOverrideUrlLoading(webview: android.webkit.WebView, request: any): boolean; +} diff --git a/src/android/nativechat-web-view-client.ts b/src/android/nativechat-web-view-client.ts new file mode 100644 index 0000000..636b3e2 --- /dev/null +++ b/src/android/nativechat-web-view-client.ts @@ -0,0 +1,21 @@ +import * as application from 'tns-core-modules/application'; +import { Config } from '../nativechat'; + +export class NativeChatWebViewClient extends android.webkit.WebViewClient { + shouldOverrideUrlLoading(webview: android.webkit.WebView, request): boolean { // request: android.webkit.WebResourceRequest | string + const uri = request.getUrl ? request.getUrl() : android.net.Uri.parse(request); + const url = uri && uri.toString(); + const openInView = url && (url.startsWith(`https://${Config.webchatUrl}`) || url.startsWith(`http://${Config.webchatUrl}`)); + if (!openInView) { + const context = application.android.currentContext; + try { + context.startActivity(new android.content.Intent(android.content.Intent.ACTION_VIEW, uri)); + return true; + } catch (error) { + android.widget.Toast.makeText(context, 'Cannot open url', android.widget.Toast.LENGTH_LONG).show(); + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/android/upload-file-activity.d.ts b/src/android/upload-file-activity.d.ts new file mode 100644 index 0000000..8fe498c --- /dev/null +++ b/src/android/upload-file-activity.d.ts @@ -0,0 +1,3 @@ +export interface IUploadFileActivity { + uploadFileCallback: android.webkit.ValueCallback; +} diff --git a/src/android/upload-file-activity.ts b/src/android/upload-file-activity.ts new file mode 100644 index 0000000..8fe498c --- /dev/null +++ b/src/android/upload-file-activity.ts @@ -0,0 +1,3 @@ +export interface IUploadFileActivity { + uploadFileCallback: android.webkit.ValueCallback; +} diff --git a/src/index.d.ts b/src/index.d.ts index 5a2c45d..46c5d42 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1 +1,3 @@ -export { NativeChat, NativeChatConfig, Session, User } from './nativechat'; +export { NativeChat } from './nativechat'; +export { NativeChatConfig, Session, User } from './models'; +export { IGeolocationActivity, IUploadFileActivity } from './android'; diff --git a/src/index.ts b/src/index.ts index 114cffe..3e24faa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,4 @@ -export { NativeChat, NativeChatConfig, Session, User } from './nativechat'; \ No newline at end of file +export { NativeChat } from './nativechat'; +export { NativeChatConfig, Session, User } from './models'; +export { IGeolocationActivity, IUploadFileActivity } from './android'; + diff --git a/src/models/index.d.ts b/src/models/index.d.ts new file mode 100644 index 0000000..d71ef6f --- /dev/null +++ b/src/models/index.d.ts @@ -0,0 +1,3 @@ +export { Session } from "./session"; +export { User } from "./user"; +export { NativeChatConfig } from "./nativechat-config"; diff --git a/src/models/index.ts b/src/models/index.ts new file mode 100644 index 0000000..4feb955 --- /dev/null +++ b/src/models/index.ts @@ -0,0 +1,3 @@ +export { Session } from "./session"; +export { User } from "./user"; +export { NativeChatConfig } from "./nativechat-config"; \ No newline at end of file diff --git a/src/models/nativechat-config.d.ts b/src/models/nativechat-config.d.ts new file mode 100644 index 0000000..ed13c57 --- /dev/null +++ b/src/models/nativechat-config.d.ts @@ -0,0 +1,10 @@ +import { Session } from "./session"; +import { User } from "./user"; +export interface NativeChatConfig { + botId: string; + channelId: string; + channelToken: string; + gtmId?: string; + session?: Session; + user?: User; +} diff --git a/src/models/nativechat-config.ts b/src/models/nativechat-config.ts new file mode 100644 index 0000000..137f7cf --- /dev/null +++ b/src/models/nativechat-config.ts @@ -0,0 +1,11 @@ +import { Session } from "./session"; +import { User } from "./user"; + +export interface NativeChatConfig { + botId: string; + channelId: string; + channelToken: string; + gtmId?: string; + session?: Session; + user?: User; +} diff --git a/src/models/session.d.ts b/src/models/session.d.ts new file mode 100644 index 0000000..c96e48c --- /dev/null +++ b/src/models/session.d.ts @@ -0,0 +1,5 @@ +export interface Session { + clear?: boolean; + context?: object; + userMessage?: string; +} diff --git a/src/models/session.ts b/src/models/session.ts new file mode 100644 index 0000000..c96e48c --- /dev/null +++ b/src/models/session.ts @@ -0,0 +1,5 @@ +export interface Session { + clear?: boolean; + context?: object; + userMessage?: string; +} diff --git a/src/models/user.d.ts b/src/models/user.d.ts new file mode 100644 index 0000000..46401b2 --- /dev/null +++ b/src/models/user.d.ts @@ -0,0 +1,4 @@ +export interface User { + id?: string; + name?: string; +} diff --git a/src/models/user.ts b/src/models/user.ts new file mode 100644 index 0000000..46401b2 --- /dev/null +++ b/src/models/user.ts @@ -0,0 +1,4 @@ +export interface User { + id?: string; + name?: string; +} diff --git a/src/nativechat.ts b/src/nativechat.ts index 0892e9b..4999c81 100644 --- a/src/nativechat.ts +++ b/src/nativechat.ts @@ -2,113 +2,114 @@ import { GridLayout } from 'tns-core-modules/ui/layouts/grid-layout'; import { WebView } from 'tns-core-modules/ui/web-view'; import { isAndroid } from "tns-core-modules/platform" import { Observable, fromObject, EventData } from 'tns-core-modules/data/observable/observable'; +import * as builder from 'tns-core-modules/ui/builder' -const builder = require('tns-core-modules/ui/builder'); -const webchatUrl = 'https://webchat.nativechat.com/v1'; +import { NativeChatConfig } from './models'; +import { NativeChatWebChromeClient } from './android/nativechat-web-chrome-client'; +import { NativeChatWebViewClient } from './android/nativechat-web-view-client'; -export interface NativeChatConfig { - botId: string; - channelId: string; - channelToken: string; - gtmId?: string; - session?: Session; - user?: User; -} - -export interface User { - id?: string; - name?: string; -} - -export interface Session { - clear?: boolean; - context?: object; - userMessage?: string; -} +export const Config = { + webchatUrl: 'webchat.nativechat.com', + webchatVersion: 'v1' +}; export class NativeChat extends GridLayout { - private _webView: WebView; - private _config: NativeChatConfig; - - private webChatConfig: Observable; - - set config(value: NativeChatConfig) { - if (this._config && this._config.constructor.prototype instanceof Observable) { - (this._config).off('propertyChange', this.configPropertyChange.bind(this)); - } + public static platform = { + android: { + SELECT_FILE_RESULT_CODE: 100, + LOCATION_REQUEST_CODE: 200 + } + }; - this._config = value; - this.updateUrl(); + private _webView: WebView; + private _config: NativeChatConfig; - if (this._config && this._config.constructor.prototype instanceof Observable) { - (this._config).on('propertyChange', this.configPropertyChange.bind(this)); - } - } - - get config(): NativeChatConfig { - return this._config; - } - - constructor() { - super(); - this.webChatConfig = fromObject({ url: '' }); - this._webView = builder.load(__dirname + '/nativechat.xml') as WebView; - this._webView.bindingContext = this.webChatConfig; - this._webView.on('loadFinished', this.webViewLoaded); - - this.addChild(this._webView); - } - - private webViewLoaded(args) { - var webview: WebView = args.object; - if (isAndroid) { - const settings = webview.android.getSettings(); - settings.setDomStorageEnabled(true); - settings.setDisplayZoomControls(false); - } - } + private webChatConfig: Observable; - private configPropertyChange(data: EventData): void { - this.updateUrl(); - } + set config(value: NativeChatConfig) { + if (this._config && this._config.constructor.prototype instanceof Observable) { + (this._config).off('propertyChange', this.configPropertyChange.bind(this)); + } - private updateUrl(): void { - if (this._config && this._config.botId && this._config.channelId && this._config.channelToken) { - let url = `${webchatUrl}?botId=${encodeURIComponent(this._config.botId)}`; - url += `&channelId=${encodeURIComponent(this._config.channelId)}`; - url += `&token=${encodeURIComponent(this._config.channelToken)}`; + this._config = value; + this.updateUrl(); - if (this._config.user) { - if (this._config.user.name) { - url += `&user=${encodeURIComponent(JSON.stringify({ name: this._config.user.name }))}`; + if (this._config && this._config.constructor.prototype instanceof Observable) { + (this._config).on('propertyChange', this.configPropertyChange.bind(this)); } + } - if (this._config.user.id) { - url += `&senderId=${encodeURIComponent(this._config.user.id)}`; - } - } + get config(): NativeChatConfig { + return this._config; + } - if (this._config.session) { - if (this._config.session.context) { - url += `&context=${encodeURIComponent(JSON.stringify(this._config.session.context))}`; - } + constructor() { + super(); + this.webChatConfig = fromObject({ url: '' }); + this._webView = builder.load(__dirname + '/nativechat.xml') as WebView; + this._webView.bindingContext = this.webChatConfig; + this._webView.on('loadFinished', this.webViewLoaded); - if (this._config.session.clear) { - url += `&newSession=true`; - } + this.addChild(this._webView); + } - if (this._config.session.clear || this._config.session.userMessage) { - url += `&userMessage=${encodeURIComponent(this._config.session.userMessage || '')}`; + private webViewLoaded(args) { + var webview: WebView = args.object; + if (isAndroid) { + var settings = webview.android.getSettings(); + settings.setDomStorageEnabled(true); + settings.setDisplayZoomControls(false); + settings.setBuiltInZoomControls(false); + settings.setAppCacheEnabled(true); + settings.setDatabaseEnabled(true); + settings.setJavaScriptEnabled(true); + + webview.android.setWebChromeClient(new NativeChatWebChromeClient()); + webview.android.setWebViewClient(new NativeChatWebViewClient()); } - } + } - if (this._config.gtmId != null) { - url += `>mId=${encodeURIComponent(this._config.gtmId)}`; - } + private configPropertyChange(data: EventData): void { + this.updateUrl(); + } - this.webChatConfig.set('url', url); - } else { - this.webChatConfig.set('url', ''); + private updateUrl(): void { + if (this._config && this._config.botId && this._config.channelId && this._config.channelToken) { + let url = `https://${Config.webchatUrl}/${Config.webchatVersion}?botId=${encodeURIComponent(this._config.botId)}`; + url += `&channelId=${encodeURIComponent(this._config.channelId)}`; + url += `&token=${encodeURIComponent(this._config.channelToken)}`; + + if (this._config.user) { + if (this._config.user.name) { + url += `&user=${encodeURIComponent(JSON.stringify({ name: this._config.user.name }))}`; + } + + if (this._config.user.id) { + url += `&senderId=${encodeURIComponent(this._config.user.id)}`; + } + } + + if (this._config.session) { + if (this._config.session.context) { + url += `&context=${encodeURIComponent(JSON.stringify(this._config.session.context))}`; + } + + if (this._config.session.clear) { + url += `&newSession=true`; + } + + if (this._config.session.clear || this._config.session.userMessage) { + url += `&userMessage=${encodeURIComponent(this._config.session.userMessage || '')}`; + } + } + + if (this._config.gtmId != null) { + url += `>mId=${encodeURIComponent(this._config.gtmId)}`; + } + + this.webChatConfig.set('url', url); + } else { + this.webChatConfig.set('url', ''); + } } - } } diff --git a/src/platforms/android/README.md b/src/platforms/android/README.md index ba1db50..f9a6719 100644 --- a/src/platforms/android/README.md +++ b/src/platforms/android/README.md @@ -1,9 +1,9 @@ # Android permissions and dependencies -* (Optional) Use AndroidManifest.xml to describe any permissions, features or other configuration specifics required or used by your plugin for Android. +* (Optional) Use AndroidManifest.xml to describe any permissions, features or other configuration specifics required or used by your plugin for Android. + NOTE: The NativeScript CLI will not resolve any contradicting or duplicate entries during the merge. After the plugin is installed, you need to manually resolve such issues. * (Optional) Use include.gradle configuration to describe any native dependencies. If there are no such, this file can be removed. For more information, see the [include.gradle Specification](http://docs.nativescript.org/plugins/plugins#includegradle-specification) - [Read more about nativescript plugins](http://docs.nativescript.org/plugins/plugins) \ No newline at end of file diff --git a/src/platforms/android/nativescript_nativechat.aar b/src/platforms/android/nativescript_nativechat.aar deleted file mode 100644 index 507a296..0000000 Binary files a/src/platforms/android/nativescript_nativechat.aar and /dev/null differ