diff --git a/README.md b/README.md index fdc6ab5..f71914f 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ Reads the document referred to by this DocumnentQuery< updateDocument(options: UpdateDocument) => Promise ``` -Updates fields in the document referred to by the specified DocumnentQuery. +Updates fields in the document referred to by the specified DocumnentQuery. The update will fail if applied to a document that does not exist. | Param | Type | @@ -130,8 +130,8 @@ The update will fail if applied to a document that does not exist. setDocument(options: SetDocument) => Promise ``` -Writes to the document referred to by the specified DocumnentQuery. -If the document does not yet exist, it will be created. +Writes to the document referred to by the specified DocumnentQuery. +If the document does not yet exist, it will be created. If you provide merge or mergeFields, the provided data can be merged into an existing document. | Param | Type | @@ -162,7 +162,7 @@ Deletes the document referred to by the specified Docu addDocument(options: AddDocument) => Promise ``` -Add a new document to specified `CollectionQuery` with the given data, +Add a new document to specified `CollectionQuery` with the given data, assigning it a document ID automatically. | Param | Type | @@ -259,7 +259,7 @@ Remove all active snapshot listners enableNetwork() => Promise ``` -Re-enables use of the network for this Firestore instance after a prior +Re-enables use of the network for this Firestore instance after a prior call to {@link disableNetwork}. -------------------- @@ -271,9 +271,9 @@ call to {@link disableNetwork}. disableNetwork() => Promise ``` -Disables network usage for this instance. It can be re-enabled via {@link enableNetwork}. -While the network is disabled, any snapshot listeners, {@link getDocument} -or {@link getCollection} calls will return results from cache, and any write +Disables network usage for this instance. It can be re-enabled via {@link enableNetwork}. +While the network is disabled, any snapshot listeners, {@link getDocument} +or {@link getCollection} calls will return results from cache, and any write operations will be queued until the network is restored. -------------------- @@ -365,7 +365,7 @@ operations will be queued until the network is restored. #### QueryConstraint -A `QueryConstraint` is used to narrow the set of documents returned by a +A `QueryConstraint` is used to narrow the set of documents returned by a Firestore query. | Prop | Type | Description | @@ -404,7 +404,7 @@ Make all properties in T optional #### QueryOperators -Filter conditions in a {@link QueryConstraint} clause are specified using the +Filter conditions in a {@link QueryConstraint} clause are specified using the strings '<', '<=', '==', '>=', '>', 'array-contains' "==" | ">=" | "<=" | "<" | ">" | "array-contains" diff --git a/android/src/main/java/com/proteansoftware/capacitor/firestore/CapacitorFirestore.java b/android/src/main/java/com/proteansoftware/capacitor/firestore/CapacitorFirestore.java index c2dae09..6b5234e 100644 --- a/android/src/main/java/com/proteansoftware/capacitor/firestore/CapacitorFirestore.java +++ b/android/src/main/java/com/proteansoftware/capacitor/firestore/CapacitorFirestore.java @@ -334,4 +334,12 @@ public Task enableNetwork() { public Task disableNetwork() { return this.db.enableNetwork(); } + + public FirebaseApp getApp() { + return this.app; + } + + public FirebaseFirestore getFirestore() { + return this.db; + } } diff --git a/android/src/main/java/com/proteansoftware/capacitor/firestore/CapacitorFirestorePlugin.java b/android/src/main/java/com/proteansoftware/capacitor/firestore/CapacitorFirestorePlugin.java index 66bdda2..ce957e1 100644 --- a/android/src/main/java/com/proteansoftware/capacitor/firestore/CapacitorFirestorePlugin.java +++ b/android/src/main/java/com/proteansoftware/capacitor/firestore/CapacitorFirestorePlugin.java @@ -29,6 +29,7 @@ public class CapacitorFirestorePlugin extends Plugin { private CapacitorFirestore implementation = new CapacitorFirestore(); private Map listeners = new HashMap<>(); + private ArrayList resolvedListeners = new ArrayList<>(); private int pendingActions = 0; @Override @@ -91,6 +92,7 @@ public void clearAllSnapshotListeners(PluginCall call) { listener.remove(); } + resolvedListeners.addAll(listeners.keySet()); listeners = new HashMap<>(); call.resolve(); @@ -105,6 +107,11 @@ public void removeSnapshotListener(PluginCall call) { return; } + if (resolvedListeners.contains(callbackId)) { + call.resolve(); + return; + } + ListenerRegistration listener = listeners.get(callbackId); if (listener == null) { @@ -114,6 +121,7 @@ public void removeSnapshotListener(PluginCall call) { listener.remove(); listeners.remove(callbackId); + resolvedListeners.add(callbackId); call.resolve(); } @@ -412,6 +420,21 @@ public void disableNetwork(PluginCall call) { ); } + @PluginMethod + public void getApp(PluginCall call) { + JSObject object = new JSObject(); + object.put("app", implementation.getApp()); + call.resolve(object); + } + + @PluginMethod + public void getApp(PluginCall call) { + implementation.getApp(); + JSObject object = new JSObject(); + object.put("firestore", implementation.getFirestore()); + call.resolve(object); + } + private HashMap mapJSObject(JSONObject jsObject) throws JSONException { HashMap mapData = new HashMap<>(); Iterator keys = jsObject.keys(); diff --git a/ios/Plugin/CapacitorFirestore.swift b/ios/Plugin/CapacitorFirestore.swift index 9c0fada..21b36b8 100644 --- a/ios/Plugin/CapacitorFirestore.swift +++ b/ios/Plugin/CapacitorFirestore.swift @@ -359,6 +359,20 @@ enum CapacitorFirestoreError: Error { self.db?.settings = settings } } + + + @objc public func getApp() -> JSObject { + var result = JSObject() + result["app"] = self.app + return result + } + + + @objc public func getFirestore() -> JSObject { + var result = JSObject() + result["firestore"] = self.db + return result + } private func configure() throws { if self.app != nil { diff --git a/ios/Plugin/CapacitorFirestorePlugin.m b/ios/Plugin/CapacitorFirestorePlugin.m index 58a0c04..a52ce50 100644 --- a/ios/Plugin/CapacitorFirestorePlugin.m +++ b/ios/Plugin/CapacitorFirestorePlugin.m @@ -17,6 +17,8 @@ CAP_PLUGIN_METHOD(getCollection, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(enableNetwork, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(disableNetwork, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(getApp, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(getFirestore, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(addDocumentSnapshotListener, CAPPluginReturnCallback); CAP_PLUGIN_METHOD(addCollectionSnapshotListener, CAPPluginReturnCallback); ) diff --git a/ios/Plugin/CapacitorFirestorePlugin.swift b/ios/Plugin/CapacitorFirestorePlugin.swift index d4ce142..25fd6ad 100644 --- a/ios/Plugin/CapacitorFirestorePlugin.swift +++ b/ios/Plugin/CapacitorFirestorePlugin.swift @@ -10,6 +10,7 @@ import FirebaseFirestore public class CapacitorFirestorePlugin: CAPPlugin { private let implementation = CapacitorFirestore() private var listeners: [ String: ListenerRegistration ] = [:] + private var resolvedListenders: [String] = [] private var pendingActions: Int = 0 override public func load() { @@ -74,14 +75,15 @@ public class CapacitorFirestorePlugin: CAPPlugin { call.reject("Unknown error", nil, error, [:]) } } - + @objc func clearAllSnapshotListeners(_ call: CAPPluginCall) { listeners.values.forEach { ListenerRegistration in - ListenerRegistration.remove(); + ListenerRegistration.remove() } - + + resolvedListenders.append(contentsOf: listeners.keys) listeners = [:] - + call.resolve() } @@ -93,6 +95,11 @@ public class CapacitorFirestorePlugin: CAPPlugin { return } + if resolvedListenders.contains(callbackId!) { + call.resolve() + return + } + let listener = listeners[callbackId!] if listener == nil { @@ -101,6 +108,7 @@ public class CapacitorFirestorePlugin: CAPPlugin { } listener?.remove() + resolvedListenders.append(callbackId!) call.resolve() } diff --git a/package-lock.json b/package-lock.json index 08c3de2..9a9abf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@proteansoftware/capacitor-firestore", - "version": "4.0.0", + "version": "4.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@proteansoftware/capacitor-firestore", - "version": "4.0.0", + "version": "4.1.0", "license": "MIT", "devDependencies": { "@capacitor/android": "^4.0.0", diff --git a/package.json b/package.json index 5565172..9702b79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@proteansoftware/capacitor-firestore", - "version": "4.0.0", + "version": "4.1.0", "description": "Capacitor Plugin for Native Firestore", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", diff --git a/src/definitions.ts b/src/definitions.ts index c2578b9..354f354 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -1,6 +1,8 @@ /* eslint-disable no-prototype-builtins */ /// +import type { FirebaseApp } from "@firebase/app"; +import type { Firestore } from "@firebase/firestore"; import { Timestamp } from "firebase/firestore"; declare module "@capacitor/cli" { @@ -51,11 +53,7 @@ export type QueryOperators = "==" | ">=" | "<=" | "<" | ">" | "array-contains"; * @param value The value for comparison * @returns The created {@link QueryConstraint}. */ -export function createQueryConstraint( - field: string, - operator: QueryOperators, - value: any, -): QueryConstraint { +export function createQueryConstraint(field: string, operator: QueryOperators, value: any): QueryConstraint { return { fieldPath: field, opStr: operator, @@ -66,7 +64,7 @@ export function createQueryConstraint( export function prepDataForFirestore(data: T): T { for (const prop in data) { if (data[prop] instanceof Timestamp) { - const timestamp = (data[prop] as unknown) as Timestamp; + const timestamp = data[prop] as unknown as Timestamp; data[prop] = { specialType: "Timestamp", seconds: timestamp.seconds, @@ -204,15 +202,9 @@ export interface PendingActions { count: number; } -export type DocumentSnapshotCallback = ( - data: DocumentSnapshot | null, - err?: any, -) => void; +export type DocumentSnapshotCallback = (data: DocumentSnapshot | null, err?: any) => void; -export type CollectionSnapshotCallback = ( - data: CollectionSnapshot | null, - err?: any, -) => void; +export type CollectionSnapshotCallback = (data: CollectionSnapshot | null, err?: any) => void; export interface CapacitorFirestorePlugin { /** @@ -287,10 +279,7 @@ export interface CapacitorFirestorePlugin { * @param callback * @returns The callback id which can be used to remove the listener. */ - addDocumentSnapshotListener( - options: DocumnentQuery, - callback: DocumentSnapshotCallback, - ): Promise; + addDocumentSnapshotListener(options: DocumnentQuery, callback: DocumentSnapshotCallback): Promise; /** * Executes the query and returns the results as a CollectionSnapshot @@ -307,7 +296,7 @@ export interface CapacitorFirestorePlugin { */ addCollectionSnapshotListener( options: CollectionQuery, - callback: CollectionSnapshotCallback, + callback: CollectionSnapshotCallback ): Promise; /** @@ -338,4 +327,14 @@ export interface CapacitorFirestorePlugin { * @returns A `Promise` that is resolved once the network has been disabled. */ disableNetwork(): Promise; + + /** + * Gets the current Firebase App + */ + getApp(): { app: FirebaseApp | null; }; + + /** + * Gets the current Firestore database + */ + getFirestore(): { firestore: Firestore | null; }; } diff --git a/src/index.ts b/src/index.ts index 17155b2..f3fc116 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,23 +1,11 @@ import { registerPlugin } from "@capacitor/core"; -import { - createQueryConstraint, - prepDataForFirestore, - processDocumentData, -} from "./definitions"; +import { createQueryConstraint, prepDataForFirestore, processDocumentData } from "./definitions"; import type { CapacitorFirestorePlugin } from "./definitions"; -const CapacitorFirestore = registerPlugin( - "CapacitorFirestore", - { - web: () => import("./web").then(m => new m.CapacitorFirestoreWeb()), - }, -); +const CapacitorFirestore = registerPlugin("CapacitorFirestore", { + web: () => import("./web").then((m) => new m.CapacitorFirestoreWeb()), +}); export * from "./definitions"; -export { - CapacitorFirestore, - createQueryConstraint, - prepDataForFirestore, - processDocumentData, -}; +export { CapacitorFirestore, createQueryConstraint, prepDataForFirestore, processDocumentData }; diff --git a/src/web.ts b/src/web.ts index 72baca6..a2815d3 100644 --- a/src/web.ts +++ b/src/web.ts @@ -41,9 +41,7 @@ import type { PendingActions, } from "./definitions"; -export class CapacitorFirestoreWeb - extends WebPlugin - implements CapacitorFirestorePlugin { +export class CapacitorFirestoreWeb extends WebPlugin implements CapacitorFirestorePlugin { private app: FirebaseApp | null = null; private firestore: Firestore | null = null; @@ -70,7 +68,7 @@ export class CapacitorFirestoreWeb appId: options.applicationId, projectId: options.projectId, }, - "CapacitorFirestore", + "CapacitorFirestore" ); this.firestore = initializeFirestore(this.app, { @@ -82,22 +80,19 @@ export class CapacitorFirestoreWeb public addDocumentSnapshotListener( options: DocumnentQuery, - callback: DocumentSnapshotCallback, + callback: DocumentSnapshotCallback ): Promise { if (this.firestore === null) { return Promise.reject("Firestore not initialized"); } - const unSubFunc = onSnapshot( - doc(this.firestore, options.reference), - snapshot => { - callback({ - id: snapshot.id, - path: snapshot.ref.path, - data: snapshot.exists() ? (snapshot.data() as T) : null, - }); - }, - ); + const unSubFunc = onSnapshot(doc(this.firestore, options.reference), (snapshot) => { + callback({ + id: snapshot.id, + path: snapshot.ref.path, + data: snapshot.exists() ? (snapshot.data() as T) : null, + }); + }); const id = new Date().getTime().toString(); this.subscriptions[id] = unSubFunc; @@ -130,7 +125,7 @@ export class CapacitorFirestoreWeb .then(() => { this.pendingActions--; }) - .catch(err => { + .catch((err) => { this.pendingActions--; reject(err); }); @@ -152,7 +147,7 @@ export class CapacitorFirestoreWeb .then(() => { this.pendingActions--; }) - .catch(err => { + .catch((err) => { this.pendingActions--; reject(err); }); @@ -172,7 +167,7 @@ export class CapacitorFirestoreWeb .then(() => { this.pendingActions--; }) - .catch(err => { + .catch((err) => { this.pendingActions--; reject(err); }); @@ -193,7 +188,7 @@ export class CapacitorFirestoreWeb .then(() => { this.pendingActions--; }) - .catch(err => { + .catch((err) => { this.pendingActions--; reject(err); }); @@ -207,7 +202,7 @@ export class CapacitorFirestoreWeb public addCollectionSnapshotListener( options: CollectionQuery, - callback: CollectionSnapshotCallback, + callback: CollectionSnapshotCallback ): Promise { if (this.firestore === null) { return Promise.reject("Firestore not initialized"); @@ -215,20 +210,17 @@ export class CapacitorFirestoreWeb let collectionQuery: Query; if (options.queryConstraints) { - const constraints = options.queryConstraints.map(constraint => - where(constraint.fieldPath, constraint.opStr, constraint.value), - ); - collectionQuery = query( - collection(this.firestore, options.reference), - ...constraints, + const constraints = options.queryConstraints.map((constraint) => + where(constraint.fieldPath, constraint.opStr, constraint.value) ); + collectionQuery = query(collection(this.firestore, options.reference), ...constraints); } else { collectionQuery = query(collection(this.firestore, options.reference)); } - const unSubFunc = onSnapshot(collectionQuery, snapshot => { + const unSubFunc = onSnapshot(collectionQuery, (snapshot) => { callback({ - collection: snapshot.docs.map(doc => { + collection: snapshot.docs.map((doc) => { return { id: doc.id, path: doc.ref.path, @@ -244,22 +236,17 @@ export class CapacitorFirestoreWeb return Promise.resolve(id); } - public async getCollection( - options: CollectionQuery, - ): Promise> { + public async getCollection(options: CollectionQuery): Promise> { if (this.firestore === null) { return Promise.reject("Firestore not initialized"); } let collectionQuery: Query; if (options.queryConstraints) { - const constraints = options.queryConstraints.map(constraint => - where(constraint.fieldPath, constraint.opStr, constraint.value), - ); - collectionQuery = query( - collection(this.firestore, options.reference), - ...constraints, + const constraints = options.queryConstraints.map((constraint) => + where(constraint.fieldPath, constraint.opStr, constraint.value) ); + collectionQuery = query(collection(this.firestore, options.reference), ...constraints); } else { collectionQuery = query(collection(this.firestore, options.reference)); } @@ -267,7 +254,7 @@ export class CapacitorFirestoreWeb const snapshot = await getDocs(collectionQuery); return { - collection: snapshot.docs.map(doc => { + collection: snapshot.docs.map((doc) => { return { id: doc.id, path: doc.ref.path, @@ -277,9 +264,7 @@ export class CapacitorFirestoreWeb }; } - public removeSnapshotListener( - options: RemoveSnapshotListener, - ): Promise { + public removeSnapshotListener(options: RemoveSnapshotListener): Promise { const unSubFunc = this.subscriptions[options.callbackId]; if (unSubFunc === undefined) { @@ -336,4 +321,12 @@ export class CapacitorFirestoreWeb return disableNetwork(this.firestore); } + + public getApp(): { app: FirebaseApp | null; } { + return { app: this.app }; + } + + public getFirestore(): { firestore: Firestore | null; } { + return { firestore: this.firestore }; + } }