├── .eslintrc.js ├── .gitignore ├── CHANGELOGS.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── ISSUE_TEMPLATE.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── firestore.gradle ├── jest.config.js ├── package-lock.json ├── package.json ├── plugin.xml ├── src ├── android │ ├── test │ │ └── uk │ │ │ └── co │ │ │ └── reallysmall │ │ │ └── cordova │ │ │ └── plugin │ │ │ └── firestore │ │ │ └── LimitQueryHandlerTest.java │ └── uk │ │ └── co │ │ └── reallysmall │ │ └── cordova │ │ └── plugin │ │ └── firestore │ │ ├── ActionHandler.java │ │ ├── BatchCommitHandler.java │ │ ├── BatchDocDeleteHandler.java │ │ ├── BatchDocSetHandler.java │ │ ├── BatchDocUpdateHandler.java │ │ ├── BatchHandler.java │ │ ├── CollectionAddHandler.java │ │ ├── CollectionGetHandler.java │ │ ├── CollectionOnSnapshotHandler.java │ │ ├── CollectionUnsubscribeHandler.java │ │ ├── DocDeleteHandler.java │ │ ├── DocGetHandler.java │ │ ├── DocOnSnapshotHandler.java │ │ ├── DocSetHandler.java │ │ ├── DocSetOptions.java │ │ ├── DocUnsubscribeHandler.java │ │ ├── DocUpdateHandler.java │ │ ├── EndAtQueryHandler.java │ │ ├── EndBeforeQueryHandler.java │ │ ├── FieldPathHelper.java │ │ ├── FieldValueHelper.java │ │ ├── FirestoreLog.java │ │ ├── FirestorePlugin.java │ │ ├── InitialiseHandler.java │ │ ├── JSONDateWrapper.java │ │ ├── JSONGeopointWrapper.java │ │ ├── JSONHelper.java │ │ ├── JSONReferenceWrapper.java │ │ ├── JSONTimestampWrapper.java │ │ ├── LimitQueryHandler.java │ │ ├── OrderByQueryHandler.java │ │ ├── PluginResultHelper.java │ │ ├── QueryHandler.java │ │ ├── QueryHelper.java │ │ ├── RunTransactionHandler.java │ │ ├── StartAfterQueryHandler.java │ │ ├── StartAtQueryHandler.java │ │ ├── TransactionDetails.java │ │ ├── TransactionDocDeleteHandler.java │ │ ├── TransactionDocGetHandler.java │ │ ├── TransactionDocSetHandler.java │ │ ├── TransactionDocUpdateHandler.java │ │ ├── TransactionOperationType.java │ │ ├── TransactionQueue.java │ │ ├── TransactionResolveHandler.java │ │ └── WhereQueryHandler.java └── ios │ ├── FirestorePlugin.h │ ├── FirestorePlugin.m │ ├── FirestorePluginJSONHelper.h │ ├── FirestorePluginJSONHelper.m │ ├── FirestorePluginResultHelper.h │ ├── FirestorePluginResultHelper.m │ ├── FirestoreTransaction.h │ └── FirestoreTransaction.m ├── types └── index.d.ts └── www ├── __mocks__ └── cordova │ ├── exec.js │ └── utils.js ├── android_ios ├── batch.js ├── collection_reference.js ├── collection_reference.test.js ├── document_reference.js ├── document_reference.test.js ├── document_snapshot.js ├── firestore.js ├── firestore_timestamp.js ├── geo_point.js ├── path.js ├── path.test.js ├── query.js ├── query_document_snapshot.js ├── query_snapshot.js ├── query_snapshot.test.js ├── transaction.js ├── utilities.js └── utils.js └── browser └── firestore.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true, 7 | "jest": true 8 | }, 9 | "extends": "eslint:recommended", 10 | "globals": { 11 | "Atomics": "readonly", 12 | "SharedArrayBuffer": "readonly" 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2018 16 | }, 17 | "rules": { 18 | "accessor-pairs": "error", 19 | "array-bracket-newline": "off", 20 | "array-bracket-spacing": [ 21 | "error", 22 | "never" 23 | ], 24 | "array-callback-return": "error", 25 | "array-element-newline": "off", 26 | "arrow-body-style": "error", 27 | "arrow-parens": "error", 28 | "arrow-spacing": [ 29 | "error", 30 | { 31 | "after": true, 32 | "before": true 33 | } 34 | ], 35 | "block-scoped-var": "off", 36 | "block-spacing": "error", 37 | "brace-style": [ 38 | "error", 39 | "1tbs" 40 | ], 41 | "callback-return": "off", 42 | "camelcase": "error", 43 | "capitalized-comments": [ 44 | "error", 45 | "never" 46 | ], 47 | "class-methods-use-this": "error", 48 | "comma-dangle": "error", 49 | "comma-spacing": [ 50 | "error", 51 | { 52 | "after": true, 53 | "before": false 54 | } 55 | ], 56 | "comma-style": [ 57 | "error", 58 | "last" 59 | ], 60 | "complexity": "error", 61 | "computed-property-spacing": [ 62 | "error", 63 | "never" 64 | ], 65 | "consistent-return": "error", 66 | "consistent-this": "error", 67 | "curly": "error", 68 | "default-case": "error", 69 | "dot-location": "error", 70 | "dot-notation": [ 71 | "error", 72 | { 73 | "allowKeywords": true 74 | } 75 | ], 76 | "eol-last": "error", 77 | "eqeqeq": "error", 78 | "func-call-spacing": "error", 79 | "func-name-matching": "error", 80 | "func-names": "off", 81 | "func-style": "off", 82 | "function-paren-newline": "error", 83 | "generator-star-spacing": "error", 84 | "global-require": "error", 85 | "guard-for-in": "error", 86 | "handle-callback-err": "off", 87 | "id-blacklist": "error", 88 | "id-length": "off", 89 | "id-match": "error", 90 | "implicit-arrow-linebreak": "error", 91 | "indent": "off", 92 | "indent-legacy": "off", 93 | "init-declarations": "off", 94 | "jsx-quotes": "error", 95 | "key-spacing": "error", 96 | "keyword-spacing": [ 97 | "error", 98 | { 99 | "after": true, 100 | "before": true 101 | } 102 | ], 103 | "line-comment-position": "off", 104 | "linebreak-style": [ 105 | "error", 106 | "unix" 107 | ], 108 | "lines-around-comment": "error", 109 | "lines-around-directive": "error", 110 | "lines-between-class-members": "error", 111 | "max-classes-per-file": "error", 112 | "max-depth": "error", 113 | "max-len": "off", 114 | "max-lines": "error", 115 | "max-lines-per-function": "error", 116 | "max-nested-callbacks": "error", 117 | "max-params": "error", 118 | "max-statements": "off", 119 | "max-statements-per-line": "error", 120 | "multiline-comment-style": "error", 121 | "new-cap": "error", 122 | "new-parens": "error", 123 | "newline-after-var": "off", 124 | "newline-before-return": "off", 125 | "newline-per-chained-call": "off", 126 | "no-alert": "error", 127 | "no-array-constructor": "error", 128 | "no-async-promise-executor": "error", 129 | "no-await-in-loop": "error", 130 | "no-bitwise": "error", 131 | "no-buffer-constructor": "error", 132 | "no-caller": "error", 133 | "no-catch-shadow": "error", 134 | "no-confusing-arrow": "error", 135 | "no-continue": "error", 136 | "no-div-regex": "error", 137 | "no-duplicate-imports": "error", 138 | "no-else-return": "off", 139 | "no-empty-function": "off", 140 | "no-eq-null": "error", 141 | "no-eval": "error", 142 | "no-extend-native": "off", 143 | "no-extra-bind": "error", 144 | "no-extra-label": "error", 145 | "no-extra-parens": "off", 146 | "no-floating-decimal": "error", 147 | "no-implicit-globals": "error", 148 | "no-implied-eval": "error", 149 | "no-inline-comments": "off", 150 | "no-inner-declarations": [ 151 | "error", 152 | "functions" 153 | ], 154 | "no-invalid-this": "error", 155 | "no-iterator": "error", 156 | "no-label-var": "error", 157 | "no-labels": "error", 158 | "no-lone-blocks": "error", 159 | "no-lonely-if": "error", 160 | "no-loop-func": "error", 161 | "no-magic-numbers": "off", 162 | "no-misleading-character-class": "error", 163 | "no-mixed-operators": "error", 164 | "no-mixed-requires": "error", 165 | "no-multi-assign": "error", 166 | "no-multi-spaces": "error", 167 | "no-multi-str": "error", 168 | "no-multiple-empty-lines": "error", 169 | "no-native-reassign": "error", 170 | "no-negated-condition": "error", 171 | "no-negated-in-lhs": "error", 172 | "no-nested-ternary": "error", 173 | "no-new": "error", 174 | "no-new-func": "error", 175 | "no-new-object": "error", 176 | "no-new-require": "error", 177 | "no-new-wrappers": "error", 178 | "no-octal-escape": "error", 179 | "no-param-reassign": "off", 180 | "no-path-concat": "error", 181 | "no-plusplus": [ 182 | "error", 183 | { 184 | "allowForLoopAfterthoughts": true 185 | } 186 | ], 187 | "no-process-env": "error", 188 | "no-process-exit": "error", 189 | "no-proto": "error", 190 | "no-prototype-builtins": "error", 191 | "no-restricted-globals": "error", 192 | "no-restricted-imports": "error", 193 | "no-restricted-modules": "error", 194 | "no-restricted-properties": "error", 195 | "no-restricted-syntax": "error", 196 | "no-return-assign": "error", 197 | "no-return-await": "error", 198 | "no-script-url": "error", 199 | "no-self-compare": "error", 200 | "no-sequences": "error", 201 | "no-shadow": "off", 202 | "no-shadow-restricted-names": "error", 203 | "no-spaced-func": "error", 204 | "no-sync": "error", 205 | "no-tabs": "error", 206 | "no-template-curly-in-string": "error", 207 | "no-ternary": "off", 208 | "no-throw-literal": "off", 209 | "no-trailing-spaces": "off", 210 | "no-undef-init": "error", 211 | "no-undefined": "off", 212 | "no-underscore-dangle": "off", 213 | "no-unmodified-loop-condition": "error", 214 | "no-unneeded-ternary": "error", 215 | "no-unused-expressions": "error", 216 | "no-use-before-define": "error", 217 | "no-useless-call": "error", 218 | "no-useless-catch": "error", 219 | "no-useless-computed-key": "error", 220 | "no-useless-concat": "error", 221 | "no-useless-constructor": "error", 222 | "no-useless-rename": "error", 223 | "no-useless-return": "off", 224 | "no-var": "off", 225 | "no-void": "error", 226 | "no-warning-comments": "error", 227 | "no-whitespace-before-property": "error", 228 | "no-with": "error", 229 | "nonblock-statement-body-position": "error", 230 | "object-curly-newline": "error", 231 | "object-curly-spacing": "error", 232 | "object-property-newline": "error", 233 | "object-shorthand": "off", 234 | "one-var": "off", 235 | "one-var-declaration-per-line": "error", 236 | "operator-assignment": "error", 237 | "operator-linebreak": "error", 238 | "padded-blocks": "off", 239 | "padding-line-between-statements": "error", 240 | "prefer-arrow-callback": "off", 241 | "prefer-const": "error", 242 | "prefer-destructuring": "off", 243 | "prefer-named-capture-group": "error", 244 | "prefer-numeric-literals": "error", 245 | "prefer-object-spread": "error", 246 | "prefer-promise-reject-errors": "off", 247 | "prefer-reflect": "off", 248 | "prefer-rest-params": "error", 249 | "prefer-spread": "error", 250 | "prefer-template": "off", 251 | "quote-props": "off", 252 | "quotes": "off", 253 | "radix": [ 254 | "error", 255 | "as-needed" 256 | ], 257 | "require-atomic-updates": "error", 258 | "require-await": "error", 259 | "require-jsdoc": "off", 260 | "require-unicode-regexp": "error", 261 | "rest-spread-spacing": "error", 262 | "semi": "error", 263 | "semi-spacing": [ 264 | "error", 265 | { 266 | "after": true, 267 | "before": false 268 | } 269 | ], 270 | "semi-style": [ 271 | "error", 272 | "last" 273 | ], 274 | "sort-imports": "error", 275 | "sort-keys": "off", 276 | "sort-vars": "error", 277 | "space-before-blocks": "error", 278 | "space-before-function-paren": "off", 279 | "space-in-parens": [ 280 | "error", 281 | "never" 282 | ], 283 | "space-infix-ops": "error", 284 | "space-unary-ops": "error", 285 | "spaced-comment": [ 286 | "error", 287 | "always" 288 | ], 289 | "strict": [ 290 | "error", 291 | "never" 292 | ], 293 | "switch-colon-spacing": "error", 294 | "symbol-description": "error", 295 | "template-curly-spacing": "error", 296 | "template-tag-spacing": "error", 297 | "unicode-bom": [ 298 | "error", 299 | "never" 300 | ], 301 | "valid-jsdoc": "error", 302 | "vars-on-top": "off", 303 | "wrap-iife": "error", 304 | "wrap-regex": "error", 305 | "yield-star-spacing": "error", 306 | "yoda": [ 307 | "error", 308 | "never" 309 | ] 310 | } 311 | }; 312 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .git.safe 2 | /node_modules 3 | .DS_Store 4 | coverage/ 5 | .vscode 6 | -------------------------------------------------------------------------------- /CHANGELOGS.md: -------------------------------------------------------------------------------- 1 | # History 2 | ## 4.1.1 3 | - Fixed Javascript onSnapshot error handling 4 | 5 | ## 4.1.0 6 | - Ensure Android errors are mapped to Javascript 7 | - Ensure iOS errors are mapped to Javascript 8 | - Fix problem with JS snapshot error handlers not being called 9 | 10 | ## 4.0.0 11 | - Amended typescript support 12 | - Updated android firebase dependencies 13 | - Migrate to androidx 14 | - Added in, array-contains and array-contains-any to where method 15 | - Updated Javascript version from 5.5.0 to 7.6.0 16 | - Implemented FieldValue.increment(n), FieldValue.arrayRemove(elements), FieldValue.arrayUnion(elements) 17 | - BREAKING CHANGE: DocumentReference.onSnapshot mimics native Javascritp SDK more accurately 18 | - Add Firestore.totalReads() in Android and iOS to help debug usage 19 | 20 | ## 3.1.0 21 | - Added type definitions 22 | 23 | ## 3.0.0 24 | ### User-facing improvements 25 | - Breaking change: QuerySnapshot.docs() now wraps documents in QueryDocumentSnapshots like firebase-js-sdk 26 | - Revert minimum cordova-ios to 4.5.0 and cordova-android to 7.0.0 27 | - Firestore.doc() improvement, can now call .parent correctly on first-level documents 28 | - CollectionReference.doc() supports id generation when no argument is passed 29 | - Native logs improvement 30 | 31 | ### Technical improvements 32 | - Rename javascript imports to be consistent with file names, this allows running 33 | the JavaScript when running tests 34 | - Add tests for the new features and refactoring 35 | 36 | ## 2.0.0 37 | - Updated README 38 | - Updated Android dependencies 39 | - Added full nested collection implementation - may break backwards compatibility 40 | - Added doc() support to Firestore class 41 | - Add parent to DocumentReference and CollectionReference 42 | - Fix podspec for cordova-ios 5 43 | 44 | ## 1.3.2 45 | - Refactor README 46 | - Reset some significant breaking changes 47 | - Make sure jshint task passes 48 | - Implement Geopoint and Timestamp 49 | 50 | ## 1.3.1 51 | - Organize file structure. 52 | - Normalize initialize options. 53 | 54 | ## 1.3.0 55 | - Merge multi-project config changes 56 | - Merge sub document changes 57 | - Update Web SDK reference to 5.2.0 58 | - Introduce QueryDataSnapshot 59 | - Implement timestampsInSnapshots option in configuration 60 | 61 | ## 1.2.0 62 | - Update Android dependency versions 63 | - Update iOS dependency versions 64 | - Update plugin dependencies 65 | - WARNING: The Android update may require you to update com.google.gms:google-services to 4.0.0, com.android.tools.build:gradle to 3.1.2 and gradle to 4.4.4 (look in platforms/android/cordova/lib/builders/GradleBuilder.js) 66 | 67 | ## 1.1.0 68 | - Add support for FieldValue 69 | - Add experimental support for Transactions. _Please note this is **experimental**!_ 70 | - Add startswith polyfill 71 | 72 | ## 1.0.10 73 | - Correct log level when creating results 74 | 75 | ## 1.0.9 76 | - Updated Dependencies 77 | - Remove incorrect Java 7 dependency 78 | 79 | ## 1.0.8 80 | - Ensure dates work for queries and nested data 81 | - Implement delete() 82 | - Update README 83 | 84 | ## 1.0.7 85 | - Remove dependency on cordova-plugin-firebase-hooks 86 | 87 | ## 1.0.6 88 | - Correct README History 89 | - Make browser firebase dependency loading smarter 90 | 91 | ## 1.0.5 92 | ## 1.0.4 93 | ## 1.0.3 94 | - Address plugin dependency issues 95 | 96 | ## 1.0.2 97 | - Updated version 98 | - Added firebase hooks dependency 99 | - Corrected iOS source/header-file config 100 | 101 | ## 1.0.0 102 | Initial release 103 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at richard.windley@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | We want to make contributing to this project as easy and transparent as possible, whether it's: 3 | 4 | - Reporting a bug 5 | - Discussing the current state of the code 6 | - Submitting a fix 7 | - Proposing new features 8 | 9 | ## We Develop with Github 10 | We use github to host code, to track issues and feature requests, as well as accept pull requests. 11 | 12 | ## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html), So All Code Changes Happen Through Pull Requests 13 | Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests: 14 | 15 | 1. Fork the repo and create your branch from `master`. 16 | 2. If you've added code that should be tested, add tests. 17 | 3. If you've changed APIs, update the documentation. 18 | 4. Ensure the test suite passes. 19 | 5. Make sure your code lints. 20 | 6. Issue that pull request! 21 | 22 | ## Any contributions you make will be under the licence specified in the LICENSE file 23 | In short, when you submit code changes, your submissions are understood to be under the same License that covers the project. Feel free to contact the maintainers if that's a concern. 24 | 25 | ## Report bugs using Github's [issues](https://github.com/ReallySmallSoftware/cordova-plugin-firestore/issues) 26 | We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/ReallySmallSoftware/cordova-plugin-firestore/issues/new); it's that easy! 27 | 28 | ## Write bug reports with detail, background, and sample code where appropriate 29 | Make sure at the very least you provide the information in the [issue template](https://github.com/ReallySmallSoftware/cordova-plugin-firestore/blob/master/ISSUE_TEMPLATE.md) 30 | 31 | ## Use a Consistent Coding Style 32 | The coding style should match what is present in the code currently. Do not reformat entire files. 33 | 34 | ## References 35 | This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md) 36 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Project configuration. 4 | grunt.initConfig({ 5 | jshint: { 6 | options: { 7 | curly: true, 8 | eqeqeq: true, 9 | immed: true, 10 | latedef: true, 11 | newcap: true, 12 | noarg: true, 13 | sub: true, 14 | undef: true, 15 | boss: true, 16 | eqnull: true, 17 | node: true, 18 | es5: true, 19 | globals: { 20 | jasmine: false, 21 | describe: false, 22 | beforeEach: false, 23 | afterEach: false, 24 | expect: false, 25 | it: false, 26 | spyOn: false, 27 | $: false, 28 | cordova: false, 29 | launchnavigator: false, 30 | window: false, 31 | document: false, 32 | ons: false, 33 | navigator: false, 34 | google: false, 35 | FCMPlugin: false, 36 | device: false, 37 | plugins: false, 38 | addFixture: false, 39 | truncateSql: false 40 | } 41 | }, 42 | all: ['Gruntfile.js', 'www/**/*.js'] 43 | } 44 | }); 45 | 46 | grunt.loadNpmTasks('grunt-contrib-jshint'); 47 | }; 48 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 10 | ## Specifications 11 | 12 | - Plugin version: 13 | - Framework: 14 | - Framework version: 15 | - Operating system: 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Richard Windley 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | ## Proposed Changes 4 | 5 | - 6 | - 7 | - 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cordova Firestore Plugin 2 | A Google Firebase Firestore plugin to enable realtime synchronisation between app and cloud and automatically handle limited connectivity. 3 | 4 | # What is Firestore? 5 | From the Google documentation (https://firebase.google.com/docs/firestore/): 6 | 7 | > Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud Platform. Like Firebase Realtime Database, it keeps your data in sync across client apps through realtime listeners and offers offline support for mobile and web so you can build responsive apps that work regardless of network latency or Internet connectivity. Cloud Firestore also offers seamless integration with other Firebase and Google Cloud Platform products, including Cloud Functions 8 | 9 | # Supported platforms 10 | This plugin supports the following platforms: 11 | 12 | - Android 13 | - iOS 14 | - Browser 15 | 16 | # Installation 17 | ## Install the plugin 18 | 19 | ```bash 20 | cordova plugin add cordova-plugin-firestore --save 21 | ``` 22 | 23 | or 24 | 25 | ```bash 26 | phonegap plugin add cordova-plugin-firestore 27 | ``` 28 | 29 | ### Optional installation variables for Android 30 | 31 | #### ANDROID_FIREBASE_CORE_VERSION 32 | Version of `com.google.firebase:firebase-core`. This defaults to `16.0.8`. 33 | 34 | #### ANDROID_FIREBASE_FIRESTORE_VERSION 35 | Version of `com.google.firebase:firebase-firestore`. This defaults to `18.2.0`. 36 | 37 | You can find the latest versions of these [here](https://firebase.google.com/docs/android/setup#available_libraries). 38 | 39 | ## Android specific installation 40 | Download `google-services.json` to `(your_project_dir)/google-services.json` 41 | 42 | Hint: [Get a config file for your Android App](https://support.google.com/firebase/answer/7015592#android) 43 | 44 | You must ensure that `google-services.json` is put in the correct location. This can be achieved using the following in your `config.xml`: 45 | 46 | ```xml 47 | 48 | 49 | 50 | ``` 51 | 52 | ### Dependencies 53 | #### cordova-support-google-services 54 | 55 | In order to ensure Firebase initialises correctly on Android this plugin can be used. This is not automatically added as a dependency to allow for the configuration it performs to be done manually if desired. 56 | 57 | ## iOS specific installation 58 | 59 | Download `GoogleService-Info.plist` to `(your_project_dir)/GoogleService-Info.plist` 60 | 61 | Hint: [Get a config file for your iOS App](https://support.google.com/firebase/answer/7015592#ios) 62 | 63 | You must ensure that `GoogleService-Info.plist` is put in the correct location. This can be done as follows: 64 | 65 | ```xml 66 | 67 | 68 | 69 | ``` 70 | 71 | #### Keychain Sharing Capability 72 | If using multiple Firebase plugins it may be necessary to enable this. 73 | 74 | ## Firestore configuration 75 | It is good practice to make sure your Firestore database is only accessible for authorised users, at least for write operations. It is recommended you take time to understand [Firestore rules](https://firebase.google.com/docs/firestore/security/get-started). 76 | 77 | An example that only allows access for authorised users is shown below: 78 | 79 | ``` 80 | service cloud.firestore { 81 | match /databases/{database}/documents { 82 | match /{document=**} { 83 | allow read, write : if request.auth != null; 84 | } 85 | } 86 | } 87 | ``` 88 | 89 | Authenticating users is beyond the scope of this plugin, but the `cordova-plugin-firebaseui-auth` is one such plugin you can use to achieve this which will work alongside this plugin. 90 | 91 | ## Dependencies 92 | ### Promises 93 | This plugin uses Promises. If you want to use this with Android 4.4 then you will need to include a `Promise` polyfill. 94 | 95 | 96 | # Example 97 | A simple example is shown below which sets up the necessary options, initialises the database and then adds a document to a `users` collection: 98 | 99 | ```js 100 | var options = { 101 | "datePrefix": '__DATE:', 102 | "fieldValueDelete": "__DELETE", 103 | "fieldValueServerTimestamp" : "__SERVERTIMESTAMP", 104 | "persist": true, 105 | "config" : {} 106 | }; 107 | 108 | if (cordova.platformId === "browser") { 109 | 110 | options.config = { 111 | apiKey: "(your api key)", 112 | authDomain: "localhost", 113 | projectId: "(your project id)" 114 | }; 115 | } 116 | 117 | Firestore.initialise(options).then(function(db) { 118 | // Add a second document with a generated ID. 119 | db.get().collection("users").add({ 120 | first: "Alan", 121 | middle: "Mathison", 122 | last: "Turing", 123 | born: 1912 124 | }) 125 | .then(function(docRef) { 126 | console.log("Document written with ID: ", docRef.id); 127 | }) 128 | .catch(function(error) { 129 | console.error("Error adding document: ", error); 130 | }); 131 | }); 132 | ``` 133 | 134 | ## options.config 135 | In the above example this is being used for the browser version, but it can also be used for Android and iOS to specify different databases than the default in the `google-services.json` and `GoogleService-Info.plist` files. 136 | 137 | # What is supported? 138 | 139 | ## Firestore 140 | - collection(collectionPath) 141 | - runTransaction(updateFunction) 142 | - doc(id) 143 | 144 | ## DocumentSnapshot and QueryDataSnapshot 145 | - data() 146 | - get(fieldPath) 147 | - exists 148 | - id 149 | - ref 150 | 151 | ## QuerySnapshot 152 | - docs 153 | - empty 154 | - size 155 | 156 | ## DocumentReference 157 | - collection(collectionPath) 158 | - delete() 159 | - get() 160 | - onSnapshot(optionsOrObserverOrOnNext, observerOrOnNextOrOnError, onError) 161 | - set(data, options) 162 | - update(data) 163 | - id 164 | - parent 165 | 166 | ## Query 167 | - endAt(snapshotOrVarArgs) 168 | - endBefore(snapshotOrVarArgs) 169 | - limit(limit) 170 | - orderBy(field, direction) 171 | - get() 172 | - onSnapshot(optionsOrObserverOrOnNext, observerOrOnNextOrOnError, onError) 173 | - startAfter(snapshotOrVarArgs) 174 | - startAt(snapshotOrVarArgs) 175 | - where(fieldPath, opStr, passedValue) 176 | 177 | ## CollectionReference (inherits from Query) 178 | - add(data) 179 | - id 180 | - doc(id) 181 | - parent 182 | 183 | ## Transaction 184 | - get() 185 | - delete() 186 | - set() 187 | - update() 188 | 189 | ## FieldValue 190 | - FieldValue.delete() 191 | - FieldValue.serverTimestamp() 192 | 193 | ## GeoPoint 194 | - Firestore.GeoPoint(latitude, longitude) 195 | 196 | ## Timestamp 197 | - Firestore.Timestamp(seconds, nanoseconds) 198 | 199 | ## Dates 200 | Because data is transferred to the client as JSON there is extra logic in place to handle the conversion of dates for some operations. 201 | 202 | When initialising the plugin you can set up a prefix that is applied to a string value which is used to identify it as a date. The default prefix is `__DATE:` 203 | 204 | Therefore, when a date field is retrieved from the database by the native code it will be transferred in the JSON looking similar to the following: 205 | 206 | ```json 207 | { 208 | "dateField" : "__DATE:123456789" 209 | } 210 | ``` 211 | 212 | The number is seconds since epoch. 213 | 214 | The client will receive the field as a Javascript Date. 215 | 216 | This conversion also happens when specifying a field in a where condition. 217 | 218 | ### timestampsInSnapshots 219 | By default this option is set to `false` to mirror the current default. [This explains the setting](https://firebase.google.com/docs/reference/js/firebase.firestore.Settings). 220 | 221 | Not setting this to `true` will result in the following message when running in the browser: 222 | 223 | ``` 224 | The behavior for Date objects stored in Firestore is going to change 225 | AND YOUR APP MAY BREAK. 226 | To hide this warning and ensure your app does not break, you need to add the 227 | following code to your app before calling any other Cloud Firestore methods: 228 | 229 | const firestore = firebase.firestore(); 230 | const settings = {/* your settings... */ timestampsInSnapshots: true}; 231 | firestore.settings(settings); 232 | 233 | With this change, timestamps stored in Cloud Firestore will be read back as 234 | Firebase Timestamp objects instead of as system Date objects. So you will also 235 | need to update code expecting a Date to instead expect a Timestamp. For example: 236 | 237 | // Old: 238 | const date = snapshot.get('created_at'); 239 | // New: 240 | const timestamp = snapshot.get('created_at'); 241 | const date = timestamp.toDate(); 242 | 243 | Please audit all existing usages of Date when you enable the new behavior. In a 244 | future release, the behavior will change to the new behavior, so if you do not 245 | follow these steps, YOUR APP MAY BREAK. 246 | ``` 247 | 248 | ## FieldValue constants 249 | Similar to the situation with dates, there are special values used for `FieldValue` values: 250 | 251 | - FieldValue.delete() equates to `__DELETE` 252 | - FieldValue.serverTimestamp() equates to `__SERVERTIMESTAMP` 253 | 254 | These values can be changed when initialisation is performed. 255 | 256 | ## Nested collections 257 | Nested collections are supported. This can take either form as follows: 258 | 259 | ``` 260 | db.get().collection("mycollection").doc("mydoc").collection("mysubcollection"); 261 | db.get().collection("mycollection/mydoc/mysubcollection"); 262 | ``` 263 | 264 | Note that the second form is slightly more efficient as it results in less objects being instantiated. 265 | 266 | ## Typescript 267 | Support is now included for typescript. Use the following to reference the typescript definitions: 268 | 269 | ``` 270 | /// 271 | e 272 | private static crashlytics: FirebaseCrashlytics = FirebaseCrashlyticsPlugin.initialise(); 273 | crashlytics.logException("my message"); 274 | ``` 275 | 276 | You may also need to add an external to webpack.config.ls: 277 | 278 | ``` 279 | externals: { 280 | 'cordova-plugin-firebase-crashlytics': "cordova-plugin-firebase-crashlytics" 281 | '/exec':"cordova/exec" 282 | }, 283 | ``` 284 | 285 | ## Learnings and notes 286 | I have learnt a number of things whilst implementing this: 287 | - The documentation states that the database cannot be initialised in a seperate thread when using persistence. In my experience this should say it cannot be *used* in multiple threads. 288 | - When used on Android ensure that at least `com.google.gms:google-services:3.1.1` is used in build dependencies. Earlier versions did not work for me. 289 | - Yes, I did spell initialise() with an 's' - Original plugin developer @ReallySmallSoftware is from the UK 290 | -------------------------------------------------------------------------------- /firestore.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url 'https://maven.google.com' } 4 | mavenLocal() 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.3.2' 9 | classpath 'com.google.gms:google-services:4.2.0' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // For a detailed explanation regarding each configuration property, visit: 2 | // https://jestjs.io/docs/en/configuration.html 3 | 4 | module.exports = { 5 | // All imported modules in your tests should be mocked automatically 6 | // automock: false, 7 | 8 | // Stop running tests after `n` failures 9 | // bail: 0, 10 | 11 | // Respect "browser" field in package.json when resolving modules 12 | // browser: false, 13 | 14 | // The directory where Jest should store its cached dependency information 15 | // cacheDirectory: "/tmp/jest_rs", 16 | 17 | // Automatically clear mock calls and instances between every test 18 | clearMocks: true, 19 | 20 | // Indicates whether the coverage information should be collected while executing the test 21 | // collectCoverage: false, 22 | 23 | // An array of glob patterns indicating a set of files for which coverage information should be collected 24 | // collectCoverageFrom: null, 25 | 26 | // The directory where Jest should output its coverage files 27 | coverageDirectory: "coverage", 28 | 29 | // An array of regexp pattern strings used to skip coverage collection 30 | // coveragePathIgnorePatterns: [ 31 | // "/node_modules/" 32 | // ], 33 | 34 | // A list of reporter names that Jest uses when writing coverage reports 35 | // coverageReporters: [ 36 | // "json", 37 | // "text", 38 | // "lcov", 39 | // "clover" 40 | // ], 41 | 42 | // An object that configures minimum threshold enforcement for coverage results 43 | // coverageThreshold: null, 44 | 45 | // A path to a custom dependency extractor 46 | // dependencyExtractor: null, 47 | 48 | // Make calling deprecated APIs throw helpful error messages 49 | // errorOnDeprecated: false, 50 | 51 | // Force coverage collection from ignored files using an array of glob patterns 52 | // forceCoverageMatch: [], 53 | 54 | // A path to a module which exports an async function that is triggered once before all test suites 55 | // globalSetup: null, 56 | 57 | // A path to a module which exports an async function that is triggered once after all test suites 58 | // globalTeardown: null, 59 | 60 | // A set of global variables that need to be available in all test environments 61 | // globals: {}, 62 | 63 | // An array of directory names to be searched recursively up from the requiring module's location 64 | // moduleDirectories: [ 65 | // "node_modules" 66 | // ], 67 | 68 | // An array of file extensions your modules use 69 | // moduleFileExtensions: [ 70 | // "js", 71 | // "json", 72 | // "jsx", 73 | // "ts", 74 | // "tsx", 75 | // "node" 76 | // ], 77 | 78 | // A map from regular expressions to module names that allow to stub out resources with a single module 79 | // moduleNameMapper: {}, 80 | 81 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 82 | // modulePathIgnorePatterns: [], 83 | 84 | // Activates notifications for test results 85 | // notify: false, 86 | 87 | // An enum that specifies notification mode. Requires { notify: true } 88 | // notifyMode: "failure-change", 89 | 90 | // A preset that is used as a base for Jest's configuration 91 | // preset: null, 92 | 93 | // Run tests from one or more projects 94 | // projects: null, 95 | 96 | // Use this configuration option to add custom reporters to Jest 97 | // reporters: undefined, 98 | 99 | // Automatically reset mock state between every test 100 | // resetMocks: false, 101 | 102 | // Reset the module registry before running each individual test 103 | // resetModules: false, 104 | 105 | // A path to a custom resolver 106 | // resolver: null, 107 | 108 | // Automatically restore mock state between every test 109 | // restoreMocks: false, 110 | 111 | // The root directory that Jest should scan for tests and modules within 112 | // rootDir: null, 113 | 114 | // A list of paths to directories that Jest should use to search for files in 115 | // roots: [ 116 | // "" 117 | // ], 118 | 119 | // Allows you to use a custom runner instead of Jest's default test runner 120 | // runner: "jest-runner", 121 | 122 | // The paths to modules that run some code to configure or set up the testing environment before each test 123 | // setupFiles: [], 124 | 125 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 126 | // setupFilesAfterEnv: [], 127 | 128 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 129 | // snapshotSerializers: [], 130 | 131 | // The test environment that will be used for testing 132 | testEnvironment: "node", 133 | 134 | // Options that will be passed to the testEnvironment 135 | // testEnvironmentOptions: {}, 136 | 137 | // Adds a location field to test results 138 | // testLocationInResults: false, 139 | 140 | // The glob patterns Jest uses to detect test files 141 | // testMatch: [ 142 | // "**/__tests__/**/*.[jt]s?(x)", 143 | // "**/?(*.)+(spec|test).[tj]s?(x)" 144 | // ], 145 | 146 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 147 | // testPathIgnorePatterns: [ 148 | // "/node_modules/" 149 | // ], 150 | 151 | // The regexp pattern or array of patterns that Jest uses to detect test files 152 | // testRegex: [], 153 | 154 | // This option allows the use of a custom results processor 155 | // testResultsProcessor: null, 156 | 157 | // This option allows use of a custom test runner 158 | // testRunner: "jasmine2", 159 | 160 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 161 | // testURL: "http://localhost", 162 | 163 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 164 | // timers: "real", 165 | 166 | // A map from regular expressions to paths to transformers 167 | // transform: null, 168 | 169 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 170 | // transformIgnorePatterns: [ 171 | // "/node_modules/" 172 | // ], 173 | 174 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 175 | // unmockedModulePathPatterns: undefined, 176 | 177 | // Indicates whether each individual test should be reported during the run 178 | // verbose: null, 179 | 180 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 181 | // watchPathIgnorePatterns: [], 182 | 183 | // Whether to use watchman for file crawling 184 | // watchman: true, 185 | }; 186 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-firestore", 3 | "version": "4.1.1", 4 | "cordova": { 5 | "id": "cordova-plugin-firestore", 6 | "platforms": [ 7 | "android", 8 | "ios", 9 | "browser" 10 | ] 11 | }, 12 | "types": "./types/index.d.ts", 13 | "engines": { 14 | "cordovaDependencies": { 15 | "0.0.7": { 16 | "cordova": ">=7.0.0", 17 | "cordova-android": ">=5.0.0", 18 | "cordova-ios": ">=4.0.0" 19 | } 20 | } 21 | }, 22 | "description": "A Google Firebase Firestore plugin", 23 | "devDependencies": { 24 | "grunt": "^1.0.4", 25 | "grunt-contrib-jshint": "^1.1.0", 26 | "jest": "^24.7.1" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/ReallySmallSoftware/cordova-plugin-firestore.git" 31 | }, 32 | "author": "Richard Windley (http://www.reallysmall.co.uk)", 33 | "license": "Apache-2.0", 34 | "bugs": { 35 | "url": "https://github.com/ReallySmallSoftware/cordova-plugin-firestore/issues" 36 | }, 37 | "homepage": "https://github.com/ReallySmallSoftware/cordova-plugin-firestore", 38 | "keywords": [ 39 | "ecosystem:cordova", 40 | "cordova-android", 41 | "cordova-ios", 42 | "cordova-browser" 43 | ], 44 | "scripts": { 45 | "test": "jest" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/android/test/uk/co/reallysmall/cordova/plugin/firestore/LimitQueryHandlerTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.Query; 4 | 5 | import org.junit.Test; 6 | 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.verify; 9 | 10 | public class LimitQueryHandlerTest { 11 | LimitQueryHandler limitQueryHandler = new LimitQueryHandler(); 12 | 13 | Query query = mock(Query.class); 14 | 15 | 16 | @Test 17 | public void testShouldHandleStringArgument() { 18 | limitQueryHandler.handle(query, "10"); 19 | verify(query).limit(10); 20 | } 21 | 22 | @Test 23 | public void testShouldHandleIntArgument() { 24 | limitQueryHandler.handle(query, 10); 25 | verify(query).limit(10); 26 | } 27 | 28 | @Test(expected = IllegalArgumentException.class) 29 | public void testShouldThrowIllegalArgumentExceptiononArrayArgument() { 30 | limitQueryHandler.handle(query, new String[5]); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/ActionHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import org.apache.cordova.CallbackContext; 4 | import org.json.JSONArray; 5 | import org.json.JSONException; 6 | 7 | public interface ActionHandler { 8 | boolean handle(JSONArray args, final CallbackContext callbackContext) throws JSONException; 9 | } 10 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/BatchCommitHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | import com.google.android.gms.tasks.OnCompleteListener; 7 | import com.google.android.gms.tasks.Task; 8 | import com.google.firebase.firestore.FirebaseFirestoreException; 9 | import com.google.firebase.firestore.WriteBatch; 10 | 11 | import org.apache.cordova.CallbackContext; 12 | import org.json.JSONArray; 13 | import org.json.JSONException; 14 | 15 | public class BatchCommitHandler implements ActionHandler { 16 | private final FirestorePlugin firestorePlugin; 17 | 18 | public BatchCommitHandler(FirestorePlugin firestorePlugin) { 19 | this.firestorePlugin = firestorePlugin; 20 | } 21 | 22 | @Override 23 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 24 | try { 25 | final String batchId = args.getString(0); 26 | 27 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Batch commit for %s", batchId)); 28 | 29 | WriteBatch batch = this.firestorePlugin.getBatch(batchId); 30 | 31 | batch.commit().addOnCompleteListener(new OnCompleteListener() { 32 | @Override 33 | public void onComplete(@NonNull Task task) { 34 | firestorePlugin.removeBatch((batchId)); 35 | 36 | if (task.isSuccessful()) { 37 | callbackContext.success(); 38 | } else { 39 | String errorCode = ((FirebaseFirestoreException) task.getException()).getCode().name(); 40 | callbackContext.error(PluginResultHelper.createError(errorCode, task.getException().getMessage())); 41 | } 42 | } 43 | }); 44 | 45 | } catch (JSONException e) { 46 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing batch commit", e); 47 | callbackContext.error(e.getMessage()); 48 | } 49 | 50 | return true; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/BatchDocDeleteHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | 4 | import com.google.firebase.firestore.DocumentReference; 5 | import com.google.firebase.firestore.WriteBatch; 6 | 7 | import org.apache.cordova.CallbackContext; 8 | import org.json.JSONArray; 9 | import org.json.JSONException; 10 | 11 | public class BatchDocDeleteHandler implements ActionHandler { 12 | private final FirestorePlugin firestorePlugin; 13 | 14 | public BatchDocDeleteHandler(FirestorePlugin firestorePlugin) { 15 | this.firestorePlugin = firestorePlugin; 16 | } 17 | 18 | @Override 19 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 20 | try { 21 | final String batchId = args.getString(0); 22 | final String docId = args.getString(1); 23 | final String collectionPath = args.getString(2); 24 | 25 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Batch document delete for %s", batchId)); 26 | 27 | DocumentReference documentRef = firestorePlugin.getDatabase().collection(collectionPath).document(docId); 28 | 29 | WriteBatch batch = this.firestorePlugin.getBatch(batchId); 30 | batch.delete(documentRef); 31 | 32 | callbackContext.success(); 33 | 34 | } catch (JSONException e) { 35 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing batch document delete", e); 36 | callbackContext.error(e.getMessage()); 37 | } 38 | 39 | return true; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/BatchDocSetHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.DocumentReference; 4 | import com.google.firebase.firestore.SetOptions; 5 | import com.google.firebase.firestore.WriteBatch; 6 | 7 | import org.apache.cordova.CallbackContext; 8 | import org.json.JSONArray; 9 | import org.json.JSONException; 10 | import org.json.JSONObject; 11 | 12 | public class BatchDocSetHandler implements ActionHandler { 13 | private final FirestorePlugin firestorePlugin; 14 | 15 | public BatchDocSetHandler(FirestorePlugin firestorePlugin) { 16 | this.firestorePlugin = firestorePlugin; 17 | } 18 | 19 | @Override 20 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 21 | try { 22 | final String batchId = args.getString(0); 23 | final String docId = args.getString(1); 24 | final String collectionPath = args.getString(2); 25 | final JSONObject data = args.getJSONObject(3); 26 | 27 | final JSONObject options; 28 | 29 | if (!args.isNull(4)) { 30 | options = args.getJSONObject(4); 31 | } else { 32 | options = null; 33 | } 34 | 35 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Batch document set for %s", batchId)); 36 | 37 | SetOptions setOptions = DocSetOptions.getSetOptions(options); 38 | 39 | DocumentReference documentRef = firestorePlugin.getDatabase().collection(collectionPath).document(docId); 40 | 41 | WriteBatch batch = this.firestorePlugin.getBatch(batchId); 42 | 43 | if (setOptions == null) { 44 | batch.set(documentRef, JSONHelper.fromJSON(data)); 45 | } else { 46 | batch.set(documentRef, JSONHelper.fromJSON(data), setOptions); 47 | } 48 | 49 | callbackContext.success(); 50 | 51 | } catch (JSONException e) { 52 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing batch document set", e); 53 | callbackContext.error(e.getMessage()); 54 | } 55 | 56 | return true; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/BatchDocUpdateHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.DocumentReference; 4 | import com.google.firebase.firestore.SetOptions; 5 | import com.google.firebase.firestore.WriteBatch; 6 | 7 | import org.apache.cordova.CallbackContext; 8 | import org.json.JSONArray; 9 | import org.json.JSONException; 10 | import org.json.JSONObject; 11 | 12 | import java.util.Map; 13 | 14 | public class BatchDocUpdateHandler implements ActionHandler { 15 | private final FirestorePlugin firestorePlugin; 16 | 17 | public BatchDocUpdateHandler(FirestorePlugin firestorePlugin) { 18 | this.firestorePlugin = firestorePlugin; 19 | } 20 | 21 | @Override 22 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 23 | try { 24 | final String batchId = args.getString(0); 25 | final String docId = args.getString(1); 26 | final String collectionPath = args.getString(2); 27 | final JSONObject data = args.getJSONObject(3); 28 | 29 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Batch document set for %s", batchId)); 30 | 31 | DocumentReference documentRef = firestorePlugin.getDatabase().collection(collectionPath).document(docId); 32 | 33 | WriteBatch batch = this.firestorePlugin.getBatch(batchId); 34 | 35 | batch.update(documentRef, (Map)JSONHelper.fromJSON(data)); 36 | 37 | callbackContext.success(); 38 | 39 | } catch (JSONException e) { 40 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing batch document update", e); 41 | callbackContext.error(e.getMessage()); 42 | } 43 | 44 | return true; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/BatchHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | 4 | import com.google.firebase.firestore.WriteBatch; 5 | 6 | import org.apache.cordova.CallbackContext; 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | 10 | public class BatchHandler implements ActionHandler { 11 | private final FirestorePlugin firestorePlugin; 12 | 13 | public BatchHandler(FirestorePlugin firestorePlugin) { 14 | this.firestorePlugin = firestorePlugin; 15 | } 16 | 17 | @Override 18 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 19 | try { 20 | final String batchId = args.getString(0); 21 | 22 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Batch %s", batchId)); 23 | 24 | WriteBatch batch = this.firestorePlugin.getDatabase().batch(); 25 | this.firestorePlugin.storeBatch(batchId,batch); 26 | 27 | callbackContext.success(); 28 | 29 | } catch (JSONException e) { 30 | FirestoreLog.e(FirestorePlugin.TAG, "Error creating batch", e); 31 | callbackContext.error(e.getMessage()); 32 | } 33 | 34 | return true; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/CollectionAddHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | //import android.support.annotation.NonNull; 4 | import androidx.annotation.NonNull; 5 | 6 | import android.util.Log; 7 | 8 | import com.google.android.gms.tasks.OnFailureListener; 9 | import com.google.android.gms.tasks.OnSuccessListener; 10 | import com.google.firebase.firestore.DocumentReference; 11 | import com.google.firebase.firestore.FirebaseFirestoreException; 12 | 13 | import org.apache.cordova.CallbackContext; 14 | import org.json.JSONArray; 15 | import org.json.JSONException; 16 | import org.json.JSONObject; 17 | 18 | public class CollectionAddHandler implements ActionHandler { 19 | 20 | private FirestorePlugin firestorePlugin; 21 | 22 | public CollectionAddHandler(FirestorePlugin firestorePlugin) { 23 | this.firestorePlugin = firestorePlugin; 24 | } 25 | 26 | @Override 27 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 28 | try { 29 | final String collectionPath = args.getString(0); 30 | final JSONObject data = args.getJSONObject(1); 31 | 32 | FirestoreLog.d(FirestorePlugin.TAG, "Writing document to collection"); 33 | 34 | try { 35 | firestorePlugin.getDatabase().collection(collectionPath).add(JSONHelper.fromJSON(data)).addOnSuccessListener(new OnSuccessListener() { 36 | @Override 37 | public void onSuccess(DocumentReference documentReference) { 38 | callbackContext.sendPluginResult(PluginResultHelper.createPluginResult(documentReference, false)); 39 | 40 | FirestoreLog.d(FirestorePlugin.TAG, "Successfully written document to collection"); 41 | } 42 | }).addOnFailureListener(new OnFailureListener() { 43 | @Override 44 | public void onFailure(@NonNull Exception e) { 45 | 46 | FirestoreLog.w(FirestorePlugin.TAG, "Error writing document to collection", e); 47 | String errorCode = ((FirebaseFirestoreException) e).getCode().name(); 48 | callbackContext.error(PluginResultHelper.createError(errorCode, e.getMessage())); 49 | } 50 | }); 51 | } catch (Exception e) { 52 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing collection add in thread", e); 53 | callbackContext.error(e.getMessage()); 54 | } 55 | } catch (JSONException e) { 56 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing collection add", e); 57 | callbackContext.error(e.getMessage()); 58 | } 59 | 60 | return true; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/CollectionGetHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | //import android.support.annotation.NonNull; 4 | import androidx.annotation.NonNull; 5 | 6 | import android.util.Log; 7 | 8 | import com.google.android.gms.tasks.OnFailureListener; 9 | import com.google.android.gms.tasks.OnSuccessListener; 10 | import com.google.firebase.firestore.CollectionReference; 11 | import com.google.firebase.firestore.FirebaseFirestoreException; 12 | import com.google.firebase.firestore.Query; 13 | import com.google.firebase.firestore.QuerySnapshot; 14 | 15 | import org.apache.cordova.CallbackContext; 16 | import org.json.JSONArray; 17 | import org.json.JSONException; 18 | 19 | import static uk.co.reallysmall.cordova.plugin.firestore.PluginResultHelper.createPluginResult; 20 | 21 | public class CollectionGetHandler implements ActionHandler { 22 | 23 | private FirestorePlugin firestorePlugin; 24 | 25 | public CollectionGetHandler(FirestorePlugin firestorePlugin) { 26 | this.firestorePlugin = firestorePlugin; 27 | } 28 | 29 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 30 | try { 31 | final String collectionPath = args.getString(0); 32 | final JSONArray queries = args.getJSONArray(1); 33 | 34 | 35 | FirestoreLog.d(FirestorePlugin.TAG, "Getting document from collection"); 36 | 37 | try { 38 | CollectionReference collectionRef = firestorePlugin.getDatabase().collection(collectionPath); 39 | Query query = QueryHelper.processQueries(queries, collectionRef, this.firestorePlugin); 40 | 41 | query.get().addOnSuccessListener(new OnSuccessListener() { 42 | @Override 43 | public void onSuccess(QuerySnapshot querySnapshot) { 44 | callbackContext.sendPluginResult(createPluginResult(querySnapshot, false)); 45 | FirestoreLog.d(FirestorePlugin.TAG, "Successfully got collection"); 46 | } 47 | }).addOnFailureListener(new OnFailureListener() { 48 | @Override 49 | public void onFailure(@NonNull Exception e) { 50 | FirestoreLog.w(FirestorePlugin.TAG, "Error getting collection", e); 51 | String errorCode = ((FirebaseFirestoreException) e).getCode().name(); 52 | callbackContext.error(PluginResultHelper.createError(errorCode, e.getMessage())); 53 | } 54 | }); 55 | } catch (Exception e) { 56 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing collection get in thread", e); 57 | callbackContext.error(e.getMessage()); 58 | } 59 | 60 | } catch (JSONException e) { 61 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing collection get", e); 62 | callbackContext.error(e.getMessage()); 63 | } 64 | 65 | return true; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/CollectionOnSnapshotHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import androidx.annotation.Nullable; 4 | import android.util.Log; 5 | 6 | import com.google.firebase.firestore.CollectionReference; 7 | import com.google.firebase.firestore.EventListener; 8 | import com.google.firebase.firestore.FirebaseFirestoreException; 9 | import com.google.firebase.firestore.Query; 10 | import com.google.firebase.firestore.MetadataChanges; 11 | import com.google.firebase.firestore.QuerySnapshot; 12 | 13 | import org.apache.cordova.CallbackContext; 14 | import org.json.JSONArray; 15 | import org.json.JSONException; 16 | import org.json.JSONObject; 17 | 18 | public class CollectionOnSnapshotHandler implements ActionHandler { 19 | private FirestorePlugin firestorePlugin; 20 | 21 | public CollectionOnSnapshotHandler(FirestorePlugin firestorePlugin) { 22 | this.firestorePlugin = firestorePlugin; 23 | } 24 | 25 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 26 | try { 27 | final String collection = args.getString(0); 28 | final JSONArray queries = args.getJSONArray(1); 29 | final JSONObject options = args.optJSONObject(2); 30 | final String callbackId = args.getString(3); 31 | 32 | 33 | FirestoreLog.d(FirestorePlugin.TAG, "Listening to collection " + collection); 34 | 35 | try { 36 | CollectionReference collectionRef = firestorePlugin.getDatabase().collection(collection); 37 | MetadataChanges metadataChanges = getMetadataChanges(options); 38 | 39 | Query query = QueryHelper.processQueries(queries, collectionRef, this.firestorePlugin); 40 | 41 | EventListener eventListener = new EventListener() { 42 | @Override 43 | public void onEvent(@Nullable QuerySnapshot value, 44 | @Nullable FirebaseFirestoreException e) { 45 | 46 | if (e != null) { 47 | FirestoreLog.w(FirestorePlugin.TAG, "Collection snapshot listener error " + collection, e); 48 | callbackContext.sendPluginResult(PluginResultHelper.createPluginErrorResult(e, true)); 49 | return; 50 | } 51 | 52 | FirestoreLog.d(FirestorePlugin.TAG, "Got collection snapshot data " + collection); 53 | callbackContext.sendPluginResult(PluginResultHelper.createPluginResult(value, true)); 54 | } 55 | }; 56 | 57 | firestorePlugin.addRegistration(callbackId, query.addSnapshotListener(metadataChanges, eventListener)); 58 | 59 | } catch (Exception e) { 60 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing collection snapshot in thread " + collection, e); 61 | callbackContext.error(e.getMessage()); 62 | } 63 | 64 | } catch (JSONException e) { 65 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing collection snapshot", e); 66 | callbackContext.error(e.getMessage()); 67 | } 68 | 69 | return true; 70 | } 71 | 72 | private MetadataChanges getMetadataChanges(JSONObject options) { 73 | MetadataChanges metadataChanges = MetadataChanges.EXCLUDE; 74 | 75 | if (options != null) { 76 | 77 | try { 78 | if (options.getBoolean("includeDocumentMetadataChanges")) { 79 | metadataChanges = MetadataChanges.INCLUDE; 80 | } 81 | if (options.getBoolean("includeQueryMetadataChanges")) { 82 | metadataChanges = MetadataChanges.INCLUDE; 83 | } 84 | if (options.getBoolean("includeMetadataChanges")) { 85 | metadataChanges = MetadataChanges.INCLUDE; 86 | } 87 | } catch (JSONException e) { 88 | FirestoreLog.e(FirestorePlugin.TAG, "Error getting query options", e); 89 | throw new RuntimeException(e); 90 | } 91 | 92 | FirestoreLog.d(FirestorePlugin.TAG, "Set document options"); 93 | } 94 | 95 | return metadataChanges; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/CollectionUnsubscribeHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import android.util.Log; 4 | 5 | import org.apache.cordova.CallbackContext; 6 | import org.json.JSONArray; 7 | import org.json.JSONException; 8 | 9 | public class CollectionUnsubscribeHandler implements ActionHandler { 10 | 11 | private FirestorePlugin firestorePlugin; 12 | 13 | public CollectionUnsubscribeHandler(FirestorePlugin firestorePlugin) { 14 | this.firestorePlugin = firestorePlugin; 15 | } 16 | 17 | @Override 18 | public boolean handle(JSONArray args, CallbackContext callbackContext) { 19 | try { 20 | String callbackId = args.getString(0); 21 | firestorePlugin.unregister(callbackId); 22 | callbackContext.success(); 23 | } catch (JSONException e) { 24 | FirestoreLog.e(FirestorePlugin.TAG, "Error unsubscribing from collection", e); 25 | callbackContext.error(e.getMessage()); 26 | } 27 | 28 | return true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/DocDeleteHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | //import android.support.annotation.NonNull; 4 | import androidx.annotation.NonNull; 5 | import android.util.Log; 6 | 7 | import com.google.android.gms.tasks.OnFailureListener; 8 | import com.google.android.gms.tasks.OnSuccessListener; 9 | import com.google.firebase.firestore.DocumentReference; 10 | 11 | import org.apache.cordova.CallbackContext; 12 | import org.json.JSONArray; 13 | import org.json.JSONException; 14 | 15 | public class DocDeleteHandler implements ActionHandler { 16 | 17 | private FirestorePlugin firestorePlugin; 18 | 19 | public DocDeleteHandler(FirestorePlugin firestorePlugin) { 20 | this.firestorePlugin = firestorePlugin; 21 | } 22 | 23 | @Override 24 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 25 | try { 26 | final String collectionPath = args.getString(0); 27 | final String doc = args.getString(1); 28 | 29 | FirestoreLog.d(FirestorePlugin.TAG, "Deleting document"); 30 | 31 | try { 32 | DocumentReference documentRef = firestorePlugin.getDatabase().collection(collectionPath).document(doc); 33 | FirestoreLog.d(FirestorePlugin.TAG, "Get for document " + collectionPath + "/" + doc); 34 | 35 | documentRef.delete().addOnSuccessListener(new OnSuccessListener() { 36 | @Override 37 | public void onSuccess(Void aVoid) { 38 | callbackContext.success(0); 39 | FirestoreLog.d(FirestorePlugin.TAG, "Successfully deleted document"); 40 | } 41 | }).addOnFailureListener(new OnFailureListener() { 42 | @Override 43 | public void onFailure(@NonNull Exception e) { 44 | FirestoreLog.w(FirestorePlugin.TAG, "Error deleting document", e); 45 | callbackContext.error(e.getMessage()); 46 | } 47 | }); 48 | } catch (Exception e) { 49 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing document delete in thread", e); 50 | callbackContext.error(e.getMessage()); 51 | } 52 | 53 | } catch (JSONException e) { 54 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing document delete", e); 55 | callbackContext.error(e.getMessage()); 56 | } 57 | 58 | return true; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/DocGetHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | 4 | //import android.support.annotation.NonNull; 5 | import androidx.annotation.NonNull; 6 | 7 | import android.util.Log; 8 | 9 | import com.google.android.gms.tasks.OnFailureListener; 10 | import com.google.android.gms.tasks.OnSuccessListener; 11 | import com.google.firebase.firestore.DocumentReference; 12 | import com.google.firebase.firestore.DocumentSnapshot; 13 | import com.google.firebase.firestore.FirebaseFirestoreException; 14 | 15 | import org.apache.cordova.CallbackContext; 16 | import org.json.JSONArray; 17 | import org.json.JSONException; 18 | 19 | import static uk.co.reallysmall.cordova.plugin.firestore.PluginResultHelper.createPluginResult; 20 | 21 | public class DocGetHandler implements ActionHandler { 22 | 23 | private FirestorePlugin firestorePlugin; 24 | 25 | public DocGetHandler(FirestorePlugin firestorePlugin) { 26 | this.firestorePlugin = firestorePlugin; 27 | } 28 | 29 | @Override 30 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 31 | try { 32 | final String collectionPath = args.getString(0); 33 | final String docId = args.getString(1); 34 | final String docPath = collectionPath + "/" + docId; 35 | FirestoreLog.d(FirestorePlugin.TAG, "Getting document : " + docPath); 36 | 37 | try { 38 | DocumentReference documentRef = firestorePlugin.getDatabase().collection(collectionPath).document(docId); 39 | 40 | documentRef.get().addOnSuccessListener(new OnSuccessListener() { 41 | @Override 42 | public void onSuccess(DocumentSnapshot documentSnapshot) { 43 | callbackContext.sendPluginResult(createPluginResult(documentSnapshot, false)); 44 | FirestoreLog.d(FirestorePlugin.TAG, "Successfully got document " + docPath); 45 | } 46 | }).addOnFailureListener(new OnFailureListener() { 47 | @Override 48 | public void onFailure(@NonNull Exception e) { 49 | FirestoreLog.w(FirestorePlugin.TAG, "Error getting document " + docPath, e); 50 | String errorCode = ((FirebaseFirestoreException) e).getCode().name(); 51 | callbackContext.error(PluginResultHelper.createError(errorCode, e.getMessage())); } 52 | }); 53 | } catch (Exception e) { 54 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing document get " + docPath, e); 55 | callbackContext.error(e.getMessage()); 56 | } 57 | 58 | } catch (JSONException e) { 59 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing document snapshot ", e); 60 | callbackContext.error(e.getMessage()); 61 | } 62 | 63 | return true; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/DocOnSnapshotHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | 4 | import androidx.annotation.Nullable; 5 | import android.util.Log; 6 | 7 | import com.google.firebase.firestore.DocumentReference; 8 | import com.google.firebase.firestore.DocumentSnapshot; 9 | import com.google.firebase.firestore.EventListener; 10 | import com.google.firebase.firestore.FirebaseFirestoreException; 11 | import com.google.firebase.firestore.MetadataChanges; 12 | 13 | import org.apache.cordova.CallbackContext; 14 | import org.json.JSONArray; 15 | import org.json.JSONException; 16 | import org.json.JSONObject; 17 | 18 | public class DocOnSnapshotHandler implements ActionHandler { 19 | private FirestorePlugin firestorePlugin; 20 | 21 | public DocOnSnapshotHandler(FirestorePlugin firestorePlugin) { 22 | this.firestorePlugin = firestorePlugin; 23 | } 24 | 25 | @Override 26 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 27 | try { 28 | final String collectionPath = args.getString(0); 29 | final String docId = args.getString(1); 30 | final String docPath = collectionPath + "/" + docId; 31 | final String callbackId = args.getString(2); 32 | 33 | final JSONObject options; 34 | 35 | if (args.length() > 3) { 36 | options = args.getJSONObject(3); 37 | } else { 38 | options = null; 39 | } 40 | 41 | DocumentReference documentRef = firestorePlugin.getDatabase().collection(collectionPath).document(docId); 42 | MetadataChanges metadataChanges = getMetadataChanges(options); 43 | 44 | FirestoreLog.d(FirestorePlugin.TAG, "Launching onSnapshot handler for document " + docPath); 45 | 46 | EventListener eventListener = new EventListener() { 47 | @Override 48 | public void onEvent(@Nullable DocumentSnapshot documentSnapshot, 49 | @Nullable FirebaseFirestoreException e) { 50 | if (e != null) { 51 | FirestoreLog.w(FirestorePlugin.TAG, "Document snapshot listener error", e); 52 | callbackContext.sendPluginResult(PluginResultHelper.createPluginErrorResult(e, true)); 53 | return; 54 | } 55 | 56 | FirestoreLog.d(FirestorePlugin.TAG, "Got document snapshot data"); 57 | callbackContext.sendPluginResult(PluginResultHelper.createPluginResult(documentSnapshot, true)); 58 | } 59 | }; 60 | 61 | firestorePlugin.addRegistration(callbackId, documentRef.addSnapshotListener(metadataChanges, eventListener)); 62 | 63 | } catch (JSONException e) { 64 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing document snapshot", e); 65 | callbackContext.error(e.getMessage()); 66 | } 67 | 68 | return true; 69 | } 70 | 71 | private MetadataChanges getMetadataChanges(JSONObject options) { 72 | MetadataChanges metadataChanges = MetadataChanges.EXCLUDE; 73 | 74 | if (options != null) { 75 | 76 | try { 77 | if (options.getBoolean("includeMetadataChanges")) { 78 | metadataChanges = MetadataChanges.INCLUDE; 79 | } 80 | } catch (JSONException e) { 81 | FirestoreLog.e(FirestorePlugin.TAG, "Error getting document option includeMetadataChanges", e); 82 | throw new RuntimeException(e); 83 | } 84 | 85 | FirestoreLog.d(FirestorePlugin.TAG, "Set document options"); 86 | } 87 | 88 | return metadataChanges; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/DocSetHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | //import android.support.annotation.NonNull; 4 | import androidx.annotation.NonNull; 5 | 6 | import android.util.Log; 7 | 8 | import com.google.android.gms.tasks.OnFailureListener; 9 | import com.google.android.gms.tasks.OnSuccessListener; 10 | import com.google.firebase.firestore.DocumentReference; 11 | import com.google.firebase.firestore.FirebaseFirestoreException; 12 | import com.google.firebase.firestore.SetOptions; 13 | 14 | import org.apache.cordova.CallbackContext; 15 | import org.json.JSONArray; 16 | import org.json.JSONException; 17 | import org.json.JSONObject; 18 | 19 | public class DocSetHandler implements ActionHandler { 20 | 21 | private FirestorePlugin firestorePlugin; 22 | 23 | public DocSetHandler(FirestorePlugin firestorePlugin) { 24 | this.firestorePlugin = firestorePlugin; 25 | } 26 | 27 | @Override 28 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 29 | try { 30 | final String collection = args.getString(0); 31 | final String docId = args.getString(1); 32 | final String docPath = collection + "/" + docId; 33 | final JSONObject data = args.getJSONObject(2); 34 | 35 | final JSONObject options; 36 | 37 | if (!args.isNull(3)) { 38 | options = args.getJSONObject(3); 39 | } else { 40 | options = null; 41 | } 42 | 43 | try { 44 | 45 | SetOptions setOptions = DocSetOptions.getSetOptions(options); 46 | 47 | FirestoreLog.d(FirestorePlugin.TAG, "Setting document " + docPath); 48 | 49 | DocumentReference documentReference = firestorePlugin.getDatabase().collection(collection).document(docId); 50 | 51 | OnSuccessListener onSuccessListener = new OnSuccessListener() { 52 | @Override 53 | public void onSuccess(Void aVoid) { 54 | callbackContext.success(); 55 | FirestoreLog.d(FirestorePlugin.TAG, "Successfully written document " + docPath); 56 | } 57 | }; 58 | 59 | OnFailureListener onFailureListener = new OnFailureListener() { 60 | @Override 61 | public void onFailure(@NonNull Exception e) { 62 | FirestoreLog.w(FirestorePlugin.TAG, "Error writing document " + docPath, e); 63 | String errorCode = ((FirebaseFirestoreException) e).getCode().name(); 64 | callbackContext.error(PluginResultHelper.createError(errorCode, e.getMessage())); } 65 | }; 66 | 67 | if (setOptions == null) { 68 | documentReference.set(JSONHelper.fromJSON(data)).addOnSuccessListener(onSuccessListener).addOnFailureListener(onFailureListener); 69 | } else { 70 | documentReference.set(JSONHelper.fromJSON(data), setOptions).addOnSuccessListener(onSuccessListener).addOnFailureListener(onFailureListener); 71 | } 72 | } catch (Exception e) { 73 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing document set " + docPath, e); 74 | callbackContext.error(e.getMessage()); 75 | } 76 | } catch (JSONException e) { 77 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing document set", e); 78 | callbackContext.error(e.getMessage()); 79 | } 80 | 81 | return true; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/DocSetOptions.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.firebase.firestore.SetOptions; 6 | 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | public class DocSetOptions { 11 | 12 | public static SetOptions getSetOptions(JSONObject options) { 13 | SetOptions setOptions = null; 14 | 15 | try { 16 | if (options != null && options.getBoolean("merge")) { 17 | setOptions = SetOptions.merge(); 18 | } 19 | } catch (JSONException e) { 20 | FirestoreLog.e(FirestorePlugin.TAG, "Error getting document option", e); 21 | throw new RuntimeException(e); 22 | } 23 | 24 | FirestoreLog.d(FirestorePlugin.TAG, "Set document options"); 25 | 26 | return setOptions; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/DocUnsubscribeHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import android.util.Log; 4 | 5 | import org.apache.cordova.CallbackContext; 6 | import org.json.JSONArray; 7 | import org.json.JSONException; 8 | 9 | public class DocUnsubscribeHandler implements ActionHandler { 10 | 11 | private FirestorePlugin firestorePlugin; 12 | 13 | public DocUnsubscribeHandler(FirestorePlugin firestorePlugin) { 14 | this.firestorePlugin = firestorePlugin; 15 | } 16 | 17 | @Override 18 | public boolean handle(JSONArray args, CallbackContext callbackContext) { 19 | try { 20 | String callbackId = args.getString(0); 21 | firestorePlugin.unregister(callbackId); 22 | callbackContext.success(); 23 | } catch (JSONException e) { 24 | FirestoreLog.e(FirestorePlugin.TAG, "Error unsubscribing from document", e); 25 | callbackContext.error(e.getMessage()); 26 | } 27 | 28 | return true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/DocUpdateHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | //import android.support.annotation.NonNull; 4 | import androidx.annotation.NonNull; 5 | import android.util.Log; 6 | 7 | import com.google.android.gms.tasks.OnFailureListener; 8 | import com.google.android.gms.tasks.OnSuccessListener; 9 | import com.google.firebase.firestore.FirebaseFirestoreException; 10 | 11 | import org.apache.cordova.CallbackContext; 12 | import org.json.JSONArray; 13 | import org.json.JSONException; 14 | import org.json.JSONObject; 15 | 16 | import java.util.Map; 17 | 18 | 19 | public class DocUpdateHandler implements ActionHandler { 20 | private FirestorePlugin firestorePlugin; 21 | 22 | public DocUpdateHandler(FirestorePlugin firestorePlugin) { 23 | this.firestorePlugin = firestorePlugin; 24 | } 25 | 26 | @Override 27 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 28 | try { 29 | final String collection = args.getString(0); 30 | final String docId = args.getString(1); 31 | final JSONObject data = args.getJSONObject(2); 32 | 33 | 34 | FirestoreLog.d(FirestorePlugin.TAG, "Updating document"); 35 | 36 | try { 37 | firestorePlugin.getDatabase().collection(collection).document(docId).update((Map)JSONHelper.fromJSON(data)).addOnSuccessListener(new OnSuccessListener() { 38 | @Override 39 | public void onSuccess(Void aVoid) { 40 | callbackContext.success(); 41 | FirestoreLog.d(FirestorePlugin.TAG, "Successfully updated document"); 42 | } 43 | }).addOnFailureListener(new OnFailureListener() { 44 | @Override 45 | public void onFailure(@NonNull Exception e) { 46 | String errorCode = ((FirebaseFirestoreException) e).getCode().name(); 47 | callbackContext.error(PluginResultHelper.createError(errorCode, e.getMessage())); 48 | FirestoreLog.w(FirestorePlugin.TAG, "Error updating document", e); 49 | } 50 | }); 51 | } catch (Exception e) { 52 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing document update in thread", e); 53 | callbackContext.error(e.getMessage()); 54 | } 55 | ; 56 | } catch (JSONException e) { 57 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing document update", e); 58 | callbackContext.error(e.getMessage()); 59 | } 60 | 61 | return true; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/EndAtQueryHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.Query; 4 | 5 | public class EndAtQueryHandler implements QueryHandler { 6 | @Override 7 | public Query handle(Query query, Object endAt) { 8 | return query.endAt(JSONHelper.fromJSON(endAt)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/EndBeforeQueryHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.Query; 4 | 5 | public class EndBeforeQueryHandler implements QueryHandler { 6 | @Override 7 | public Query handle(Query query, Object endBefore) { 8 | return query.endBefore(JSONHelper.fromJSON(endBefore)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/FieldPathHelper.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.FieldPath; 4 | 5 | public class FieldPathHelper { 6 | 7 | private static String fieldPathDocumentId = "__DOCUMENTID"; 8 | 9 | public static void setDocumentIdePrefix(String fieldPathDocumentId) { 10 | FieldPathHelper.fieldPathDocumentId = fieldPathDocumentId; 11 | } 12 | 13 | public static boolean isWrapped(String value) { 14 | if (fieldPathDocumentId.equals(value)) { 15 | return true; 16 | } 17 | return false; 18 | } 19 | 20 | public static FieldPath unwrapFieldPath(Object value) { 21 | return FieldPath.documentId(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/FieldValueHelper.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.FieldValue; 4 | 5 | import org.json.JSONArray; 6 | import org.json.JSONObject; 7 | 8 | public class FieldValueHelper { 9 | 10 | private static String fieldValueDelete = "__DELETE"; 11 | private static String fieldValueServerTimestamp = "__SERVERTIMESTAMP"; 12 | private static String fieldValueIncrement = "__INCREMENT"; 13 | private static String fieldValueArrayRemove = "__ARRAYREMOVE"; 14 | private static String fieldValueArrayUnion = "__ARRAYUNION"; 15 | 16 | public static void setDeletePrefix(String fieldValueDelete) { 17 | FieldValueHelper.fieldValueDelete = fieldValueDelete; 18 | } 19 | 20 | public static void setServerTimestampPrefix(String fieldValueServerTimestamp) { 21 | FieldValueHelper.fieldValueServerTimestamp = fieldValueServerTimestamp; 22 | } 23 | 24 | public static void setIncrementPrefix(String fieldValueIncrement) { 25 | FieldValueHelper.fieldValueIncrement = fieldValueIncrement; 26 | } 27 | 28 | public static void setArrayRemovePrefix(String fieldValueArrayRemove) { 29 | FieldValueHelper.fieldValueArrayRemove = fieldValueArrayRemove; 30 | } 31 | 32 | public static void setArrayUnionPrefix(String fieldValueArrayUnion) { 33 | FieldValueHelper.fieldValueArrayUnion = fieldValueArrayUnion; 34 | } 35 | 36 | public static Object unwrapFieldValue(Object value) { 37 | String valueString = (String) value; 38 | 39 | if (fieldValueDelete.equals(valueString)) { 40 | return FieldValue.delete(); 41 | } 42 | 43 | if (fieldValueServerTimestamp.equals(valueString)) { 44 | return FieldValue.serverTimestamp(); 45 | } 46 | 47 | if (valueString.startsWith(fieldValueIncrement)) { 48 | return FieldValue.increment(Long.parseLong(FieldValueHelper.unwrap(valueString, fieldValueIncrement))); 49 | } 50 | 51 | if (valueString.startsWith(fieldValueArrayRemove)) { 52 | String unwrapped = FieldValueHelper.unwrap(valueString, fieldValueArrayRemove); 53 | 54 | return FieldValue.arrayRemove(JSONArrayToArray(unwrapped)); 55 | } 56 | 57 | if (valueString.startsWith(fieldValueArrayUnion)) { 58 | String unwrapped = FieldValueHelper.unwrap(valueString, fieldValueArrayUnion); 59 | 60 | return FieldValue.arrayUnion(JSONArrayToArray(unwrapped)); 61 | } 62 | 63 | return value; 64 | } 65 | 66 | private static Object[] JSONArrayToArray(String unwrapped) { 67 | 68 | try { 69 | JSONArray jsonArray = new JSONArray(unwrapped); 70 | 71 | if (jsonArray == null) 72 | return null; 73 | 74 | Object[] array = new Object[jsonArray.length()]; 75 | for (int i = 0; i < array.length; i++) { 76 | array[i] = jsonArray.opt(i); 77 | } 78 | return array; 79 | } catch (Exception ex) { 80 | return null; 81 | } 82 | } 83 | 84 | public static boolean isWrappedFieldValue(Object value) { 85 | if (value instanceof String) { 86 | String valueString = (String) value; 87 | 88 | if (fieldValueDelete.equals(valueString)) { 89 | return true; 90 | } 91 | 92 | if (fieldValueServerTimestamp.equals(valueString)) { 93 | return true; 94 | } 95 | 96 | if (valueString.startsWith(fieldValueIncrement)) { 97 | return true; 98 | } 99 | 100 | if (valueString.startsWith(fieldValueArrayRemove)) { 101 | return true; 102 | } 103 | 104 | if (valueString.startsWith(fieldValueArrayUnion)) { 105 | return true; 106 | } 107 | } 108 | 109 | return false; 110 | } 111 | 112 | public static String unwrap(String valueString, String prefix) { 113 | int prefixLength = prefix.length(); 114 | String ret = valueString.substring(prefixLength + 1); 115 | return ret; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/FirestoreLog.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import android.util.Log; 4 | 5 | public class FirestoreLog { 6 | 7 | private static int _logLevel = Log.DEBUG; 8 | 9 | public static void setLogLevel(String logLevel) { 10 | FirestoreLog.w(FirestorePlugin.TAG, "Setting log level to : " + logLevel); 11 | switch(logLevel.toLowerCase()) { 12 | case "debug": 13 | _logLevel = Log.DEBUG; 14 | break; 15 | case "error": 16 | _logLevel = Log.ERROR; 17 | break; 18 | case "warn": 19 | case "warning": 20 | _logLevel = Log.WARN; 21 | break; 22 | default: 23 | FirestoreLog.e(FirestorePlugin.TAG, "New logLevel unknown, leaving previous setting : " + _logLevel); 24 | break; 25 | } 26 | 27 | FirestoreLog.d(FirestorePlugin.TAG, "New logLevel : " + _logLevel); 28 | } 29 | 30 | public static void d(String TAG, String message) { 31 | if (_logLevel <= Log.DEBUG) { 32 | Log.d(TAG, message); 33 | } 34 | } 35 | public static void w(String TAG, String message) { 36 | if (_logLevel <= Log.WARN) { 37 | Log.w(TAG, message); 38 | } 39 | } 40 | public static void w(String TAG, String message, Exception e) { 41 | if (_logLevel <= Log.WARN) { 42 | Log.w(TAG, message, e); 43 | } 44 | } 45 | public static void e(String TAG, String message) { 46 | if (_logLevel <= Log.ERROR) { 47 | Log.e(TAG, message); 48 | } 49 | } 50 | public static void e(String TAG, String message, Exception e) { 51 | if (_logLevel <= Log.ERROR) { 52 | Log.e(TAG, message, e); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/FirestorePlugin.java: -------------------------------------------------------------------------------- 1 | /** 2 | */ 3 | package uk.co.reallysmall.cordova.plugin.firestore; 4 | 5 | import android.util.Log; 6 | 7 | import com.google.firebase.firestore.FirebaseFirestore; 8 | import com.google.firebase.firestore.ListenerRegistration; 9 | import com.google.firebase.firestore.Transaction; 10 | import com.google.firebase.firestore.WriteBatch; 11 | 12 | import org.apache.cordova.CallbackContext; 13 | import org.apache.cordova.CordovaInterface; 14 | import org.apache.cordova.CordovaPlugin; 15 | import org.apache.cordova.CordovaWebView; 16 | import org.json.JSONArray; 17 | import org.json.JSONException; 18 | 19 | import java.util.Hashtable; 20 | import java.util.Map; 21 | 22 | public class FirestorePlugin extends CordovaPlugin { 23 | static final String TAG = "FirestorePlugin"; 24 | private static FirebaseFirestore database; 25 | private Map handlers = new Hashtable(); 26 | private Map registrations = new Hashtable(); 27 | private Map transactions = new Hashtable(); 28 | private Map batches = new Hashtable(); 29 | 30 | public void initialize(CordovaInterface cordova, CordovaWebView webView) { 31 | FirestoreLog.d(TAG, "Initializing FirestorePlugin"); 32 | super.initialize(cordova, webView); 33 | 34 | handlers.put("collectionOnSnapshot", new CollectionOnSnapshotHandler(FirestorePlugin.this)); 35 | handlers.put("collectionUnsubscribe", new CollectionUnsubscribeHandler(FirestorePlugin.this)); 36 | handlers.put("collectionAdd", new CollectionAddHandler(FirestorePlugin.this)); 37 | handlers.put("collectionGet", new CollectionGetHandler(FirestorePlugin.this)); 38 | handlers.put("initialise", new InitialiseHandler(webView.getContext().getApplicationContext(), FirestorePlugin.this)); 39 | handlers.put("docSet", new DocSetHandler(FirestorePlugin.this)); 40 | handlers.put("docUpdate", new DocUpdateHandler(FirestorePlugin.this)); 41 | handlers.put("docOnSnapshot", new DocOnSnapshotHandler(FirestorePlugin.this)); 42 | handlers.put("docUnsubscribe", new DocUnsubscribeHandler(FirestorePlugin.this)); 43 | handlers.put("docGet", new DocGetHandler(FirestorePlugin.this)); 44 | handlers.put("docDelete", new DocDeleteHandler(FirestorePlugin.this)); 45 | handlers.put("runTransaction", new RunTransactionHandler(FirestorePlugin.this)); 46 | handlers.put("transactionDocGet", new TransactionDocGetHandler(FirestorePlugin.this)); 47 | handlers.put("transactionDocUpdate", new TransactionDocUpdateHandler(FirestorePlugin.this)); 48 | handlers.put("transactionDocSet", new TransactionDocSetHandler(FirestorePlugin.this)); 49 | handlers.put("transactionDocDelete", new TransactionDocDeleteHandler(FirestorePlugin.this)); 50 | handlers.put("transactionResolve", new TransactionResolveHandler(FirestorePlugin.this)); 51 | handlers.put("setLogLevel", new setLogLevel()); 52 | handlers.put("batch", new BatchHandler(FirestorePlugin.this)); 53 | handlers.put("batchUpdate", new BatchDocUpdateHandler(FirestorePlugin.this)); 54 | handlers.put("batchSet", new BatchDocSetHandler(FirestorePlugin.this)); 55 | handlers.put("batchDelete", new BatchDocDeleteHandler(FirestorePlugin.this)); 56 | handlers.put("batchCommit", new BatchCommitHandler(FirestorePlugin.this)); 57 | 58 | FirestoreLog.d(TAG, "Done Initializing FirestorePlugin"); 59 | } 60 | 61 | public FirebaseFirestore getDatabase() { 62 | return database; 63 | } 64 | 65 | public void setDatabase(FirebaseFirestore database) { 66 | this.database = database; 67 | } 68 | 69 | public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { 70 | FirestoreLog.d(TAG, action); 71 | 72 | if (handlers.containsKey(action)) { 73 | return handlers.get(action).handle(args, callbackContext); 74 | } 75 | 76 | return false; 77 | } 78 | 79 | public void addRegistration(String callbackId, ListenerRegistration listenerRegistration) { 80 | registrations.put(callbackId, listenerRegistration); 81 | FirestoreLog.d(TAG, "Registered subscriber " + callbackId); 82 | 83 | } 84 | 85 | public void unregister(String callbackId) { 86 | if (registrations.containsKey(callbackId)) { 87 | registrations.get(callbackId).remove(); 88 | registrations.remove(callbackId); 89 | FirestoreLog.d(TAG, "Unregistered subscriber " + callbackId); 90 | } 91 | } 92 | 93 | public void storeTransaction(String transactionId, Transaction transaction) { 94 | TransactionQueue transactionQueue = new TransactionQueue(); 95 | transactionQueue.transaction = transaction; 96 | transactions.put(transactionId, transactionQueue); 97 | } 98 | 99 | public TransactionQueue getTransaction(String transactionId) { 100 | return transactions.get(transactionId); 101 | } 102 | 103 | public void removeTransaction(String transactionId) { 104 | transactions.remove(transactionId); 105 | } 106 | 107 | public void storeBatch(String batchId, WriteBatch batch) { 108 | batches.put(batchId, batch); 109 | } 110 | 111 | public WriteBatch getBatch(String batchId) { 112 | return batches.get(batchId); 113 | } 114 | 115 | public void removeBatch(String batchId) { 116 | batches.remove(batchId); 117 | } 118 | 119 | private class setLogLevel implements ActionHandler { 120 | 121 | @Override 122 | public boolean handle(JSONArray args, CallbackContext callbackContext) throws JSONException { 123 | FirestoreLog.setLogLevel(args.getString(0)); 124 | callbackContext.success(); 125 | return true; 126 | } 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/InitialiseHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import com.google.firebase.FirebaseApp; 7 | import com.google.firebase.FirebaseOptions; 8 | import com.google.firebase.firestore.FirebaseFirestore; 9 | import com.google.firebase.firestore.FirebaseFirestoreSettings; 10 | 11 | import org.apache.cordova.CallbackContext; 12 | import org.json.JSONArray; 13 | import org.json.JSONException; 14 | import org.json.JSONObject; 15 | 16 | 17 | public class InitialiseHandler implements ActionHandler { 18 | 19 | public static final String PERSIST = "persist"; 20 | public static final String DATE_PREFIX = "datePrefix"; 21 | public static final String TIMESTAMP_PREFIX = "timestampPrefix"; 22 | public static final String GEOPOINT_PREFIX = "geopointPrefix"; 23 | public static final String REFERENCE_PREFIX = "referencePrefix"; 24 | public static final String FIELDVALUE_DELETE = "fieldValueDelete"; 25 | public static final String FIELDVALUE_SERVERTIMESTAMP = "fieldValueServerTimestamp"; 26 | public static final String FIELDVALUE_ARRAYREMOVE = "fieldValueArrayRemove"; 27 | public static final String FIELDVALUE_ARRAYUNION = "fieldValueArrayUnion"; 28 | public static final String FIELDVALUE_INCREMENT = "fieldValueIncrement"; 29 | public static final String TIMESTAMPSINSNAPSHOTS = "timestampsInSnapshots"; 30 | public static final String CONFIG = "config"; 31 | private FirestorePlugin firestorePlugin; 32 | private Context context; 33 | 34 | public InitialiseHandler(Context context, FirestorePlugin firestorePlugin) { 35 | this.context = context; 36 | this.firestorePlugin = firestorePlugin; 37 | } 38 | 39 | @Override 40 | public boolean handle(final JSONArray args, CallbackContext callbackContext) { 41 | 42 | try { 43 | 44 | FirestoreLog.d(FirestorePlugin.TAG, "Initialising Firestore..."); 45 | 46 | final JSONObject options = args.getJSONObject(0); 47 | FirestoreLog.d(FirestorePlugin.TAG, "Options : " + options.toString()); 48 | 49 | FirebaseFirestore.setLoggingEnabled(true); 50 | if (options.has(CONFIG)) { 51 | JSONObject config = options.getJSONObject(CONFIG); 52 | FirebaseOptions.Builder configBuilder = new FirebaseOptions.Builder(); 53 | if (options.has("applicationId")) { 54 | configBuilder.setApplicationId(options.getString("applicationId")); 55 | } 56 | if (options.has("gcmSenderId")) { 57 | configBuilder.setGcmSenderId(options.getString("gcmSenderId")); 58 | } 59 | if (options.has("apiKey")) { 60 | configBuilder.setApiKey(options.getString("apiKey")); 61 | } 62 | if (options.has("projectId")) { 63 | configBuilder.setProjectId(options.getString("projectId")); 64 | } 65 | if (options.has("databaseUrl")) { 66 | configBuilder.setDatabaseUrl(options.getString("databaseUrl")); 67 | } 68 | if (options.has("storageBucket")) { 69 | configBuilder.setStorageBucket(options.getString("storageBucket")); 70 | } 71 | 72 | FirebaseOptions customOptions = configBuilder.build(); 73 | 74 | FirebaseApp customApp; 75 | try { 76 | customApp = FirebaseApp.getInstance(config.getString("apiKey")); 77 | } catch (Exception err) { 78 | FirebaseApp.initializeApp(this.context, customOptions, config.getString("apiKey")); 79 | customApp = FirebaseApp.getInstance(config.getString("apiKey")); 80 | err.printStackTrace(); 81 | } 82 | 83 | firestorePlugin.setDatabase(FirebaseFirestore.getInstance(customApp)); 84 | } else{ 85 | firestorePlugin.setDatabase(FirebaseFirestore.getInstance()); 86 | } 87 | 88 | boolean persist = false; 89 | 90 | if (options.has(PERSIST) && options.getBoolean(PERSIST)) { 91 | persist = true; 92 | } 93 | 94 | if (options.has(DATE_PREFIX)) { 95 | JSONDateWrapper.setDatePrefix(options.getString(DATE_PREFIX)); 96 | } 97 | 98 | if (options.has(TIMESTAMP_PREFIX)) { 99 | JSONTimestampWrapper.setTimestampPrefix(options.getString(TIMESTAMP_PREFIX)); 100 | } 101 | 102 | if (options.has(GEOPOINT_PREFIX)) { 103 | JSONGeopointWrapper.setGeopointPrefix(options.getString(GEOPOINT_PREFIX)); 104 | } 105 | 106 | if (options.has(REFERENCE_PREFIX)) { 107 | JSONReferenceWrapper.setReferencePrefix(options.getString(REFERENCE_PREFIX)); 108 | } 109 | 110 | if (options.has(FIELDVALUE_DELETE)) { 111 | FieldValueHelper.setDeletePrefix(options.getString(FIELDVALUE_DELETE)); 112 | } 113 | 114 | if (options.has(FIELDVALUE_SERVERTIMESTAMP)) { 115 | FieldValueHelper.setServerTimestampPrefix(options.getString(FIELDVALUE_SERVERTIMESTAMP)); 116 | } 117 | 118 | if (options.has(FIELDVALUE_ARRAYREMOVE)) { 119 | FieldValueHelper.setArrayRemovePrefix(options.getString(FIELDVALUE_ARRAYREMOVE)); 120 | } 121 | 122 | if (options.has(FIELDVALUE_ARRAYUNION)) { 123 | FieldValueHelper.setArrayUnionPrefix(options.getString(FIELDVALUE_ARRAYUNION)); 124 | } 125 | 126 | if (options.has(FIELDVALUE_INCREMENT)) { 127 | FieldValueHelper.setIncrementPrefix(options.getString(FIELDVALUE_INCREMENT)); 128 | } 129 | 130 | JSONHelper.setPlugin(this.firestorePlugin); 131 | 132 | FirestoreLog.d(FirestorePlugin.TAG, "Setting Firestore persistance to " + persist); 133 | 134 | boolean timestampsInSnapshots = false; 135 | 136 | if (options.has(TIMESTAMPSINSNAPSHOTS)) { 137 | timestampsInSnapshots = options.getBoolean(TIMESTAMPSINSNAPSHOTS); 138 | } 139 | 140 | FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder() 141 | .setPersistenceEnabled(persist) 142 | .setTimestampsInSnapshotsEnabled(timestampsInSnapshots) 143 | .build(); 144 | firestorePlugin.getDatabase().setFirestoreSettings(settings); 145 | 146 | callbackContext.success(); 147 | } catch (JSONException e) { 148 | FirestoreLog.e(FirestorePlugin.TAG, "Error initialising Firestore", e); 149 | callbackContext.error(e.getMessage()); 150 | } 151 | 152 | return true; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/JSONDateWrapper.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.GeoPoint; 4 | 5 | import java.util.Date; 6 | 7 | public class JSONDateWrapper extends Date { 8 | 9 | private static String datePrefix = "__DATE:"; 10 | 11 | public JSONDateWrapper(Date date) { 12 | super(date.getTime()); 13 | } 14 | 15 | public static void setDatePrefix(String datePrefix) { 16 | JSONDateWrapper.datePrefix = datePrefix; 17 | } 18 | 19 | public static boolean isWrappedDate(Object value) { 20 | 21 | 22 | if (value instanceof String && ((String) value).startsWith(datePrefix)) { 23 | return true; 24 | } 25 | 26 | return false; 27 | } 28 | 29 | public static Date unwrapDate(Object value) { 30 | String stringValue = (String) value; 31 | int prefixLength = datePrefix.length(); 32 | String timestamp = stringValue.substring(prefixLength + 1); 33 | 34 | return new Date(Long.parseLong(timestamp)); 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return this.datePrefix + this.getTime(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/JSONGeopointWrapper.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.GeoPoint; 4 | 5 | import java.util.Date; 6 | 7 | public class JSONGeopointWrapper extends GeoPoint { 8 | 9 | private static String geopointPrefix = "__GEOPOINT:"; 10 | 11 | public JSONGeopointWrapper(GeoPoint geoPoint) { 12 | super(geoPoint.getLatitude(), geoPoint.getLongitude()); 13 | } 14 | 15 | public static void setGeopointPrefix(String geopointPrefix) { 16 | JSONGeopointWrapper.geopointPrefix = geopointPrefix; 17 | } 18 | 19 | public static boolean isWrappedGeoPoint(Object value) { 20 | 21 | 22 | if (value instanceof String && ((String) value).startsWith(geopointPrefix)) { 23 | return true; 24 | } 25 | 26 | return false; 27 | } 28 | 29 | public static GeoPoint unwrapGeoPoint(Object value) { 30 | String stringValue = (String) value; 31 | int prefixLength = geopointPrefix.length(); 32 | String latLng = stringValue.substring(prefixLength + 1); 33 | String[] tmp = latLng.split(","); 34 | return new GeoPoint(Double.parseDouble(tmp[0]), Double.parseDouble(tmp[1])); 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return this.geopointPrefix + this.getLatitude() + "," + this.getLongitude(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/JSONHelper.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.DocumentReference; 4 | 5 | import com.google.firebase.Timestamp; 6 | import com.google.firebase.firestore.GeoPoint; 7 | import com.google.gson.Gson; 8 | import com.google.gson.reflect.TypeToken; 9 | 10 | import org.json.JSONArray; 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | 14 | import java.lang.reflect.Type; 15 | import java.util.ArrayList; 16 | import java.util.Date; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | public class JSONHelper { 21 | 22 | private static FirestorePlugin firestorePlugin; 23 | 24 | static void setPlugin(FirestorePlugin firestorePlugin) { 25 | JSONHelper.firestorePlugin = firestorePlugin; 26 | } 27 | 28 | static JSONObject toJSON(Map values) throws JSONException { 29 | JSONObject result = new JSONObject(); 30 | 31 | for (Map.Entry entry : values.entrySet()) { 32 | Object value = entry.getValue(); 33 | if (value instanceof Map) { 34 | value = toJSON((Map) value); 35 | ; 36 | } else if (value instanceof List) { 37 | value = toJSONArray((List) value); 38 | } else if (value instanceof Date) { 39 | value = new JSONDateWrapper((Date) value); 40 | } else if (value instanceof Timestamp) { 41 | value = new JSONTimestampWrapper((Timestamp) value); 42 | } else if (value instanceof GeoPoint) { 43 | value = new JSONGeopointWrapper((GeoPoint) value); 44 | } else if (value instanceof DocumentReference) { 45 | value = new JSONReferenceWrapper((DocumentReference) value); 46 | } 47 | result.put(entry.getKey(), value); 48 | } 49 | return result; 50 | } 51 | 52 | private static JSONArray toJSONArray(List values) throws JSONException { 53 | JSONArray result = new JSONArray(); 54 | 55 | for (Object value : values) { 56 | if (value instanceof Map) { 57 | value = toJSON((Map) value); 58 | ; 59 | } else if (value instanceof List) { 60 | value = toJSONArray((List) value); 61 | } else if (value instanceof Date) { 62 | value = new JSONDateWrapper((Date) value); 63 | } else if (value instanceof Timestamp) { 64 | value = new JSONTimestampWrapper((Timestamp) value); 65 | } else if (value instanceof GeoPoint) { 66 | value = new JSONGeopointWrapper((GeoPoint) value); 67 | } else if (value instanceof DocumentReference) { 68 | value = new JSONReferenceWrapper((DocumentReference) value); 69 | } 70 | result.put(value); 71 | } 72 | return result; 73 | } 74 | 75 | 76 | public static Object fromJSON(Object value) { 77 | Object newValue; 78 | 79 | if (value instanceof String) { 80 | if (JSONGeopointWrapper.isWrappedGeoPoint(value)) { 81 | newValue = JSONGeopointWrapper.unwrapGeoPoint(value); 82 | } else if (JSONDateWrapper.isWrappedDate(value)) { 83 | newValue = JSONDateWrapper.unwrapDate(value); 84 | } else if (JSONTimestampWrapper.isWrappedTimestamp(value)) { 85 | newValue = JSONTimestampWrapper.unwrapTimestamp(value); 86 | } else if (JSONReferenceWrapper.isWrappedReference(value)) { 87 | newValue = JSONReferenceWrapper.unwrapReference(JSONHelper.firestorePlugin, value); 88 | } else if (FieldValueHelper.isWrappedFieldValue(value)) { 89 | newValue = FieldValueHelper.unwrapFieldValue(value); 90 | } else { 91 | newValue = value; 92 | } 93 | } else if (value instanceof Map) { 94 | newValue = toSettableMapInternal((Map) value); 95 | } else if (value instanceof ArrayList) { 96 | newValue = toSettableArrayInternal((ArrayList) value); 97 | } else if (value instanceof JSONObject) { 98 | newValue = toSettableJSONInternal((JSONObject) value); 99 | } 100 | else { 101 | newValue = value; 102 | } 103 | 104 | return newValue; 105 | } 106 | 107 | 108 | private static Map toSettableJSONInternal(JSONObject map) { 109 | Type type = new TypeToken>() { 110 | }.getType(); 111 | Map value = new Gson().fromJson(map.toString(), type); 112 | 113 | return toSettableMapInternal(value); 114 | } 115 | 116 | private static Map toSettableMapInternal(Map value) { 117 | 118 | for (Map.Entry entry : value.entrySet()) { 119 | entry.setValue(fromJSON(entry.getValue())); 120 | } 121 | return value; 122 | } 123 | 124 | private static ArrayList toSettableArrayInternal(ArrayList array) { 125 | 126 | int i = 0; 127 | 128 | for (Object entryValue : array) { 129 | array.set(i,fromJSON(entryValue)); 130 | 131 | i++; 132 | } 133 | return array; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/JSONReferenceWrapper.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.DocumentReference; 4 | 5 | import java.util.Date; 6 | 7 | public class JSONReferenceWrapper { 8 | 9 | private static String referencePrefix = "__REFERENCE:"; 10 | private DocumentReference documentReference; 11 | 12 | public JSONReferenceWrapper(DocumentReference documentReference) { 13 | this.documentReference = documentReference; 14 | } 15 | 16 | public static void setReferencePrefix(String referencePrefix) { 17 | JSONReferenceWrapper.referencePrefix = referencePrefix; 18 | } 19 | 20 | public static boolean isWrappedReference(Object value) { 21 | 22 | 23 | if (value instanceof String && ((String) value).startsWith(referencePrefix)) { 24 | return true; 25 | } 26 | 27 | return false; 28 | } 29 | 30 | public static DocumentReference unwrapReference(FirestorePlugin firestorePlugin, Object value) { 31 | String stringValue = (String) value; 32 | int prefixLength = referencePrefix.length(); 33 | String reference = stringValue.substring(prefixLength + 1); 34 | return firestorePlugin.getDatabase().document(reference); 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return this.referencePrefix + this.documentReference.getPath(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/JSONTimestampWrapper.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.Timestamp; 4 | 5 | import java.util.Date; 6 | 7 | public class JSONTimestampWrapper { 8 | 9 | private static String timestampPrefix = "__TIMESTAMP:"; 10 | private Timestamp timestamp; 11 | 12 | public JSONTimestampWrapper(Timestamp timestamp) { 13 | this.timestamp = timestamp; 14 | } 15 | 16 | public static void setTimestampPrefix(String timestampPrefix) { 17 | JSONTimestampWrapper.timestampPrefix = timestampPrefix; 18 | } 19 | 20 | public static boolean isWrappedTimestamp(Object value) { 21 | 22 | 23 | if (value instanceof String && ((String) value).startsWith(timestampPrefix)) { 24 | return true; 25 | } 26 | 27 | return false; 28 | } 29 | 30 | public static Timestamp unwrapTimestamp(Object value) { 31 | String stringValue = (String) value; 32 | int prefixLength = timestampPrefix.length(); 33 | String timestamp = stringValue.substring(prefixLength).substring(0, stringValue.length() - prefixLength); 34 | 35 | long seconds = 0L; 36 | int nanoseconds = 0; 37 | 38 | if (timestamp.contains("_")) { 39 | String[] timestampParts = timestamp.split("_"); 40 | seconds = Long.parseLong(timestampParts[0]); 41 | nanoseconds = Integer.parseInt(timestampParts[1]); 42 | } else { 43 | seconds = Long.parseLong(timestamp); 44 | } 45 | 46 | return new Timestamp(seconds, nanoseconds); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return this.timestampPrefix + timestamp.getSeconds() + "_" + timestamp.getNanoseconds(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/LimitQueryHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | 4 | import com.google.firebase.firestore.Query; 5 | 6 | public class LimitQueryHandler implements QueryHandler { 7 | @Override 8 | public Query handle(Query query, Object limit) { 9 | System.out.println("Type of limit " + limit.getClass().getName()); 10 | Long longLimit; 11 | if (limit instanceof String) { 12 | longLimit = Long.parseLong((String) limit); 13 | } else if(limit instanceof Number) { 14 | longLimit = ((Number) limit).longValue(); 15 | } else { 16 | throw new IllegalArgumentException("Limit should be instanceof String or Number, got : " + limit.getClass().getName()); 17 | } 18 | 19 | return query.limit(longLimit); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/OrderByQueryHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | 4 | import android.util.Log; 5 | 6 | import com.google.firebase.firestore.Query; 7 | 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | public class OrderByQueryHandler implements QueryHandler { 12 | @Override 13 | public Query handle(Query query, Object orderByObject) { 14 | 15 | JSONObject order = (JSONObject) orderByObject; 16 | 17 | try { 18 | Query.Direction direction = Query.Direction.ASCENDING; 19 | 20 | if ("desc".equals(order.getString("direction"))) { 21 | direction = Query.Direction.DESCENDING; 22 | } 23 | 24 | query = query.orderBy(order.getString("field"), direction); 25 | 26 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Order by %s (%s)", order.getString("field"), direction.toString())); 27 | 28 | } catch (JSONException e) { 29 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing ordering", e); 30 | } 31 | 32 | return query; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/PluginResultHelper.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.firebase.firestore.DocumentReference; 6 | import com.google.firebase.firestore.DocumentSnapshot; 7 | import com.google.firebase.firestore.FirebaseFirestoreException; 8 | import com.google.firebase.firestore.QuerySnapshot; 9 | import com.google.firebase.firestore.QueryDocumentSnapshot; 10 | 11 | import org.apache.cordova.PluginResult; 12 | import org.json.JSONArray; 13 | import org.json.JSONException; 14 | import org.json.JSONObject; 15 | 16 | import java.util.Collections; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | public class PluginResultHelper { 21 | 22 | private static final Map errorCodeMap = initMap(); 23 | 24 | private static Map initMap() { 25 | Map map = new HashMap<>(); 26 | map.put(FirebaseFirestoreException.Code.ABORTED,"aborted"); 27 | map.put(FirebaseFirestoreException.Code.ALREADY_EXISTS,"already-exists"); 28 | map.put(FirebaseFirestoreException.Code.CANCELLED,"cancelled"); 29 | map.put(FirebaseFirestoreException.Code.DATA_LOSS,"data-loss"); 30 | map.put(FirebaseFirestoreException.Code.DEADLINE_EXCEEDED,"deadline-exceeded"); 31 | map.put(FirebaseFirestoreException.Code.FAILED_PRECONDITION,"failed-precondition"); 32 | map.put(FirebaseFirestoreException.Code.INTERNAL,"internal"); 33 | map.put(FirebaseFirestoreException.Code.INVALID_ARGUMENT,"invalid-argument"); 34 | map.put(FirebaseFirestoreException.Code.NOT_FOUND,"not-found"); 35 | map.put(FirebaseFirestoreException.Code.OK,"ok"); 36 | map.put(FirebaseFirestoreException.Code.OUT_OF_RANGE,"out-of-range"); 37 | map.put(FirebaseFirestoreException.Code.PERMISSION_DENIED,"permission-denied"); 38 | map.put(FirebaseFirestoreException.Code.RESOURCE_EXHAUSTED,"resource-exhausted"); 39 | map.put(FirebaseFirestoreException.Code.UNAUTHENTICATED,"unauthenticated"); 40 | map.put(FirebaseFirestoreException.Code.UNAVAILABLE,"unavailable"); 41 | map.put(FirebaseFirestoreException.Code.UNIMPLEMENTED,"unimplemented"); 42 | map.put(FirebaseFirestoreException.Code.UNKNOWN,"unknown"); 43 | 44 | return Collections.unmodifiableMap(map); 45 | } 46 | 47 | static PluginResult createPluginErrorResult(FirebaseFirestoreException e, boolean reusable) { 48 | PluginResult pluginResult = new PluginResult(PluginResult.Status.ERROR, 49 | createError(e.getCode().toString(),e.getMessage())); 50 | pluginResult.setKeepCallback(reusable); 51 | return pluginResult; 52 | } 53 | 54 | static PluginResult createPluginResult(DocumentSnapshot doc, boolean reusable) { 55 | PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, createDocumentSnapshot(doc)); 56 | pluginResult.setKeepCallback(reusable); 57 | return pluginResult; 58 | } 59 | 60 | static PluginResult createPluginResult(DocumentReference doc, boolean reusable) { 61 | PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, createDocumentReference(doc)); 62 | pluginResult.setKeepCallback(reusable); 63 | return pluginResult; 64 | } 65 | 66 | static PluginResult createPluginResult(QuerySnapshot value, boolean reusable) { 67 | JSONObject querySnapshot = new JSONObject(); 68 | JSONArray array = new JSONArray(); 69 | 70 | FirestoreLog.d(FirestorePlugin.TAG, "Creating query snapshot result"); 71 | 72 | for (QueryDocumentSnapshot doc : value) { 73 | JSONObject document = createDocumentSnapshot(doc); 74 | array.put(document); 75 | } 76 | 77 | try { 78 | querySnapshot.put("docs", array); 79 | } catch (JSONException e) { 80 | throw new RuntimeException(e); 81 | } 82 | 83 | PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, querySnapshot); 84 | pluginResult.setKeepCallback(reusable); 85 | return pluginResult; 86 | } 87 | 88 | public static JSONObject createError(String code, String message) { 89 | JSONObject error = new JSONObject(); 90 | 91 | FirestoreLog.d(FirestorePlugin.TAG, "Creating error result"); 92 | 93 | try { 94 | error.put("code", errorCodeMap.get(code)); 95 | error.put("message", message); 96 | 97 | } catch (JSONException e) { 98 | FirestoreLog.e(FirestorePlugin.TAG, "Error creating error result", e); 99 | throw new RuntimeException(e); 100 | } 101 | 102 | return error; 103 | } 104 | 105 | private static JSONObject createDocumentSnapshot(DocumentSnapshot doc) { 106 | JSONObject documentSnapshot = new JSONObject(); 107 | 108 | FirestoreLog.d(FirestorePlugin.TAG, "Creating document snapshot result"); 109 | 110 | try { 111 | documentSnapshot.put("id", doc.getId()); 112 | documentSnapshot.put("exists", doc.exists()); 113 | documentSnapshot.put("ref", doc.getReference().getId()); 114 | 115 | if (doc.exists()) { 116 | documentSnapshot.put("_data", JSONHelper.toJSON(doc.getData())); 117 | } 118 | 119 | } catch (JSONException e) { 120 | FirestoreLog.e(FirestorePlugin.TAG, "Error creating document snapshot result", e); 121 | throw new RuntimeException(e); 122 | } 123 | 124 | return documentSnapshot; 125 | } 126 | 127 | private static JSONObject createDocumentReference(DocumentReference doc) { 128 | JSONObject documentReference = new JSONObject(); 129 | 130 | FirestoreLog.e(FirestorePlugin.TAG, "Creating document snapshot result"); 131 | 132 | try { 133 | documentReference.put("id", doc.getId()); 134 | 135 | } catch (JSONException e) { 136 | FirestoreLog.e(FirestorePlugin.TAG, "Error creating document reference result", e); 137 | throw new RuntimeException(e); 138 | } 139 | 140 | return documentReference; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/QueryHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.Query; 4 | 5 | public interface QueryHandler { 6 | Query handle(Query query, Object value); 7 | } 8 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/QueryHelper.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.firebase.firestore.Query; 6 | 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class QueryHelper { 15 | private static Map queryHandlers = new HashMap(); 16 | 17 | static { 18 | queryHandlers.put("limit", new LimitQueryHandler()); 19 | queryHandlers.put("where", new WhereQueryHandler()); 20 | queryHandlers.put("orderBy", new OrderByQueryHandler()); 21 | queryHandlers.put("startAfter", new StartAfterQueryHandler()); 22 | queryHandlers.put("startAt", new StartAtQueryHandler()); 23 | queryHandlers.put("endAt", new EndAtQueryHandler()); 24 | queryHandlers.put("endBefore", new EndBeforeQueryHandler()); 25 | } 26 | 27 | 28 | public static Query processQueries(JSONArray queries, Query query, FirestorePlugin firestorePlugin) throws JSONException { 29 | 30 | FirestoreLog.d(FirestorePlugin.TAG, "Processing queries"); 31 | 32 | int length = queries.length(); 33 | for (int i = 0; i < length; i++) { 34 | JSONObject queryDefinition = queries.getJSONObject(i); 35 | 36 | String queryType = queryDefinition.getString("queryType"); 37 | 38 | if (queryHandlers.containsKey(queryType)) { 39 | query = queryHandlers.get(queryType).handle(query, queryDefinition.get("value")); 40 | } else { 41 | FirestoreLog.e(FirestorePlugin.TAG, String.format("Unknown query type %s", queryType)); 42 | } 43 | } 44 | 45 | return query; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/RunTransactionHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | 4 | //import android.support.annotation.NonNull; 5 | import androidx.annotation.NonNull; 6 | 7 | import android.util.Log; 8 | import android.webkit.ValueCallback; 9 | import android.webkit.WebView; 10 | 11 | import com.google.android.gms.tasks.OnFailureListener; 12 | import com.google.android.gms.tasks.OnSuccessListener; 13 | import com.google.firebase.firestore.DocumentReference; 14 | import com.google.firebase.firestore.FirebaseFirestoreException; 15 | import com.google.firebase.firestore.SetOptions; 16 | import com.google.firebase.firestore.Transaction; 17 | 18 | import org.apache.cordova.CallbackContext; 19 | import org.json.JSONArray; 20 | import org.json.JSONException; 21 | 22 | import java.util.Map; 23 | 24 | public class RunTransactionHandler implements ActionHandler { 25 | 26 | public static final int TRANSACTION_TIMEOUT = 30000; 27 | private FirestorePlugin firestorePlugin; 28 | 29 | public RunTransactionHandler(FirestorePlugin firestorePlugin) { 30 | this.firestorePlugin = firestorePlugin; 31 | } 32 | 33 | @Override 34 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 35 | try { 36 | final String transactionId = args.getString(0); 37 | 38 | FirestoreLog.d(FirestorePlugin.TAG, "Running transaction"); 39 | 40 | try { 41 | firestorePlugin.getDatabase().runTransaction(new Transaction.Function() { 42 | @Override 43 | public String apply(Transaction transaction) throws FirebaseFirestoreException { 44 | 45 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Applying transaction %s", transactionId)); 46 | 47 | firestorePlugin.storeTransaction(transactionId, transaction); 48 | TransactionQueue transactionQueue = firestorePlugin.getTransaction(transactionId); 49 | 50 | Runnable runnable = new Runnable() { 51 | @Override 52 | public void run() { 53 | 54 | WebView wv = (WebView) firestorePlugin.webView.getView(); 55 | wv.evaluateJavascript(String.format("Firestore.__executeTransaction('%s');", transactionId), new ValueCallback() { 56 | @Override 57 | public void onReceiveValue(String value) { 58 | } 59 | }); 60 | } 61 | }; 62 | 63 | firestorePlugin.cordova.getActivity().runOnUiThread(runnable); 64 | 65 | Long started = System.currentTimeMillis(); 66 | 67 | boolean timedOut = false; 68 | 69 | TransactionOperationType transactionOperationType = TransactionOperationType.NONE; 70 | 71 | while (transactionOperationType != TransactionOperationType.RESOLVE && !timedOut) { 72 | 73 | timedOut = timedOut(started); 74 | 75 | while (transactionQueue.queue.size() < 1 && !timedOut) { 76 | timedOut = timedOut(started); 77 | } 78 | 79 | TransactionDetails transactionDetails = transactionQueue.queue.get(0); 80 | transactionOperationType = transactionDetails.transactionOperationType; 81 | 82 | switch (transactionOperationType) { 83 | case SET: 84 | performSet(transaction, transactionDetails, transactionId); 85 | break; 86 | case DELETE: 87 | performDelete(transaction, transactionDetails, transactionId); 88 | break; 89 | case UPDATE: 90 | performUpdate(transaction, transactionDetails, transactionId); 91 | break; 92 | default: 93 | break; 94 | } 95 | 96 | transactionQueue.queue.remove(0); 97 | } 98 | 99 | firestorePlugin.removeTransaction(transactionId); 100 | 101 | if (timedOut) { 102 | throw new RuntimeException("Transaction timed out"); 103 | } else { 104 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Sync result complete for transaction %s", transactionId)); 105 | } 106 | 107 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Returning transaction %s result %s", transactionId, transactionQueue.results.toString())); 108 | return transactionQueue.results.toString(); 109 | } 110 | 111 | private boolean timedOut(Long started) { 112 | Long current = System.currentTimeMillis(); 113 | 114 | if (current - started > TRANSACTION_TIMEOUT) { 115 | return true; 116 | } 117 | 118 | return false; 119 | } 120 | }).addOnSuccessListener(new OnSuccessListener() { 121 | @Override 122 | public void onSuccess(String result) { 123 | callbackContext.success(result); 124 | FirestoreLog.d(FirestorePlugin.TAG, "Transaction success"); 125 | } 126 | }).addOnFailureListener(new OnFailureListener() { 127 | @Override 128 | public void onFailure(@NonNull Exception e) { 129 | FirestoreLog.w(FirestorePlugin.TAG, "Transaction failure", e); 130 | String errorCode = ((FirebaseFirestoreException) e).getCode().name(); 131 | callbackContext.error(PluginResultHelper.createError(errorCode, e.getMessage())); 132 | } 133 | }); 134 | 135 | } catch (Exception e) { 136 | FirestoreLog.e(FirestorePlugin.TAG, "Error running transaction", e); 137 | callbackContext.error(e.getMessage()); 138 | } 139 | 140 | } catch (JSONException e) { 141 | FirestoreLog.e(FirestorePlugin.TAG, "Error running transaction", e); 142 | callbackContext.error(e.getMessage()); 143 | } 144 | 145 | return true; 146 | } 147 | 148 | public void performDelete(Transaction transaction, TransactionDetails transactionDetails, String transactionId) { 149 | 150 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Perform transactional document delete for %s", transactionId)); 151 | 152 | try { 153 | DocumentReference documentRef = firestorePlugin.getDatabase().collection(transactionDetails.collectionPath).document(transactionDetails.docId); 154 | transaction.delete(documentRef); 155 | 156 | } catch (Exception e) { 157 | FirestoreLog.e(FirestorePlugin.TAG, "Error performing transactional document delete in thread", e); 158 | throw new RuntimeException(e); 159 | } 160 | } 161 | 162 | public void performSet(Transaction transaction, TransactionDetails transactionDetails, String transactionId) { 163 | 164 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Perform transactional document set for %s", transactionId)); 165 | 166 | SetOptions setOptions = DocSetOptions.getSetOptions(transactionDetails.options); 167 | 168 | try { 169 | DocumentReference documentRef = firestorePlugin.getDatabase().collection(transactionDetails.collectionPath).document(transactionDetails.docId); 170 | 171 | if (setOptions == null) { 172 | transaction.set(documentRef, JSONHelper.fromJSON(transactionDetails.data)); 173 | } else { 174 | transaction.set(documentRef, JSONHelper.fromJSON(transactionDetails.data), setOptions); 175 | } 176 | 177 | } catch (Exception e) { 178 | FirestoreLog.e(FirestorePlugin.TAG, "Error performing transactional document set in thread", e); 179 | throw new RuntimeException(e); 180 | } 181 | } 182 | 183 | public void performUpdate(Transaction transaction, TransactionDetails transactionDetails, String transactionId) { 184 | 185 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Perform transactional document update for %s", transactionId)); 186 | 187 | try { 188 | DocumentReference documentRef = firestorePlugin.getDatabase().collection(transactionDetails.collectionPath).document(transactionDetails.docId); 189 | transaction.update(documentRef,(Map)JSONHelper.fromJSON(transactionDetails.data)); 190 | 191 | } catch (Exception e) { 192 | FirestoreLog.e(FirestorePlugin.TAG, "Error performing transactional document update in thread", e); 193 | throw new RuntimeException(e); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/StartAfterQueryHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.Query; 4 | 5 | public class StartAfterQueryHandler implements QueryHandler { 6 | @Override 7 | public Query handle(Query query, Object startAfter) { 8 | return query.startAfter(JSONHelper.fromJSON(startAfter)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/StartAtQueryHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.Query; 4 | 5 | public class StartAtQueryHandler implements QueryHandler { 6 | @Override 7 | public Query handle(Query query, Object startAt) { 8 | return query.startAt(JSONHelper.fromJSON(startAt)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/TransactionDetails.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import org.json.JSONObject; 4 | 5 | public class TransactionDetails { 6 | public String collectionPath; 7 | public String docId; 8 | public JSONObject data; 9 | public JSONObject options; 10 | public String results; 11 | public TransactionOperationType transactionOperationType; 12 | } 13 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/TransactionDocDeleteHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | 4 | import android.util.Log; 5 | 6 | import org.apache.cordova.CallbackContext; 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | 10 | public class TransactionDocDeleteHandler implements ActionHandler { 11 | private final FirestorePlugin firestorePlugin; 12 | 13 | public TransactionDocDeleteHandler(FirestorePlugin firestorePlugin) { 14 | this.firestorePlugin = firestorePlugin; 15 | } 16 | 17 | @Override 18 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 19 | try { 20 | final String transactionId = args.getString(0); 21 | final String docId = args.getString(1); 22 | final String collectionPath = args.getString(2); 23 | 24 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Transactional document delete for %s", transactionId)); 25 | 26 | TransactionQueue transactionQueue = firestorePlugin.getTransaction(transactionId); 27 | 28 | TransactionDetails transactionDetails = new TransactionDetails(); 29 | transactionDetails.collectionPath = collectionPath; 30 | transactionDetails.docId = docId; 31 | transactionDetails.transactionOperationType = TransactionOperationType.DELETE; 32 | 33 | transactionQueue.queue.add(transactionDetails); 34 | callbackContext.success(); 35 | 36 | } catch (JSONException e) { 37 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing transactional document delete", e); 38 | callbackContext.error(e.getMessage()); 39 | } 40 | 41 | return true; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/TransactionDocGetHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | 4 | import android.util.Log; 5 | 6 | import com.google.firebase.firestore.DocumentReference; 7 | import com.google.firebase.firestore.FirebaseFirestoreException; 8 | 9 | import org.apache.cordova.CallbackContext; 10 | import org.json.JSONArray; 11 | import org.json.JSONException; 12 | 13 | import static uk.co.reallysmall.cordova.plugin.firestore.PluginResultHelper.createPluginResult; 14 | 15 | public class TransactionDocGetHandler implements ActionHandler { 16 | 17 | private FirestorePlugin firestorePlugin; 18 | 19 | public TransactionDocGetHandler(FirestorePlugin firestorePlugin) { 20 | this.firestorePlugin = firestorePlugin; 21 | } 22 | 23 | @Override 24 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 25 | try { 26 | final String transactionId = args.getString(0); 27 | final String doc = args.getString(1); 28 | final String collectionPath = args.getString(2); 29 | 30 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Transactional document get for %s", transactionId)); 31 | 32 | TransactionQueue transactionQueue = firestorePlugin.getTransaction(transactionId); 33 | 34 | try { 35 | DocumentReference documentRef = firestorePlugin.getDatabase().collection(collectionPath).document(doc); 36 | 37 | callbackContext.sendPluginResult(createPluginResult(transactionQueue.transaction.get(documentRef), false)); 38 | 39 | } catch (Exception e) { 40 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing transactional document get in thread", e); 41 | String errorCode = ((FirebaseFirestoreException) e).getCode().name(); 42 | callbackContext.error(PluginResultHelper.createError(errorCode, e.getMessage())); 43 | } 44 | 45 | } catch (JSONException e) { 46 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing transactional document snapshot", e); 47 | callbackContext.error(e.getMessage()); 48 | } 49 | 50 | return true; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/TransactionDocSetHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import android.util.Log; 4 | 5 | import org.apache.cordova.CallbackContext; 6 | import org.json.JSONArray; 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | public class TransactionDocSetHandler implements ActionHandler { 11 | private final FirestorePlugin firestorePlugin; 12 | 13 | public TransactionDocSetHandler(FirestorePlugin firestorePlugin) { 14 | this.firestorePlugin = firestorePlugin; 15 | } 16 | 17 | @Override 18 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 19 | try { 20 | final String transactionId = args.getString(0); 21 | final String docId = args.getString(1); 22 | final String collectionPath = args.getString(2); 23 | final JSONObject data = args.getJSONObject(3); 24 | 25 | final JSONObject options; 26 | 27 | if (!args.isNull(4)) { 28 | options = args.getJSONObject(4); 29 | } else { 30 | options = null; 31 | } 32 | 33 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Transactional document set for %s", transactionId)); 34 | 35 | TransactionQueue transactionQueue = firestorePlugin.getTransaction(transactionId); 36 | 37 | TransactionDetails transactionDetails = new TransactionDetails(); 38 | transactionDetails.collectionPath = collectionPath; 39 | transactionDetails.docId = docId; 40 | transactionDetails.data = data; 41 | transactionDetails.options = options; 42 | transactionDetails.transactionOperationType = TransactionOperationType.SET; 43 | 44 | transactionQueue.queue.add(transactionDetails); 45 | callbackContext.success(); 46 | 47 | } catch (JSONException e) { 48 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing transactional document set", e); 49 | callbackContext.error(e.getMessage()); 50 | } 51 | 52 | return true; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/TransactionDocUpdateHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | 4 | import android.util.Log; 5 | 6 | import org.apache.cordova.CallbackContext; 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | public class TransactionDocUpdateHandler implements ActionHandler { 12 | 13 | private FirestorePlugin firestorePlugin; 14 | 15 | public TransactionDocUpdateHandler(FirestorePlugin firestorePlugin) { 16 | this.firestorePlugin = firestorePlugin; 17 | } 18 | 19 | @Override 20 | public boolean handle(JSONArray args, final CallbackContext callbackContext) { 21 | try { 22 | final String transactionId = args.getString(0); 23 | final String docId = args.getString(1); 24 | final String collectionPath = args.getString(2); 25 | final JSONObject data = args.getJSONObject(3); 26 | 27 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Transactional document update for %s", transactionId)); 28 | 29 | TransactionQueue transactionQueue = firestorePlugin.getTransaction(transactionId); 30 | 31 | TransactionDetails transactionDetails = new TransactionDetails(); 32 | transactionDetails.collectionPath = collectionPath; 33 | transactionDetails.docId = docId; 34 | transactionDetails.data = data; 35 | transactionDetails.transactionOperationType = TransactionOperationType.UPDATE; 36 | 37 | transactionQueue.queue.add(transactionDetails); 38 | callbackContext.success(); 39 | 40 | } catch (JSONException e) { 41 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing transactional document update", e); 42 | callbackContext.error(e.getMessage()); 43 | } 44 | 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/TransactionOperationType.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | enum TransactionOperationType { 4 | NONE, 5 | SET, 6 | UPDATE, 7 | DELETE, 8 | RESOLVE 9 | } 10 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/TransactionQueue.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import com.google.firebase.firestore.Transaction; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | public class TransactionQueue { 10 | public Transaction transaction; 11 | public StringBuilder results = new StringBuilder(); 12 | public List queue = Collections.synchronizedList(new ArrayList()); 13 | } 14 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/TransactionResolveHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | 4 | import android.util.Log; 5 | 6 | import org.apache.cordova.CallbackContext; 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | 10 | public class TransactionResolveHandler implements ActionHandler { 11 | private final FirestorePlugin firestorePlugin; 12 | 13 | public TransactionResolveHandler(FirestorePlugin firestorePlugin) { 14 | this.firestorePlugin = firestorePlugin; 15 | } 16 | 17 | @Override 18 | public boolean handle(JSONArray args, CallbackContext callbackContext) { 19 | 20 | try { 21 | String transactionId = args.getString(0); 22 | String result = args.getString(1); 23 | 24 | FirestoreLog.d(FirestorePlugin.TAG, String.format("Transactional resolve for %s", transactionId)); 25 | 26 | TransactionQueue transactionQueue = firestorePlugin.getTransaction(transactionId); 27 | transactionQueue.results.append(result); 28 | 29 | TransactionDetails transactionDetails = new TransactionDetails(); 30 | transactionDetails.transactionOperationType = TransactionOperationType.RESOLVE; 31 | 32 | transactionQueue.queue.add(transactionDetails); 33 | 34 | } catch (JSONException e) { 35 | FirestoreLog.e(FirestorePlugin.TAG, "Error resolving transaction", e); 36 | callbackContext.error(e.getMessage()); 37 | } 38 | 39 | return false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/android/uk/co/reallysmall/cordova/plugin/firestore/WhereQueryHandler.java: -------------------------------------------------------------------------------- 1 | package uk.co.reallysmall.cordova.plugin.firestore; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.firebase.firestore.Query; 6 | import com.google.firebase.firestore.FieldPath; 7 | 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | public class WhereQueryHandler implements QueryHandler { 12 | @Override 13 | public Query handle(Query query, Object whereObject) { 14 | 15 | try { 16 | JSONObject where = (JSONObject) whereObject; 17 | 18 | String fieldPath =where.getString("fieldPath"); 19 | String opStr = where.getString("opStr"); 20 | Object value = parseWhereValue(where); 21 | 22 | if (FieldPathHelper.isWrapped(fieldPath)) { 23 | 24 | FieldPath fieldPathUnwrapped = FieldPathHelper.unwrapFieldPath(fieldPath); 25 | 26 | if ("==".equals(opStr)) { 27 | query = query.whereEqualTo(fieldPathUnwrapped, value); 28 | } else if (">".equals(opStr)) { 29 | query = query.whereGreaterThan(fieldPathUnwrapped, value); 30 | } else if (">=".equals(opStr)) { 31 | query = query.whereGreaterThanOrEqualTo(fieldPathUnwrapped, value); 32 | } else if ("<".equals(opStr)) { 33 | query = query.whereLessThan(fieldPathUnwrapped, value); 34 | } else if ("<=".equals(opStr)) { 35 | query = query.whereLessThanOrEqualTo(fieldPathUnwrapped, value); 36 | } else if ("in".equals(opStr)) { 37 | // query = query.whereIn(fieldPathUnwrapped, value); 38 | } else if ("array-contains".equals(opStr)) { 39 | query = query.whereArrayContains(fieldPathUnwrapped, value); 40 | } else if ("array-contains-any".equals(opStr)) { 41 | query = query.whereArrayContains(fieldPathUnwrapped, value); 42 | } else { 43 | throw new RuntimeException("Unknown operator type " + opStr); 44 | } 45 | } else { 46 | if ("==".equals(opStr)) { 47 | query = query.whereEqualTo(fieldPath, value); 48 | } else if (">".equals(opStr)) { 49 | query = query.whereGreaterThan(fieldPath, value); 50 | } else if (">=".equals(opStr)) { 51 | query = query.whereGreaterThanOrEqualTo(fieldPath, value); 52 | } else if ("<".equals(opStr)) { 53 | query = query.whereLessThan(fieldPath, value); 54 | } else if ("<=".equals(opStr)) { 55 | query = query.whereLessThanOrEqualTo(fieldPath, value); 56 | } else if ("in".equals(opStr)) { 57 | // query = query.whereIn(fieldPath, value); 58 | } else if ("array-contains".equals(opStr)) { 59 | query = query.whereArrayContains(fieldPath, value); 60 | } else if ("array-contains-any".equals(opStr)) { 61 | query = query.whereArrayContains(fieldPath, value); 62 | } else { 63 | throw new RuntimeException("Unknown operator type " + opStr); 64 | } 65 | } 66 | } catch (JSONException e) { 67 | FirestoreLog.e(FirestorePlugin.TAG, "Error processing where", e); 68 | throw new RuntimeException(e); 69 | } 70 | 71 | return query; 72 | } 73 | 74 | private Object parseWhereValue(JSONObject where) throws JSONException { 75 | return JSONHelper.fromJSON(where.get("value")); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/ios/FirestorePlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | @import Firebase; 3 | 4 | #import "FirestoreTransaction.h" 5 | #import "FirestorePluginResultHelper.h" 6 | 7 | @interface FirestorePlugin : CDVPlugin 8 | 9 | - (void)collectionOnSnapshot:(CDVInvokedUrlCommand *)command; 10 | - (void)collectionUnsubscribe:(CDVInvokedUrlCommand *)command; 11 | - (void)collectionAdd:(CDVInvokedUrlCommand *)command; 12 | - (void)collectionGet:(CDVInvokedUrlCommand *)command; 13 | - (void)initialise:(CDVInvokedUrlCommand *)command; 14 | - (void)docSet:(CDVInvokedUrlCommand *)command; 15 | - (void)docUpdate:(CDVInvokedUrlCommand *)command; 16 | - (void)docOnSnapshot:(CDVInvokedUrlCommand *)command; 17 | - (void)docUnsubscribe:(CDVInvokedUrlCommand *)command; 18 | - (void)docGet:(CDVInvokedUrlCommand *)command; 19 | - (void)docDelete:(CDVInvokedUrlCommand *)command; 20 | - (void)transactionDocSet:(CDVInvokedUrlCommand *)command; 21 | - (void)transactionDocUpdate:(CDVInvokedUrlCommand *)command; 22 | - (void)transactionDocGet:(CDVInvokedUrlCommand *)command; 23 | - (void)transactionDocDelete:(CDVInvokedUrlCommand *)command; 24 | - (void)runTransaction:(CDVInvokedUrlCommand *)command; 25 | - (void)transactionResolve:(CDVInvokedUrlCommand *)command; 26 | - (void)setLogLevel:(CDVInvokedUrlCommand *)command; 27 | - (void)batchDocSet:(CDVInvokedUrlCommand *)command; 28 | - (void)batchDocUpdate:(CDVInvokedUrlCommand *)command; 29 | - (void)batchDoc:(CDVInvokedUrlCommand *)command; 30 | - (void)batchDocDelete:(CDVInvokedUrlCommand *)command; 31 | - (void)batchCommit:(CDVInvokedUrlCommand *)command; 32 | - (FIRFirestore *)getFirestore; 33 | 34 | - (FIRQuery *)processQueries:(NSArray *)queries ForQuery:(FIRQuery *)query; 35 | - (FIRQuery *)processQueryLimit:(FIRQuery *)query ForValue:(NSObject *)value; 36 | - (FIRQuery *)processQueryWhere:(FIRQuery *)query ForValue:(NSObject *)value; 37 | - (FIRQuery *)processQueryOrderBy:(FIRQuery *)query ForValue:(NSObject *)value; 38 | - (FIRQuery *)processQueryStartAfter:(FIRQuery *)query ForValue:(NSObject *)value; 39 | - (FIRQuery *)processQueryStartAt:(FIRQuery *)query ForValue:(NSObject *)value; 40 | - (FIRQuery *)processQueryEndAt:(FIRQuery *)query ForValue:(NSObject *)value; 41 | - (FIRQuery *)processQueryEndBefore:(FIRQuery *)query ForValue:(NSObject *)value; 42 | 43 | @property(strong) FIRFirestore *firestore; 44 | @property(strong) NSMutableDictionary *listeners; 45 | @property(strong) NSMutableDictionary *transactions; 46 | @property(strong) NSString *fieldPathDocumentIdPrefix; 47 | 48 | @end 49 | 50 | typedef void (^DocumentSetBlock)(NSError *_Nullable error); 51 | -------------------------------------------------------------------------------- /src/ios/FirestorePluginJSONHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // FirestorePluginJSONHelper.h 3 | // leave your Egos at the door 4 | // 5 | // Created by Richard WIndley on 05/12/2017. 6 | // 7 | 8 | #ifndef FirestorePluginJSONHelper_h 9 | #define FirestorePluginJSONHelper_h 10 | 11 | #import "FirestorePlugin.h" 12 | 13 | @interface FirestorePluginJSONHelper : NSObject 14 | 15 | + (NSDictionary *)toJSON:(NSDictionary *)values; 16 | + (NSDictionary *)fromJSON:(NSDictionary *)values ForPlugin:(FirestorePlugin *)firestorePlugin; 17 | + (void)setReferencePrefix:(NSString *)referencePrefix; 18 | + (void)setGeopointPrefix:(NSString *)geopointPrefix; 19 | + (void)setDatePrefix:(NSString *)datePrefix; 20 | + (void)setTimestampPrefix:(NSString *)timestampPrefix; 21 | + (void)setFieldValueDelete:(NSString *)fieldValueDelete; 22 | + (void)setFieldValueServerTimestamp:(NSString *)fieldValueServerTimestamp; 23 | + (void)setFieldValueIncrement:(NSString *)fieldValueIncrement; 24 | + (void)setFieldValueArrayUnion:(NSString *)fieldValueArrayUnion; 25 | + (void)setFieldValueArrayRemove:(NSString *)fieldValueArrayRemove; 26 | + (NSObject *)parseSpecial:(NSObject *)value; 27 | 28 | @end 29 | 30 | #endif /* FirestorePluginJSONHelper_h */ 31 | -------------------------------------------------------------------------------- /src/ios/FirestorePluginJSONHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // FirestorePluginJSONHelper.m 3 | // leave your Egos at the door 4 | // 5 | // Created by Richard WIndley on 05/12/2017. 6 | // 7 | 8 | #import "FirestorePluginJSONHelper.h" 9 | #import "FirestorePlugin.h" 10 | @import Firebase; 11 | 12 | @implementation FirestorePluginJSONHelper; 13 | 14 | static NSString *datePrefix = @"__DATE:"; 15 | static NSString *timestampPrefix = @"__TIMESTAMP:"; 16 | static NSString *geopointPrefix = @"__GEOPOINT:"; 17 | static NSString *referencePrefix = @"__REFERENCE:"; 18 | static NSString *fieldValueDelete = @"__DELETE"; 19 | static NSString *fieldValueServerTimestamp = @"__SERVERTIMESTAMP"; 20 | static NSString *fieldValueIncrement = @"__INCREMENT"; 21 | static NSString *fieldValueArrayRemove = @"__ARRAYREMOVE"; 22 | static NSString *fieldValueArrayUnion = @"__ARRAYUNION"; 23 | 24 | + (NSDictionary *)toJSON:(NSDictionary *)values { 25 | NSMutableDictionary *result = [[NSMutableDictionary alloc]initWithCapacity:[values count]]; 26 | 27 | for (id key in values) { 28 | id value = [values objectForKey:key]; 29 | 30 | if ([value isKindOfClass:[NSDate class]]) { 31 | 32 | NSDate *date = (NSDate *)value; 33 | 34 | NSMutableString *dateString = [[NSMutableString alloc] init]; 35 | [dateString appendString:datePrefix]; 36 | [dateString appendString:[NSString stringWithFormat:@"%.0f000",[date timeIntervalSince1970]]]; 37 | value = dateString; 38 | } else if ([value isKindOfClass:[FIRTimestamp class]]) { 39 | 40 | FIRTimestamp *timestamp = (FIRTimestamp *)value; 41 | 42 | NSMutableString *timestampString = [[NSMutableString alloc] init]; 43 | [timestampString appendString:timestampPrefix]; 44 | [timestampString appendString:[NSString stringWithFormat:@"%lld_%d",[timestamp seconds], [timestamp nanoseconds]]]; 45 | value = timestampString; 46 | } else if ([value isKindOfClass:[FIRGeoPoint class]]) { 47 | FIRGeoPoint *point = (FIRGeoPoint *)value; 48 | 49 | NSMutableString *geopointString = [[NSMutableString alloc] init]; 50 | [geopointString appendString:geopointPrefix]; 51 | [geopointString appendString:[NSString stringWithFormat:@"%f,%f", point.latitude, point.longitude]]; 52 | value = geopointString; 53 | } else if ([value isKindOfClass:[FIRDocumentReference class]]) { 54 | FIRDocumentReference *reference = (FIRDocumentReference *)value; 55 | 56 | NSMutableString *referenceString = [[NSMutableString alloc] init]; 57 | [referenceString appendString:referencePrefix]; 58 | [referenceString appendString:[NSString stringWithFormat:@"%s,%s", [reference.path UTF8String], [reference.documentID UTF8String]]]; 59 | value = referenceString; 60 | } else if ([value isKindOfClass:[NSDictionary class]]) { 61 | value = [self toJSON:value]; 62 | } 63 | 64 | [result setObject:value forKey:key]; 65 | } 66 | 67 | return result; 68 | } 69 | 70 | + (NSObject *)fromJSON:(NSObject *)value ForPlugin:(FirestorePlugin *)firestorePlugin { 71 | 72 | if ([value isKindOfClass:[NSString class]]) { 73 | NSString *stringValue = (NSString *)value; 74 | 75 | NSUInteger datePrefixLength = (NSUInteger)datePrefix.length; 76 | NSUInteger geopointPrefixLength = (NSUInteger)geopointPrefix.length; 77 | NSUInteger timestampPrefixLength = (NSUInteger)timestampPrefix.length; 78 | NSUInteger referencePrefixLength = (NSUInteger)referencePrefix.length; 79 | 80 | if ([stringValue length] > datePrefixLength && [datePrefix isEqualToString:[stringValue substringToIndex:datePrefixLength]]) { 81 | NSTimeInterval timestamp = [[stringValue substringFromIndex:datePrefixLength] doubleValue]; 82 | timestamp /= 1000; 83 | NSDate *date = [[NSDate alloc] initWithTimeIntervalSince1970:timestamp]; 84 | value = date; 85 | } 86 | 87 | if ([stringValue length] > timestampPrefixLength && [timestampPrefix isEqualToString:[stringValue substringToIndex:timestampPrefixLength]]) { 88 | NSArray *tmp = [[stringValue substringFromIndex:timestampPrefixLength] componentsSeparatedByString:@"_"]; 89 | FIRTimestamp *timestamp = [[FIRTimestamp alloc] initWithSeconds:[[tmp objectAtIndex:0] longLongValue] nanoseconds:[[tmp objectAtIndex:1] intValue]]; 90 | value = timestamp; 91 | } 92 | 93 | if ([stringValue length] > geopointPrefixLength && [geopointPrefix isEqualToString:[stringValue substringToIndex:geopointPrefixLength]]) { 94 | NSArray *tmp = [[stringValue substringFromIndex:geopointPrefixLength] componentsSeparatedByString:@","]; 95 | FIRGeoPoint *geopoint = [[FIRGeoPoint alloc] initWithLatitude:[[tmp objectAtIndex:0] doubleValue] longitude:[[tmp objectAtIndex:1] doubleValue]]; 96 | value = geopoint; 97 | } 98 | 99 | if ([stringValue length] > referencePrefixLength && [referencePrefix isEqualToString:[stringValue substringToIndex:referencePrefixLength]]) { 100 | NSArray *tmp = [[stringValue substringFromIndex:referencePrefixLength] componentsSeparatedByString:@","]; 101 | FIRFirestore *firestore = [firestorePlugin getFirestore]; 102 | FIRDocumentReference *reference = [[firestore collectionWithPath:@""] documentWithPath:[tmp objectAtIndex:0]]; //[[FIRDocumentReference alloc] initWithReference:[[tmp objectAtIndex:0] stringValue]]; 103 | value = reference; 104 | } 105 | 106 | if ([self isWrappedFieldValue:stringValue]) { 107 | value = [self unwrapFieldValue:stringValue]; 108 | } 109 | 110 | } else if ([value isKindOfClass:[NSDictionary class]]) { 111 | value = [self toSettableDictionaryInternal:(NSDictionary *)value ForPlugin:firestorePlugin]; 112 | } else if ([value isKindOfClass:[NSArray class]]) { 113 | value = [self toSettableArrayInternal:(NSArray *)value ForPlugin:firestorePlugin]; 114 | } 115 | 116 | return value; 117 | } 118 | 119 | + (NSObject *)toSettableDictionaryInternal:(NSDictionary *)values ForPlugin:(FirestorePlugin *)firestorePlugin { 120 | NSMutableDictionary *result = [[NSMutableDictionary alloc]initWithCapacity:[values count]]; 121 | 122 | for (id key in values) { 123 | id value = [values objectForKey:key]; 124 | value = [self fromJSON:value ForPlugin:firestorePlugin]; 125 | [result setObject:value forKey:key]; 126 | } 127 | 128 | return result; 129 | } 130 | 131 | + (NSObject *)toSettableArrayInternal:(NSArray *)values ForPlugin:(FirestorePlugin *)firestorePlugin { 132 | NSMutableArray *result = [[NSMutableArray alloc]initWithCapacity:[values count]]; 133 | 134 | for (id key in values) { 135 | [result addObject:key]; 136 | } 137 | 138 | return result; 139 | } 140 | 141 | + (void)setDatePrefix:(NSString *)newDatePrefix { 142 | datePrefix = newDatePrefix; 143 | } 144 | 145 | + (void)setTimestampPrefix:(NSString *)newTimestampPrefix { 146 | timestampPrefix = newTimestampPrefix; 147 | } 148 | 149 | + (void)setReferencePrefix:(NSString *)newReferencePrefix { 150 | referencePrefix = newReferencePrefix; 151 | } 152 | 153 | + (void)setGeopointPrefix:(NSString *)newGeopointPrefix { 154 | geopointPrefix = newGeopointPrefix; 155 | } 156 | 157 | + (void)setFieldValueDelete:(NSString *)newFieldValueDelete { 158 | fieldValueDelete = newFieldValueDelete; 159 | } 160 | 161 | + (void)setFieldValueServerTimestamp:(NSString *)newFieldValueServerTimestamp { 162 | fieldValueServerTimestamp = newFieldValueServerTimestamp; 163 | } 164 | 165 | + (void)setFieldValueIncrement:(NSString *)newFieldValueIncrement { 166 | fieldValueIncrement = newFieldValueIncrement; 167 | } 168 | 169 | + (void)setFieldValueArrayUnion:(NSString *)newFieldValueArrayUnion { 170 | fieldValueArrayUnion = newFieldValueArrayUnion; 171 | } 172 | 173 | + (void)setFieldValueArrayRemove:(NSString *)newFieldValueArrayRemove { 174 | fieldValueArrayRemove = newFieldValueArrayRemove; 175 | } 176 | 177 | +(Boolean)isWrappedFieldValue:(NSString *)value { 178 | if ([value isEqualToString:fieldValueDelete]) { 179 | return true; 180 | } 181 | 182 | if ([value isEqualToString:fieldValueServerTimestamp]) { 183 | return true; 184 | } 185 | 186 | if ([value length] >= fieldValueIncrement.length && [[value substringToIndex:fieldValueIncrement.length] isEqualToString:fieldValueIncrement]) { 187 | return true; 188 | } 189 | 190 | if ([value length] >= fieldValueArrayUnion.length && [[value substringToIndex:fieldValueArrayUnion.length] isEqualToString:fieldValueArrayUnion]) { 191 | return true; 192 | } 193 | 194 | if ([value length] >= fieldValueArrayRemove.length && [[value substringToIndex:fieldValueArrayRemove.length] isEqualToString:fieldValueArrayRemove]) { 195 | return true; 196 | } 197 | 198 | return false; 199 | } 200 | 201 | + (NSObject *)unwrapFieldValue:(NSString *)stringValue { 202 | if ([fieldValueDelete isEqualToString:stringValue]) { 203 | return FIRFieldValue.fieldValueForDelete; 204 | } 205 | 206 | if ([fieldValueServerTimestamp isEqualToString:stringValue]) { 207 | return FIRFieldValue.fieldValueForServerTimestamp; 208 | } 209 | 210 | if ([[stringValue substringToIndex:fieldValueIncrement.length] isEqualToString:fieldValueIncrement]) { 211 | return [FIRFieldValue fieldValueForIntegerIncrement:[[self unwrap:stringValue ForPrefix:fieldValueIncrement] longLongValue]]; 212 | } 213 | 214 | if ([[stringValue substringToIndex:fieldValueArrayUnion.length] isEqualToString:fieldValueArrayUnion]) { 215 | NSString *unwrapped =[self unwrap:stringValue ForPrefix:fieldValueArrayUnion]; 216 | return [FIRFieldValue fieldValueForArrayUnion:[self JSONArrayToArray:unwrapped]]; 217 | } 218 | 219 | if ([[stringValue substringToIndex:fieldValueArrayRemove.length] isEqualToString:fieldValueArrayRemove]) { 220 | NSString *unwrapped =[self unwrap:stringValue ForPrefix:fieldValueArrayRemove]; 221 | return [FIRFieldValue fieldValueForArrayRemove:[self JSONArrayToArray:unwrapped]]; 222 | } 223 | 224 | return stringValue; 225 | } 226 | 227 | + (NSString *)unwrap:(NSString *)stringValue ForPrefix:(NSString *)prefix { 228 | return [stringValue substringFromIndex:prefix.length]; 229 | } 230 | 231 | + (NSArray *)JSONArrayToArray:(NSString *)array { 232 | 233 | } 234 | @end 235 | -------------------------------------------------------------------------------- /src/ios/FirestorePluginResultHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // FirestorePluginResultHelper.h 3 | // leave your Egos at the door 4 | // 5 | // Created by Richard WIndley on 05/12/2017. 6 | // 7 | 8 | #ifndef FirestorePluginResultHelper_h 9 | #define FirestorePluginResultHelper_h 10 | 11 | #import 12 | 13 | @import Firebase; 14 | 15 | @interface FirestorePluginResultHelper : NSObject 16 | 17 | + (CDVPluginResult *)createDocumentPluginResult:(FIRDocumentSnapshot *)doc :(BOOL )reusable; 18 | + (CDVPluginResult *)createQueryPluginResult:(FIRQuerySnapshot *)doc :(BOOL )reusable; 19 | + (CDVPluginResult *)createDocumentReferencePluginResult:(FIRDocumentReference *)doc :(BOOL )reusable; 20 | + (CDVPluginResult *)createPluginErrorResult:(NSError *)error :(BOOL )reusable; 21 | 22 | + (NSDictionary *)createDocumentSnapshot:(FIRDocumentSnapshot *)doc; 23 | + (NSDictionary *)createError:(NSInteger)code :(NSString *)message; 24 | 25 | @end 26 | 27 | #endif /* FirestorePluginResultHelper_h */ 28 | -------------------------------------------------------------------------------- /src/ios/FirestorePluginResultHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // FirestorePluginResultHelper.m 3 | // leave your Egos at the door 4 | // 5 | // Created by Richard WIndley on 05/12/2017. 6 | // 7 | 8 | #import "FirestorePluginResultHelper.h" 9 | #import "FirestorePluginJSONHelper.h" 10 | #import 11 | 12 | @implementation FirestorePluginResultHelper; 13 | 14 | NSDictionary *mappedErrors; 15 | 16 | +(void)load { 17 | mappedErrors = @{ @(FIRFirestoreErrorCodeAborted) : @"aborted",\ 18 | @(FIRFirestoreErrorCodeAlreadyExists): @"already-exists",\ 19 | @(FIRFirestoreErrorCodeCancelled):@"cancelled",\ 20 | @(FIRFirestoreErrorCodeDataLoss): @"data-loss",\ 21 | @(FIRFirestoreErrorCodeDeadlineExceeded): @"deadline-exceeded",\ 22 | @(FIRFirestoreErrorCodeFailedPrecondition):@"failed-precondition",\ 23 | @(FIRFirestoreErrorCodeInternal):@"internal", \ 24 | @(FIRFirestoreErrorCodeInvalidArgument):@"invalid-argument",\ 25 | @(FIRFirestoreErrorCodeNotFound):@"not-found",\ 26 | @(FIRFirestoreErrorCodeOK):@"ok",\ 27 | @(FIRFirestoreErrorCodeOutOfRange):@"out-of-range",\ 28 | @(FIRFirestoreErrorCodePermissionDenied):@"permission-denied",\ 29 | @(FIRFirestoreErrorCodeResourceExhausted):@"resource-exhausted",\ 30 | @(FIRFirestoreErrorCodeUnauthenticated):@"unauthenticated",\ 31 | @(FIRFirestoreErrorCodeUnavailable):@"unavailable",\ 32 | @(FIRFirestoreErrorCodeUnimplemented):@"unimplemented",\ 33 | @(FIRFirestoreErrorCodeUnknown):@"unknown"}; 34 | } 35 | + (CDVPluginResult *)createPluginErrorResult:(NSError *)error :(BOOL )reusable { 36 | CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createError:error.code :error.localizedDescription]]; 37 | [pluginResult setKeepCallbackAsBool:reusable]; 38 | 39 | return pluginResult; 40 | } 41 | 42 | + (CDVPluginResult *)createDocumentPluginResult:(FIRDocumentSnapshot *)doc :(BOOL )reusable { 43 | NSDictionary *result = [FirestorePluginResultHelper createDocumentSnapshot:doc]; 44 | CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result]; 45 | [pluginResult setKeepCallbackAsBool:reusable]; 46 | 47 | return pluginResult; 48 | } 49 | 50 | + (CDVPluginResult *)createDocumentReferencePluginResult:(FIRDocumentReference *)doc :(BOOL )reusable { 51 | NSDictionary *result = [FirestorePluginResultHelper createDocumentReference:doc]; 52 | CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result]; 53 | [pluginResult setKeepCallbackAsBool:reusable]; 54 | 55 | return pluginResult; 56 | } 57 | 58 | + (CDVPluginResult *)createQueryPluginResult:(FIRQuerySnapshot *)query :(BOOL )reusable { 59 | NSDictionary *querySnapshot = @{}; 60 | NSMutableArray *result = [[NSMutableArray alloc] init]; 61 | 62 | os_log_debug(OS_LOG_DEFAULT, "Creating query snapshot result"); 63 | 64 | if (query.documents != nil) { 65 | for (FIRQueryDocumentSnapshot *doc in query.documents) { 66 | NSDictionary *document = [FirestorePluginResultHelper createDocumentSnapshot:doc]; 67 | [result addObject:document]; 68 | } 69 | } 70 | 71 | querySnapshot = @{ @"docs" : result}; 72 | 73 | CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:querySnapshot]; 74 | [pluginResult setKeepCallbackAsBool:reusable]; 75 | 76 | return pluginResult; 77 | } 78 | 79 | + (NSDictionary *)createError:(NSInteger )code :(NSString *)message { 80 | 81 | NSDictionary *error; 82 | 83 | os_log_debug(OS_LOG_DEFAULT, "Creating error result"); 84 | 85 | error = @{ @"code" : [mappedErrors objectForKey:@(code)], 86 | @"message" : message 87 | }; 88 | 89 | return error; 90 | } 91 | 92 | + (NSDictionary *)createDocumentSnapshot:(FIRDocumentSnapshot *)doc { 93 | 94 | NSDictionary *documentSnapshot; 95 | 96 | os_log_debug(OS_LOG_DEFAULT, "Creating document snapshot result"); 97 | 98 | if (doc.exists) { 99 | documentSnapshot = @{ @"id" : doc.documentID, 100 | @"exists" : @YES, 101 | @"ref" : doc.reference.documentID, 102 | @"_data" : [FirestorePluginJSONHelper toJSON:doc.data] 103 | }; 104 | } else { 105 | documentSnapshot = @{ @"id" : doc.documentID, 106 | @"exists" : @NO, 107 | @"ref" : doc.reference.documentID 108 | }; 109 | } 110 | 111 | return documentSnapshot; 112 | } 113 | 114 | + (NSDictionary *)createDocumentReference:(FIRDocumentReference *)doc { 115 | 116 | NSDictionary *documentReference; 117 | 118 | os_log_debug(OS_LOG_DEFAULT, "Creating document reference result"); 119 | 120 | documentReference = @{ @"id" : doc.documentID}; 121 | 122 | return documentReference; 123 | } 124 | 125 | @end 126 | 127 | -------------------------------------------------------------------------------- /src/ios/FirestoreTransaction.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | typedef NS_ENUM(NSInteger, FirestoreTransactionOperationType) { 4 | NONE, 5 | SET, 6 | UPDATE, 7 | DELETE, 8 | GET, 9 | RESOLVE 10 | }; 11 | 12 | @interface FirestoreTransaction : NSObject 13 | 14 | @property(strong) NSString *collectionPath; 15 | @property(strong) NSString *docId; 16 | @property(strong) NSDictionary *data; 17 | @property(strong) NSDictionary *options; 18 | @property(assign) FirestoreTransactionOperationType transactionOperationType; 19 | 20 | @end 21 | 22 | @interface FirestoreTransactionQueue : NSObject 23 | 24 | @property(strong) NSString *result; 25 | @property(strong) NSMutableArray *queue; 26 | @property(strong) CDVPluginResult *pluginResult; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /src/ios/FirestoreTransaction.m: -------------------------------------------------------------------------------- 1 | #import "FirestoreTransaction.h" 2 | 3 | @implementation FirestoreTransaction 4 | @end 5 | 6 | @implementation FirestoreTransactionQueue 7 | @end 8 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Firestore { 2 | 3 | export interface FirestoreOptions { 4 | datePrefix?: string; 5 | fieldValueDelete?: string; 6 | geopointPrefix?: string; 7 | referencePrefix?:string; 8 | fieldValueServerTimestamp?: string; 9 | persist?: boolean; 10 | timestampsInSnapshots?: boolean; 11 | config?: any; 12 | } 13 | 14 | export interface FieldValue { 15 | delete(): string; 16 | serverTimestamp(): string; 17 | increment(n:number): string; 18 | arrayUnion(...elements:any[]):string; 19 | arrayRemove(...elements:any[]):string; 20 | } 21 | 22 | export interface FieldPath { 23 | documentId(): string; 24 | } 25 | 26 | export interface DocumentData { 27 | [field: string]: any; 28 | } 29 | 30 | export interface UpdateData extends DocumentData { 31 | } 32 | 33 | export interface DocumentSnapshotCallback { 34 | (documentSnapshot: DocumentSnapshot): void; 35 | } 36 | 37 | export interface FirestoreError extends Error { 38 | code: string; 39 | } 40 | 41 | export interface DocumentErrorCallback { 42 | (error: FirestoreError): void; 43 | } 44 | 45 | export interface SnapshotListenOptions { 46 | includeMetadataChanges: boolean; 47 | } 48 | 49 | export interface SetOptions { 50 | merge: boolean; 51 | } 52 | 53 | export interface Unsubscribe { 54 | (): void; 55 | } 56 | 57 | export interface DocumentReference { 58 | collection(collectionPath: string): CollectionReference; 59 | delete(): Promise; 60 | get(): Promise; 61 | onSnapshot(optionsOrObserverOrOnNext: object | SnapshotListenOptions | DocumentSnapshotCallback, 62 | observerOrOnNextOrOnError?: object | DocumentSnapshotCallback | DocumentErrorCallback, 63 | onError?: DocumentErrorCallback): Unsubscribe; 64 | set(data: DocumentData, options?: SetOptions): Promise; 65 | update(data: UpdateData): Promise; 66 | 67 | firestore: Firestore; 68 | id: string; 69 | parent: CollectionReference; 70 | path: string; 71 | } 72 | 73 | export interface DocumentSnapshot { 74 | data(): DocumentData | undefined; 75 | get(fieldPath: string): any; 76 | 77 | exists: boolean; 78 | id: string; 79 | metadata: any; 80 | ref: DocumentReference; 81 | } 82 | 83 | export interface QueryDocumentSnapshot extends DocumentSnapshot { 84 | } 85 | 86 | export interface QuerySnapshotCallback { 87 | (documentSnapshot: QueryDocumentSnapshot): void; 88 | } 89 | 90 | export interface FirestoreTimestamp { 91 | new(seconds: number, nanoseconds: number): FirestoreTimestamp; 92 | toDate(): Date; 93 | } 94 | 95 | export interface GeoPoint { 96 | new(latitude: number, longitude: number): GeoPoint; 97 | latitude: number; 98 | longitude: number; 99 | } 100 | 101 | export interface Path { 102 | new(path: string): GeoPoint; 103 | } 104 | 105 | export interface QuerySnapshot { 106 | forEach(callback: QuerySnapshotCallback): void; 107 | docs: QueryDocumentSnapshot[]; 108 | empty: boolean; 109 | metadata: any; 110 | query: Query; 111 | size: number; 112 | } 113 | 114 | export interface QueryCallback { 115 | (snapshot: QuerySnapshot): void; 116 | } 117 | 118 | export interface QueryErrorCallback { 119 | (error: FirestoreError): void; 120 | } 121 | 122 | export interface Query { 123 | endAt(snapshotOrFieldValues: DocumentSnapshot | any): Query; 124 | endBefore(snapshotOrFieldValues: DocumentSnapshot | any): Query; 125 | limit(limit: number): Query; 126 | orderBy(fieldPath: string, direction: string): Query; 127 | 128 | get(): Promise; 129 | onSnapshot(optionsOrObserverOrOnNext: object | SnapshotListenOptions | QuerySnapshotCallback, 130 | observerOrOnNextOrOnError?: object | QuerySnapshotCallback | QueryErrorCallback, 131 | onError?: QueryErrorCallback): Unsubscribe; 132 | startAfter(snapshotOrFieldValues: DocumentSnapshot | any): Query; 133 | startAt(snapshotOrFieldValues: DocumentSnapshot | any): Query; 134 | where(fieldPath: string, opStr: string, value: any): Query; 135 | } 136 | 137 | export interface CollectionReference extends Query { 138 | firestore: Firestore; 139 | id: string; 140 | parent: Document; 141 | path: string; 142 | 143 | add(data: any): Promise; 144 | doc(id?: string): DocumentReference; 145 | } 146 | 147 | export interface Transaction { 148 | delete(documentRef: DocumentReference): Transaction; 149 | get(documentRef: DocumentReference): Promise; 150 | set(documentRef: DocumentReference, data: DocumentData, options?: SetOptions): Transaction; 151 | update(documentRef: DocumentReference, data: UpdateData): Transaction; 152 | } 153 | 154 | export interface RunTransactionUpdateFunction { 155 | (transaction: Transaction): Promise; 156 | } 157 | 158 | export interface WriteBatch { 159 | delete(documentRef: DocumentReference): WriteBatch; 160 | set(documentRef: DocumentReference, data: DocumentData, options?: SetOptions): WriteBatch; 161 | update(documentRef: DocumentReference, data: UpdateData): WriteBatch; 162 | commit():Promise 163 | } 164 | 165 | export abstract class Firestore { 166 | get(): Firestore; 167 | batch(): WriteBatch; 168 | collection(collectionPath: string): CollectionReference; 169 | disableNetwork(): Promise; 170 | doc(documentPath: string): DocumentReference; 171 | enableNetwork(): void; 172 | enablePersistence(): void; 173 | runTransaction(callback: RunTransactionUpdateFunction): Promise; 174 | setLogLevel(logLevel: string): void; 175 | settings(settings: any): void; 176 | 177 | static FieldValue: FieldValue; 178 | static FieldPath: FieldPath; 179 | static GeoPoint: GeoPoint; 180 | } 181 | 182 | export function initialise(options: FirestoreOptions): Promise; 183 | export function totalReads(): number; 184 | } -------------------------------------------------------------------------------- /www/__mocks__/cordova/exec.js: -------------------------------------------------------------------------------- 1 | module.exports = jest.fn(); 2 | -------------------------------------------------------------------------------- /www/__mocks__/cordova/utils.js: -------------------------------------------------------------------------------- 1 | module.exports = jest.fn(); 2 | -------------------------------------------------------------------------------- /www/android_ios/batch.js: -------------------------------------------------------------------------------- 1 | /* global Promise: false */ 2 | 3 | var PLUGIN_NAME = 'Firestore'; 4 | var exec = require('cordova/exec'); 5 | var DocumentSnapshot = require("./document_snapshot"); 6 | var Utilities = require('./utilities'); 7 | 8 | function WriteBatch(id) { 9 | this._id = id; 10 | } 11 | 12 | WriteBatch.prototype = { 13 | delete: function (documentReference) { 14 | var args = [this._id, documentReference.id, documentReference._collectionReference.path]; 15 | 16 | var success = function () { 17 | }; 18 | 19 | var failure = function () { 20 | throw new Error("Undefined error in batchDocDelete"); 21 | }; 22 | 23 | exec(success, failure, PLUGIN_NAME, 'batchDocDelete', args); 24 | 25 | return this; 26 | }, 27 | set: function (documentReference, data, options) { 28 | 29 | var args = [this._id, documentReference.id, documentReference._collectionReference.path, Utilities.wrap(data), options]; 30 | 31 | var success = function () { 32 | }; 33 | 34 | var failure = function () { 35 | throw new Error("Undefined error in batchDocSet"); 36 | }; 37 | 38 | exec(success, failure, PLUGIN_NAME, 'batchDocSet', args); 39 | 40 | return this; 41 | }, 42 | update: function (documentReference, data) { 43 | 44 | var args = [this._id, documentReference.id, documentReference._collectionReference.path, Utilities.wrap(data)]; 45 | 46 | var success = function () { 47 | }; 48 | 49 | var failure = function () { 50 | throw new Error("Undefined error in batchDocUpdate"); 51 | }; 52 | 53 | exec(success, failure, PLUGIN_NAME, 'batchDocUpdate', args); 54 | 55 | return this; 56 | }, 57 | commit: function() { 58 | var args = [this._id]; 59 | 60 | return new Promise(function (resolve, reject) { 61 | exec(resolve, reject, PLUGIN_NAME, 'batchCommit', args); 62 | }).then(function () { 63 | return; 64 | }); 65 | } 66 | }; 67 | 68 | module.exports = Transaction; 69 | -------------------------------------------------------------------------------- /www/android_ios/collection_reference.js: -------------------------------------------------------------------------------- 1 | /* global Promise: false */ 2 | 3 | var PLUGIN_NAME = 'Firestore'; 4 | var exec = require('cordova/exec'); 5 | var DocumentReference; 6 | var Utilities = require("./utilities"); 7 | var Path = require('./path'); 8 | var Query = require("./query"); 9 | 10 | function CollectionReference(path, parent) { 11 | 12 | /* 13 | * weird, in tests if DocumentReference is imported at the top of the file 14 | * it is an empty object and not a function 15 | */ 16 | if (DocumentReference === undefined) { 17 | DocumentReference = require('./document_reference'); 18 | } 19 | this._path = new Path(path); 20 | this._id = this._path.id; 21 | this._ref = this; 22 | this._queries = []; 23 | this._parent = parent || null; 24 | } 25 | 26 | CollectionReference.prototype = Object.create(Query.prototype, { 27 | firestore: { 28 | get: function () { 29 | throw "CollectionReference.firestore: Not supported"; 30 | } 31 | }, 32 | id: { 33 | get: function () { 34 | return this._id; 35 | } 36 | }, 37 | parent: { 38 | get: function () { 39 | return this._parent; 40 | } 41 | }, 42 | path: { 43 | get: function() { 44 | return this._path.cleanPath; 45 | } 46 | } 47 | }); 48 | 49 | CollectionReference.prototype.add = function (data) { 50 | var args = [this._path.cleanPath, Utilities.wrap(data)]; 51 | 52 | return new Promise(function (resolve, reject) { 53 | exec(resolve, reject, PLUGIN_NAME, 'collectionAdd', args); 54 | }); 55 | }; 56 | 57 | CollectionReference.prototype.doc = function (id) { 58 | return new DocumentReference(this, id); 59 | }; 60 | 61 | CollectionReference.prototype.newInstance = function(documentReference, path) { 62 | return new CollectionReference(path, documentReference); 63 | }; 64 | 65 | module.exports = CollectionReference; 66 | -------------------------------------------------------------------------------- /www/android_ios/collection_reference.test.js: -------------------------------------------------------------------------------- 1 | /* global Promise: false */ 2 | 3 | var PLUGIN_NAME = 'Firestore'; 4 | var exec = require('cordova/exec'); 5 | var Query = require("./query"); 6 | var DocumentReference = require("./document_reference"); 7 | var Utilities = require("./utilities"); 8 | const CollectionReference = require('./collection_reference'); 9 | 10 | const mockParent = {}; 11 | 12 | describe('CollectionReference', () => { 13 | describe('parent', () => { 14 | it('should be null when no parent specified', () => { 15 | const collectionRef = new CollectionReference('path'); 16 | expect(collectionRef.parent).toBe(null); 17 | expect(collectionRef.path).toBe('path'); 18 | expect(collectionRef.id).toBe('path'); 19 | }); 20 | 21 | it('should return parent when passed to constructor', () => { 22 | const collectionRef = new CollectionReference('path', mockParent); 23 | expect(collectionRef.parent).toBe(mockParent); 24 | }); 25 | }); 26 | 27 | it('should dispatch path, id and parent when used as subcollection', () => { 28 | const subCollRef = new CollectionReference('/root/parent/sub/', mockParent); 29 | expect(subCollRef.id).toBe('sub'); 30 | expect(subCollRef.path).toBe('root/parent/sub'); 31 | expect(subCollRef.parent).toBe(mockParent); 32 | }); 33 | 34 | describe('.doc allows for infinite recursion of collRef -> docRef -> collRef...', () => { 35 | const rootCollection = new CollectionReference('root'); 36 | const parentDocument = rootCollection.doc('parent'); 37 | const subCollection = parentDocument.collection('sub'); 38 | const subDocument = subCollection.doc('child'); 39 | const subSubCollection = subDocument.collection('subSub'); 40 | 41 | it('should set paths and ids', () => { 42 | expect(rootCollection.path).toBe('root'); 43 | expect(rootCollection.id).toBe('root'); 44 | expect(parentDocument.path).toBe('root/parent'); 45 | expect(parentDocument.id).toBe('parent'); 46 | expect(subCollection.path).toBe('root/parent/sub'); 47 | expect(subCollection.id).toBe('sub'); 48 | expect(subDocument.path).toBe('root/parent/sub/child'); 49 | expect(subDocument.id).toBe('child'); 50 | expect(subSubCollection.path).toBe('root/parent/sub/child/subSub'); 51 | expect(subSubCollection.id).toBe('subSub'); 52 | }); 53 | 54 | it('should set parents all the way', () => { 55 | expect(rootCollection.parent).toBe(null); 56 | expect(parentDocument.parent).toBe(rootCollection); 57 | expect(subCollection.parent).toBe(parentDocument); 58 | expect(subDocument.parent).toBe(subCollection); 59 | expect(subSubCollection.parent).toBe(subDocument); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /www/android_ios/document_reference.js: -------------------------------------------------------------------------------- 1 | var PLUGIN_NAME = 'Firestore'; 2 | var exec = require('cordova/exec'); 3 | var CollectionReference = require('./collection_reference'); 4 | var Utilities = require('./utilities'); 5 | var cordovaUtils = require("cordova/utils"); 6 | var DocumentSnapshot = require("./document_snapshot"); 7 | var utils = require('./utils'); 8 | 9 | function DocumentReference(collectionReference, id) { 10 | this._id = utils.getOrGenerateId(id); 11 | this._collectionReference = collectionReference; 12 | } 13 | 14 | DocumentReference.prototype = { 15 | _isFunction: function (functionToCheck) { 16 | var getType = {}; 17 | return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; 18 | }, 19 | collection: function (collectionPath) { 20 | return new CollectionReference([collectionPath].join('/'), this); 21 | }, 22 | delete: function () { 23 | var args = [this._collectionReference.path, this._id]; 24 | 25 | return new Promise(function (resolve, reject) { 26 | exec(resolve, reject, PLUGIN_NAME, 'docDelete', args); 27 | }).then(function () { 28 | return; 29 | }); 30 | }, 31 | get: function () { 32 | var args = [this._collectionReference.path, this._id]; 33 | 34 | return new Promise(function (resolve, reject) { 35 | exec(resolve, reject, PLUGIN_NAME, 'docGet', args); 36 | }).then(function (data) { 37 | return new DocumentSnapshot(data); 38 | }); 39 | }, 40 | onSnapshot: function (optionsOrObserverOrOnNext, observerOrOnNextOrOnError, onError) { 41 | 42 | var callbackId = cordovaUtils.createUUID(); 43 | 44 | var args = [this._collectionReference.path, this._id, callbackId]; 45 | 46 | if (!this._isFunction(optionsOrObserverOrOnNext)) { 47 | args.push(optionsOrObserverOrOnNext); 48 | } 49 | var wrappedCallback; 50 | var wrappedError; 51 | 52 | if (this._isFunction(optionsOrObserverOrOnNext)) { 53 | wrappedCallback = function (documentSnapshot) { 54 | optionsOrObserverOrOnNext(new DocumentSnapshot(documentSnapshot)); 55 | }; 56 | 57 | if (this._isFunction(observerOrOnNextOrOnError)) { 58 | wrappedError = observerOrOnNextOrOnError; 59 | } 60 | } else if (this._isFunction(observerOrOnNextOrOnError)) { 61 | wrappedCallback = function (documentSnapshot) { 62 | observerOrOnNextOrOnError(new DocumentSnapshot(documentSnapshot)); 63 | }; 64 | 65 | if (this._isFunction(onError)) { 66 | wrappedError = onError; 67 | } 68 | } else { 69 | wrappedCallback = function (documentSnapshot) { }; 70 | } 71 | 72 | if (!wrappedError) { 73 | wrappedError = function () { 74 | throw new Error("Undefined error in docOnSnapshot"); 75 | }; 76 | } 77 | 78 | exec(wrappedCallback, wrappedError, PLUGIN_NAME, 'docOnSnapshot', args); 79 | 80 | return function () { 81 | exec(function () { }, function () { 82 | throw new Error("Undefined error in docUnsubscribe"); 83 | }, PLUGIN_NAME, 'docUnsubscribe', [callbackId]); 84 | }; 85 | }, 86 | set: function (data, options) { 87 | 88 | var args = [this._collectionReference.path, this._id, Utilities.wrap(data), options]; 89 | 90 | return new Promise(function (resolve, reject) { 91 | exec(resolve, reject, PLUGIN_NAME, 'docSet', args); 92 | }); 93 | }, 94 | update: function (data) { 95 | 96 | var args = [this._collectionReference.path, this._id, Utilities.wrap(data)]; 97 | 98 | return new Promise(function (resolve, reject) { 99 | exec(resolve, reject, PLUGIN_NAME, 'docUpdate', args); 100 | }); 101 | } 102 | }; 103 | 104 | Object.defineProperties(DocumentReference.prototype, { 105 | firestore: { 106 | get: function () { 107 | throw "DocumentReference.firestore: Not supported"; 108 | } 109 | }, 110 | id: { 111 | get: function () { 112 | return this._id; 113 | } 114 | }, 115 | parent: { 116 | get: function () { 117 | return this._collectionReference; 118 | } 119 | }, 120 | path: { 121 | get: function () { 122 | return this._collectionReference.path + '/' + this.id; 123 | } 124 | } 125 | }); 126 | 127 | module.exports = DocumentReference; 128 | -------------------------------------------------------------------------------- /www/android_ios/document_reference.test.js: -------------------------------------------------------------------------------- 1 | const uuid = require('uuid'); 2 | const DocumentReference = require('./document_reference'); 3 | const CollectionReference = require('./collection_reference'); 4 | 5 | const rootCollection = {path: 'root'}; 6 | const subCollection = 'sub'; 7 | const parentId = 'parent'; 8 | const childId = 'child'; 9 | 10 | describe('DocumentReference', () => { 11 | const parentDocRef = new DocumentReference(rootCollection, parentId); 12 | it('should implement path', () => { 13 | expect(parentDocRef.path).toBe('root/parent'); 14 | }); 15 | 16 | it('should autogenerate an id when none is specified', () => { 17 | const docRefWithAutoId = new DocumentReference(rootCollection); 18 | expect(docRefWithAutoId.id).toBeDefined(); 19 | expect(docRefWithAutoId.id).toMatch(/[a-zA-Z0-9]{20}/); 20 | }); 21 | 22 | describe('collection', () => { 23 | it('should return a CollectionReference with proper path', () => { 24 | 25 | const subCollectionRef = (parentDocRef).collection(subCollection); 26 | expect(subCollectionRef).toBeInstanceOf(CollectionReference); 27 | expect(subCollectionRef.path).toBe('root/parent/sub'); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /www/android_ios/document_snapshot.js: -------------------------------------------------------------------------------- 1 | /* global Promise: false, Firestore: false, FirestoreTimestamp: false */ 2 | 3 | var FirestoreTimestamp = require("./firestore_timestamp"); 4 | var GeoPoint = require("./geo_point"); 5 | var DocumentReference; 6 | var CollectionReference; 7 | var Path = require("./path"); 8 | 9 | function DocumentSnapshot(data) { 10 | 11 | this._data = data; 12 | 13 | if (data.exists) { 14 | DocumentSnapshot._reads++; 15 | this._data._data = this._parse(this._data._data); 16 | } 17 | } 18 | 19 | DocumentSnapshot._reads = 0; 20 | 21 | DocumentSnapshot.prototype = { 22 | _newDocumentReference: function(documentPath) { 23 | if (DocumentReference === undefined) { 24 | DocumentReference = require('./document_reference'); 25 | } 26 | 27 | if (CollectionReference === undefined) { 28 | CollectionReference = require('./collection_reference'); 29 | } 30 | 31 | var path = new Path(documentPath); 32 | return new DocumentReference(new CollectionReference(path.parent), path.id); 33 | }, 34 | _parse: function (data) { 35 | var keys = Object.keys(data); 36 | 37 | for (var i = 0; i < keys.length; i++) { 38 | var key = keys[i]; 39 | 40 | if (Object.prototype.toString.call(data[key]) === '[object String]') { 41 | 42 | if (data[key].startsWith(Firestore.options().datePrefix)) { 43 | var length = data[key].length; 44 | var prefixLength = Firestore.options().datePrefix.length; 45 | 46 | var timestamp = data[key].substr(prefixLength, length - prefixLength); 47 | data[key] = new Date(parseInt(timestamp)); 48 | 49 | } else if (data[key].startsWith(Firestore.options().timestampPrefix)) { 50 | var length = data[key].length; 51 | var prefixLength = Firestore.options().timestampPrefix.length; 52 | 53 | var timestamp = data[key].substr(prefixLength, length - prefixLength); 54 | 55 | if (timestamp.includes("_")) { 56 | var timestampParts = timestamp.split("_"); 57 | data[key] = new FirestoreTimestamp(parseInt(timestampParts[0]), parseInt(timestampParts[1])); 58 | 59 | } else { 60 | data[key] = new FirestoreTimestamp(parseInt(timestamp), 0); 61 | } 62 | } else if (data[key].startsWith(Firestore.options().geopointPrefix)) { 63 | var length = data[key].length; 64 | var prefixLength = Firestore.options().geopointPrefix.length; 65 | 66 | var geopoint = data[key].substr(prefixLength, length - prefixLength); 67 | 68 | if (geopoint.includes(",")) { 69 | var geopointParts = geopoint.split(","); 70 | data[key] = new GeoPoint(parseFloat(geopointParts[0]), parseFloat(geopointParts[1])); 71 | } 72 | } else if (data[key].startsWith(Firestore.options().referencePrefix)) { 73 | var length = data[key].length; 74 | var prefixLength = Firestore.options().referencePrefix.length; 75 | 76 | var reference = data[key].substr(prefixLength, length - prefixLength); 77 | 78 | console.log("create doc ref"); 79 | data[key] = this._newDocumentReference(reference); 80 | 81 | } else if (Object.prototype.toString.call(data[key]) === '[object Object]') { 82 | data[key] = this._parse(data[key]); 83 | } 84 | } else if (Array.isArray(data[key])) { 85 | 86 | data[key] = this._parse(data[key]); 87 | } 88 | } 89 | 90 | return data; 91 | }, 92 | _fieldPath: function (obj, i) { 93 | return obj[i]; 94 | }, 95 | data: function () { 96 | return this._data._data; 97 | }, 98 | get: function (fieldPath) { 99 | return fieldPath.split('.').reduce(this._fieldPath, this._data); 100 | } 101 | }; 102 | 103 | Object.defineProperties(DocumentSnapshot.prototype, { 104 | exists: { 105 | get: function () { 106 | return this._data.exists; 107 | } 108 | }, 109 | id: { 110 | get: function () { 111 | return this._data.id; 112 | } 113 | }, 114 | metadata: { 115 | get: function () { 116 | throw "DocumentReference.metadata: Not supported"; 117 | } 118 | }, 119 | ref: { 120 | get: function () { 121 | return this._data.ref; 122 | } 123 | } 124 | }); 125 | 126 | module.exports = DocumentSnapshot; 127 | -------------------------------------------------------------------------------- /www/android_ios/firestore.js: -------------------------------------------------------------------------------- 1 | /* global Promise: false */ 2 | 3 | var exec = require('cordova/exec'); 4 | var utils = require("cordova/utils"); 5 | var Path = require("./path"); 6 | var DocumentReference = require("./document_reference"); 7 | var CollectionReference = require("./collection_reference"); 8 | var FirestoreTimestamp = require("./firestore_timestamp"); 9 | var Transaction = require("./transaction"); 10 | var GeoPoint = require("./geo_point"); 11 | var Utilities = require("./utilities"); 12 | var DocumentSnapshot = require("./document_snapshot"); 13 | 14 | var PLUGIN_NAME = 'Firestore'; 15 | 16 | var __transactionList = {}; 17 | 18 | var __firestoreOptions = {}; 19 | 20 | if (!String.prototype.startsWith) { 21 | String.prototype.startsWith = function (search, pos) { 22 | return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; 23 | }; 24 | } 25 | 26 | var FieldValue = { 27 | delete: function () { 28 | return __firestoreOptions.fieldValueDelete; 29 | }, 30 | serverTimestamp: function () { 31 | return __firestoreOptions.fieldValueServerTimestamp; 32 | }, 33 | increment: function(n) { 34 | return __firestoreOptions.fieldValueIncrement + ":" + n; 35 | }, 36 | arrayUnion: function(elements) { 37 | return __firestoreOptions.fieldValueArrayUnion + ":" + JSON.stringify(Object.values(arguments)); 38 | }, 39 | arrayRemove: function(elements) { 40 | return __firestoreOptions.fieldValueArrayRemove + ":" + JSON.stringify(Object.values(arguments)); 41 | } 42 | }; 43 | 44 | var FieldPath = { 45 | id: function () { 46 | return __firestoreOptions.fieldPathId; 47 | } 48 | }; 49 | 50 | function Firestore(options) { 51 | 52 | if (options.datePrefix === undefined) { 53 | options.datePrefix = "__DATE:"; 54 | } 55 | if (options.timestampPrefix === undefined) { 56 | options.timestampPrefix = "__TIMESTAMP:"; 57 | } 58 | if (options.fieldValueDelete === undefined) { 59 | options.fieldValueDelete = "__DELETE"; 60 | } 61 | if (options.fieldValueIncrement === undefined) { 62 | options.fieldValueIncrement = "__INCREMENT"; 63 | } 64 | if (options.fieldValueArrayUnion === undefined) { 65 | options.fieldValueArrayUnion = "__ARRAYUNION"; 66 | } 67 | if (options.fieldValueArrayRemove === undefined) { 68 | options.fieldValueArrayRemove = "__ARRAYREMOVE"; 69 | } 70 | if (options.fieldPathId === undefined) { 71 | options.fieldPathId = "__ID"; 72 | } 73 | if (options.geopointPrefix === undefined) { 74 | options.geopointPrefix = "__GEOPOINT:"; 75 | } 76 | if (options.referencePrefix === undefined) { 77 | options.referencePrefix = "__REFERENCE:"; 78 | } 79 | if (options.fieldValueServerTimestamp === undefined) { 80 | options.fieldValueServerTimestamp = "__SERVERTIMESTAMP"; 81 | } 82 | if (options.persist === undefined) { 83 | options.persist = true; 84 | } 85 | if (options.timestampsInSnapshots === undefined) { 86 | options.timestampsInSnapshots = false; 87 | } 88 | 89 | __firestoreOptions = options; 90 | 91 | exec(function () { }, null, PLUGIN_NAME, 'initialise', [__firestoreOptions]); 92 | } 93 | 94 | Firestore.prototype = { 95 | get: function () { 96 | return this; 97 | }, 98 | batch: function () { 99 | var batchId = utils.createUUID(); 100 | var batch = new WriteBatch(batchId); 101 | 102 | var success = function () { 103 | }; 104 | 105 | var failure = function () { 106 | throw new Error("Undefined error in batch"); 107 | }; 108 | 109 | exec(success, failure, PLUGIN_NAME, 'batch', args); 110 | 111 | return batch; 112 | }, 113 | collection: function (path) { 114 | return new CollectionReference(path); 115 | }, 116 | disableNetwork: function () { 117 | throw "Firestore.disableNetwork: Not supported"; 118 | }, 119 | doc: function (path) { 120 | // TODO firestore.doc not implemented properly, should allow getting 121 | // .parent all the way down when nested path is passed 122 | 123 | var path = new Path(path); 124 | var collectionReference = new CollectionReference(path.parent); 125 | return collectionReference.doc(path.id); 126 | }, 127 | enableNetwork: function () { 128 | throw "Firestore.enableNetwork: Not supported"; 129 | }, 130 | enablePersistence: function () { 131 | throw "Firestore.enablePersistence: Not supported. Please specify using initialisation options."; 132 | }, 133 | runTransaction: function (updateFunction) { 134 | 135 | var transactionId = utils.createUUID(); 136 | var transaction = new Transaction(transactionId); 137 | 138 | __transactionList[transactionId] = { 139 | "transaction": transaction, 140 | "updateFunction": updateFunction 141 | }; 142 | 143 | var args = [transactionId]; 144 | 145 | return new Promise(function (resolve, reject) { 146 | var wrappedResolve = function (data) { 147 | delete __transactionList[transactionId]; 148 | resolve(data); 149 | }; 150 | var wrappedReject = function (err) { 151 | delete __transactionList[transactionId]; 152 | reject(err); 153 | }; 154 | exec(wrappedResolve, wrappedReject, PLUGIN_NAME, 'runTransaction', args); 155 | }); 156 | }, 157 | setLogLevel: function (logLevel) { 158 | if (['debug', 'error', 'silent'].indexOf(logLevel) > -1) { 159 | return new Promise(function (resolve, reject) { 160 | exec(resolve, reject, PLUGIN_NAME, 'setLogLevel', [logLevel]); 161 | }); 162 | } else { 163 | return Promise.reject("supported logLevel is one of 'debug', 'error' or 'silent'"); 164 | } 165 | }, 166 | settings: function () { 167 | throw "Firestore.settings: Not supported"; 168 | } 169 | }; 170 | 171 | Firestore.FieldValue = FieldValue; 172 | Firestore.FieldPath = FieldPath; 173 | Firestore.GeoPoint = GeoPoint; 174 | 175 | function initialise(options) { 176 | return new Promise(function (resolve) { 177 | 178 | if (options && options.config) { 179 | var normalizedOptions = {}; 180 | normalizedOptions.applicationId = options.config.applicationId || options.config.applicationID; 181 | normalizedOptions.apiKey = options.config.apiKey || options.config.apikey; 182 | normalizedOptions.clientId = options.config.clientId || options.config.clientID; 183 | normalizedOptions.gcmSenderId = options.config.gcmSenderId || options.config.gcmSenderID; 184 | normalizedOptions.databaseUrl = options.config.databaseUrl || options.config.databaseURL; 185 | normalizedOptions.projectId = options.config.projectId || options.config.projectID; 186 | normalizedOptions.storageBucket = options.config.storageBucket; 187 | normalizedOptions.authDomain = options.config.authDomain; 188 | normalizedOptions.googleAppId = options.config.googleAppId || options.config.googleAppID; 189 | 190 | delete options.config; 191 | options.config = normalizedOptions; 192 | } 193 | resolve(new Firestore(options)); 194 | }); 195 | } 196 | 197 | module.exports = { 198 | initialise: initialise, // original implementation 199 | initialize: initialise, // better for common usage 200 | Firestore: Firestore, 201 | 202 | __executeTransaction: function (transactionId) { 203 | __transactionList[transactionId].updateFunction(__transactionList[transactionId].transaction).then(function (result) { 204 | var args = [transactionId, Utilities.wrap(result)]; 205 | exec(function () { }, function () { }, PLUGIN_NAME, 'transactionResolve', args); 206 | }).catch(function (error) { 207 | throw new Error("Unexpected error in transaction " + error); 208 | }); 209 | }, 210 | 211 | newTimestamp: function (date) { 212 | return new FirestoreTimestamp(date.getTime() / 1000, date.getMilliseconds() * 1000); 213 | }, 214 | 215 | options: function () { 216 | return __firestoreOptions; 217 | }, 218 | 219 | Timestamp: function (seconds, nanoseconds) { 220 | return new FirestoreTimestamp(seconds, nanoseconds); 221 | }, 222 | 223 | GeoPoint: function (latitude, longitude) { 224 | return new GeoPoint(latitude, longitude); 225 | }, 226 | 227 | totalReads: function() { 228 | return DocumentSnapshot._reads; 229 | } 230 | }; 231 | -------------------------------------------------------------------------------- /www/android_ios/firestore_timestamp.js: -------------------------------------------------------------------------------- 1 | function FirestoreTimestamp(seconds, nanoseconds) { 2 | this.seconds = seconds; 3 | this.nanoseconds = nanoseconds; 4 | } 5 | 6 | FirestoreTimestamp.prototype = { 7 | toDate: function() { 8 | return new Date((this.seconds * 1000) + (this.nanoseconds / 1000)); 9 | } 10 | }; 11 | 12 | module.exports = FirestoreTimestamp; 13 | -------------------------------------------------------------------------------- /www/android_ios/geo_point.js: -------------------------------------------------------------------------------- 1 | function GeoPoint(latitude, longitude) { 2 | this._lat = latitude; 3 | this._long = longitude; 4 | 5 | Object.defineProperty(this, 'latitude', { 6 | get: function() { 7 | return this._lat; 8 | } 9 | }); 10 | Object.defineProperty(this, 'longitude', { 11 | get: function() { 12 | return this._long; 13 | } 14 | }); 15 | } 16 | 17 | GeoPoint.prototype = { 18 | isEqual: function (other) { 19 | return other._lat === this._lat && 20 | other._long === this._long; 21 | } 22 | }; 23 | 24 | module.exports = GeoPoint; 25 | -------------------------------------------------------------------------------- /www/android_ios/path.js: -------------------------------------------------------------------------------- 1 | function Path(path) { 2 | this._original = path; 3 | this.segments = path.split('/').filter(function(segment) { 4 | return segment.length > 0; 5 | }); 6 | 7 | this.cleanPath = this.segments.join('/'); 8 | this.id = this.segments[this.segments.length - 1]; 9 | this.parent = this.segments.slice(0, this.segments.length - 1).join('/'); 10 | } 11 | 12 | module.exports = Path; 13 | -------------------------------------------------------------------------------- /www/android_ios/path.test.js: -------------------------------------------------------------------------------- 1 | var Path = require('./path'); 2 | 3 | describe('Path', () => { 4 | describe('parent', () => { 5 | it('should return empty string when on top level', () => { 6 | expect((new Path('collection')).parent).toEqual(''); 7 | }); 8 | 9 | it('should return parent', () => { 10 | expect((new Path('parent/sub')).parent).toEqual('parent'); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /www/android_ios/query.js: -------------------------------------------------------------------------------- 1 | /* global Promise: false */ 2 | 3 | var PLUGIN_NAME = 'Firestore'; 4 | var exec = require('cordova/exec'); 5 | var QuerySnapshot = require("./query_snapshot"); 6 | var utilities = require('./utilities'); 7 | 8 | var cordovaUtils = require("cordova/utils"); 9 | 10 | function Query(ref, queryType, value) { 11 | this._ref = ref; 12 | this._ref._queries.push({ 13 | "queryType": queryType, 14 | "value": value 15 | }); 16 | } 17 | 18 | Query.prototype = Object.create({ 19 | _isFunction: function (functionToCheck) { 20 | var getType = {}; 21 | return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; 22 | }, 23 | endAt: function (snapshotOrVarArgs) { 24 | return new Query(this._ref, "endAt", utilities.wrap(snapshotOrVarArgs)); 25 | }, 26 | endBefore: function (snapshotOrVarArgs) { 27 | return new Query(this._ref, "endBefore", utilities.wrap(snapshotOrVarArgs, true)); 28 | }, 29 | limit: function (limit) { 30 | return new Query(this._ref, "limit", limit); 31 | }, 32 | orderBy: function (field, direction) { 33 | if (direction === undefined) { 34 | direction = "ASCENDING"; 35 | } 36 | 37 | var orderByField = { 38 | "field": field, 39 | "direction": direction 40 | }; 41 | return new Query(this._ref, "orderBy", orderByField); 42 | }, 43 | get: function () { 44 | var args = [this._ref.path, this._ref._queries]; 45 | 46 | return new Promise(function (resolve, reject) { 47 | exec(resolve, reject, PLUGIN_NAME, 'collectionGet', args); 48 | }).then(function (data) { 49 | return new QuerySnapshot(data); 50 | }); 51 | }, 52 | onSnapshot: function (optionsOrObserverOrOnNext, observerOrOnNextOrOnError, onError) { 53 | 54 | var callbackId = cordovaUtils.createUUID(); 55 | 56 | var options = undefined; 57 | 58 | if (!this._isFunction(optionsOrObserverOrOnNext)) { 59 | options = optionsOrObserverOrOnNext; 60 | } 61 | 62 | var wrappedCallback; 63 | var wrappedError; 64 | 65 | if (this._isFunction(optionsOrObserverOrOnNext)) { 66 | wrappedCallback = function (querySnapshot) { 67 | optionsOrObserverOrOnNext(new QuerySnapshot(querySnapshot)); 68 | }; 69 | 70 | if (this._isFunction(observerOrOnNextOrOnError)) { 71 | wrappedError = observerOrOnNextOrOnError; 72 | } 73 | } else if (this._isFunction(observerOrOnNextOrOnError)) { 74 | wrappedCallback = function (querySnapshot) { 75 | observerOrOnNextOrOnError(new QuerySnapshot(querySnapshot)); 76 | }; 77 | 78 | if (this._isFunction(onError)) { 79 | wrappedError = onError; 80 | } 81 | } else { 82 | wrappedCallback = function (querySnapshot) { }; 83 | } 84 | 85 | var args = [this._ref.path, this._ref._queries, options, callbackId]; 86 | 87 | if (!wrappedError) { 88 | wrappedError = function () { 89 | throw new Error("Undefined error in collectionOnSnapshot"); 90 | }; 91 | } 92 | 93 | exec(wrappedCallback, wrappedError, PLUGIN_NAME, 'collectionOnSnapshot', args); 94 | 95 | return function () { 96 | exec(function () { }, function (e) { 97 | throw new Error("Undefined error in collectionUnsubscribe", e); 98 | }, PLUGIN_NAME, 'collectionUnsubscribe', [callbackId]); 99 | }; 100 | }, 101 | startAfter: function (snapshotOrVarArgs) { 102 | return new Query(this._ref, "startAfter", utilities.wrap(snapshotOrVarArgs)); 103 | }, 104 | startAt: function (snapshotOrVarArgs) { 105 | return new Query(this._ref, "startAt", utilities.wrap(snapshotOrVarArgs)); 106 | }, 107 | where: function (fieldPath, opStr, passedValue) { 108 | var value = utilities.wrap(passedValue); 109 | 110 | var whereField = { 111 | "fieldPath": fieldPath, 112 | "opStr": opStr, 113 | "value": value 114 | }; 115 | return new Query(this._ref, "where", whereField); 116 | } 117 | }); 118 | 119 | module.exports = Query; 120 | -------------------------------------------------------------------------------- /www/android_ios/query_document_snapshot.js: -------------------------------------------------------------------------------- 1 | var DocumentSnapshot = require("./document_snapshot"); 2 | 3 | function QueryDocumentSnapshot(data) { 4 | DocumentSnapshot.call(this, data); 5 | } 6 | 7 | QueryDocumentSnapshot.prototype = Object.create(DocumentSnapshot.prototype); 8 | QueryDocumentSnapshot.prototype.constructor = QueryDocumentSnapshot; 9 | 10 | module.exports = QueryDocumentSnapshot; 11 | -------------------------------------------------------------------------------- /www/android_ios/query_snapshot.js: -------------------------------------------------------------------------------- 1 | var QueryDocumentSnapshot = require('./query_document_snapshot'); 2 | 3 | function QuerySnapshot(data) { 4 | this._data = data; 5 | } 6 | 7 | QuerySnapshot.prototype = { 8 | forEach: function (callback) { 9 | var keys = Object.keys(this._data.docs); 10 | for (var i = 0; i < keys.length; i++) { 11 | callback(new QueryDocumentSnapshot(this._data.docs[i])); 12 | } 13 | } 14 | }; 15 | 16 | Object.defineProperties(QuerySnapshot.prototype, { 17 | docChanges: { 18 | get: function () { 19 | throw "QuerySnapshot.docChanges: Not supported"; 20 | } 21 | }, 22 | docs: { 23 | get: function () { 24 | return this._data.docs.map(function(doc) { 25 | return new QueryDocumentSnapshot(doc); 26 | }); 27 | } 28 | }, 29 | empty: { 30 | get: function () { 31 | return this._data.docs.length === 0; 32 | } 33 | }, 34 | metadata: { 35 | get: function () { 36 | throw "QuerySnapshot.metadata: Not supported"; 37 | } 38 | }, 39 | query: { 40 | get: function () { 41 | throw "QuerySnapshot.query: Not supported"; 42 | } 43 | }, 44 | size: { 45 | get: function () { 46 | return this._data.docs.length; 47 | } 48 | } 49 | }); 50 | 51 | module.exports = QuerySnapshot; 52 | -------------------------------------------------------------------------------- /www/android_ios/query_snapshot.test.js: -------------------------------------------------------------------------------- 1 | const QuerySnapshot = require('./query_snapshot'); 2 | const QueryDocumentSnapshot = require('./query_document_snapshot'); 3 | 4 | 5 | describe('QuerySnapshot', () => { 6 | describe('docs', () => { 7 | it('should return an array of QueryDocumentSnapshot', () => { 8 | const docs = (new QuerySnapshot({docs: ['foo', 'bar']})).docs; 9 | expect(docs.length).toBe(2); 10 | expect(docs[0]).toBeInstanceOf(QueryDocumentSnapshot); 11 | expect(docs[1]).toBeInstanceOf(QueryDocumentSnapshot); 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /www/android_ios/transaction.js: -------------------------------------------------------------------------------- 1 | /* global Promise: false */ 2 | 3 | var PLUGIN_NAME = 'Firestore'; 4 | var exec = require('cordova/exec'); 5 | var DocumentSnapshot = require("./document_snapshot"); 6 | var Utilities = require('./utilities'); 7 | 8 | function Transaction(id) { 9 | this._id = id; 10 | } 11 | 12 | Transaction.prototype = { 13 | delete: function (documentReference) { 14 | var args = [this._id, documentReference.id, documentReference._collectionReference.path]; 15 | 16 | var success = function () { 17 | }; 18 | 19 | var failure = function () { 20 | throw new Error("Undefined error in transactionDocDelete"); 21 | }; 22 | 23 | exec(success, failure, PLUGIN_NAME, 'transactionDocDelete', args); 24 | 25 | return this; 26 | }, 27 | get: function (documentReference) { 28 | var args = [this._id, documentReference.id, documentReference._collectionReference.path]; 29 | 30 | return new Promise(function (resolve, reject) { 31 | exec(resolve, reject, PLUGIN_NAME, 'transactionDocGet', args); 32 | }).then(function (data) { 33 | return new DocumentSnapshot(data); 34 | }).catch(function (err) { 35 | }); 36 | }, 37 | set: function (documentReference, data, options) { 38 | 39 | var args = [this._id, documentReference.id, documentReference._collectionReference.path, Utilities.wrap(data), options]; 40 | 41 | var success = function () { 42 | }; 43 | 44 | var failure = function () { 45 | throw new Error("Undefined error in transactionDocSet"); 46 | }; 47 | 48 | exec(success, failure, PLUGIN_NAME, 'transactionDocSet', args); 49 | 50 | return this; 51 | }, 52 | update: function (documentReference, data) { 53 | 54 | var args = [this._id, documentReference.id, documentReference._collectionReference.path, Utilities.wrap(data)]; 55 | 56 | var success = function () { 57 | }; 58 | 59 | var failure = function () { 60 | throw new Error("Undefined error in transactionDocUpdate"); 61 | }; 62 | 63 | exec(success, failure, PLUGIN_NAME, 'transactionDocUpdate', args); 64 | 65 | return this; 66 | } 67 | }; 68 | 69 | module.exports = Transaction; 70 | -------------------------------------------------------------------------------- /www/android_ios/utilities.js: -------------------------------------------------------------------------------- 1 | /* global Firestore */ 2 | 3 | var GeoPoint = require('./geo_point'); 4 | 5 | var wrap = function (data) { 6 | if (data instanceof GeoPoint) { 7 | return Firestore.options().geopointPrefix + data.latitude + "," + data.longitude; 8 | } 9 | 10 | if (Object.prototype.toString.call(data) === '[object Date]') { 11 | return Firestore.options().datePrefix + data.getTime(); 12 | } 13 | 14 | if (Object.prototype.toString.call(data) === '[object DocumentReference]') { 15 | return Firestore.options().referencePrefix + data.path; 16 | } 17 | 18 | if (Object.prototype.toString.call(data) !== '[object Object]') { 19 | return data; 20 | } 21 | 22 | var keys = Object.keys(data); 23 | for (var i = 0; i < keys.length; i++) { 24 | var key = keys[i]; 25 | 26 | if (data[key] instanceof GeoPoint) { 27 | data[key] = Firestore.options().geopointPrefix + data[key].latitude + "," + data[key].longitude; 28 | } else if (Object.prototype.toString.call(data[key]) === '[object Date]') { 29 | if (Firestore.options().timestampsInSnapshots) { 30 | var seconds = data[key].getTime() / 1000; 31 | var nanoseconds = data[key].getMilliseconds() * 1000; 32 | data[key] = Firestore.options().timestampPrefix + seconds + "_" + nanoseconds; 33 | } else { 34 | data[key] = Firestore.options().datePrefix + data[key].getTime(); 35 | } 36 | } else if (Object.prototype.toString.call(data[key]) === '[object Object]') { 37 | data[key] = wrap(data[key]); 38 | } 39 | } 40 | return data; 41 | }; 42 | 43 | module.exports = { 44 | wrap: wrap 45 | }; 46 | -------------------------------------------------------------------------------- /www/android_ios/utils.js: -------------------------------------------------------------------------------- 1 | function AutoId() { 2 | } 3 | 4 | AutoId.prototype = { 5 | newId: function() { 6 | // alphanumeric characters 7 | var chars = 8 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 9 | var autoId = ''; 10 | for (var i = 0; i < 20; i++) { 11 | autoId += chars.charAt(Math.floor(Math.random() * chars.length)); 12 | } 13 | if (autoId.length !== 20) { 14 | throw new Error('Invalid auto ID: ' + autoId); 15 | } 16 | return autoId; 17 | } 18 | }; 19 | 20 | function getOrGenerateId(id) { 21 | if (typeof id === 'string') { 22 | return id; 23 | } 24 | 25 | return new AutoId().newId(); 26 | } 27 | 28 | module.exports = { 29 | AutoId: AutoId, 30 | getOrGenerateId: getOrGenerateId 31 | }; 32 | -------------------------------------------------------------------------------- /www/browser/firestore.js: -------------------------------------------------------------------------------- 1 | /* global firebase: false, Promise: false */ 2 | 3 | var isInitialized = function (packageName) { 4 | var parent = window; 5 | var steps = packageName.split(/\./); 6 | var results = steps.filter(function (step) { 7 | if (step in parent) { 8 | parent = parent[step]; 9 | return true; 10 | } else { 11 | return false; 12 | } 13 | }); 14 | 15 | return results.length === steps.length; 16 | }; 17 | 18 | var loadJs = function (options) { 19 | return new Promise(function (resolve, reject) { 20 | 21 | if (isInitialized(options.package)) { 22 | resolve(); 23 | } else { 24 | var scriptTag = document.createElement('script'); 25 | scriptTag.src = options.url; 26 | scriptTag.onload = function () { 27 | var timer = setInterval(function () { 28 | if (isInitialized(options.package)) { 29 | clearInterval(timer); 30 | resolve(); 31 | } 32 | }, 10); 33 | }; 34 | scriptTag.onerror = reject; 35 | document.body.appendChild(scriptTag); 36 | } 37 | }); 38 | }; 39 | 40 | function Firestore(options, resolve, reject) { 41 | 42 | var self = this; 43 | 44 | var initialised = false; 45 | 46 | for (var i = 0; i < firebase.apps.length; i++) { 47 | if (firebase.apps[i].options.projectId === options.config.projectId) { 48 | initialised = true; 49 | } 50 | } 51 | 52 | if (!initialised) { 53 | firebase.initializeApp(options.config); 54 | } 55 | 56 | // Default false, to maintain backwards compatability 57 | var timestampsInSnapshots = 'timestampsInSnapshots' in options ? 58 | options.timestampsInSnapshots : true; 59 | 60 | var firestore = firebase.firestore(); 61 | firestore.settings({ 62 | 'timestampsInSnapshots': timestampsInSnapshots 63 | }); 64 | 65 | if (options.persist) { 66 | firestore 67 | .enablePersistence({ 68 | experimentalTabSynchronization: true 69 | }) 70 | .then(function () { 71 | self.database = firestore; 72 | resolve(self); 73 | }) 74 | .catch(reject); 75 | } else { 76 | self.database = firestore; 77 | resolve(self); 78 | } 79 | } 80 | 81 | Firestore.prototype.get = function () { 82 | return this.database; 83 | }; 84 | 85 | 86 | function createInstance(options) { 87 | return new Promise(function (resolve, reject) { 88 | 89 | Firestore.FieldValue = firebase.firestore.FieldValue; 90 | Firestore.FieldPath = firebase.firestore.FieldPath; 91 | Firestore.GeoPoint = firebase.firestore.GeoPoint; 92 | 93 | var db = new Firestore(options, resolve, reject); 94 | }); 95 | } 96 | 97 | function initialise(options) { 98 | return loadJs({ 99 | 'url': 'https://www.gstatic.com/firebasejs/7.6.1/firebase-app.js', 100 | 'package': 'firebase.app' 101 | }) 102 | .then(function () { 103 | return loadJs({ 104 | 'url': 'https://www.gstatic.com/firebasejs/7.6.1/firebase-firestore.js', 105 | 'package': 'firebase.firestore' 106 | }); 107 | }) 108 | .then(function () { 109 | return createInstance(options); 110 | }); 111 | } 112 | 113 | module.exports = { 114 | initialise: initialise, // original developer name 115 | initialize: initialise, // better for common usage 116 | Firestore: Firestore, 117 | 118 | newTimestamp: function (date) { 119 | return firebase.firestore.Timestamp.fromDate(date); 120 | }, 121 | 122 | Timestamp: function (seconds, nanoseconds) { 123 | return new firebase.firestore.Timestamp(seconds, nanoseconds); 124 | }, 125 | 126 | GeoPoint: function (latitude, longitude) { 127 | return new firebase.firestore.GeoPoint(latitude, longitude); 128 | }, 129 | 130 | totalReads: function() { 131 | return -1; 132 | } 133 | }; 134 | --------------------------------------------------------------------------------