diff --git a/.gitignore b/.gitignore
index 4c83da03..05b95710 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,8 +10,8 @@ src/**/*.d.ts
src/platforms/ios_lib/TNSMLKitCamera/TNSMLKitCamera.xcodeproj/project.xcworkspace
src/platforms/ios_lib/TNSMLKitCamera/TNSMLKitCamera.xcodeproj/xcuserdata
src/platforms/ios/Podfile
-src/platforms/ios/build.xcconfig
src/platforms/android/include.gradle
+src/platforms/android/mlkithelpersrc/**/*.class
!src/firebase.d.ts
!src/index.d.ts
!src/references.d.ts
@@ -34,6 +34,7 @@ demo/firebasefunctions/functions/lib
!demo-push/karma.conf.js
demo-push/*.d.ts
demo-ng/*.d.ts
+!demo-vue/app/main.js
!demo/references.d.ts
!demo-push/references.d.ts
!demo-ng/references.d.ts
diff --git a/.travis.yml b/.travis.yml
index 948bf5fa..12f650cd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,7 @@
branches:
only:
- master
+
matrix:
include:
- stage: "Lint"
@@ -8,39 +9,28 @@ matrix:
os: linux
node_js: "8"
script: "npm run ci.tslint"
- - stage: "Webpack"
- os: osx
- env:
- - Webpack="iOS"
- osx_image: xcode10.0
- language: node_js
- node_js: "8"
- jdk: oraclejdk8
- before_install:
- - gem install cocoapods
- - pod repo update
- script: travis_wait travis_retry tns build ios --bundle
- - language: android
+ - stage: "WebPack"
+ language: android
os: linux
env:
- - Webpack="Android"
+ - WebPack="Android"
jdk: oraclejdk8
- before_install: nvm install 8.11.4
+ before_install: nvm install stable
script: travis_wait travis_retry tns build android --bundle
- stage: "Build"
env:
- - BuildAndroid="26"
+ - BuildAndroid="28"
language: android
os: linux
jdk: oraclejdk8
- before_install: nvm install 8.11.4
+ before_install: nvm install stable
script:
- travis_wait travis_retry tns build android
- os: osx
env:
- - BuildiOS="12.0"
- - Xcode="10.0"
- osx_image: xcode10.0
+ - BuildiOS="12"
+ - Xcode="10.2"
+ osx_image: "xcode10.2"
language: node_js
node_js: "8"
jdk: oraclejdk8
@@ -57,18 +47,19 @@ android:
- build-tools-28.0.3
- android-28
- extra-android-m2repository
+ - sys-img-armeabi-v7a-android-21
before_install:
- - sudo pip install --upgrade pip
- - sudo pip install six
+# - sudo pip install --upgrade pip
+# - sudo pip install six
install:
- - echo no | npm install -g nativescript
- - tns usage-reporting disable
- - tns error-reporting disable
- - cd src
- - npm i --ignore-scripts
- - npm run tsc
- - npm run package
- - cd ../demo
- - npm i
+ - echo no | npm install -g nativescript
+ - tns usage-reporting disable
+ - tns error-reporting disable
+ - cd src
+ - npm i --ignore-scripts
+ - npm run tsc
+ - npm run package
+ - cd ../demo
+ - npm i
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71104be5..7f62b4ba 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,51 @@
- [Firebase iOS SDK Changelog](https://firebase.google.com/support/release-notes/ios)
- [Firebase Android SDK Changelog](https://firebase.google.com/support/release-notes/android)
+## 8.2.0 (2019, April ?)
+[Fixes & Enhancements](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/milestone/98?closed=1)
+
+> Note: The Firebase iOS SDK now requires Xcode 10.1 or later.
+
+## 8.1.1 (2019, March 24)
+[Fixes & Enhancements](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/milestone/97?closed=1)
+
+
+## 8.1.0 (2019, March 24)
+[Fixes & Enhancements](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/milestone/96?closed=1)
+
+
+## 8.0.1 (2019, March 16)
+[Fixes & Enhancements](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/milestone/95?closed=1)
+
+
+## 8.0.0 (2019, February 26)
+[Fixes & Enhancements](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/milestone/94?closed=1)
+
+### BREAKING CHANGES
+- `getAuthToken` no longer returns a token (`string`), but an `GetAuthTokenResult` object which contains more data. See #1008.
+- For better alignment with the Web API, `changePassword` is now `updatePassword`, `resetPassword` is now `sendPasswordResetEmail`. See #1080.
+
+
+## 7.7.0 (2019, January 20)
+[Fixes & Enhancements](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/milestone/92?closed=1)
+
+
+## 7.6.1 (2019, January 8)
+[Fixes & Enhancements](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/milestone/91?closed=1)
+
+
+## 7.6.0 (2018, December 21)
+[Fixes & Enhancements](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/milestone/90?closed=1)
+
+
+## 7.5.1 (2018, December 15)
+[Fixes & Enhancements](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/milestone/89?closed=1)
+
+
+## 7.5.0 (2018, December 15)
+[Fixes & Enhancements](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/milestone/88?closed=1)
+
+
## 7.4.6 (2018, December 10)
[Fixes & Enhancements](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/milestone/87?closed=1)
diff --git a/README.md b/README.md
index 6b4c9362..b37d4485 100755
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
# NativeScript Firebase plugin
-[![Build Status][build-status]][build-url]
[![NPM version][npm-image]][npm-url]
[![Downloads][downloads-image]][npm-url]
+[![TotalDownloads][total-downloads-image]][npm-url]
[![Twitter Follow][twitter-image]][twitter-url]
[build-status]:https://travis-ci.org/EddyVerbruggen/nativescript-plugin-firebase.svg?branch=master
@@ -10,6 +10,7 @@
[npm-image]:http://img.shields.io/npm/v/nativescript-plugin-firebase.svg
[npm-url]:https://npmjs.org/package/nativescript-plugin-firebase
[downloads-image]:http://img.shields.io/npm/dm/nativescript-plugin-firebase.svg
+[total-downloads-image]:http://img.shields.io/npm/dt/nativescript-plugin-firebase.svg?label=total%20downloads
[twitter-image]:https://img.shields.io/twitter/follow/eddyverbruggen.svg?style=social&label=Follow%20me
[twitter-url]:https://twitter.com/eddyverbruggen
@@ -109,7 +110,7 @@ firebase.init({
// Optionally pass in properties for database, authentication and cloud messaging,
// see their respective docs.
}).then(
- function (instance) {
+ function () {
console.log("firebase.init done");
},
function (error) {
@@ -126,7 +127,7 @@ firebase.init({
// Optionally pass in properties for database, authentication and cloud messaging,
// see their respective docs.
}).then(
- instance => {
+ () => {
console.log("firebase.init done");
},
error => {
@@ -149,7 +150,7 @@ export class AppComponent implements OnInit {
// Optionally pass in properties for database, authentication and cloud messaging,
// see their respective docs.
}).then(
- instance => {
+ () => {
console.log("firebase.init done");
},
error => {
diff --git a/demo-ng/app/App_Resources/Android/AndroidManifest.xml b/demo-ng/app/App_Resources/Android/AndroidManifest.xml
index 1964d795..ef328945 100644
--- a/demo-ng/app/App_Resources/Android/AndroidManifest.xml
+++ b/demo-ng/app/App_Resources/Android/AndroidManifest.xml
@@ -30,9 +30,9 @@
android:label="@string/app_name"
android:theme="@style/AppTheme">
-
+
> collectionRef.parent: " + collectionRef.parent); // should be null (has no parent)
collectionRef.get()
.then((querySnapshot: firestore.QuerySnapshot) => {
querySnapshot.forEach(doc => console.log(`${doc.id} => ${JSON.stringify(doc.data())}`));
})
.catch(err => console.log("Get failed, error: " + err));
+ // testing 'parent'
+ const bjDistrictsRef: firestore.CollectionReference = firebase.firestore().collection("cities").doc("BJ").collection("districts");
+ console.log(">> bjDistrictsRef.parent.id: " + bjDistrictsRef.parent.id);
+
// examples from https://firebase.google.com/docs/firestore/query-data/get-data
const docRef: firestore.DocumentReference = firebase.firestore().collection("cities").doc("BJ");
-
- docRef.get().then((doc: firestore.DocumentSnapshot) => {
- if (doc.exists) {
- console.log("Document data:", JSON.stringify(doc.data()));
- // since there's a reference stored here, we can use that to retrieve its data
- const docRef: firestore.DocumentReference = doc.data().referenceToCitiesDC;
- docRef.get().then(res => console.log("docref.get: " + JSON.stringify(res.data())));
- } else {
- console.log("No such document!");
- }
- }).catch(function (error) {
- console.log("Error getting document:", error);
- });
+ console.log(">> docRef.parent.id: " + docRef.parent.id);
+
+ docRef.get()
+ .then((doc: firestore.DocumentSnapshot) => {
+ if (doc.exists) {
+ console.log("Document data:", JSON.stringify(doc.data()));
+ // since there's a reference stored here, we can use that to retrieve its data
+ const docRef: firestore.DocumentReference = doc.data().referenceToCitiesDC;
+ console.log(">> docRef2.parent.id: " + docRef.parent.id);
+ docRef.get()
+ .then(res => console.log("docref.get: " + JSON.stringify(res.data())))
+ .catch(err => console.log("docref.get error: " + err));
+ } else {
+ console.log("No such document!");
+ }
+ }).catch(error => console.log("Error getting document:", error));
}
firestoreGetNested(): void {
@@ -192,15 +200,15 @@ export class FirestoreComponent {
.doc("QZNrg22tkN8W71YC3qCb"); // id of 'main st.'
// .doc("doesntexist");
- mainStreetInSFDocRef.get().then((doc: firestore.DocumentSnapshot) => {
- if (doc.exists) {
- console.log("Document data:", JSON.stringify(doc.data()));
- } else {
- console.log("No such document!");
- }
- }).catch(function (error) {
- console.log("Error getting document:", error);
- });
+ mainStreetInSFDocRef.get()
+ .then((doc: firestore.DocumentSnapshot) => {
+ if (doc.exists) {
+ console.log("Document data:", JSON.stringify(doc.data()));
+ } else {
+ console.log("No such document!");
+ }
+ })
+ .catch(error => console.log("Error getting document:", error));
}
deleteFields(): void {
@@ -226,7 +234,8 @@ export class FirestoreComponent {
.then(() => console.log("Woofie updated from 'delete'"))
.catch(err => console.log("Updating Woofie from 'delete' failed, error: " + JSON.stringify(err)));
}, 2000);
- });
+ })
+ .catch(err => console.log("deleteFields error: " + err));
}
arrayUnion(): void {
@@ -236,8 +245,12 @@ export class FirestoreComponent {
fieldToDelete: firestore.FieldValue.delete(),
updateTs: firebase.firestore().FieldValue().serverTimestamp(),
// just fyi - both of these work:
- colors: firestore.FieldValue.arrayUnion("red", "blue")
- // colors: firebase.firestore().FieldValue().arrayUnion(["red", "blue"])
+ colors: firestore.FieldValue.arrayUnion("red", "blue"),
+ messages: firebase.firestore().FieldValue().arrayUnion({
+ message: "Test 1",
+ source: "central",
+ time: Date.now()
+ })
})
.then(() => console.log("Woofie updated from 'arrayUnion'"))
.catch(err => console.log("Updating Woofie from 'arrayUnion' failed, error: " + JSON.stringify(err)));
@@ -248,7 +261,12 @@ export class FirestoreComponent {
.update({
last: "Updated From 'arrayRemove'",
updateTs: firebase.firestore().FieldValue().serverTimestamp(),
- colors: firebase.firestore().FieldValue().arrayRemove("red")
+ colors: firestore.FieldValue.arrayUnion("red"),
+ messages: firebase.firestore().FieldValue().arrayRemove({
+ message: "Test 1",
+ source: "central",
+ time: Date.now()
+ })
})
.then(() => console.log("Woofie updated from 'arrayRemove'"))
.catch(err => console.log("Updating Woofie from 'arrayRemove' failed, error: " + JSON.stringify(err)));
@@ -257,25 +275,38 @@ export class FirestoreComponent {
firestoreDocumentObservable(): void {
this.myCity$ = Observable.create(subscriber => {
const docRef: firestore.DocumentReference = firebase.firestore().collection("cities").doc("SF");
- docRef.onSnapshot((doc: firestore.DocumentSnapshot) => {
- this.zone.run(() => {
- this.city = doc.data();
- subscriber.next(this.city);
- });
- });
+ docRef.onSnapshot(
+ {includeMetadataChanges: true},
+ (doc: firestore.DocumentSnapshot) => {
+
+ const source = doc.metadata.fromCache ? "local cache" : "server";
+ console.log("Data came from " + source);
+ console.log("Has pending writes? " + doc.metadata.hasPendingWrites);
+
+ this.zone.run(() => {
+ this.city = doc.data();
+ subscriber.next(this.city);
+ });
+ });
});
}
firestoreCollectionObservable(): void {
this.myCities$ = Observable.create(subscriber => {
const colRef: firestore.CollectionReference = firebase.firestore().collection("cities");
- colRef.onSnapshot((snapshot: firestore.QuerySnapshot) => {
- this.zone.run(() => {
- this.cities = [];
- snapshot.forEach(docSnap => this.cities.push(docSnap.data()));
- subscriber.next(this.cities);
- });
- });
+ colRef.onSnapshot(
+ {includeMetadataChanges: true},
+ (snapshot: firestore.QuerySnapshot) => {
+ const source = snapshot.metadata.fromCache ? "local cache" : "server";
+ console.log("Data came from " + source);
+ console.log("Has pending writes? " + snapshot.metadata.hasPendingWrites);
+
+ this.zone.run(() => {
+ this.cities = [];
+ snapshot.forEach(docSnap => this.cities.push(docSnap.data()));
+ subscriber.next(this.cities);
+ });
+ });
});
}
diff --git a/demo-ng/app/tabs/mlkit/barcodescanning/barcodescanning.component.html b/demo-ng/app/tabs/mlkit/barcodescanning/barcodescanning.component.html
index febcdfd8..fc8e3064 100644
--- a/demo-ng/app/tabs/mlkit/barcodescanning/barcodescanning.component.html
+++ b/demo-ng/app/tabs/mlkit/barcodescanning/barcodescanning.component.html
@@ -6,7 +6,8 @@
-
+
diff --git a/demo-ng/app/tabs/mlkit/barcodescanning/barcodescanning.component.ts b/demo-ng/app/tabs/mlkit/barcodescanning/barcodescanning.component.ts
index 64c75e31..8176b82e 100644
--- a/demo-ng/app/tabs/mlkit/barcodescanning/barcodescanning.component.ts
+++ b/demo-ng/app/tabs/mlkit/barcodescanning/barcodescanning.component.ts
@@ -1,5 +1,8 @@
import { Component } from "@angular/core";
-import { MLKitScanBarcodesOnDeviceResult } from "nativescript-plugin-firebase/mlkit/barcodescanning";
+import {
+ MLKitScanBarcodesOnDeviceResult,
+ MLKitScanBarcodesResultBarcode
+} from "nativescript-plugin-firebase/mlkit/barcodescanning";
import { AbstractMLKitViewComponent } from "~/tabs/mlkit/abstract.mlkitview.component";
@Component({
@@ -8,10 +11,8 @@ import { AbstractMLKitViewComponent } from "~/tabs/mlkit/abstract.mlkitview.comp
templateUrl: "./barcodescanning.component.html",
})
export class BarcodeScanningComponent extends AbstractMLKitViewComponent {
- barcodes: Array<{
- value: string;
- format: string;
- }>;
+
+ barcodes: Array;
pause: boolean = false;
@@ -19,12 +20,11 @@ export class BarcodeScanningComponent extends AbstractMLKitViewComponent {
const result: MLKitScanBarcodesOnDeviceResult = event.value;
this.barcodes = result.barcodes;
- console.log("this.barcodes: " + JSON.stringify(this.barcodes));
-
if (this.barcodes.length > 0) {
- console.log("pausing the scanner for 3 seconds (to test the 'pause' feature)");
+ console.log("this.barcodes: " + JSON.stringify(this.barcodes));
+ console.log("pausing the scanner for 1 second (to show the 'pause' feature)");
this.pause = true;
- setTimeout(() => this.pause = false, 3000)
+ setTimeout(() => this.pause = false, 1000)
}
}
}
diff --git a/demo-ng/app/tabs/mlkit/custommodel/custommodel.component.html b/demo-ng/app/tabs/mlkit/custommodel/custommodel.component.html
new file mode 100644
index 00000000..141262ca
--- /dev/null
+++ b/demo-ng/app/tabs/mlkit/custommodel/custommodel.component.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo-ng/app/tabs/mlkit/custommodel/custommodel.component.ts b/demo-ng/app/tabs/mlkit/custommodel/custommodel.component.ts
new file mode 100644
index 00000000..31488508
--- /dev/null
+++ b/demo-ng/app/tabs/mlkit/custommodel/custommodel.component.ts
@@ -0,0 +1,20 @@
+import { Component } from "@angular/core";
+import { MLKitCustomModelResult } from "nativescript-plugin-firebase/mlkit/custommodel";
+import { AbstractMLKitViewComponent } from "~/tabs/mlkit/abstract.mlkitview.component";
+
+@Component({
+ selector: "mlkit-custommodel",
+ moduleId: module.id,
+ templateUrl: "./custommodel.component.html",
+})
+export class CustomModelComponent extends AbstractMLKitViewComponent {
+ labels: Array<{
+ text: string;
+ confidence: number;
+ }>;
+
+ onCustomModelResult(scanResult: any): void {
+ const value: MLKitCustomModelResult = scanResult.value;
+ this.labels = value.result;
+ }
+}
diff --git a/demo-ng/app/tabs/mlkit/facedetection/facedetection.component.ts b/demo-ng/app/tabs/mlkit/facedetection/facedetection.component.ts
index 38e0774a..453d8c29 100644
--- a/demo-ng/app/tabs/mlkit/facedetection/facedetection.component.ts
+++ b/demo-ng/app/tabs/mlkit/facedetection/facedetection.component.ts
@@ -16,6 +16,7 @@ export class FaceDetectionComponent extends AbstractMLKitViewComponent {
const value: MLKitDetectFacesOnDeviceResult = scanResult.value;
if (value.faces.length > 0) {
this.faces = value.faces;
+ console.log("this.faces: " + JSON.stringify(this.faces));
let allSmilingAndEyesOpen = true;
value.faces.forEach(face => {
diff --git a/demo-ng/app/tabs/mlkit/mlkit.component.html b/demo-ng/app/tabs/mlkit/mlkit.component.html
index b6fdc418..bc46ced7 100644
--- a/demo-ng/app/tabs/mlkit/mlkit.component.html
+++ b/demo-ng/app/tabs/mlkit/mlkit.component.html
@@ -4,7 +4,7 @@
-
+
diff --git a/demo-ng/app/tabs/mlkit/mlkit.component.ts b/demo-ng/app/tabs/mlkit/mlkit.component.ts
index 9d6f8c18..746a0827 100644
--- a/demo-ng/app/tabs/mlkit/mlkit.component.ts
+++ b/demo-ng/app/tabs/mlkit/mlkit.component.ts
@@ -1,20 +1,21 @@
import { Component, NgZone } from "@angular/core";
import { RouterExtensions } from "nativescript-angular";
-import { fromFile, ImageSource } from "tns-core-modules/image-source";
-import * as fileSystemModule from "tns-core-modules/file-system";
-import { action } from "tns-core-modules/ui/dialogs";
-import { ImageAsset } from "tns-core-modules/image-asset";
-import { isIOS } from "tns-core-modules/platform";
-import * as ImagePicker from "nativescript-imagepicker";
import * as Camera from "nativescript-camera";
+import * as ImagePicker from "nativescript-imagepicker";
import { BarcodeFormat, MLKitScanBarcodesOnDeviceResult } from "nativescript-plugin-firebase/mlkit/barcodescanning";
-import { MLKitLandmarkRecognitionCloudResult } from "nativescript-plugin-firebase/mlkit/landmarkrecognition";
+import { MLKitCustomModelResult } from "nativescript-plugin-firebase/mlkit/custommodel";
import { MLKitDetectFacesOnDeviceResult } from "nativescript-plugin-firebase/mlkit/facedetection";
-import { MLKitRecognizeTextResult } from "nativescript-plugin-firebase/mlkit/textrecognition";
import {
MLKitImageLabelingCloudResult,
MLKitImageLabelingOnDeviceResult
} from "nativescript-plugin-firebase/mlkit/imagelabeling";
+import { MLKitLandmarkRecognitionCloudResult } from "nativescript-plugin-firebase/mlkit/landmarkrecognition";
+import { MLKitRecognizeTextResult } from "nativescript-plugin-firebase/mlkit/textrecognition";
+import * as fileSystemModule from "tns-core-modules/file-system";
+import { ImageAsset } from "tns-core-modules/image-asset";
+import { fromFile, ImageSource } from "tns-core-modules/image-source";
+import { isIOS } from "tns-core-modules/platform";
+import { action } from "tns-core-modules/ui/dialogs";
const firebase = require("nativescript-plugin-firebase");
@@ -34,6 +35,7 @@ export class MLKitComponent {
"Face detection (on device)",
"Image labeling (on device)",
"Image labeling (cloud)",
+ "Custom model",
"Landmark recognition (cloud)"
];
@@ -41,7 +43,8 @@ export class MLKitComponent {
"Text recognition",
"Barcode scanning",
"Face detection",
- "Image labeling"
+ "Image labeling",
+ "Custom model"
];
constructor(private routerExtensions: RouterExtensions,
@@ -63,6 +66,8 @@ export class MLKitComponent {
to = "/tabs/mlkit/facedetection";
} else if (pickedItem === "Image labeling") {
to = "/tabs/mlkit/imagelabeling";
+ } else if (pickedItem === "Custom model") {
+ to = "/tabs/mlkit/custommodel";
}
if (to !== undefined) {
@@ -96,8 +101,8 @@ export class MLKitComponent {
Camera.requestPermissions();
}
Camera.takePicture({
- width: 800,
- height: 800,
+ width: 600,
+ height: 600,
keepAspectRatio: true,
saveToGallery: true,
cameraFacing: "rear"
@@ -110,7 +115,7 @@ export class MLKitComponent {
});
}
- fromCameraroll(): void {
+ fromCameraRoll(): void {
const imagePicker = ImagePicker.create({
mode: "single"
});
@@ -122,8 +127,8 @@ export class MLKitComponent {
if (selection.length === 0) return;
const selected = selection[0];
- selected.options.height = 800;
- selected.options.width = 800;
+ selected.options.height = 600;
+ selected.options.width = 600;
selected.options.keepAspectRatio = true;
selected.getImageAsync((image: any, error: any) => {
if (error) {
@@ -179,8 +184,8 @@ export class MLKitComponent {
this.labelImageCloud(imageSource);
} else if (pickedItem === "Landmark recognition (cloud)") {
this.recognizeLandmarkCloud(imageSource);
- // } else if (pickedItem === "Custom model (on device)") {
- // this.customModelOnDevice(imageSource);
+ } else if (pickedItem === "Custom model") {
+ this.customModel(imageSource);
}
});
}
@@ -230,6 +235,42 @@ export class MLKitComponent {
.catch(errorMessage => console.log("ML Kit error: " + errorMessage));
}
+ private customModel(imageSource: ImageSource): void {
+ firebase.mlkit.custommodel.useCustomModel({
+ image: imageSource,
+
+ // note that only local quant models work currently (so not 'float' models, and not loaded from the cloud)
+
+ // cloudModelName: "~/mobilenet_quant_v2_1_0_299",
+ // cloudModelName: "~/inception_v3_quant",
+
+ // note that there's an issue with this model (making the app crash): "ValueError: Model provided has model identifier 'Mobi', should be 'TFL3'" (reported by https://github.com/EddyVerbruggen/ns-mlkit-tflite-curated/blob/master/scripts/get_model_details.py)
+ // localModelFile: "~/custommodel/nutella/nutella_quantize.tflite",
+ // labelsFile: "~/custommodel/nutella/nutella_labels.txt",
+
+ // localModelFile: "~/custommodel/mobilenet/mobilenet_quant_v2_1.0_299.tflite",
+ // labelsFile: "~/custommodel/mobilenet/mobilenet_labels.txt",
+
+ localModelFile: "~/custommodel/inception/inception_v3_quant.tflite",
+ labelsFile: "~/custommodel/inception/inception_labels.txt",
+
+ maxResults: 5,
+ modelInput: [{
+ // shape: [1, 224, 224, 3], // flowers / nutella
+ shape: [1, 299, 299, 3], // others
+ type: "QUANT" // the only currently supported type of model
+ }],
+ }).then(
+ (result: MLKitCustomModelResult) => {
+ alert({
+ title: `Result`,
+ message: JSON.stringify(result.result),
+ okButtonText: "OK"
+ });
+ })
+ .catch(errorMessage => console.log("ML Kit error: " + errorMessage));
+ }
+
private scanBarcodeOnDevice(imageSource: ImageSource): void {
console.log(">>> imageSource.rotationAngle: " + imageSource.rotationAngle);
firebase.mlkit.barcodescanning.scanBarcodesOnDevice({
diff --git a/demo-ng/app/tabs/tabs-routing.module.ts b/demo-ng/app/tabs/tabs-routing.module.ts
index a882135f..46e0cd4a 100644
--- a/demo-ng/app/tabs/tabs-routing.module.ts
+++ b/demo-ng/app/tabs/tabs-routing.module.ts
@@ -7,13 +7,15 @@ import { TextRecognitionComponent } from "~/tabs/mlkit/textrecognition/textrecog
import { BarcodeScanningComponent } from "~/tabs/mlkit/barcodescanning/barcodescanning.component";
import { FaceDetectionComponent } from "~/tabs/mlkit/facedetection/facedetection.component";
import { ImageLabelingComponent } from "~/tabs/mlkit/imagelabeling/imagelabeling.component";
+import { CustomModelComponent } from "~/tabs/mlkit/custommodel/custommodel.component";
const routes: Routes = [
{ path: "", component: TabsComponent },
{ path: "mlkit/textrecognition", component: TextRecognitionComponent },
{ path: "mlkit/barcodescanning", component: BarcodeScanningComponent },
{ path: "mlkit/facedetection", component: FaceDetectionComponent },
- { path: "mlkit/imagelabeling", component: ImageLabelingComponent }
+ { path: "mlkit/imagelabeling", component: ImageLabelingComponent },
+ { path: "mlkit/custommodel", component: CustomModelComponent }
];
@NgModule({
diff --git a/demo-ng/app/tabs/tabs.module.ts b/demo-ng/app/tabs/tabs.module.ts
index 2752508b..3ba0b307 100644
--- a/demo-ng/app/tabs/tabs.module.ts
+++ b/demo-ng/app/tabs/tabs.module.ts
@@ -10,12 +10,14 @@ import { TextRecognitionComponent } from "~/tabs/mlkit/textrecognition/textrecog
import { BarcodeScanningComponent } from "~/tabs/mlkit/barcodescanning/barcodescanning.component";
import { FaceDetectionComponent } from "~/tabs/mlkit/facedetection/facedetection.component";
import { ImageLabelingComponent } from "~/tabs/mlkit/imagelabeling/imagelabeling.component";
+import { CustomModelComponent } from "~/tabs/mlkit/custommodel/custommodel.component";
import { registerElement } from "nativescript-angular/element-registry";
registerElement("MLKitBarcodeScanner", () => require("nativescript-plugin-firebase/mlkit/barcodescanning").MLKitBarcodeScanner);
registerElement("MLKitFaceDetection", () => require("nativescript-plugin-firebase/mlkit/facedetection").MLKitFaceDetection);
registerElement("MLKitTextRecognition", () => require("nativescript-plugin-firebase/mlkit/textrecognition").MLKitTextRecognition);
registerElement("MLKitImageLabeling", () => require("nativescript-plugin-firebase/mlkit/imagelabeling").MLKitImageLabeling);
+registerElement("MLKitCustomModel", () => require("nativescript-plugin-firebase/mlkit/custommodel").MLKitCustomModel);
@NgModule({
imports: [
@@ -29,7 +31,8 @@ registerElement("MLKitImageLabeling", () => require("nativescript-plugin-firebas
ImageLabelingComponent,
MLKitComponent,
TabsComponent,
- TextRecognitionComponent
+ TextRecognitionComponent,
+ CustomModelComponent
],
schemas: [
NO_ERRORS_SCHEMA
diff --git a/demo-ng/firebase.nativescript.json b/demo-ng/firebase.nativescript.json
index be4437a0..5a91893d 100644
--- a/demo-ng/firebase.nativescript.json
+++ b/demo-ng/firebase.nativescript.json
@@ -1,14 +1,18 @@
{
+ "external_push_client_only": false,
"using_ios": true,
"using_android": true,
- "realtimedb": false,
"firestore": true,
+ "realtimedb": false,
+ "authentication": true,
"remote_config": false,
"performance_monitoring": true,
"messaging": false,
- "crash_reporting": false,
+ "in_app_messaging": false,
"crashlytics": false,
+ "crash_reporting": false,
"storage": false,
+ "functions": false,
"facebook_auth": false,
"google_auth": false,
"admob": false,
@@ -19,5 +23,7 @@
"ml_kit_barcode_scanning": true,
"ml_kit_face_detection": true,
"ml_kit_image_labeling": true,
- "ml_kit_custom_model": false
-}
+ "ml_kit_custom_model": true,
+ "ml_kit_natural_language_identification": true,
+ "ml_kit_natural_language_smartreply": true
+}
\ No newline at end of file
diff --git a/demo-ng/package.json b/demo-ng/package.json
index 0dcfb80c..bd0add1f 100644
--- a/demo-ng/package.json
+++ b/demo-ng/package.json
@@ -6,10 +6,10 @@
"nativescript": {
"id": "org.nativescript.firebasedemo.firestore",
"tns-android": {
- "version": "5.0.0"
+ "version": "5.1.0"
},
"tns-ios": {
- "version": "5.0.0"
+ "version": "5.1.0"
}
},
"dependencies": {
@@ -23,13 +23,13 @@
"@angular/platform-browser-dynamic": "~6.1.0",
"@angular/router": "~6.1.0",
"nativescript-angular": "^6.1.0",
- "nativescript-camera": "^4.0.2",
- "nativescript-imagepicker": "~6.0.4",
- "nativescript-plugin-firebase": "file:../publish/package/nativescript-plugin-firebase-7.4.6.tgz",
+ "nativescript-camera": "~4.1.1",
+ "nativescript-imagepicker": "~6.0.5",
+ "nativescript-plugin-firebase": "file:../publish/package/nativescript-plugin-firebase-8.2.0.tgz",
"nativescript-theme-core": "~1.0.4",
"reflect-metadata": "~0.1.10",
"rxjs": "~6.0.0 || >=6.1.0",
- "tns-core-modules": "~5.0.1",
+ "tns-core-modules": "~5.1.0",
"zone.js": "~0.8.26"
},
"devDependencies": {
@@ -41,6 +41,6 @@
"lazy": "1.0.11",
"nativescript-dev-typescript": "~0.7.0",
"nativescript-dev-webpack": "^0.15.1",
- "typescript": "~2.7.2"
+ "typescript": "~2.8.0"
}
-}
\ No newline at end of file
+}
diff --git a/demo-push/app/package.json b/demo-push/app/package.json
index 7c8be405..eb552dc7 100644
--- a/demo-push/app/package.json
+++ b/demo-push/app/package.json
@@ -26,9 +26,9 @@
},
"homepage": "https://github.com/NativeScript/template-hello-world-ts",
"android": {
- "v8Flags": "--expose_gc",
- "discardUncaughtJsExceptions": true
+ "v8Flags": "--expose_gc"
},
+ "discardUncaughtJsExceptions": true,
"devDependencies": {
"nativescript-dev-typescript": "^0.3.0"
},
diff --git a/demo-push/app_resources/iOS/GoogleService-Info.plist b/demo-push/app_resources/iOS/GoogleService-Info.plist
new file mode 100644
index 00000000..03e23a4d
--- /dev/null
+++ b/demo-push/app_resources/iOS/GoogleService-Info.plist
@@ -0,0 +1,40 @@
+
+
+
+
+ AD_UNIT_ID_FOR_BANNER_TEST
+ ca-app-pub-3940256099942544/2934735716
+ AD_UNIT_ID_FOR_INTERSTITIAL_TEST
+ ca-app-pub-3940256099942544/4411468910
+ CLIENT_ID
+ 176080762547-8g2ls3h76dcgdt5uuingun2hiehl49r7.apps.googleusercontent.com
+ REVERSED_CLIENT_ID
+ com.googleusercontent.apps.176080762547-8g2ls3h76dcgdt5uuingun2hiehl49r7
+ API_KEY
+ AIzaSyC6yOZO4Kl0yGyRdyH_Z_Q0DysqvNYt3l0
+ GCM_SENDER_ID
+ 176080762547
+ PLIST_VERSION
+ 1
+ BUNDLE_ID
+ org.nativescript.firebasedemo.firestore
+ PROJECT_ID
+ n-plugin-test-firestore
+ STORAGE_BUCKET
+ n-plugin-test-firestore.appspot.com
+ IS_ADS_ENABLED
+
+ IS_ANALYTICS_ENABLED
+
+ IS_APPINVITE_ENABLED
+
+ IS_GCM_ENABLED
+
+ IS_SIGNIN_ENABLED
+
+ GOOGLE_APP_ID
+ 1:176080762547:ios:fbe95ab1f255d884
+ DATABASE_URL
+ https://n-plugin-test-firestore.firebaseio.com
+
+
\ No newline at end of file
diff --git a/demo-push/package.json b/demo-push/package.json
index db45056d..1d87659c 100644
--- a/demo-push/package.json
+++ b/demo-push/package.json
@@ -9,7 +9,7 @@
}
},
"dependencies": {
- "nativescript-plugin-firebase": "file:../publish/package/nativescript-plugin-firebase-7.4.6.tgz",
+ "nativescript-plugin-firebase": "file:../publish/package/nativescript-plugin-firebase-8.2.0.tgz",
"nativescript-theme-core": "^1.0.4",
"nativescript-unit-test-runner": "^0.3.4",
"tns-core-modules": "~4.2.0"
@@ -43,4 +43,4 @@
"build.plugin": "cd ../src && npm run build",
"ci.tslint": "npm i && tslint --config '../tslint.json' 'app/**/*.ts' --exclude '**/node_modules/**' --exclude '**/typings/**'"
}
-}
\ No newline at end of file
+}
diff --git a/demo-vue/.gitignore b/demo-vue/.gitignore
new file mode 100644
index 00000000..b4a60da7
--- /dev/null
+++ b/demo-vue/.gitignore
@@ -0,0 +1,9 @@
+# JetBrains project files
+.idea
+
+# NPM
+node_modules
+
+# NativeScript application
+hooks
+platforms
diff --git a/demo-vue/README.md b/demo-vue/README.md
new file mode 100644
index 00000000..ec55eaf1
--- /dev/null
+++ b/demo-vue/README.md
@@ -0,0 +1,19 @@
+# Firebase Plugin Vue Demo
+
+> Vue demo app for the NativeScript Firebase plugin
+
+## Usage
+
+``` bash
+# Install dependencies
+npm install
+
+# Build for production
+tns build --bundle
+
+# Build, watch for changes and debug the application
+tns debug --bundle
+
+# Build, watch for changes and run the application
+tns run --bundle
+```
\ No newline at end of file
diff --git a/demo-vue/app/App_Resources/Android/app.gradle b/demo-vue/app/App_Resources/Android/app.gradle
new file mode 100644
index 00000000..b0f962f4
--- /dev/null
+++ b/demo-vue/app/App_Resources/Android/app.gradle
@@ -0,0 +1,11 @@
+// Add your native dependencies here:
+
+android {
+ defaultConfig {
+ generatedDensities = []
+ applicationId = "org.nativescript.firebasedemo.firestore"
+ }
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+}
diff --git a/demo-vue/app/App_Resources/Android/google-services.json b/demo-vue/app/App_Resources/Android/google-services.json
new file mode 100644
index 00000000..610a6c36
--- /dev/null
+++ b/demo-vue/app/App_Resources/Android/google-services.json
@@ -0,0 +1,42 @@
+{
+ "project_info": {
+ "project_number": "176080762547",
+ "firebase_url": "https://n-plugin-test-firestore.firebaseio.com",
+ "project_id": "n-plugin-test-firestore",
+ "storage_bucket": "n-plugin-test-firestore.appspot.com"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:176080762547:android:fbe95ab1f255d884",
+ "android_client_info": {
+ "package_name": "org.nativescript.firebasedemo.firestore"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "176080762547-e030nqu3u61ntnq5d3jilip6ik6au4cq.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyCbzMMocv610ByMwPvcv2W0h45btMojqLw"
+ }
+ ],
+ "services": {
+ "analytics_service": {
+ "status": 1
+ },
+ "appinvite_service": {
+ "status": 1,
+ "other_platform_oauth_client": []
+ },
+ "ads_service": {
+ "status": 2
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/demo-vue/app/App_Resources/Android/src/main/AndroidManifest.xml b/demo-vue/app/App_Resources/Android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..ca537b8c
--- /dev/null
+++ b/demo-vue/app/App_Resources/Android/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png
new file mode 100644
index 00000000..64200327
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png
new file mode 100644
index 00000000..117b444a
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png
new file mode 100644
index 00000000..711905f3
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png
new file mode 100644
index 00000000..03befc22
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png
new file mode 100644
index 00000000..bd04848e
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png
new file mode 100644
index 00000000..af908e46
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png
new file mode 100644
index 00000000..cfe4a7c2
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png
new file mode 100644
index 00000000..32aa6176
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png
new file mode 100644
index 00000000..c21ae444
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml b/demo-vue/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
new file mode 100644
index 00000000..ada77f92
--- /dev/null
+++ b/demo-vue/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
@@ -0,0 +1,8 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png
new file mode 100644
index 00000000..b06ae267
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png
new file mode 100644
index 00000000..12950046
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png
new file mode 100644
index 00000000..4ad5346d
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png
new file mode 100644
index 00000000..9bc7f010
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png
new file mode 100644
index 00000000..541e7591
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png
new file mode 100644
index 00000000..bcc40119
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png
new file mode 100644
index 00000000..d93c3d8f
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png
new file mode 100644
index 00000000..072b6013
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png
new file mode 100644
index 00000000..96acb1ec
Binary files /dev/null and b/demo-vue/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png differ
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/values-v21/colors.xml b/demo-vue/app/App_Resources/Android/src/main/res/values-v21/colors.xml
new file mode 100644
index 00000000..a64641a9
--- /dev/null
+++ b/demo-vue/app/App_Resources/Android/src/main/res/values-v21/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #3d5afe
+
\ No newline at end of file
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/values-v21/strings.xml b/demo-vue/app/App_Resources/Android/src/main/res/values-v21/strings.xml
new file mode 100644
index 00000000..ef994511
--- /dev/null
+++ b/demo-vue/app/App_Resources/Android/src/main/res/values-v21/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Firebase Plugin Vue Demo
+ Firebase Plugin Vue Demo
+
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/values-v21/styles.xml b/demo-vue/app/App_Resources/Android/src/main/res/values-v21/styles.xml
new file mode 100644
index 00000000..acff7c9c
--- /dev/null
+++ b/demo-vue/app/App_Resources/Android/src/main/res/values-v21/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/values/colors.xml b/demo-vue/app/App_Resources/Android/src/main/res/values/colors.xml
new file mode 100644
index 00000000..2d0390d8
--- /dev/null
+++ b/demo-vue/app/App_Resources/Android/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #F5F5F5
+ #53ba82
+ #33B5E5
+ #272734
+
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/values/strings.xml b/demo-vue/app/App_Resources/Android/src/main/res/values/strings.xml
new file mode 100644
index 00000000..ef994511
--- /dev/null
+++ b/demo-vue/app/App_Resources/Android/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Firebase Plugin Vue Demo
+ Firebase Plugin Vue Demo
+
diff --git a/demo-vue/app/App_Resources/Android/src/main/res/values/styles.xml b/demo-vue/app/App_Resources/Android/src/main/res/values/styles.xml
new file mode 100644
index 00000000..fae0f4b7
--- /dev/null
+++ b/demo-vue/app/App_Resources/Android/src/main/res/values/styles.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..4034b76e
--- /dev/null
+++ b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "icon-29.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "icon-29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "icon-29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "icon-40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "icon-40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "icon-60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "icon-60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "icon-29.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "icon-29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "icon-40.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "icon-40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "icon-76.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "icon-76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "icon-83.5@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "icon-1024.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png
new file mode 100644
index 00000000..fe7c5040
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png
new file mode 100644
index 00000000..275ddd11
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
new file mode 100644
index 00000000..906e4b4c
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
new file mode 100644
index 00000000..5b9a78e3
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png
new file mode 100644
index 00000000..3e4a7ea5
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
new file mode 100644
index 00000000..f1cf7ae0
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
new file mode 100644
index 00000000..a6b0b6fc
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
new file mode 100644
index 00000000..091c1360
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
new file mode 100644
index 00000000..eb0279cf
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png
new file mode 100644
index 00000000..42d84e1c
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
new file mode 100644
index 00000000..50f1e707
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
new file mode 100644
index 00000000..11dc75f5
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/Contents.json b/demo-vue/app/App_Resources/iOS/Assets.xcassets/Contents.json
new file mode 100644
index 00000000..da4a164c
--- /dev/null
+++ b/demo-vue/app/App_Resources/iOS/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 00000000..11bfcf55
--- /dev/null
+++ b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,176 @@
+{
+ "images" : [
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "2436h",
+ "filename" : "Default-1125h.png",
+ "minimum-system-version" : "11.0",
+ "orientation" : "portrait",
+ "scale" : "3x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "iphone",
+ "extent" : "full-screen",
+ "filename" : "Default-Landscape-X.png",
+ "minimum-system-version" : "11.0",
+ "subtype" : "2436h",
+ "scale" : "3x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "736h",
+ "filename" : "Default-736h@3x.png",
+ "minimum-system-version" : "8.0",
+ "orientation" : "portrait",
+ "scale" : "3x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "736h",
+ "filename" : "Default-Landscape@3x.png",
+ "minimum-system-version" : "8.0",
+ "orientation" : "landscape",
+ "scale" : "3x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "667h",
+ "filename" : "Default-667h@2x.png",
+ "minimum-system-version" : "8.0",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "filename" : "Default@2x.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "retina4",
+ "filename" : "Default-568h@2x.png",
+ "minimum-system-version" : "7.0",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "filename" : "Default-Portrait.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "filename" : "Default-Landscape.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "filename" : "Default-Portrait@2x.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "filename" : "Default-Landscape@2x.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "filename" : "Default.png",
+ "extent" : "full-screen",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "filename" : "Default@2x.png",
+ "extent" : "full-screen",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "filename" : "Default-568h@2x.png",
+ "extent" : "full-screen",
+ "subtype" : "retina4",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "extent" : "to-status-bar",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "filename" : "Default-Portrait.png",
+ "extent" : "full-screen",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "extent" : "to-status-bar",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "filename" : "Default-Landscape.png",
+ "extent" : "full-screen",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "extent" : "to-status-bar",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "filename" : "Default-Portrait@2x.png",
+ "extent" : "full-screen",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "extent" : "to-status-bar",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "filename" : "Default-Landscape@2x.png",
+ "extent" : "full-screen",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png
new file mode 100644
index 00000000..2913f85d
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png
new file mode 100644
index 00000000..d7f17fcd
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png
new file mode 100644
index 00000000..b8841540
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png
new file mode 100644
index 00000000..faab4b63
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png
new file mode 100644
index 00000000..cd94a3ac
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png
new file mode 100644
index 00000000..3365ba3c
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png
new file mode 100644
index 00000000..a44945c1
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png
new file mode 100644
index 00000000..e6dca626
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png
new file mode 100644
index 00000000..1a500796
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png
new file mode 100644
index 00000000..73d8b920
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png
new file mode 100644
index 00000000..9f1f6ce3
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png
new file mode 100644
index 00000000..514fc5cd
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json
new file mode 100644
index 00000000..4f4e9c50
--- /dev/null
+++ b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchScreen-AspectFill.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchScreen-AspectFill@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png
new file mode 100644
index 00000000..c293f9c7
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png
new file mode 100644
index 00000000..233693a6
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json
new file mode 100644
index 00000000..23c0ffd7
--- /dev/null
+++ b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchScreen-Center.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchScreen-Center@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png
new file mode 100644
index 00000000..a5a775a2
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png differ
diff --git a/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png
new file mode 100644
index 00000000..154c1934
Binary files /dev/null and b/demo-vue/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png differ
diff --git a/demo-vue/app/App_Resources/iOS/GoogleService-Info.plist b/demo-vue/app/App_Resources/iOS/GoogleService-Info.plist
new file mode 100644
index 00000000..03e23a4d
--- /dev/null
+++ b/demo-vue/app/App_Resources/iOS/GoogleService-Info.plist
@@ -0,0 +1,40 @@
+
+
+
+
+ AD_UNIT_ID_FOR_BANNER_TEST
+ ca-app-pub-3940256099942544/2934735716
+ AD_UNIT_ID_FOR_INTERSTITIAL_TEST
+ ca-app-pub-3940256099942544/4411468910
+ CLIENT_ID
+ 176080762547-8g2ls3h76dcgdt5uuingun2hiehl49r7.apps.googleusercontent.com
+ REVERSED_CLIENT_ID
+ com.googleusercontent.apps.176080762547-8g2ls3h76dcgdt5uuingun2hiehl49r7
+ API_KEY
+ AIzaSyC6yOZO4Kl0yGyRdyH_Z_Q0DysqvNYt3l0
+ GCM_SENDER_ID
+ 176080762547
+ PLIST_VERSION
+ 1
+ BUNDLE_ID
+ org.nativescript.firebasedemo.firestore
+ PROJECT_ID
+ n-plugin-test-firestore
+ STORAGE_BUCKET
+ n-plugin-test-firestore.appspot.com
+ IS_ADS_ENABLED
+
+ IS_ANALYTICS_ENABLED
+
+ IS_APPINVITE_ENABLED
+
+ IS_GCM_ENABLED
+
+ IS_SIGNIN_ENABLED
+
+ GOOGLE_APP_ID
+ 1:176080762547:ios:fbe95ab1f255d884
+ DATABASE_URL
+ https://n-plugin-test-firestore.firebaseio.com
+
+
\ No newline at end of file
diff --git a/demo-vue/app/App_Resources/iOS/Info.plist b/demo-vue/app/App_Resources/iOS/Info.plist
new file mode 100644
index 00000000..9fd4973f
--- /dev/null
+++ b/demo-vue/app/App_Resources/iOS/Info.plist
@@ -0,0 +1,62 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleDisplayName
+ Firebase Plugin Vue Demo
+ CFBundleExecutable
+ ${EXECUTABLE_NAME}
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ ${PRODUCT_NAME}
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0.0
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiresFullScreen
+
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLName
+ firebasedemovue.page.link
+ CFBundleURLSchemes
+
+ org.nativescript.firebasedemo.firestore
+
+
+
+
+
+
diff --git a/demo-vue/app/App_Resources/iOS/LaunchScreen.storyboard b/demo-vue/app/App_Resources/iOS/LaunchScreen.storyboard
new file mode 100644
index 00000000..2ad9471e
--- /dev/null
+++ b/demo-vue/app/App_Resources/iOS/LaunchScreen.storyboard
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo-vue/app/App_Resources/iOS/app.entitlements b/demo-vue/app/App_Resources/iOS/app.entitlements
new file mode 100644
index 00000000..205ac37d
--- /dev/null
+++ b/demo-vue/app/App_Resources/iOS/app.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.developer.associated-domains
+
+ applinks:j4ctx.app.goo.gl
+
+
+
\ No newline at end of file
diff --git a/demo-vue/app/App_Resources/iOS/build.xcconfig b/demo-vue/app/App_Resources/iOS/build.xcconfig
new file mode 100644
index 00000000..d452798a
--- /dev/null
+++ b/demo-vue/app/App_Resources/iOS/build.xcconfig
@@ -0,0 +1,10 @@
+// You can add custom settings here
+// for example you can uncomment the following line to force distribution code signing
+// CODE_SIGN_IDENTITY = iPhone Distribution
+// To build for device with Xcode 8 you need to specify your development team. More info: https://developer.apple.com/library/prerelease/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html
+// DEVELOPMENT_TEAM = YOUR_TEAM_ID;
+ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+
+CODE_SIGN_ENTITLEMENTS = demovue/demovue.entitlements
+DEVELOPMENT_TEAM = 8Q5F6M3TNS
diff --git a/demo-vue/app/app.scss b/demo-vue/app/app.scss
new file mode 100644
index 00000000..59ab05ad
--- /dev/null
+++ b/demo-vue/app/app.scss
@@ -0,0 +1,10 @@
+// NativeScript core theme
+// @see https://docs.nativescript.org/ui/theme
+@import '~nativescript-theme-core/scss/dark';
+
+// Override variables here
+
+@import '~nativescript-theme-core/scss/index';
+
+// Global SCSS styling
+// @see https://docs.nativescript.org/ui/styling
diff --git a/demo-vue/app/assets/images/NativeScript-Vue.png b/demo-vue/app/assets/images/NativeScript-Vue.png
new file mode 100644
index 00000000..2ad8ed20
Binary files /dev/null and b/demo-vue/app/assets/images/NativeScript-Vue.png differ
diff --git a/demo-vue/app/components/App.vue b/demo-vue/app/components/App.vue
new file mode 100644
index 00000000..fae2cf7b
--- /dev/null
+++ b/demo-vue/app/components/App.vue
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo-vue/app/fonts/.gitkeep b/demo-vue/app/fonts/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/demo-vue/app/main.js b/demo-vue/app/main.js
new file mode 100644
index 00000000..a20282f1
--- /dev/null
+++ b/demo-vue/app/main.js
@@ -0,0 +1,12 @@
+import Vue from "nativescript-vue";
+import App from "./components/App";
+
+require("nativescript-plugin-firebase");
+Vue.registerElement("MLKitTextRecognition", () => require("nativescript-plugin-firebase/mlkit/textrecognition").MLKitTextRecognition);
+
+// Prints Vue logs when --env.production is *NOT* set while building
+Vue.config.silent = (TNS_ENV === "production");
+
+new Vue({
+ render: h => h('frame', [h(App)])
+}).$start();
diff --git a/demo-vue/app/package.json b/demo-vue/app/package.json
new file mode 100644
index 00000000..1296b84f
--- /dev/null
+++ b/demo-vue/app/package.json
@@ -0,0 +1,9 @@
+{
+ "android": {
+ "v8Flags": "--expose_gc"
+ },
+ "discardUncaughtJsExceptions": true,
+ "main": "main",
+ "name": "demo-vue",
+ "version": "1.0.0"
+}
diff --git a/demo-vue/firebase.nativescript.json b/demo-vue/firebase.nativescript.json
new file mode 100644
index 00000000..a2db98ce
--- /dev/null
+++ b/demo-vue/firebase.nativescript.json
@@ -0,0 +1,23 @@
+{
+ "external_push_client_only": false,
+ "using_ios": true,
+ "using_android": true,
+ "firestore": false,
+ "realtimedb": false,
+ "authentication": true,
+ "remote_config": false,
+ "performance_monitoring": false,
+ "messaging": false,
+ "in_app_messaging": true,
+ "crashlytics": false,
+ "crash_reporting": false,
+ "storage": false,
+ "functions": false,
+ "facebook_auth": false,
+ "google_auth": false,
+ "admob": false,
+ "invites": false,
+ "dynamic_links": true,
+ "ml_kit": true,
+ "ml_kit_text_recognition": true
+}
\ No newline at end of file
diff --git a/demo-vue/package.json b/demo-vue/package.json
new file mode 100644
index 00000000..33b06f50
--- /dev/null
+++ b/demo-vue/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "demo-vue",
+ "version": "1.0.0",
+ "description": "Vue demo app for the NativeScript Firebase plugin",
+ "author": "EddyVerbruggen ",
+ "license": "MIT",
+ "nativescript": {
+ "id": "org.nativescript.firebasedemo.firestore",
+ "tns-ios": {
+ "version": "5.0.0"
+ },
+ "tns-android": {
+ "version": "5.0.0"
+ }
+ },
+ "dependencies": {
+ "nativescript-plugin-firebase": "file:../publish/package/nativescript-plugin-firebase-8.2.0.tgz",
+ "nativescript-theme-core": "^1.0.4",
+ "nativescript-vue": "^2.0.0",
+ "tns-core-modules": "^5.0.2"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.0.0",
+ "@babel/preset-env": "^7.0.0",
+ "babel-loader": "^8.0.2",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "clean-webpack-plugin": "^0.1.19",
+ "copy-webpack-plugin": "^4.5.2",
+ "css-loader": "^1.0.0",
+ "lazy": "1.0.11",
+ "nativescript-dev-webpack": "next",
+ "nativescript-vue-template-compiler": "^2.0.0",
+ "nativescript-worker-loader": "~0.9.0",
+ "node-sass": "^4.9.2",
+ "sass-loader": "^7.1.0",
+ "terser-webpack-plugin": "^1.1.0",
+ "vue-loader": "^15.2.6",
+ "webpack": "^4.16.4",
+ "webpack-bundle-analyzer": "~2.13.1",
+ "webpack-cli": "^3.1.0"
+ }
+}
diff --git a/demo/app/main-page.xml b/demo/app/main-page.xml
index 5fac77ad..eaf82bc5 100644
--- a/demo/app/main-page.xml
+++ b/demo/app/main-page.xml
@@ -18,7 +18,7 @@
@@ -34,7 +34,11 @@
-
+
+
+
+
+
@@ -85,7 +89,7 @@
@@ -103,137 +107,138 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
+
-
-
+
-
-
+
+
-
-
+
+
-
+
+
-
+
-
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
+
-
-
+
-
-
+
+
-
+
+
-
-
+
-
-
+
+
-
-
+
+
-
+
+
-
-
+
-
-
+
+
-
+
+
-
+
+
-
-
+
-
-
-
-
-
-
-
+
-
+
+
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/app/main-view-model.ts b/demo/app/main-view-model.ts
index 2fcc2203..166d06b7 100644
--- a/demo/app/main-view-model.ts
+++ b/demo/app/main-view-model.ts
@@ -1,18 +1,22 @@
-import { Observable } from "tns-core-modules/data/observable";
-import { alert, prompt } from "tns-core-modules/ui/dialogs";
-import { isAndroid, isIOS } from "tns-core-modules/platform";
import * as firebase from "nativescript-plugin-firebase";
import {
AddEventListenerResult,
admob as firebaseAdMob,
crashlytics as firebaseCrashlytics,
+ IdTokenResult,
+ GetRemoteConfigResult,
+ LogComplexEventTypeParameter,
performance as firebasePerformance,
storage as firebaseStorage,
User
} from "nativescript-plugin-firebase";
+import { RewardedVideoAdReward } from "nativescript-plugin-firebase/admob/admob";
+import { FirebaseTrace } from "nativescript-plugin-firebase/performance/performance";
+import { Observable } from "tns-core-modules/data/observable";
import * as fs from "tns-core-modules/file-system";
+import { isAndroid, isIOS } from "tns-core-modules/platform";
+import { alert, prompt } from "tns-core-modules/ui/dialogs";
import { MessagingViewModel } from './messaging-view-model';
-import { FirebaseTrace } from "nativescript-plugin-firebase/performance/performance";
const firebaseWebApi = require("nativescript-plugin-firebase/app");
@@ -74,8 +78,15 @@ export class HelloWorldModel extends Observable {
public doWebLoginByPassword(): void {
this.ensureWebOnAuthChangedHandler();
- firebaseWebApi.auth().signInWithEmailAndPassword('eddyverbruggen@gmail.com', 'firebase')
- .then(() => console.log("User logged in"))
+ firebaseWebApi.auth().signInWithEmailAndPassword('eddyverbruggen+firebase@gmail.com', 'pwd123LOL')
+ .then(() => {
+ console.log("User logged in");
+
+ // now retrieve an auth token we can use to access Firebase from our server
+ firebaseWebApi.auth().currentUser.getIdToken(false)
+ .then((token: string) => console.log("Auth token retrieved: " + token))
+ .catch(errorMessage => console.log("Auth token retrieval error: " + errorMessage));
+ })
.catch(err => {
alert({
title: "Login error",
@@ -236,6 +247,7 @@ export class HelloWorldModel extends Observable {
okButtonText: "Darn!"
});
} else {
+ console.log("key exists? " + result.exists());
this.set("path", path);
this.set("key", result.key);
this.set("value", JSON.stringify(result.val()));
@@ -255,6 +267,7 @@ export class HelloWorldModel extends Observable {
firebaseWebApi.database().ref(path)
.once("value")
.then(result => {
+ console.log("key exists? " + result.exists());
this.set("path", path);
this.set("key", result.key);
this.set("value", JSON.stringify(result.val()));
@@ -267,6 +280,7 @@ export class HelloWorldModel extends Observable {
firebaseWebApi.database().ref(path)
.once("value")
.then(result => {
+ console.log("key exists? " + result.exists());
this.set("path", path);
this.set("key", result.key);
this.set("value", JSON.stringify(result.val()));
@@ -307,6 +321,7 @@ export class HelloWorldModel extends Observable {
firebaseWebApi.database().ref(path).orderByChild(child)
.once("value")
.then(result => {
+ console.log("key exists? " + result.exists());
this.set("path", path);
this.set("key", result.key);
this.set("value", JSON.stringify(result.val()));
@@ -476,7 +491,7 @@ export class HelloWorldModel extends Observable {
});
}
- public doLogAnalyticsEvent(): void {
+ public doLogAnalyticsEvents(): void {
firebase.analytics.logEvent({
// see https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Event.html
key: "add_to_cart",
@@ -503,6 +518,45 @@ export class HelloWorldModel extends Observable {
});
}
);
+
+ /**
+ * Same thing as logEvent but can add an array or specific types not just string (LogComplexEventTypeParameter.BOOLEAN, LogComplexEventTypeParameter.STRING,
+ * LogComplexEventTypeParameter.DOUBLE, LogComplexEventTypeParameter.FLOAT, LogComplexEventTypeParameter.INT, LogComplexEventTypeParameter.ARRAY)
+ */
+ firebase.analytics.logComplexEvent({
+ key: "view_item_list",
+ parameters: [{
+ key: "item1",
+ type: "array",
+ value: [
+ {
+ parameters: [
+ {key: "item_id", value: "id of item", type: LogComplexEventTypeParameter.STRING},
+ {key: "item_name", value: "name of item", type: LogComplexEventTypeParameter.STRING},
+ {key: "item_category", value: "category", type: LogComplexEventTypeParameter.STRING},
+ {key: "item_variant", value: "variant", type: LogComplexEventTypeParameter.STRING},
+ {key: "item_brand", value: "name of item brand", type: LogComplexEventTypeParameter.STRING},
+ {key: "price", value: 1, type: LogComplexEventTypeParameter.DOUBLE},
+ {key: "item_list", value: "name of list", type: LogComplexEventTypeParameter.STRING},
+ {key: "index", value: 1, type: LogComplexEventTypeParameter.INT}
+ ]
+ },
+ {
+ parameters: [
+ {key: "item_id", value: "id of item", type: LogComplexEventTypeParameter.STRING},
+ {key: "item_name", value: "name of item", type: LogComplexEventTypeParameter.STRING},
+ {key: "item_category", value: "category", type: LogComplexEventTypeParameter.STRING},
+ {key: "item_variant", value: "variant", type: LogComplexEventTypeParameter.STRING},
+ {key: "item_brand", value: "name of item brand", type: LogComplexEventTypeParameter.STRING},
+ {key: "price", value: 1, type: LogComplexEventTypeParameter.DOUBLE},
+ {key: "item_list", value: "name of list", type: LogComplexEventTypeParameter.STRING},
+ {key: "index", value: 2, type: LogComplexEventTypeParameter.INT}
+ ]
+ }
+ ]
+ }]
+ });
+
}
public doSetAnalyticsUserProperty(): void {
@@ -590,7 +644,7 @@ export class HelloWorldModel extends Observable {
public doShowAdMobInterstitial(): void {
firebase.admob.showInterstitial({
iosInterstitialId: "ca-app-pub-9517346003011652/6938836122",
- androidInterstitialId: "ca-app-pub-9517346003011652/6938836122",
+ androidInterstitialId: "ca-app-pub-9517346003011652/9225834529",
testing: true,
// Android automatically adds the connected device as test device with testing:true, iOS does not
iosTestDeviceIds: [
@@ -615,7 +669,7 @@ export class HelloWorldModel extends Observable {
public doPreloadAdMobInterstitial(): void {
firebaseAdMob.preloadInterstitial({
iosInterstitialId: "ca-app-pub-9517346003011652/6938836122",
- androidInterstitialId: "ca-app-pub-9517346003011652/6938836122",
+ androidInterstitialId: "ca-app-pub-9517346003011652/9225834529",
testing: true,
// Android automatically adds the connected device as test device with testing:true, iOS does not
iosTestDeviceIds: [
@@ -648,6 +702,69 @@ export class HelloWorldModel extends Observable {
);
}
+ public doPreloadRewardedVideoAd(): void {
+ firebaseAdMob.preloadRewardedVideoAd({
+ iosAdPlacementId: "ca-app-pub-9517346003011652/8586553377",
+ androidAdPlacementId: "ca-app-pub-9517346003011652/2819097664",
+ testing: true,
+ // Android automatically adds the connected device as test device with testing:true, iOS does not
+ iosTestDeviceIds: [
+ "45d77bf513dfabc2949ba053da83c0c7b7e87715", // Eddy's iPhone 6s
+ "fee4cf319a242eab4701543e4c16db89c722731f" // Eddy's iPad Pro
+ ],
+ keywords: [
+ "foo",
+ "bar"
+ ]
+ }).then(
+ () => console.log("AdMob rewarded video ad preloaded"),
+ errorMessage => {
+ alert({
+ title: "AdMob error",
+ message: errorMessage,
+ okButtonText: "Hmmkay"
+ });
+ }
+ );
+ }
+
+ public doShowPreloadedRewardedVideoAd(): void {
+ let reward: RewardedVideoAdReward;
+ firebaseAdMob.showRewardedVideoAd({
+ onRewarded: receivedReward => {
+ reward = receivedReward;
+ console.log("Rewarded video ad: rewarded. Details: " + JSON.stringify(reward));
+ },
+ onLoaded: () => console.log("Rewarded video ad: loaded"),
+ onFailedToLoad: () => console.log("Rewarded video ad: failed to load"),
+ onOpened: () => console.log("Rewarded video ad: opened"),
+ onStarted: () => console.log("Rewarded video ad: started"),
+ onCompleted: () => console.log("Rewarded video ad: completed"),
+ onClosed: () => {
+ console.log("Rewarded video ad: closed");
+ if (reward) {
+ setTimeout(() => {
+ alert({
+ title: "You were rewarded!",
+ message: `${reward.amount} ${reward.type}`,
+ okButtonText: "Thanks!"
+ });
+ }, 500);
+ }
+ },
+ onLeftApplication: () => console.log("Rewarded video ad: left application")
+ }).then(
+ () => console.log("AdMob rewarded video ad showing"),
+ errorMessage => {
+ alert({
+ title: "AdMob error",
+ message: errorMessage,
+ okButtonText: "Hmmkay"
+ });
+ }
+ );
+ }
+
/**
* Note that an interstitial is supposed to be hidden by clicking the close button,
* so there's no function to do it programmatically.
@@ -674,10 +791,11 @@ export class HelloWorldModel extends Observable {
firebase.getRemoteConfig({
developerMode: true,
cacheExpirationSeconds: 600, // 10 minutes, default is 12 hours
- properties: [{
- "key": "holiday_promo_enabled",
- "default": false
- },
+ properties: [
+ {
+ "key": "holiday_promo_enabled",
+ "default": false
+ },
{
"key": "default_only_prop",
"default": 77
@@ -699,7 +817,7 @@ export class HelloWorldModel extends Observable {
"default": 11
}]
}).then(
- result => {
+ (result: GetRemoteConfigResult) => {
console.log("remote config fetched: " + JSON.stringify(result.properties));
alert({
title: `Fetched at ${result.lastFetch} ${result.throttled ? '(throttled)' : ''}`,
@@ -891,8 +1009,8 @@ export class HelloWorldModel extends Observable {
type: firebase.LoginType.PASSWORD,
passwordOptions: {
// note that these credentials have been pre-configured in our demo firebase instance
- email: 'eddyverbruggen@gmail.com',
- password: 'firebase'
+ email: 'eddyverbruggen+firebase@gmail.com',
+ password: 'pwd123LOL'
}
}).then(
result => {
@@ -904,16 +1022,12 @@ export class HelloWorldModel extends Observable {
});
// now retrieve an auth token we can use to access Firebase from our server
- firebase.getAuthToken({
- forceRefresh: false
- }).then(
- token => {
- console.log("Auth token retrieved: " + token);
- },
- errorMessage => {
- console.log("Auth token retrieval error: " + errorMessage);
- }
- );
+ firebase.getAuthToken(
+ {
+ forceRefresh: false
+ })
+ .then((result: IdTokenResult) => console.log("Auth token retrieved: " + JSON.stringify(result)))
+ .catch(errorMessage => console.log("Auth token retrieval error: " + errorMessage));
},
errorMessage => {
console.log("Login error: " + errorMessage);
@@ -1052,9 +1166,28 @@ export class HelloWorldModel extends Observable {
}
public doResetPassword(): void {
- firebase.resetPassword({
- email: 'eddyverbruggen@gmail.com'
- }).then(
+ firebase.sendPasswordResetEmail("eddyverbruggen+firebase@gmail.com").then(
+ () => {
+ console.log("Password reset. Check your email.");
+ this.set("userEmailOrPhone", "Password reset mail sent to eddyverbruggen@gmail.com.");
+ alert({
+ title: "Password reset. Check your email.",
+ okButtonText: "OK, nice!"
+ });
+ },
+ error => {
+ console.log("Password reset error: " + error);
+ alert({
+ title: "Password reset error",
+ message: error,
+ okButtonText: "Hmmkay :("
+ });
+ }
+ );
+ }
+
+ public doWebResetPassword(): void {
+ firebaseWebApi.auth().sendPasswordResetEmail("eddyverbruggen+firebase@gmail.com").then(
() => {
console.log("Password reset. Check your email.");
this.set("userEmailOrPhone", "Password reset mail sent to eddyverbruggen@gmail.com.");
@@ -1074,6 +1207,90 @@ export class HelloWorldModel extends Observable {
);
}
+ public doUpdateEmail(): void {
+ firebase.updateEmail("eddyverbruggen+firebase@gmail.com").then(
+ () => {
+ console.log("Email updated.");
+ this.set("userEmailOrPhone", "Email updated to eddyverbruggen+firebase@gmail.com");
+ alert({
+ title: "Email updated.",
+ okButtonText: "OK, nice!"
+ });
+ },
+ error => {
+ console.log("Email update error: " + error);
+ alert({
+ title: "Email update error",
+ message: error,
+ okButtonText: "Hmmkay :("
+ });
+ }
+ );
+ }
+
+ public doWebUpdateEmail(): void {
+ firebaseWebApi.auth().updateEmail("eddyverbruggen+firebase@gmail.com").then(
+ () => {
+ console.log("Email updated.");
+ this.set("userEmailOrPhone", "Email updated to eddyverbruggen+firebase@gmail.com");
+ alert({
+ title: "Email updated.",
+ okButtonText: "OK, nice!"
+ });
+ },
+ error => {
+ console.log("Email update error: " + error);
+ alert({
+ title: "Email update error",
+ message: error,
+ okButtonText: "Hmmkay :("
+ });
+ }
+ );
+ }
+
+ public doUpdatePassword(): void {
+ firebase.updatePassword("pwd123LOL").then(
+ () => {
+ console.log("Password updated.");
+ this.set("userEmailOrPhone", "Password updated to pwd123LOL");
+ alert({
+ title: "Password updated.",
+ okButtonText: "OK, nice!"
+ });
+ },
+ error => {
+ console.log("Password update error: " + error);
+ alert({
+ title: "Password update error",
+ message: error,
+ okButtonText: "Hmmkay :("
+ });
+ }
+ );
+ }
+
+ public doWebUpdatePassword(): void {
+ firebaseWebApi.auth().updatePassword("pwd123LOL").then(
+ () => {
+ console.log("Password updated.");
+ this.set("userEmailOrPhone", "Password updated to pwd123LOL");
+ alert({
+ title: "Password updated.",
+ okButtonText: "OK, nice!"
+ });
+ },
+ error => {
+ console.log("Password update error: " + error);
+ alert({
+ title: "Password update error",
+ message: error,
+ okButtonText: "Hmmkay :("
+ });
+ }
+ );
+ }
+
public doSendEmailVerification(): void {
firebase.sendEmailVerification().then(
() => {
@@ -1348,10 +1565,11 @@ export class HelloWorldModel extends Observable {
limit: {
type: firebase.QueryLimitType.LAST,
value: 2
- }
+ },
+ singleEvent: true
}
).then(
- result => console.log("firebase.doQueryBulgarianCompanies done; added a listener"),
+ result => console.log("firebase.doQueryBulgarianCompanies done; added a listener, result: " + JSON.stringify(result)),
errorMessage => {
alert({
title: "Query error",
@@ -1387,7 +1605,8 @@ export class HelloWorldModel extends Observable {
{
singleEvent: true,
orderBy: {
- type: firebase.QueryOrderByType.KEY
+ type: firebase.QueryOrderByType.CHILD,
+ value: "first"
}
}
).then(
@@ -1676,16 +1895,13 @@ export class HelloWorldModel extends Observable {
);
}
- public doForceCrashIOS(): void {
- Crashlytics.sharedInstance().crash();
- }
-
- public doForceCrashAndroid(): void {
- throw new java.lang.Exception("Forced an exception.");
+ public doCrash(): void {
+ console.log("Gonna crash on demand");
+ firebaseCrashlytics.crash();
}
public doLogMessageCrashlytics(): void {
- firebaseCrashlytics.log(1, "TAG", "Tag message");
+ firebaseCrashlytics.log("Tag message", "TAG", 1);
if (isAndroid) {
firebaseCrashlytics.sendCrashLog(new java.lang.Exception("test Exception"));
} else if (isIOS) {
diff --git a/demo/app/package.json b/demo/app/package.json
index 7c8be405..be85b967 100644
--- a/demo/app/package.json
+++ b/demo/app/package.json
@@ -26,9 +26,9 @@
},
"homepage": "https://github.com/NativeScript/template-hello-world-ts",
"android": {
- "v8Flags": "--expose_gc",
- "discardUncaughtJsExceptions": true
+ "v8Flags": "--expose_gc"
},
+ "discardUncaughtJsExceptions": false,
"devDependencies": {
"nativescript-dev-typescript": "^0.3.0"
},
diff --git a/demo/app_resources/iOS/GoogleService-Info.plist b/demo/app_resources/iOS/GoogleService-Info.plist
index e0e0fc65..7a4c2cb4 100644
--- a/demo/app_resources/iOS/GoogleService-Info.plist
+++ b/demo/app_resources/iOS/GoogleService-Info.plist
@@ -37,4 +37,4 @@
DATABASE_URL
https://n-plugin-test.firebaseio.com
-
+
\ No newline at end of file
diff --git a/demo/app_resources/iOS/Info.plist b/demo/app_resources/iOS/Info.plist
index e97ff6c6..c04d233d 100644
--- a/demo/app_resources/iOS/Info.plist
+++ b/demo/app_resources/iOS/Info.plist
@@ -102,5 +102,7 @@
NSContactsUsageDescription
Required for Firebase Invites
+ GADApplicationIdentifier
+ ca-app-pub-9517346003011652~2508636525
diff --git a/demo/app_resources/iOS/app.entitlements b/demo/app_resources/iOS/app.entitlements
index 33b5cd05..8a313bce 100644
--- a/demo/app_resources/iOS/app.entitlements
+++ b/demo/app_resources/iOS/app.entitlements
@@ -8,5 +8,5 @@
applinks:j4ctx.app.goo.gl
-
+
\ No newline at end of file
diff --git a/demo/app_resources/iOS/build.xcconfig b/demo/app_resources/iOS/build.xcconfig
index f33179d2..dbbb7751 100644
--- a/demo/app_resources/iOS/build.xcconfig
+++ b/demo/app_resources/iOS/build.xcconfig
@@ -6,3 +6,5 @@ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CODE_SIGN_ENTITLEMENTS = demo/demo.entitlements
// DEVELOPMENT_TEAM = "Don't set this here, but store in the platforms folder when prompted (to support CI and cross-team development)"
+
+// DEVELOPMENT_TEAM = 8Q5F6M3TNS
diff --git a/demo/firebasefunctions/functions/package.json b/demo/firebasefunctions/functions/package.json
index 76eed03d..88330175 100644
--- a/demo/firebasefunctions/functions/package.json
+++ b/demo/firebasefunctions/functions/package.json
@@ -11,12 +11,12 @@
},
"main": "lib/index.js",
"dependencies": {
- "firebase-admin": "~6.0.0",
- "firebase-functions": "^2.0.3"
+ "firebase-admin": "~7.0.0",
+ "firebase-functions": "~2.2.0"
},
"devDependencies": {
- "tslint": "~5.8.0",
- "typescript": "~2.8.3"
+ "tslint": "~5.12.1",
+ "typescript": "~3.3.1"
},
"private": true
}
diff --git a/demo/firebasefunctions/functions/src/index.ts b/demo/firebasefunctions/functions/src/index.ts
index 1c149329..c17792e1 100644
--- a/demo/firebasefunctions/functions/src/index.ts
+++ b/demo/firebasefunctions/functions/src/index.ts
@@ -14,6 +14,6 @@ export const helloWorldJson = functions.https.onRequest((request, response) => {
// this is a proper callable function
export const helloName = functions.https.onCall((data, context) => {
return {
- message: "Hello, " + data
+ message: "Hello: " + data
}
});
\ No newline at end of file
diff --git a/demo/firebasefunctions/functions/tsconfig.json b/demo/firebasefunctions/functions/tsconfig.json
index d098421a..7440085f 100644
--- a/demo/firebasefunctions/functions/tsconfig.json
+++ b/demo/firebasefunctions/functions/tsconfig.json
@@ -2,6 +2,7 @@
"compilerOptions": {
"lib": ["es6"],
"module": "commonjs",
+ "skipLibCheck": true,
"noImplicitReturns": true,
"outDir": "lib",
"sourceMap": true,
@@ -10,5 +11,8 @@
"compileOnSave": true,
"include": [
"src"
+ ],
+ "exclude": [
+ "node_modules"
]
}
diff --git a/demo/package.json b/demo/package.json
index 15b4d52b..96ec1a03 100644
--- a/demo/package.json
+++ b/demo/package.json
@@ -2,18 +2,18 @@
"nativescript": {
"id": "org.nativescript.firebasedemo",
"tns-ios": {
- "version": "5.0.0"
+ "version": "5.2.0"
},
"tns-android": {
- "version": "5.0.0"
+ "version": "5.2.0"
}
},
"dependencies": {
"firebase-functions": "^2.0.5",
- "nativescript-plugin-firebase": "file:../publish/package/nativescript-plugin-firebase-7.4.6.tgz",
+ "nativescript-plugin-firebase": "file:../publish/package/nativescript-plugin-firebase-8.2.0.tgz",
"nativescript-theme-core": "^1.0.4",
"nativescript-unit-test-runner": "^0.3.4",
- "tns-core-modules": "~5.0.5"
+ "tns-core-modules": "~5.2.0"
},
"devDependencies": {
"@types/jasmine": "~2.8.0",
@@ -29,7 +29,7 @@
"nativescript-css-loader": "~0.26.0",
"nativescript-dev-typescript": "~0.7.1",
"nativescript-dev-webpack": "^0.16.2",
- "tns-platform-declarations": "~5.0.5",
+ "tns-platform-declarations": "~5.2.0",
"tslint": "~5.4.3",
"typescript": "~2.8.0"
},
@@ -37,4 +37,4 @@
"build.plugin": "cd ../src && npm run build",
"ci.tslint": "npm i && tslint --config '../tslint.json' 'app/**/*.ts' --exclude '**/node_modules/**' --exclude '**/typings/**'"
}
-}
\ No newline at end of file
+}
diff --git a/docs/ADMOB.md b/docs/ADMOB.md
index c51f9b39..afab9aba 100644
--- a/docs/ADMOB.md
+++ b/docs/ADMOB.md
@@ -1,9 +1,10 @@
-## Enabling AdMob
-Since plugin version 3.10.0 you can use Firebase _AdMob_ features.
+
+
+_AdMob currently supports these three types of Ads, as does this plugin_
-_AdMob_ lets you show banners or interstitials (full screen ads) in your app so you can earn some money.
+## Enabling AdMob
### Android
> ⚠️ Important! Plugin version 7.4.0+ requires you to do this - or your app will crash on start-up! ⚠️
@@ -21,6 +22,16 @@ Open your App_Resources/Android/AndroidManifest.xml file and add this `meta-data
### iOS
+> ⚠️ Important! Plugin version 8.2.0+ requires you to do this - or your app will crash on start-up! ⚠️
+
+As can be read [here](https://developers.google.com/admob/ios/quick-start#update_your_infoplist) you should open your
+App_Resources/iOS/Info.plist file and add this `GADApplicationIdentifier` key and replace the value by the actual App ID of [your app](https://apps.admob.com/)!:
+
+```xml
+ GADApplicationIdentifier
+ ca-app-pub-9517346003011652~2508636525
+```
+
#### App Transport Security
Open `app/App_Resources/iOS/Info.plist` and add this to the bottom:
@@ -39,16 +50,17 @@ Open `app/App_Resources/iOS/Info.plist` and add this to the bottom:
[More info on this subject.](https://firebase.google.com/docs/admob/ios/app-transport-security)
## Functions
+> Note that it may take up to 24 hours after adding an Ad to your [AdMob console](https://apps.admob.com) before it's available for your app. Until then you'll see a ⚠️ warning about an unknown Ad ID.
### admob.showBanner
-Go [manage your AdMob app](https://apps.admob.com/#account/appmgmt:) and grab the banner, then show it in your app:
+Go [manage your AdMob app](https://apps.admob.com) and grab the banner, then show it in your app:
```js
firebase.admob.showBanner({
size: firebase.admob.AD_SIZE.SMART_BANNER, // see firebase.admob.AD_SIZE for all options
margins: { // optional nr of device independent pixels from the top or bottom (don't set both)
bottom: 10,
- top: 0
+ top: -1
},
androidBannerId: "ca-app-pub-9517346003011652/7749101329",
iosBannerId: "ca-app-pub-9517346003011652/3985369721",
@@ -143,8 +155,56 @@ After the preload Promise resolved successfully, you can show the interstitial a
);
```
+### preloadRewardedVideoAd
+Use this for instance while loading your view, so it's ready for the moment you want to actually show it (by calling `showRewardedVideoAd`).
+
+```js
+firebase.admob.preloadRewardedVideoAd({
+ testing: true,
+ iosAdPlacementId: "ca-app-pub-XXXXXX/YYYYY2", // add your own
+ androidAdPlacementId: "ca-app-pub-AAAAAAAA/BBBBBB2", // add your own
+ keywords: ["keyword1", "keyword2"], // add keywords for ad targeting
+ }).then(
+ function() {
+ console.log("RewardedVideoAd preloaded - you can now call 'showRewardedVideoAd' whenever you're ready to do so");
+ },
+ function(error) {
+ console.log("admob preloadRewardedVideoAd error: " + error);
+ }
+)
+```
+
+### showRewardedVideoAd
+At any moment after `preloadRewardedVideoAd` successfully resolves, you can call `showRewardedVideoAd`.
+
+Note that when you want to use `showRewardedVideoAd` again, you also have to use `preloadRewardedVideoAd` again because those ads can't be reused.
+
+`onRewarded` is probably the only callback you need to worry about.
+
+```js
+firebase.admob.showRewardedVideoAd({
+ onRewarded: (reward) => {
+ // the properties 'amount' and 'type' correlate to the values set at https://apps.admob.com
+ console.log("onRewarded called with amount " + reward.amount);
+ console.log("onRewarded called with type " + reward.type);
+ },
+ onLeftApplication: () => console.log("onLeftApplication"),
+ onClosed: () => console.log("onClosed"),
+ onOpened: () => console.log("onOpened"),
+ onStarted: () => console.log("onStarted"),
+ onCompleted: () => console.log("onCompleted"),
+}).then(
+ function() {
+ console.log("RewardedVideoAd showing");
+ },
+ function(error) {
+ console.log("showRewardedVideoAd error: " + error);
+ }
+)
+```
+
## What about the nativescript-admob plugin?
-There's currently no functional difference between the AdMob features in the Firebase plugin and
+There's no functional difference between the AdMob features in the Firebase plugin and
[nativescript-admob](https://github.com/EddyVerbruggen/nativescript-admob).
The main advantage of using the version in the Firebase plugin is to avoid a gradle build conflict
diff --git a/docs/ANALYTICS.md b/docs/ANALYTICS.md
index 938fc7b9..cdbd5ce1 100644
--- a/docs/ANALYTICS.md
+++ b/docs/ANALYTICS.md
@@ -99,3 +99,10 @@ You can also pass this property during `init()`:
```
> If you want to disable collection without calling this function programmatically, then you can add a flag to your `App_Resources/iOS/Info.plist` and `App_Resources/Android/AndroidManifest.xml`, see [Firebase's documentation](https://firebase.google.com/support/guides/disable-analytics) for details.
+
+### analytics.setSessionTimeoutDuration
+Sets the duration of inactivity that terminates the current session. The default value is 1800 seconds (30 minutes).
+
+```js
+ firebase.analytics.setSessionTimeoutDuration(600); // 10 minutes
+```
diff --git a/docs/AUTHENTICATION.md b/docs/AUTHENTICATION.md
index 017db054..5c497343 100644
--- a/docs/AUTHENTICATION.md
+++ b/docs/AUTHENTICATION.md
@@ -320,9 +320,7 @@ that receives the link will need to be redirected to your app.
-#### Managing email-password accounts
-
-##### Creating a Password account
+#### Creating a Password account
This may not work on an (Android) simulator. See #463.
@@ -365,43 +363,81 @@ This may not work on an (Android) simulator. See #463.
#### Resetting a password
-```js
- firebase.resetPassword({
- email: 'useraccount@provider.com'
- }).then(
- function () {
- // called when password reset was successful,
- // you could now prompt the user to check his email
- },
- function (errorMessage) {
- console.log(errorMessage);
- }
- );
+> ⚠️ The method name and signature has changed in 8.0.0 from `resetPassword` to `sendPasswordResetEmail` to better align with the Web API.
+
+
+ Native API
+
+```typescript
+ firebase.sendPasswordResetEmail("user@example.com")
+ .then(() => console.log("Password reset email sent"))
+ .catch(error => console.log("Error sending password reset email: " + error));
+```
+
+
+
+ Web API
+
+```typescript
+ firebaseWebApi.auth().sendPasswordResetEmail("user@example.com")
+ .then(() => console.log("Password reset email sent"))
+ .catch(error => console.log("Error sending password reset email: " + error));
+```
+
+
+#### Updating an email address
+Note that changing an email address may fail if your login for this `email` was too long ago (per Firebase's standards, whatever they are).
+
+
+ Native API
+
+```typescript
+ firebase.updateEmail("user@example.com")
+ .then(() => console.log("Email updated"))
+ .catch(error => console.log("Error updating email: " + error));
```
+
+
+
+ Web API
+
+```typescript
+ firebaseWebApi.auth().updateEmail("user@example.com")
+ .then(() => console.log("Email updated"))
+ .catch(error => console.log("Error updating email: " + error));
+```
+
+
+#### Updating a password
+> ⚠️ The method name and signature has changed in 8.0.0 from `changePassword` to `updatePassword` to better align with the Web API.
-#### Changing a password
Note that changing a password may fail if your login for this `email` was too long ago (per Firebase's standards, whatever they are).
-```js
- firebase.changePassword({
- email: 'useraccount@provider.com',
- oldPassword: 'myOldPassword',
- newPassword: 'myNewPassword'
- }).then(
- function () {
- // called when password change was successful
- },
- function (errorMessage) {
- console.log(errorMessage);
- }
- );
+
+ Native API
+
+```typescript
+ firebase.updatePassword("myNewPassword")
+ .then(() => console.log("Password updated"))
+ .catch(error => console.log("Error updating password: " + error));
+```
+
+
+
+ Web API
+
+```typescript
+ firebaseWebApi.auth().updatePassword("myNewPassword")
+ .then(() => console.log("Password updated"))
+ .catch(error => console.log("Error updating password: " + error));
```
+
### Phone Verification
* Don't forget to enable Phone login in your firebase instance.
* You can only test this on a real device (not on an emulator/simulator).
* Use the phone number of the device you're testing on.
-* _ANDROID:_ [Make sure you've uploaded your SHA1 fingerprint(s)](https://developers.google.com/android/guides/client-auth) to the Firebase console, then download the latest `google-services.json` file and add it to `app/App_Resources/Android`.
+* _ANDROID:_ [Make sure you've uploaded your SHA1 fingerprints](https://developers.google.com/android/guides/client-auth) to the Firebase console, then download the latest `google-services.json` file and add it to `app/App_Resources/Android`.
* _iOS:_ Make sure you have messaging enabled as well, as this uses push notifications on iOS.
```js
@@ -425,6 +461,9 @@ Note that changing a password may fail if your login for this `email` was too lo
### Custom login
Use this login type to authenticate against firebase using a token generated by your own backend server. See these [instructions on how to generate the authentication token](https://firebase.google.com/docs/auth/server).
+
+ Native API
+
```js
var token = "myBackendToken";
@@ -442,6 +481,17 @@ Use this login type to authenticate against firebase using a token generated by
}
);
```
+
+
+
+ Web API
+
+```typescript
+ firebaseWebApi.auth().signInWithCustomToken(token)
+ .then(result => console.log(JSON.stringify(result)))
+ .catch(error => console.log(JSON.stringify(error)));
+```
+
### Facebook login
@@ -517,7 +567,13 @@ Upon successful authentication, Facebook creates an access token that can be obt
First, enable Google Sign-In in your firebase instance and add the _Web SDK configuration_.
-Make sure you've uploaded your SHA1 fingerprint(s)](https://developers.google.com/android/guides/client-auth) to the Firebase console, then download the latest `google-services.json` file and add it to `app/App_Resources/Android`.
+Make sure you've uploaded your [SHA1 fingerprints](https://developers.google.com/android/guides/client-auth) to the Firebase console, then download the latest `google-services.json` file and add it to `app/App_Resources/Android`.
+
+> **Uploading your SHA1 fingerprint is required for _debug_ and _release_ builds.**
+
+> If you have enabled Google Play's _App Signing_ feature you will need to add the SHA1 for Google's signing certificate to your Firebase project's fingerprints. If you fail to do this, your release builds will fail because they were not signed by Google. See image below:
+>
+>
Then add the following lines to your code and check for setup instructions for your platform below.
@@ -603,23 +659,42 @@ To solve, you will want to pass in the appropriate iOS controller of the active
2. Google Sign-In requires an SHA1 fingerprint: see [Authenticating Your Client for details](https://developers.google.com/android/guides/client-auth). If you don't do this you will see the account selection popup, but you won't be able to actually sign in.
3. Those fingerprints need to be added to your Firebase console. Go to 'project overview', 'project settings', then scroll down a bit.
-### getAuthToken
-If you want to authenticate your user from your backend server you can obtain
-a Firebase auth token for the currently logged in user.
+### getAuthToken / getIdToken
+If you want to authenticate your user from your backend server you can obtain a Firebase auth token for the currently logged in user.
+
+You'll get the token, as well as the provider that was used to sign in, and any custom claims you may have previously set via the Firebase Admin SDK as outlined [here](https://firebase.google.com/docs/auth/admin/custom-claims):
+
+
+
+ Native API
```js
firebase.getAuthToken({
// default false, not recommended to set to true by Firebase but exposed for {N} devs nonetheless :)
forceRefresh: false
}).then(
- function (token) {
- console.log("Auth token retrieved: " + token);
+ function (result) {
+ // for both platforms
+ console.log("Auth token retrieved: " + result.token);
+ console.log("Sign-In provider: " + result.signInProvider);
+ console.log("Specific custom claim retrieved: " + result.claims.yourClaimKey); // or result.claims["yourClaimKey"]
},
function (errorMessage) {
- console.log("Auth token retrieval error: " + errorMessage);
+ console.log("Auth result retrieval error: " + errorMessage);
}
);
```
+
+
+
+ Web API
+
+```typescript
+ firebaseWebApi.auth().currentUser.getIdToken(false)
+ .then((token: string) => console.log("Auth token retrieved: " + token))
+ .catch(errorMessage => console.log("Auth token retrieval error: " + errorMessage));
+```
+
### logout
Shouldn't be more complicated than:
@@ -642,6 +717,29 @@ Shouldn't be more complicated than:
```
+### unlinking provider
+For a given user, and a given provider ("google.com","password",...)
+
+
+ Native API
+
+```js
+ user.unlink(providerId /* string */)
+ .then(user => console.log("Unlink OK, user: " + JSON.stringify(user)))
+ .catch(error => console.log("Unlink error: " + JSON.stringify(error)));
+```
+
+
+
+ Web API
+
+```js
+ firebaseWebApi.auth().unlink(providerId /* string */)
+ .then(user => console.log("Unlink OK, user: " + JSON.stringify(user)))
+ .catch(error => console.log("Unlink error: " + JSON.stringify(error)));
+```
+
+
### reauthenticate
Some security-sensitive actions (deleting an account, changing a password) require that the user has recently signed in.
If you perform one of these actions, and the user signed in too long ago, the action fails.
@@ -677,14 +775,46 @@ When this happens (or to prevent it from happening), re-authenticate the user.
Sending an "email confirmation" email can be done after the user logged in:
```js
- firebase.sendEmailVerification().then(
- function () {
- console.log("Email verification sent");
- },
- function (error) {
- console.log("Error sending email verification: " + error);
- }
- );
+firebase.sendEmailVerification().then(
+ function () {
+ console.log("Email verification sent");
+ },
+ function (error) {
+ console.log("Error sending email verification: " + error);
+ }
+);
+```
+
+You can also pass state to this function - for details on the properties, see [the Firebase docs](https://firebase.google.com/docs/auth/web/passing-state-in-email-actions).
+
+```js
+firebase.sendEmailVerification({
+ url: "https://www.google.com",
+ handleCodeInApp: true,
+ iOS: {
+ bundleId: "com.bla.hoopla",
+ dynamicLinkDomain: "xyz"
+ },
+ android: {
+ minimumVersion: "13",
+ installApp: true,
+ packageName: "x.y.z"
+ }
+ }).then(
+ function () {
+ console.log("Email verification sent");
+ },
+ function (error) {
+ console.log("Error sending email verification: " + error);
+ }
+);
```
+Note that you can also use this with the Web API:
+```js
+const firebaseWebApi = require("nativescript-plugin-firebase/app");
+const user = firebaseWebApi.auth().currentUser;
+
+user.sendEmailVerification() // see the implementation above
+```
diff --git a/docs/CRASHREPORTING.md b/docs/CRASHREPORTING.md
index dda5cd91..0bcaf932 100644
--- a/docs/CRASHREPORTING.md
+++ b/docs/CRASHREPORTING.md
@@ -9,6 +9,11 @@ You will be prompted during installation of the plugin to enable either Crashlyt
Note that if you want to use Crashlytics, make sure your `firebase.nativescript.json` file has `"crashlytics": true` and `"crash_reporting": false`,
then remove the `platforms` folder so these changes are picked up.
+## Configuration in the Firebase Console
+When setting up Crashlytics, select "This app is new to Crashlytics" and press "Next".
+Then the screen changes to something like "waiting for your first crash report".
+Then produce a crash, and it can easily take a day before that screen changes (later data comes in much quicker).
+
## Crashlytics API
### `sendCrashLog`
@@ -39,7 +44,7 @@ if (isAndroid) {
Set a value that will be logged with an error and showing in the Firebase console on the 'Keys' tab of the error details.
```typescript
-import { crashlytics } from "nativescript-plugin-firebase"; // and do: crashlytics.sendCrashLog
+import { crashlytics } from "nativescript-plugin-firebase";
crashlytics.setString("test_key", "test_value");
crashlytics.setBool("test_key_bool", true);
@@ -49,3 +54,14 @@ crashlytics.setFloat("test_key", 54646.45);
crashlytics.setUserId("user#42");
```
+
+### `crash`
+For easier testing, version 8.2.0 exposed this `crash()` function of the native Firebase Crashlytics SDKs:
+
+```typescript
+import { crashlytics } from "nativescript-plugin-firebase";
+
+crashlytics.crash();
+```
+
+> This should crash your app unless you have `discardUncaughtJsExceptions` set to `true` in `app/package.json`.
\ No newline at end of file
diff --git a/docs/DATABASE.md b/docs/DATABASE.md
index 0fc707d1..288c451a 100644
--- a/docs/DATABASE.md
+++ b/docs/DATABASE.md
@@ -170,7 +170,8 @@ Let's say we have the structure as defined at `setValue`, then use this query to
if (!result.error) {
console.log("Event type: " + result.type);
console.log("Key: " + result.key);
- console.log("Value: " + JSON.stringify(result.value));
+ console.log("Value: " + JSON.stringify(result.value)); // a JSON object
+ console.log("Children: " + JSON.stringify(result.children)); // an array, added in plugin v 8.0.0
}
};
@@ -311,7 +312,8 @@ The link is for the iOS SDK, but it's the same for Android.
console.log("Listener error: " + result.error);
} else {
console.log("Key: " + result.key);
- console.log("Calue: " + JSON.stringify(result.val()));
+ console.log("key exists? " + result.exists());
+ console.log("Value: " + JSON.stringify(result.val()));
}
};
@@ -345,6 +347,42 @@ You can see an example of this (for both the native and web API) in the [demo ap
```
+### OnDisconnect
+Use OnDisconnect to run operations on Firebase Realtime Database when the client disconnects.
+Disconnections can happen when the app is killed from the recent tasks, internet loss, etc. When
+you regain internet (app wasn't closed) then the database will update instantly.
+
+Note that if the device restarts / app is killed in background (ungraceful disconnect) the database will
+NOT update in realtime (there's no way), but after Firebase detects that the device is unreachable then it
+will run the function given to onDisconnect.
+
+Rather than passing in callbacks every function returns a promise.
+
+
+ Native API
+
+```typescript
+ firebase.onDisconnect("/companies").cancel().then(() => console.log("Success")).catch(error => console.log(error));
+ firebase.onDisconnect("/companies").remove();
+ firebase.onDisconnect("/companies").set(value);
+ firebase.onDisconnect("/companies").setWithPriority(value, priority /* string | number */);
+ firebase.onDisconnect("/companies").update(value);
+
+```
+
+
+
+ Web API
+
+```typescript
+ firebaseWebApi.database().ref("/companies").onDisconnect().cancel().then(() => console.log("Success"));
+ firebaseWebApi.database().ref("/companies").onDisconnect().remove();
+ firebaseWebApi.database().ref("/companies").onDisconnect().set(value);
+ firebaseWebApi.database().ref("/companies").onDisconnect().setWithPriority(value, priority /* string | number*/);
+ firebaseWebApi.database().ref("/companies").onDisconnect().update(values);
+```
+
+
### remove
You can remove the entire database content by passing `/` as param,
but if you only want to for instance wipe everything at `/users`, do this:
@@ -371,6 +409,94 @@ but if you only want to for instance wipe everything at `/users`, do this:
```
+### Transaction
+Transactions are used when you want to atomically modify data at this location. This
+ensures there are no conflicts with other clients writing to the same location at the
+same time.
+
+You can look at the [docs](https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction) for more information.
+
+Note that a return value of `null` will delete the value at this location whereas returning
+undefined will not modify the data at this location. Firebase web aborts the transaction when
+given an undefined, but due to technically difficulties we just return transaction success which
+results in committed = true. On transaction complete a promise is returned containing
+{committed:boolean, snapshot: DataSnapshot} and an error will be returned if the transaciton
+failed.
+
+
+ Native API
+
+```typescript
+firebase.transaction(path, (currentValue => {
+ if (currentValue === null) {
+ return 0;
+ } else {
+ return ++currentValue; // Increment the current value. Do not try to increment currentValue if its NaN!
+ }
+ }))
+ .then((result: { committed: boolean, snapshot: firebase.DataSnapshot }) => {
+ console.log(result.committed + " snapshotValue: " + result.snapshot.val());
+ }).catch(err => console.log("Encountered an error " + err));
+```
+
+
+
+ Web API
+
+```typescript
+firebaseWebApi.database().ref(path).transaction(currentValue => {
+ if (currentValue === null) {
+ return { name: { first: 'Ada', last: 'Lovelace' } };
+ } else {
+ // console.log('User ada already exists.');
+ return; // Abort the transaction.
+ }
+ })
+ .then((result: { committed: boolean, snapshot: firebase.DataSnapshot }) => {
+ console.log(result.committed + " snapshotValue: " + result.snapshot.val());
+ }).catch(err => console.log("Encountered an error " + err));
+
+
+firebaseWebApi.database().ref(path).transaction(currentValue => {
+ if (currentValue === null) {
+ return null; // Do nothing if this value doesn't exist or return undefined (null works here because theres nothing at this path)
+ //return 0 // If you want to put a 0 in if no value exist
+ } else {
+ return ++currentValue; // increment the value
+ }
+ })
+
+// Based off Firebase simple blog post. You can also treat the
+// data as an object and return an updated version of post
+firebaseWebApi.database().ref(path).transaction(function(post) {
+ if (post) {
+ console.log("Post Object looks like: " + JSON.stringify(post));
+ if (post.stars && post.stars[uid]) {
+ post.starCount--;
+ post.stars[uid] = null;
+ } else {
+ post.starCount++;
+ if (!post.stars) {
+ post.stars = {};
+ }
+ post.stars[uid] = true;
+ }
+ }
+ return post;
+ });
+```
+
+
+### enableLogging
+The Firebase Realtime Database allows you turn on/off logs. This can be especially useful when trying to pinpoint any issues you may be having.
+By default the log level is set to INFO. Turning on logging will set the log level to DEBUG and off will set it to NONE.
+
+You MUST call `enableLogging()` before initializing firebase otherwise the app will crash.
+
+```js
+ firebase.enableLogging(true); // OR
+ firebaseWebApi.database.enableLogging(false);
+```
### keepInSync
The Firebase Realtime Database synchronizes and stores a local copy of the data for active listeners (see the methods above). In addition, you can keep specific locations in sync.
diff --git a/docs/FIRESTORE.md b/docs/FIRESTORE.md
index 0a8ae5ab..0c9cd654 100644
--- a/docs/FIRESTORE.md
+++ b/docs/FIRESTORE.md
@@ -60,10 +60,54 @@ const unsubscribe = citiesCollection.onSnapshot((snapshot: firestore.QuerySnapsh
// then after a while, to detach the listener:
unsubscribe();
+
+/**
+ * If you pass in SnapshotOptions for the first parameter then your next should be onNext and
+ * onError if you want for the third parameter. If you pass onNext as the first parameter the
+ * second will be interpreted as onError callback. Note that you could pass onComplete, but
+ * it will never be called as stated by Firestore docs.
+ *
+ * onError callbacks are optional!
+ * onSnapshot(p1: SnapshotListenOptions|onNextCallback, p2?: onNextCallback | onErrorCallback, p3?: onErrorCallback?)
+ */
+const docRef: firestore.DocumentReference = firebase.firestore().collection("cities").doc("SF");
+docRef.onSnapshot(
+ {includeMetadataChanges: true}, // Comment out if you just want onNext && onError callbacks
+ (doc: firestore.DocumentSnapshot) => {
+
+ const source = doc.metadata.fromCache ? "local cache" : "server";
+ console.log("Data came from " + source);
+ console.log("Has pending writes? " + doc.metadata.hasPendingWrites);
+ },
+ (error: Error) => {
+ console.error(error);
+ }
+ );
```
> Using **Observables**? [Check the example in the demo app](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/blob/f6972433dea48bf1d342a6e4ef7f955dff341837/demo-ng/app/item/items.component.ts#L187-L198).
+#### Snapshot metadata (for queries and documents)
+Firestore can return metadata when passing the `includeMetadataChanges` boolean property. This can be used for:
+
+- `snapshot.metadata.fromCache`: True if the snapshot was created from cached data rather than guaranteed up-to-date server data.
+- `snapshot.metadata.hasPendingWrites`: True if the snapshot contains the result of local writes that have not yet been committed to the backend.
+
+```typescript
+import { firestore } from "nativescript-plugin-firebase";
+const citiesCollection = firebase.firestore().collection("cities");
+
+const unsubscribe = citiesCollection.onSnapshot(({ includeMetadataChanges: true }, snapshot: firestore.QuerySnapshot) => {
+ snapshot.forEach(city => console.log(city.data()));
+
+ console.log("Data came from " + (snapshot.metadata.fromCache ? "local cache" : "server"));
+ console.log("Has pending writes? " + snapshot.metadata.hasPendingWrites);
+});
+
+// then after a while, to detach the listener:
+unsubscribe();
+```
+
### `collection.doc()`
As mentioned, a document lives inside a collection and contains the actual data:
@@ -185,6 +229,8 @@ sanFranciscoDocument.update({
});
```
+> NB: serverTimestamp() only works in an update on the Android SDK, not add or set.
+
### `collection.where()`
Firestore supports advanced querying with the `where` function. Those `where` clauses can be chained to form logical 'AND' queries:
@@ -355,3 +401,17 @@ firebase.firestore().runTransaction(transaction => {
.then(() => console.log("Transaction successfully committed"))
.catch(error => console.log(`Transaction error: ${error}`));
```
+
+### Firestore configurations: `settings()`
+> You must set these before invoking any other methods!
+
+Setting cacheSizeBytes is Android only
+
+You can modify `host`, `ssl` and `cacheSizeBytes`. (`timestampsInSnapshots` shouldn't be used as it will be deprecated)
+See [docs](https://firebase.google.com/docs/reference/js/firebase.firestore.Settings) for more information.
+
+```typescript
+ firebase.firestore.settings({});
+
+ firebaseWebApi.firestore().settings({"host" : "Example", "ssl" : false});
+```
diff --git a/docs/INVITES_DYNAMICLINKS.md b/docs/INVITES_DYNAMICLINKS.md
index 4c58a64e..843c68d3 100644
--- a/docs/INVITES_DYNAMICLINKS.md
+++ b/docs/INVITES_DYNAMICLINKS.md
@@ -8,7 +8,7 @@ _Invites_ lets you invite other users to your app from right within your own app
Keep in mind that invites are based of dynamic links, and so calling for an invite may return a plain dynamic link, in which case invitationId is null.
### Android
-* [Make sure you've uploaded your SHA1 and SHA256 fingerprints](https://developers.google.com/android/guides/client-auth) to the Firebase console.
+* [Make sure you've uploaded your SHA256 fingerprints](https://developers.google.com/android/guides/client-auth) to the Firebase console.
### iOS
* On iOS the user must be signed in with their Google Account to send invitations.
diff --git a/docs/ML_KIT.md b/docs/ML_KIT.md
index 76cae09f..cd8d5769 100644
--- a/docs/ML_KIT.md
+++ b/docs/ML_KIT.md
@@ -84,7 +84,7 @@ To be able to use Cloud features you need to do two things:
|[Barcode scanning](#barcode-scanning)|✅|
|[Image labeling](#image-labeling)|✅|✅
|[Landmark recognition](#landmark-recognition)||✅
-|[Custom model inference](#custom-model-inference)||
+|[Custom model inference](#custom-model-inference)|✅|✅
### Text recognition
@@ -161,7 +161,7 @@ you can pause the scanner with the `pause` property.
> Look at [the demo app](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/tree/master/demo-ng) to see how to wire up that `onTextRecognitionResult` function, and how to wire `torchOn` to a `Switch`.
-##### Angular / Vue
+##### Angular
Register a custom element like so in the component/module:
```typescript
@@ -184,6 +184,25 @@ Now you're able to use the registered element in the view:
```
+##### Vue
+Register a custom element like so in `main.js`:
+
+```typescript
+Vue.registerElement("MLKitTextRecognition", () => require("nativescript-plugin-firebase/mlkit/textrecognition").MLKitTextRecognition);
+```
+
+Now you're able to use the registered element in your `.Vue` file:
+
+```vue
+
+
+```
+
##### XML
Declare a namespace at the top of the embedding page, and use it anywhere on the page:
@@ -279,6 +298,7 @@ registerElement("MLKitBarcodeScanner", () => require("nativescript-plugin-fireba
require("nativescript-plugin-fireba
Note that `formats` is optional but recommended for better recognition performance. Supported types:
`CODE_128`, `CODE_39`, `CODE_93`, `CODABAR`, `DATA_MATRIX`, `EAN_13`, `EAN_8`, `ITF`, `QR_CODE`, `UPC_A`, `UPC_E`, `PDF417`, `AZTEC`.
+Also note that `beepOnScan` is optional and (since version 8.1.0) default `true`.
+
### Image labeling
@@ -363,6 +385,59 @@ firebase.mlkit.landmarkrecognition.recognizeLandmarksCloud({
```
### Custom model inference
+
+
[Firebase documentation 🌎](https://firebase.google.com/docs/ml-kit/use-custom-models)
-Coming soon. See issue #702.
+⚠️ **Please take note of the following:**
+
+- Currently only models bundled with your app can be used (not ones hosted on Firebase). That may change in the future.
+- Prefix the `localModelFile` and `labelsFile` below with `~/` so they point to your `app/` folder. This is for future compatibility, because I'd like to support loading models from the native bundle as well.
+- On Android, make sure the model is not compressed by adding [your model's file extension to app.gradle](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/blob/57969d0a62d761bffb98b19db85af88bfae858dd/demo-ng/app/App_Resources/Android/app.gradle#L22).
+- Only "Quantized" models can be used. Not "Float" models, so `modelInput.type` below must be set to `QUANT`.
+- The `modelInput.shape` parameter below must specify your model's dimensions. If you're not sure, use the script in the paragraph "Specify the model's input and output" at [the Firebase docs](https://firebase.google.com/docs/ml-kit/ios/use-custom-models).
+
+#### Still image (on-device)
+
+```typescript
+import { MLKitCustomModelResult } from "nativescript-plugin-firebase/mlkit/custommodel";
+const firebase = require("nativescript-plugin-firebase");
+
+firebase.mlkit.custommodel.useCustomModel({
+ image: imageSource, // a NativeScript Image or ImageSource, see the demo for examples
+ maxResults: 10, // default 5 (limit numbers to this amount of results)
+ localModelFile: "~/custommodel/inception/inception_v3_quant.tflite", // see the demo, where the model lives in app/custommodel/etc..
+ labelsFile: "~/custommodel/inception/inception_labels.txt",
+ modelInput: [{ // Array
+ shape: [1, 299, 299, 3], // see the tips above
+ type: "QUANT" // for now, must be "QUANT" (and you should use a 'quantized' model (not 'float'))
+ }]
+})
+.then((result: MLKitCustomModelResult) => console.log(JSON.stringify(result.result)))
+.catch(errorMessage => console.log("ML Kit error: " + errorMessage));
+```
+
+#### Live camera feed
+The basics are explained above for 'Text recognition'.
+
+```typescript
+import { registerElement } from "nativescript-angular/element-registry";
+registerElement("MLKitCustomModel", () => require("nativescript-plugin-firebase/mlkit/custommodel").MLKitCustomModel);
+```
+
+```html
+
+
+```
+
+> ⚠️ Make sure to specify `modelInputShape` without the `[` and `]` characters. Spaces are allowed.
diff --git a/docs/PERFORMANCE_MONITORING.md b/docs/PERFORMANCE_MONITORING.md
index 9f50ab22..efc270df 100644
--- a/docs/PERFORMANCE_MONITORING.md
+++ b/docs/PERFORMANCE_MONITORING.md
@@ -15,10 +15,14 @@ To add this feature to your project, either:
In both cases, remove the `/platforms` folder afterwards so the required native library will be added upon the next build.
+> ⚠️ It may take up to 12 hours before results pop up in your Firebase console, so be patient.
+
+> ℹ️ Per Firebase's documentation, by enabling this feature, Remote Config will be enabled as well. So you may be interested in [what Remote Config can do for your app](../REMOTECONFIG.md).
+
## API
### `startTrace`
-To interact with a started trace, we're remembering it in the property `firebaseTrace`:
+To be able to interact with a started trace, you can remember it in a property (in this case `firebaseTrace`):
```typescript
import { performance as firebasePerformance } from "nativescript-plugin-firebase";
@@ -27,7 +31,7 @@ import { FirebaseTrace } from "nativescript-plugin-firebase/performance/performa
const firebaseTrace: FirebaseTrace = firebasePerformance.startTrace("myTrace");
```
-Now you can call several functions on the remembered trace object, read on below. And don't forget to use `trace.stop`.
+Now you can call several functions on the remembered trace object, read on below. And don't forget to use `trace.stop` afterwards.
### `trace.setValue`
diff --git a/docs/STORAGE.md b/docs/STORAGE.md
index 8d0bff0f..86cedb35 100644
--- a/docs/STORAGE.md
+++ b/docs/STORAGE.md
@@ -1,19 +1,16 @@
+_Storage_ lets you upload and download files to/from Google Cloud Storage which is connected to your Firebase instance.
+
> NOTE: since plugin version 6.2.0 you have to use `firebase.storage.xxx` instead of `firebase.xxx`, because I've extracted storage-related functions into a separate module.
## Enabling Storage
-Since plugin version 3.4.0 you can use Firebase _Storage_ features.
-
-_Storage_ lets you upload and download files to/from Google Cloud Storage which is connected to your Firebase instance.
-
-If you didn't choose this feature during installation you can manually uncomment
-the relevant lines to add the SDK's to your app in
-[Podfile](../platforms/ios/Podfile) and [include.gradle](../platforms/android/include.gradle).
+During plugin installation you'll be asked whether or not you use "Firebase Storage".
-Just uncomment the relevant lines (one for each platform) to add the SDK's to your app.
+In case you're upgrading and you have the `firebase.nativescript.json` file in your project root, edit it and add: `"storage": true`.
+Then run `rm -rf platforms && rm -rf node_modules && npm i`.
-### Setting the storage bucket (optional since plugin version 6.5.0)
+### Setting the storage bucket (no longer required since plugin version 6.5.0)
If (in the odd situation) the storage bucket URL you want to use is different than the one in the downloaded config file,
you can tell Firebase what it should use instead.
diff --git a/docs/images/admob-types.png b/docs/images/admob-types.png
new file mode 100644
index 00000000..39643a77
Binary files /dev/null and b/docs/images/admob-types.png differ
diff --git a/docs/images/app-signing.png b/docs/images/app-signing.png
new file mode 100644
index 00000000..ac90b141
Binary files /dev/null and b/docs/images/app-signing.png differ
diff --git a/docs/images/features/mlkit_custom_model_tflite.png b/docs/images/features/mlkit_custom_model_tflite.png
new file mode 100644
index 00000000..3e34088a
Binary files /dev/null and b/docs/images/features/mlkit_custom_model_tflite.png differ
diff --git a/docs/images/mlkit-custom-cloud-model.png b/docs/images/mlkit-custom-cloud-model.png
new file mode 100644
index 00000000..3e31e8b6
Binary files /dev/null and b/docs/images/mlkit-custom-cloud-model.png differ
diff --git a/publish/scripts/installer.js b/publish/scripts/installer.js
index 41a408cf..722ea149 100755
--- a/publish/scripts/installer.js
+++ b/publish/scripts/installer.js
@@ -2,6 +2,20 @@ var fs = require('fs');
var path = require('path');
var prompt = require('prompt-lite');
+const { execSync } = require('child_process');
+const semver = require('semver');
+const tnsVersionFull = execSync('tns --version', { encoding: 'ascii'});
+
+// iOS modern build system is supported from version NativeScript-CLI version 5.2.0
+const supportsIOSModernBuildSystem = tnsVersionFull.indexOf("5.2.0-") > -1 || semver.gte(tnsVersionFull, "5.2.0");
+
+// Custom gradle buildscripts are supported from NativeScript-Android version 5.3.0 (TODO this actually checks the CLI version)
+const supportsGradleBuildscripts = tnsVersionFull.indexOf("5.3.0-") > -1 || semver.gte(tnsVersionFull, "5.3.0");
+
+if (!supportsIOSModernBuildSystem) {
+ console.log(`You're using NativeScript ${tnsVersionFull}.. which doesn't support the latest Firestore and in-app-messaging SDKs. Upgrade NativeScript to at least 5.2.0 if you need those!\n\n`);
+}
+
// Default settings for a few prompts
var usingiOS = false, usingAndroid = false, externalPushClientOnly = false;
@@ -163,7 +177,11 @@ function promptQuestions() {
default: 'n'
}, {
name: 'messaging',
- description: 'Are you using Firebase Messaging? (y/n)',
+ description: 'Are you using Firebase Cloud Messaging? (y/n)',
+ default: 'n'
+ }, {
+ name: 'in_app_messaging',
+ description: 'Are you using In-App Messaging? (y/n)',
default: 'n'
}, {
name: 'crashlytics',
@@ -234,6 +252,14 @@ function promptQuestions() {
name: 'ml_kit_custom_model',
description: 'With Ml Kit, do you want to use a custom TensorFlow Lite model? (y/n)',
default: 'n'
+ }, {
+ name: 'ml_kit_natural_language_identification',
+ description: 'With Ml Kit, do you want to recognize natural languages? (y/n)',
+ default: 'n'
+ }, {
+ name: 'ml_kit_natural_language_smartreply',
+ description: 'With Ml Kit, do you want to use smart reply? (y/n)',
+ default: 'n'
}], function (mlkitErr, mlkitResult) {
if (mlkitErr) {
return console.log(mlkitErr);
@@ -254,8 +280,9 @@ function promptQuestionsResult(result) {
if (!externalPushClientOnly) {
writePodFile(result);
}
+ writeGoogleServiceCopyHook();
writeBuildscriptHookForCrashlytics(isSelected(result.crashlytics));
- writeBuildscriptHookForFirestore(isSelected(result.firestore));
+ writeBuildscriptHookForFirestore(isSelected(result.firestore) && !supportsIOSModernBuildSystem);
}
if (usingAndroid) {
@@ -263,7 +290,8 @@ function promptQuestionsResult(result) {
writeGoogleServiceCopyHook();
writeGoogleServiceGradleHook(result);
echoAndroidManifestChanges(result);
- activateAndroidPushNotificationsLib(isSelected(result.messaging || externalPushClientOnly));
+ activateAndroidPushNotificationsLib(isSelected(result.messaging) || externalPushClientOnly);
+ activateAndroidMLKitCustomModelLib(isSelected(result.ml_kit) && isSelected(result.ml_kit_custom_model));
}
console.log('Firebase post install completed. To re-run this script, navigate to the root directory of `nativescript-plugin-firebase` in your `node_modules` folder and run: `npm run config`.');
@@ -303,6 +331,14 @@ function activateAndroidPushNotificationsLib(enable) {
}
}
+function activateAndroidMLKitCustomModelLib(enable) {
+ if (enable && fs.existsSync(path.join(directories.android, 'nativescript-firebase-mlkit-helper.jar-disabled'))) {
+ fs.renameSync(path.join(directories.android, 'nativescript-firebase-mlkit-helper.jar-disabled'), path.join(directories.android, 'nativescript-firebase-mlkit-helper.jar'));
+ } else if (!enable && fs.existsSync(path.join(directories.android, 'nativescript-firebase-mlkit-helper.jar'))) {
+ fs.renameSync(path.join(directories.android, 'nativescript-firebase-mlkit-helper.jar'), path.join(directories.android, 'nativescript-firebase-mlkit-helper.jar-disabled'));
+ }
+}
+
function askSaveConfigPrompt() {
prompt.get({
name: 'save_config',
@@ -332,8 +368,8 @@ function writePodFile(result) {
// The MLVision pod requires a minimum of iOS 9, otherwise the build will fail
(isPresent(result.ml_kit) ? `` : `#`) + `platform :ios, '9.0'
-pod 'Firebase/Core', '~> 5.12.0'
-pod 'GoogleAppMeasurement', '5.3' # temp fix for https://github.com/firebase/firebase-ios-sdk/issues/2151 (remove when bumping 'Firebase/Core')
+# With NativeScript < 5.2 we can't bump Firebase/Core beyond 5.15.0, but with 5.2+ we can
+pod 'Firebase/Core', '~> ` + (supportsIOSModernBuildSystem ? '5.20.1' : '5.15.0') + `'
# Authentication
` + (!isPresent(result.authentication) || isSelected(result.authentication) ? `` : `#`) + `pod 'Firebase/Auth'
@@ -341,8 +377,10 @@ pod 'GoogleAppMeasurement', '5.3' # temp fix for https://github.com/firebase/fir
# Realtime DB
` + (!isPresent(result.realtimedb) || isSelected(result.realtimedb) ? `` : `#`) + `pod 'Firebase/Database'
-# Cloud Firestore
-` + (isSelected(result.firestore) ? `` : `#`) + `pod 'Firebase/Firestore'
+# Cloud Firestore (sticking to 0.14 for now because of build error - see https://github.com/firebase/firebase-ios-sdk/issues/2177)
+` + (isSelected(result.firestore) && !supportsIOSModernBuildSystem ? `` : `#`) + `pod 'FirebaseFirestore', '~> 0.14.0'
+# .. unless the modern build system is supported, then we can use the latest version (NativeScript 5.2+)
+` + (isSelected(result.firestore) && supportsIOSModernBuildSystem ? `` : `#`) + `pod 'Firebase/Firestore'
# Remote Config
` + (isSelected(result.remote_config) ? `` : `#`) + `pod 'Firebase/RemoteConfig'
@@ -370,6 +408,9 @@ end`) + `
# Firebase Cloud Messaging (FCM)
` + (isSelected(result.messaging) ? `` : `#`) + `pod 'Firebase/Messaging'
+# Firebase In-App Messaging (supported on NativeScript 5.2+)
+` + (isSelected(result.in_app_messaging) && supportsIOSModernBuildSystem ? `` : `#`) + `pod 'Firebase/InAppMessagingDisplay'
+
# Firebase Cloud Storage
` + (isSelected(result.storage) ? `` : `#`) + `pod 'Firebase/Storage'
@@ -392,10 +433,14 @@ end`) + `
` + (isSelected(result.ml_kit) && isSelected(result.ml_kit_face_detection) ? `` : `#`) + `pod 'Firebase/MLVisionFaceModel'
` + (isSelected(result.ml_kit) && isSelected(result.ml_kit_image_labeling) ? `` : `#`) + `pod 'Firebase/MLVisionLabelModel'
` + (isSelected(result.ml_kit) && isSelected(result.ml_kit_custom_model) ? `` : `#`) + `pod 'Firebase/MLModelInterpreter'
+` + (isSelected(result.ml_kit) && isSelected(result.ml_kit_natural_language_identification) ? `` : `#`) + `pod 'Firebase/MLNaturalLanguage'
+` + (isSelected(result.ml_kit) && isSelected(result.ml_kit_natural_language_identification) ? `` : `#`) + `pod 'Firebase/MLNLLanguageID'
+` + (isSelected(result.ml_kit) && isSelected(result.ml_kit_natural_language_smartreply) ? `` : `#`) + `pod 'Firebase/MLCommon'
+` + (isSelected(result.ml_kit) && isSelected(result.ml_kit_natural_language_smartreply) ? `` : `#`) + `pod 'Firebase/MLNLSmartReply'
# Facebook Authentication
-` + (isSelected(result.facebook_auth) ? `` : `#`) + `pod 'FBSDKCoreKit'
-` + (isSelected(result.facebook_auth) ? `` : `#`) + `pod 'FBSDKLoginKit'
+` + (isSelected(result.facebook_auth) ? `` : `#`) + `pod 'FBSDKCoreKit', '~> 4.38.0'
+` + (isSelected(result.facebook_auth) ? `` : `#`) + `pod 'FBSDKLoginKit', '~> 4.38.0'
# Google Authentication
` + (isSelected(result.google_auth) ? `` : `#`) + `pod 'GoogleSignIn'`);
@@ -493,7 +538,7 @@ module.exports = function($logger, $projectData, hookArgs) {
if (fs.existsSync(xcodeProjectPath)) {
var xcodeProject = xcode.project(xcodeProjectPath);
xcodeProject.parseSync();
- var options = { shellPath: '/bin/sh', shellScript: '\${PODS_ROOT}/Fabric/run' };
+ var options = { shellPath: '/bin/sh', shellScript: '\"\${PODS_ROOT}/Fabric/run\"' };
xcodeProject.addBuildPhase(
[], 'PBXShellScriptBuildPhase', 'Configure Crashlytics', undefined, options
).buildPhase;
@@ -663,10 +708,9 @@ function writeGradleFile(result) {
fs.writeFileSync(directories.android + '/include.gradle',
`
android {
- productFlavors {
- "fireb" {
- dimension "fireb"
- }
+ // (possibly-temporary) workaround for https://stackoverflow.com/questions/52518378/more-than-one-file-was-found-with-os-independent-path-meta-inf-proguard-android
+ packagingOptions {
+ exclude 'META-INF/proguard/androidx-annotations.pro'
}
}
@@ -678,74 +722,81 @@ repositories {
jcenter()
}
-def supportVersion = project.hasProperty("supportVersion") ? project.supportVersion : "26.1.0"
-def googlePlayServicesVersion = project.hasProperty('googlePlayServicesVersion') ? project.googlePlayServicesVersion : "16.0.1"
-
-if (googlePlayServicesVersion != '+' && VersionNumber.parse(googlePlayServicesVersion) < VersionNumber.parse('15.0.+')) {
- throw new GradleException(" googlePlayServicesVersion set too low, please update to at least 15.0.0 / 15.0.+ (currently set to $googlePlayServicesVersion)");
-}
-
dependencies {
- compile "com.android.support:appcompat-v7:$supportVersion"
- compile "com.android.support:cardview-v7:$supportVersion"
- compile "com.android.support:customtabs:$supportVersion"
- compile "com.android.support:design:$supportVersion"
- compile "com.android.support:support-compat:$supportVersion"
+ def supportVersion = project.hasProperty("supportVersion") ? project.supportVersion : "26.1.0"
+ def googlePlayServicesVersion = project.hasProperty('googlePlayServicesVersion') ? project.googlePlayServicesVersion : "16.0.1"
+
+ if (googlePlayServicesVersion != '+' && VersionNumber.parse(googlePlayServicesVersion) < VersionNumber.parse('15.0.+')) {
+ throw new GradleException(" googlePlayServicesVersion set too low, please update to at least 15.0.0 / 15.0.+ (currently set to $googlePlayServicesVersion)");
+ }
+
+ implementation "com.android.support:appcompat-v7:$supportVersion"
+ implementation "com.android.support:cardview-v7:$supportVersion"
+ implementation "com.android.support:customtabs:$supportVersion"
+ implementation "com.android.support:design:$supportVersion"
+ implementation "com.android.support:support-compat:$supportVersion"
// make sure you have these versions by updating your local Android SDK's (Android Support repo and Google repo)
- compile "com.google.firebase:firebase-core:16.0.4"
+ implementation "com.google.firebase:firebase-core:16.0.8"
// for reading google-services.json and configuration
- compile "com.google.android.gms:play-services-base:$googlePlayServicesVersion"
+ implementation "com.google.android.gms:play-services-base:$googlePlayServicesVersion"
// Authentication
- ` + (!externalPushClientOnly && (!isPresent(result.authentication) || isSelected(result.authentication)) ? `` : `//`) + ` compile "com.google.firebase:firebase-auth:16.0.5"
+ ` + (!externalPushClientOnly && (!isPresent(result.authentication) || isSelected(result.authentication)) ? `` : `//`) + ` implementation "com.google.firebase:firebase-auth:16.2.1"
// Realtime DB
- ` + (!externalPushClientOnly && (!isPresent(result.realtimedb) || isSelected(result.realtimedb)) ? `` : `//`) + ` compile "com.google.firebase:firebase-database:16.0.4"
+ ` + (!externalPushClientOnly && (!isPresent(result.realtimedb) || isSelected(result.realtimedb)) ? `` : `//`) + ` implementation "com.google.firebase:firebase-database:16.1.0"
// Cloud Firestore
- ` + (isSelected(result.firestore) ? `` : `//`) + ` compile "com.google.firebase:firebase-firestore:17.1.2"
+ ` + (isSelected(result.firestore) ? `` : `//`) + ` implementation "com.google.firebase:firebase-firestore:18.2.0"
// Remote Config
- ` + (isSelected(result.remote_config) ? `` : `//`) + ` compile "com.google.firebase:firebase-config:16.1.0"
+ ` + (isSelected(result.remote_config) ? `` : `//`) + ` implementation "com.google.firebase:firebase-config:16.5.0"
// Performance Monitoring
- ` + (isSelected(result.performance_monitoring) ? `` : `//`) + ` compile "com.google.firebase:firebase-perf:16.2.0"
+ ` + (isSelected(result.performance_monitoring) ? `` : `//`) + ` implementation "com.google.firebase:firebase-perf:16.2.5"
// Crash Reporting
- ` + (isSelected(result.crash_reporting) && !isSelected(result.crashlytics) ? `` : `//`) + ` compile "com.google.firebase:firebase-crash:16.2.1"
+ ` + (isSelected(result.crash_reporting) && !isSelected(result.crashlytics) ? `` : `//`) + ` implementation "com.google.firebase:firebase-crash:16.2.1"
// Crashlytics
- ` + (isSelected(result.crashlytics) ? `` : `//`) + ` compile "com.crashlytics.sdk.android:crashlytics:2.9.6"
+ ` + (isSelected(result.crashlytics) ? `` : `//`) + ` implementation "com.crashlytics.sdk.android:crashlytics:2.9.9"
+
+ // Cloud Messaging (FCM)
+ ` + (isSelected(result.messaging) || externalPushClientOnly ? `` : `//`) + ` implementation "com.google.firebase:firebase-messaging:17.6.0"
- // Firebase Cloud Messaging (FCM)
- ` + (isSelected(result.messaging) || externalPushClientOnly ? `` : `//`) + ` compile "com.google.firebase:firebase-messaging:17.3.4"
+ // In-App Messaging
+ ` + (isSelected(result.in_app_messaging) ? `` : `//`) + ` implementation "com.google.firebase:firebase-inappmessaging-display:17.1.1"
// Cloud Storage
- ` + (isSelected(result.storage) ? `` : `//`) + ` compile "com.google.firebase:firebase-storage:16.0.3"
+ ` + (isSelected(result.storage) ? `` : `//`) + ` implementation "com.google.firebase:firebase-storage:16.1.0"
// Cloud Functions
- ` + (isSelected(result.functions) ? `` : `//`) + ` compile "com.google.firebase:firebase-functions:16.1.2"
+ ` + (isSelected(result.functions) ? `` : `//`) + ` implementation "com.google.firebase:firebase-functions:16.3.0"
// AdMob / Ads
- ` + (isSelected(result.admob) ? `` : `//`) + ` compile "com.google.firebase:firebase-ads:17.0.0"
+ ` + (isSelected(result.admob) ? `` : `//`) + ` implementation "com.google.firebase:firebase-ads:17.2.0"
// ML Kit
- ` + (isSelected(result.ml_kit) ? `` : `//`) + ` compile "com.google.firebase:firebase-ml-vision:18.0.1"
- ` + (isSelected(result.ml_kit_image_labeling) ? `` : `//`) + ` compile "com.google.firebase:firebase-ml-vision-image-label-model:17.0.2"
+ ` + (isSelected(result.ml_kit) ? `` : `//`) + ` implementation "com.google.firebase:firebase-ml-vision:19.0.3"
+ ` + (isSelected(result.ml_kit_image_labeling) ? `` : `//`) + ` implementation "com.google.firebase:firebase-ml-vision-image-label-model:17.0.2"
+ ` + (isSelected(result.ml_kit_custom_model) ? `` : `//`) + ` implementation "com.google.firebase:firebase-ml-model-interpreter:18.0.0"
+ ` + (isSelected(result.ml_kit_natural_language_identification) || isSelected(result.ml_kit_natural_language_smartreply) ? `` : `//`) + ` implementation "com.google.firebase:firebase-ml-natural-language:18.2.0"
+ ` + (isSelected(result.ml_kit_natural_language_identification) ? `` : `//`) + ` implementation "com.google.firebase:firebase-ml-natural-language-language-id-model:18.0.3"
+ ` + (isSelected(result.ml_kit_natural_language_smartreply) ? `` : `//`) + ` implementation "com.google.firebase:firebase-ml-natural-language-smart-reply-model:18.0.0"
// Facebook Authentication
- ` + (isSelected(result.facebook_auth) ? `` : `//`) + ` compile ("com.facebook.android:facebook-android-sdk:4.35.0"){ exclude group: 'com.google.zxing' }
+ ` + (isSelected(result.facebook_auth) ? `` : `//`) + ` implementation ("com.facebook.android:facebook-android-sdk:4.35.0"){ exclude group: 'com.google.zxing' }
// Google Sign-In Authentication
- ` + (isSelected(result.google_auth) ? `` : `//`) + ` compile "com.google.android.gms:play-services-auth:16.0.0"
+ ` + (isSelected(result.google_auth) ? `` : `//`) + ` implementation "com.google.android.gms:play-services-auth:$googlePlayServicesVersion"
- // Firebase Invites
- ` + (isSelected(result.invites) ? `` : `//`) + ` compile "com.google.firebase:firebase-invites:16.0.4"
+ // Invites
+ ` + (isSelected(result.invites) ? `` : `//`) + ` implementation "com.google.firebase:firebase-invites:16.1.1"
- // Firebase Dynamic Links
- ` + (isSelected(result.dynamic_links) ? `` : `//`) + ` compile "com.google.firebase:firebase-dynamic-links:16.1.2" // BEWARE: 16.1.2 is fine, but 16.1.3 results in a build error
+ // Dynamic Links
+ ` + (isSelected(result.dynamic_links) ? `` : `//`) + ` implementation "com.google.firebase:firebase-dynamic-links:16.1.8"
}
apply plugin: "com.google.gms.google-services"
@@ -920,7 +971,7 @@ module.exports = function($logger, $projectData, hookArgs) {
}
} else { $logger.info('nativescript-plugin-firebase: '+npfInfoPath+' not found, forcing prepare!'); }
- if (forcePrepare && fs.existsSync(nsPrepareInfoPath)) {
+ if (forcePrepare) {
$logger.info('nativescript-plugin-firebase: running release build or change in environment detected, forcing prepare!');
if (fs.existsSync(npfInfoPath)) { fs.unlinkSync(npfInfoPath); }
@@ -1031,8 +1082,6 @@ module.exports = function($logger, $projectData) {
});
}
- buildGradleContent = buildGradleContent.replace("com.android.tools.build:gradle:3.2.0", "com.android.tools.build:gradle:3.2.1");
-
fs.writeFileSync(projectBuildGradlePath, buildGradleContent);
}
diff --git a/src/.npmignore b/src/.npmignore
index e58a1f76..f7b6d114 100644
--- a/src/.npmignore
+++ b/src/.npmignore
@@ -4,10 +4,10 @@
tsconfig.json
references.d.ts
platforms/android/libraryproject/
+platforms/android/mlkithelpersrc/
platforms/ios/typings/
platforms/ios_lib/
platforms/android/typings/
platforms/web
platforms/ios/Podfile
-platforms/ios/build.xcconfig
platforms/android/include.gradle
diff --git a/src/admob/admob-common.ts b/src/admob/admob-common.ts
index fc7eb822..16f97b08 100644
--- a/src/admob/admob-common.ts
+++ b/src/admob/admob-common.ts
@@ -1,3 +1,5 @@
+import { RewardedVideoAdCallbacks, RewardedVideoAdReward } from "./admob";
+
export const AD_SIZE = {
SMART_BANNER: "SMART",
LARGE_BANNER: "LARGE",
@@ -18,3 +20,20 @@ export const BANNER_DEFAULTS = {
size: "SMART",
view: undefined
};
+
+export const rewardedVideoCallbacks: RewardedVideoAdCallbacks = {
+ onRewarded: (reward: RewardedVideoAdReward) => console.warn("onRewarded callback not set - the fallback implementation caught this reward: " + JSON.stringify(reward)),
+ onLeftApplication: () => {
+ },
+ onClosed: () => {
+ },
+ onOpened: () => {
+ },
+ onStarted: () => {
+ },
+ onCompleted: () => {
+ },
+ onLoaded: () => {
+ },
+ onFailedToLoad: (err) => console.warn("onFailedToLoad not set - the fallback implementation caught this error: " + err),
+};
diff --git a/src/admob/admob.android.ts b/src/admob/admob.android.ts
index f5e51927..611ed580 100644
--- a/src/admob/admob.android.ts
+++ b/src/admob/admob.android.ts
@@ -1,11 +1,11 @@
import { firebase } from "../firebase-common";
-import { BannerOptions, InterstitialOptions } from "./admob";
-import { AD_SIZE, BANNER_DEFAULTS } from "./admob-common";
+import { BannerOptions, InterstitialOptions, PreloadRewardedVideoAdOptions, ShowRewardedVideoAdOptions } from "./admob";
+import { AD_SIZE, BANNER_DEFAULTS, rewardedVideoCallbacks } from "./admob-common";
import * as appModule from "tns-core-modules/application";
import { topmost } from "tns-core-modules/ui/frame";
import { layout } from "tns-core-modules/utils/utils";
-declare const android, com: any;
+declare const com: any;
export { AD_SIZE };
@@ -13,7 +13,6 @@ export function showBanner(arg: BannerOptions): Promise {
return new Promise((resolve, reject) => {
try {
const settings = firebase.merge(arg, BANNER_DEFAULTS);
- console.log({settings});
// always close a previously opened banner
if (firebase.admob.adView !== null && firebase.admob.adView !== undefined) {
@@ -39,7 +38,6 @@ export function showBanner(arg: BannerOptions): Promise {
this.resolve();
},
onAdFailedToLoad: errorCode => {
- // console.log('ad error: ' + errorCode);
this.reject(errorCode);
}
});
@@ -76,12 +74,15 @@ export function showBanner(arg: BannerOptions): Promise {
// Wrapping it in a timeout makes sure that when this function is loaded from a Page.loaded event 'frame.topmost()' doesn't resolve to 'undefined'.
// Also, in NativeScript 4+ it may be undefined anyway.. so using the appModule in that case.
setTimeout(() => {
- if (topmost() !== undefined) {
- topmost().currentPage.android.getParent().addView(adViewLayout, relativeLayoutParamsOuter);
- } else {
+ const top = topmost();
+ if (top !== undefined && top.currentPage && top.currentPage.android && top.currentPage.android.getParent()) {
+ top.currentPage.android.getParent().addView(adViewLayout, relativeLayoutParamsOuter);
+ } else if (appModule.android && appModule.android.foregroundActivity) {
appModule.android.foregroundActivity.getWindow().getDecorView().addView(adViewLayout, relativeLayoutParamsOuter);
+ } else {
+ console.log("Could not find a view to add the banner to");
}
- }, 0);
+ }, 100);
} catch (ex) {
console.log("Error in firebase.admob.showBanner: " + ex);
reject(ex);
@@ -175,6 +176,103 @@ export function showInterstitial(arg?: InterstitialOptions): Promise {
});
}
+export function preloadRewardedVideoAd(arg: PreloadRewardedVideoAdOptions): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ const settings = firebase.merge(arg, BANNER_DEFAULTS);
+ const activity = appModule.android.foregroundActivity || appModule.android.startActivity;
+ firebase.admob.rewardedAdVideoView = com.google.android.gms.ads.MobileAds.getRewardedVideoAdInstance(activity);
+
+ rewardedVideoCallbacks.onLoaded = resolve;
+ rewardedVideoCallbacks.onFailedToLoad = reject;
+
+ // rewarded Ads must be loaded before they can be shown, so adding a listener
+ const RewardedVideoAdListener = com.google.android.gms.ads.reward.RewardedVideoAdListener.extend({
+ onRewarded(reward) {
+ rewardedVideoCallbacks.onRewarded({
+ amount: reward.getAmount(),
+ type: reward.getType()
+ });
+ },
+ onRewardedVideoAdLeftApplication() {
+ rewardedVideoCallbacks.onLeftApplication();
+ },
+ onRewardedVideoAdClosed() {
+ if (firebase.admob.rewardedAdVideoView) {
+ firebase.admob.rewardedAdVideoView.setRewardedVideoAdListener(null);
+ firebase.admob.rewardedAdVideoView = null;
+ }
+ rewardedVideoCallbacks.onClosed();
+ },
+ onRewardedVideoAdFailedToLoad(errorCode) {
+ rewardedVideoCallbacks.onFailedToLoad(errorCode);
+ },
+ onRewardedVideoAdLoaded() {
+ rewardedVideoCallbacks.onLoaded();
+ },
+ onRewardedVideoAdOpened() {
+ rewardedVideoCallbacks.onOpened();
+ },
+ onRewardedVideoStarted() {
+ rewardedVideoCallbacks.onStarted();
+ },
+ onRewardedVideoCompleted() {
+ rewardedVideoCallbacks.onCompleted();
+ }
+ });
+
+ firebase.admob.rewardedAdVideoView.setRewardedVideoAdListener(new RewardedVideoAdListener());
+
+ const ad = _buildAdRequest(settings);
+ firebase.admob.rewardedAdVideoView.loadAd(settings.androidAdPlacementId, ad);
+ } catch (ex) {
+ console.log("Error in firebase.admob.preloadRewardedVideoAd: " + ex);
+ reject(ex);
+ }
+ });
+}
+
+export function showRewardedVideoAd(arg?: ShowRewardedVideoAdOptions): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ if (!firebase.admob.rewardedAdVideoView) {
+ reject("Please call 'preloadRewardedVideoAd' first");
+ return;
+ }
+
+ if (arg.onRewarded) {
+ rewardedVideoCallbacks.onRewarded = arg.onRewarded;
+ }
+
+ if (arg.onLeftApplication) {
+ rewardedVideoCallbacks.onLeftApplication = arg.onLeftApplication;
+ }
+
+ if (arg.onClosed) {
+ rewardedVideoCallbacks.onClosed = arg.onClosed;
+ }
+
+ if (arg.onOpened) {
+ rewardedVideoCallbacks.onOpened = arg.onOpened;
+ }
+
+ if (arg.onStarted) {
+ rewardedVideoCallbacks.onStarted = arg.onStarted;
+ }
+
+ if (arg.onCompleted) {
+ rewardedVideoCallbacks.onCompleted = arg.onCompleted;
+ }
+
+ firebase.admob.rewardedAdVideoView.show();
+ resolve();
+ } catch (ex) {
+ console.log("Error in firebase.admob.showRewardedVideoAd: " + ex);
+ reject(ex);
+ }
+ });
+}
+
export function hideBanner(): Promise {
return new Promise((resolve, reject) => {
try {
@@ -194,7 +292,6 @@ export function hideBanner(): Promise {
}
function _getBannerType(size): any {
- console.log(">> _getBannerType: " + size);
if (size === AD_SIZE.BANNER) {
return com.google.android.gms.ads.AdSize.BANNER;
} else if (size === AD_SIZE.LARGE_BANNER) {
diff --git a/src/admob/admob.d.ts b/src/admob/admob.d.ts
index 78f7dcab..aab79491 100644
--- a/src/admob/admob.d.ts
+++ b/src/admob/admob.d.ts
@@ -83,6 +83,11 @@ export interface InterstitialOptions {
*/
testing?: boolean;
+ /**
+ * callback to be exucted when add is loaded
+ */
+ onAdLoaded?: function;
+
/**
* Something like "ca-app-pub-AAAAAAAA/BBBBBBB".
*/
@@ -107,6 +112,55 @@ export interface InterstitialOptions {
onAdClosed?: () => void;
}
+export interface PreloadRewardedVideoAdOptions {
+ /**
+ * When true you'll use googles testing iosAdPlacementId and androidAdPlacementId.
+ */
+ testing?: boolean;
+
+ /**
+ * Something like "ca-app-pub-AAAAAAAA/BBBBBBB".
+ */
+ androidAdPlacementId?: string;
+
+ /**
+ * Something like "ca-app-pub-XXXXXX/YYYYYY".
+ */
+ iosAdPlacementId?: string;
+
+ /**
+ * If testing is true, the simulator is allowed to receive test banners.
+ * Android automatically add the connceted device as test device, but iOS does not.
+ * If you also want to test on real devices, add it here like this:
+ * ["ce97330130c9047ce0d4430d37d713b1", ".."]
+ */
+ iosTestDeviceIds?: string[];
+
+ /**
+ * Specify keywords for ad targeting
+ */
+ keywords?: Array;
+}
+
+export interface RewardedVideoAdReward {
+ amount: number;
+ type: string;
+}
+
+export interface RewardedVideoAdCallbacks {
+ onOpened?: () => void;
+ onStarted?: () => void;
+ onCompleted?: () => void;
+ onClosed?: () => void;
+ onLeftApplication?: () => void;
+ onLoaded?: () => void,
+ onFailedToLoad?: (err) => void,
+ onRewarded?: (reward: RewardedVideoAdReward) => void;
+}
+
+export interface ShowRewardedVideoAdOptions extends RewardedVideoAdCallbacks {
+}
+
export declare function showBanner(options: BannerOptions): Promise;
export declare function hideBanner(): Promise;
@@ -126,3 +180,7 @@ export declare function preloadInterstitial(options: InterstitialOptions): Promi
* 2) DEPRECATED: with arguments (same as 'preloadInterstitial'). This will preload and _then_ show the interstitial, so a delay will be noticable by the user, which is against Google's policies.
*/
export declare function showInterstitial(options?: InterstitialOptions): Promise;
+
+export declare function preloadRewardedVideoAd(options: PreloadRewardedVideoAdOptions): Promise;
+
+export declare function showRewardedVideoAd(options?: ShowRewardedVideoAdOptions): Promise;
diff --git a/src/admob/admob.ios.ts b/src/admob/admob.ios.ts
index c241cfbb..1378633f 100644
--- a/src/admob/admob.ios.ts
+++ b/src/admob/admob.ios.ts
@@ -1,14 +1,17 @@
-import { firebase } from "../firebase-common";
-import { BannerOptions, InterstitialOptions } from "./admob";
import { device } from "tns-core-modules/platform/platform";
import { DeviceType } from "tns-core-modules/ui/enums/enums";
import { ios as iOSUtils } from "tns-core-modules/utils/utils";
-import { AD_SIZE, BANNER_DEFAULTS } from "./admob-common";
+import { firebase } from "../firebase-common";
+import { BannerOptions, InterstitialOptions, PreloadRewardedVideoAdOptions, ShowRewardedVideoAdOptions } from "./admob";
+import { AD_SIZE, BANNER_DEFAULTS, rewardedVideoCallbacks } from "./admob-common";
export { AD_SIZE };
// helps global app behavior (ie: orientation handling)
-let _bannerOptions = null;
+let _bannerOptions = undefined;
+
+// these are needed in multiple functions
+let _rewardBasedVideoAdDelegate = undefined;
export function showBanner(arg: BannerOptions): Promise {
return new Promise((resolve, reject) => {
@@ -100,7 +103,7 @@ export function preloadInterstitial(arg: InterstitialOptions): Promise {
}, () => {
arg.onAdClosed && arg.onAdClosed();
});
- // we're leaving the app to switch to Google's OAuth screen, so making sure this is retained
+
CFRetain(delegate);
firebase.admob.interstitialView.delegate = delegate;
@@ -121,7 +124,7 @@ export function preloadInterstitial(arg: InterstitialOptions): Promise {
firebase.admob.interstitialView.loadRequest(adRequest);
} catch (ex) {
- console.log("Error in firebase.admob.showInterstitial: " + ex);
+ console.log("Error in firebase.admob.preloadInterstitial: " + ex);
reject(ex);
}
});
@@ -188,6 +191,93 @@ export function showInterstitial(arg?: InterstitialOptions): Promise {
});
}
+export function preloadRewardedVideoAd(arg: PreloadRewardedVideoAdOptions): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ if (typeof (GADRequest) === "undefined") {
+ reject("Enable AdMob first - see the plugin documentation");
+ return;
+ }
+
+ const onLoaded = () => resolve();
+ const onError = err => reject(err);
+ _rewardBasedVideoAdDelegate = GADRewardBasedVideoAdDelegateImpl.new().initWithCallback(onLoaded, onError);
+ CFRetain(_rewardBasedVideoAdDelegate);
+
+ firebase.admob.rewardedAdVideoView = GADRewardBasedVideoAd.sharedInstance();
+ firebase.admob.rewardedAdVideoView.delegate = _rewardBasedVideoAdDelegate;
+
+ const settings = firebase.merge(arg, BANNER_DEFAULTS);
+ const adRequest = GADRequest.request();
+
+ if (settings.testing) {
+ let testDevices: any = [];
+ try {
+ testDevices.push("Simulator");
+ } catch (ignore) {
+ // can happen on a real device
+ }
+ if (settings.iosTestDeviceIds) {
+ testDevices = testDevices.concat(settings.iosTestDeviceIds);
+ }
+ adRequest.testDevices = testDevices;
+ }
+
+ firebase.admob.rewardedAdVideoView.loadRequestWithAdUnitID(adRequest, settings.iosAdPlacementId);
+ } catch (ex) {
+ console.log("Error in firebase.admob.preloadRewardedVideoAd: " + ex);
+ reject(ex);
+ }
+ });
+}
+
+export function showRewardedVideoAd(arg?: ShowRewardedVideoAdOptions): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ if (typeof (GADRequest) === "undefined") {
+ reject("Enable AdMob first - see the plugin documentation");
+ return;
+ }
+
+ if (!firebase.admob.rewardedAdVideoView) {
+ reject("Please call 'preloadRewardedVideoAd' first");
+ return;
+ }
+
+ if (arg.onRewarded) {
+ rewardedVideoCallbacks.onRewarded = arg.onRewarded;
+ }
+
+ if (arg.onLeftApplication) {
+ rewardedVideoCallbacks.onLeftApplication = arg.onLeftApplication;
+ }
+
+ if (arg.onClosed) {
+ rewardedVideoCallbacks.onClosed = arg.onClosed;
+ }
+
+ if (arg.onOpened) {
+ rewardedVideoCallbacks.onOpened = arg.onOpened;
+ }
+
+ if (arg.onStarted) {
+ rewardedVideoCallbacks.onStarted = arg.onStarted;
+ }
+
+ if (arg.onCompleted) {
+ rewardedVideoCallbacks.onCompleted = arg.onCompleted;
+ }
+
+ firebase.admob.rewardedAdVideoView.presentFromRootViewController(iOSUtils.getter(UIApplication, UIApplication.sharedApplication).keyWindow.rootViewController);
+ resolve();
+
+ } catch (ex) {
+ console.log("Error in firebase.admob.showRewardedVideoAd: " + ex);
+ reject(ex);
+ }
+ });
+}
+
export function hideBanner(): Promise {
return new Promise((resolve, reject) => {
try {
@@ -271,3 +361,62 @@ class GADInterstitialDelegateImpl extends NSObject implements GADInterstitialDel
this.callback(ad, error);
}
}
+
+class GADRewardBasedVideoAdDelegateImpl extends NSObject implements GADRewardBasedVideoAdDelegate {
+ public static ObjCProtocols = [];
+ _loaded: () => void;
+ _error: (err) => void;
+
+ static new(): GADRewardBasedVideoAdDelegateImpl {
+ if (GADRewardBasedVideoAdDelegateImpl.ObjCProtocols.length === 0 && typeof (GADRewardBasedVideoAdDelegate) !== "undefined") {
+ GADRewardBasedVideoAdDelegateImpl.ObjCProtocols.push(GADRewardBasedVideoAdDelegate);
+ }
+ return super.new();
+ }
+
+ public initWithCallback(loaded: () => void, error: (err) => void): GADRewardBasedVideoAdDelegateImpl {
+ this._loaded = loaded;
+ this._error = error;
+ return this;
+ }
+
+ rewardBasedVideoAdDidClose(rewardBasedVideoAd: GADRewardBasedVideoAd): void {
+ firebase.admob.rewardedAdVideoView = undefined;
+ rewardedVideoCallbacks.onClosed();
+ setTimeout(function () {
+ CFRelease(_rewardBasedVideoAdDelegate);
+ _rewardBasedVideoAdDelegate = undefined;
+ });
+ }
+
+ rewardBasedVideoAdDidCompletePlaying(rewardBasedVideoAd: GADRewardBasedVideoAd): void {
+ rewardedVideoCallbacks.onCompleted();
+ }
+
+ rewardBasedVideoAdDidFailToLoadWithError(rewardBasedVideoAd: GADRewardBasedVideoAd, error: NSError): void {
+ this._error(error.localizedDescription);
+ }
+
+ rewardBasedVideoAdDidOpen(rewardBasedVideoAd: GADRewardBasedVideoAd): void {
+ rewardedVideoCallbacks.onOpened();
+ }
+
+ rewardBasedVideoAdDidReceiveAd(rewardBasedVideoAd: GADRewardBasedVideoAd): void {
+ this._loaded();
+ }
+
+ rewardBasedVideoAdDidRewardUserWithReward(rewardBasedVideoAd: GADRewardBasedVideoAd, reward: GADAdReward): void {
+ rewardedVideoCallbacks.onRewarded({
+ amount: reward.amount ? reward.amount.doubleValue : undefined,
+ type: reward.type
+ });
+ }
+
+ rewardBasedVideoAdDidStartPlaying(rewardBasedVideoAd: GADRewardBasedVideoAd): void {
+ rewardedVideoCallbacks.onStarted();
+ }
+
+ rewardBasedVideoAdWillLeaveApplication(rewardBasedVideoAd: GADRewardBasedVideoAd): void {
+ rewardedVideoCallbacks.onLeftApplication();
+ }
+}
diff --git a/src/analytics/analytics.android.ts b/src/analytics/analytics.android.ts
index f1c0d855..054b6a24 100644
--- a/src/analytics/analytics.android.ts
+++ b/src/analytics/analytics.android.ts
@@ -1,5 +1,11 @@
import * as appModule from "tns-core-modules/application";
-import { LogEventOptions, SetScreenNameOptions, SetUserPropertyOptions } from "./analytics";
+import {
+ LogEventOptions,
+ SetScreenNameOptions,
+ SetUserPropertyOptions,
+ LogComplexEventOptions,
+ LogComplexEventParameter
+} from "./analytics";
declare const com: any;
@@ -33,6 +39,71 @@ export function logEvent(options: LogEventOptions): Promise {
});
}
+function getArrayList(array: Array): java.util.ArrayList {
+ let returnArray = new java.util.ArrayList();
+ for (const p in array) {
+ const param = array[p];
+ if (param.parameters !== undefined) {
+ let bundle: android.os.Bundle = buildBundle(param.parameters);
+ returnArray.add(bundle);
+ } else {
+ console.log("BE CARREFUL, no parameters into your complex event");
+ }
+ }
+ return returnArray;
+}
+
+function buildBundle(params: Array): android.os.Bundle {
+ const bundle = new android.os.Bundle();
+ for (const p in params) {
+ const param = params[p];
+ if (param.value !== undefined) {
+ if (param.type === "string") {
+ bundle.putString(param.key, param.value);
+ } else if (param.type === "double") {
+ bundle.putDouble(param.key, param.value);
+ } else if (param.type === "float") {
+ bundle.putFloat(param.key, param.value);
+ } else if (param.type === "int") {
+ bundle.putInt(param.key, param.value);
+ } else if (param.type === "long") {
+ bundle.putLong(param.key, param.value);
+ } else if (param.type === "boolean") {
+ bundle.putBoolean(param.key, param.value);
+ } else if (param.type === "array") {
+ bundle.putParcelableArrayList(param.key, getArrayList(param.value));
+ }
+ // bundle.putString(param.key, param.value);
+ }
+ }
+ return bundle;
+}
+
+export function logComplexEvent(options: LogComplexEventOptions): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ if (options.key === undefined) {
+ reject("Argument 'key' is missing");
+ return;
+ }
+
+ let bundle = new android.os.Bundle();
+ if (options.parameters !== undefined) {
+ bundle = buildBundle(options.parameters);
+ }
+
+ com.google.firebase.analytics.FirebaseAnalytics.getInstance(
+ appModule.android.currentContext || com.tns.NativeScriptApplication.getInstance()
+ ).logEvent(options.key, bundle);
+
+ resolve();
+ } catch (ex) {
+ console.log("Error in firebase.analytics.logEvent: " + ex);
+ reject(ex);
+ }
+ });
+}
+
export function setUserId(arg): Promise {
return new Promise((resolve, reject) => {
try {
@@ -100,3 +171,9 @@ export function setAnalyticsCollectionEnabled(enabled: boolean): void {
appModule.android.currentContext || com.tns.NativeScriptApplication.getInstance()
).setAnalyticsCollectionEnabled(enabled);
}
+
+export function setSessionTimeoutDuration(seconds: number): void {
+ com.google.firebase.analytics.FirebaseAnalytics.getInstance(
+ appModule.android.currentContext || com.tns.NativeScriptApplication.getInstance()
+ ).setSessionTimeoutDuration(seconds * 1000); // Android expects ms
+}
diff --git a/src/analytics/analytics.d.ts b/src/analytics/analytics.d.ts
index 2257acdd..99020b70 100644
--- a/src/analytics/analytics.d.ts
+++ b/src/analytics/analytics.d.ts
@@ -1,3 +1,9 @@
+export interface LogComplexEventParameter {
+ key: string;
+ value: any;
+ type: string;
+}
+
export interface LogEventParameter {
key: string;
value: string;
@@ -23,6 +29,26 @@ export interface LogEventOptions {
parameters?: Array;
}
+export interface LogComplexEventOptions {
+ /**
+ * The name of the event. You can use any name, but it's recommended to use one of
+ * the predefined constants. These values are the same for both iOS and Android, so
+ * for the complete list see https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Event.html
+ */
+ key: string;
+ /**
+ * Each (predefined) event has its own set of optional parameters, see
+ * https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Param
+ * Example:
+ *
+ * parameters: [{
+ * key: "item_name",
+ * value: "abc"
+ * }, ..]
+ */
+ parameters?: Array;
+}
+
export interface SetUserIdOptions {
userId: string;
}
@@ -38,6 +64,8 @@ export interface SetScreenNameOptions {
export declare function logEvent(options: LogEventOptions): Promise;
+export declare function logComplexEvent(options: LogComplexEventOptions): Promise;
+
export declare function setUserId(options: SetUserIdOptions): Promise;
export declare function setUserProperty(options: SetUserPropertyOptions): Promise;
@@ -45,3 +73,5 @@ export declare function setUserProperty(options: SetUserPropertyOptions): Promis
export declare function setScreenName(options: SetScreenNameOptions): Promise;
export declare function setAnalyticsCollectionEnabled(enabled: boolean): void;
+
+export declare function setSessionTimeoutDuration(seconds: number): void;
diff --git a/src/analytics/analytics.ios.ts b/src/analytics/analytics.ios.ts
index 8e784063..c8a2c11e 100644
--- a/src/analytics/analytics.ios.ts
+++ b/src/analytics/analytics.ios.ts
@@ -1,4 +1,9 @@
-import { LogEventOptions, SetScreenNameOptions, SetUserPropertyOptions } from "./analytics";
+import {
+ LogEventOptions,
+ SetScreenNameOptions,
+ SetUserPropertyOptions,
+ LogComplexEventOptions
+} from "./analytics";
export function logEvent(options: LogEventOptions): Promise {
return new Promise((resolve, reject) => {
@@ -28,6 +33,128 @@ export function logEvent(options: LogEventOptions): Promise {
});
}
+/*
+function getArrayList(array: Array): Array> {
+ let returnArray: Array> = new Array();
+ for (const p in array) {
+ const param = array[p];
+ if (param.parameters !== undefined) {
+ let bundle: NSMutableDictionary = buildBundle(param.parameters);
+ returnArray.push(bundle);
+ } else {
+ console.log("BE CARREFUL, no parameters into your complex event");
+ }
+ }
+ return returnArray;
+}
+
+function buildBundle(params: Array): NSMutableDictionary {
+ const bundle: NSMutableDictionary = NSMutableDictionary.new();
+ for (const p in params) {
+ const param = params[p];
+ if (param.value !== undefined) {
+ let nsString = NSMutableString.new();
+ nsString.setString(param.key);
+ if (param.type === "string" || param.type === "double" || param.type === "float" || param.type === "int" || param.type === "long" || param.type === "boolean") {
+ bundle.setObjectForKey(param.value, param.key);
+ }
+ else if (param.type === "array") {
+ let arrayList = getArrayList(param.value);
+ bundle.setObjectForKey(arrayList, param.key);
+ }
+ // bundle.putString(param.key, param.value);
+ }
+ }
+ return bundle;
+}*/
+
+/*export function logComplexEvent(options: LogComplexEventOptions): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ const dic: any = NSMutableDictionary.new();
+ if (options.parameters !== undefined) {
+ for (let p in options.parameters) {
+ const param = options.parameters[p];
+ if (param.value !== undefined) {
+ let dic1: NSMutableDictionary = buildBundle(options.parameters);
+ dic.setObjectForKey(dic1, param.key);
+ }
+ }
+ }
+ FIRAnalytics.logEventWithNameParameters(options.key, dic);
+ resolve();
+ }
+ catch (ex) {
+ console.log("Error in firebase.analytics.logEvent: " + ex);
+ reject(ex);
+ }
+ });
+}*/
+
+/*
+export function logComplexEvent(options: LogComplexEventOptions): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ const dic: any = NSMutableDictionary.new();
+ if (options.parameters !== undefined) {
+ for (let p in options.parameters) {
+ const param = options.parameters[p];
+ if (param.value !== undefined) {
+ const dic1: any = NSMutableDictionary.new();
+ dic1.setObjectForKey("value56", "testval");
+ dic.setObjectForKey(new Array(dic1), param.key);
+ }
+ }
+ }
+ FIRAnalytics.logEventWithNameParameters(options.key, dic);
+ resolve();
+ }
+ catch (ex) {
+ console.log("Error in firebase.analytics.logEvent: " + ex);
+ reject(ex);
+ }
+ });
+}*/
+
+
+export function logComplexEvent(options: LogComplexEventOptions): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ const dic: any = NSMutableDictionary.new();
+ if (options.parameters !== undefined) {
+ for (let p in options.parameters) {
+ const param = options.parameters[p];
+ if (param.type === "array" && param.value !== undefined) {
+ const listArray = new Array();
+ for (let val in param.value) {
+ const value = param.value[val];
+ if (value.parameters !== undefined) {
+ const dicTemp: any = NSMutableDictionary.new();
+ for (let i in value.parameters) {
+ const item = value.parameters[i];
+ if (item.type !== "array" && item.value !== undefined && item.key !== undefined) {
+ dicTemp.setObjectForKey(item.value, item.key);
+ }
+ }
+ listArray.push(dicTemp);
+ }
+ }
+ dic.setObjectForKey(listArray, param.key);
+ } else if (param.type === "string" || param.type === "double" || param.type === "float" || param.type === "int" || param.type === "long" || param.type === "boolean") {
+ dic.setObjectForKey(param.value, param.key);
+ }
+ }
+ }
+ FIRAnalytics.logEventWithNameParameters(options.key, dic);
+ resolve();
+ } catch (ex) {
+ console.log("Error in firebase.analytics.logEvent: " + ex);
+ reject(ex);
+ }
+ });
+}
+
+
export function setUserId(arg): Promise {
return new Promise((resolve, reject) => {
try {
@@ -87,5 +214,9 @@ export function setScreenName(options: SetScreenNameOptions): Promise {
}
export function setAnalyticsCollectionEnabled(enabled: boolean): void {
- FIRAnalyticsConfiguration.sharedInstance().setAnalyticsCollectionEnabled(enabled);
+ FIRAnalytics.setAnalyticsCollectionEnabled(enabled);
+}
+
+export function setSessionTimeoutDuration(seconds: number): void {
+ FIRAnalytics.setSessionTimeoutInterval(seconds);
}
diff --git a/src/app/auth/index.ts b/src/app/auth/index.ts
index 31f337bb..2c208c07 100644
--- a/src/app/auth/index.ts
+++ b/src/app/auth/index.ts
@@ -1,15 +1,59 @@
import * as firebase from "../../firebase";
-import { FirebaseEmailLinkActionCodeSettings, LoginType, User } from "../../firebase";
+import { FirebaseEmailLinkActionCodeSettings, LoginType, User, Unsubscribe } from "../../firebase";
-export module auth {
+export namespace auth {
export class Auth {
private authStateChangedHandler;
+ private authStateOnErrorHandler;
public currentUser: User;
+ public languageCode: string | null;
- public onAuthStateChanged(handler: (user: User) => void): void {
+ private loginHelper(options: firebase.LoginOptions) {
+ return new Promise((resolve, reject) => {
+ firebase.login(options)
+ .then((user: User) => {
+ this.currentUser = user;
+ this.authStateChangedHandler && this.authStateChangedHandler(user);
+ resolve({
+ additionalUserInfo: user.additionalUserInfo,
+ credential: null,
+ operationType: "SignIn",
+ user: user,
+ });
+ }).catch(err => {
+ let code = 'auth/exception';
+ let message = err.toString();
+ // Identify code for android. Note that the IOS implementation doesn't return a code.
+ if (message.includes('com.google.firebase.auth.FirebaseAuthInvalidCredentialsException')) {
+ code = 'auth/wrong-password';
+ } else if (message.includes('com.google.firebase.auth.FirebaseAuthInvalidUserException')) {
+ code = 'auth/user-not-found';
+ // Note that Android returns one exception for both user not found and invalid email whereas
+ // the web api returns seperate codes. Therefore the conditional below can never be satisfied
+ // for android.
+ // } else if (message.includes('com.google.firebase.auth.FirebaseAuthInvalidUserException')) {
+ // code = 'auth/invalid-email'
+ }
+ this.authStateOnErrorHandler && this.authStateOnErrorHandler(err.toString());
+ reject({
+ code: code,
+ message: message
+ });
+ });
+ });
+ }
+
+ // Completed will never be called, but it is here to closely follow the web api interface.
+ public onAuthStateChanged(handler: (user: User) => void, error?: (err) => any, completed?: Unsubscribe): Unsubscribe {
this.authStateChangedHandler = handler;
+ if (error) this.authStateOnErrorHandler = error;
console.log(">> added onAuthStateChanged handler");
- };
+
+ return () => {
+ this.authStateChangedHandler = undefined;
+ this.authStateOnErrorHandler = undefined;
+ };
+ }
public signOut(): Promise {
return new Promise((resolve, reject) => {
@@ -20,6 +64,7 @@ export module auth {
resolve();
})
.catch(err => {
+ this.authStateOnErrorHandler && this.authStateOnErrorHandler(err);
reject({
// code: "",
message: err
@@ -28,46 +73,70 @@ export module auth {
});
}
- public signInWithEmailAndPassword(email: string, password: string): Promise {
+ public unlink(providerId: string): Promise {
return new Promise((resolve, reject) => {
- firebase.login({
- type: LoginType.PASSWORD,
- passwordOptions: {
- email: email,
- password: password
- }
- }).then((user: User) => {
- this.currentUser = user;
- this.authStateChangedHandler && this.authStateChangedHandler(user);
- resolve();
- }, (err => {
- reject({
- // code: "",
- message: err
- });
- }));
+ firebase.unlink(providerId)
+ .then(user => {
+ this.currentUser = user;
+ resolve(user);
+ })
+ .catch(err => {
+ reject({
+ // code: "",
+ message: err
+ });
+ });
});
}
+ public signInWithEmailAndPassword(email: string, password: string): Promise {
+ const emailOption: firebase.LoginOptions = {
+ type: LoginType.PASSWORD,
+ passwordOptions: {
+ email: email,
+ password: password
+ }
+ };
+ return this.loginHelper(emailOption);
+ }
+
+ public signInWithCustomToken(token: string): Promise {
+ const customTokenOption: firebase.LoginOptions = {
+ type: LoginType.CUSTOM,
+ customOptions: {
+ token: token
+ }
+ };
+ return this.loginHelper(customTokenOption);
+ }
+
+ public signInAnonymously(): Promise {
+ const anonymousOption: firebase.LoginOptions = {
+ type: LoginType.ANONYMOUS
+ };
+ return this.loginHelper(anonymousOption);
+ }
+
public sendSignInLinkToEmail(email: string, actionCodeSettings: FirebaseEmailLinkActionCodeSettings): Promise {
- return new Promise((resolve, reject) => {
- firebase.login({
- type: LoginType.EMAIL_LINK,
+ const sendSignInLinklOption: firebase.LoginOptions = {
+ type: LoginType.EMAIL_LINK,
emailLinkOptions: {
email: email,
url: actionCodeSettings.url,
}
- }).then((user: User) => {
- this.currentUser = user;
- this.authStateChangedHandler && this.authStateChangedHandler(user);
- resolve();
- }, (err => {
- reject({
- // code: "",
- message: err
- });
- }));
- });
+ };
+ return this.loginHelper(sendSignInLinklOption);
+ }
+
+ public signInWithEmailLink(email: string, emailLink: string): Promise {
+ const signInWithEmailOption: firebase.LoginOptions = {
+ type: firebase.LoginType.EMAIL_LINK,
+ emailLinkOptions: {
+ email: email,
+ url: emailLink
+ }
+ };
+ return this.loginHelper(signInWithEmailOption);
}
public createUserWithEmailAndPassword(email: string, password: string): Promise {
@@ -79,23 +148,30 @@ export module auth {
this.currentUser = user;
resolve(user);
}).catch(err => reject(err));
- })
+ });
}
- public signInAnonymously(): Promise {
- return new Promise((resolve, reject) => {
- firebase.login({
- type: LoginType.ANONYMOUS
- }).then((user: User) => {
- this.currentUser = user;
- this.authStateChangedHandler && this.authStateChangedHandler(user);
- resolve();
- }, (err => {
- reject({
- // code: "",
- message: err
- });
- }));
+ public updateEmail(newEmail: string): Promise {
+ return new Promise((resolve, reject) => {
+ firebase.updateEmail(newEmail)
+ .then(() => resolve())
+ .catch(err => reject(err));
+ });
+ }
+
+ public updatePassword(newPassword: string): Promise {
+ return new Promise((resolve, reject) => {
+ firebase.updatePassword(newPassword)
+ .then(() => resolve())
+ .catch(err => reject(err));
+ });
+ }
+
+ public sendPasswordResetEmail(email: string): Promise {
+ return new Promise((resolve, reject) => {
+ firebase.sendPasswordResetEmail(email)
+ .then(() => resolve())
+ .catch(err => reject(err));
});
}
diff --git a/src/app/database/index.ts b/src/app/database/index.ts
index 583babd0..892e431c 100644
--- a/src/app/database/index.ts
+++ b/src/app/database/index.ts
@@ -5,7 +5,7 @@ import { nextPushId } from "./util/NextPushId";
export module database {
export interface DataSnapshot {
// child(path: string): DataSnapshot;
- // exists(): boolean;
+ exists(): boolean;
// exportVal(): any;
// forEach(action: (a: DataSnapshot) => boolean): boolean;
// getPriority(): string | number | null;
@@ -31,13 +31,12 @@ export module database {
public on(eventType /* TODO use */: string, callback: (a: DataSnapshot | null, b?: string) => any, cancelCallbackOrContext?: Object | null, context?: Object | null): (a: DataSnapshot | null, b?: string) => any {
const onValueEvent = result => {
if (result.error) {
- callback(result.error);
+ callback(result);
} else {
callback({
key: result.key,
- val: () => {
- return result.value;
- }
+ val: () => result.value,
+ exists: () => !!result.value
});
}
};
@@ -74,14 +73,13 @@ export module database {
return null;
}
- public once(eventType: string, successCallback?: (a: DataSnapshot, b?: string) => any, failureCallbackOrContext?: Object | null, context?: Object | null): Promise {
+ public once(eventType: string, successCallback?: (a: DataSnapshot, b?: string) => any, failureCallbackOrContext?: Object | null, context?: Object | null): Promise {
return new Promise((resolve, reject) => {
firebase.getValue(this.path).then(result => {
resolve({
key: result.key,
- val: () => {
- return result.value;
- }
+ val: () => result.value,
+ exists: () => !!result.value
});
});
});
@@ -93,9 +91,8 @@ export module database {
callbacks && callbacks.map(callback => {
callback({
key: result.key,
- val: () => {
- return result.value;
- }
+ val: () => result.value,
+ exists: () => !!result.value
});
});
};
@@ -244,6 +241,21 @@ export module database {
});
});
}
+
+ public onDisconnect(): firebase.OnDisconnect {
+ return firebase.onDisconnect(this.path);
+ }
+
+ public transaction(
+ transactionUpdate: (a: any) => any,
+ onComplete?: (
+ a: Error | null,
+ b: boolean,
+ c: firebase.DataSnapshot | null
+ ) => any,
+ applyLocally?: boolean): Promise {
+ return firebase.transaction(this.path, transactionUpdate, onComplete);
+ }
}
export interface ThenableReference extends Reference /*, PromiseLike */
diff --git a/src/app/firestore/index.ts b/src/app/firestore/index.ts
index 24381e2a..93baed7d 100644
--- a/src/app/firestore/index.ts
+++ b/src/app/firestore/index.ts
@@ -33,5 +33,9 @@ export namespace firestore {
batch(): firebase.firestore.WriteBatch {
return firebase.firestore.batch();
}
+
+ settings(settings: firebase.firestore.Settings): void {
+ firebase.firestore.settings(settings);
+ }
}
}
diff --git a/src/app/index.ts b/src/app/index.ts
index 6c4bec96..472b87b9 100644
--- a/src/app/index.ts
+++ b/src/app/index.ts
@@ -52,6 +52,14 @@ export function firestore(app?: any): firebaseFirestoreModule.Firestore {
let functionsCache;
+export namespace database {
+ // This is just to follow the webs interface. On android and ios enable logging only accepts a boolean
+ // By default logging is set to Info. We will set to debug if true and none if false.
+ export function enableLogging(logger?: boolean | ((a: string) => any), persistent?: boolean): any {
+ firebase.enableLogging(logger, persistent);
+ }
+}
+
export function functions(app?: any): firebaseFunctionsModule.Functions {
if (app) {
console.log("The 'app' param is ignored at the moment.");
diff --git a/src/crashlytics/crashlytics.android.ts b/src/crashlytics/crashlytics.android.ts
index d5f4df3c..26b97d47 100644
--- a/src/crashlytics/crashlytics.android.ts
+++ b/src/crashlytics/crashlytics.android.ts
@@ -6,9 +6,13 @@ export function sendCrashLog(exception: any /* java.lang.Exception */): void {
}
}
-export function log(priority: number, tag: string, msg: string): void {
+export function log(msg: string, tag?: string, priority?: number): void {
if (isCrashlyticsAvailable()) {
- com.crashlytics.android.Crashlytics.log(priority, tag, msg);
+ if (tag && priority) {
+ com.crashlytics.android.Crashlytics.log(priority, tag, msg);
+ } else {
+ com.crashlytics.android.Crashlytics.log(msg);
+ }
}
}
@@ -48,6 +52,12 @@ export function setUserId(id: string): void {
}
}
+export function crash(): void {
+ if (isCrashlyticsAvailable()) {
+ com.crashlytics.android.Crashlytics.crash();
+ }
+}
+
function isCrashlyticsAvailable(): boolean {
if (typeof (com.crashlytics) === "undefined" || typeof (com.crashlytics.android.Crashlytics) === "undefined") {
console.log("Add 'crashlytics: true' to firebase.nativescript.json and remove the platforms folder");
diff --git a/src/crashlytics/crashlytics.d.ts b/src/crashlytics/crashlytics.d.ts
index 5633587e..51378bd8 100644
--- a/src/crashlytics/crashlytics.d.ts
+++ b/src/crashlytics/crashlytics.d.ts
@@ -4,7 +4,7 @@
*/
export declare function sendCrashLog(exception: any): void;
-export declare function log(priority: number, tag: string, msg: string): void;
+export declare function log(msg: string, tag?: string, priority?: number): void;
export declare function setString(key: string, value: string): void;
@@ -17,3 +17,5 @@ export declare function setInt(key: string, value: number): void;
export declare function setDouble(key: string, value: number): void;
export declare function setUserId(userId: string): void;
+
+export declare function crash(): void;
\ No newline at end of file
diff --git a/src/crashlytics/crashlytics.ios.ts b/src/crashlytics/crashlytics.ios.ts
index 69ab9623..1b357cb7 100644
--- a/src/crashlytics/crashlytics.ios.ts
+++ b/src/crashlytics/crashlytics.ios.ts
@@ -4,9 +4,13 @@ export function sendCrashLog(exception: any /* NSError */): void {
}
}
-export function log(priority: number, tag: string, msg: string): void {
+export function log(msg: string, tag?: string, priority?: number): void {
if (isCrashlyticsAvailable()) {
- Crashlytics.sharedInstance().logEvent(tag + " - " + msg);
+ if (tag) {
+ TNSCrashlyticsLoggerWrapper.log(tag + " - " + msg);
+ } else {
+ TNSCrashlyticsLoggerWrapper.log(msg);
+ }
}
}
@@ -46,6 +50,12 @@ export function setUserId(id: string): void {
}
}
+export function crash(): void {
+ if (isCrashlyticsAvailable()) {
+ Crashlytics.sharedInstance().crash();
+ }
+}
+
function isCrashlyticsAvailable(): boolean {
if (typeof (Crashlytics) === "undefined") {
console.log("Add 'crashlytics: true' to firebase.nativescript.json and remove the platforms folder");
diff --git a/src/firebase-common.ts b/src/firebase-common.ts
index 92136c08..3b3db39f 100755
--- a/src/firebase-common.ts
+++ b/src/firebase-common.ts
@@ -65,6 +65,15 @@ export const firebase: any = {
GOOGLE: "google",
EMAIL_LINK: "emailLink"
},
+ LogComplexEventTypeParameter: {
+ STRING: "string",
+ INT: "int",
+ FLOAT: "float",
+ DOUBLE: "double",
+ LONG: "long",
+ ARRAY: "array",
+ BOOLEAN: "boolean"
+ },
QueryOrderByType: {
KEY: "key",
VALUE: "value",
@@ -134,9 +143,10 @@ export const firebase: any = {
requestPhoneAuthVerificationCode: (onUserResponse, verificationPrompt) => {
prompt(verificationPrompt || "Verification code").then(promptResult => {
if (!promptResult.result) {
- return;
+ onUserResponse(undefined);
+ } else {
+ onUserResponse(promptResult.text);
}
- onUserResponse(promptResult.text);
});
},
// for backward compatibility, because plugin version 4.0.0 moved the params to per-logintype objects
diff --git a/src/firebase.android.ts b/src/firebase.android.ts
index 19f43ab1..ade91d54 100755
--- a/src/firebase.android.ts
+++ b/src/firebase.android.ts
@@ -1,3 +1,18 @@
+import * as appModule from "tns-core-modules/application";
+import { AndroidActivityResultEventData } from "tns-core-modules/application";
+import lazy from "tns-core-modules/utils/lazy";
+import { ad as AndroidUtils } from "tns-core-modules/utils/utils";
+import {
+ ActionCodeSettings,
+ DataSnapshot,
+ FBDataSingleEvent,
+ FBErrorData,
+ firestore,
+ GetAuthTokenOptions,
+ IdTokenResult,
+ OnDisconnect as OnDisconnectBase, QueryOptions,
+ User
+} from "./firebase";
import {
DocumentSnapshot as DocumentSnapshotBase,
FieldValue,
@@ -5,21 +20,23 @@ import {
GeoPoint,
isDocumentReference
} from "./firebase-common";
-import * as firebaseMessaging from "./messaging/messaging";
import * as firebaseFunctions from "./functions/functions";
-import * as appModule from "tns-core-modules/application";
-import { AndroidActivityResultEventData } from "tns-core-modules/application";
-import { ad as AndroidUtils } from "tns-core-modules/utils/utils";
-import lazy from "tns-core-modules/utils/lazy";
-import { firestore, User } from "./firebase";
+import * as firebaseMessaging from "./messaging/messaging";
-declare const android, com: any;
+declare const com: any;
+const gmsAds = (com.google.android.gms).ads;
+const gmsTasks = (com.google.android.gms).tasks;
class DocumentSnapshot extends DocumentSnapshotBase {
android: com.google.firebase.firestore.DocumentSnapshot;
+ metadata = {
+ fromCache: this.snapshot.getMetadata().isFromCache(),
+ hasPendingWrites: this.snapshot.getMetadata().hasPendingWrites()
+ };
+
constructor(public snapshot: com.google.firebase.firestore.DocumentSnapshot) {
- super(snapshot ? snapshot.getId() : null, snapshot.exists(), firebase.toJsObject(snapshot.getData()), convertDocRef(snapshot.getReference()));
+ super(snapshot ? snapshot.getId() : null, snapshot.exists(), firebase.toJsObject(snapshot.getData()), firebase.firestore._getDocumentReference(snapshot.getReference()));
this.android = snapshot;
}
}
@@ -32,6 +49,7 @@ firebase._googleSignInIdToken = null;
firebase._facebookAccessToken = null;
let fbCallbackManager = null;
+let initializeArguments: any;
const GOOGLE_SIGNIN_INTENT_ID = 123;
const REQUEST_INVITE_INTENT_ID = 48;
@@ -52,7 +70,7 @@ const dynamicLinksEnabled = lazy(() => typeof (com.google.firebase.dynamiclinks)
if (authEnabled() && com.google.firebase.auth.FirebaseAuth.getInstance().isSignInWithEmailLink(emailLink)) {
const rememberedEmail = firebase.getRememberedEmailForEmailLinkLogin();
if (rememberedEmail !== undefined) {
- const emailLinkOnCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const emailLinkOnCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task => {
if (task.isSuccessful()) {
const authResult = task.getResult();
@@ -73,7 +91,7 @@ const dynamicLinksEnabled = lazy(() => typeof (com.google.firebase.dynamiclinks)
}
} else {
- const getDynamicLinksCallback = new com.google.android.gms.tasks.OnSuccessListener({
+ const getDynamicLinksCallback = new gmsTasks.OnSuccessListener({
onSuccess: pendingDynamicLinkData => {
if (pendingDynamicLinkData != null) {
@@ -125,9 +143,13 @@ firebase.toHashMap = obj => {
const fieldValue: FieldValue = obj[property];
if (fieldValue.type === "ARRAY_UNION") {
// nested arrays are not allowed, so harden against wrong usage: arrayUnion(["foo", "bar"]) vs arrayUnion("foo", "bar")
- node.put(property, com.google.firebase.firestore.FieldValue.arrayUnion(Array.isArray(fieldValue.value[0]) ? fieldValue.value[0] : fieldValue.value));
+ let values: Array = Array.isArray(fieldValue.value[0]) ? fieldValue.value[0] : fieldValue.value;
+ values = values.map(v => typeof (v) === "object" ? firebase.toHashMap(v) : v);
+ node.put(property, com.google.firebase.firestore.FieldValue.arrayUnion(values));
} else if (fieldValue.type === "ARRAY_REMOVE") {
- node.put(property, com.google.firebase.firestore.FieldValue.arrayRemove(Array.isArray(fieldValue.value[0]) ? fieldValue.value[0] : fieldValue.value));
+ let values: Array = Array.isArray(fieldValue.value[0]) ? fieldValue.value[0] : fieldValue.value;
+ values = values.map(v => typeof (v) === "object" ? firebase.toHashMap(v) : v);
+ node.put(property, com.google.firebase.firestore.FieldValue.arrayRemove(values));
} else {
console.log("You found a bug! Please report an issue at https://github.com/EddyVerbruggen/nativescript-plugin-firebase/issues, mention fieldValue.type = '" + fieldValue.type + "'. Thanks!");
}
@@ -282,11 +304,14 @@ firebase.init = arg => {
return new Promise((resolve, reject) => {
if (firebase.initialized) {
reject("Firebase already initialized");
+ return;
}
+
firebase.initialized = true;
const runInit = () => {
arg = arg || {};
+ initializeArguments = arg;
com.google.firebase.analytics.FirebaseAnalytics.getInstance(
appModule.android.currentContext || com.tns.NativeScriptApplication.getInstance()
@@ -307,12 +332,16 @@ firebase.init = arg => {
firebase.instance = fDatabase.getInstance().getReference();
}
- if (typeof (com.google.firebase.firestore) !== "undefined") {
- com.google.firebase.firestore.FirebaseFirestore.getInstance().setFirestoreSettings(
- new com.google.firebase.firestore.FirebaseFirestoreSettings.Builder()
- .setPersistenceEnabled(arg.persist !== false)
- .setTimestampsInSnapshotsEnabled(true)
- .build());
+ // Firestore has offline persistence enabled by default
+ if (arg.persist === false && typeof (com.google.firebase.firestore) !== "undefined") {
+ try {
+ com.google.firebase.firestore.FirebaseFirestore.getInstance().setFirestoreSettings(
+ new com.google.firebase.firestore.FirebaseFirestoreSettings.Builder()
+ .setPersistenceEnabled(false)
+ .build());
+ } catch (ignore) {
+ // this may happen during livesync, and without catching this exception the app would crash
+ }
}
if (authEnabled()) {
@@ -357,9 +386,9 @@ firebase.init = arg => {
}
// Firebase AdMob
- if (typeof (com.google.android.gms.ads) !== "undefined" && typeof (com.google.android.gms.ads.MobileAds) !== "undefined") {
+ if (typeof (gmsAds) !== "undefined" && typeof (gmsAds.MobileAds) !== "undefined") {
// init admob
- com.google.android.gms.ads.MobileAds.initialize(appModule.android.context);
+ gmsAds.MobileAds.initialize(appModule.android.context);
}
resolve(firebase.instance);
@@ -387,7 +416,7 @@ firebase.fetchProvidersForEmail = email => {
return;
}
- const onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task /* */ => {
if (!task.isSuccessful()) {
reject((task.getException() && task.getException().getReason ? task.getException().getReason() : task.getException()));
@@ -414,7 +443,7 @@ firebase.fetchSignInMethodsForEmail = email => {
return;
}
- const onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task /* */ => {
if (!task.isSuccessful()) {
reject((task.getException() && task.getException().getReason ? task.getException().getReason() : task.getException()));
@@ -482,7 +511,7 @@ firebase.getRemoteConfigDefaults = properties => {
firebase._isGooglePlayServicesAvailable = () => {
const activity = appModule.android.foregroundActivity || appModule.android.startActivity;
const googleApiAvailability = com.google.android.gms.common.GoogleApiAvailability.getInstance();
- const playServiceStatusSuccess = com.google.android.gms.common.ConnectionResult.SUCCESS; // 0
+ const playServiceStatusSuccess = 0; // com.google.android.gms.common.ConnectionResult.SUCCESS;
const playServicesStatus = googleApiAvailability.isGooglePlayServicesAvailable(activity);
const available = playServicesStatus === playServiceStatusSuccess;
if (!available && googleApiAvailability.isUserResolvableError(playServicesStatus)) {
@@ -522,54 +551,70 @@ firebase.getRemoteConfig = arg => {
const remoteConfigSettings = new com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(arg.developerMode || false)
.build();
- firebaseRemoteConfig.setConfigSettings(remoteConfigSettings);
- const defaults = firebase.getRemoteConfigDefaults(arg.properties);
- firebaseRemoteConfig.setDefaults(firebase.toHashMap(defaults));
-
- const returnMethod = throttled => {
- firebaseRemoteConfig.activateFetched();
-
- const lastFetchTime = firebaseRemoteConfig.getInfo().getFetchTimeMillis();
- const lastFetch = new Date(lastFetchTime);
+ firebaseRemoteConfig.setConfigSettings(remoteConfigSettings);
- const result = {
- lastFetch: lastFetch,
- throttled: throttled,
- properties: {}
- };
+ const addOnCompleteListener = new gmsTasks.OnCompleteListener({
+ onComplete: task => {
+ if (!task.isSuccessful()) {
+ reject((task.getException() && task.getException().getReason ? task.getException().getReason() : task.getException()));
+ } else {
+ const returnMethod = throttled => {
+ const addOnCompleteActivateListener = new gmsTasks.OnCompleteListener({
+ onComplete: task => {
+ if (!task.isSuccessful()) {
+ reject((task.getException() && task.getException().getReason ? task.getException().getReason() : task.getException()));
+ } else {
+ const lastFetchTime = firebaseRemoteConfig.getInfo().getFetchTimeMillis();
+ const lastFetch = new Date(lastFetchTime);
+
+ const result = {
+ lastFetch: lastFetch,
+ throttled: throttled,
+ properties: {}
+ };
+
+ for (const p in arg.properties) {
+ const prop = arg.properties[p];
+ const key = prop.key;
+ const value = firebaseRemoteConfig.getString(key);
+ // we could have the user pass in the type but this seems easier to use
+ result.properties[key] = firebase.strongTypeify(value);
+ }
+ resolve(result);
+ }
+ }
+ });
+ firebaseRemoteConfig.activate().addOnCompleteListener(addOnCompleteActivateListener);
+ };
- for (const p in arg.properties) {
- const prop = arg.properties[p];
- const key = prop.key;
- const value = firebaseRemoteConfig.getString(key);
- // we could have the user pass in the type but this seems easier to use
- result.properties[key] = firebase.strongTypeify(value);
- }
+ const onSuccessListener = new gmsTasks.OnSuccessListener({
+ onSuccess: () => returnMethod(false)
+ });
- resolve(result);
- };
+ const onFailureListener = new gmsTasks.OnFailureListener({
+ onFailure: exception => {
+ if (exception.getMessage() === "com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException") {
+ returnMethod(true);
+ } else {
+ reject("Retrieving remote config data failed. " + exception);
+ }
+ }
+ });
- const onSuccessListener = new com.google.android.gms.tasks.OnSuccessListener({
- onSuccess: () => returnMethod(false)
- });
+ // default 12 hours, just like the SDK does
+ const expirationDuration = arg.cacheExpirationSeconds || 43200;
- const onFailureListener = new com.google.android.gms.tasks.OnFailureListener({
- onFailure: exception => {
- if (exception.getMessage() === "com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException") {
- returnMethod(true);
- } else {
- reject("Retrieving remote config data failed. " + exception);
+ firebaseRemoteConfig.fetch(expirationDuration)
+ .addOnSuccessListener(onSuccessListener)
+ .addOnFailureListener(onFailureListener);
}
}
});
- // default 12 hours, just like the SDK does
- const expirationDuration = arg.cacheExpirationSeconds || 43200;
-
- firebaseRemoteConfig.fetch(expirationDuration)
- .addOnSuccessListener(onSuccessListener)
- .addOnFailureListener(onFailureListener);
+ const defaults = firebase.getRemoteConfigDefaults(arg.properties);
+ firebaseRemoteConfig.setDefaultsAsync(firebase.toHashMap(defaults))
+ .addOnCompleteListener(addOnCompleteListener);
};
try {
@@ -607,13 +652,13 @@ firebase.getCurrentUser = arg => {
});
};
-firebase.sendEmailVerification = () => {
+firebase.sendEmailVerification = (actionCodeSettings?: ActionCodeSettings) => {
return new Promise((resolve, reject) => {
try {
const firebaseAuth = com.google.firebase.auth.FirebaseAuth.getInstance();
const user = firebaseAuth.getCurrentUser();
if (user !== null) {
- const addOnCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const addOnCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task => {
if (!task.isSuccessful()) {
reject((task.getException() && task.getException().getReason ? task.getException().getReason() : task.getException()));
@@ -623,7 +668,24 @@ firebase.sendEmailVerification = () => {
}
});
- user.sendEmailVerification().addOnCompleteListener(addOnCompleteListener);
+ if (actionCodeSettings) {
+ const settingsBuilder = new com.google.firebase.auth.ActionCodeSettings.newBuilder();
+ if (actionCodeSettings.handleCodeInApp !== undefined) {
+ settingsBuilder.setHandleCodeInApp(actionCodeSettings.handleCodeInApp);
+ }
+ if (actionCodeSettings.url) {
+ settingsBuilder.setUrl(actionCodeSettings.url);
+ }
+ if (actionCodeSettings.iOS && actionCodeSettings.iOS.bundleId) {
+ settingsBuilder.setIOSBundleId(actionCodeSettings.iOS.bundleId);
+ }
+ if (actionCodeSettings.android && actionCodeSettings.android.packageName) {
+ settingsBuilder.setAndroidPackageName(actionCodeSettings.android.packageName, actionCodeSettings.android.installApp, actionCodeSettings.android.minimumVersion || null);
+ }
+ user.sendEmailVerification(settingsBuilder.build()).addOnCompleteListener(addOnCompleteListener);
+ } else {
+ user.sendEmailVerification().addOnCompleteListener(addOnCompleteListener);
+ }
} else {
reject("Log in first");
}
@@ -659,19 +721,53 @@ firebase.logout = arg => {
});
};
-firebase.getAuthToken = arg => {
+firebase.unlink = providerId => {
+ return new Promise((resolve, reject) => {
+ try {
+ const user = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser();
+ if (!user) {
+ reject("Not logged in");
+ return;
+ }
+
+ user.unlink(providerId)
+ .addOnCompleteListener(new gmsTasks.OnCompleteListener({
+ onComplete: task => {
+ if (task.isSuccessful()) {
+ resolve();
+ } else {
+ reject((task.getException() && task.getException().getReason ? task.getException().getReason() : task.getException()));
+ }
+ }
+ })
+ );
+ } catch (ex) {
+ console.log("Error in firebase.unlink: " + ex);
+ reject(ex);
+ }
+ });
+};
+
+firebase.getAuthToken = (arg: GetAuthTokenOptions): Promise => {
return new Promise((resolve, reject) => {
try {
const firebaseAuth = com.google.firebase.auth.FirebaseAuth.getInstance();
const user = firebaseAuth.getCurrentUser();
if (user !== null) {
- const onSuccessListener = new com.google.android.gms.tasks.OnSuccessListener({
- onSuccess: getTokenResult => {
- resolve(getTokenResult.getToken());
+ const onSuccessListener = new gmsTasks.OnSuccessListener({
+ onSuccess: tokenResult => {
+ resolve({
+ token: tokenResult.getToken(),
+ claims: firebase.toJsObject(tokenResult.getClaims()),
+ signInProvider: tokenResult.getSignInProvider(),
+ expirationTime: tokenResult.getExpirationTimestamp(),
+ issuedAtTime: tokenResult.getIssuedAtTimestamp(),
+ authTime: tokenResult.getAuthTimestamp()
+ });
}
});
- const onFailureListener = new com.google.android.gms.tasks.OnFailureListener({
+ const onFailureListener = new gmsTasks.OnFailureListener({
onFailure: exception => {
reject(exception);
}
@@ -728,7 +824,18 @@ function toLoginResult(user, additionalUserInfo?): User {
metadata: {
creationTimestamp: user.getMetadata() ? new Date(user.getMetadata().getCreationTimestamp() as number) : null,
lastSignInTimestamp: user.getMetadata() ? new Date(user.getMetadata().getLastSignInTimestamp() as number) : null
- }
+ },
+ getIdToken: (forceRefresh?: boolean) => new Promise((resolve, reject) => {
+ firebase.getAuthToken({forceRefresh})
+ .then((result: IdTokenResult) => resolve(result.token))
+ .catch(reject);
+ }),
+ getIdTokenResult: (forceRefresh?: boolean) => new Promise((resolve, reject) => {
+ firebase.getAuthToken({forceRefresh})
+ .then((result: IdTokenResult) => resolve(result))
+ .catch(reject);
+ }),
+ sendEmailVerification: (actionCodeSettings?: ActionCodeSettings) => firebase.sendEmailVerification(actionCodeSettings)
};
if (firebase.currentAdditionalUserInfo) {
@@ -758,7 +865,7 @@ firebase.login = arg => {
firebase.moveLoginOptionsToObjects(arg);
const firebaseAuth = com.google.firebase.auth.FirebaseAuth.getInstance();
- const onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task => {
if (!task.isSuccessful()) {
console.log("Logging in the user failed. " + (task.getException() && task.getException().getReason ? task.getException().getReason() : task.getException()));
@@ -818,7 +925,7 @@ firebase.login = arg => {
arg.emailLinkOptions.android ? arg.emailLinkOptions.android.minimumVersion || "1" : "1")
.build();
- const onEmailLinkCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const onEmailLinkCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task => {
if (!task.isSuccessful()) {
reject((task.getException() && task.getException().getReason ? task.getException().getReason() : task.getException()));
@@ -876,6 +983,10 @@ firebase.login = arg => {
if (firebase._verifyPhoneNumberInProgress) {
firebase._verifyPhoneNumberInProgress = false;
firebase.requestPhoneAuthVerificationCode(userResponse => {
+ if (userResponse === undefined && this.reject) {
+ this.reject("Prompt was canceled");
+ return;
+ }
const authCredential = com.google.firebase.auth.PhoneAuthProvider.getCredential(verificationId, userResponse);
const user = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser();
@@ -1096,7 +1207,7 @@ firebase.reauthenticate = arg => {
return;
}
- const onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task => {
if (task.isSuccessful()) {
resolve();
@@ -1124,7 +1235,7 @@ firebase.reloadUser = () => {
return;
}
- const onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task => {
if (task.isSuccessful()) {
resolve();
@@ -1141,59 +1252,78 @@ firebase.reloadUser = () => {
});
};
-firebase.resetPassword = arg => {
- return new Promise((resolve, reject) => {
+firebase.sendPasswordResetEmail = (email: string): Promise => {
+ return new Promise((resolve, reject) => {
try {
- if (!arg.email) {
- reject("Resetting a password requires an email argument");
- } else {
- const onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
- onComplete: task => {
- if (task.isSuccessful()) {
- resolve();
- } else {
- // TODO extract error
- reject("Sending password reset email failed");
- }
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
+ onComplete: task => {
+ if (task.isSuccessful()) {
+ resolve();
+ } else {
+ // TODO extract error
+ reject("Sending password reset email failed");
}
- });
+ }
+ });
- const firebaseAuth = com.google.firebase.auth.FirebaseAuth.getInstance();
- firebaseAuth.sendPasswordResetEmail(arg.email).addOnCompleteListener(onCompleteListener);
- }
+ const firebaseAuth = com.google.firebase.auth.FirebaseAuth.getInstance();
+ firebaseAuth.sendPasswordResetEmail(email).addOnCompleteListener(onCompleteListener);
} catch (ex) {
- console.log("Error in firebase.resetPassword: " + ex);
+ console.log("Error in firebase.sendPasswordResetEmail: " + ex);
reject(ex);
}
});
};
-firebase.changePassword = arg => {
- return new Promise((resolve, reject) => {
+firebase.updateEmail = (newEmail: string): Promise => {
+ return new Promise((resolve, reject) => {
try {
- if (!arg.email || !arg.oldPassword || !arg.newPassword) {
- reject("Changing a password requires an email and an oldPassword and a newPassword arguments");
- } else {
- const onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
- onComplete: task => {
- if (task.isSuccessful()) {
- resolve();
- } else {
- reject("Updating password failed. " + (task.getException() && task.getException().getReason ? task.getException().getReason() : task.getException()));
- }
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
+ onComplete: task => {
+ if (task.isSuccessful()) {
+ resolve();
+ } else {
+ reject("Updating email failed. " + (task.getException() && task.getException().getReason ? task.getException().getReason() : task.getException()));
}
- });
+ }
+ });
- const user = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser();
+ const user = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser();
- if (user === null) {
- reject("no current user");
- } else {
- user.updatePassword(arg.newPassword).addOnCompleteListener(onCompleteListener);
+ if (user === null) {
+ reject("no current user");
+ } else {
+ user.updateEmail(newEmail).addOnCompleteListener(onCompleteListener);
+ }
+ } catch (ex) {
+ console.log("Error in firebase.updateEmail: " + ex);
+ reject(ex);
+ }
+ });
+};
+
+firebase.updatePassword = (newPassword: string): Promise => {
+ return new Promise((resolve, reject) => {
+ try {
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
+ onComplete: task => {
+ if (task.isSuccessful()) {
+ resolve();
+ } else {
+ reject("Updating password failed. " + (task.getException() && task.getException().getReason ? task.getException().getReason() : task.getException()));
+ }
}
+ });
+
+ const user = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser();
+
+ if (user === null) {
+ reject("no current user");
+ } else {
+ user.updatePassword(newPassword).addOnCompleteListener(onCompleteListener);
}
} catch (ex) {
- console.log("Error in firebase.changePassword: " + ex);
+ console.log("Error in firebase.updatePassword: " + ex);
reject(ex);
}
});
@@ -1207,7 +1337,7 @@ firebase.createUser = arg => {
} else {
const firebaseAuth = com.google.firebase.auth.FirebaseAuth.getInstance();
- const onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task => {
// see https://firebase.google.com/docs/reference/android/com/google/firebase/auth/FirebaseAuth#public-methods
if (!task.isSuccessful()) {
@@ -1239,7 +1369,7 @@ firebase.deleteUser = arg => {
return;
}
- const onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task => {
if (!task.isSuccessful()) {
reject("Deleting a user failed. " + (task.getException() && task.getException().getReason ? task.getException().getReason() : task.getException()));
@@ -1271,7 +1401,7 @@ firebase.updateProfile = arg => {
return;
}
- const onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task => {
if (task.isSuccessful()) {
resolve();
@@ -1299,6 +1429,10 @@ firebase.updateProfile = arg => {
});
};
+/***********************************************
+ * Start Realtime Database Functions
+ ***********************************************/
+
firebase.keepInSync = (path, switchOn) => {
return new Promise((resolve, reject) => {
try {
@@ -1319,9 +1453,9 @@ firebase.keepInSync = (path, switchOn) => {
firebase._addObservers = (to, updateCallback) => {
const listener = new com.google.firebase.database.ChildEventListener({
- onCancelled: error => {
+ onCancelled: databaseError => {
updateCallback({
- type: 'Cancelled'
+ error: databaseError.getMessage()
});
},
onChildAdded: (snapshot, previousChildKey) => {
@@ -1446,10 +1580,10 @@ firebase.push = (path, val) => {
const pushInstance = firebase.instance.child(path).push();
pushInstance.setValue(firebase.toValue(val))
- .addOnSuccessListener(new com.google.android.gms.tasks.OnSuccessListener({
+ .addOnSuccessListener(new gmsTasks.OnSuccessListener({
onSuccess: () => resolve({key: pushInstance.getKey()})
}))
- .addOnFailureListener(new com.google.android.gms.tasks.OnFailureListener({
+ .addOnFailureListener(new gmsTasks.OnFailureListener({
onFailure: exception => reject(exception.getMessage())
}));
@@ -1469,10 +1603,10 @@ firebase.setValue = (path, val) => {
}
firebase.instance.child(path).setValue(firebase.toValue(val))
- .addOnSuccessListener(new com.google.android.gms.tasks.OnSuccessListener({
+ .addOnSuccessListener(new gmsTasks.OnSuccessListener({
onSuccess: () => resolve()
}))
- .addOnFailureListener(new com.google.android.gms.tasks.OnFailureListener({
+ .addOnFailureListener(new gmsTasks.OnFailureListener({
onFailure: exception => reject(exception.getMessage())
}));
@@ -1491,11 +1625,11 @@ firebase.update = (path, val) => {
return;
}
- const onSuccessListener = new com.google.android.gms.tasks.OnSuccessListener({
+ const onSuccessListener = new gmsTasks.OnSuccessListener({
onSuccess: () => resolve()
});
- const onFailureListener = new com.google.android.gms.tasks.OnFailureListener({
+ const onFailureListener = new gmsTasks.OnFailureListener({
onFailure: exception => reject(exception.getMessage())
});
@@ -1519,8 +1653,8 @@ firebase.update = (path, val) => {
});
};
-firebase.query = (updateCallback, path, options) => {
- return new Promise((resolve, reject) => {
+firebase.query = (updateCallback: (data: FBDataSingleEvent | FBErrorData) => void, path: string, options: QueryOptions): Promise => {
+ return new Promise((resolve, reject) => {
try {
if (firebase.instance === null) {
reject("Run init() first!");
@@ -1606,10 +1740,24 @@ firebase.query = (updateCallback, path, options) => {
if (options.singleEvent) {
const listener = new com.google.firebase.database.ValueEventListener({
onDataChange: snapshot => {
- const data = firebase.getCallbackData('ValueChanged', snapshot);
- if (updateCallback) updateCallback(data);
+
+ const result = {
+ type: "ValueChanged",
+ key: snapshot.getKey(),
+ value: {},
+ children: []
+ };
+
+ for (let iterator = snapshot.getChildren().iterator(); iterator.hasNext();) {
+ const snap = iterator.next();
+ const val = firebase.toJsObject(snap.getValue());
+ result.value[snap.getKey()] = val;
+ result.children.push(val);
+ }
+
+ if (updateCallback) updateCallback(result);
// resolve promise with data in case of single event, see https://github.com/EddyVerbruggen/nativescript-plugin-firebase/issues/126
- resolve(data);
+ resolve(result);
},
onCancelled: databaseError => {
if (updateCallback) updateCallback({
@@ -1644,10 +1792,10 @@ firebase.remove = path => {
}
firebase.instance.child(path).setValue(null)
- .addOnSuccessListener(new com.google.android.gms.tasks.OnSuccessListener({
+ .addOnSuccessListener(new gmsTasks.OnSuccessListener({
onSuccess: () => resolve()
}))
- .addOnFailureListener(new com.google.android.gms.tasks.OnFailureListener({
+ .addOnFailureListener(new gmsTasks.OnFailureListener({
onFailure: exception => reject(exception.getMessage())
}));
} catch (ex) {
@@ -1657,6 +1805,182 @@ firebase.remove = path => {
});
};
+class OnDisconnect implements OnDisconnectBase {
+
+ constructor(private disconnectInstance: com.google.firebase.database.OnDisconnect) {
+ }
+
+ cancel(): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ this.disconnectInstance.cancel()
+ .addOnSuccessListener(new gmsTasks.OnSuccessListener({
+ onSuccess: () => resolve()
+ }))
+ .addOnFailureListener(new gmsTasks.OnFailureListener({
+ onFailure: exception => reject(exception.getMessage())
+ }));
+ } catch (ex) {
+ console.log("Error in firebase.onDisconnect.cancel: " + ex);
+ reject(ex);
+ }
+ });
+ }
+
+ remove(): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ this.disconnectInstance.removeValue()
+ .addOnSuccessListener(new gmsTasks.OnSuccessListener({
+ onSuccess: () => resolve()
+ }))
+ .addOnFailureListener(new gmsTasks.OnFailureListener({
+ onFailure: exception => reject(exception.getMessage())
+ }));
+ } catch (ex) {
+ console.log("Error in firebase.onDisconnect.remove: " + ex);
+ reject(ex);
+ }
+ });
+ }
+
+ set(value: any): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ this.disconnectInstance.setValue(firebase.toValue(value))
+ .addOnSuccessListener(new gmsTasks.OnSuccessListener({
+ onSuccess: () => resolve()
+ }))
+ .addOnFailureListener(new gmsTasks.OnFailureListener({
+ onFailure: exception => reject(exception.getMessage())
+ }));
+ } catch (ex) {
+ console.log("Error in firebase.onDisconnect.set: " + ex);
+ reject(ex);
+ }
+ });
+ }
+
+ setWithPriority(value: any, priority: any): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ this.disconnectInstance.setValue(firebase.toValue(value), priority)
+ .addOnSuccessListener(new gmsTasks.OnSuccessListener({
+ onSuccess: () => resolve()
+ }))
+ .addOnFailureListener(new gmsTasks.OnFailureListener({
+ onFailure: exception => reject(exception.getMessage())
+ }));
+ } catch (ex) {
+ console.log("Error in firebase.onDisconnect.setWithPriority: " + ex);
+ reject(ex);
+ }
+ });
+ }
+
+ update(values: Object, onComplete?: (a: Error | null) => any): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ this.disconnectInstance.updateChildren(firebase.toHashMap(values))
+ .addOnSuccessListener(new gmsTasks.OnSuccessListener({
+ onSuccess: () => resolve()
+ }))
+ .addOnFailureListener(new gmsTasks.OnFailureListener({
+ onFailure: exception => reject(exception.getMessage())
+ }));
+ } catch (ex) {
+ console.log("Error in firebase.onDisconnect.update: " + ex);
+ reject(ex);
+ }
+ });
+ }
+}
+
+firebase.onDisconnect = (path: string): OnDisconnectBase => {
+ if (!firebase.initialized) {
+ console.error("Please run firebase.init() before firebase.onDisconnect()");
+ throw new Error("FirebaseApp is not initialized. Make sure you run firebase.init() first");
+ }
+ const disconnectInstance: com.google.firebase.database.OnDisconnect = firebase.instance.child(path).onDisconnect();
+ return new OnDisconnect(disconnectInstance);
+};
+
+firebase.transaction = (path: string, transactionUpdate: (currentState) => any,
+ onComplete: (a: Error | null, b: boolean, c: DataSnapshot) => Promise) => {
+ return new Promise((resolve, reject) => {
+ if (!firebase.initialized) {
+ console.error("Please run firebase.init() before firebase.transaction()");
+ throw new Error("FirebaseApp is not initialized. Make sure you run firebase.init() first");
+ }
+ const dbRef: com.google.firebase.database.DatabaseReference = firebase.instance.child(path);
+ const handler: com.google.firebase.database.Transaction.Handler = new com.google.firebase.database.Transaction.Handler({
+ doTransaction: (mutableData: com.google.firebase.database.MutableData) => {
+ const desiredValue = transactionUpdate(firebase.toJsObject(mutableData.getValue()));
+ // Java does not have undefined, but web transactions use undefined to detect if an abort() is desired.
+ if (desiredValue === undefined) {
+ // Same problem as iOS. The very first call to runTransaction will see that we get undefined
+ // and immediately abort the transaction which results in us failing to update the value. Subsequent
+ // calls are working fine unlike in iOS which always fail.
+
+ // TLDR: Abort would be ideal, but atm it can result in a failed update (when it shouln't)
+ // Returning success fixes this but makes our { committed: always true }...
+
+ // return com.google.firebase.database.Transaction.abort();
+ return com.google.firebase.database.Transaction.success(mutableData);
+ }
+ mutableData.setValue(firebase.toValue(desiredValue));
+ return com.google.firebase.database.Transaction.success(mutableData);
+
+ },
+ onComplete: (databaseError: com.google.firebase.database.DatabaseError, commited: boolean, snapshot: com.google.firebase.database.DataSnapshot) => {
+ databaseError !== null ? reject(databaseError.getMessage()) :
+ resolve({committed: commited, snapshot: nativeSnapshotToWebSnapshot(snapshot)});
+ }
+ });
+ dbRef.runTransaction(handler);
+ });
+};
+
+// Converts Android DataSnapshot into Web Datasnapshot
+function nativeSnapshotToWebSnapshot(snapshot: com.google.firebase.database.DataSnapshot): DataSnapshot {
+ function forEach(action: (datasnapshot: DataSnapshot) => any): boolean {
+ let innerSnapshot: DataSnapshot;
+ for (let iterator = snapshot.getChildren().iterator(); iterator.hasNext();) {
+ innerSnapshot = nativeSnapshotToWebSnapshot(iterator.next());
+ if (action(innerSnapshot)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return {
+ key: snapshot.getKey(),
+ ref: snapshot.getRef(),
+ child: (path: string) => nativeSnapshotToWebSnapshot(snapshot.child(path)),
+ exists: () => snapshot.exists(),
+ forEach: (func: (datasnapshot) => any) => forEach(func),
+ getPriority: () => firebase.toJsObject(snapshot.getPriority()),
+ hasChild: (path: string) => snapshot.hasChild(path),
+ hasChildren: () => snapshot.hasChildren(),
+ numChildren: () => snapshot.getChildrenCount(),
+ toJSON: () => firebase.toJsObject(snapshot.toString()),
+ val: () => firebase.toJsObject(snapshot.getValue())
+ };
+}
+
+firebase.enableLogging = (logging: boolean, persistent?: boolean) => {
+ if (logging) {
+ com.google.firebase.database.FirebaseDatabase.getInstance().setLogLevel(com.google.firebase.database.Logger.Level.DEBUG);
+ } else {
+ com.google.firebase.database.FirebaseDatabase.getInstance().setLogLevel(com.google.firebase.database.Logger.Level.NONE);
+ }
+};
+
+/***********************************************
+ * END Realtime Database Functions
+ ***********************************************/
+
firebase.sendCrashLog = arg => {
return new Promise((resolve, reject) => {
try {
@@ -1775,7 +2099,7 @@ firebase.invites.getInvitation = () => {
const firebaseDynamicLinks = com.google.firebase.dynamiclinks.FirebaseDynamicLinks.getInstance();
- const onSuccessListener = new com.google.android.gms.tasks.OnSuccessListener({
+ const onSuccessListener = new gmsTasks.OnSuccessListener({
onSuccess: pendingDynamicLinkData => {
if (pendingDynamicLinkData === null) {
reject("Not launched by invitation");
@@ -1796,7 +2120,7 @@ firebase.invites.getInvitation = () => {
}
});
- const onFailureListener = new com.google.android.gms.tasks.OnFailureListener({
+ const onFailureListener = new gmsTasks.OnFailureListener({
onFailure: exception => {
reject(exception.getMessage());
}
@@ -1838,7 +2162,7 @@ class FirestoreWriteBatch implements firestore.WriteBatch {
public commit(): Promise {
return new Promise((resolve, reject) => {
- const onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task => {
if (!task.isSuccessful()) {
const ex = task.getException();
@@ -1870,6 +2194,7 @@ firebase.firestore.runTransaction = (updateFunction: (transaction: firestore.Tra
});
};
+
/*
class FirestoreTransaction implements firestore.Transaction {
@@ -1909,17 +2234,17 @@ firebase.firestore.Transaction = (nativeTransaction: com.google.firebase.firesto
firebase.firestore.runTransaction = (updateFunction: (transaction: firestore.Transaction) => Promise): Promise => {
return new Promise((resolve, reject) => {
- const onSuccessListenert = new com.google.android.gms.tasks.OnSuccessListener({
+ const onSuccessListenert = new gmsTasks.OnSuccessListener({
onSuccess: () => {
const i = 1;
}
});
- const onSuccessListener = new com.google.android.gms.tasks.OnSuccessListener({
+ const onSuccessListener = new gmsTasks.OnSuccessListener({
onSuccess: () => resolve()
});
- const onFailureListener = new com.google.android.gms.tasks.OnFailureListener({
+ const onFailureListener = new gmsTasks.OnFailureListener({
onFailure: exception => reject(exception.getMessage())
});
@@ -1939,6 +2264,22 @@ firebase.firestore.runTransaction = (updateFunction: (transaction: firestore.Tra
};
*/
+firebase.firestore.settings = (settings: firestore.Settings) => {
+ if (typeof (com.google.firebase.firestore) !== "undefined") {
+ try {
+ const builder = new com.google.firebase.firestore.FirebaseFirestoreSettings.Builder();
+ (settings.cacheSizeBytes !== undefined) && builder.setCacheSizeBytes(long(settings.cacheSizeBytes));
+ (settings.ssl !== undefined) && builder.setSslEnabled(settings.ssl);
+ (settings.host !== undefined) && builder.setHost(settings.host);
+ (initializeArguments.persist !== undefined) && builder.setPersistenceEnabled(initializeArguments.persist);
+
+ com.google.firebase.firestore.FirebaseFirestore.getInstance().setFirestoreSettings(builder.build());
+ } catch (err) {
+ console.log("Error in firebase.firestore.settings: " + err);
+ }
+ }
+};
+
firebase.firestore.collection = (collectionPath: string): firestore.CollectionReference => {
try {
@@ -1947,36 +2288,43 @@ firebase.firestore.collection = (collectionPath: string): firestore.CollectionRe
return null;
}
- const db = com.google.firebase.firestore.FirebaseFirestore.getInstance();
- const collectionRef: com.google.firebase.firestore.CollectionReference = db.collection(collectionPath);
-
- return {
- id: collectionRef.getId(),
- doc: (documentPath?: string) => firebase.firestore.doc(collectionPath, documentPath),
- add: document => firebase.firestore.add(collectionPath, document),
- get: () => firebase.firestore.get(collectionPath),
- where: (fieldPath: string, opStr: firestore.WhereFilterOp, value: any) => firebase.firestore.where(collectionPath, fieldPath, opStr, value),
- orderBy: (fieldPath: string, directionStr: firestore.OrderByDirection): firestore.Query => firebase.firestore.orderBy(collectionPath, fieldPath, directionStr, collectionRef),
- limit: (limit: number): firestore.Query => firebase.firestore.limit(collectionPath, limit, collectionRef),
- onSnapshot: (callback: (snapshot: QuerySnapshot) => void) => firebase.firestore.onCollectionSnapshot(collectionRef, callback),
- startAfter: (snapshot: DocumentSnapshot): firestore.Query => firebase.firestore.startAfter(collectionPath, snapshot, collectionRef),
- startAt: (snapshot: DocumentSnapshot): firestore.Query => firebase.firestore.startAt(collectionPath, snapshot, collectionRef),
- endAt: (snapshot: DocumentSnapshot): firestore.Query => firebase.firestore.endAt(collectionPath, snapshot, collectionRef),
- endBefore: (snapshot: DocumentSnapshot): firestore.Query => firebase.firestore.endBefore(collectionPath, snapshot, collectionRef),
- };
+ if (!firebase.initialized) {
+ console.log("Please run firebase.init() before firebase.firestore.collection()");
+ return null;
+ }
+ const db = com.google.firebase.firestore.FirebaseFirestore.getInstance();
+ return firebase.firestore._getCollectionReference(db.collection(collectionPath));
} catch (ex) {
console.log("Error in firebase.firestore.collection: " + ex);
return null;
}
};
-firebase.firestore.onDocumentSnapshot = (docRef: com.google.firebase.firestore.DocumentReference, callback: (doc: DocumentSnapshot) => void): () => void => {
- const listener = docRef.addSnapshotListener(new com.google.firebase.firestore.EventListener({
- onEvent: ((snapshot: com.google.firebase.firestore.DocumentSnapshot, exception) => {
- if (exception === null) {
- callback(new DocumentSnapshot(snapshot));
+firebase.firestore.onDocumentSnapshot = (docRef: com.google.firebase.firestore.DocumentReference, optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: DocumentSnapshot) => void), callbackOrOnError: (docOrError: DocumentSnapshot | Error) => void, onError: (error: Error) => void): () => void => {
+ let options = com.google.firebase.firestore.MetadataChanges.EXCLUDE;
+ let onNextCallback: (snapshot: DocumentSnapshot) => void;
+ let onErrorCallback: (error: Error) => void;
+
+ if ((typeof optionsOrCallback) === "function") {
+ onNextCallback = <(snapshot: DocumentSnapshot) => void>optionsOrCallback;
+ onErrorCallback = callbackOrOnError;
+ } else {
+ onNextCallback = callbackOrOnError;
+ onErrorCallback = onError;
+ }
+ if ((optionsOrCallback).includeMetadataChanges) {
+ options = com.google.firebase.firestore.MetadataChanges.INCLUDE;
+ }
+
+ const listener = docRef.addSnapshotListener(options, new com.google.firebase.firestore.EventListener({
+ onEvent: ((snapshot: com.google.firebase.firestore.DocumentSnapshot, exception: com.google.firebase.firestore.FirebaseFirestoreException) => {
+ if (exception !== null) {
+ const error = "onDocumentSnapshot error code: " + exception.getCode();
+ onErrorCallback && onErrorCallback(new Error(error));
+ return;
}
+ onNextCallback && onNextCallback(new DocumentSnapshot(snapshot));
})
})
);
@@ -1984,34 +2332,80 @@ firebase.firestore.onDocumentSnapshot = (docRef: com.google.firebase.firestore.D
return () => listener.remove();
};
-firebase.firestore.onCollectionSnapshot = (colRef: com.google.firebase.firestore.CollectionReference, callback: (snapshot: QuerySnapshot) => void): () => void => {
- const listener = colRef.addSnapshotListener(new com.google.firebase.firestore.EventListener({
- onEvent: ((snapshot: com.google.firebase.firestore.QuerySnapshot, exception: com.google.firebase.firestore.FirebaseFirestoreException) => {
- if (exception !== null) {
- console.error('onCollectionSnapshot error code: ' + exception.getCode());
- return;
- }
+firebase.firestore.onCollectionSnapshot = (colRef: com.google.firebase.firestore.CollectionReference, optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: QuerySnapshot) => void), callbackOrOnError: (snapshotOrError: QuerySnapshot | Error) => void, onError?: (error: Error) => void): () => void => {
+ let options = com.google.firebase.firestore.MetadataChanges.EXCLUDE;
+ let onNextCallback: (snapshot: QuerySnapshot) => void;
+ let onErrorCallback: (error: Error) => void;
- callback(new QuerySnapshot(snapshot));
- })
- })
- );
+ // If we passed in an onNext for the first parameter, the next parameter would be onError if provided
+ // If options was the first parameter then the next parameter would be onNext if provided
+ if ((typeof optionsOrCallback) === "function") {
+ onNextCallback = <(snapshot: QuerySnapshot) => void>optionsOrCallback;
+ onErrorCallback = callbackOrOnError;
+ } else {
+ onNextCallback = callbackOrOnError; // Can be undefined if callback was not provided
+ onErrorCallback = onError; // Can be undefined if onError was not provided
+ }
+ if ((optionsOrCallback).includeMetadataChanges) {
+ options = com.google.firebase.firestore.MetadataChanges.INCLUDE;
+ }
+ const listener = colRef.addSnapshotListener(options, new com.google.firebase.firestore.EventListener({
+ onEvent: ((snapshot: com.google.firebase.firestore.QuerySnapshot, exception: com.google.firebase.firestore.FirebaseFirestoreException) => {
+ if (exception !== null) {
+ const error = "onCollectionSnapshot error code: " + exception.getCode();
+ onErrorCallback && onErrorCallback(new Error(error));
+ return;
+ }
+ onNextCallback && onNextCallback(new QuerySnapshot(snapshot));
+ })
+ }));
return () => listener.remove();
};
-firebase.firestore._getDocumentReference = (javaObj: JDocumentReference, collectionPath, documentPath): firestore.DocumentReference => {
+firebase.firestore._getDocumentReference = (docRef?: JDocumentReference): firestore.DocumentReference => {
+ if (!docRef) {
+ return null;
+ }
+
+ const collectionPath = docRef.getParent().getPath();
+
return {
discriminator: "docRef",
- id: javaObj.getId(),
- path: javaObj.getPath(),
- collection: cp => firebase.firestore.collection(`${collectionPath}/${documentPath}/${cp}`),
- set: (data: any, options?: firestore.SetOptions) => firebase.firestore.set(collectionPath, javaObj.getId(), data, options),
- get: () => firebase.firestore.getDocument(collectionPath, javaObj.getId()),
- update: (data: any) => firebase.firestore.update(collectionPath, javaObj.getId(), data),
- delete: () => firebase.firestore.delete(collectionPath, javaObj.getId()),
- onSnapshot: (callback: (doc: DocumentSnapshot) => void) => firebase.firestore.onDocumentSnapshot(javaObj, callback),
- android: javaObj
+ id: docRef.getId(),
+ parent: firebase.firestore._getCollectionReference(docRef.getParent()),
+ path: docRef.getPath(),
+ collection: cp => firebase.firestore.collection(`${collectionPath}/${docRef.getId()}/${cp}`),
+ set: (data: any, options?: firestore.SetOptions) => firebase.firestore.set(collectionPath, docRef.getId(), data, options),
+ get: () => firebase.firestore.getDocument(collectionPath, docRef.getId()),
+ update: (data: any) => firebase.firestore.update(collectionPath, docRef.getId(), data),
+ delete: () => firebase.firestore.delete(collectionPath, docRef.getId()),
+ onSnapshot: (optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: DocumentSnapshot) => void), callbackOrOnError?: (docOrError: DocumentSnapshot | Error) => void, onError?: (error: Error) => void) => firebase.firestore.onDocumentSnapshot(docRef, optionsOrCallback, callbackOrOnError, onError),
+ android: docRef
+ };
+};
+
+firebase.firestore._getCollectionReference = (colRef?: JCollectionReference): firestore.CollectionReference => {
+ if (!colRef) {
+ return null;
+ }
+
+ const collectionPath = colRef.getPath();
+
+ return {
+ id: colRef.getId(),
+ parent: firebase.firestore._getDocumentReference(colRef.getParent()),
+ doc: (documentPath?: string) => firebase.firestore.doc(collectionPath, documentPath),
+ add: document => firebase.firestore.add(collectionPath, document),
+ get: () => firebase.firestore.get(collectionPath),
+ where: (fieldPath: string, opStr: firestore.WhereFilterOp, value: any) => firebase.firestore.where(collectionPath, fieldPath, opStr, value),
+ orderBy: (fieldPath: string, directionStr: firestore.OrderByDirection): firestore.Query => firebase.firestore.orderBy(collectionPath, fieldPath, directionStr, colRef),
+ limit: (limit: number): firestore.Query => firebase.firestore.limit(collectionPath, limit, colRef),
+ onSnapshot: (optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: QuerySnapshot) => void), callbackOrOnError?: (snapshotOrError: QuerySnapshot | Error) => void, onError?: (error: Error) => void) => firebase.firestore.onCollectionSnapshot(colRef, optionsOrCallback, callbackOrOnError, onError),
+ startAfter: (snapshot: DocumentSnapshot): firestore.Query => firebase.firestore.startAfter(collectionPath, snapshot, colRef),
+ startAt: (snapshot: DocumentSnapshot): firestore.Query => firebase.firestore.startAt(collectionPath, snapshot, colRef),
+ endAt: (snapshot: DocumentSnapshot): firestore.Query => firebase.firestore.endAt(collectionPath, snapshot, colRef),
+ endBefore: (snapshot: DocumentSnapshot): firestore.Query => firebase.firestore.endBefore(collectionPath, snapshot, colRef),
};
};
@@ -2022,10 +2416,15 @@ firebase.firestore.doc = (collectionPath: string, documentPath?: string): firest
return null;
}
+ if (!firebase.initialized) {
+ console.log("Please run firebase.init() before firebase.firestore.doc()");
+ return null;
+ }
+
const db = com.google.firebase.firestore.FirebaseFirestore.getInstance();
const colRef: com.google.firebase.firestore.CollectionReference = db.collection(collectionPath);
const docRef: com.google.firebase.firestore.DocumentReference = documentPath ? colRef.document(documentPath) : colRef.document();
- return firebase.firestore._getDocumentReference(docRef, collectionPath, documentPath);
+ return firebase.firestore._getDocumentReference(docRef);
} catch (ex) {
console.log("Error in firebase.firestore.doc: " + ex);
return null;
@@ -2039,9 +2438,7 @@ firebase.firestore.docRef = (documentPath: string): firestore.DocumentReference
}
const db: com.google.firebase.firestore.FirebaseFirestore = com.google.firebase.firestore.FirebaseFirestore.getInstance();
- const docRef: JDocumentReference = db.document(documentPath);
-
- return convertDocRef(docRef);
+ return firebase.firestore._getDocumentReference(db.document(documentPath));
};
firebase.firestore.add = (collectionPath: string, document: any): Promise => {
@@ -2055,23 +2452,13 @@ firebase.firestore.add = (collectionPath: string, document: any): Promise {
- resolve({
- discriminator: "docRef",
- id: docRef.getId(),
- path: docRef.getPath(),
- collection: cp => firebase.firestore.collection(cp),
- set: (data: any, options?: firestore.SetOptions) => firebase.firestore.set(collectionPath, docRef.getId(), data, options),
- get: () => firebase.firestore.getDocument(collectionPath, docRef.getId()),
- update: (data: any) => firebase.firestore.update(collectionPath, docRef.getId(), data),
- delete: () => firebase.firestore.delete(collectionPath, docRef.getId()),
- onSnapshot: (callback: (doc: DocumentSnapshot) => void) => firebase.firestore.onDocumentSnapshot(docRef, callback)
- });
+ resolve(firebase.firestore._getDocumentReference(docRef));
}
});
- const onFailureListener = new com.google.android.gms.tasks.OnFailureListener({
+ const onFailureListener = new gmsTasks.OnFailureListener({
onFailure: exception => reject(exception.getMessage())
});
@@ -2098,11 +2485,11 @@ firebase.firestore.set = (collectionPath: string, documentPath: string, document
const db = com.google.firebase.firestore.FirebaseFirestore.getInstance();
- const onSuccessListener = new com.google.android.gms.tasks.OnSuccessListener({
+ const onSuccessListener = new gmsTasks.OnSuccessListener({
onSuccess: () => resolve()
});
- const onFailureListener = new com.google.android.gms.tasks.OnFailureListener({
+ const onFailureListener = new gmsTasks.OnFailureListener({
onFailure: exception => reject(exception.getMessage())
});
@@ -2137,11 +2524,11 @@ firebase.firestore.update = (collectionPath: string, documentPath: string, docum
const db = com.google.firebase.firestore.FirebaseFirestore.getInstance();
- const onSuccessListener = new com.google.android.gms.tasks.OnSuccessListener({
+ const onSuccessListener = new gmsTasks.OnSuccessListener({
onSuccess: () => resolve()
});
- const onFailureListener = new com.google.android.gms.tasks.OnFailureListener({
+ const onFailureListener = new gmsTasks.OnFailureListener({
onFailure: exception => reject(exception.getMessage())
});
@@ -2169,11 +2556,11 @@ firebase.firestore.delete = (collectionPath: string, documentPath: string): Prom
const db = com.google.firebase.firestore.FirebaseFirestore.getInstance();
- const onSuccessListener = new com.google.android.gms.tasks.OnSuccessListener({
+ const onSuccessListener = new gmsTasks.OnSuccessListener({
onSuccess: () => resolve()
});
- const onFailureListener = new com.google.android.gms.tasks.OnFailureListener({
+ const onFailureListener = new gmsTasks.OnFailureListener({
onFailure: exception => reject(exception.getMessage())
});
@@ -2201,29 +2588,21 @@ firebase.firestore.getCollection = (collectionPath: string): Promise {
if (!task.isSuccessful()) {
const ex = task.getException();
reject(ex && ex.getReason ? ex.getReason() : ex);
} else {
const result: com.google.firebase.firestore.QuerySnapshot = task.getResult();
-
resolve(new QuerySnapshot(result));
}
}
});
- const onFailureListener = new com.google.android.gms.tasks.OnFailureListener({
- onFailure: exception => {
- reject(exception.getMessage());
- }
- });
-
db.collection(collectionPath)
.get()
- .addOnCompleteListener(onCompleteListener)
- .addOnFailureListener(onFailureListener);
+ .addOnCompleteListener(onCompleteListener);
} catch (ex) {
console.log("Error in firebase.firestore.getCollection: " + ex);
@@ -2247,7 +2626,7 @@ firebase.firestore.getDocument = (collectionPath: string, documentPath: string):
const db = com.google.firebase.firestore.FirebaseFirestore.getInstance();
- const onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task => {
if (!task.isSuccessful()) {
const ex = task.getException();
@@ -2259,17 +2638,10 @@ firebase.firestore.getDocument = (collectionPath: string, documentPath: string):
}
});
- const onFailureListener = new com.google.android.gms.tasks.OnFailureListener({
- onFailure: exception => {
- reject(exception.getMessage());
- }
- });
-
db.collection(collectionPath)
.document(documentPath)
.get()
- .addOnCompleteListener(onCompleteListener)
- .addOnFailureListener(onFailureListener);
+ .addOnCompleteListener(onCompleteListener);
} catch (ex) {
console.log("Error in firebase.firestore.getDocument: " + ex);
@@ -2281,14 +2653,13 @@ firebase.firestore.getDocument = (collectionPath: string, documentPath: string):
firebase.firestore._getQuery = (collectionPath: string, query: com.google.firebase.firestore.Query): firestore.Query => {
return {
get: () => new Promise((resolve, reject) => {
- const onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
+ const onCompleteListener = new gmsTasks.OnCompleteListener({
onComplete: task => {
if (!task.isSuccessful()) {
const ex = task.getException();
reject(ex && ex.getReason ? ex.getReason() : ex);
} else {
const result: com.google.firebase.firestore.QuerySnapshot = task.getResult();
-
resolve(new QuerySnapshot(result));
}
}
@@ -2298,7 +2669,7 @@ firebase.firestore._getQuery = (collectionPath: string, query: com.google.fireba
where: (fp: string, os: firestore.WhereFilterOp, v: any): firestore.Query => firebase.firestore.where(collectionPath, fp, os, v, query),
orderBy: (fp: string, directionStr: firestore.OrderByDirection): firestore.Query => firebase.firestore.orderBy(collectionPath, fp, directionStr, query),
limit: (limit: number): firestore.Query => firebase.firestore.limit(collectionPath, limit, query),
- onSnapshot: (callback: (snapshot: QuerySnapshot) => void) => firebase.firestore.onCollectionSnapshot(query, callback),
+ onSnapshot: (optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: QuerySnapshot) => void), callbackOrOnError?: (snapshotOrError: QuerySnapshot | Error) => void, onError?: (error: Error) => void) => firebase.firestore.onCollectionSnapshot(query, optionsOrCallback, callbackOrOnError, onError),
startAfter: (snapshot: DocumentSnapshot) => firebase.firestore.startAfter(collectionPath, snapshot, query),
startAt: (snapshot: DocumentSnapshot) => firebase.firestore.startAt(collectionPath, snapshot, query),
endAt: (snapshot: DocumentSnapshot) => firebase.firestore.endAt(collectionPath, snapshot, query),
@@ -2369,23 +2740,7 @@ firebase.firestore.endBefore = (collectionPath: string, snapshot: DocumentSnapsh
};
export type JDocumentReference = com.google.firebase.firestore.DocumentReference;
-
-function convertDocRef(docRef: JDocumentReference): firestore.DocumentReference {
- const collectionPath = docRef.getParent().getPath();
-
- return {
- discriminator: "docRef",
- id: docRef.getId(),
- path: docRef.getPath(),
- collection: cp => firebase.firestore.collection(`${collectionPath}/${docRef.getId()}/${cp}`),
- set: (data: any, options?: firestore.SetOptions) => firebase.firestore.set(collectionPath, docRef.getId(), data, options),
- get: () => firebase.firestore.getDocument(collectionPath, docRef.getId()),
- update: (data: any) => firebase.firestore.update(collectionPath, docRef.getId(), data),
- delete: () => firebase.firestore.delete(collectionPath, docRef.getId()),
- onSnapshot: (callback: (doc: DocumentSnapshot) => void) => firebase.firestore.onDocumentSnapshot(docRef, callback),
- android: docRef
- };
-}
+export type JCollectionReference = com.google.firebase.firestore.CollectionReference;
function convertDocChangeType(type: com.google.firebase.firestore.DocumentChange.Type) {
switch (type) {
@@ -2410,6 +2765,11 @@ export class QuerySnapshot implements firestore.QuerySnapshot {
constructor(private snapshot: com.google.firebase.firestore.QuerySnapshot) {
}
+ metadata = {
+ fromCache: this.snapshot.getMetadata().isFromCache(),
+ hasPendingWrites: this.snapshot.getMetadata().hasPendingWrites()
+ };
+
get docs(): firestore.QueryDocumentSnapshot[] {
const getSnapshots = () => {
const docSnapshots: firestore.QueryDocumentSnapshot[] = [];
diff --git a/src/firebase.d.ts b/src/firebase.d.ts
index 742d10c7..c6f0b392 100644
--- a/src/firebase.d.ts
+++ b/src/firebase.d.ts
@@ -39,6 +39,16 @@ export enum LoginType {
EMAIL_LINK
}
+export enum LogComplexEventTypeParameter {
+ STRING,
+ INT,
+ FLOAT,
+ DOUBLE,
+ LONG,
+ ARRAY,
+ BOOLEAN
+}
+
/**
* The allowed values for QueryOptions.orderBy.type.
*/
@@ -194,6 +204,15 @@ export interface GetAuthTokenOptions {
forceRefresh?: boolean;
}
+export interface IdTokenResult {
+ token: string;
+ claims: { [key: string]: any; };
+ signInProvider: string;
+ expirationTime: number;
+ issuedAtTime: number;
+ authTime: number;
+}
+
export interface Provider {
id: string;
}
@@ -306,6 +325,20 @@ export interface ReauthenticateOptions {
password?: string;
}
+type ActionCodeSettings = {
+ url: string;
+ handleCodeInApp?: boolean;
+ android?: {
+ installApp?: boolean;
+ minimumVersion?: string;
+ packageName: string;
+ };
+ iOS?: {
+ bundleId: string;
+ dynamicLinkDomain?: string;
+ };
+};
+
/**
* The returned object from the login function.
*/
@@ -321,8 +354,15 @@ export interface User {
profileImageURL?: string;
metadata: UserMetadata;
additionalUserInfo?: AdditionalUserInfo;
+
/** iOS only */
refreshToken?: string;
+
+ getIdToken(forceRefresh?: boolean): Promise;
+
+ getIdTokenResult(forceRefresh?: boolean): Promise;
+
+ sendEmailVerification(actionCodeSettings?: ActionCodeSettings): Promise;
}
/**
@@ -374,13 +414,6 @@ export interface UpdateProfileOptions {
photoURL?: string;
}
-/**
- * The options object passed into the resetPassword function.
- */
-export interface ResetPasswordOptions {
- email: string;
-}
-
/**
* The returned object in the callback handlers
* of the addChildEventListener and addValueEventListener functions.
@@ -391,13 +424,12 @@ export interface FBData {
value: any;
}
-/**
- * The options object passed into the changePassword function.
- */
-export interface ChangePasswordOptions {
- email: string;
- oldPassword: string;
- newPassword: string;
+export interface FBDataSingleEvent extends FBData {
+ children?: Array;
+}
+
+export interface FBErrorData {
+ error: string;
}
export interface AuthStateData {
@@ -508,6 +540,54 @@ export interface SendCrashLogOptions {
export function init(options?: InitOptions): Promise;
// Database
+export interface OnDisconnect {
+ cancel(): Promise;
+
+ remove(): Promise;
+
+ set(value: any): Promise;
+
+ setWithPriority(
+ value: any,
+ priority: number | string
+ ): Promise;
+
+ update(values: Object): Promise;
+}
+
+export interface DataSnapshot {
+ key: string;
+ ref: any; // TODO: Type it so that it returns a databaseReference.
+ child(path: string): DataSnapshot;
+
+ exists(): boolean;
+
+ forEach(action: (snapshot: DataSnapshot) => any): boolean;
+
+ getPriority(): string | number | null;
+
+ hasChild(path: string): boolean;
+
+ hasChildren(): boolean;
+
+ numChildren(): number;
+
+ toJSON(): Object;
+
+ val(): any;
+}
+
+export interface FirebaseQueryResult {
+ type: string;
+ key: string;
+ value: any;
+}
+
+export type Unsubscribe = () => void;
+
+export function transaction(path: string, transactionUpdate: (a: any) => any,
+ onComplete?: (error: Error | null, committed: boolean, dataSnapshot: DataSnapshot) => any): Promise;
+
export function push(path: string, value: any): Promise;
export function getValue(path: string): Promise;
@@ -518,7 +598,7 @@ export function update(path: string, value: any): Promise;
export function remove(path: string): Promise;
-export function query(onValueEvent: (data: FBData) => void, path: string, options: QueryOptions): Promise;
+export function query(onValueEvent: (data: FBData | FBErrorData) => void, path: string, options: QueryOptions): Promise;
export function addChildEventListener(onChildEvent: (data: FBData) => void, path: string): Promise;
@@ -526,6 +606,10 @@ export function addValueEventListener(onValueEvent: (data: FBData) => void, path
export function removeEventListeners(listeners: Array, path: string): Promise;
+export function onDisconnect(path: string): OnDisconnect;
+
+export function enableLogging(logger?: boolean | ((a: string) => any), persistent?: boolean);
+
/**
* Tells the client to keep its local cache in sync with the server automatically.
*/
@@ -617,10 +701,79 @@ export namespace firestore {
export function GeoPoint(latitude: number, longitude: number): GeoPoint;
+ export interface Settings {
+ /** The hostname to connect to. */
+ host?: string;
+ /** Whether to use SSL when connecting. */
+ ssl?: boolean;
+
+ /**
+ * Specifies whether to use `Timestamp` objects for timestamp fields in
+ * `DocumentSnapshot`s. This is enabled by default and should not be
+ * disabled.
+ *
+ * Previously, Firestore returned timestamp fields as `Date` but `Date`
+ * only supports millisecond precision, which leads to truncation and
+ * causes unexpected behavior when using a timestamp from a snapshot as a
+ * part of a subsequent query.
+ *
+ * So now Firestore returns `Timestamp` values instead of `Date`, avoiding
+ * this kind of problem.
+ *
+ * To opt into the old behavior of returning `Date` objects, you can
+ * temporarily set `timestampsInSnapshots` to false.
+ *
+ * @deprecated This setting will be removed in a future release. You should
+ * update your code to expect `Timestamp` objects and stop using the
+ * `timestampsInSnapshots` setting.
+ */
+ timestampsInSnapshots?: boolean;
+
+ /**
+ * An approximate cache size threshold for the on-disk data. If the cache grows beyond this
+ * size, Firestore will start removing data that hasn't been recently used. The size is not a
+ * guarantee that the cache will stay below that size, only that if the cache exceeds the given
+ * size, cleanup will be attempted.
+ *
+ * The default value is 40 MB. The threshold must be set to at least 1 MB, and can be set to
+ * CACHE_SIZE_UNLIMITED to disable garbage collection.
+ */
+ cacheSizeBytes?: number;
+ }
+
+ /**
+ * Specifies custom settings to be used to configure the `Firestore`
+ * instance. Must be set before invoking any other methods.
+ *
+ * @param settings The settings to use.
+ */
+ export function settings(settings: Settings): void;
+
export interface SetOptions {
merge?: boolean;
}
+ export interface SnapshotMetadata {
+ /**
+ * True if the snapshot contains the result of local writes (e.g. set() or
+ * update() calls) that have not yet been committed to the backend.
+ * If your listener has opted into metadata updates (via
+ * `DocumentListenOptions` or `QueryListenOptions`) you will receive another
+ * snapshot with `hasPendingWrites` equal to false once the writes have been
+ * committed to the backend.
+ */
+ readonly hasPendingWrites: boolean;
+
+ /**
+ * True if the snapshot was created from cached data rather than
+ * guaranteed up-to-date server data. If your listener has opted into
+ * metadata updates (via `DocumentListenOptions` or `QueryListenOptions`)
+ * you will receive another snapshot with `fromCache` equal to false once
+ * the client has received up-to-date data from the backend.
+ */
+ readonly fromCache: boolean;
+ }
+
export interface DocumentSnapshot {
ios?: any;
/* FIRDocumentSnapshot */
@@ -630,22 +783,48 @@ export namespace firestore {
exists: boolean;
ref: DocumentReference;
+ /**
+ * Included when includeMetadataChanges is true.
+ */
+ readonly metadata?: SnapshotMetadata;
+
data(): DocumentData;
}
+ export interface SnapshotListenOptions {
+ /**
+ * Include a change even if only the metadata of the query or of a document changed.
+ * Default false.
+ */
+ readonly includeMetadataChanges?: boolean;
+ }
+
export interface DocumentReference {
- discriminator: "docRef";
- id: string;
- path: string;
+ readonly discriminator: "docRef";
+
+ readonly id: string;
+
+ /**
+ * A reference to the Collection to which this DocumentReference belongs.
+ */
+ readonly parent: CollectionReference;
+
+ readonly path: string;
+
collection: (collectionPath: string) => CollectionReference;
+
set: (document: any, options?: SetOptions) => Promise;
+
get: () => Promise;
+
update: (document: any) => Promise;
+
delete: () => Promise;
- onSnapshot(callback: (doc: DocumentSnapshot) => void): () => void;
+ onSnapshot(optionsOrCallback: SnapshotListenOptions | ((snapshot: DocumentSnapshot) => void), callbackOrOnError?: (snapshot: DocumentSnapshot | Error) => void, onError?: (error: Error) => void): () => void;
android?: any;
+
ios?: any;
}
@@ -658,7 +837,7 @@ export namespace firestore {
limit(limit: number): Query;
- onSnapshot(callback: (snapshot: QuerySnapshot) => void): () => void;
+ onSnapshot(optionsOrCallback: SnapshotListenOptions | ((snapshot: QuerySnapshot) => void), callbackOrOnError?: (snapshotOrError: QuerySnapshot | Error) => void, onError?: (error: Error) => void): () => void;
startAt(snapshot: DocumentSnapshot): Query;
@@ -670,7 +849,12 @@ export namespace firestore {
}
export interface CollectionReference extends Query {
- id: string;
+ readonly id: string;
+
+ /**
+ * A reference to the containing Document if this is a subcollection, else null.
+ */
+ readonly parent: DocumentReference | null;
doc(documentPath?: string): DocumentReference;
@@ -797,6 +981,11 @@ export namespace firestore {
docSnapshots: firestore.DocumentSnapshot[];
docs: firestore.QueryDocumentSnapshot[];
+ /**
+ * Included when includeMetadataChanges is true.
+ */
+ readonly metadata: SnapshotMetadata;
+
docChanges(options?: SnapshotListenOptions): DocumentChange[];
forEach(callback: (result: DocumentSnapshot) => void, thisArg?: any): void;
@@ -836,15 +1025,17 @@ export function reauthenticate(options: ReauthenticateOptions): Promise;
export function reloadUser(): Promise;
-export function getAuthToken(option: GetAuthTokenOptions): Promise;
+export function getAuthToken(option: GetAuthTokenOptions): Promise;
export function logout(): Promise;
+export function unlink(providerId: string): Promise;
+
export function fetchProvidersForEmail(email: string): Promise>;
export function fetchSignInMethodsForEmail(email: string): Promise>;
-export function sendEmailVerification(): Promise;
+export function sendEmailVerification(actionCodeSettings?: ActionCodeSettings): Promise;
export function createUser(options: CreateUserOptions): Promise;
@@ -852,9 +1043,11 @@ export function deleteUser(): Promise;
export function updateProfile(options: UpdateProfileOptions): Promise;
-export function resetPassword(options: ResetPasswordOptions): Promise;
+export function sendPasswordResetEmail(email: string): Promise;
+
+export function updateEmail(newEmail: string): Promise;
-export function changePassword(options: ChangePasswordOptions): Promise;
+export function updatePassword(newPassword: string): Promise;
export function addAuthStateListener(listener: AuthStateChangeListener): boolean;
diff --git a/src/firebase.ios.ts b/src/firebase.ios.ts
index b080b88f..5a4f828c 100755
--- a/src/firebase.ios.ts
+++ b/src/firebase.ios.ts
@@ -1,3 +1,14 @@
+import * as application from "tns-core-modules/application/application";
+import {
+ ActionCodeSettings,
+ DataSnapshot,
+ FBDataSingleEvent,
+ firestore,
+ GetAuthTokenOptions,
+ IdTokenResult,
+ OnDisconnect as OnDisconnectBase, QueryOptions,
+ User
+} from "./firebase";
import {
DocumentSnapshot as DocumentSnapshotBase,
FieldValue,
@@ -5,11 +16,8 @@ import {
GeoPoint,
isDocumentReference
} from "./firebase-common";
-import * as firebaseMessaging from "./messaging/messaging";
-import * as application from "tns-core-modules/application/application";
-import { ios as iOSUtils } from "tns-core-modules/utils/utils";
import * as firebaseFunctions from './functions/functions';
-import { firestore, User } from "./firebase";
+import * as firebaseMessaging from "./messaging/messaging";
import { firebaseUtils } from "./utils";
firebase._gIDAuthentication = null;
@@ -19,11 +27,18 @@ firebase._configured = false;
const useExternalPushProvider = NSBundle.mainBundle.infoDictionary.objectForKey("UseExternalPushProvider") === true;
+let initializeArguments: any;
+
class DocumentSnapshot extends DocumentSnapshotBase {
ios: FIRDocumentSnapshot;
- constructor(snapshot: FIRDocumentSnapshot) {
- super(snapshot.documentID, snapshot.exists, firebaseUtils.toJsObject(snapshot.data()), convertDocRef(snapshot.reference));
+ metadata = {
+ fromCache: this.snapshot.metadata.fromCache,
+ hasPendingWrites: this.snapshot.metadata.pendingWrites
+ };
+
+ constructor(public snapshot: FIRDocumentSnapshot) {
+ super(snapshot.documentID, snapshot.exists, firebaseUtils.toJsObject(snapshot.data()), firebase.firestore._getDocumentReference(snapshot.reference));
this.ios = snapshot;
}
}
@@ -52,7 +67,8 @@ firebase.addAppDelegateMethods = appDelegate => {
FIRApp.configure();
}
}
- // If the app was terminated and the iOS is launching it in result of push notification tapped by the user, this will hold the notification data.
+
+ // If the app was terminated and iOS is launching it in result of a push notification tapped by the user, this will hold the notification data.
if (launchOptions) {
const remoteNotification = launchOptions.objectForKey(UIApplicationLaunchOptionsRemoteNotificationKey);
if (remoteNotification) {
@@ -94,7 +110,6 @@ firebase.addAppDelegateMethods = appDelegate => {
if (typeof (FIRDynamicLink) !== "undefined") {
const dynamicLink = FIRDynamicLinks.dynamicLinks().dynamicLinkFromCustomSchemeURL(url);
if (dynamicLink) {
- console.log(">>> dynamicLink.url.absoluteString: " + dynamicLink.url.absoluteString);
firebase._cachedDynamicLink = {
url: dynamicLink.url.absoluteString,
// matchConfidence: dynamicLink.matchConfidence,
@@ -110,6 +125,7 @@ firebase.addAppDelegateMethods = appDelegate => {
if (typeof (FBSDKApplicationDelegate) !== "undefined" || typeof (GIDSignIn) !== "undefined" || typeof (FIRDynamicLink) !== "undefined") {
appDelegate.prototype.applicationOpenURLOptions = (application, url, options) => {
+
let result = false;
if (typeof (FBSDKApplicationDelegate) !== "undefined") {
result = FBSDKApplicationDelegate.sharedInstance().applicationOpenURLSourceApplicationAnnotation(
@@ -129,24 +145,21 @@ firebase.addAppDelegateMethods = appDelegate => {
if (typeof (FIRDynamicLink) !== "undefined") {
const dynamicLinks: FIRDynamicLinks = FIRDynamicLinks.dynamicLinks();
const dynamicLink: FIRDynamicLink = dynamicLinks.dynamicLinkFromCustomSchemeURL(url);
- if (dynamicLink) {
- if (dynamicLink.url !== null) {
- console.log(">>> dynamicLink.url.absoluteString: " + dynamicLink.url.absoluteString);
- if (firebase._dynamicLinkCallback) {
- firebase._dynamicLinkCallback({
- url: dynamicLink.url.absoluteString,
- // matchConfidence: dynamicLink.matchConfidence,
- minimumAppVersion: dynamicLink.minimumAppVersion
- });
- } else {
- firebase._cachedDynamicLink = {
- url: dynamicLink.url.absoluteString,
- // matchConfidence: dynamicLink.matchConfidence,
- minimumAppVersion: dynamicLink.minimumAppVersion
- };
- }
- result = true;
+ if (dynamicLink && dynamicLink.url !== null) {
+ if (firebase._dynamicLinkCallback) {
+ firebase._dynamicLinkCallback({
+ url: dynamicLink.url.absoluteString,
+ // matchConfidence: dynamicLink.matchConfidence,
+ minimumAppVersion: dynamicLink.minimumAppVersion
+ });
+ } else {
+ firebase._cachedDynamicLink = {
+ url: dynamicLink.url.absoluteString,
+ // matchConfidence: dynamicLink.matchConfidence,
+ minimumAppVersion: dynamicLink.minimumAppVersion
+ };
}
+ result = true;
}
}
return result;
@@ -305,6 +318,7 @@ if (typeof (FIRMessaging) !== "undefined" || useExternalPushProvider) {
firebaseMessaging.prepAppDelegate();
}
+// This breaks in-app-messaging :(
function getAppDelegate() {
// Play nice with other plugins by not completely ignoring anything already added to the appdelegate
if (application.ios.delegate === undefined) {
@@ -332,7 +346,9 @@ firebase.init = arg => {
return new Promise((resolve, reject) => {
if (firebase.initialized) {
reject("Firebase already initialized");
+ return;
}
+
firebase.initialized = true;
try {
@@ -347,10 +363,11 @@ firebase.init = arg => {
}
arg = arg || {};
+ initializeArguments = arg;
// if deeplinks are used, then for this scheme to work the use must have added the bundle as a scheme to their plist (this is in our docs)
if (FIROptions.defaultOptions() !== null) {
- FIROptions.defaultOptions().deepLinkURLScheme = iOSUtils.getter(NSBundle, NSBundle.mainBundle).bundleIdentifier;
+ FIROptions.defaultOptions().deepLinkURLScheme = NSBundle.mainBundle.bundleIdentifier;
}
FIRAnalyticsConfiguration.sharedInstance().setAnalyticsCollectionEnabled(arg.analyticsCollectionEnabled !== false);
@@ -369,16 +386,12 @@ firebase.init = arg => {
}
if (typeof (FIRFirestore) !== "undefined") {
- // fix a deprecation warning
- const fIRFirestoreSettings = FIRFirestoreSettings.new();
- fIRFirestoreSettings.timestampsInSnapshotsEnabled = true;
-
// Firestore has offline persistence enabled by default
if (arg.persist === false) {
+ const fIRFirestoreSettings = FIRFirestoreSettings.new();
fIRFirestoreSettings.persistenceEnabled = false;
+ FIRFirestore.firestore().settings = fIRFirestoreSettings;
}
-
- FIRFirestore.firestore().settings = fIRFirestoreSettings;
}
if (typeof (FIRAuth) !== "undefined") {
@@ -534,7 +547,7 @@ firebase.getCurrentUser = arg => {
});
};
-firebase.sendEmailVerification = () => {
+firebase.sendEmailVerification = (actionCodeSettings?: ActionCodeSettings) => {
return new Promise((resolve, reject) => {
try {
const fAuth = FIRAuth.auth();
@@ -552,7 +565,29 @@ firebase.sendEmailVerification = () => {
resolve(true);
}
};
- user.sendEmailVerificationWithCompletion(onCompletion);
+ if (actionCodeSettings) {
+ const firActionCodeSettings = FIRActionCodeSettings.new();
+ if (actionCodeSettings.handleCodeInApp !== undefined) {
+ firActionCodeSettings.handleCodeInApp = actionCodeSettings.handleCodeInApp;
+ }
+ if (actionCodeSettings.url) {
+ firActionCodeSettings.URL = NSURL.URLWithString(actionCodeSettings.url);
+ }
+ if (actionCodeSettings.iOS) {
+ if (actionCodeSettings.iOS.bundleId) {
+ firActionCodeSettings.setIOSBundleID(actionCodeSettings.iOS.bundleId);
+ }
+ if (actionCodeSettings.iOS.dynamicLinkDomain) {
+ firActionCodeSettings.dynamicLinkDomain = actionCodeSettings.iOS.dynamicLinkDomain;
+ }
+ }
+ if (actionCodeSettings.android && actionCodeSettings.android.packageName) {
+ firActionCodeSettings.setAndroidPackageNameInstallIfNotAvailableMinimumVersion(actionCodeSettings.android.packageName, actionCodeSettings.android.installApp, actionCodeSettings.android.minimumVersion || null);
+ }
+ user.sendEmailVerificationWithActionCodeSettingsCompletion(firActionCodeSettings, onCompletion);
+ } else {
+ user.sendEmailVerificationWithCompletion(onCompletion);
+ }
} else {
reject("Log in first");
}
@@ -588,6 +623,30 @@ firebase.logout = arg => {
});
};
+firebase.unlink = providerId => {
+ return new Promise((resolve, reject) => {
+ try {
+ const user = FIRAuth.auth().currentUser;
+ if (!user) {
+ reject("Not logged in");
+ return;
+ }
+
+ user.unlinkFromProviderCompletion(providerId, (user, error) => {
+ if (error) {
+ reject(error.localizedDescription);
+ } else {
+ resolve(user);
+ }
+ });
+
+ } catch (ex) {
+ console.log("Error in firebase.logout: " + ex);
+ reject(ex);
+ }
+ });
+};
+
function toLoginResult(user, additionalUserInfo?: FIRAdditionalUserInfo): User {
if (!user) {
return null;
@@ -627,7 +686,18 @@ function toLoginResult(user, additionalUserInfo?: FIRAdditionalUserInfo): User {
metadata: {
creationTimestamp: user.metadata.creationDate as Date,
lastSignInTimestamp: user.metadata.lastSignInDate as Date
- }
+ },
+ getIdToken: (forceRefresh?: boolean) => new Promise((resolve, reject) => {
+ firebase.getAuthToken({forceRefresh})
+ .then((result: IdTokenResult) => resolve(result.token))
+ .catch(reject);
+ }),
+ getIdTokenResult: (forceRefresh?: boolean) => new Promise((resolve, reject) => {
+ firebase.getAuthToken({forceRefresh})
+ .then((result: IdTokenResult) => resolve(result))
+ .catch(reject);
+ }),
+ sendEmailVerification: (actionCodeSettings?: ActionCodeSettings) => firebase.sendEmailVerification(actionCodeSettings)
};
if (firebase.currentAdditionalUserInfo) {
@@ -642,7 +712,7 @@ function toLoginResult(user, additionalUserInfo?: FIRAdditionalUserInfo): User {
return loginResult;
}
-firebase.getAuthToken = arg => {
+firebase.getAuthToken = (arg: GetAuthTokenOptions): Promise => {
return new Promise((resolve, reject) => {
try {
const fAuth = FIRAuth.auth();
@@ -653,14 +723,20 @@ firebase.getAuthToken = arg => {
const user = fAuth.currentUser;
if (user) {
- const onCompletion = (token, error) => {
+ user.getIDTokenResultForcingRefreshCompletion(arg.forceRefresh, (result: FIRAuthTokenResult, error: NSError) => {
if (error) {
reject(error.localizedDescription);
} else {
- resolve(token);
+ resolve({
+ token: result.token,
+ claims: firebaseUtils.toJsObject(result.claims),
+ signInProvider: result.signInProvider,
+ expirationTime: firebaseUtils.toJsObject(result.expirationDate),
+ issuedAtTime: firebaseUtils.toJsObject(result.issuedAtDate),
+ authTime: firebaseUtils.toJsObject(result.authDate)
+ });
}
- };
- user.getIDTokenForcingRefreshCompletion(arg.forceRefresh, onCompletion);
+ });
} else {
reject("Log in first");
}
@@ -776,6 +852,10 @@ firebase.login = arg => {
}
firebase.requestPhoneAuthVerificationCode(userResponse => {
+ if (userResponse === undefined) {
+ reject("Prompt was canceled");
+ return;
+ }
const fIRAuthCredential = FIRPhoneAuthProvider.provider().credentialWithVerificationIDVerificationCode(verificationID, userResponse);
if (fAuth.currentUser) {
const onCompletionLink = (authData: FIRAuthDataResult, error: NSError) => {
@@ -1009,8 +1089,8 @@ firebase.reloadUser = () => {
});
};
-firebase.resetPassword = arg => {
- return new Promise((resolve, reject) => {
+firebase.sendPasswordResetEmail = (email: string): Promise => {
+ return new Promise((resolve, reject) => {
try {
const onCompletion = error => {
if (error) {
@@ -1020,20 +1100,16 @@ firebase.resetPassword = arg => {
}
};
- if (!arg.email) {
- reject("Resetting a password requires an email argument");
- } else {
- FIRAuth.auth().sendPasswordResetWithEmailCompletion(arg.email, onCompletion);
- }
+ FIRAuth.auth().sendPasswordResetWithEmailCompletion(email, onCompletion);
} catch (ex) {
- console.log("Error in firebase.resetPassword: " + ex);
+ console.log("Error in firebase.sendPasswordResetEmail: " + ex);
reject(ex);
}
});
};
-firebase.changePassword = arg => {
- return new Promise((resolve, reject) => {
+firebase.updateEmail = (newEmail: string): Promise => {
+ return new Promise((resolve, reject) => {
try {
const onCompletion = error => {
if (error) {
@@ -1043,18 +1119,38 @@ firebase.changePassword = arg => {
}
};
- if (!arg.email || !arg.oldPassword || !arg.newPassword) {
- reject("Changing a password requires an email and an oldPassword and a newPassword arguments");
+ const user = FIRAuth.auth().currentUser;
+ if (user === null) {
+ reject("no current user");
} else {
- const user = FIRAuth.auth().currentUser;
- if (user === null) {
- reject("no current user");
+ user.updateEmailCompletion(newEmail, onCompletion);
+ }
+ } catch (ex) {
+ console.log("Error in firebase.updateEmail: " + ex);
+ reject(ex);
+ }
+ });
+};
+
+firebase.updatePassword = (newPassword: string): Promise => {
+ return new Promise((resolve, reject) => {
+ try {
+ const onCompletion = error => {
+ if (error) {
+ reject(error.localizedDescription);
} else {
- user.updatePasswordCompletion(arg.newPassword, onCompletion);
+ resolve();
}
+ };
+
+ const user = FIRAuth.auth().currentUser;
+ if (user === null) {
+ reject("no current user");
+ } else {
+ user.updatePasswordCompletion(newPassword, onCompletion);
}
} catch (ex) {
- console.log("Error in firebase.changePassword: " + ex);
+ console.log("Error in firebase.updatePassword: " + ex);
reject(ex);
}
});
@@ -1146,6 +1242,10 @@ firebase.updateProfile = arg => {
});
};
+/***********************************************
+ * START Realtime Database Functions
+ ***********************************************/
+
firebase._addObservers = (to, updateCallback) => {
const listeners = [];
listeners.push(to.observeEventTypeWithBlock(FIRDataEventType.ChildAdded, snapshot => {
@@ -1301,11 +1401,11 @@ firebase.update = (path, val) => {
});
};
-firebase.query = (updateCallback, path, options) => {
- return new Promise((resolve, reject) => {
+firebase.query = (updateCallback: (data: FBDataSingleEvent) => void, path: string, options: QueryOptions): Promise => {
+ return new Promise((resolve, reject) => {
try {
const where = path === undefined ? FIRDatabase.database().reference() : FIRDatabase.database().reference().childByAppendingPath(path);
- let query;
+ let query: FIRDatabaseQuery;
// orderBy
if (options.orderBy.type === firebase.QueryOrderByType.KEY) {
@@ -1383,9 +1483,22 @@ firebase.query = (updateCallback, path, options) => {
if (options.singleEvent) {
query.observeSingleEventOfTypeWithBlock(FIRDataEventType.Value, snapshot => {
- if (updateCallback) updateCallback(firebase.getCallbackData('ValueChanged', snapshot));
+ const result = {
+ type: "ValueChanged",
+ key: snapshot.key,
+ value: {},
+ children: []
+ };
+ for (let i = 0; i < snapshot.children.allObjects.count; i++) {
+ const snap: FIRDataSnapshot = snapshot.children.allObjects.objectAtIndex(i);
+ const val = firebaseUtils.toJsObject(snap.value);
+ result.value[snap.key] = val;
+ result.children.push(val);
+ }
+
+ if (updateCallback) updateCallback(result);
// resolve promise with data in case of single event, see https://github.com/EddyVerbruggen/nativescript-plugin-firebase/issues/126
- resolve(firebase.getCallbackData('ValueChanged', snapshot));
+ resolve(result);
});
} else {
resolve({
@@ -1413,6 +1526,174 @@ firebase.remove = path => {
});
};
+class OnDisconnect implements OnDisconnectBase {
+ constructor(private dbRef: FIRDatabaseReference, private path: string) {
+ }
+
+ cancel(): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ this.dbRef.cancelDisconnectOperationsWithCompletionBlock((error: NSError, dbRef: FIRDatabaseReference) => {
+ error ? reject(error.localizedDescription) : resolve();
+ });
+ } catch (ex) {
+ console.log("Error in firebase.onDisconnect.cancel: " + ex);
+ reject(ex);
+ }
+ });
+ }
+
+ remove(): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ this.dbRef.onDisconnectRemoveValueWithCompletionBlock((error: NSError, dbRef: FIRDatabaseReference) => {
+ error ? reject(error.localizedDescription) : resolve();
+ });
+ } catch (ex) {
+ console.log("Error in firebase.onDisconnect.remove: " + ex);
+ reject(ex);
+ }
+ });
+ }
+
+ set(value: any): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ this.dbRef.onDisconnectSetValueWithCompletionBlock(value, (error: NSError, dbRef: FIRDatabaseReference) => {
+ error ? reject(error.localizedDescription) : resolve();
+ });
+ } catch (ex) {
+ console.log("Error in firebase.onDisconnect.set: " + ex);
+ reject(ex);
+ }
+ });
+ }
+
+ setWithPriority(value: any, priority: string | number): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ this.dbRef.onDisconnectSetValueAndPriorityWithCompletionBlock(value, priority, (error: NSError, dbRef: FIRDatabaseReference) => {
+ error ? reject(error.localizedDescription) : resolve();
+ });
+ } catch (ex) {
+ console.log("Error in firebase.onDisconnect.setWithPriority: " + ex);
+ reject(ex);
+ }
+ });
+ }
+
+ update(values: any): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ if (typeof values === "object") {
+ this.dbRef.onDisconnectUpdateChildValuesWithCompletionBlock(values, (error: NSError, dbRef: FIRDatabaseReference) => {
+ error ? reject(error.localizedDescription) : resolve();
+ });
+ } else {
+ const lastPartOfPath = this.path.lastIndexOf("/");
+ const pathPrefix = this.path.substring(0, lastPartOfPath);
+ const pathSuffix = this.path.substring(lastPartOfPath + 1);
+ const updateObject = '{"' + pathSuffix + '" : "' + values + '"}';
+ FIRDatabase.database().reference().childByAppendingPath(pathPrefix).updateChildValuesWithCompletionBlock(JSON.parse(updateObject), (error: NSError, dbRef: FIRDatabaseReference) => {
+ error ? reject(error.localizedDescription) : resolve();
+ });
+ }
+ } catch (ex) {
+ console.log("Error in firebase.onDisconnect.update: " + ex);
+ reject(ex);
+ }
+ });
+ }
+}
+
+firebase.onDisconnect = (path: string): OnDisconnect => {
+ if (!firebase.initialized) {
+ console.error("Please run firebase.init() before firebase.onDisconnect()");
+ throw new Error("FirebaseApp is not initialized. Make sure you run firebase.init() first");
+ }
+ const dbRef: FIRDatabaseReference = FIRDatabase.database().reference().child(path);
+ return new OnDisconnect(dbRef, path);
+};
+
+firebase.transaction = (path: string, transactionUpdate: (currentState) => any,
+ onComplete: (a: Error | null, b: boolean, c: DataSnapshot) => Promise) => {
+ return new Promise((resolve, reject) => {
+ if (!firebase.initialized) {
+ console.error("Please run firebase.init() before firebase.transaction()");
+ throw new Error("FirebaseApp is not initialized. Make sure you run firebase.init() first");
+ }
+ const dbRef: FIRDatabaseReference = FIRDatabase.database().reference().child(path);
+
+ dbRef.runTransactionBlockAndCompletionBlock(
+ (mutableData: FIRMutableData): FIRTransactionResult => {
+ const desiredValue = transactionUpdate(firebaseUtils.toJsObject(mutableData.value));
+ if (desiredValue === undefined) {
+ // The problem case : user returns undefined when the the value we give them (mutableData) is null.
+ // This is a valid case as the user will want to abort if he thinks theres no data, BUT mutualData
+ // is usually null when runTransaction is called the first time(which is why its called multiple times).
+ // Result: we would abort and the transaction terminates, but the real data didn't have a chance to come in
+ // for the function to be called a second time.
+ // Even in the ios simple blog example their complete block is called twice with committed first being false
+ // followed by a second one saying committed is true... So with this implementation I favored having an "incorrect"
+ // committed boolean, but have the correct updated value
+
+ // TLDR: if user returns undefined then we may never execute his function with the correct input
+ // For now the way to resolve this is to call success with the original value (so we don't modify anything)
+ // And then the user will get his expected value, but { committed: always true }....
+
+ // return FIRTransactionResult.abort();
+ return FIRTransactionResult.successWithValue(mutableData);
+ } else {
+ mutableData.value = desiredValue;
+ return FIRTransactionResult.successWithValue(mutableData);
+ }
+ },
+ (error: NSError, commited: boolean, snapshot: FIRDataSnapshot): void => {
+ error !== null ? reject(error.localizedDescription) :
+ resolve({committed: commited, snapshot: nativeSnapshotToWebSnapshot(snapshot)});
+ }
+ );
+ });
+};
+
+// Converts FIRDataSnapshot into Web DataSnapshot
+function nativeSnapshotToWebSnapshot(snapshot: FIRDataSnapshot): DataSnapshot {
+ function forEach(action: (datasnapshot: DataSnapshot) => any): boolean {
+ const iterator: NSEnumerator = snapshot.children;
+ let innerSnapshot: FIRDataSnapshot;
+ let datasnapshot: DataSnapshot;
+ while (innerSnapshot = iterator.nextObject()) {
+ datasnapshot = nativeSnapshotToWebSnapshot(innerSnapshot);
+ if (action(datasnapshot)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return {
+ key: snapshot.key,
+ ref: snapshot.ref,
+ child: (path: string) => nativeSnapshotToWebSnapshot(snapshot.childSnapshotForPath(path)),
+ exists: () => snapshot.exists(),
+ forEach: (func: (datasnapshot) => any) => forEach(func),
+ getPriority: () => firebaseUtils.toJsObject(snapshot.priority),
+ hasChild: (path: string) => snapshot.hasChild(path),
+ hasChildren: () => snapshot.hasChildren(),
+ numChildren: () => snapshot.childrenCount,
+ toJSON: () => snapshot.valueInExportFormat(),
+ val: () => firebaseUtils.toJsObject(snapshot.value)
+ };
+}
+
+firebase.enableLogging = (logging: boolean, persistent?: boolean) => {
+ FIRDatabase.setLoggingEnabled(logging);
+};
+
+/***********************************************
+ * END Realtime Database Functions
+ ***********************************************/
+
firebase.sendCrashLog = arg => {
return new Promise((resolve, reject) => {
try {
@@ -1621,48 +1902,71 @@ firebase.firestore.runTransaction = (updateFunction: (transaction: firestore.Tra
});
};
+firebase.firestore.settings = (settings: firestore.Settings) => {
+ if (typeof (FIRFirestore) !== "undefined") {
+ try {
+ const fIRFirestoreSettings = FIRFirestoreSettings.new();
+ if (initializeArguments.persist !== undefined) fIRFirestoreSettings.persistenceEnabled = initializeArguments.persist;
+ if (settings.ssl !== undefined) fIRFirestoreSettings.sslEnabled = settings.ssl;
+ if (settings.host !== undefined) fIRFirestoreSettings.host = settings.host;
+ // Cannot do this because of nativescript cannot convert Number to int64_t
+ // fIRFirestoreSettings.cacheSizeBytes = settings.cacheSizeBytes;
+ FIRFirestore.firestore().settings = fIRFirestoreSettings;
+ } catch (err) {
+ console.log("Error in firebase.firestore.settings: " + err);
+ }
+ }
+};
+
firebase.firestore.collection = (collectionPath: string): firestore.CollectionReference => {
try {
- if (typeof(FIRFirestore) === "undefined") {
+ if (typeof (FIRFirestore) === "undefined") {
console.log("Make sure 'Firebase/Firestore' is in the plugin's Podfile");
return null;
}
- const fIRCollectionReference = FIRFirestore.firestore().collectionWithPath(collectionPath);
-
- return {
- id: fIRCollectionReference.collectionID,
- doc: (documentPath?: string) => firebase.firestore.doc(collectionPath, documentPath),
- add: document => firebase.firestore.add(collectionPath, document),
- get: () => firebase.firestore.get(collectionPath),
- where: (fieldPath: string, opStr: firestore.WhereFilterOp, value: any) => firebase.firestore.where(collectionPath, fieldPath, opStr, value),
- orderBy: (fieldPath: string, directionStr: firestore.OrderByDirection): firestore.Query => firebase.firestore.orderBy(collectionPath, fieldPath, directionStr, fIRCollectionReference),
- limit: (limit: number): firestore.Query => firebase.firestore.limit(collectionPath, limit, fIRCollectionReference),
- onSnapshot: (callback: (snapshot: QuerySnapshot) => void) => firebase.firestore.onCollectionSnapshot(fIRCollectionReference, callback),
- startAfter: (document: DocumentSnapshot) => firebase.firestore.startAfter(collectionPath, document, fIRCollectionReference),
- startAt: (document: DocumentSnapshot) => firebase.firestore.startAt(collectionPath, document, fIRCollectionReference),
- endAt: (document: DocumentSnapshot) => firebase.firestore.endAt(collectionPath, document, fIRCollectionReference),
- endBefore: (document: DocumentSnapshot) => firebase.firestore.endBefore(collectionPath, document, fIRCollectionReference),
- };
+ if (!firebase.initialized) {
+ console.log("Please run firebase.init() before firebase.firestore.collection()");
+ return null;
+ }
+ return firebase.firestore._getCollectionReference(FIRFirestore.firestore().collectionWithPath(collectionPath));
} catch (ex) {
console.log("Error in firebase.firestore.collection: " + ex);
return null;
}
};
-firebase.firestore.onDocumentSnapshot = (docRef: FIRDocumentReference, callback: (doc: DocumentSnapshot) => void): () => void => {
- const listener = docRef.addSnapshotListener((snapshot: FIRDocumentSnapshot, error: NSError) => {
- if (!error && snapshot) {
- callback(new DocumentSnapshot(snapshot));
+firebase.firestore.onDocumentSnapshot = (docRef: FIRDocumentReference, optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: DocumentSnapshot) => void), callbackOrOnError: (docOrError: DocumentSnapshot | Error) => void, onError: (error: Error) => void): () => void => {
+ let includeMetadataChanges = false;
+ let onNextCallback: (snapshot: DocumentSnapshot) => void;
+ let onErrorCallback: (error: Error) => void;
+
+ if ((typeof optionsOrCallback) === "function") {
+ onNextCallback = <(snapshot: DocumentSnapshot) => void>optionsOrCallback;
+ onErrorCallback = callbackOrOnError;
+ } else {
+ onNextCallback = callbackOrOnError;
+ onErrorCallback = onError;
+ }
+
+ if ((optionsOrCallback).includeMetadataChanges === true) {
+ includeMetadataChanges = true;
+ }
+
+ const listener = docRef.addSnapshotListenerWithIncludeMetadataChangesListener(includeMetadataChanges, (snapshot: FIRDocumentSnapshot, error: NSError) => {
+ if (error || !snapshot) {
+ error && onErrorCallback && onErrorCallback(new Error(error.localizedDescription));
+ return;
}
+ onNextCallback && onNextCallback(new DocumentSnapshot(snapshot));
});
// There's a bug resulting this function to be undefined..
if (listener.remove === undefined) {
return () => {
// .. so we're just ignoring anything received from the server (until the callback is set again when 'onSnapshot' is invoked).
- callback = () => {
+ onNextCallback = () => {
};
};
} else {
@@ -1670,20 +1974,38 @@ firebase.firestore.onDocumentSnapshot = (docRef: FIRDocumentReference, callback:
}
};
-firebase.firestore.onCollectionSnapshot = (colRef: FIRCollectionReference, callback: (snapshot: QuerySnapshot) => void): () => void => {
- const listener = colRef.addSnapshotListener((snapshot: FIRQuerySnapshot, error: NSError) => {
+firebase.firestore.onCollectionSnapshot = (colRef: FIRCollectionReference, optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: QuerySnapshot) => void), callbackOrOnError: (snapshotOrError: QuerySnapshot | Error) => void, onError?: (error: Error) => void): () => void => {
+ let includeMetadataChanges = false;
+ let onNextCallback: (snapshot: QuerySnapshot) => void;
+ let onErrorCallback: (error: Error) => void;
+
+ // If we passed in an onNext for the first parameter, the next parameter would be onError if provided
+ // If options was the first parameter then the next parameter would be onNext if provided
+ if ((typeof optionsOrCallback) === "function") {
+ onNextCallback = <(snapshot: QuerySnapshot) => void>optionsOrCallback;
+ onErrorCallback = callbackOrOnError;
+ } else {
+ onNextCallback = callbackOrOnError; // Can be undefined if callback was not provided
+ onErrorCallback = onError; // Can be undefined if onError was not provided
+ }
+
+ if ((optionsOrCallback).includeMetadataChanges === true) {
+ includeMetadataChanges = true;
+ }
+
+ const listener = colRef.addSnapshotListenerWithIncludeMetadataChangesListener(includeMetadataChanges, (snapshot: FIRQuerySnapshot, error: NSError) => {
if (error || !snapshot) {
+ error && onErrorCallback && onErrorCallback(new Error(error.localizedDescription));
return;
}
-
- callback(new QuerySnapshot(snapshot));
+ onNextCallback && onNextCallback(new QuerySnapshot(snapshot));
});
// There's a bug resulting in this function to be undefined..
if (listener.remove === undefined) {
return () => {
// .. so we're just ignoring anything received from the server (until the callback is set again when 'onSnapshot' is invoked).
- callback = () => {
+ onNextCallback = () => {
};
};
} else {
@@ -1691,31 +2013,67 @@ firebase.firestore.onCollectionSnapshot = (colRef: FIRCollectionReference, callb
}
};
-firebase.firestore._getDocumentReference = (fIRDocumentReference: FIRDocumentReference, collectionPath: string, documentPath: string): firestore.DocumentReference => {
+firebase.firestore._getCollectionReference = (colRef?: FIRCollectionReference): firestore.CollectionReference => {
+ if (!colRef) {
+ return null;
+ }
+
+ const collectionPath = colRef.path;
+
+ return {
+ id: colRef.collectionID,
+ parent: firebase.firestore._getDocumentReference(colRef.parent),
+ doc: (documentPath?: string) => firebase.firestore.doc(collectionPath, documentPath),
+ add: document => firebase.firestore.add(collectionPath, document),
+ get: () => firebase.firestore.get(collectionPath),
+ where: (fieldPath: string, opStr: firestore.WhereFilterOp, value: any) => firebase.firestore.where(collectionPath, fieldPath, opStr, value),
+ orderBy: (fieldPath: string, directionStr: firestore.OrderByDirection): firestore.Query => firebase.firestore.orderBy(collectionPath, fieldPath, directionStr, colRef),
+ limit: (limit: number): firestore.Query => firebase.firestore.limit(collectionPath, limit, colRef),
+ onSnapshot: (optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: QuerySnapshot) => void), callbackOrOnError?: (snapshotOrError: QuerySnapshot | Error) => void, onError?: (error: Error) => void) => firebase.firestore.onCollectionSnapshot(colRef, optionsOrCallback, callbackOrOnError, onError),
+ startAfter: (document: DocumentSnapshot) => firebase.firestore.startAfter(collectionPath, document, colRef),
+ startAt: (document: DocumentSnapshot) => firebase.firestore.startAt(collectionPath, document, colRef),
+ endAt: (document: DocumentSnapshot) => firebase.firestore.endAt(collectionPath, document, colRef),
+ endBefore: (document: DocumentSnapshot) => firebase.firestore.endBefore(collectionPath, document, colRef),
+ };
+};
+
+firebase.firestore._getDocumentReference = (docRef?: FIRDocumentReference): firestore.DocumentReference => {
+ if (!docRef) {
+ return null;
+ }
+
+ const collectionPath = docRef.parent.path;
+
return {
discriminator: "docRef",
- id: fIRDocumentReference.documentID,
- path: fIRDocumentReference.path,
- collection: cp => firebase.firestore.collection(`${collectionPath}/${documentPath}/${cp}`),
- set: (data: any, options?: firestore.SetOptions) => firebase.firestore.set(collectionPath, fIRDocumentReference.documentID, data, options),
- get: () => firebase.firestore.getDocument(collectionPath, fIRDocumentReference.documentID),
- update: (data: any) => firebase.firestore.update(collectionPath, fIRDocumentReference.documentID, data),
- delete: () => firebase.firestore.delete(collectionPath, fIRDocumentReference.documentID),
- onSnapshot: (callback: (doc: DocumentSnapshot) => void) => firebase.firestore.onDocumentSnapshot(fIRDocumentReference, callback),
- ios: fIRDocumentReference
+ id: docRef.documentID,
+ parent: firebase.firestore._getCollectionReference(docRef.parent),
+ path: docRef.path,
+ collection: cp => firebase.firestore.collection(`${collectionPath}/${docRef.documentID}/${cp}`),
+ set: (data: any, options?: firestore.SetOptions) => firebase.firestore.set(collectionPath, docRef.documentID, data, options),
+ get: () => firebase.firestore.getDocument(collectionPath, docRef.documentID),
+ update: (data: any) => firebase.firestore.update(collectionPath, docRef.documentID, data),
+ delete: () => firebase.firestore.delete(collectionPath, docRef.documentID),
+ onSnapshot: (optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: DocumentSnapshot) => void), callbackOrOnError?: (docOrError: DocumentSnapshot | Error) => void, onError?: (error: Error) => void) => firebase.firestore.onDocumentSnapshot(docRef, optionsOrCallback, callbackOrOnError, onError),
+ ios: docRef
};
};
firebase.firestore.doc = (collectionPath: string, documentPath?: string): firestore.DocumentReference => {
try {
- if (typeof(FIRFirestore) === "undefined") {
+ if (typeof (FIRFirestore) === "undefined") {
console.log("Make sure 'Firebase/Firestore' is in the plugin's Podfile");
return null;
}
+ if (!firebase.initialized) {
+ console.log("Please run firebase.init() before firebase.firestore.doc()");
+ return null;
+ }
+
const fIRCollectionReference = FIRFirestore.firestore().collectionWithPath(collectionPath);
const fIRDocumentReference = documentPath ? fIRCollectionReference.documentWithPath(documentPath) : fIRCollectionReference.documentWithAutoID();
- return firebase.firestore._getDocumentReference(fIRDocumentReference, collectionPath, documentPath);
+ return firebase.firestore._getDocumentReference(fIRDocumentReference);
} catch (ex) {
console.log("Error in firebase.firestore.doc: " + ex);
return null;
@@ -1728,13 +2086,13 @@ firebase.firestore.docRef = (documentPath: string): firestore.DocumentReference
return null;
}
- return convertDocRef(FIRFirestore.firestore().documentWithPath(documentPath));
+ return firebase.firestore._getDocumentReference(FIRFirestore.firestore().documentWithPath(documentPath));
};
firebase.firestore.add = (collectionPath: string, document: any): Promise => {
return new Promise((resolve, reject) => {
try {
- if (typeof(FIRFirestore) === "undefined") {
+ if (typeof (FIRFirestore) === "undefined") {
reject("Make sure 'Firebase/Firestore' is in the plugin's Podfile");
return;
}
@@ -1746,17 +2104,7 @@ firebase.firestore.add = (collectionPath: string, document: any): Promise firebase.firestore.collection(cp),
- set: (data: any, options?: firestore.SetOptions) => firebase.firestore.set(collectionPath, fIRDocumentReference.documentID, data, options),
- get: () => firebase.firestore.getDocument(collectionPath, fIRDocumentReference.documentID),
- update: (data: any) => firebase.firestore.update(collectionPath, fIRDocumentReference.documentID, data),
- delete: () => firebase.firestore.delete(collectionPath, fIRDocumentReference.documentID),
- onSnapshot: (callback: (doc: DocumentSnapshot) => void) => firebase.firestore.onDocumentSnapshot(fIRDocumentReference, callback)
- });
+ resolve(firebase.firestore._getDocumentReference(fIRDocumentReference));
}
});
@@ -1770,7 +2118,7 @@ firebase.firestore.add = (collectionPath: string, document: any): Promise => {
return new Promise((resolve, reject) => {
try {
- if (typeof(FIRFirestore) === "undefined") {
+ if (typeof (FIRFirestore) === "undefined") {
reject("Make sure 'Firebase/Firestore' is in the plugin's Podfile");
return;
}
@@ -1851,7 +2199,7 @@ function fixSpecialField(item): any {
firebase.firestore.update = (collectionPath: string, documentPath: string, document: any): Promise => {
return new Promise((resolve, reject) => {
try {
- if (typeof(FIRFirestore) === "undefined") {
+ if (typeof (FIRFirestore) === "undefined") {
reject("Make sure 'Firebase/Firestore' is in the plugin's Podfile");
return;
}
@@ -1879,7 +2227,7 @@ firebase.firestore.update = (collectionPath: string, documentPath: string, docum
firebase.firestore.delete = (collectionPath: string, documentPath: string): Promise => {
return new Promise((resolve, reject) => {
try {
- if (typeof(FIRFirestore) === "undefined") {
+ if (typeof (FIRFirestore) === "undefined") {
reject("Make sure 'Firebase/Firestore' is in the plugin's Podfile");
return;
}
@@ -1906,7 +2254,7 @@ firebase.firestore.delete = (collectionPath: string, documentPath: string): Prom
firebase.firestore.getCollection = (collectionPath: string): Promise => {
return new Promise((resolve, reject) => {
try {
- if (typeof(FIRFirestore) === "undefined") {
+ if (typeof (FIRFirestore) === "undefined") {
reject("Make sure 'Firebase/Firestore' is in the plugin's Podfile");
return;
}
@@ -1936,7 +2284,7 @@ firebase.firestore.get = (collectionPath: string): Promise => {
return new Promise((resolve, reject) => {
try {
- if (typeof(FIRFirestore) === "undefined") {
+ if (typeof (FIRFirestore) === "undefined") {
reject("Make sure 'Firebase/Firestore' is in the plugin's Podfile");
return;
}
@@ -1973,7 +2321,7 @@ firebase.firestore._getQuery = (collectionPath: string, query: FIRQuery): firest
where: (fp: string, os: firestore.WhereFilterOp, v: any): firestore.Query => firebase.firestore.where(collectionPath, fp, os, v, query),
orderBy: (fp: string, directionStr: firestore.OrderByDirection): firestore.Query => firebase.firestore.orderBy(collectionPath, fp, directionStr, query),
limit: (limit: number): firestore.Query => firebase.firestore.limit(collectionPath, limit, query),
- onSnapshot: (callback: (snapshot: QuerySnapshot) => void) => firebase.firestore.onCollectionSnapshot(query, callback),
+ onSnapshot: (optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: QuerySnapshot) => void), callbackOrOnError?: (snapshotOrError: QuerySnapshot | Error) => void, onError?: (error: Error) => void) => firebase.firestore.onCollectionSnapshot(query, optionsOrCallback, callbackOrOnError, onError),
startAfter: (document: DocumentSnapshot) => firebase.firestore.startAfter(collectionPath, document, query),
startAt: (document: DocumentSnapshot) => firebase.firestore.startAt(collectionPath, document, query),
endAt: (document: DocumentSnapshot) => firebase.firestore.endAt(collectionPath, document, query),
@@ -1983,7 +2331,7 @@ firebase.firestore._getQuery = (collectionPath: string, query: FIRQuery): firest
firebase.firestore.where = (collectionPath: string, fieldPath: string, opStr: firestore.WhereFilterOp, value: any, query?: FIRQuery): firestore.Query => {
try {
- if (typeof(FIRFirestore) === "undefined") {
+ if (typeof (FIRFirestore) === "undefined") {
console.log("Make sure 'Firebase/Firestore' is in the plugin's Podfile");
return null;
}
@@ -2046,7 +2394,7 @@ class FIRInviteDelegateImpl extends NSObject implements FIRInviteDelegate {
public static ObjCProtocols = [];
static new(): FIRInviteDelegateImpl {
- if (FIRInviteDelegateImpl.ObjCProtocols.length === 0 && typeof(FIRInviteDelegate) !== "undefined") {
+ if (FIRInviteDelegateImpl.ObjCProtocols.length === 0 && typeof (FIRInviteDelegate) !== "undefined") {
FIRInviteDelegateImpl.ObjCProtocols.push(FIRInviteDelegate);
}
return super.new();
@@ -2086,23 +2434,6 @@ class GIDSignInDelegateImpl extends NSObject implements GIDSignInDelegate {
}
}
-function convertDocRef(docRef: FIRDocumentReference): firestore.DocumentReference {
- const collectionPath = docRef.parent.path;
-
- return {
- discriminator: "docRef",
- id: docRef.documentID,
- path: docRef.path,
- collection: cp => firebase.firestore.collection(`${collectionPath}/${docRef.documentID}/${cp}`),
- set: (data: any, options?: firestore.SetOptions) => firebase.firestore.set(collectionPath, docRef.documentID, data, options),
- get: () => firebase.firestore.getDocument(collectionPath, docRef.documentID),
- update: (data: any) => firebase.firestore.update(collectionPath, docRef.documentID, data),
- delete: () => firebase.firestore.delete(collectionPath, docRef.documentID),
- onSnapshot: (callback: (doc: DocumentSnapshot) => void) => firebase.firestore.onDocumentSnapshot(docRef, callback),
- ios: docRef
- };
-}
-
function convertDocChangeType(type: FIRDocumentChangeType) {
switch (type) {
case FIRDocumentChangeType.Added:
@@ -2126,6 +2457,11 @@ export class QuerySnapshot implements firestore.QuerySnapshot {
constructor(private snapshot: FIRQuerySnapshot) {
}
+ metadata = {
+ fromCache: this.snapshot.metadata.fromCache,
+ hasPendingWrites: this.snapshot.metadata.pendingWrites
+ };
+
get docs(): firestore.QueryDocumentSnapshot[] {
const getSnapshots = () => {
const docSnapshots: firestore.QueryDocumentSnapshot[] = [];
diff --git a/src/functions/functions.ios.ts b/src/functions/functions.ios.ts
index 25d38cff..e9969e78 100644
--- a/src/functions/functions.ios.ts
+++ b/src/functions/functions.ios.ts
@@ -11,12 +11,6 @@ export function httpsCallable(functionName: string): HttpsCallab
const handleCompletion = (result: FIRHTTPSCallableResult, err: NSError) => {
if (err) {
- if (err.domain === FIRFunctionsErrorDomain) {
- const message = err.localizedDescription;
- reject(message);
- return;
- }
-
reject(err.localizedDescription);
return;
}
diff --git a/src/messaging/messaging.ios.ts b/src/messaging/messaging.ios.ts
index 3b88081a..7d47ef85 100755
--- a/src/messaging/messaging.ios.ts
+++ b/src/messaging/messaging.ios.ts
@@ -378,9 +378,7 @@ const updateUserInfo = userInfoJSON => {
function _registerForRemoteNotifications() {
let app = iOSUtils.getter(UIApplication, UIApplication.sharedApplication);
if (!app) {
- application.on("launch", () => {
- _registerForRemoteNotifications();
- });
+ application.on("launch", () => _registerForRemoteNotifications());
return;
}
diff --git a/src/mlkit/barcodescanning/index.android.ts b/src/mlkit/barcodescanning/index.android.ts
index 898a0edb..e028422b 100644
--- a/src/mlkit/barcodescanning/index.android.ts
+++ b/src/mlkit/barcodescanning/index.android.ts
@@ -1,14 +1,25 @@
import { ImageSource } from "tns-core-modules/image-source";
-import { MLKitScanBarcodesOnDeviceOptions, MLKitScanBarcodesOnDeviceResult } from "./";
-import { MLKitOptions } from "../index";
+import { MLKitScanBarcodesOnDeviceOptions, MLKitScanBarcodesOnDeviceResult, MLKitScanBarcodesResultBounds } from "./";
+import { MLKitVisionOptions } from "../index";
import { BarcodeFormat, MLKitBarcodeScanner as MLKitBarcodeScannerBase } from "./barcodescanning-common";
-
-declare const com: any;
+import * as application from "tns-core-modules/application";
export { BarcodeFormat };
+const gmsTasks = (com.google.android.gms).tasks;
+
export class MLKitBarcodeScanner extends MLKitBarcodeScannerBase {
+ private player: android.media.MediaPlayer;
+
+ disposeNativeView(): void {
+ super.disposeNativeView();
+ if (this.player) {
+ this.player.release();
+ this.player = undefined;
+ }
+ }
+
protected createDetector(): any {
let formats: Array;
if (this.formats) {
@@ -16,27 +27,61 @@ export class MLKitBarcodeScanner extends MLKitBarcodeScannerBase {
const requestedFormats = this.formats.split(",");
requestedFormats.forEach(format => formats.push(BarcodeFormat[format.trim().toUpperCase()]))
}
+
+ if (this.beepOnScan) {
+ const activity = (application.android.foregroundActivity || application.android.startActivity);
+ activity.setVolumeControlStream(android.media.AudioManager.STREAM_MUSIC);
+ try {
+ const file = application.android.context.getResources().getIdentifier("beep", "raw", application.android.context.getPackageName());
+ if (file === 0) {
+ console.log("No 'beep.*' soundfile found in the resources /raw folder. There will be no audible feedback upon scanning a barcode.");
+ } else {
+ this.player = new android.media.MediaPlayer();
+ const fileDescriptor: android.content.res.AssetFileDescriptor = application.android.context.getResources().openRawResourceFd(file);
+ try {
+ this.player.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(), fileDescriptor.getLength());
+ } finally {
+ fileDescriptor.close();
+ }
+ // this.mediaPlayer.setOnErrorListener(this);
+ this.player.setAudioStreamType(android.media.AudioManager.STREAM_MUSIC);
+ this.player.setLooping(false);
+ this.player.setVolume(0.10, 0.10);
+ this.player.prepare();
+ }
+ } catch (e) {
+ console.log(e);
+ this.player.release();
+ this.player = undefined;
+ }
+ }
+
return getBarcodeDetector(formats);
}
protected createSuccessListener(): any {
- return new com.google.android.gms.tasks.OnSuccessListener({
+ return new gmsTasks.OnSuccessListener({
onSuccess: barcodes => {
const result = {
barcodes: []
};
- if (barcodes) {
+ if (barcodes && barcodes.size() > 0) {
// see https://github.com/firebase/quickstart-android/blob/0f4c86877fc5f771cac95797dffa8bd026dd9dc7/mlkit/app/src/main/java/com/google/firebase/samples/apps/mlkit/textrecognition/TextRecognitionProcessor.java#L62
for (let i = 0; i < barcodes.size(); i++) {
const barcode = barcodes.get(i);
result.barcodes.push({
value: barcode.getRawValue(),
format: BarcodeFormat[barcode.getFormat()],
- android: barcode
+ android: barcode,
+ bounds: boundingBoxToBounds(barcode.getBoundingBox())
});
}
+
+ if (this.player) {
+ this.player.start();
+ }
}
this.notify({
@@ -49,6 +94,19 @@ export class MLKitBarcodeScanner extends MLKitBarcodeScannerBase {
}
}
+function boundingBoxToBounds(rect: any): MLKitScanBarcodesResultBounds {
+ return {
+ origin: {
+ x: rect.left,
+ y: rect.top
+ },
+ size: {
+ width: rect.width(),
+ height: rect.height()
+ }
+ }
+}
+
function getBarcodeDetector(formats?: Array): any {
if (formats && formats.length > 0) {
const firebaseVisionBarcodeDetectorOptions =
@@ -66,7 +124,7 @@ export function scanBarcodesOnDevice(options: MLKitScanBarcodesOnDeviceOptions):
try {
const firebaseVisionBarcodeDetector = getBarcodeDetector(options.formats);
- const onSuccessListener = new com.google.android.gms.tasks.OnSuccessListener({
+ const onSuccessListener = new gmsTasks.OnSuccessListener({
onSuccess: barcodes => {
const result = {
barcodes: []
@@ -79,7 +137,8 @@ export function scanBarcodesOnDevice(options: MLKitScanBarcodesOnDeviceOptions):
result.barcodes.push({
value: barcode.getRawValue(),
format: BarcodeFormat[barcode.getFormat()],
- android: barcode
+ android: barcode,
+ bounds: boundingBoxToBounds(barcode.getBoundingBox())
});
}
}
@@ -89,7 +148,7 @@ export function scanBarcodesOnDevice(options: MLKitScanBarcodesOnDeviceOptions):
}
});
- const onFailureListener = new com.google.android.gms.tasks.OnFailureListener({
+ const onFailureListener = new gmsTasks.OnFailureListener({
onFailure: exception => reject(exception.getMessage())
});
@@ -105,7 +164,7 @@ export function scanBarcodesOnDevice(options: MLKitScanBarcodesOnDeviceOptions):
});
}
-function getImage(options: MLKitOptions): any /* com.google.firebase.ml.vision.common.FirebaseVisionImage */ {
+function getImage(options: MLKitVisionOptions): any /* com.google.firebase.ml.vision.common.FirebaseVisionImage */ {
const image: android.graphics.Bitmap = options.image instanceof ImageSource ? options.image.android : options.image.imageSource.android;
return com.google.firebase.ml.vision.common.FirebaseVisionImage.fromBitmap(image);
}
diff --git a/src/mlkit/barcodescanning/index.d.ts b/src/mlkit/barcodescanning/index.d.ts
index 347bea7f..fe93e64a 100644
--- a/src/mlkit/barcodescanning/index.d.ts
+++ b/src/mlkit/barcodescanning/index.d.ts
@@ -1,29 +1,53 @@
-import { MLKitOptions } from "../";
+import { MLKitCameraView, MLKitVisionOptions, MLKitVisionResult } from "../";
import { BarcodeFormat } from "./barcodescanning-common";
-import { MLKitResult, MLKitCameraView } from "../index";
export { BarcodeFormat };
+export interface MLKitScanBarcodesResultBounds {
+ origin: {
+ x: number;
+ y: number;
+ },
+ size: {
+ width: number;
+ height: number;
+ }
+}
+
export interface MLKitScanBarcodesResultBarcode {
value: string;
format: string;
ios?: any;
android?: any;
+ bounds?: MLKitScanBarcodesResultBounds;
// TODO details
}
-export interface MLKitScanBarcodesOnDeviceResult extends MLKitResult {
+export interface MLKitScanBarcodesOnDeviceResult extends MLKitVisionResult {
barcodes: Array;
}
-export interface MLKitScanBarcodesOnDeviceOptions extends MLKitOptions {
+export interface MLKitScanBarcodesOnDeviceOptions extends MLKitVisionOptions {
/**
* Limit to only what you need to speed up processing.
* If not set, we'll detect all supported formats.
*/
formats?: Array;
+
+ /**
+ * Play a sound when a code was scanned.
+ * Default: true
+ */
+ beepOnScan?: boolean;
+
+ /**
+ * Wheter or not to report duplicate scan results during continuous scanning.
+ * Default false.
+ */
+ reportDuplicates?: boolean;
}
export declare function scanBarcodesOnDevice(options: MLKitScanBarcodesOnDeviceOptions): Promise;
-export declare class MLKitBarcodeScanner extends MLKitCameraView {}
+export declare class MLKitBarcodeScanner extends MLKitCameraView {
+}
diff --git a/src/mlkit/barcodescanning/index.ios.ts b/src/mlkit/barcodescanning/index.ios.ts
index 1c27ae34..7192fe3f 100644
--- a/src/mlkit/barcodescanning/index.ios.ts
+++ b/src/mlkit/barcodescanning/index.ios.ts
@@ -1,12 +1,14 @@
import { ImageSource } from "tns-core-modules/image-source";
import { MLKitScanBarcodesOnDeviceOptions, MLKitScanBarcodesOnDeviceResult } from "./index";
-import { MLKitOptions } from "../index";
+import { MLKitVisionOptions } from "../index";
import { BarcodeFormat, MLKitBarcodeScanner as MLKitBarcodeScannerBase } from "./barcodescanning-common";
export { BarcodeFormat };
export class MLKitBarcodeScanner extends MLKitBarcodeScannerBase {
+ private player: AVAudioPlayer;
+
protected createDetector(): any {
let formats: Array;
if (this.formats) {
@@ -14,6 +16,19 @@ export class MLKitBarcodeScanner extends MLKitBarcodeScannerBase {
const requestedFormats = this.formats.split(",");
requestedFormats.forEach(format => formats.push(BarcodeFormat[format.trim().toUpperCase()]))
}
+
+ if (this.beepOnScan) {
+ // play nice with others when playing sound
+ AVAudioSession.sharedInstance().setCategoryModeOptionsError(AVAudioSessionCategoryPlayback, AVAudioSessionModeDefault, AVAudioSessionCategoryOptions.MixWithOthers)
+
+ // prepare an audio player, with a sound file bundled in our custom fwk
+ const barcodeBundlePath = NSBundle.bundleWithIdentifier("org.nativescript.plugin.firebase.MLKit").bundlePath;
+ this.player = new AVAudioPlayer({contentsOfURL: NSURL.fileURLWithPath(barcodeBundlePath + "/beep.caf")});
+ this.player.numberOfLoops = 1;
+ this.player.volume = 0.7; // this is not the actual volume, as that really depends on the device volume
+ this.player.prepareToPlay();
+ }
+
return getBarcodeDetector(formats);
}
@@ -32,7 +47,8 @@ export class MLKitBarcodeScanner extends MLKitBarcodeScannerBase {
result.barcodes.push({
value: barcode.rawValue,
format: BarcodeFormat[barcode.format],
- ios: barcode
+ ios: barcode,
+ bounds: barcode.frame
});
}
@@ -41,6 +57,10 @@ export class MLKitBarcodeScanner extends MLKitBarcodeScannerBase {
object: this,
value: result
});
+
+ if (barcodes.count > 0 && this.player) {
+ this.player.play();
+ }
}
}
}
@@ -79,7 +99,8 @@ export function scanBarcodesOnDevice(options: MLKitScanBarcodesOnDeviceOptions):
result.barcodes.push({
value: barcode.rawValue,
format: BarcodeFormat[barcode.format],
- ios: barcode
+ ios: barcode,
+ bounds: barcode.frame
});
}
resolve(result);
@@ -92,7 +113,7 @@ export function scanBarcodesOnDevice(options: MLKitScanBarcodesOnDeviceOptions):
});
}
-function getImage(options: MLKitOptions): FIRVisionImage {
+function getImage(options: MLKitVisionOptions): FIRVisionImage {
const image: UIImage = options.image instanceof ImageSource ? options.image.ios : options.image.imageSource.ios;
return FIRVisionImage.alloc().initWithImage(image);
}
diff --git a/src/mlkit/custommodel/custommodel-common.ts b/src/mlkit/custommodel/custommodel-common.ts
index 57b7bec9..3dca8b5c 100644
--- a/src/mlkit/custommodel/custommodel-common.ts
+++ b/src/mlkit/custommodel/custommodel-common.ts
@@ -1,5 +1,94 @@
+import * as fs from "tns-core-modules/file-system";
+import { Property } from "tns-core-modules/ui/core/properties";
import { MLKitCameraView } from "../mlkit-cameraview";
+import { MLKitCustomModelType } from "./index";
+
+export const localModelFileProperty = new Property({
+ name: "localModelFile",
+ defaultValue: null,
+});
+
+export const labelsFileProperty = new Property({
+ name: "labelsFile",
+ defaultValue: null,
+});
+
+export const modelInputShapeProperty = new Property({
+ name: "modelInputShape",
+ defaultValue: null,
+});
+
+export const modelInputTypeProperty = new Property({
+ name: "modelInputType",
+ defaultValue: null,
+});
+
+// TODO could combine this with 'confidenceThreshold'
+export const maxResultsProperty = new Property({
+ name: "maxResults",
+ defaultValue: 5
+});
export abstract class MLKitCustomModel extends MLKitCameraView {
static scanResultEvent: string = "scanResult";
+ protected localModelFile: string;
+ protected labelsFile: string;
+ protected maxResults: number;
+ protected modelInputShape: Array;
+ protected modelInputType: MLKitCustomModelType;
+
+ protected onSuccessListener;
+ protected detectorBusy: boolean;
+
+ protected labels: Array;
+
+ [localModelFileProperty.setNative](value: string) {
+ this.localModelFile = value;
+ }
+
+ [labelsFileProperty.setNative](value: string) {
+ this.labelsFile = value;
+ if (value.indexOf("~/") === 0) {
+ this.labels = getLabelsFromAppFolder(value);
+ } else {
+ // no dice loading from assets yet, let's advice users to use ~/ for now
+ console.log("For the 'labelsFile' property, use the ~/ prefix for now..");
+ return;
+ }
+ }
+
+ [maxResultsProperty.setNative](value: any) {
+ this.maxResults = parseInt(value);
+ }
+
+ [modelInputShapeProperty.setNative](value: string) {
+ if ((typeof value) === "string") {
+ this.modelInputShape = value.split(",").map(v => parseInt(v.trim()));
+ }
+ }
+
+ [modelInputTypeProperty.setNative](value: MLKitCustomModelType) {
+ this.modelInputType = value;
+ }
}
+
+localModelFileProperty.register(MLKitCustomModel);
+labelsFileProperty.register(MLKitCustomModel);
+maxResultsProperty.register(MLKitCustomModel);
+modelInputShapeProperty.register(MLKitCustomModel);
+modelInputTypeProperty.register(MLKitCustomModel);
+
+export function getLabelsFromAppFolder(labelsFile: string): Array {
+ const labelsPath = fs.knownFolders.currentApp().path + labelsFile.substring(1);
+ return getLabelsFromFile(labelsPath);
+}
+
+export function getLabelsFromFile(labelsFile: string): Array {
+ const fileContents = fs.File.fromPath(labelsFile).readTextSync();
+ const lines: Array = fileContents.split("\n");
+ // remove possibly empty trailing lines
+ while (lines[lines.length - 1].trim() === "") {
+ lines.pop();
+ }
+ return lines;
+}
\ No newline at end of file
diff --git a/src/mlkit/custommodel/index.android.ts b/src/mlkit/custommodel/index.android.ts
index 0562d589..0f1a95dd 100644
--- a/src/mlkit/custommodel/index.android.ts
+++ b/src/mlkit/custommodel/index.android.ts
@@ -1,67 +1,167 @@
+import * as fs from "tns-core-modules/file-system";
import { ImageSource } from "tns-core-modules/image-source";
-import { MLKitOptions } from "../";
-import { MLKitCustomModelOptions, MLKitCustomModelResult } from "./";
-import { MLKitCustomModel as MLKitCustomModelBase } from "./custommodel-common";
+import { MLKitCustomModelOptions, MLKitCustomModelResult, MLKitCustomModelResultValue } from "./";
+import { getLabelsFromAppFolder, MLKitCustomModel as MLKitCustomModelBase } from "./custommodel-common";
-declare const com: any;
+const gmsTasks = (com.google.android.gms).tasks;
export class MLKitCustomModel extends MLKitCustomModelBase {
+ private detector;
+ private onFailureListener;
+ private inputOutputOptions;
protected createDetector(): any {
- return getInterpreter();
+ this.detector = getInterpreter(this.localModelFile);
+ return this.detector;
+ }
+
+ protected runDetector(imageByteBuffer, previewWidth, previewHeight): void {
+ if (this.detectorBusy) {
+ return;
+ }
+
+ this.detectorBusy = true;
+
+ if (!this.onFailureListener) {
+ this.onFailureListener = new gmsTasks.OnFailureListener({
+ onFailure: exception => {
+ console.log(exception.getMessage());
+ this.detectorBusy = false;
+ }
+ });
+ }
+
+ const modelExpectsWidth = this.modelInputShape[1];
+ const modelExpectsHeight = this.modelInputShape[2];
+ const isQuantized = this.modelInputType !== "FLOAT32";
+
+ if (!this.inputOutputOptions) {
+ let intArrayIn = Array.create("int", 4);
+ intArrayIn[0] = this.modelInputShape[0];
+ intArrayIn[1] = modelExpectsWidth;
+ intArrayIn[2] = modelExpectsHeight;
+ intArrayIn[3] = this.modelInputShape[3];
+
+ const inputType = isQuantized ? com.google.firebase.ml.custom.FirebaseModelDataType.BYTE : com.google.firebase.ml.custom.FirebaseModelDataType.FLOAT32;
+
+ let intArrayOut = Array.create("int", 2);
+ intArrayOut[0] = 1;
+ intArrayOut[1] = this.labels.length;
+
+ this.inputOutputOptions = new com.google.firebase.ml.custom.FirebaseModelInputOutputOptions.Builder()
+ .setInputFormat(0, inputType, intArrayIn)
+ .setOutputFormat(0, inputType, intArrayOut)
+ .build();
+ }
+
+ const input = org.nativescript.plugins.firebase.mlkit.BitmapUtil.byteBufferToByteBuffer(imageByteBuffer, previewWidth, previewHeight, modelExpectsWidth, modelExpectsHeight, isQuantized);
+ const inputs = new com.google.firebase.ml.custom.FirebaseModelInputs.Builder()
+ .add(input) // add as many input arrays as your model requires
+ .build();
+
+ this.detector
+ .run(inputs, this.inputOutputOptions)
+ .addOnSuccessListener(this.onSuccessListener)
+ .addOnFailureListener(this.onFailureListener);
}
protected createSuccessListener(): any {
- return new com.google.android.gms.tasks.OnSuccessListener({
- onSuccess: labels => {
+ this.onSuccessListener = new gmsTasks.OnSuccessListener({
+ onSuccess: output => {
+ const probabilities: Array = output.getOutput(0)[0];
- if (labels.size() === 0) return;
+ if (this.labels.length !== probabilities.length) {
+ console.log(`The number of labels (${this.labels.length}) is not equal to the interpretation result (${probabilities.length})!`);
+ return;
+ }
const result = {
- result: []
+ result: getSortedResult(this.labels, probabilities, this.maxResults)
};
- // see https://github.com/firebase/quickstart-android/blob/0f4c86877fc5f771cac95797dffa8bd026dd9dc7/mlkit/app/src/main/java/com/google/firebase/samples/apps/mlkit/textrecognition/TextRecognitionProcessor.java#L62
- for (let i = 0; i < labels.size(); i++) {
- const label = labels.get(i);
- result.result.push({
- text: label.getLabel(),
- confidence: label.getConfidence()
- });
- }
-
this.notify({
eventName: MLKitCustomModel.scanResultEvent,
object: this,
value: result
});
+
+ this.detectorBusy = false;
}
});
+
+ return this.onSuccessListener;
}
}
-function getInterpreter(): any {
- const localSource = new com.google.firebase.ml.custom.model.FirebaseLocalModelSource.Builder("my_local_model")
- .setAssetFilePath("mobilenet_quant_v1_224.tflite") // Or setFilePath if you downloaded from your host
- .build();
- com.google.firebase.ml.custom.FirebaseModelManager.getInstance().registerLocalModelSource(localSource);
-
- const options = new com.google.firebase.ml.custom.FirebaseModelOptions.Builder()
- // .setCloudModelName("my_cloud_model")
- .setLocalModelName("my_local_model")
- .build();
- return com.google.firebase.ml.custom.FirebaseModelInterpreter.getInstance(options);
+const registeredModels = [];
+
+function getInterpreter(localModelFile?: string): any {
+ const firModelOptionsBuilder = new com.google.firebase.ml.custom.FirebaseModelOptions.Builder();
+
+ const localModelName = localModelFile.lastIndexOf("/") === -1 ? localModelFile : localModelFile.substring(localModelFile.lastIndexOf("/") + 1);
+ let localModelRegistrationSuccess = false;
+
+ if (localModelFile) {
+ if (registeredModels.indexOf(localModelName) > -1) {
+ localModelRegistrationSuccess = true;
+ firModelOptionsBuilder.setLocalModelName(localModelName)
+ } else {
+ const firModelLocalBuilder = new com.google.firebase.ml.common.modeldownload.FirebaseLocalModel.Builder(localModelName);
+
+ if (localModelFile.indexOf("~/") === 0) {
+ firModelLocalBuilder.setFilePath(fs.knownFolders.currentApp().path + localModelFile.substring(1));
+ } else {
+ // note that this doesn't seem to work, let's advice users to use ~/ for now
+ firModelLocalBuilder.setAssetFilePath(localModelFile);
+ }
+
+ localModelRegistrationSuccess = com.google.firebase.ml.common.modeldownload.FirebaseModelManager.getInstance().registerLocalModel(firModelLocalBuilder.build());
+
+ if (localModelRegistrationSuccess) {
+ registeredModels.push(localModelName);
+ firModelOptionsBuilder.setLocalModelName(localModelName)
+ }
+ }
+ }
+
+ // if (options.cloudModelName) {
+ // firModelOptionsBuilder.setRemoteModelName(options.cloudModelName)
+ // }
+
+ if (!localModelRegistrationSuccess) {
+ // TODO handle this case upstream
+ console.log("No (cloud or local) model was successfully loaded.");
+ return null;
+ }
+
+ return com.google.firebase.ml.custom.FirebaseModelInterpreter.getInstance(firModelOptionsBuilder.build());
}
export function useCustomModel(options: MLKitCustomModelOptions): Promise {
return new Promise((resolve, reject) => {
try {
- const interpreter = getInterpreter();
+ const interpreter = getInterpreter(options.localModelFile);
+
+ let labels: Array;
+ if (options.labelsFile.indexOf("~/") === 0) {
+ labels = getLabelsFromAppFolder(options.labelsFile);
+ } else {
+ // no dice loading from assets yet, let's advice users to use ~/ for now
+ reject("Use the ~/ prefix for now..");
+ return;
+ }
+
+ const onSuccessListener = new gmsTasks.OnSuccessListener({
+ onSuccess: output => {
+ const probabilities: Array = output.getOutput(0)[0];
+
+ if (labels.length !== probabilities.length) {
+ console.log(`The number of labels in ${options.labelsFile} (${labels.length}) is not equal to the interpretation result (${probabilities.length})!`);
+ return;
+ }
- const onSuccessListener = new com.google.android.gms.tasks.OnSuccessListener({
- onSuccess: labels => {
const result = {
- result: []
+ result: getSortedResult(labels, probabilities, options.maxResults)
};
resolve(result);
@@ -69,34 +169,34 @@ export function useCustomModel(options: MLKitCustomModelOptions): Promise reject(exception.getMessage())
});
- let intArrayIn = Array.create('int', 4);
- intArrayIn[0] = 1;
- intArrayIn[1] = 640;
- intArrayIn[2] = 480;
- intArrayIn[3] = 3;
+ let intArrayIn = Array.create("int", 4);
+ intArrayIn[0] = options.modelInput[0].shape[0];
+ intArrayIn[1] = options.modelInput[0].shape[1];
+ intArrayIn[2] = options.modelInput[0].shape[2];
+ intArrayIn[3] = options.modelInput[0].shape[3];
+
+ const isQuantized = options.modelInput[0].type !== "FLOAT32";
+ const inputType = isQuantized ? com.google.firebase.ml.custom.FirebaseModelDataType.BYTE : com.google.firebase.ml.custom.FirebaseModelDataType.FLOAT32;
- let intArrayOut = Array.create('int', 2);
+ let intArrayOut = Array.create("int", 2);
intArrayOut[0] = 1;
- intArrayOut[1] = 1000;
+ intArrayOut[1] = labels.length;
const inputOutputOptions = new com.google.firebase.ml.custom.FirebaseModelInputOutputOptions.Builder()
- .setInputFormat(0, com.google.firebase.ml.custom.FirebaseModelDataType.BYTE, intArrayIn)
- .setOutputFormat(0, com.google.firebase.ml.custom.FirebaseModelDataType.FLOAT32, intArrayOut)
+ .setInputFormat(0, inputType, intArrayIn)
+ .setOutputFormat(0, inputType, intArrayOut)
.build();
- // TODO check native example project
- const input = null; // getData();
-
- // input = getYourInputData();
+ const image: android.graphics.Bitmap = options.image instanceof ImageSource ? options.image.android : options.image.imageSource.android;
+ const input = org.nativescript.plugins.firebase.mlkit.BitmapUtil.bitmapToByteBuffer(image, options.modelInput[0].shape[1], options.modelInput[0].shape[2], isQuantized);
const inputs = new com.google.firebase.ml.custom.FirebaseModelInputs.Builder()
- .add(input) // add() as many input arrays as your model requires
+ .add(input) // add as many input arrays as your model requires
.build();
- // TODO see https://firebase.google.com/docs/ml-kit/android/use-custom-models
interpreter
.run(inputs, inputOutputOptions)
.addOnSuccessListener(onSuccessListener)
@@ -109,7 +209,13 @@ export function useCustomModel(options: MLKitCustomModelOptions): Promise, probabilities: Array, maxResults = 5): Array {
+ const result: Array = [];
+ labels.forEach((text, i) => result.push({text, confidence: probabilities[i]}));
+ result.sort((a, b) => a.confidence < b.confidence ? 1 : (a.confidence === b.confidence ? 0 : -1));
+ if (result.length > maxResults) {
+ result.splice(maxResults);
+ }
+ result.map(r => r.confidence = (r.confidence & 0xff) / 255.0);
+ return result;
}
diff --git a/src/mlkit/custommodel/index.d.ts b/src/mlkit/custommodel/index.d.ts
index 39c08c8e..040ceb36 100644
--- a/src/mlkit/custommodel/index.d.ts
+++ b/src/mlkit/custommodel/index.d.ts
@@ -1,12 +1,48 @@
-import { MLKitCameraView, MLKitOptions, MLKitResult } from "../index";
+import { MLKitCameraView, MLKitVisionOptions, MLKitVisionResult } from "../index";
-export interface MLKitCustomModelResult extends MLKitResult {
- result: any;
+export interface MLKitCustomModelResultValue {
+ text: string;
+ confidence: number;
}
-export interface MLKitCustomModelOptions extends MLKitOptions {
+export interface MLKitCustomModelResult extends MLKitVisionResult {
+ result: Array;
+}
+
+export type MLKitCustomModelType = "FLOAT32" | "QUANT";
+
+export interface TNSCustomModelInput {
+ shape: Array,
+ type: MLKitCustomModelType
+}
+
+// see https://firebase.google.com/docs/ml-kit/ios/use-custom-models
+export interface MLKitCustomModelOptions extends MLKitVisionOptions {
+ localModelFile?: string;
+ labelsFile: string;
+ /**
+ * Default 5
+ */
+ maxResults?: number;
+ modelInput: Array
+ /**
+ * Ignoring this for now as we deduct it from the model spec.
+ */
+ // modelOutput?: Array<{
+ // shape: Array,
+ // type: MLKitCustomModelType
+ // }>
+ /**
+ * Never got this working, so not supporting it for now.
+ */
+ // cloudModelName?: string;
+ /**
+ * Default false
+ */
+ // requireWifiForCloudModelDownload?: boolean;
}
export declare function useCustomModel(options: MLKitCustomModelOptions): Promise;
-export declare class MLKitCustomModel extends MLKitCameraView {}
+export declare class MLKitCustomModel extends MLKitCameraView {
+}
diff --git a/src/mlkit/custommodel/index.ios.ts b/src/mlkit/custommodel/index.ios.ts
index bf2cee14..45fb4842 100644
--- a/src/mlkit/custommodel/index.ios.ts
+++ b/src/mlkit/custommodel/index.ios.ts
@@ -1,13 +1,77 @@
+import * as fs from "tns-core-modules/file-system";
import { ImageSource } from "tns-core-modules/image-source";
-import { MLKitCustomModelOptions, MLKitCustomModelResult } from "./";
-import { MLKitCustomModel as MLKitCustomModelBase } from "./custommodel-common";
+import { MLKitCustomModelOptions, MLKitCustomModelResult, MLKitCustomModelResultValue } from "./";
+import {
+ getLabelsFromAppFolder,
+ getLabelsFromFile,
+ MLKitCustomModel as MLKitCustomModelBase
+} from "./custommodel-common";
declare const TNSMLKitCameraView: any;
export class MLKitCustomModel extends MLKitCustomModelBase {
+ private modelInterpreter: FIRModelInterpreter;
+ private inputOutputOptions: FIRModelInputOutputOptions;
protected createDetector(): any {
- return getInterpreter();
+ this.modelInterpreter = getInterpreter(this.localModelFile);
+ return this.modelInterpreter;
+ }
+
+ runDetector(image: UIImage, onComplete: () => void): void {
+ const modelExpectsWidth = this.modelInputShape[1];
+ const modelExpectsHeight = this.modelInputShape[2];
+ const isQuantized = this.modelInputType !== "FLOAT32";
+
+ if (!this.inputOutputOptions) {
+ this.inputOutputOptions = FIRModelInputOutputOptions.new();
+ let inputType;
+ const arrIn = NSMutableArray.new();
+ this.modelInputShape.forEach(dim => arrIn.addObject(dim));
+ inputType = isQuantized ? FIRModelElementType.UInt8 : FIRModelElementType.Float32;
+ this.inputOutputOptions.setInputFormatForIndexTypeDimensionsError(0, inputType, arrIn);
+
+ const arrOut = NSMutableArray.new();
+ arrOut.addObject(1);
+ arrOut.addObject(this.labels.length);
+ this.inputOutputOptions.setOutputFormatForIndexTypeDimensionsError(0, inputType, arrOut);
+ }
+
+ let inputData: NSMutableData;
+ if (isQuantized) {
+ inputData = TNSMLKitCameraView.scaledDataWithSizeByteCountIsQuantized(image, CGSizeMake(modelExpectsWidth, modelExpectsHeight), modelExpectsWidth * modelExpectsHeight * this.modelInputShape[3] * this.modelInputShape[0], isQuantized);
+ } else {
+ // Note that this doesn't work correctly.. users should use quant (aka UInt8 aka BYTE)
+ inputData = TNSMLKitCameraView.getInputDataWithRowsAndColumnsAndType(image, modelExpectsWidth, modelExpectsHeight, "Float32");
+ }
+
+ const inputs = FIRModelInputs.new();
+ inputs.addInputError(inputData);
+
+ this.modelInterpreter.runWithInputsOptionsCompletion(inputs, this.inputOutputOptions, (outputs: FIRModelOutputs, error: NSError) => {
+ if (error !== null) {
+ console.log(error.localizedDescription);
+
+ } else if (outputs !== null) {
+ const probabilities: NSArray = outputs.outputAtIndexError(0)[0];
+
+ if (this.labels.length !== probabilities.count) {
+ console.log(`The number of labels (${this.labels.length}) is not equal to the interpretation result (${probabilities.count})!`);
+ onComplete();
+ } else {
+ const result = {
+ result: getSortedResult(this.labels, probabilities, this.maxResults)
+ };
+
+ this.notify({
+ eventName: MLKitCustomModel.scanResultEvent,
+ object: this,
+ value: result
+ });
+ }
+ }
+ onComplete();
+ })
}
protected createSuccessListener(): any {
@@ -36,80 +100,121 @@ export class MLKitCustomModel extends MLKitCustomModelBase {
}
}
-function getInterpreter(): FIRModelInterpreter {
- const fIRModelDownloadConditions = FIRModelDownloadConditions.alloc().initWithIsWiFiRequiredCanDownloadInBackground(false, true);
-
- const fIRCloudModelSource = FIRCloudModelSource.alloc().initWithModelNameEnableModelUpdatesInitialConditionsUpdateConditions(
- "my-custom-model",
- true,
- fIRModelDownloadConditions,
- fIRModelDownloadConditions);
-
- // const cloudModelRegistrationSuccess = FIRModelManager.modelManager().registerCloudModelSource(fIRCloudModelSource);
- // console.log("cloudModelRegistrationSuccess: " + cloudModelRegistrationSuccess);
+function getInterpreter(localModelFile: string): FIRModelInterpreter {
+ let localModelRegistrationSuccess = false;
+ let cloudModelRegistrationSuccess = false;
+ let localModelName;
+
+ if (localModelFile) {
+ localModelName = localModelFile.lastIndexOf("/") === -1 ? localModelFile : localModelFile.substring(localModelFile.lastIndexOf("/") + 1);
+
+ // make sure we load the model (with the same name) only once
+ if (FIRModelManager.modelManager().localModelWithName(localModelName)) {
+ localModelRegistrationSuccess = true;
+ } else {
+ let localModelFilePath: string;
+ if (localModelFile.indexOf("~/") === 0) {
+ localModelFilePath = fs.knownFolders.currentApp().path + localModelFile.substring(1);
+ } else {
+ localModelFilePath = NSBundle.mainBundle.pathForResourceOfType(
+ localModelFile.substring(0, localModelFile.lastIndexOf(".")),
+ localModelFile.substring(localModelFile.lastIndexOf(".") + 1));
+ }
+ const localModelSource = FIRLocalModel.alloc().initWithNamePath(localModelName, localModelFilePath);
+ localModelRegistrationSuccess = FIRModelManager.modelManager().registerLocalModel(localModelSource);
+ }
+ }
- loadLocalModel();
+ /*
+ if (options.cloudModelName) {
+ const fIRModelDownloadConditions = FIRModelDownloadConditions.alloc().initWithAllowsCellularAccessAllowsBackgroundDownloading(options.requireWifiForCloudModelDownload, true);
- const fIRModelOptions = FIRModelOptions.alloc().initWithCloudModelNameLocalModelName(
- // "my-custom-model",
- null,
- "mobilenet");
+ const fIRCloudModelSource = FIRRemoteModel.alloc().initWithNameAllowsModelUpdatesInitialConditionsUpdateConditions(
+ options.cloudModelName,
+ true,
+ fIRModelDownloadConditions,
+ fIRModelDownloadConditions);
- return FIRModelInterpreter.modelInterpreterWithOptions(fIRModelOptions);
-}
+ cloudModelRegistrationSuccess = FIRModelManager.modelManager().registerRemoteModel(fIRCloudModelSource);
+ console.log("cloudModelRegistrationSuccess: " + cloudModelRegistrationSuccess);
+ }
+ */
-function loadLocalModel(): void {
- const localModelFilePath = NSBundle.mainBundle.pathForResourceOfType("mobilenet_quant_v1_224", "tflite");
- console.log(">>> localModelFilePath: " + localModelFilePath);
+ if (!localModelRegistrationSuccess && !cloudModelRegistrationSuccess) {
+ // TODO handle this case upstream
+ console.log("No (cloud or local) model was successfully loaded.");
+ return null;
+ }
- const localModelSource = FIRLocalModelSource.alloc().initWithModelNamePath("mobilenet", localModelFilePath);
- console.log(">>> localModelSource: " + localModelSource);
+ const fIRModelOptions = FIRModelOptions.alloc().initWithRemoteModelNameLocalModelName(
+ null, // cloudModelRegistrationSuccess ? cloudModelName : null,
+ localModelRegistrationSuccess ? localModelName : null);
- const localModelRegistrationSuccess = FIRModelManager.modelManager().registerLocalModelSource(localModelSource);
- console.log("localModelRegistrationSuccess: " + localModelRegistrationSuccess);
+ return FIRModelInterpreter.modelInterpreterWithOptions(fIRModelOptions);
}
export function useCustomModel(options: MLKitCustomModelOptions): Promise {
return new Promise((resolve, reject) => {
try {
- const modelInterpreter = getInterpreter();
- const inputs = FIRModelInputs.new();
const image: UIImage = options.image instanceof ImageSource ? options.image.ios : options.image.imageSource.ios;
- // note that there's a LoC in this native function that crashes the app (see the code for details)
- const resizedImg = TNSMLKitCameraView.resizeImage(image);
- const successAddingInput = inputs.addInputError(resizedImg);
+ const isQuant = options.modelInput[0].type !== "FLOAT32";
+
+ let inputData: NSMutableData;
+ if (isQuant) {
+ inputData = TNSMLKitCameraView.scaledDataWithSizeByteCountIsQuantized(
+ image, CGSizeMake(options.modelInput[0].shape[1], options.modelInput[0].shape[2]), options.modelInput[0].shape[1] * options.modelInput[0].shape[2] * options.modelInput[0].shape[3] * options.modelInput[0].shape[0], options.modelInput[0].type !== "FLOAT32");
+ } else {
+ // Note that this doesn't work correctly.. users should use quant (aka UInt8 aka BYTE)
+ inputData = TNSMLKitCameraView.getInputDataWithRowsAndColumnsAndType(
+ image, options.modelInput[0].shape[1], options.modelInput[0].shape[2], "Float32");
+ }
+
+ const inputs = FIRModelInputs.new();
+ inputs.addInputError(inputData);
const inputOptions = FIRModelInputOutputOptions.new();
- const arrIn = NSMutableArray.new();
- arrIn.addObject(1);
- arrIn.addObject(image.size.width);
- arrIn.addObject(image.size.height);
- arrIn.addObject(3);
+ let inputType;
+ options.modelInput.forEach((dimensionAndType, i) => {
+ const arrIn = NSMutableArray.new();
+ dimensionAndType.shape.forEach(dim => arrIn.addObject(dim));
+ inputType = dimensionAndType.type === "FLOAT32" ? FIRModelElementType.Float32 : FIRModelElementType.UInt8;
+ inputOptions.setInputFormatForIndexTypeDimensionsError(i, inputType, arrIn);
+ });
+
+ let labels: Array;
+ if (options.labelsFile.indexOf("~/") === 0) {
+ labels = getLabelsFromAppFolder(options.labelsFile);
+ } else {
+ const labelsFile = NSBundle.mainBundle.pathForResourceOfType(
+ options.labelsFile.substring(0, options.labelsFile.lastIndexOf(".")),
+ options.labelsFile.substring(options.labelsFile.lastIndexOf(".") + 1));
+ labels = getLabelsFromFile(labelsFile);
+ }
const arrOut = NSMutableArray.new();
arrOut.addObject(1);
- arrOut.addObject(1001);
-
- inputOptions.setInputFormatForIndexTypeDimensionsError(0, FIRModelElementType.UInt8, arrIn);
- inputOptions.setOutputFormatForIndexTypeDimensionsError(0, FIRModelElementType.UInt8, arrOut);
+ arrOut.addObject(labels.length);
+ inputOptions.setOutputFormatForIndexTypeDimensionsError(0, inputType, arrOut);
+ const modelInterpreter = getInterpreter(options.localModelFile);
modelInterpreter.runWithInputsOptionsCompletion(inputs, inputOptions, (outputs: FIRModelOutputs, error: NSError) => {
- console.log(">>> error: " + error);
- console.log(">>> outputs: " + outputs);
-
if (error !== null) {
reject(error.localizedDescription);
} else if (outputs !== null) {
- console.log(">>> outputs.count: " + outputs.outputAtIndexError(0));
+ const probabilities: NSArray = outputs.outputAtIndexError(0)[0];
+
+ if (labels.length !== probabilities.count) {
+ console.log(`The number of labels in ${options.labelsFile} (${labels.length}) is not equal to the interpretation result (${probabilities.count})!`);
+ return;
+ }
+
const result = {
- result: []
+ result: getSortedResult(labels, probabilities, options.maxResults)
};
- console.log(">>> outputs: " + outputs);
-
resolve(result);
}
});
@@ -119,3 +224,15 @@ export function useCustomModel(options: MLKitCustomModelOptions): Promise, probabilities: NSArray, maxResults = 5): Array {
+ const result: Array