├── .babelrc ├── .gitignore ├── .npmignore ├── Contributing.md ├── Firestack.podspec ├── LICENSE ├── README.md ├── android ├── .idea │ └── gradle.xml ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── io │ └── fullstack │ └── firestack │ ├── FirestackAnalytics.java │ ├── FirestackAuth.java │ ├── FirestackCloudMessaging.java │ ├── FirestackDatabase.java │ ├── FirestackInstanceIdService.java │ ├── FirestackMessagingService.java │ ├── FirestackModule.java │ ├── FirestackPackage.java │ ├── FirestackStorage.java │ └── FirestackUtils.java ├── bin ├── cocoapods.sh └── prepare.sh ├── firestack.android.js ├── firestack.ios.js ├── index.js ├── ios ├── Firestack.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcuserdata │ │ ├── Cheol.xcuserdatad │ │ └── xcschemes │ │ │ ├── Firestack.xcscheme │ │ │ └── xcschememanagement.plist │ │ └── auser.xcuserdatad │ │ └── xcschemes │ │ ├── Firestack.xcscheme │ │ └── xcschememanagement.plist ├── Firestack │ ├── Firestack.h │ ├── Firestack.m │ ├── FirestackAnalytics.h │ ├── FirestackAnalytics.m │ ├── FirestackAuth.h │ ├── FirestackAuth.m │ ├── FirestackCloudMessaging.h │ ├── FirestackCloudMessaging.m │ ├── FirestackDatabase.h │ ├── FirestackDatabase.m │ ├── FirestackErrors.h │ ├── FirestackErrors.m │ ├── FirestackEvents.h │ ├── FirestackStorage.h │ └── FirestackStorage.m ├── Podfile ├── Podfile.template └── buildScript.sh ├── lib ├── firestack.js ├── firestackModule.js ├── modules │ ├── analytics.js │ ├── authentication.js │ ├── base.js │ ├── cloudmessaging.js │ ├── database.js │ ├── presence.js │ ├── remoteConfig.js │ └── storage.js └── utils │ ├── __tests__ │ ├── log-test.js │ ├── promisify-test.js │ └── singleton-test.js │ ├── log.js │ ├── promisify.js │ ├── singleton.js │ └── window-or-global.js ├── package.json └── test ├── firestack.test.js ├── mocha.opts └── test-helper.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | *.DS_Store 4 | 5 | # Xcode 6 | *.pbxuser 7 | *.mode1v3 8 | *.mode2v3 9 | *.perspectivev3 10 | *.xcuserstate 11 | project.xcworkspace/ 12 | xcuserdata/ 13 | 14 | # Android 15 | 16 | # Built application files 17 | android/*/build/ 18 | 19 | # Crashlytics configuations 20 | android/com_crashlytics_export_strings.xml 21 | 22 | # Local configuration file (sdk path, etc) 23 | android/local.properties 24 | 25 | # Gradle generated files 26 | android/.gradle/ 27 | 28 | # Signing files 29 | android/.signing/ 30 | 31 | # User-specific configurations 32 | android/.idea/libraries/ 33 | android/.idea/workspace.xml 34 | android/.idea/tasks.xml 35 | android/.idea/.name 36 | android/.idea/compiler.xml 37 | android/.idea/copyright/profiles_settings.xml 38 | android/.idea/encodings.xml 39 | android/.idea/misc.xml 40 | android/.idea/modules.xml 41 | android/.idea/scopes/scope_settings.xml 42 | android/.idea/vcs.xml 43 | android/*.iml 44 | 45 | # OS-specific files 46 | .DS_Store 47 | .DS_Store? 48 | ._* 49 | .Spotlight-V100 50 | .Trashes 51 | ehthumbs.db 52 | Thumbs.dbandroid/gradle 53 | android/gradlew 54 | android/gradlew.bat 55 | android/gradle/ 56 | .idea 57 | .idea 58 | coverage 59 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | *.iml 4 | .*.haste_cache.* 5 | .DS_Store 6 | .idea 7 | .babelrc 8 | .eslintrc 9 | npm-debug.log 10 | src/ 11 | examples/ 12 | public/ 13 | scripts/ 14 | test/ -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | ## Contributing guide 2 | 3 | This is an in-progress guide to help guide you in understanding how Firestack works with the goal to help on-board your contributions. If you have any questions, comments, or concerns, feel free to leave it here or join the [gitter channel at https://gitter.im/fullstackreact/react-native-firestack](https://gitter.im/fullstackreact/react-native-firestack). 4 | 5 | ## Contribution methods 6 | 7 | Contributing is easy. Make a fork of the project on [github](https://github.com/fullstackreact/react-native-firestack). Clone this repo on your machine and work on the edits there. 8 | 9 | ```shell 10 | git clone https://github.com/[your_name]/react-native-firestack.git 11 | cd react-native-firestack 12 | npm install 13 | ``` 14 | 15 | We have an [Example app - FirestackApp](https://github.com/fullstackreact/FirestackApp) which we use to demonstrate and test features (until we can get a proper testing environment). Currently, our workflow looks like this: 16 | 17 | 1. Write JS/native feature 18 | 2. `rsync` the local library to your `node_modules` directory (react-native does not play well with symlinks). 19 | For instance, running the following in the firestackApp root directory. Make sure you replace the `~/Development/react-native/mine/react-native-firestack` with the path of your cloned repo on your drive: 20 | 21 | ```javascript 22 | rsync -avhW --delete \ 23 | --exclude='node_modules' \ 24 | --exclude='.git' \ 25 | --exclude='coverage' \ 26 | ~/Development/react-native/mine/react-native-firestack/ \ 27 | ./node_modules/react-native-firestack/ 28 | ``` 29 | 30 | 3. Test in-app 31 | 4. Update README.md with bugfix/feature 32 | 5. Create a pull request (PR) 33 | 34 | ## High level 35 | 36 | ## How it works technically 37 | 38 | Firestack is broken up by functional modules which control/interact with the different features of Firebase. I.e. there is a database module, which maps to the Real-Time Database feature in Firebase, Analytics maps to the Firebase analytics stack. 39 | 40 | When the user creates a new instance of Firestack, they are creating an instance of the JS class defined in `lib/firestack.js`. 41 | 42 | ```javascript 43 | // This creates a JS instance of the 44 | // Firestack class 45 | const firestack = new Firestack({}); 46 | ``` 47 | 48 | Each of the modules in Firestack can be accessed through this instance. For instance, when we want to access the real-time database through the `firestack` instance, the JS API exposes a `database` accessor. 49 | 50 | For instance, when interacting with the database from the instance above, we would call `.database` to get access to a singleton instance of the JS `Database` class defined in `lib/modules/database.js`. 51 | 52 | ### Database walk-through 53 | 54 | ```javascript 55 | const db = firestack.database; 56 | ``` 57 | 58 | The `lib/modules/database.js` file exports two classes, one called `Database` and the other called `DatabaseRef`. Essentially, the `Database` class is a wrapper class that provides a handful of methods to forward off to a `DatabaseRef` instance. 59 | 60 | The `DatabaseRef` class defines the actual interaction with the native Firebase SDK. Let's look at the `getAt` method as an example of how the JS side interacts with the native-side and back. 61 | 62 | When the user accessess a Firebase ref, the `Database` instance creates a new instance of the `DatabaseRef` JS class. 63 | 64 | ```javascript 65 | const ref = db.ref('/events'); 66 | ``` 67 | 68 | The `DatabaseRef` class is the wrapper that maps to Firebase database points. For efficiency, the `paths` are stored as an array so we can walk up and down the firebase database using the `parent()` and `child()` methods on a database ref. 69 | 70 | Calling `getAt()` on the `ref` (an instance of the `DatabaseRef` class) will make a call to the **native** SDK using a method called `promisify()` 71 | 72 | ```javascript 73 | class DatabaseRef { 74 | // ... 75 | getAt(key) { 76 | let path = this.path; 77 | if (key && typeof(key) == 'string') { 78 | path = `${path}${separator}${key}` 79 | } 80 | return promisify('onOnce', FirestackDatabase)(path); 81 | } 82 | } 83 | ``` 84 | 85 | Ignoring the first few lines (which are helpers to add to the `path`, which we'll look at shortly), the `promisify()` function (defined in `lib/promisify.js`) takes two arguments: 86 | 87 | 1. The 'string' name of the native function to call 88 | 2. The native module we want to call it on 89 | 90 | The `promisify()` function returns a function that returns a `Promise` object in JS. This returned function calls the native function with a React-Native callback. When the React Native function calls the callback function, the Promise is resolved. 91 | 92 | Getting back to the Database example, the `getAt()` function (which has an alias of `get`) calls the `onOnce` function on the `FirestackDatabase` native module. Each platform has their own native module version for each feature area of Firebase. 93 | 94 | Every function on the `DatabaseRef` class is called with the `path` from Firebase as well as it's other options. 95 | 96 | Let's look at the `onOnce` function of the iOS version of `FirestackDatabase` implemented in `ios/Firestack/FirestackDatabase.m`: 97 | 98 | ``` 99 | // This might differ from the current code, but 100 | // is implemented this way at the time of the writing 101 | // of this document 102 | RCT_EXPORT_METHOD(onOnce:(NSString *) path 103 | name:(NSString *) name 104 | callback:(RCTResponseSenderBlock) callback) 105 | { 106 | int eventType = [self eventTypeFromName:name]; 107 | 108 | FIRDatabaseReference *ref = [self getRefAtPath:path]; 109 | [ref observeSingleEventOfType:eventType 110 | withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { 111 | callback(@[[NSNull null], [self snapshotToDict:snapshot]]); 112 | } 113 | withCancelBlock:^(NSError * _Nonnull error) { 114 | NSLog(@"Error onDBEventOnce: %@", [error debugDescription]); 115 | callback(@[@{ 116 | @"error": @"onceError", 117 | @"msg": [error debugDescription] 118 | }]); 119 | }]; 120 | } 121 | ``` 122 | 123 | Every native function (in either iOS or Android) is expected to accept a single callback as the final argument. The `onOnce` function accepts the path (as the first argument) and the name of the event we're interested in (such as `value`) and uses the Native SDK to set up the appropriate functionality. When the function has been called and completed, the callback is called with an error on failure and with success on success. 124 | 125 | > An error response is considered one which the first argument is non-null. Therefore, to send a successful response, the first value when calling the callback should be null to indicate success. 126 | 127 | ## Adding functionality 128 | 129 | // TODO -------------------------------------------------------------------------------- /Firestack.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | package = JSON.parse(File.read('package.json')) 3 | version = package["version"] 4 | repo = package['repository'] 5 | author = package['author'] 6 | 7 | all_pods = [ 8 | 'FirebaseAnalytics', 'FirebaseAuth', 'FirebaseRemoteConfig', 9 | 'FirebaseDatabase', 'FirebaseStorage', 'FirebaseInstanceID', 10 | 'GoogleInterchangeUtilities', 'GoogleIPhoneUtilities', 11 | 'GoogleNetworkingUtilities', 'GoogleParsingUtilities', 12 | 'GoogleSymbolUtilities' 13 | ] 14 | 15 | Pod::Spec.new do |s| 16 | 17 | s.name = "Firestack" 18 | s.version = version 19 | s.summary = "Firestack makes working with Firebase v3 easy" 20 | 21 | s.description = <<-DESC 22 | Wanna integrate firebase into your app using React Native? 23 | DESC 24 | 25 | s.homepage = "http://fullstackreact.com" 26 | 27 | s.license = { :type => "MIT", :file => "LICENSE" } 28 | s.author = { "Ari Lerner" => author } 29 | s.social_media_url = 'http://twitter.com/fullstackio' 30 | 31 | # When using multiple platforms 32 | s.ios.deployment_target = "8.0" 33 | # s.osx.deployment_target = "10.7" 34 | # s.watchos.deployment_target = "2.0" 35 | # s.tvos.deployment_target = "9.0" 36 | 37 | s.source = { :git => repo['url'], :tag => "v#{version}" } 38 | s.public_header_files = "ios/Firestack/*.h" 39 | 40 | s.source_files = 'ios/Firestack/*.{h,m}' 41 | s.preserve_paths = 'README.md', 'package.json', '*.js' 42 | 43 | s.ios.frameworks = [ 44 | 'CFNetwork', 'Security', 'SystemConfiguration' 45 | ] 46 | s.ios.libraries = ['icucore', 'c++', 'sqlite3', 'z'] 47 | 48 | s.xcconfig = { 49 | 'HEADER_SEARCH_PATHS' => [ 50 | "$(inherited)", 51 | "${SRCROOT}/../../React/**", 52 | "${SRCROOT}/../../node_modules/react-native/**" 53 | ].join(' '), 54 | 'FRAMEWORK_SEARCH_PATHS' => [ 55 | "$(inherited)", 56 | "${PODS_ROOT}/Firebase/**", 57 | "${PODS_ROOT}/FirebaseStorage/**", 58 | ].join(' '), 59 | 'OTHER_LDFLAGS' => '$(inherited) -ObjC' 60 | } 61 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Ari Lerner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 16 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | multiDexEnabled true 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile 'com.facebook.react:react-native:0.20.+' 23 | compile 'com.google.android.gms:play-services-base:+' 24 | 25 | compile 'com.google.firebase:firebase-core:10.0.1' 26 | compile 'com.google.firebase:firebase-auth:10.0.1' 27 | compile 'com.google.firebase:firebase-analytics:10.0.1' 28 | compile 'com.google.firebase:firebase-database:10.0.1' 29 | compile 'com.google.firebase:firebase-storage:10.0.1' 30 | compile 'com.google.firebase:firebase-messaging:10.0.1' 31 | } 32 | 33 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | import android.os.Bundle; 6 | import java.util.Iterator; 7 | import java.util.Map; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | 11 | import com.facebook.react.bridge.Arguments; 12 | import com.facebook.react.bridge.ReactApplicationContext; 13 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 14 | import com.facebook.react.bridge.ReactMethod; 15 | import com.facebook.react.bridge.Callback; 16 | import com.facebook.react.bridge.WritableMap; 17 | import com.facebook.react.bridge.ReadableMap; 18 | import com.facebook.react.bridge.ReactContext; 19 | 20 | import com.google.android.gms.tasks.OnCompleteListener; 21 | import com.google.android.gms.tasks.OnFailureListener; 22 | import com.google.android.gms.tasks.Task; 23 | import com.google.firebase.FirebaseApp; 24 | import com.google.firebase.analytics.FirebaseAnalytics; 25 | import com.google.firebase.analytics.FirebaseAnalytics.Event.*; 26 | import com.google.firebase.analytics.FirebaseAnalytics.Param; 27 | 28 | class FirestackAnalyticsModule extends ReactContextBaseJavaModule { 29 | 30 | private static final String TAG = "FirestackAnalytics"; 31 | 32 | private Context context; 33 | private ReactContext mReactContext; 34 | private FirebaseAnalytics mFirebaseAnalytics; 35 | 36 | public FirestackAnalyticsModule(ReactApplicationContext reactContext) { 37 | super(reactContext); 38 | this.context = reactContext; 39 | mReactContext = reactContext; 40 | 41 | Log.d(TAG, "New instance"); 42 | mFirebaseAnalytics = FirebaseAnalytics.getInstance(this.context); 43 | } 44 | 45 | @Override 46 | public String getName() { 47 | return TAG; 48 | } 49 | 50 | @ReactMethod 51 | public void logEventWithName(final String name, final ReadableMap props, final Callback callback) { 52 | // TODO 53 | // FirestackUtils.todoNote(TAG, "logEventWithName", callback); 54 | Map m = FirestackUtils.recursivelyDeconstructReadableMap(props); 55 | final String eventName = getEventName(name); 56 | final Bundle bundle = makeEventBundle(name, m); 57 | Log.d(TAG, "Logging event " + eventName); 58 | mFirebaseAnalytics.logEvent(name, bundle); 59 | } 60 | 61 | private String getEventName(final String name) { 62 | if (name == FirebaseAnalytics.Event.ADD_PAYMENT_INFO) {return FirebaseAnalytics.Event.ADD_PAYMENT_INFO; } 63 | else if (name == FirebaseAnalytics.Event.ADD_TO_CART) {return FirebaseAnalytics.Event.ADD_TO_CART;} 64 | else if (name == FirebaseAnalytics.Event.ADD_TO_WISHLIST) {return FirebaseAnalytics.Event.ADD_TO_WISHLIST;} 65 | else if (name == FirebaseAnalytics.Event.APP_OPEN) {return FirebaseAnalytics.Event.APP_OPEN;} 66 | else if (name == FirebaseAnalytics.Event.BEGIN_CHECKOUT) {return FirebaseAnalytics.Event.BEGIN_CHECKOUT;} 67 | else if (name == FirebaseAnalytics.Event.ECOMMERCE_PURCHASE) {return FirebaseAnalytics.Event.ECOMMERCE_PURCHASE;} 68 | else if (name == FirebaseAnalytics.Event.GENERATE_LEAD) {return FirebaseAnalytics.Event.GENERATE_LEAD;} 69 | else if (name == FirebaseAnalytics.Event.JOIN_GROUP) {return FirebaseAnalytics.Event.JOIN_GROUP;} 70 | else if (name == FirebaseAnalytics.Event.LEVEL_UP) {return FirebaseAnalytics.Event.LEVEL_UP;} 71 | else if (name == FirebaseAnalytics.Event.LOGIN) {return FirebaseAnalytics.Event.LOGIN;} 72 | else if (name == FirebaseAnalytics.Event.POST_SCORE) {return FirebaseAnalytics.Event.POST_SCORE;} 73 | else if (name == FirebaseAnalytics.Event.PRESENT_OFFER) {return FirebaseAnalytics.Event.PRESENT_OFFER;} 74 | else if (name == FirebaseAnalytics.Event.PURCHASE_REFUND) {return FirebaseAnalytics.Event.PURCHASE_REFUND;} 75 | else if (name == FirebaseAnalytics.Event.SEARCH) {return FirebaseAnalytics.Event.SEARCH;} 76 | else if (name == FirebaseAnalytics.Event.SELECT_CONTENT) {return FirebaseAnalytics.Event.SELECT_CONTENT;} 77 | else if (name == FirebaseAnalytics.Event.SHARE) {return FirebaseAnalytics.Event.SHARE;} 78 | else if (name == FirebaseAnalytics.Event.SIGN_UP) {return FirebaseAnalytics.Event.SIGN_UP;} 79 | else if (name == FirebaseAnalytics.Event.SPEND_VIRTUAL_CURRENCY) {return FirebaseAnalytics.Event.SPEND_VIRTUAL_CURRENCY;} 80 | else if (name == FirebaseAnalytics.Event.TUTORIAL_BEGIN) {return FirebaseAnalytics.Event.TUTORIAL_BEGIN;} 81 | else if (name == FirebaseAnalytics.Event.TUTORIAL_COMPLETE) {return FirebaseAnalytics.Event.TUTORIAL_COMPLETE;} 82 | else if (name == FirebaseAnalytics.Event.UNLOCK_ACHIEVEMENT) {return FirebaseAnalytics.Event.UNLOCK_ACHIEVEMENT;} 83 | else if (name == FirebaseAnalytics.Event.VIEW_ITEM) {return FirebaseAnalytics.Event.VIEW_ITEM;} 84 | else if (name == FirebaseAnalytics.Event.VIEW_ITEM_LIST) {return FirebaseAnalytics.Event.VIEW_ITEM_LIST;} 85 | else if (name == FirebaseAnalytics.Event.VIEW_SEARCH_RESULTS) {return FirebaseAnalytics.Event.VIEW_SEARCH_RESULTS;} 86 | else return name; 87 | } 88 | 89 | private Bundle makeEventBundle(final String name, final Map map) { 90 | Bundle bundle = new Bundle(); 91 | // Available from the Analytics event 92 | if (map.containsKey("id")) { 93 | String id = (String) map.get("id"); 94 | bundle.putString(FirebaseAnalytics.Param.ITEM_ID, id); 95 | } 96 | if (map.containsKey("name")) { 97 | String val = (String) map.get("name"); 98 | bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, val); 99 | } 100 | if (map.containsKey("category")) { 101 | String val = (String) map.get("category"); 102 | bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, val); 103 | } 104 | if (map.containsKey("quantity")) { 105 | double val = (double) map.get("quantity"); 106 | bundle.putDouble(FirebaseAnalytics.Param.QUANTITY, val); 107 | } 108 | if (map.containsKey("price")) { 109 | double val = (double) map.get("price"); 110 | bundle.putDouble(FirebaseAnalytics.Param.PRICE, val); 111 | } 112 | if (map.containsKey("value")) { 113 | double val = (double) map.get("value"); 114 | bundle.putDouble(FirebaseAnalytics.Param.VALUE, val); 115 | } 116 | if (map.containsKey("currency")) { 117 | String val = (String) map.get("currency"); 118 | bundle.putString(FirebaseAnalytics.Param.CURRENCY, val); 119 | } 120 | if (map.containsKey("origin")) { 121 | String val = (String) map.get("origin"); 122 | bundle.putString(FirebaseAnalytics.Param.ORIGIN, val); 123 | } 124 | if (map.containsKey("item_location_id")) { 125 | String val = (String) map.get("item_location_id"); 126 | bundle.putString(FirebaseAnalytics.Param.ITEM_LOCATION_ID, val); 127 | } 128 | if (map.containsKey("location")) { 129 | String val = (String) map.get("location"); 130 | bundle.putString(FirebaseAnalytics.Param.LOCATION, val); 131 | } 132 | if (map.containsKey("destination")) { 133 | String val = (String) map.get("destination"); 134 | bundle.putString(FirebaseAnalytics.Param.DESTINATION, val); 135 | } 136 | if (map.containsKey("start_date")) { 137 | String val = (String) map.get("start_date"); 138 | bundle.putString(FirebaseAnalytics.Param.START_DATE, val); 139 | } 140 | if (map.containsKey("end_date")) { 141 | String val = (String) map.get("end_date"); 142 | bundle.putString(FirebaseAnalytics.Param.END_DATE, val); 143 | } 144 | if (map.containsKey("transaction_id")) { 145 | String val = (String) map.get("transaction_id"); 146 | bundle.putString(FirebaseAnalytics.Param.TRANSACTION_ID, val); 147 | } 148 | if (map.containsKey("number_of_nights")) { 149 | long val = (long) map.get("number_of_nights"); 150 | bundle.putLong(FirebaseAnalytics.Param.NUMBER_OF_NIGHTS, val); 151 | } 152 | if (map.containsKey("number_of_rooms")) { 153 | long val = (long) map.get("number_of_rooms"); 154 | bundle.putLong(FirebaseAnalytics.Param.NUMBER_OF_ROOMS, val); 155 | } 156 | if (map.containsKey("number_of_passengers")) { 157 | long val = (long) map.get("number_of_passengers"); 158 | bundle.putLong(FirebaseAnalytics.Param.NUMBER_OF_PASSENGERS, val); 159 | } 160 | if (map.containsKey("travel_class")) { 161 | String val = (String) map.get("travel_class"); 162 | bundle.putString(FirebaseAnalytics.Param.TRAVEL_CLASS, val); 163 | } 164 | if (map.containsKey("coupon")) { 165 | String val = (String) map.get("coupon"); 166 | bundle.putString(FirebaseAnalytics.Param.COUPON, val); 167 | } 168 | if (map.containsKey("tax")) { 169 | long val = (long) map.get("tax"); 170 | bundle.putLong(FirebaseAnalytics.Param.TAX, val); 171 | } 172 | if (map.containsKey("shipping")) { 173 | double val = (double) map.get("shipping"); 174 | bundle.putDouble(FirebaseAnalytics.Param.SHIPPING, val); 175 | } 176 | if (map.containsKey("group_id")) { 177 | String val = (String) map.get("group_id"); 178 | bundle.putString(FirebaseAnalytics.Param.GROUP_ID, val); 179 | } 180 | if (map.containsKey("level")) { 181 | long val = (long) map.get("level"); 182 | bundle.putLong(FirebaseAnalytics.Param.LEVEL, val); 183 | } 184 | if (map.containsKey("character")) { 185 | String val = (String) map.get("character"); 186 | bundle.putString(FirebaseAnalytics.Param.CHARACTER, val); 187 | } 188 | if (map.containsKey("score")) { 189 | long val = (long) map.get("score"); 190 | bundle.putLong(FirebaseAnalytics.Param.SCORE, val); 191 | } 192 | if (map.containsKey("search_term")) { 193 | String val = (String) map.get("search_term"); 194 | bundle.putString(FirebaseAnalytics.Param.SEARCH_TERM, val); 195 | } 196 | if (map.containsKey("content_type")) { 197 | String val = (String) map.get("content_type"); 198 | bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, val); 199 | } 200 | if (map.containsKey("sign_up_method")) { 201 | String val = (String) map.get("sign_up_method"); 202 | bundle.putString(FirebaseAnalytics.Param.SIGN_UP_METHOD, val); 203 | } 204 | if (map.containsKey("virtual_currency_name")) { 205 | String val = (String) map.get("virtual_currency_name"); 206 | bundle.putString(FirebaseAnalytics.Param.VIRTUAL_CURRENCY_NAME, val); 207 | } 208 | if (map.containsKey("achievement_id")) { 209 | String val = (String) map.get("achievement_id"); 210 | bundle.putString(FirebaseAnalytics.Param.ACHIEVEMENT_ID, val); 211 | } 212 | if (map.containsKey("flight_number")) { 213 | String val = (String) map.get("flight_number"); 214 | bundle.putString(FirebaseAnalytics.Param.FLIGHT_NUMBER, val); 215 | } 216 | 217 | Iterator> entries = map.entrySet().iterator(); 218 | while (entries.hasNext()) { 219 | Map.Entry entry = entries.next(); 220 | if (bundle.getBundle(entry.getKey()) == null) { 221 | bundle.putString(entry.getKey(), entry.getValue().toString()); 222 | } 223 | } 224 | return bundle; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/FirestackCloudMessaging.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack; 2 | 3 | import java.util.Map; 4 | 5 | import android.content.Context; 6 | import android.content.IntentFilter; 7 | import android.content.Intent; 8 | import android.content.BroadcastReceiver; 9 | import android.util.Log; 10 | 11 | import com.facebook.react.bridge.Arguments; 12 | import com.facebook.react.bridge.Callback; 13 | import com.facebook.react.bridge.ReactApplicationContext; 14 | import com.facebook.react.bridge.ReactContext; 15 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 16 | import com.facebook.react.bridge.Promise; 17 | import com.facebook.react.bridge.ReactMethod; 18 | import com.facebook.react.bridge.ReadableMap; 19 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 20 | import com.facebook.react.bridge.ReadableType; 21 | import com.facebook.react.bridge.WritableMap; 22 | 23 | import com.google.firebase.iid.FirebaseInstanceId; 24 | import com.google.firebase.messaging.FirebaseMessaging; 25 | import com.google.firebase.messaging.RemoteMessage; 26 | 27 | /** 28 | * Created by nori on 2016/09/12. 29 | */ 30 | public class FirestackCloudMessaging extends ReactContextBaseJavaModule { 31 | 32 | private static final String TAG = "FirestackCloudMessaging"; 33 | private static final String EVENT_NAME_TOKEN = "FirestackRefreshToken"; 34 | private static final String EVENT_NAME_NOTIFICATION = "FirestackReceiveNotification"; 35 | private static final String EVENT_NAME_SEND = "FirestackUpstreamSend"; 36 | 37 | public static final String INTENT_NAME_TOKEN = "io.fullstack.firestack.refreshToken"; 38 | public static final String INTENT_NAME_NOTIFICATION = "io.fullstack.firestack.ReceiveNotification"; 39 | public static final String INTENT_NAME_SEND = "io.fullstack.firestack.Upstream"; 40 | 41 | private ReactContext mReactContext; 42 | private IntentFilter mRefreshTokenIntentFilter; 43 | private IntentFilter mReceiveNotificationIntentFilter; 44 | private IntentFilter mReceiveSendIntentFilter; 45 | 46 | public FirestackCloudMessaging(ReactApplicationContext reactContext) { 47 | super(reactContext); 48 | mReactContext = reactContext; 49 | mRefreshTokenIntentFilter = new IntentFilter(INTENT_NAME_TOKEN); 50 | mReceiveNotificationIntentFilter = new IntentFilter(INTENT_NAME_NOTIFICATION); 51 | mReceiveSendIntentFilter = new IntentFilter(INTENT_NAME_SEND); 52 | initRefreshTokenHandler(); 53 | initMessageHandler(); 54 | initSendHandler(); 55 | Log.d(TAG, "New instance"); 56 | } 57 | 58 | @Override 59 | public String getName() { 60 | return TAG; 61 | } 62 | 63 | @ReactMethod 64 | public void getToken(final Callback callback) { 65 | 66 | try { 67 | String token = FirebaseInstanceId.getInstance().getToken(); 68 | Log.d(TAG, "Firebase token: " + token); 69 | callback.invoke(null, token); 70 | } catch (Exception e) { 71 | WritableMap error = Arguments.createMap(); 72 | error.putString("message", e.getMessage()); 73 | callback.invoke(error); 74 | } 75 | } 76 | 77 | /** 78 | * 79 | */ 80 | private void initRefreshTokenHandler() { 81 | getReactApplicationContext().registerReceiver(new BroadcastReceiver() { 82 | @Override 83 | public void onReceive(Context context, Intent intent) { 84 | WritableMap params = Arguments.createMap(); 85 | params.putString("token", intent.getStringExtra("token")); 86 | ReactContext ctx = getReactApplicationContext(); 87 | Log.d(TAG, "initRefreshTokenHandler received event " + EVENT_NAME_TOKEN); 88 | FirestackUtils.sendEvent(ctx, EVENT_NAME_TOKEN, params); 89 | } 90 | 91 | ; 92 | }, mRefreshTokenIntentFilter); 93 | } 94 | 95 | @ReactMethod 96 | public void subscribeToTopic(String topic, final Callback callback) { 97 | try { 98 | FirebaseMessaging.getInstance().subscribeToTopic(topic); 99 | callback.invoke(null,topic); 100 | } catch (Exception e) { 101 | e.printStackTrace(); 102 | Log.d(TAG, "Firebase token: " + e); 103 | WritableMap error = Arguments.createMap(); 104 | error.putString("message", e.getMessage()); 105 | callback.invoke(error); 106 | 107 | } 108 | } 109 | 110 | @ReactMethod 111 | public void unsubscribeFromTopic(String topic, final Callback callback) { 112 | try { 113 | FirebaseMessaging.getInstance().unsubscribeFromTopic(topic); 114 | callback.invoke(null,topic); 115 | } catch (Exception e) { 116 | WritableMap error = Arguments.createMap(); 117 | error.putString("message", e.getMessage()); 118 | callback.invoke(error); 119 | } 120 | } 121 | 122 | private void initMessageHandler() { 123 | Log.d(TAG, "Firestack initMessageHandler called"); 124 | getReactApplicationContext().registerReceiver(new BroadcastReceiver() { 125 | @Override 126 | public void onReceive(Context context, Intent intent) { 127 | RemoteMessage remoteMessage = intent.getParcelableExtra("data"); 128 | Log.d(TAG, "Firebase onReceive: " + remoteMessage); 129 | WritableMap params = Arguments.createMap(); 130 | if (remoteMessage.getData().size() != 0) { 131 | WritableMap dataMap = Arguments.createMap(); 132 | Map data = remoteMessage.getData(); 133 | //Set keysIterator = data.keySet(); 134 | for (String key : data.keySet()) { 135 | dataMap.putString(key, data.get(key)); 136 | } 137 | params.putMap("data", dataMap); 138 | } else { 139 | params.putNull("data"); 140 | } 141 | if (remoteMessage.getNotification() != null) { 142 | WritableMap notificationMap = Arguments.createMap(); 143 | RemoteMessage.Notification notification = remoteMessage.getNotification(); 144 | notificationMap.putString("title", notification.getTitle()); 145 | notificationMap.putString("body", notification.getBody()); 146 | notificationMap.putString("icon", notification.getIcon()); 147 | notificationMap.putString("sound", notification.getSound()); 148 | notificationMap.putString("tag", notification.getTag()); 149 | params.putMap("notification", notificationMap); 150 | } else { 151 | params.putNull("notification"); 152 | } 153 | ReactContext ctx = getReactApplicationContext(); 154 | FirestackUtils.sendEvent(ctx, EVENT_NAME_NOTIFICATION, params); 155 | } 156 | }, mReceiveNotificationIntentFilter); 157 | } 158 | 159 | @ReactMethod 160 | public void send(String senderId, String messageId, String messageType, ReadableMap params, final Callback callback) { 161 | FirebaseMessaging fm = FirebaseMessaging.getInstance(); 162 | RemoteMessage.Builder remoteMessage = new RemoteMessage.Builder(senderId); 163 | remoteMessage.setMessageId(messageId); 164 | remoteMessage.setMessageType(messageType); 165 | ReadableMapKeySetIterator iterator = params.keySetIterator(); 166 | while (iterator.hasNextKey()) { 167 | String key = iterator.nextKey(); 168 | ReadableType type = params.getType(key); 169 | if (type == ReadableType.String) { 170 | remoteMessage.addData(key, params.getString(key)); 171 | Log.d(TAG, "Firebase send: " + key); 172 | Log.d(TAG, "Firebase send: " + params.getString(key)); 173 | } 174 | } 175 | try { 176 | fm.send(remoteMessage.build()); 177 | WritableMap res = Arguments.createMap(); 178 | res.putString("status", "success"); 179 | callback.invoke(null, res); 180 | } catch(Exception e) { 181 | Log.e(TAG, "Error sending message", e); 182 | WritableMap error = Arguments.createMap(); 183 | error.putString("code", e.toString()); 184 | error.putString("message", e.toString()); 185 | callback.invoke(error); 186 | } 187 | } 188 | 189 | private void initSendHandler() { 190 | getReactApplicationContext().registerReceiver(new BroadcastReceiver() { 191 | @Override 192 | public void onReceive(Context context, Intent intent) { 193 | WritableMap params = Arguments.createMap(); 194 | if (intent.getBooleanExtra("hasError", false)) { 195 | WritableMap error = Arguments.createMap(); 196 | error.putInt("code", intent.getIntExtra("errCode", 0)); 197 | error.putString("message", intent.getStringExtra("errorMessage")); 198 | params.putMap("err", error); 199 | } else { 200 | params.putNull("err"); 201 | } 202 | ReactContext ctx = getReactApplicationContext(); 203 | FirestackUtils.sendEvent(ctx, EVENT_NAME_SEND, params); 204 | } 205 | }, mReceiveSendIntentFilter); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack; 2 | 3 | /** 4 | * Created by nori on 2016/09/12. 5 | */ 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.util.Log; 9 | 10 | import com.google.firebase.iid.FirebaseInstanceId; 11 | import com.google.firebase.iid.FirebaseInstanceIdService; 12 | 13 | public class FirestackInstanceIdService extends FirebaseInstanceIdService { 14 | 15 | private static final String TAG = "FSInstanceIdService"; 16 | 17 | /** 18 | * 19 | */ 20 | @Override 21 | public void onTokenRefresh() { 22 | String refreshedToken = FirebaseInstanceId.getInstance().getToken(); 23 | Log.d(TAG, "Refreshed token: " + refreshedToken); 24 | 25 | 26 | // send Intent 27 | Intent i = new Intent(FirestackCloudMessaging.INTENT_NAME_TOKEN); 28 | Bundle bundle = new Bundle(); 29 | bundle.putString("token", refreshedToken); 30 | i.putExtras(bundle); 31 | sendBroadcast(i); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack; 2 | 3 | import android.content.Intent; 4 | import android.util.Log; 5 | 6 | import com.google.firebase.messaging.FirebaseMessagingService; 7 | import com.google.firebase.messaging.RemoteMessage; 8 | import com.google.firebase.messaging.SendException; 9 | 10 | public class FirestackMessagingService extends FirebaseMessagingService { 11 | 12 | private static final String TAG = "FSMessagingService"; 13 | 14 | @Override 15 | public void onMessageReceived(RemoteMessage remoteMessage) { 16 | Log.d(TAG, "Remote message received"); 17 | // debug 18 | Log.d(TAG, "From: " + remoteMessage.getFrom()); 19 | if (remoteMessage.getData().size() > 0) { 20 | Log.d(TAG, "Message data payload: " + remoteMessage.getData()); 21 | } 22 | if (remoteMessage.getNotification() != null) { 23 | Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody()); 24 | } 25 | if (remoteMessage.getNotification() != null) { 26 | 27 | } 28 | Intent i = new Intent(FirestackCloudMessaging.INTENT_NAME_NOTIFICATION); 29 | i.putExtra("data", remoteMessage); 30 | sendOrderedBroadcast(i, null); 31 | 32 | } 33 | 34 | @Override 35 | public void onMessageSent(String msgId) { 36 | // Called when an upstream message has been successfully sent to the GCM connection server. 37 | Log.d(TAG, "upstream message has been successfully sent"); 38 | Intent i = new Intent(FirestackCloudMessaging.INTENT_NAME_SEND); 39 | i.putExtra("msgId", msgId); 40 | sendOrderedBroadcast(i, null); 41 | } 42 | 43 | @Override 44 | public void onSendError(String msgId, Exception exception) { 45 | // Called when there was an error sending an upstream message. 46 | Log.d(TAG, "error sending an upstream message"); 47 | Intent i = new Intent(FirestackCloudMessaging.INTENT_NAME_SEND); 48 | i.putExtra("msgId", msgId); 49 | i.putExtra("hasError", true); 50 | SendException sendException = (SendException) exception; 51 | i.putExtra("errorCode", sendException.getErrorCode()); 52 | switch(sendException.getErrorCode()){ 53 | case SendException.ERROR_INVALID_PARAMETERS: 54 | i.putExtra("errorMessage", "Message was sent with invalid parameters."); 55 | break; 56 | case SendException.ERROR_SIZE: 57 | i.putExtra("errorMessage", "Message exceeded the maximum payload size."); 58 | break; 59 | case SendException.ERROR_TOO_MANY_MESSAGES: 60 | i.putExtra("errorMessage", "App has too many pending messages so this one was dropped."); 61 | break; 62 | case SendException.ERROR_TTL_EXCEEDED: 63 | i.putExtra("errorMessage", "Message time to live (TTL) was exceeded before the message could be sent."); 64 | break; 65 | case SendException.ERROR_UNKNOWN: 66 | default: 67 | i.putExtra("errorMessage", "Unknown error."); 68 | break; 69 | } 70 | sendOrderedBroadcast(i, null); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/FirestackModule.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | import java.util.Map; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | 9 | import com.facebook.react.bridge.Arguments; 10 | import com.facebook.react.bridge.LifecycleEventListener; 11 | import com.facebook.react.bridge.ReactApplicationContext; 12 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 13 | import com.facebook.react.bridge.ReactMethod; 14 | import com.facebook.react.bridge.Callback; 15 | import com.facebook.react.bridge.WritableMap; 16 | import com.facebook.react.bridge.ReadableMap; 17 | import com.facebook.react.modules.core.DeviceEventManagerModule; 18 | import com.facebook.react.bridge.ReactContext; 19 | 20 | import com.google.android.gms.tasks.OnCompleteListener; 21 | import com.google.android.gms.tasks.OnFailureListener; 22 | import com.google.android.gms.tasks.Task; 23 | import com.google.firebase.FirebaseApp; 24 | import com.google.firebase.FirebaseOptions; 25 | import com.google.firebase.database.ServerValue; 26 | 27 | interface KeySetterFn { 28 | String setKeyOrDefault(String a, String b); 29 | } 30 | 31 | class FirestackModule extends ReactContextBaseJavaModule implements LifecycleEventListener { 32 | private static final String TAG = "Firestack"; 33 | private Context context; 34 | private ReactContext mReactContext; 35 | private FirebaseApp app; 36 | 37 | public FirestackModule(ReactApplicationContext reactContext, Context context) { 38 | super(reactContext); 39 | this.context = context; 40 | mReactContext = reactContext; 41 | 42 | Log.d(TAG, "New instance"); 43 | } 44 | 45 | @Override 46 | public String getName() { 47 | return TAG; 48 | } 49 | 50 | @ReactMethod 51 | public void configureWithOptions(final ReadableMap params, @Nullable final Callback onComplete) { 52 | Log.i(TAG, "configureWithOptions"); 53 | 54 | FirebaseOptions.Builder builder = new FirebaseOptions.Builder(); 55 | FirebaseOptions defaultOptions = FirebaseOptions.fromResource(this.context); 56 | 57 | if (defaultOptions == null) { 58 | defaultOptions = new FirebaseOptions.Builder().build(); 59 | } 60 | 61 | KeySetterFn fn = new KeySetterFn() { 62 | public String setKeyOrDefault( 63 | final String key, 64 | final String defaultValue) { 65 | if (params.hasKey(key)) { 66 | // User-set key 67 | final String val = params.getString(key); 68 | Log.d(TAG, "Setting " + key + " from params to: " + val); 69 | return val; 70 | } else if (defaultValue != null && !defaultValue.equals("")) { 71 | Log.d(TAG, "Setting " + key + " from params to: " + defaultValue); 72 | return defaultValue; 73 | } else { 74 | return null; 75 | } 76 | } 77 | }; 78 | 79 | String val = fn.setKeyOrDefault("applicationId", 80 | defaultOptions.getApplicationId()); 81 | if (val != null) { 82 | builder.setApplicationId(val); 83 | } 84 | 85 | val = fn.setKeyOrDefault("apiKey", 86 | defaultOptions.getApiKey()); 87 | if (val != null) { 88 | builder.setApiKey(val); 89 | } 90 | 91 | val = fn.setKeyOrDefault("gcmSenderID", 92 | defaultOptions.getGcmSenderId()); 93 | if (val != null) { 94 | builder.setGcmSenderId(val); 95 | } 96 | 97 | val = fn.setKeyOrDefault("storageBucket", 98 | defaultOptions.getStorageBucket()); 99 | if (val != null) { 100 | builder.setStorageBucket(val); 101 | } 102 | 103 | val = fn.setKeyOrDefault("databaseURL", 104 | defaultOptions.getDatabaseUrl()); 105 | if (val != null) { 106 | builder.setDatabaseUrl(val); 107 | } 108 | 109 | val = fn.setKeyOrDefault("databaseUrl", 110 | defaultOptions.getDatabaseUrl()); 111 | if (val != null) { 112 | builder.setDatabaseUrl(val); 113 | } 114 | 115 | val = fn.setKeyOrDefault("clientId", 116 | defaultOptions.getApplicationId()); 117 | if (val != null) { 118 | builder.setApplicationId(val); 119 | } 120 | 121 | 122 | // if (params.hasKey("applicationId")) { 123 | // final String applicationId = params.getString("applicationId"); 124 | // Log.d(TAG, "Setting applicationId from params " + applicationId); 125 | // builder.setApplicationId(applicationId); 126 | // } 127 | // if (params.hasKey("apiKey")) { 128 | // final String apiKey = params.getString("apiKey"); 129 | // Log.d(TAG, "Setting API key from params " + apiKey); 130 | // builder.setApiKey(apiKey); 131 | // } 132 | // if (params.hasKey("APIKey")) { 133 | // final String apiKey = params.getString("APIKey"); 134 | // Log.d(TAG, "Setting API key from params " + apiKey); 135 | // builder.setApiKey(apiKey); 136 | // } 137 | // if (params.hasKey("gcmSenderID")) { 138 | // final String gcmSenderID = params.getString("gcmSenderID"); 139 | // Log.d(TAG, "Setting gcmSenderID from params " + gcmSenderID ); 140 | // builder.setGcmSenderId(gcmSenderID); 141 | // } 142 | // if (params.hasKey("storageBucket")) { 143 | // final String storageBucket = params.getString("storageBucket"); 144 | // Log.d(TAG, "Setting storageBucket from params " + storageBucket); 145 | // builder.setStorageBucket(storageBucket); 146 | // } 147 | // if (params.hasKey("databaseURL")) { 148 | // final String databaseURL = params.getString("databaseURL"); 149 | // Log.d(TAG, "Setting databaseURL from params " + databaseURL); 150 | // builder.setDatabaseUrl(databaseURL); 151 | // } 152 | // if (params.hasKey("clientID")) { 153 | // final String clientID = params.getString("clientID"); 154 | // Log.d(TAG, "Setting clientID from params " + clientID); 155 | // builder.setApplicationId(clientID); 156 | // } 157 | 158 | try { 159 | Log.i(TAG, "Configuring app"); 160 | if (app == null) { 161 | app = FirebaseApp.initializeApp(this.context, builder.build()); 162 | } 163 | Log.i(TAG, "Configured"); 164 | 165 | WritableMap resp = Arguments.createMap(); 166 | resp.putString("msg", "success"); 167 | onComplete.invoke(null, resp); 168 | } 169 | catch (Exception ex){ 170 | Log.e(TAG, "ERROR configureWithOptions"); 171 | Log.e(TAG, ex.getMessage()); 172 | 173 | WritableMap resp = Arguments.createMap(); 174 | resp.putString("msg", ex.getMessage()); 175 | 176 | onComplete.invoke(resp); 177 | } 178 | } 179 | 180 | @ReactMethod 181 | public void serverValue(@Nullable final Callback onComplete) { 182 | WritableMap timestampMap = Arguments.createMap(); 183 | for (Map.Entry entry : ServerValue.TIMESTAMP.entrySet()) { 184 | timestampMap.putString(entry.getKey(), entry.getValue()); 185 | } 186 | 187 | WritableMap map = Arguments.createMap(); 188 | map.putMap("TIMESTAMP", timestampMap); 189 | onComplete.invoke(null, map); 190 | } 191 | 192 | // Internal helpers 193 | @Override 194 | public void onHostResume() { 195 | WritableMap params = Arguments.createMap(); 196 | params.putBoolean("isForeground", true); 197 | FirestackUtils.sendEvent(mReactContext, "FirestackAppState", params); 198 | } 199 | 200 | @Override 201 | public void onHostPause() { 202 | WritableMap params = Arguments.createMap(); 203 | params.putBoolean("isForeground", false); 204 | FirestackUtils.sendEvent(mReactContext, "FirestackAppState", params); 205 | } 206 | 207 | @Override 208 | public void onHostDestroy() { 209 | 210 | } 211 | } -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/FirestackPackage.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack; 2 | 3 | import android.content.Context; 4 | 5 | import com.facebook.react.ReactPackage; 6 | import com.facebook.react.bridge.JavaScriptModule; 7 | import com.facebook.react.bridge.NativeModule; 8 | import com.facebook.react.bridge.ReactApplicationContext; 9 | import com.facebook.react.uimanager.ViewManager; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Collections; 13 | import java.util.List; 14 | 15 | public class FirestackPackage implements ReactPackage { 16 | private Context mContext; 17 | 18 | public FirestackPackage() { 19 | } 20 | /** 21 | * @param reactContext react application context that can be used to create modules 22 | * @return list of native modules to register with the newly created catalyst instance 23 | */ 24 | @Override 25 | public List createNativeModules(ReactApplicationContext reactContext) { 26 | List modules = new ArrayList<>(); 27 | 28 | modules.add(new FirestackModule(reactContext, reactContext.getBaseContext())); 29 | modules.add(new FirestackAuthModule(reactContext)); 30 | modules.add(new FirestackDatabaseModule(reactContext)); 31 | modules.add(new FirestackAnalyticsModule(reactContext)); 32 | modules.add(new FirestackStorage(reactContext)); 33 | // modules.add(new FirestackCloudMessaging(reactContext)); 34 | return modules; 35 | } 36 | 37 | /** 38 | * @return list of JS modules to register with the newly created catalyst instance. 39 | *

40 | * IMPORTANT: Note that only modules that needs to be accessible from the native code should be 41 | * listed here. Also listing a native module here doesn't imply that the JS implementation of it 42 | * will be automatically included in the JS bundle. 43 | */ 44 | @Override 45 | public List> createJSModules() { 46 | return Collections.emptyList(); 47 | } 48 | 49 | /** 50 | * @param reactContext 51 | * @return a list of view managers that should be registered with {@link UIManagerModule} 52 | */ 53 | @Override 54 | public List createViewManagers(ReactApplicationContext reactContext) { 55 | return Collections.emptyList(); 56 | } 57 | } -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/FirestackStorage.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack; 2 | 3 | import android.os.Environment; 4 | import android.os.StatFs; 5 | import android.content.Context; 6 | import android.util.Log; 7 | import java.util.Map; 8 | import java.util.HashMap; 9 | import java.io.File; 10 | import java.io.FileInputStream; 11 | import java.io.InputStream; 12 | import java.io.FileNotFoundException; 13 | 14 | import android.net.Uri; 15 | import android.provider.MediaStore; 16 | import android.database.Cursor; 17 | import android.support.annotation.NonNull; 18 | import android.support.annotation.Nullable; 19 | 20 | import com.facebook.react.bridge.Arguments; 21 | import com.facebook.react.bridge.ReactApplicationContext; 22 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 23 | import com.facebook.react.bridge.ReactMethod; 24 | import com.facebook.react.bridge.Callback; 25 | import com.facebook.react.bridge.WritableMap; 26 | import com.facebook.react.bridge.ReadableMap; 27 | import com.facebook.react.modules.core.DeviceEventManagerModule; 28 | import com.facebook.react.bridge.ReactContext; 29 | 30 | import com.google.android.gms.tasks.OnCompleteListener; 31 | import com.google.android.gms.tasks.OnFailureListener; 32 | import com.google.android.gms.tasks.OnSuccessListener; 33 | import com.google.android.gms.tasks.Task; 34 | 35 | import com.google.firebase.storage.OnProgressListener; 36 | import com.google.firebase.storage.OnPausedListener; 37 | 38 | import com.google.firebase.FirebaseApp; 39 | 40 | import com.google.firebase.storage.FirebaseStorage; 41 | import com.google.firebase.storage.UploadTask; 42 | 43 | import com.google.firebase.storage.StorageMetadata; 44 | import com.google.firebase.storage.StorageReference; 45 | 46 | class FirestackStorage extends ReactContextBaseJavaModule { 47 | 48 | private static final String TAG = "FirestackStorage"; 49 | private static final String DocumentDirectoryPath = "DOCUMENT_DIRECTORY_PATH"; 50 | private static final String ExternalDirectoryPath = "EXTERNAL_DIRECTORY_PATH"; 51 | private static final String ExternalStorageDirectoryPath = "EXTERNAL_STORAGE_DIRECTORY_PATH"; 52 | private static final String PicturesDirectoryPath = "PICTURES_DIRECTORY_PATH"; 53 | private static final String TemporaryDirectoryPath = "TEMPORARY_DIRECTORY_PATH"; 54 | private static final String CachesDirectoryPath = "CACHES_DIRECTORY_PATH"; 55 | private static final String DocumentDirectory = "DOCUMENT_DIRECTORY_PATH"; 56 | private static final String BundlePath = "MAIN_BUNDLE_PATH"; 57 | 58 | private static final String FileTypeRegular = "FILETYPE_REGULAR"; 59 | private static final String FileTypeDirectory = "FILETYPE_DIRECTORY"; 60 | 61 | 62 | private Context context; 63 | private ReactContext mReactContext; 64 | private FirebaseApp app; 65 | 66 | public FirestackStorage(ReactApplicationContext reactContext) { 67 | super(reactContext); 68 | 69 | Log.d(TAG, "Attaching FirestackStorage"); 70 | this.context = reactContext; 71 | mReactContext = reactContext; 72 | 73 | Log.d(TAG, "New instance"); 74 | } 75 | 76 | @Override 77 | public String getName() { 78 | return TAG; 79 | } 80 | 81 | @ReactMethod 82 | public void downloadUrl(final String javascriptStorageBucket, 83 | final String path, 84 | final Callback callback) { 85 | FirebaseStorage storage = FirebaseStorage.getInstance(); 86 | String storageBucket = storage.getApp().getOptions().getStorageBucket(); 87 | String storageUrl = "gs://"+storageBucket; 88 | Log.d(TAG, "Storage url " + storageUrl + path); 89 | final StorageReference storageRef = storage.getReferenceFromUrl(storageUrl); 90 | final StorageReference fileRef = storageRef.child(path); 91 | 92 | Task downloadTask = fileRef.getDownloadUrl(); 93 | downloadTask.addOnSuccessListener(new OnSuccessListener() { 94 | @Override 95 | public void onSuccess(Uri uri) { 96 | final WritableMap res = Arguments.createMap(); 97 | 98 | res.putString("status", "success"); 99 | res.putString("bucket", storageRef.getBucket()); 100 | res.putString("fullPath", uri.toString()); 101 | res.putString("path", uri.getPath()); 102 | res.putString("url", uri.toString()); 103 | 104 | fileRef.getMetadata() 105 | .addOnSuccessListener(new OnSuccessListener() { 106 | @Override 107 | public void onSuccess(final StorageMetadata storageMetadata) { 108 | Log.d(TAG, "getMetadata success " + storageMetadata); 109 | res.putString("name", storageMetadata.getName()); 110 | 111 | WritableMap metadata = Arguments.createMap(); 112 | metadata.putString("getBucket", storageMetadata.getBucket()); 113 | metadata.putString("getName", storageMetadata.getName()); 114 | metadata.putDouble("sizeBytes", storageMetadata.getSizeBytes()); 115 | metadata.putDouble("created_at", storageMetadata.getCreationTimeMillis()); 116 | metadata.putDouble("updated_at", storageMetadata.getUpdatedTimeMillis()); 117 | metadata.putString("md5hash", storageMetadata.getMd5Hash()); 118 | metadata.putString("encoding", storageMetadata.getContentEncoding()); 119 | res.putString("url", storageMetadata.getDownloadUrl().toString()); 120 | 121 | res.putMap("metadata", metadata); 122 | callback.invoke(null, res); 123 | } 124 | }).addOnFailureListener(new OnFailureListener() { 125 | @Override 126 | public void onFailure(@NonNull Exception exception) { 127 | Log.e(TAG, "Failure in download " + exception); 128 | callback.invoke(makeErrorPayload(1, exception)); 129 | } 130 | }); 131 | 132 | } 133 | }).addOnFailureListener(new OnFailureListener() { 134 | @Override 135 | public void onFailure(@NonNull Exception exception) { 136 | Log.e(TAG, "Failed to download file " + exception.getMessage()); 137 | 138 | WritableMap err = Arguments.createMap(); 139 | err.putString("status", "error"); 140 | err.putString("description", exception.getLocalizedMessage()); 141 | 142 | callback.invoke(err); 143 | } 144 | }); 145 | } 146 | 147 | // STORAGE 148 | @ReactMethod 149 | public void uploadFile(final String urlStr, final String name, final String filepath, final ReadableMap metadata, final Callback callback) { 150 | FirebaseStorage storage = FirebaseStorage.getInstance(); 151 | 152 | StorageReference storageRef = urlStr!=null ? storage.getReferenceFromUrl(urlStr) : storage.getReference(); 153 | StorageReference fileRef = storageRef.child(name); 154 | 155 | Log.i(TAG, "From file: " + filepath + " to " + urlStr + " with name " + name); 156 | try { 157 | // InputStream stream = new FileInputStream(new File(filepath)); 158 | Uri file = Uri.fromFile(new File(filepath)); 159 | 160 | StorageMetadata.Builder metadataBuilder = new StorageMetadata.Builder(); 161 | Map m = FirestackUtils.recursivelyDeconstructReadableMap(metadata); 162 | 163 | StorageMetadata md = metadataBuilder.build(); 164 | UploadTask uploadTask = fileRef.putFile(file, md); 165 | // UploadTask uploadTask = fileRef.putStream(stream, md); 166 | 167 | // Register observers to listen for when the download is done or if it fails 168 | uploadTask.addOnFailureListener(new OnFailureListener() { 169 | @Override 170 | public void onFailure(@NonNull Exception exception) { 171 | // Handle unsuccessful uploads 172 | Log.e(TAG, "Failed to upload file " + exception.getMessage()); 173 | 174 | WritableMap err = Arguments.createMap(); 175 | err.putString("description", exception.getLocalizedMessage()); 176 | 177 | callback.invoke(err); 178 | } 179 | }).addOnSuccessListener(new OnSuccessListener() { 180 | @Override 181 | public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { 182 | Log.d(TAG, "Successfully uploaded file " + taskSnapshot); 183 | // taskSnapshot.getMetadata() contains file metadata such as size, content-type, and download URL. 184 | WritableMap resp = getDownloadData(taskSnapshot); 185 | // NSDictionary *props = @{ 186 | // @"fullPath": ref.fullPath, 187 | // @"bucket": ref.bucket, 188 | // @"name": ref.name, 189 | // @"metadata": [snapshot.metadata dictionaryRepresentation] 190 | // }; 191 | 192 | callback.invoke(null, resp); 193 | } 194 | }) 195 | .addOnProgressListener(new OnProgressListener() { 196 | @Override 197 | public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { 198 | double totalBytes = taskSnapshot.getTotalByteCount(); 199 | double bytesTransferred = taskSnapshot.getBytesTransferred(); 200 | double progress = (100.0 * bytesTransferred) / totalBytes; 201 | 202 | System.out.println("Transferred " + bytesTransferred + "/" + totalBytes + "("+progress + "% complete)"); 203 | 204 | if (progress >= 0) { 205 | WritableMap data = Arguments.createMap(); 206 | data.putString("eventName", "upload_progress"); 207 | data.putDouble("progress", progress); 208 | FirestackUtils.sendEvent(mReactContext, "upload_progress", data); 209 | } 210 | } 211 | }).addOnPausedListener(new OnPausedListener() { 212 | @Override 213 | public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { 214 | System.out.println("Upload is paused"); 215 | StorageMetadata d = taskSnapshot.getMetadata(); 216 | String bucket = d.getBucket(); 217 | WritableMap data = Arguments.createMap(); 218 | data.putString("eventName", "upload_paused"); 219 | data.putString("ref", bucket); 220 | FirestackUtils.sendEvent(mReactContext, "upload_paused", data); 221 | } 222 | }); 223 | } 224 | catch (Exception ex) { 225 | callback.invoke(makeErrorPayload(2, ex)); 226 | } 227 | } 228 | 229 | @ReactMethod 230 | public void getRealPathFromURI(final String uri, final Callback callback) { 231 | try { 232 | Context context = getReactApplicationContext(); 233 | String [] proj = {MediaStore.Images.Media.DATA}; 234 | Cursor cursor = context.getContentResolver().query(Uri.parse(uri), proj, null, null, null); 235 | int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); 236 | cursor.moveToFirst(); 237 | String path = cursor.getString(column_index); 238 | cursor.close(); 239 | 240 | callback.invoke(null, path); 241 | } catch (Exception ex) { 242 | ex.printStackTrace(); 243 | callback.invoke(makeErrorPayload(1, ex)); 244 | } 245 | } 246 | 247 | private WritableMap getDownloadData(final UploadTask.TaskSnapshot taskSnapshot) { 248 | Uri downloadUrl = taskSnapshot.getDownloadUrl(); 249 | StorageMetadata d = taskSnapshot.getMetadata(); 250 | 251 | WritableMap resp = Arguments.createMap(); 252 | resp.putString("downloadUrl", downloadUrl.toString()); 253 | resp.putString("fullPath", d.getPath()); 254 | resp.putString("bucket", d.getBucket()); 255 | resp.putString("name", d.getName()); 256 | 257 | WritableMap metadataObj = Arguments.createMap(); 258 | metadataObj.putString("cacheControl", d.getCacheControl()); 259 | metadataObj.putString("contentDisposition", d.getContentDisposition()); 260 | metadataObj.putString("contentType", d.getContentType()); 261 | resp.putMap("metadata", metadataObj); 262 | 263 | return resp; 264 | } 265 | 266 | private WritableMap makeErrorPayload(double code, Exception ex) { 267 | WritableMap error = Arguments.createMap(); 268 | error.putDouble("code", code); 269 | error.putString("message", ex.getMessage()); 270 | return error; 271 | } 272 | 273 | // Comes almost directory from react-native-fs 274 | @Override 275 | public Map getConstants() { 276 | final Map constants = new HashMap<>(); 277 | 278 | constants.put(DocumentDirectory, 0); 279 | constants.put(DocumentDirectoryPath, this.getReactApplicationContext().getFilesDir().getAbsolutePath()); 280 | constants.put(TemporaryDirectoryPath, null); 281 | constants.put(PicturesDirectoryPath, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath()); 282 | constants.put(CachesDirectoryPath, this.getReactApplicationContext().getCacheDir().getAbsolutePath()); 283 | constants.put(FileTypeRegular, 0); 284 | constants.put(FileTypeDirectory, 1); 285 | constants.put(BundlePath, null); 286 | 287 | File externalStorageDirectory = Environment.getExternalStorageDirectory(); 288 | if (externalStorageDirectory != null) { 289 | constants.put(ExternalStorageDirectoryPath, externalStorageDirectory.getAbsolutePath()); 290 | } else { 291 | constants.put(ExternalStorageDirectoryPath, null); 292 | } 293 | 294 | File externalDirectory = this.getReactApplicationContext().getExternalFilesDir(null); 295 | if (externalDirectory != null) { 296 | constants.put(ExternalDirectoryPath, externalDirectory.getAbsolutePath()); 297 | } else { 298 | constants.put(ExternalDirectoryPath, null); 299 | } 300 | 301 | return constants; 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/FirestackUtils.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack; 2 | 3 | import android.util.Log; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import com.facebook.react.bridge.ReactContext; 11 | import com.facebook.react.modules.core.DeviceEventManagerModule; 12 | import com.facebook.react.bridge.Arguments; 13 | import com.facebook.react.bridge.Callback; 14 | import com.facebook.react.bridge.WritableMap; 15 | import com.facebook.react.bridge.WritableArray; 16 | import com.facebook.react.bridge.ReadableMap; 17 | 18 | import com.facebook.react.bridge.ReadableArray; 19 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 20 | import com.facebook.react.bridge.ReadableType; 21 | import com.google.firebase.database.DataSnapshot; 22 | 23 | public class FirestackUtils { 24 | private static final String TAG = "FirestackUtils"; 25 | 26 | // TODO NOTE 27 | public static void todoNote(final String tag, final String name, final Callback callback) { 28 | Log.e(tag, "The method " + name + " has not yet been implemented."); 29 | Log.e(tag, "Feel free to contribute to finish the method in the source."); 30 | 31 | WritableMap errorMap = Arguments.createMap(); 32 | errorMap.putString("error", "unimplemented"); 33 | callback.invoke(null, errorMap); 34 | } 35 | 36 | /** 37 | * send a JS event 38 | **/ 39 | public static void sendEvent(final ReactContext context, 40 | final String eventName, 41 | final WritableMap params) { 42 | if (context.hasActiveCatalystInstance()) { 43 | context 44 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 45 | .emit(eventName, params); 46 | } else { 47 | Log.d(TAG, "Waiting for CatalystInstance before sending event"); 48 | } 49 | } 50 | 51 | // snapshot 52 | public static WritableMap dataSnapshotToMap(String name, 53 | String path, 54 | DataSnapshot dataSnapshot) { 55 | WritableMap data = Arguments.createMap(); 56 | 57 | data.putString("key", dataSnapshot.getKey()); 58 | data.putBoolean("exists", dataSnapshot.exists()); 59 | data.putBoolean("hasChildren", dataSnapshot.hasChildren()); 60 | 61 | data.putDouble("childrenCount", dataSnapshot.getChildrenCount()); 62 | if (!dataSnapshot.hasChildren()) { 63 | Object value = dataSnapshot.getValue(); 64 | String type = value!=null ? value.getClass().getName() : ""; 65 | switch (type) { 66 | case "java.lang.Boolean": 67 | data.putBoolean("value", (Boolean)value); 68 | break; 69 | case "java.lang.Long": 70 | Long longVal = (Long) value; 71 | data.putDouble("value", (double)longVal); 72 | break; 73 | case "java.lang.Double": 74 | data.putDouble("value", (Double) value); 75 | break; 76 | case "java.lang.String": 77 | data.putString("value",(String) value); 78 | break; 79 | default: 80 | data.putString("value", null); 81 | } 82 | } else{ 83 | WritableMap valueMap = FirestackUtils.castSnapshotValue(dataSnapshot); 84 | data.putMap("value", valueMap); 85 | } 86 | 87 | // Child keys 88 | WritableArray childKeys = FirestackUtils.getChildKeys(dataSnapshot); 89 | data.putArray("childKeys", childKeys); 90 | 91 | Object priority = dataSnapshot.getPriority(); 92 | if (priority == null) { 93 | data.putString("priority", null); 94 | } else { 95 | data.putString("priority", priority.toString()); 96 | } 97 | 98 | WritableMap eventMap = Arguments.createMap(); 99 | eventMap.putString("eventName", name); 100 | eventMap.putMap("snapshot", data); 101 | eventMap.putString("path", path); 102 | return eventMap; 103 | } 104 | 105 | public static Any castSnapshotValue(DataSnapshot snapshot) { 106 | if (snapshot.hasChildren()) { 107 | WritableMap data = Arguments.createMap(); 108 | for (DataSnapshot child : snapshot.getChildren()) { 109 | Any castedChild = castSnapshotValue(child); 110 | switch (castedChild.getClass().getName()) { 111 | case "java.lang.Boolean": 112 | data.putBoolean(child.getKey(), (Boolean) castedChild); 113 | break; 114 | case "java.lang.Long": 115 | Long longVal = (Long) castedChild; 116 | data.putDouble(child.getKey(), (double)longVal); 117 | break; 118 | case "java.lang.Double": 119 | data.putDouble(child.getKey(), (Double) castedChild); 120 | break; 121 | case "java.lang.String": 122 | data.putString(child.getKey(), (String) castedChild); 123 | break; 124 | case "com.facebook.react.bridge.WritableNativeMap": 125 | data.putMap(child.getKey(), (WritableMap) castedChild); 126 | break; 127 | default: 128 | Log.w(TAG, "Invalid type: " + castedChild.getClass().getName()); 129 | break; 130 | } 131 | } 132 | return (Any) data; 133 | } else { 134 | if (snapshot.getValue() != null) { 135 | String type = snapshot.getValue().getClass().getName(); 136 | switch (type) { 137 | case "java.lang.Boolean": 138 | return (Any)(snapshot.getValue()); 139 | case "java.lang.Long": 140 | return (Any)(snapshot.getValue()); 141 | case "java.lang.Double": 142 | return (Any)(snapshot.getValue()); 143 | case "java.lang.String": 144 | return (Any)(snapshot.getValue()); 145 | default: 146 | Log.w(TAG, "Invalid type: "+type); 147 | return (Any) null; 148 | } 149 | } 150 | return (Any) null; 151 | } 152 | } 153 | 154 | public static WritableArray getChildKeys(DataSnapshot snapshot) { 155 | WritableArray childKeys = Arguments.createArray(); 156 | 157 | if (snapshot.hasChildren()) { 158 | for (DataSnapshot child : snapshot.getChildren()) { 159 | childKeys.pushString(child.getKey()); 160 | } 161 | } 162 | 163 | return childKeys; 164 | } 165 | 166 | public static Map recursivelyDeconstructReadableMap(ReadableMap readableMap) { 167 | ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); 168 | Map deconstructedMap = new HashMap<>(); 169 | while (iterator.hasNextKey()) { 170 | String key = iterator.nextKey(); 171 | ReadableType type = readableMap.getType(key); 172 | switch (type) { 173 | case Null: 174 | deconstructedMap.put(key, null); 175 | break; 176 | case Boolean: 177 | deconstructedMap.put(key, readableMap.getBoolean(key)); 178 | break; 179 | case Number: 180 | deconstructedMap.put(key, readableMap.getDouble(key)); 181 | break; 182 | case String: 183 | deconstructedMap.put(key, readableMap.getString(key)); 184 | break; 185 | case Map: 186 | deconstructedMap.put(key, FirestackUtils.recursivelyDeconstructReadableMap(readableMap.getMap(key))); 187 | break; 188 | case Array: 189 | deconstructedMap.put(key, FirestackUtils.recursivelyDeconstructReadableArray(readableMap.getArray(key))); 190 | break; 191 | default: 192 | throw new IllegalArgumentException("Could not convert object with key: " + key + "."); 193 | } 194 | 195 | } 196 | return deconstructedMap; 197 | } 198 | 199 | public static List recursivelyDeconstructReadableArray(ReadableArray readableArray) { 200 | List deconstructedList = new ArrayList<>(readableArray.size()); 201 | for (int i = 0; i < readableArray.size(); i++) { 202 | ReadableType indexType = readableArray.getType(i); 203 | switch(indexType) { 204 | case Null: 205 | deconstructedList.add(i, null); 206 | break; 207 | case Boolean: 208 | deconstructedList.add(i, readableArray.getBoolean(i)); 209 | break; 210 | case Number: 211 | deconstructedList.add(i, readableArray.getDouble(i)); 212 | break; 213 | case String: 214 | deconstructedList.add(i, readableArray.getString(i)); 215 | break; 216 | case Map: 217 | deconstructedList.add(i, FirestackUtils.recursivelyDeconstructReadableMap(readableArray.getMap(i))); 218 | break; 219 | case Array: 220 | deconstructedList.add(i, FirestackUtils.recursivelyDeconstructReadableArray(readableArray.getArray(i))); 221 | break; 222 | default: 223 | throw new IllegalArgumentException("Could not convert object at index " + i + "."); 224 | } 225 | } 226 | return deconstructedList; 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /bin/cocoapods.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## https://github.com/auth0/react-native-lock/blob/master/bin/cocoapods.sh 4 | 5 | ios_dir=`pwd`/ios 6 | if [ -d ios_dir ] 7 | then 8 | exit 0 9 | fi 10 | 11 | podfile="$ios_dir/Podfile" 12 | template=`pwd`/node_modules/react-native-firestack/ios/Podfile.template 13 | 14 | echo "Checking Podfile in iOS project ($podfile)" 15 | 16 | if [ -f $podfile ] 17 | then 18 | echo "" 19 | echo "Found an existing Podfile, Do you want to override it? [N/y]" 20 | read generate_env_file 21 | 22 | if [ "$generate_env_file" != "y" ] 23 | then 24 | 25 | # pod outdated | grep "The following pod updates are available:" 26 | # status=$? 27 | # if [ $status -eq 0 ]; then 28 | # echo "From what I can tell, there look to be updates available..." 29 | # echo "Do you want to update your cocoapods? [N/y]" 30 | # read update_pods 31 | # if [ "$update_pods" != "y" ] 32 | # then 33 | # pod update --project-directory=ios 34 | # exit 0 35 | # else 36 | # exit 0 37 | # fi 38 | # fi 39 | 40 | echo "Add the following pods": 41 | echo "" 42 | echo "" 43 | cat $template 44 | echo "" 45 | echo "" 46 | echo "and run 'pod install' to install Firestack for iOS" 47 | exit 0 48 | fi 49 | 50 | rm -f $podfile 51 | rm -f "$podfile.lock" 52 | fi 53 | 54 | echo "Adding Podfile to iOS project" 55 | 56 | cd ios 57 | pod init >/dev/null 2>&1 58 | cat $template >> $podfile 59 | cd .. 60 | 61 | echo "Installing Pods" 62 | 63 | pod install --project-directory=ios -------------------------------------------------------------------------------- /bin/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## https://github.com/auth0/react-native-lock/blob/master/bin/prepare.sh 4 | 5 | echo "Preparing to link react-native-firestack for iOS" 6 | 7 | echo "Checking CocoaPods..." 8 | has_cocoapods=`which pod >/dev/null 2>&1` 9 | if [ -z "$has_cocoapods" ] 10 | then 11 | echo "CocoaPods already installed" 12 | else 13 | echo "Installing CocoaPods..." 14 | gem install cocoapods 15 | fi -------------------------------------------------------------------------------- /firestack.android.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @providesModule Firestack 3 | * @flow 4 | */ 5 | import Firestack from './lib/firestack' 6 | 7 | export default Firestack -------------------------------------------------------------------------------- /firestack.ios.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @providesModule Firestack 3 | * @flow 4 | */ 5 | import Firestack from './lib/firestack' 6 | 7 | export default Firestack 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import Firestack from './firestack' 2 | export { FirestackModule } from './lib/firestackModule' 3 | 4 | export default Firestack -------------------------------------------------------------------------------- /ios/Firestack.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D90882D61D89C18C00FB6742 /* FirestackCloudMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = D90882D51D89C18C00FB6742 /* FirestackCloudMessaging.m */; }; 11 | D950369E1D19C77400F7094D /* Firestack.m in Sources */ = {isa = PBXBuildFile; fileRef = D950369D1D19C77400F7094D /* Firestack.m */; }; 12 | D962903F1D6D15B00099A3EC /* FirestackErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = D962903E1D6D15B00099A3EC /* FirestackErrors.m */; }; 13 | D96290451D6D16100099A3EC /* FirestackAnalytics.m in Sources */ = {isa = PBXBuildFile; fileRef = D96290441D6D16100099A3EC /* FirestackAnalytics.m */; }; 14 | D96290851D6D28B80099A3EC /* FirestackDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = D96290841D6D28B80099A3EC /* FirestackDatabase.m */; }; 15 | D9D62E7C1D6D86FD003D826D /* FirestackStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = D9D62E7B1D6D86FD003D826D /* FirestackStorage.m */; }; 16 | D9D62E801D6D8717003D826D /* FirestackAuth.m in Sources */ = {isa = PBXBuildFile; fileRef = D9D62E7F1D6D8717003D826D /* FirestackAuth.m */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 16; 25 | files = ( 26 | ); 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 134814201AA4EA6300B7C361 /* libFirestack.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libFirestack.a; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | D90882D41D89C18C00FB6742 /* FirestackCloudMessaging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FirestackCloudMessaging.h; path = Firestack/FirestackCloudMessaging.h; sourceTree = ""; }; 34 | D90882D51D89C18C00FB6742 /* FirestackCloudMessaging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FirestackCloudMessaging.m; path = Firestack/FirestackCloudMessaging.m; sourceTree = ""; }; 35 | D950369C1D19C77400F7094D /* Firestack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Firestack.h; path = Firestack/Firestack.h; sourceTree = ""; }; 36 | D950369D1D19C77400F7094D /* Firestack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Firestack.m; path = Firestack/Firestack.m; sourceTree = ""; }; 37 | D96290391D6D152A0099A3EC /* FirestackEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FirestackEvents.h; path = Firestack/FirestackEvents.h; sourceTree = ""; }; 38 | D962903D1D6D15B00099A3EC /* FirestackErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FirestackErrors.h; path = Firestack/FirestackErrors.h; sourceTree = ""; }; 39 | D962903E1D6D15B00099A3EC /* FirestackErrors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FirestackErrors.m; path = Firestack/FirestackErrors.m; sourceTree = ""; }; 40 | D96290431D6D16100099A3EC /* FirestackAnalytics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FirestackAnalytics.h; path = Firestack/FirestackAnalytics.h; sourceTree = ""; }; 41 | D96290441D6D16100099A3EC /* FirestackAnalytics.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FirestackAnalytics.m; path = Firestack/FirestackAnalytics.m; sourceTree = ""; }; 42 | D96290831D6D28B80099A3EC /* FirestackDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FirestackDatabase.h; path = Firestack/FirestackDatabase.h; sourceTree = ""; }; 43 | D96290841D6D28B80099A3EC /* FirestackDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FirestackDatabase.m; path = Firestack/FirestackDatabase.m; sourceTree = ""; }; 44 | D9D62E7A1D6D86FD003D826D /* FirestackStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FirestackStorage.h; path = Firestack/FirestackStorage.h; sourceTree = ""; }; 45 | D9D62E7B1D6D86FD003D826D /* FirestackStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FirestackStorage.m; path = Firestack/FirestackStorage.m; sourceTree = ""; }; 46 | D9D62E7E1D6D8717003D826D /* FirestackAuth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FirestackAuth.h; path = Firestack/FirestackAuth.h; sourceTree = ""; }; 47 | D9D62E7F1D6D8717003D826D /* FirestackAuth.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FirestackAuth.m; path = Firestack/FirestackAuth.m; sourceTree = ""; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXFrameworksBuildPhase section */ 59 | 60 | /* Begin PBXGroup section */ 61 | 134814211AA4EA7D00B7C361 /* Products */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 134814201AA4EA6300B7C361 /* libFirestack.a */, 65 | ); 66 | name = Products; 67 | sourceTree = ""; 68 | }; 69 | 58B511D21A9E6C8500147676 = { 70 | isa = PBXGroup; 71 | children = ( 72 | D96290351D6D145F0099A3EC /* Modules */, 73 | D950369C1D19C77400F7094D /* Firestack.h */, 74 | D950369D1D19C77400F7094D /* Firestack.m */, 75 | 134814211AA4EA7D00B7C361 /* Products */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | D96290351D6D145F0099A3EC /* Modules */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | D90882D41D89C18C00FB6742 /* FirestackCloudMessaging.h */, 83 | D90882D51D89C18C00FB6742 /* FirestackCloudMessaging.m */, 84 | D9D62E7E1D6D8717003D826D /* FirestackAuth.h */, 85 | D9D62E7F1D6D8717003D826D /* FirestackAuth.m */, 86 | D96290391D6D152A0099A3EC /* FirestackEvents.h */, 87 | D962903D1D6D15B00099A3EC /* FirestackErrors.h */, 88 | D962903E1D6D15B00099A3EC /* FirestackErrors.m */, 89 | D96290431D6D16100099A3EC /* FirestackAnalytics.h */, 90 | D96290441D6D16100099A3EC /* FirestackAnalytics.m */, 91 | D96290831D6D28B80099A3EC /* FirestackDatabase.h */, 92 | D96290841D6D28B80099A3EC /* FirestackDatabase.m */, 93 | D9D62E7A1D6D86FD003D826D /* FirestackStorage.h */, 94 | D9D62E7B1D6D86FD003D826D /* FirestackStorage.m */, 95 | ); 96 | name = Modules; 97 | sourceTree = ""; 98 | }; 99 | /* End PBXGroup section */ 100 | 101 | /* Begin PBXNativeTarget section */ 102 | 58B511DA1A9E6C8500147676 /* Firestack */ = { 103 | isa = PBXNativeTarget; 104 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "Firestack" */; 105 | buildPhases = ( 106 | 58B511D71A9E6C8500147676 /* Sources */, 107 | 58B511D81A9E6C8500147676 /* Frameworks */, 108 | 58B511D91A9E6C8500147676 /* CopyFiles */, 109 | ); 110 | buildRules = ( 111 | ); 112 | dependencies = ( 113 | ); 114 | name = Firestack; 115 | productName = RCTDataManager; 116 | productReference = 134814201AA4EA6300B7C361 /* libFirestack.a */; 117 | productType = "com.apple.product-type.library.static"; 118 | }; 119 | /* End PBXNativeTarget section */ 120 | 121 | /* Begin PBXProject section */ 122 | 58B511D31A9E6C8500147676 /* Project object */ = { 123 | isa = PBXProject; 124 | attributes = { 125 | LastUpgradeCheck = 0610; 126 | ORGANIZATIONNAME = Facebook; 127 | TargetAttributes = { 128 | 58B511DA1A9E6C8500147676 = { 129 | CreatedOnToolsVersion = 6.1.1; 130 | }; 131 | }; 132 | }; 133 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "Firestack" */; 134 | compatibilityVersion = "Xcode 3.2"; 135 | developmentRegion = English; 136 | hasScannedForEncodings = 0; 137 | knownRegions = ( 138 | en, 139 | ); 140 | mainGroup = 58B511D21A9E6C8500147676; 141 | productRefGroup = 58B511D21A9E6C8500147676; 142 | projectDirPath = ""; 143 | projectRoot = ""; 144 | targets = ( 145 | 58B511DA1A9E6C8500147676 /* Firestack */, 146 | ); 147 | }; 148 | /* End PBXProject section */ 149 | 150 | /* Begin PBXSourcesBuildPhase section */ 151 | 58B511D71A9E6C8500147676 /* Sources */ = { 152 | isa = PBXSourcesBuildPhase; 153 | buildActionMask = 2147483647; 154 | files = ( 155 | D9D62E801D6D8717003D826D /* FirestackAuth.m in Sources */, 156 | D96290451D6D16100099A3EC /* FirestackAnalytics.m in Sources */, 157 | D9D62E7C1D6D86FD003D826D /* FirestackStorage.m in Sources */, 158 | D962903F1D6D15B00099A3EC /* FirestackErrors.m in Sources */, 159 | D950369E1D19C77400F7094D /* Firestack.m in Sources */, 160 | D90882D61D89C18C00FB6742 /* FirestackCloudMessaging.m in Sources */, 161 | D96290851D6D28B80099A3EC /* FirestackDatabase.m in Sources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXSourcesBuildPhase section */ 166 | 167 | /* Begin XCBuildConfiguration section */ 168 | 58B511ED1A9E6C8500147676 /* Debug */ = { 169 | isa = XCBuildConfiguration; 170 | buildSettings = { 171 | ALWAYS_SEARCH_USER_PATHS = NO; 172 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 173 | CLANG_CXX_LIBRARY = "libc++"; 174 | CLANG_ENABLE_MODULES = YES; 175 | CLANG_ENABLE_OBJC_ARC = YES; 176 | CLANG_WARN_BOOL_CONVERSION = YES; 177 | CLANG_WARN_CONSTANT_CONVERSION = YES; 178 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 179 | CLANG_WARN_EMPTY_BODY = YES; 180 | CLANG_WARN_ENUM_CONVERSION = YES; 181 | CLANG_WARN_INT_CONVERSION = YES; 182 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 183 | CLANG_WARN_UNREACHABLE_CODE = YES; 184 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 185 | COPY_PHASE_STRIP = NO; 186 | ENABLE_BITCODE = YES; 187 | ENABLE_STRICT_OBJC_MSGSEND = YES; 188 | GCC_C_LANGUAGE_STANDARD = gnu99; 189 | GCC_DYNAMIC_NO_PIC = NO; 190 | GCC_OPTIMIZATION_LEVEL = 0; 191 | GCC_PREPROCESSOR_DEFINITIONS = ( 192 | "DEBUG=1", 193 | "$(inherited)", 194 | ); 195 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 196 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 197 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 198 | GCC_WARN_UNDECLARED_SELECTOR = YES; 199 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 200 | GCC_WARN_UNUSED_FUNCTION = YES; 201 | GCC_WARN_UNUSED_VARIABLE = YES; 202 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 203 | MTL_ENABLE_DEBUG_INFO = YES; 204 | ONLY_ACTIVE_ARCH = YES; 205 | SDKROOT = iphoneos; 206 | USER_HEADER_SEARCH_PATHS = ""; 207 | }; 208 | name = Debug; 209 | }; 210 | 58B511EE1A9E6C8500147676 /* Release */ = { 211 | isa = XCBuildConfiguration; 212 | buildSettings = { 213 | ALWAYS_SEARCH_USER_PATHS = NO; 214 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 215 | CLANG_CXX_LIBRARY = "libc++"; 216 | CLANG_ENABLE_MODULES = YES; 217 | CLANG_ENABLE_OBJC_ARC = YES; 218 | CLANG_WARN_BOOL_CONVERSION = YES; 219 | CLANG_WARN_CONSTANT_CONVERSION = YES; 220 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 221 | CLANG_WARN_EMPTY_BODY = YES; 222 | CLANG_WARN_ENUM_CONVERSION = YES; 223 | CLANG_WARN_INT_CONVERSION = YES; 224 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 225 | CLANG_WARN_UNREACHABLE_CODE = YES; 226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 227 | COPY_PHASE_STRIP = YES; 228 | ENABLE_BITCODE = YES; 229 | ENABLE_NS_ASSERTIONS = NO; 230 | ENABLE_STRICT_OBJC_MSGSEND = YES; 231 | GCC_C_LANGUAGE_STANDARD = gnu99; 232 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 233 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 234 | GCC_WARN_UNDECLARED_SELECTOR = YES; 235 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 236 | GCC_WARN_UNUSED_FUNCTION = YES; 237 | GCC_WARN_UNUSED_VARIABLE = YES; 238 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 239 | MTL_ENABLE_DEBUG_INFO = NO; 240 | SDKROOT = iphoneos; 241 | USER_HEADER_SEARCH_PATHS = ""; 242 | VALIDATE_PRODUCT = YES; 243 | }; 244 | name = Release; 245 | }; 246 | 58B511F01A9E6C8500147676 /* Debug */ = { 247 | isa = XCBuildConfiguration; 248 | buildSettings = { 249 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; 250 | DEFINES_MODULE = NO; 251 | EMBEDDED_CONTENT_CONTAINS_SWIFT = NO; 252 | ENABLE_BITCODE = YES; 253 | FRAMEWORK_SEARCH_PATHS = ( 254 | "$(inherited)", 255 | "${BUILT_PRODUCTS_DIR}", 256 | "${PROJECT_DIR}/../../../ios/Pods/**", 257 | ); 258 | HEADER_SEARCH_PATHS = ( 259 | "$(inherited)", 260 | "$(SRCROOT)/../../React/**", 261 | "$(SRCROOT)/../../node_modules/react-native/React/**", 262 | "$(SRCROOT)/../../react-native/React/**", 263 | "${SRCROOT}/../../../ios/Pods/**", 264 | ); 265 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 266 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 267 | MACH_O_TYPE = staticlib; 268 | ONLY_ACTIVE_ARCH = YES; 269 | OTHER_LDFLAGS = "$(inherited)"; 270 | PRODUCT_NAME = Firestack; 271 | SKIP_INSTALL = YES; 272 | }; 273 | name = Debug; 274 | }; 275 | 58B511F11A9E6C8500147676 /* Release */ = { 276 | isa = XCBuildConfiguration; 277 | buildSettings = { 278 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; 279 | DEFINES_MODULE = NO; 280 | EMBEDDED_CONTENT_CONTAINS_SWIFT = NO; 281 | ENABLE_BITCODE = YES; 282 | FRAMEWORK_SEARCH_PATHS = ( 283 | "$(inherited)", 284 | "${BUILT_PRODUCTS_DIR}", 285 | "${PROJECT_DIR}/../../../ios/Pods/**", 286 | ); 287 | HEADER_SEARCH_PATHS = ( 288 | "$(inherited)", 289 | "$(SRCROOT)/../../React/**", 290 | "$(SRCROOT)/../../node_modules/react-native/React/**", 291 | "$(SRCROOT)/../../react-native/React/**", 292 | "${SRCROOT}/../../../ios/Pods/**", 293 | ); 294 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 295 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 296 | MACH_O_TYPE = staticlib; 297 | OTHER_LDFLAGS = "$(inherited)"; 298 | PRODUCT_NAME = Firestack; 299 | SKIP_INSTALL = YES; 300 | }; 301 | name = Release; 302 | }; 303 | /* End XCBuildConfiguration section */ 304 | 305 | /* Begin XCConfigurationList section */ 306 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "Firestack" */ = { 307 | isa = XCConfigurationList; 308 | buildConfigurations = ( 309 | 58B511ED1A9E6C8500147676 /* Debug */, 310 | 58B511EE1A9E6C8500147676 /* Release */, 311 | ); 312 | defaultConfigurationIsVisible = 0; 313 | defaultConfigurationName = Release; 314 | }; 315 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "Firestack" */ = { 316 | isa = XCConfigurationList; 317 | buildConfigurations = ( 318 | 58B511F01A9E6C8500147676 /* Debug */, 319 | 58B511F11A9E6C8500147676 /* Release */, 320 | ); 321 | defaultConfigurationIsVisible = 0; 322 | defaultConfigurationName = Release; 323 | }; 324 | /* End XCConfigurationList section */ 325 | }; 326 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 327 | } 328 | -------------------------------------------------------------------------------- /ios/Firestack.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Firestack.xcodeproj/xcuserdata/Cheol.xcuserdatad/xcschemes/Firestack.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /ios/Firestack.xcodeproj/xcuserdata/Cheol.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Firestack.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 58B511DA1A9E6C8500147676 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ios/Firestack.xcodeproj/xcuserdata/auser.xcuserdatad/xcschemes/Firestack.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /ios/Firestack.xcodeproj/xcuserdata/auser.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Firestack.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 58B511DA1A9E6C8500147676 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ios/Firestack/Firestack.h: -------------------------------------------------------------------------------- 1 | // 2 | // Firestack.h 3 | // Created by Ari Lerner on 5/31/16. 4 | // Copyright © 2016 Facebook. All rights reserved. 5 | // 6 | 7 | #ifndef Firestack_h 8 | #define Firestack_h 9 | 10 | #import 11 | #import "RCTBridgeModule.h" 12 | #import "RCTEventDispatcher.h" 13 | #import "RCTEventEmitter.h" 14 | 15 | @interface Firestack : RCTEventEmitter { 16 | } 17 | 18 | // + (void) registerForNotification:(NSString *) typeStr andToken:(NSData *)deviceToken; 19 | + (void) setup:(UIApplication *) application 20 | withLaunchOptions: (NSDictionary *) launchOptions; 21 | 22 | + (id) sharedInstance; 23 | 24 | - (void) debugLog:(NSString *)title 25 | msg:(NSString *)msg; 26 | 27 | - (void) sendJSEvent:(NSString *)title 28 | props:(NSDictionary *)props; 29 | 30 | 31 | @property (nonatomic) BOOL debug; 32 | @property (atomic) BOOL configured; 33 | @property (nonatomic, strong) NSDictionary *configuration; 34 | 35 | @end 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /ios/Firestack/Firestack.m: -------------------------------------------------------------------------------- 1 | // 2 | // Firestack.m 3 | // Created by Ari Lerner on 5/31/16. 4 | // Copyright © 2016 Facebook. All rights reserved. 5 | // 6 | 7 | #import "Firestack.h" 8 | #import "FirestackErrors.h" 9 | #import "FirestackEvents.h" 10 | // #import "FirestackAnalytics.h" 11 | // #import "FirestackCloudMessaging.h" 12 | 13 | static Firestack *_sharedInstance = nil; 14 | static dispatch_once_t onceToken; 15 | 16 | @implementation Firestack 17 | 18 | typedef void (^UserWithTokenResponse)(NSDictionary *, NSError *); 19 | 20 | - (void)dealloc 21 | { 22 | NSLog(@"Dealloc called on Firestack: %@", self); 23 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 24 | } 25 | 26 | // TODO: Implement 27 | + (void) setup:(UIApplication *) application 28 | withLaunchOptions: (NSDictionary *) launchOptions 29 | { 30 | NSLog(@"Called setup for firestack with application"); 31 | 32 | dispatch_once(&onceToken, ^{ 33 | [application registerForRemoteNotifications]; 34 | 35 | [[NSNotificationCenter defaultCenter] 36 | postNotificationName:kFirestackInitialized 37 | object:nil]; 38 | }); 39 | } 40 | 41 | - (id) init 42 | { 43 | self = [super init]; 44 | if (self != nil) { 45 | NSLog(@"Setting up Firestack instance"); 46 | [Firestack initializeFirestack:self]; 47 | } 48 | return self; 49 | } 50 | 51 | + (void) initializeFirestack:(Firestack *) instance 52 | { 53 | dispatch_once(&onceToken, ^{ 54 | _sharedInstance = instance; 55 | 56 | [[NSNotificationCenter defaultCenter] 57 | postNotificationName:kFirestackInitialized 58 | object:nil]; 59 | }); 60 | } 61 | 62 | 63 | + (instancetype) sharedInstance 64 | { 65 | return _sharedInstance; 66 | } 67 | 68 | - (FIRApp *) firebaseApp 69 | { 70 | return [FIRApp defaultApp]; 71 | } 72 | 73 | 74 | RCT_EXPORT_MODULE(Firestack); 75 | 76 | RCT_EXPORT_METHOD(serverValue:(RCTResponseSenderBlock) callback) 77 | { 78 | callback(@[[NSNull null], @{ 79 | @"TIMESTAMP": [FIRServerValue timestamp] 80 | }]); 81 | } 82 | 83 | RCT_EXPORT_METHOD(configureWithOptions:(NSDictionary *) opts 84 | callback:(RCTResponseSenderBlock)callback) 85 | { 86 | dispatch_async(dispatch_get_main_queue(),^{ 87 | // Are we debugging, yo? 88 | self.debug = [opts valueForKey:@"debug"] != nil ? YES : NO; 89 | NSLog(@"options passed into configureWithOptions: %@", [opts valueForKey:@"debug"]); 90 | 91 | NSDictionary *keyMapping = @{ 92 | @"GOOGLE_APP_ID": @[ 93 | @"appId", 94 | @"googleAppId", 95 | @"applicationId" 96 | ], 97 | @"BUNDLE_ID": @[ 98 | @"bundleId", 99 | @"bundleID" 100 | ], 101 | @"GCM_SENDER_ID": @[ 102 | @"gcmSenderID", 103 | @"GCMSenderID" 104 | ], 105 | @"API_KEY": @[ 106 | @"apiKey" 107 | ], 108 | @"CLIENT_ID": @[ 109 | @"clientId", 110 | @"clientID" 111 | ], 112 | @"TRACKING_ID": @[ 113 | @"trackingID", 114 | @"trackingId" 115 | ], 116 | @"ANDROID_CLIENT_ID": @[ 117 | @"applicationId", 118 | @"clientId", 119 | @"clientID", 120 | @"androidClientID", 121 | @"androidClientId" 122 | ], 123 | @"DATABASE_URL": @[ 124 | @"databaseUrl", 125 | @"databaseURL" 126 | ], 127 | @"STORAGE_BUCKET": @[ 128 | @"storageBucket" 129 | ], 130 | @"PROJECT_ID": @[ 131 | @"projectId", 132 | @"projectID" 133 | ], 134 | @"TRACKING_ID": @[ 135 | @"trackingID", 136 | @"trackingId" 137 | ], 138 | @"DEEP_LINK_SCHEME": @[ 139 | @"deepLinkScheme" 140 | ], 141 | @"MESSAGING_SENDER_ID": @[ 142 | @"messagingSenderId", 143 | @"messagingSenderID" 144 | ] 145 | }; 146 | NSArray *optionKeys = [keyMapping allKeys]; 147 | 148 | NSMutableDictionary *props; 149 | 150 | NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"]; 151 | 152 | if ([[NSFileManager defaultManager] fileExistsAtPath:plistPath]) { 153 | // If the Firebase plist is included 154 | props = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath]; 155 | } else { 156 | props = [[NSMutableDictionary alloc] initWithCapacity:[optionKeys count]]; 157 | } 158 | 159 | // Bundle ID either from options OR from the main bundle 160 | NSString *bundleID; 161 | if ([opts valueForKey:@"bundleID"]) { 162 | bundleID = [opts valueForKey:@"bundleID"]; 163 | } else { 164 | bundleID = [[NSBundle mainBundle] bundleIdentifier]; 165 | } 166 | [props setValue:bundleID forKey:@"BUNDLE_ID"]; 167 | 168 | // Prefer the user configuration options over the default options 169 | for (int i=0; i < [optionKeys count]; i++) { 170 | // Traditional for loop here 171 | @try { 172 | NSString *key = [optionKeys objectAtIndex:i]; 173 | // If the name is capitalized 174 | if ([opts valueForKey:key] != nil) { 175 | NSString *value = [opts valueForKey:key]; 176 | [props setValue:value forKey:key]; 177 | } 178 | 179 | NSArray *possibleNames = [keyMapping objectForKey:key]; 180 | 181 | for (NSString *name in possibleNames) { 182 | if ([opts valueForKey:name] != nil) { 183 | // The user passed this option in 184 | NSString *value = [opts valueForKey:name]; 185 | [props setValue:value forKey:key]; 186 | } 187 | } 188 | } 189 | @catch (NSException *err) { 190 | // Uh oh? 191 | NSLog(@"An error occurred: %@", err); 192 | } 193 | } 194 | 195 | @try { 196 | if (self.debug) { 197 | NSLog(@"props ->: %@", props); 198 | NSLog(@"GOOGLE_APP_ID: %@", [props valueForKey:@"GOOGLE_APP_ID"]); 199 | NSLog(@"BUNDLE_ID: %@", [props valueForKey:@"BUNDLE_ID"]); 200 | NSLog(@"GCM_SENDER_ID: %@", [props valueForKey:@"GCM_SENDER_ID"]); 201 | NSLog(@"API_KEY: %@", [props valueForKey:@"API_KEY"]); 202 | NSLog(@"CLIENT_ID: %@", [props valueForKey:@"CLIENT_ID"]); 203 | NSLog(@"TRACKING_ID: %@", [props valueForKey:@"TRACKING_ID"]); 204 | NSLog(@"ANDROID_CLIENT_ID: %@", [props valueForKey:@"ANDROID_CLIENT_ID"]); 205 | NSLog(@"DATABASE_URL: %@", [props valueForKey:@"DATABASE_URL"]); 206 | NSLog(@"STORAGE_BUCKET: %@", [props valueForKey:@"STORAGE_BUCKET"]); 207 | NSLog(@"DEEP_LINK_SCHEME: %@", [props valueForKey:@"DEEP_LINK_SCHEME"]); 208 | } 209 | 210 | FIROptions *finalOptions = [[FIROptions alloc] 211 | initWithGoogleAppID:[props valueForKey:@"GOOGLE_APP_ID"] 212 | bundleID:[props valueForKey:@"BUNDLE_ID"] 213 | GCMSenderID:[props valueForKey:@"GCM_SENDER_ID"] 214 | APIKey:[props valueForKey:@"API_KEY"] 215 | clientID:[props valueForKey:@"CLIENT_ID"] 216 | trackingID:[props valueForKey:@"TRACKING_ID"] 217 | androidClientID:[props valueForKey:@"ANDROID_CLIENT_ID"] 218 | databaseURL:[props valueForKey:@"DATABASE_URL"] 219 | storageBucket:[props valueForKey:@"STORAGE_BUCKET"] 220 | deepLinkURLScheme:[props valueForKey:@"DEEP_LINK_SCHEME"]]; 221 | 222 | // Save configuration option 223 | // NSDictionary *cfg = [self getConfig]; 224 | // [cfg setValuesForKeysWithDictionary:props]; 225 | 226 | // if (!self.configured) { 227 | 228 | if ([FIRApp defaultApp] == NULL) { 229 | [FIRApp configureWithOptions:finalOptions]; 230 | } 231 | [Firestack initializeFirestack:self]; 232 | callback(@[[NSNull null], props]); 233 | } 234 | @catch (NSException *exception) { 235 | NSLog(@"Exception occurred while configuring: %@", exception); 236 | [self debugLog:@"Configuring error" 237 | msg:[NSString stringWithFormat:@"An error occurred while configuring: %@", [exception debugDescription]]]; 238 | NSDictionary *errProps = @{ 239 | @"error": [exception name], 240 | @"description": [exception debugDescription] 241 | }; 242 | callback(@[errProps]); 243 | } 244 | }); 245 | } 246 | 247 | RCT_EXPORT_METHOD(configure:(RCTResponseSenderBlock)callback) 248 | { 249 | NSDictionary *props = @{}; 250 | [self configureWithOptions:props 251 | callback:callback]; 252 | } 253 | 254 | #pragma mark - Storage 255 | 256 | 257 | #pragma mark RemoteConfig 258 | 259 | // RCT_EXPORT_METHOD(setDefaultRemoteConfig:(NSDictionary *)props 260 | // callback:(RCTResponseSenderBlock) callback) 261 | // { 262 | // if (!self.remoteConfigInstance) { 263 | // // Create remote Config instance 264 | // self.remoteConfigInstance = [FIRRemoteConfig remoteConfig]; 265 | // } 266 | 267 | // [self.remoteConfigInstance setDefaults:props]; 268 | // callback(@[[NSNull null], props]); 269 | // } 270 | 271 | // RCT_EXPORT_METHOD(setDev:(RCTResponseSenderBlock) callback) 272 | // { 273 | // FIRRemoteConfigSettings *remoteConfigSettings = [[FIRRemoteConfigSettings alloc] initWithDeveloperModeEnabled:YES]; 274 | // self.remoteConfigInstance.configSettings = remoteConfigSettings; 275 | // callback(@[[NSNull null], @"ok"]); 276 | // } 277 | 278 | // RCT_EXPORT_METHOD(configValueForKey:(NSString *)name 279 | // callback:(RCTResponseSenderBlock) callback) 280 | // { 281 | // if (!self.remoteConfigInstance) { 282 | // NSDictionary *err = @{ 283 | // @"error": @"No configuration instance", 284 | // @"msg": @"No configuration instance set. Please call setDefaultRemoteConfig before using this feature" 285 | // }; 286 | // callback(@[err]); 287 | // } 288 | 289 | 290 | // FIRRemoteConfigValue *value = [self.remoteConfigInstance configValueForKey:name]; 291 | // NSString *valueStr = value.stringValue; 292 | 293 | // if (valueStr == nil) { 294 | // valueStr = @""; 295 | // } 296 | // callback(@[[NSNull null], valueStr]); 297 | // } 298 | 299 | // RCT_EXPORT_METHOD(fetchWithExpiration:(NSNumber*)expirationSeconds 300 | // callback:(RCTResponseSenderBlock) callback) 301 | // { 302 | // if (!self.remoteConfigInstance) { 303 | // NSDictionary *err = @{ 304 | // @"error": @"No configuration instance", 305 | // @"msg": @"No configuration instance set. Please call setDefaultRemoteConfig before using this feature" 306 | // }; 307 | // callback(@[err]); 308 | // } 309 | 310 | // NSTimeInterval expirationDuration = [expirationSeconds doubleValue]; 311 | 312 | // [self.remoteConfigInstance fetchWithExpirationDuration:expirationDuration completionHandler:^(FIRRemoteConfigFetchStatus status, NSError *error) { 313 | // if (status == FIRRemoteConfigFetchStatusSuccess) { 314 | // NSLog(@"Config fetched!"); 315 | // [self.remoteConfigInstance activateFetched]; 316 | // callback(@[[NSNull null], @(YES)]); 317 | // } else { 318 | // NSLog(@"Error %@", error.localizedDescription); 319 | 320 | // NSDictionary *err = @{ 321 | // @"error": @"No configuration instance", 322 | // @"msg": [error localizedDescription] 323 | // }; 324 | 325 | // callback(@[err]); 326 | // } 327 | // }]; 328 | // } 329 | 330 | #pragma mark Database 331 | 332 | #pragma mark Messaging 333 | 334 | #pragma mark Helpers 335 | 336 | - (NSDictionary *) getConfig 337 | { 338 | if (self.configuration == nil) { 339 | self.configuration = [[NSMutableDictionary alloc] initWithCapacity:20]; 340 | } 341 | return self.configuration; 342 | } 343 | 344 | - (NSDictionary *) handleFirebaseError:(NSString *) name 345 | error:(NSError *) error 346 | withUser:(FIRUser *) user 347 | { 348 | return [FirestackErrors handleFirebaseError:name 349 | error:error 350 | withUser:user]; 351 | } 352 | 353 | - (void) handleException:(NSException *)exception 354 | withCallback:(RCTResponseSenderBlock)callback 355 | { 356 | [FirestackErrors handleException:exception 357 | withCallback:callback]; 358 | } 359 | 360 | - (void) debugLog:(NSString *)title 361 | msg:(NSString *)msg 362 | { 363 | if (self.debug) { 364 | NSLog(@"%@: %@", title, msg); 365 | } 366 | } 367 | 368 | // Not sure how to get away from this... yet 369 | - (NSArray *)supportedEvents { 370 | return @[ 371 | INITIALIZED_EVENT, 372 | DEBUG_EVENT, 373 | AUTH_CHANGED_EVENT]; 374 | } 375 | 376 | - (void) sendJSEvent:(NSString *)title 377 | props:(NSDictionary *)props 378 | { 379 | @try { 380 | [self sendEventWithName:title 381 | body:props]; 382 | } 383 | @catch (NSException *err) { 384 | NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]); 385 | } 386 | } 387 | 388 | @end 389 | -------------------------------------------------------------------------------- /ios/Firestack/FirestackAnalytics.h: -------------------------------------------------------------------------------- 1 | // 2 | // FirestackAnalytics.h 3 | // Firestack 4 | // 5 | // Created by Ari Lerner on 8/23/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #ifndef FirestackAnalytics_h 10 | #define FirestackAnalytics_h 11 | 12 | #import "RCTBridgeModule.h" 13 | 14 | @interface FirestackAnalytics : NSObject { 15 | 16 | } 17 | 18 | @end 19 | 20 | #endif -------------------------------------------------------------------------------- /ios/Firestack/FirestackAnalytics.m: -------------------------------------------------------------------------------- 1 | // 2 | // FirestackAnalytics.m 3 | // Firestack 4 | // 5 | // Created by Ari Lerner on 8/23/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #import "Firestack.h" 10 | #import "FirestackEvents.h" 11 | #import "FirestackAnalytics.h" 12 | #import "Firebase.h" 13 | 14 | @implementation FirestackAnalytics 15 | 16 | - (void)dealloc 17 | { 18 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 19 | } 20 | 21 | RCT_EXPORT_MODULE(FirestackAnalytics); 22 | 23 | // Implementation 24 | RCT_EXPORT_METHOD(logEventWithName:(NSString *)name 25 | props:(NSDictionary *)props 26 | callback:(RCTResponseSenderBlock) callback) 27 | { 28 | NSString *debugMsg = [NSString stringWithFormat:@"%@: %@ with %@", 29 | @"FirestackAnalytics", name, props]; 30 | [[Firestack sharedInstance] debugLog:@"logEventWithName called" 31 | msg:debugMsg]; 32 | 33 | [FIRAnalytics logEventWithName:name parameters:props]; 34 | callback(@[[NSNull null], @YES]); 35 | } 36 | 37 | RCT_EXPORT_METHOD(setEnabled:(BOOL) enabled 38 | callback:(RCTResponseSenderBlock) callback) 39 | { 40 | [[FIRAnalyticsConfiguration sharedInstance] setAnalyticsCollectionEnabled:enabled]; 41 | callback(@[[NSNull null], @YES]); 42 | } 43 | 44 | RCT_EXPORT_METHOD(setUser: (NSString *) id 45 | props:(NSDictionary *) props 46 | callback:(RCTResponseSenderBlock) callback) 47 | { 48 | [FIRAnalytics setUserID:id]; 49 | NSMutableArray *allKeys = [[props allKeys] mutableCopy]; 50 | for (NSString *key in allKeys) { 51 | NSString *val = [props valueForKey:key]; 52 | [FIRAnalytics setUserPropertyString:val forName:key]; 53 | } 54 | 55 | callback(@[[NSNull null], @YES]); 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /ios/Firestack/FirestackAuth.h: -------------------------------------------------------------------------------- 1 | // 2 | // FirestackAuth.h 3 | // Firestack 4 | // 5 | // Created by Ari Lerner on 8/23/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #ifndef FirestackAuth_h 10 | #define FirestackAuth_h 11 | 12 | #import "Firebase.h" 13 | #import "RCTEventEmitter.h" 14 | #import "RCTBridgeModule.h" 15 | 16 | @interface FirestackAuth : RCTEventEmitter { 17 | FIRAuthStateDidChangeListenerHandle authListenerHandle; 18 | Boolean listening; 19 | } 20 | 21 | @end 22 | 23 | #endif -------------------------------------------------------------------------------- /ios/Firestack/FirestackCloudMessaging.h: -------------------------------------------------------------------------------- 1 | // 2 | // FirestackMessaging.h 3 | // Firestack 4 | // 5 | // Created by Ari Lerner on 8/23/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #ifndef FirestackCloudMessaging_h 10 | #define FirestackCloudMessaging_h 11 | 12 | #import "Firebase.h" 13 | #import "RCTEventEmitter.h" 14 | #import "RCTBridgeModule.h" 15 | #import "RCTUtils.h" 16 | 17 | @interface FirestackCloudMessaging : RCTEventEmitter { 18 | 19 | } 20 | 21 | + (void) setup:(UIApplication *)application; 22 | 23 | @end 24 | 25 | #endif -------------------------------------------------------------------------------- /ios/Firestack/FirestackCloudMessaging.m: -------------------------------------------------------------------------------- 1 | // 2 | // FirestackMessaging.m 3 | // Firestack 4 | // 5 | // Created by Ari Lerner on 8/23/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 12 | #import 13 | #endif 14 | #import "FirestackCloudMessaging.h" 15 | #import "FirestackEvents.h" 16 | // #import "RCTConvert.h" 17 | 18 | @implementation FirestackCloudMessaging 19 | 20 | // https://github.com/facebook/react-native/blob/master/Libraries/PushNotificationIOS/RCTPushNotificationManager.m 21 | static NSDictionary *RCTFormatLocalNotification(UILocalNotification *notification) 22 | { 23 | NSMutableDictionary *formattedLocalNotification = [NSMutableDictionary dictionary]; 24 | if (notification.fireDate) { 25 | NSDateFormatter *formatter = [NSDateFormatter new]; 26 | [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"]; 27 | NSString *fireDateString = [formatter stringFromDate:notification.fireDate]; 28 | formattedLocalNotification[@"fireDate"] = fireDateString; 29 | } 30 | formattedLocalNotification[@"alertAction"] = RCTNullIfNil(notification.alertAction); 31 | formattedLocalNotification[@"alertBody"] = RCTNullIfNil(notification.alertBody); 32 | formattedLocalNotification[@"applicationIconBadgeNumber"] = @(notification.applicationIconBadgeNumber); 33 | formattedLocalNotification[@"category"] = RCTNullIfNil(notification.category); 34 | formattedLocalNotification[@"soundName"] = RCTNullIfNil(notification.soundName); 35 | formattedLocalNotification[@"userInfo"] = RCTNullIfNil(RCTJSONClean(notification.userInfo)); 36 | formattedLocalNotification[@"remote"] = @NO; 37 | return formattedLocalNotification; 38 | } 39 | 40 | - (void) dealloc 41 | { 42 | [[NSNotificationCenter defaultCenter] removeObserver: self]; 43 | } 44 | 45 | + (void) setup:(UIApplication *) application 46 | { 47 | [[NSNotificationCenter defaultCenter] addObserver:self 48 | selector:@selector(connectToFirebase) 49 | name: UIApplicationDidEnterBackgroundNotification 50 | object: nil]; 51 | 52 | [[NSNotificationCenter defaultCenter] addObserver:self 53 | selector:@selector(disconnectFromFirebase) 54 | name: UIApplicationDidBecomeActiveNotification 55 | object: nil]; 56 | 57 | [[NSNotificationCenter defaultCenter] addObserver:self 58 | selector:@selector(handleRemoteNotificationReceived:) 59 | name:MESSAGING_MESSAGE_RECEIVED_REMOTE 60 | object: nil]; 61 | 62 | [[NSNotificationCenter defaultCenter] addObserver:self 63 | selector:@selector(handleLocalNotificationReceived:) 64 | name:MESSAGING_MESSAGE_RECEIVED_LOCAL 65 | object: nil]; 66 | 67 | [[NSNotificationCenter defaultCenter] addObserver:self 68 | selector:@selector(handleTokenRefresh) 69 | name:kFIRInstanceIDTokenRefreshNotification 70 | object: nil]; 71 | } 72 | 73 | #pragma mark Request permissions 74 | - (void) requestPermissions:(NSDictionary *)requestedPermissions 75 | callback:(RCTResponseSenderBlock) callback 76 | { 77 | if (SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(@"9.0")) { 78 | UIUserNotificationType allNotificationTypes = 79 | (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge); 80 | UIUserNotificationSettings *settings = 81 | [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil]; 82 | [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; 83 | } else { 84 | // iOS 10 or later 85 | #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 86 | UNAuthorizationOptions authOptions = 87 | UNAuthorizationOptionAlert 88 | | UNAuthorizationOptionSound 89 | | UNAuthorizationOptionBadge; 90 | [[UNUserNotificationCenter currentNotificationCenter] 91 | requestAuthorizationWithOptions:authOptions 92 | completionHandler:^(BOOL granted, NSError * _Nullable error) { 93 | } 94 | ]; 95 | 96 | // For iOS 10 display notification (sent via APNS) 97 | [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; 98 | // For iOS 10 data message (sent via FCM) 99 | [[FIRMessaging messaging] setRemoteMessageDelegate:self]; 100 | #endif 101 | } 102 | 103 | [[UIApplication sharedApplication] registerForRemoteNotifications]; 104 | } 105 | 106 | #pragma mark callbacks 107 | - (void) connectToFirebase 108 | { 109 | [[FIRMessaging messaging] connectWithCompletion:^(NSError *error) { 110 | NSDictionary *evt; 111 | NSString *evtName; 112 | if (error != nil) { 113 | NSLog(@"Error connecting: %@", [error debugDescription]); 114 | evtName = MESSAGING_SUBSYSTEM_ERROR; 115 | evt = @{ 116 | @"eventName": MESSAGING_SUBSYSTEM_ERROR, 117 | @"msg": [error debugDescription] 118 | }; 119 | } else { 120 | NSLog(@"Connected to Firebase messaging"); 121 | evtName = MESSAGING_SUBSYSTEM_EVENT; 122 | evt = @{ 123 | @"result": @"connected" 124 | }; 125 | [self 126 | sendJSEvent:evtName 127 | props: evt]; 128 | 129 | } 130 | }]; 131 | } 132 | 133 | - (void) disconnectFromFirebase 134 | { 135 | [[FIRMessaging messaging] disconnect]; 136 | NSLog(@"Disconnect from Firebase"); 137 | [self 138 | sendJSEvent:MESSAGING_SUBSYSTEM_EVENT 139 | props: @{ 140 | @"status": @"disconnected" 141 | }]; 142 | } 143 | 144 | - (void) handleRemoteNotificationReceived:(NSNotification *) n 145 | { 146 | NSMutableDictionary *props = [[NSMutableDictionary alloc] initWithDictionary: n.userInfo]; 147 | [self sendJSEvent:MESSAGING_MESSAGE_RECEIVED_REMOTE props: props]; 148 | } 149 | 150 | - (void) handleLocalNotificationReceived:(NSNotification *) n 151 | { 152 | NSMutableDictionary *props = [[NSMutableDictionary alloc] initWithDictionary: n.userInfo]; 153 | [self sendJSEvent:MESSAGING_MESSAGE_RECEIVED_LOCAL props: props]; 154 | } 155 | 156 | - (void) handleTokenRefresh 157 | { 158 | NSDictionary *props = @{ 159 | @"status": @"token_refreshed", 160 | @"token": [[FIRInstanceID instanceID] token] 161 | }; 162 | [self sendJSEvent:MESSAGING_TOKEN_REFRESH props: props]; 163 | } 164 | 165 | RCT_EXPORT_MODULE(FirestackCloudMessaging); 166 | 167 | 168 | RCT_EXPORT_METHOD(getToken:(RCTResponseSenderBlock)callback) 169 | { 170 | NSString *token = [[FIRInstanceID instanceID] token]; 171 | callback(@[[NSNull null], @{ 172 | @"status": @"success", 173 | @"token": token 174 | }]); 175 | } 176 | 177 | 178 | 179 | RCT_EXPORT_METHOD(sendLocal:(UILocalNotification *)notification 180 | callback:(RCTResponseSenderBlock) callback) 181 | { 182 | NSLog(@"sendLocal called with notification: %@", notification); 183 | [RCTSharedApplication() presentLocalNotificationNow:notification]; 184 | } 185 | RCT_EXPORT_METHOD(scheduleLocal:(UILocalNotification *)notification 186 | callback:(RCTResponseSenderBlock) callback) 187 | { 188 | [RCTSharedApplication() scheduleLocalNotification:notification]; 189 | } 190 | 191 | RCT_EXPORT_METHOD(cancelAllLocalNotifications) 192 | { 193 | [RCTSharedApplication() cancelAllLocalNotifications]; 194 | } 195 | 196 | RCT_EXPORT_METHOD(cancelLocalNotifications:(NSDictionary *)userInfo) 197 | { 198 | for (UILocalNotification *notification in [UIApplication sharedApplication].scheduledLocalNotifications) { 199 | __block BOOL matchesAll = YES; 200 | NSDictionary *notificationInfo = notification.userInfo; 201 | // Note: we do this with a loop instead of just `isEqualToDictionary:` 202 | // because we only require that all specified userInfo values match the 203 | // notificationInfo values - notificationInfo may contain additional values 204 | // which we don't care about. 205 | [userInfo enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { 206 | if (![notificationInfo[key] isEqual:obj]) { 207 | matchesAll = NO; 208 | *stop = YES; 209 | } 210 | }]; 211 | if (matchesAll) { 212 | [[UIApplication sharedApplication] cancelLocalNotification:notification]; 213 | } 214 | } 215 | } 216 | 217 | RCT_EXPORT_METHOD(sendRemote:(UILocalNotification *)notification 218 | callback:(RCTResponseSenderBlock) callback) 219 | { 220 | 221 | } 222 | 223 | 224 | RCT_EXPORT_METHOD(send:(NSString *) senderId 225 | messageId:(NSString *) messageId 226 | messageType:(NSString *) messageType 227 | msg: (NSString *) msg 228 | callback:(RCTResponseSenderBlock)callback) 229 | { 230 | 231 | } 232 | 233 | RCT_EXPORT_METHOD(listenForTokenRefresh:(RCTResponseSenderBlock)callback) 234 | {} 235 | 236 | RCT_EXPORT_METHOD(unlistenForTokenRefresh:(RCTResponseSenderBlock)callback) 237 | {} 238 | 239 | RCT_EXPORT_METHOD(subscribeToTopic:(NSString *) topic 240 | callback:(RCTResponseSenderBlock)callback) 241 | { 242 | [[FIRMessaging messaging] subscribeToTopic:topic]; 243 | callback(@[[NSNull null], @{ 244 | @"result": @"success", 245 | @"topic": topic 246 | }]); 247 | } 248 | 249 | RCT_EXPORT_METHOD(unsubscribeFromTopic:(NSString *) topic 250 | callback: (RCTResponseSenderBlock)callback) 251 | { 252 | [[FIRMessaging messaging] unsubscribeFromTopic:topic]; 253 | callback(@[[NSNull null], @{ 254 | @"result": @"success", 255 | @"topic": topic 256 | }]); 257 | } 258 | 259 | RCT_EXPORT_METHOD(setBadge:(NSInteger) number 260 | callback:(RCTResponseSenderBlock) callback) 261 | { 262 | RCTSharedApplication().applicationIconBadgeNumber = number; 263 | callback(@[[NSNull null], @{ 264 | @"result": @"success", 265 | @"number": @(number) 266 | }]); 267 | } 268 | 269 | RCT_EXPORT_METHOD(getBadge:(RCTResponseSenderBlock) callback) 270 | { 271 | callback(@[[NSNull null], @{ 272 | @"result": @"success", 273 | @"number": @(RCTSharedApplication().applicationIconBadgeNumber) 274 | }]); 275 | } 276 | 277 | RCT_EXPORT_METHOD(listenForReceiveNotification:(RCTResponseSenderBlock)callback) 278 | {} 279 | 280 | RCT_EXPORT_METHOD(unlistenForReceiveNotification:(RCTResponseSenderBlock)callback) 281 | {} 282 | 283 | RCT_EXPORT_METHOD(listenForReceiveUpstreamSend:(RCTResponseSenderBlock)callback) 284 | {} 285 | 286 | RCT_EXPORT_METHOD(unlistenForReceiveUpstreamSend:(RCTResponseSenderBlock)callback) 287 | {} 288 | 289 | // Not sure how to get away from this... yet 290 | - (NSArray *)supportedEvents { 291 | return @[ 292 | MESSAGING_SUBSYSTEM_EVENT, 293 | MESSAGING_SUBSYSTEM_ERROR, 294 | MESSAGING_TOKEN_REFRESH, 295 | MESSAGING_MESSAGE_RECEIVED_LOCAL, 296 | MESSAGING_MESSAGE_RECEIVED_REMOTE]; 297 | } 298 | 299 | - (void) sendJSEvent:(NSString *)title 300 | props:(NSDictionary *)props 301 | { 302 | @try { 303 | [self sendEventWithName:title 304 | body:@{ 305 | @"eventName": title, 306 | @"body": props 307 | }]; 308 | } 309 | @catch (NSException *err) { 310 | NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]); 311 | NSLog(@"Tried to send: %@ with %@", title, props); 312 | } 313 | } 314 | 315 | @end 316 | -------------------------------------------------------------------------------- /ios/Firestack/FirestackDatabase.h: -------------------------------------------------------------------------------- 1 | // 2 | // FirestackDatabase.h 3 | // Firestack 4 | // 5 | // Created by Ari Lerner on 8/23/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #ifndef FirestackDatabase_h 10 | #define FirestackDatabase_h 11 | 12 | #import "Firebase.h" 13 | #import "RCTEventEmitter.h" 14 | #import "RCTBridgeModule.h" 15 | 16 | @interface FirestackDatabase : RCTEventEmitter { 17 | 18 | } 19 | 20 | @property (nonatomic) NSDictionary *_DBHandles; 21 | @property (nonatomic, weak) FIRDatabaseReference *ref; 22 | 23 | @end 24 | 25 | #endif -------------------------------------------------------------------------------- /ios/Firestack/FirestackErrors.h: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseErrors.h 3 | // Firestack 4 | // 5 | // Created by Ari Lerner on 8/23/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #ifndef FirestackErrors_h 10 | #define FirestackErrors_h 11 | 12 | #import "RCTBridgeModule.h" 13 | #import "Firebase.h" 14 | 15 | @interface FirestackErrors : NSObject { 16 | 17 | } 18 | 19 | + (void) handleException:(NSException *)exception 20 | withCallback:(RCTResponseSenderBlock)callback; 21 | 22 | + (NSDictionary *) handleFirebaseError:(NSString *) name 23 | error:(NSError *) error 24 | withUser:(FIRUser *) user; 25 | @end 26 | 27 | #endif -------------------------------------------------------------------------------- /ios/Firestack/FirestackErrors.m: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseErrors.m 3 | // Firestack 4 | // 5 | // Created by Ari Lerner on 8/23/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #import "FirestackErrors.h" 10 | 11 | @implementation FirestackErrors 12 | 13 | RCT_EXPORT_MODULE(FirestackErrors); 14 | 15 | + (void) handleException:(NSException *)exception 16 | withCallback:(RCTResponseSenderBlock)callback 17 | { 18 | NSString *errDesc = [exception description]; 19 | NSLog(@"An error occurred: %@", errDesc); 20 | // No user is signed in. 21 | NSDictionary *err = @{ 22 | @"error": @"No user signed in", 23 | @"description": errDesc 24 | }; 25 | callback(@[err]); 26 | } 27 | 28 | + (NSDictionary *) handleFirebaseError:(NSString *) name 29 | error:(NSError *) error 30 | withUser:(FIRUser *) user 31 | { 32 | NSMutableDictionary *err = [NSMutableDictionary dictionaryWithObjectsAndKeys: 33 | name, @"name", 34 | @([error code]), @"code", 35 | [error localizedDescription], @"rawDescription", 36 | [[error userInfo] description], @"userInfo", 37 | nil]; 38 | 39 | NSString *description = @"Unknown error"; 40 | switch (error.code) { 41 | case FIRAuthErrorCodeInvalidEmail: 42 | description = @"Invalid email"; 43 | break; 44 | case FIRAuthErrorCodeUserNotFound: 45 | description = @"User not found"; 46 | break; 47 | case FIRAuthErrorCodeNetworkError: 48 | description = @"Network error"; 49 | break; 50 | case FIRAuthErrorCodeInternalError: 51 | description = @"Internal error"; 52 | break; 53 | default: 54 | break; 55 | } 56 | [err setValue:description forKey:@"description"]; 57 | return [NSDictionary dictionaryWithDictionary:err]; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /ios/Firestack/FirestackEvents.h: -------------------------------------------------------------------------------- 1 | // 2 | // FirestackEvents.h 3 | // Firestack 4 | // 5 | // Created by Ari Lerner on 8/23/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #ifndef FirestackEvents_h 10 | #define FirestackEvents_h 11 | 12 | #import 13 | 14 | #define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame) 15 | #define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending) 16 | #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) 17 | #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) 18 | #define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending) 19 | #define FIRESTACK_QUEUE_NAME "io.fullstack.firestack.WorkerQueue" 20 | 21 | static NSString *const kFirestackInitialized = @"FirestackInitializedEvent"; 22 | static NSString *const INITIALIZED_EVENT = @"firestackInitialized"; 23 | 24 | static NSString *const AUTH_CHANGED_EVENT = @"listenForAuth"; 25 | static NSString *const AUTH_ERROR_EVENT = @"authError"; 26 | static NSString *const AUTH_ANONYMOUS_ERROR_EVENT = @"authAnonymousError"; 27 | static NSString *const DEBUG_EVENT = @"debug"; 28 | 29 | // Database 30 | static NSString *const DATABASE_DATA_EVENT = @"database_event"; 31 | static NSString *const DATABASE_ERROR_EVENT = @"database_error"; 32 | 33 | static NSString *const DATABASE_VALUE_EVENT = @"value"; 34 | static NSString *const DATABASE_CHILD_ADDED_EVENT = @"child_added"; 35 | static NSString *const DATABASE_CHILD_MODIFIED_EVENT = @"child_changed"; 36 | static NSString *const DATABASE_CHILD_REMOVED_EVENT = @"child_removed"; 37 | static NSString *const DATABASE_CHILD_MOVED_EVENT = @"child_moved"; 38 | 39 | // Storage 40 | static NSString *const STORAGE_UPLOAD_PROGRESS = @"upload_progress"; 41 | static NSString *const STORAGE_UPLOAD_PAUSED = @"upload_paused"; 42 | static NSString *const STORAGE_UPLOAD_RESUMED = @"upload_resumed"; 43 | static NSString *const STORAGE_DOWNLOAD_PROGRESS = @"download_progress"; 44 | static NSString *const STORAGE_DOWNLOAD_PAUSED = @"download_paused"; 45 | static NSString *const STORAGE_DOWNLOAD_RESUMED = @"download_resumed"; 46 | 47 | // Messaging 48 | static NSString *const MESSAGING_SUBSYSTEM_EVENT = @"messaging_event"; 49 | static NSString *const MESSAGING_SUBSYSTEM_ERROR = @"messaging_error"; 50 | static NSString *const MESSAGING_TOKEN_REFRESH = @"messaging_token_refresh"; 51 | 52 | static NSString *const MESSAGING_MESSAGE_RECEIVED_REMOTE = @"messaging_remote_event_received"; 53 | static NSString *const MESSAGING_MESSAGE_RECEIVED_LOCAL = @"messaging_local_event_received"; 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /ios/Firestack/FirestackStorage.h: -------------------------------------------------------------------------------- 1 | // 2 | // FirestackStorage.h 3 | // Firestack 4 | // 5 | // Created by Ari Lerner on 8/24/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #ifndef FirestackStorage_h 10 | #define FirestackStorage_h 11 | 12 | #import "Firebase.h" 13 | #import "RCTBridgeModule.h" 14 | #import "RCTEventEmitter.h" 15 | 16 | @interface FirestackStorage : RCTEventEmitter { 17 | 18 | } 19 | 20 | @property (nonatomic) NSString *_storageUrl; 21 | 22 | @end 23 | 24 | #endif -------------------------------------------------------------------------------- /ios/Firestack/FirestackStorage.m: -------------------------------------------------------------------------------- 1 | // 2 | // FirestackStorage.m 3 | // Firestack 4 | // 5 | // Created by Ari Lerner on 8/24/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #import "FirestackStorage.h" 10 | #import "FirestackEvents.h" 11 | 12 | #import 13 | 14 | @implementation FirestackStorage 15 | 16 | RCT_EXPORT_MODULE(FirestackStorage); 17 | 18 | // Run on a different thread 19 | - (dispatch_queue_t)methodQueue 20 | { 21 | return dispatch_queue_create("io.fullstack.firestack.storage", DISPATCH_QUEUE_SERIAL); 22 | } 23 | 24 | RCT_EXPORT_METHOD(downloadUrl: (NSString *) storageUrl 25 | path:(NSString *) path 26 | callback:(RCTResponseSenderBlock) callback) 27 | { 28 | FIRStorageReference *storageRef; 29 | if (storageUrl == nil ) { 30 | storageRef = [[FIRStorage storage] reference]; 31 | } else { 32 | storageRef = [[FIRStorage storage] referenceForURL:storageUrl]; 33 | } 34 | FIRStorageReference *fileRef = [storageRef child:path]; 35 | [fileRef downloadURLWithCompletion:^(NSURL * _Nullable URL, NSError * _Nullable error) { 36 | if (error != nil) { 37 | NSDictionary *evt = @{ 38 | @"status": @"error", 39 | @"path": path, 40 | @"msg": [error debugDescription] 41 | }; 42 | callback(@[evt]); 43 | } else { 44 | NSDictionary *resp = @{ 45 | @"status": @"success", 46 | @"url": [URL absoluteString], 47 | @"path": [URL path] 48 | }; 49 | callback(@[[NSNull null], resp]); 50 | } 51 | }]; 52 | } 53 | 54 | RCT_EXPORT_METHOD(uploadFile: (NSString *) urlStr 55 | name: (NSString *) name 56 | path:(NSString *)path 57 | metadata:(NSDictionary *)metadata 58 | callback:(RCTResponseSenderBlock) callback) 59 | { 60 | FIRStorageReference *storageRef; 61 | if (urlStr == nil) { 62 | storageRef = [[FIRStorage storage] reference]; 63 | } else { 64 | storageRef = [[FIRStorage storage] referenceForURL:urlStr]; 65 | } 66 | 67 | FIRStorageReference *uploadRef = [storageRef child:name]; 68 | FIRStorageMetadata *firmetadata = [[FIRStorageMetadata alloc] initWithDictionary:metadata]; 69 | 70 | if ([path hasPrefix:@"assets-library://"]) { 71 | NSURL *localFile = [[NSURL alloc] initWithString:path]; 72 | PHFetchResult* assets = [PHAsset fetchAssetsWithALAssetURLs:@[localFile] options:nil]; 73 | PHAsset *asset = [assets firstObject]; 74 | 75 | [[PHImageManager defaultManager] requestImageDataForAsset:asset 76 | options:nil 77 | resultHandler:^(NSData * imageData, NSString * dataUTI, UIImageOrientation orientation, NSDictionary * info) { 78 | FIRStorageUploadTask *uploadTask = [uploadRef putData:imageData 79 | metadata:firmetadata]; 80 | [self addUploadObservers:uploadTask 81 | callback:callback]; 82 | }]; 83 | } else { 84 | NSURL *imageFile = [NSURL fileURLWithPath:path]; 85 | FIRStorageUploadTask *uploadTask = [uploadRef putFile:imageFile 86 | metadata:firmetadata]; 87 | 88 | [self addUploadObservers:uploadTask 89 | callback:callback]; 90 | } 91 | 92 | } 93 | 94 | - (void) addUploadObservers:(FIRStorageUploadTask *) uploadTask 95 | callback:(RCTResponseSenderBlock) callback 96 | { 97 | // Listen for state changes, errors, and completion of the upload. 98 | [uploadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) { 99 | // Upload resumed, also fires when the upload starts 100 | [self sendJSEvent:STORAGE_UPLOAD_RESUMED props:@{ 101 | @"eventName": STORAGE_UPLOAD_RESUMED, 102 | @"ref": snapshot.reference.bucket 103 | }]; 104 | }]; 105 | 106 | [uploadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) { 107 | // Upload paused 108 | [self sendJSEvent:STORAGE_UPLOAD_PAUSED props:@{ 109 | @"eventName": STORAGE_UPLOAD_PAUSED, 110 | @"ref": snapshot.reference.bucket 111 | }]; 112 | }]; 113 | [uploadTask observeStatus:FIRStorageTaskStatusProgress handler:^(FIRStorageTaskSnapshot *snapshot) { 114 | // Upload reported progress 115 | float percentComplete; 116 | if (snapshot.progress.totalUnitCount == 0) { 117 | percentComplete = 0.0; 118 | } else { 119 | percentComplete = 100.0 * (snapshot.progress.completedUnitCount) / (snapshot.progress.totalUnitCount); 120 | } 121 | 122 | [self sendJSEvent:STORAGE_UPLOAD_PROGRESS props:@{ 123 | @"eventName": STORAGE_UPLOAD_PROGRESS, 124 | @"progress": @(percentComplete) 125 | }]; 126 | 127 | }]; 128 | 129 | [uploadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) { 130 | [uploadTask removeAllObservers]; 131 | 132 | // Upload completed successfully 133 | FIRStorageReference *ref = snapshot.reference; 134 | NSDictionary *props = @{ 135 | @"fullPath": ref.fullPath, 136 | @"bucket": ref.bucket, 137 | @"name": ref.name, 138 | @"downloadUrl": snapshot.metadata.downloadURLs[0].absoluteString, 139 | @"metadata": [snapshot.metadata dictionaryRepresentation] 140 | }; 141 | 142 | callback(@[[NSNull null], props]); 143 | }]; 144 | 145 | [uploadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) { 146 | if (snapshot.error != nil) { 147 | NSDictionary *errProps = [[NSMutableDictionary alloc] init]; 148 | 149 | switch (snapshot.error.code) { 150 | case FIRStorageErrorCodeObjectNotFound: 151 | // File doesn't exist 152 | [errProps setValue:@"File does not exist" forKey:@"description"]; 153 | break; 154 | case FIRStorageErrorCodeUnauthorized: 155 | // User doesn't have permission to access file 156 | [errProps setValue:@"You do not have permissions" forKey:@"description"]; 157 | break; 158 | case FIRStorageErrorCodeCancelled: 159 | // User canceled the upload 160 | [errProps setValue:@"Upload cancelled" forKey:@"description"]; 161 | break; 162 | case FIRStorageErrorCodeUnknown: 163 | // Unknown error occurred, inspect the server response 164 | [errProps setValue:@"Unknown error" forKey:@"description"]; 165 | NSLog(@"Unknown error: %@", snapshot.error); 166 | break; 167 | } 168 | 169 | callback(@[errProps]); 170 | }}]; 171 | } 172 | 173 | RCT_EXPORT_METHOD(downloadFile: (NSString *) urlStr 174 | path:(NSString *) path 175 | localFile:(NSString *) file 176 | callback:(RCTResponseSenderBlock) callback) 177 | { 178 | if (urlStr == nil) { 179 | NSError *err = [[NSError alloc] init]; 180 | [err setValue:@"Storage configuration error" forKey:@"name"]; 181 | [err setValue:@"Call setStorageUrl() first" forKey:@"description"]; 182 | return callback(@[err]); 183 | } 184 | 185 | FIRStorageReference *storageRef = [[FIRStorage storage] referenceForURL:urlStr]; 186 | FIRStorageReference *fileRef = [storageRef child:path]; 187 | 188 | NSURL *localFile = [NSURL fileURLWithPath:file]; 189 | 190 | FIRStorageDownloadTask *downloadTask = [fileRef writeToFile:localFile]; 191 | // Listen for state changes, errors, and completion of the download. 192 | [downloadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) { 193 | // Upload resumed, also fires when the upload starts 194 | [self sendJSEvent:STORAGE_DOWNLOAD_RESUMED props:@{ 195 | @"eventName": STORAGE_DOWNLOAD_RESUMED, 196 | @"ref": snapshot.reference.bucket 197 | }]; 198 | }]; 199 | 200 | [downloadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) { 201 | // Upload paused 202 | [self sendJSEvent:STORAGE_DOWNLOAD_PAUSED props:@{ 203 | @"eventName": STORAGE_DOWNLOAD_PAUSED, 204 | @"ref": snapshot.reference.bucket 205 | }]; 206 | }]; 207 | [downloadTask observeStatus:FIRStorageTaskStatusProgress handler:^(FIRStorageTaskSnapshot *snapshot) { 208 | // Upload reported progress 209 | float percentComplete; 210 | if (snapshot.progress.totalUnitCount == 0) { 211 | percentComplete = 0.0; 212 | } else { 213 | percentComplete = 100.0 * (snapshot.progress.completedUnitCount) / (snapshot.progress.totalUnitCount); 214 | } 215 | 216 | [self sendJSEvent:STORAGE_DOWNLOAD_PROGRESS props:@{ 217 | @"eventName": STORAGE_DOWNLOAD_PROGRESS, 218 | @"progress": @(percentComplete) 219 | }]; 220 | 221 | }]; 222 | 223 | [downloadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) { 224 | [downloadTask removeAllObservers]; 225 | 226 | // Upload completed successfully 227 | FIRStorageReference *ref = snapshot.reference; 228 | NSDictionary *props = @{ 229 | @"fullPath": ref.fullPath, 230 | @"bucket": ref.bucket, 231 | @"name": ref.name 232 | }; 233 | 234 | callback(@[[NSNull null], props]); 235 | }]; 236 | 237 | [downloadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) { 238 | if (snapshot.error != nil) { 239 | NSDictionary *errProps = [[NSMutableDictionary alloc] init]; 240 | NSLog(@"Error in download: %@", snapshot.error); 241 | 242 | switch (snapshot.error.code) { 243 | case FIRStorageErrorCodeObjectNotFound: 244 | // File doesn't exist 245 | [errProps setValue:@"File does not exist" forKey:@"description"]; 246 | break; 247 | case FIRStorageErrorCodeUnauthorized: 248 | // User doesn't have permission to access file 249 | [errProps setValue:@"You do not have permissions" forKey:@"description"]; 250 | break; 251 | case FIRStorageErrorCodeCancelled: 252 | // User canceled the upload 253 | [errProps setValue:@"Download canceled" forKey:@"description"]; 254 | break; 255 | case FIRStorageErrorCodeUnknown: 256 | // Unknown error occurred, inspect the server response 257 | [errProps setValue:@"Unknown error" forKey:@"description"]; 258 | break; 259 | } 260 | 261 | callback(@[errProps]); 262 | }}]; 263 | } 264 | 265 | // Compatibility with the android library 266 | // For now, just passes the url path back 267 | RCT_EXPORT_METHOD(getRealPathFromURI: (NSString *) urlStr 268 | callback:(RCTResponseSenderBlock) callback) 269 | { 270 | callback(@[[NSNull null], urlStr]); 271 | } 272 | 273 | // This is just too good not to use, but I don't want to take credit for 274 | // this work from RNFS 275 | // https://github.com/johanneslumpe/react-native-fs/blob/master/RNFSManager.m 276 | - (NSString *)getPathForDirectory:(int)directory 277 | { 278 | NSArray *paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES); 279 | return [paths firstObject]; 280 | } 281 | 282 | - (NSDictionary *)constantsToExport 283 | { 284 | return @{ 285 | @"MAIN_BUNDLE_PATH": [[NSBundle mainBundle] bundlePath], 286 | @"CACHES_DIRECTORY_PATH": [self getPathForDirectory:NSCachesDirectory], 287 | @"DOCUMENT_DIRECTORY_PATH": [self getPathForDirectory:NSDocumentDirectory], 288 | @"EXTERNAL_DIRECTORY_PATH": [NSNull null], 289 | @"EXTERNAL_STORAGE_DIRECTORY_PATH": [NSNull null], 290 | @"TEMP_DIRECTORY_PATH": NSTemporaryDirectory(), 291 | @"LIBRARY_DIRECTORY_PATH": [self getPathForDirectory:NSLibraryDirectory], 292 | @"FILETYPE_REGULAR": NSFileTypeRegular, 293 | @"FILETYPE_DIRECTORY": NSFileTypeDirectory 294 | }; 295 | } 296 | 297 | // Not sure how to get away from this... yet 298 | - (NSArray *)supportedEvents { 299 | return @[ 300 | STORAGE_UPLOAD_PAUSED, 301 | STORAGE_UPLOAD_RESUMED, 302 | STORAGE_UPLOAD_PROGRESS, 303 | STORAGE_DOWNLOAD_PAUSED, 304 | STORAGE_DOWNLOAD_RESUMED, 305 | STORAGE_DOWNLOAD_PROGRESS 306 | ]; 307 | } 308 | 309 | - (void) sendJSEvent:(NSString *)title 310 | props:(NSDictionary *)props 311 | { 312 | @try { 313 | [self sendEventWithName:title 314 | body:props]; 315 | } 316 | @catch (NSException *err) { 317 | NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]); 318 | } 319 | } 320 | 321 | 322 | @end 323 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | use_frameworks! 3 | platform :ios, '8.0' 4 | 5 | def common_pods 6 | # pod 'Firestack', :path => '../' 7 | # pod 'React', :path => '../node_modules/react-native' 8 | [ 9 | 'Firebase', 10 | 'Firebase/Core', 11 | 'Firebase/Auth', 12 | 'Firebase/Storage', 13 | 'Firebase/Database', 14 | 'Firebase/RemoteConfig', 15 | 'Firebase/Messaging' 16 | ].each do |lib| 17 | pod lib 18 | end 19 | end 20 | 21 | def test_pods 22 | pod 'Quick', '~> 0.8.0' 23 | pod 'Nimble', '~> 3.0.0' 24 | end 25 | 26 | def setup 27 | post_install do |installer| 28 | installer.pods_project.targets.each do |target| 29 | target.build_configurations.each do |configuration| 30 | # configuration.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = "" 31 | # configuration.build_settings['CODE_SIGNING_REQUIRED'] = "YES" 32 | # configuration.build_settings['CODE_SIGNING_ALLOWED'] = "YES" 33 | 34 | target.build_settings(configuration.name)['OTHER_LDFLAGS'] = '$(inherited)' 35 | target.build_settings(configuration.name)['USER_HEADER_SEARCH_PATHS'] = "$(BUILT_PRODUCTS_DIR)" 36 | target.build_settings(configuration.name)['LD_DYLIB_INSTALL_NAME'] = '@rpath/${EXECUTABLE_NAME}' 37 | target.build_settings(configuration.name)['LD_RUNPATH_SEARCH_PATHS'] = '$(inherited) @rpath @loader_path/../Frameworks @executable_path/Frameworks' 38 | 39 | target.build_settings(configuration.name)['ONLY_ACTIVE_ARCH'] = 'NO' 40 | target.build_settings(configuration.name)['HEADER_SEARCH_PATHS'] = [ 41 | "$(inherited)", 42 | "${PODS_ROOT}/Headers/**", 43 | "$(SRCROOT)/../../React/**", 44 | "$(SRCROOT)/../../react-native/React/**", 45 | '$(RN_ROOT)/React/**', 46 | '$(PODS_ROOT)/Headers' 47 | ].join(' ') 48 | target.build_settings(configuration.name)['FRAMEWORK_SEARCH_PATHS'] = [ 49 | "$(inherited)", 50 | '$(PODS_ROOT)/**', 51 | '$(PODS_CONFIGURATION_BUILD_DIR)/**' 52 | ].join(' ') 53 | target.build_settings(configuration.name)['OTHER_LDFLAGS'] = "$(inherited)" 54 | end 55 | end 56 | end 57 | end 58 | 59 | # target 'Firestack' do 60 | # common_pods 61 | # project "Firestack.xcodeproj" 62 | # setup 63 | # end 64 | 65 | target 'FirestackTests' do 66 | use_frameworks! 67 | common_pods 68 | test_pods 69 | pod 'React', :path => '../node_modules/react-native' 70 | setup 71 | end -------------------------------------------------------------------------------- /ios/Podfile.template: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | use_frameworks! 3 | platform :ios, '8.0' 4 | 5 | [ 6 | 'Firebase', 7 | 'Firebase/Core', 8 | 'Firebase/Auth', 9 | 'Firebase/Storage', 10 | 'Firebase/Database', 11 | 'Firebase/RemoteConfig', 12 | 'Firebase/Messaging' 13 | ].each do |lib| 14 | pod lib 15 | end 16 | -------------------------------------------------------------------------------- /ios/buildScript.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # buildScript.sh 4 | # Firestack 5 | # 6 | # Created by Ari Lerner on 8/3/16. 7 | # Copyright © 2016 Facebook. All rights reserved. 8 | frameworks="Firebase FirebaseAnalytics" 9 | 10 | source "${SRCROOT}/Pods/Target Support Files/Pods-Firestack/Pods-Firestack-frameworks.sh" 11 | FRAMEWORKS_FOLDER_PATH="" 12 | 13 | for framework in $frameworks 14 | do 15 | 16 | install_framework "${SRCROOT}/Pods/$framework" 17 | 18 | done 19 | -------------------------------------------------------------------------------- /lib/firestack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @providesModule Firestack 3 | * @flow 4 | */ 5 | import Log from './utils/log' 6 | 7 | // const firebase = require('firebase'); 8 | 9 | // const app = require('firebase/app'); 10 | // const storage = require('firebase/storage'); 11 | // const db = require('firebase/database'); 12 | 13 | import {NativeModules, NativeEventEmitter, AsyncStorage} from 'react-native'; 14 | // TODO: Break out modules into component pieces 15 | // i.e. auth component, storage component, etc. 16 | const FirestackModule = NativeModules.Firestack; 17 | const FirestackModuleEvt = new NativeEventEmitter(FirestackModule); 18 | 19 | import promisify from './utils/promisify' 20 | import Singleton from './utils/singleton' 21 | 22 | import RemoteConfig from './modules/remoteConfig' 23 | import {Authentication} from './modules/authentication' 24 | import {Database} from './modules/database' 25 | import {Analytics} from './modules/analytics' 26 | import {Storage} from './modules/storage' 27 | import {Presence} from './modules/presence' 28 | import {CloudMessaging} from './modules/cloudmessaging' 29 | 30 | let log; 31 | export class Firestack extends Singleton { 32 | 33 | constructor(options) { 34 | var instance = super(options); 35 | 36 | instance.options = options || {}; 37 | instance._debug = instance.options.debug || false; 38 | 39 | Log.enable(instance._debug); 40 | log = instance._log = new Log('firestack'); 41 | 42 | log.info('Creating new firestack instance'); 43 | 44 | instance._remoteConfig = instance.options.remoteConfig || {}; 45 | delete instance.options.remoteConfig; 46 | 47 | instance.configured = instance.options.configure || false; 48 | instance.auth = null; 49 | 50 | instance.eventHandlers = {}; 51 | 52 | log.info('Calling configure with options', instance.options); 53 | instance.configurePromise = instance.configure(instance.options); 54 | 55 | instance._auth = new Authentication(instance, instance.options); 56 | } 57 | 58 | configure(opts = {}) { 59 | if (!this.configurePromise) { 60 | const firestackOptions = Object.assign({}, this.options, opts); 61 | 62 | this.configurePromise = promisify('configureWithOptions', FirestackModule)(firestackOptions) 63 | .then((configuredProperties) => { 64 | log.info('Native configureWithOptions success', configuredProperties); 65 | this.configured = true; 66 | this.firestackOptions = configuredProperties; 67 | return configuredProperties; 68 | }).catch((err) => { 69 | log.info('Native error occurred while calling configure', err); 70 | }) 71 | } 72 | return this.configurePromise; 73 | } 74 | 75 | onReady(cb) { 76 | return this.configurePromise = this.configurePromise.then(cb); 77 | } 78 | 79 | /** 80 | * Wrappers 81 | * We add methods from each wrapper to this instance 82 | * when they are needed. Not sure if this is a good 83 | * idea or not (imperative vs. direct manipulation/proxy) 84 | */ 85 | get auth() { 86 | if (!this._auth) { this._auth = new Authentication(this); } 87 | return this._auth; 88 | } 89 | // database 90 | get database() { 91 | if (!this._db) { this._db = new Database(this); } 92 | return this._db; 93 | // db.enableLogging(this._debug); 94 | // return this.appInstance.database(); 95 | } 96 | 97 | // analytics 98 | get analytics() { 99 | if (!this._analytics) { this._analytics = new Analytics(this); } 100 | return this._analytics; 101 | } 102 | 103 | // storage 104 | get storage() { 105 | if (!this._storage) { this._storage = new Storage(this); } 106 | return this._storage; 107 | } 108 | 109 | // presence 110 | get presence() { 111 | if (!this._presence) { this._presence = new Presence(this); } 112 | return this._presence; 113 | } 114 | // CloudMessaging 115 | get cloudMessaging() { 116 | if (!this._cloudMessaging) { this._cloudMessaging = new CloudMessaging(this); } 117 | return this._cloudMessaging; 118 | } 119 | 120 | // other 121 | get ServerValue() { 122 | return promisify('serverValue', FirestackModule)(); 123 | } 124 | 125 | /** 126 | * remote config 127 | */ 128 | get remoteConfig() { 129 | if (!this.remoteConfig) { 130 | this.remoteConfig = new RemoteConfig(this._remoteConfig); 131 | } 132 | return this.remoteConfig; 133 | } 134 | 135 | /** 136 | * app instance 137 | **/ 138 | get app() { 139 | return this.appInstance; 140 | } 141 | 142 | /** 143 | * Logger 144 | */ 145 | get log() { 146 | return this._log; 147 | } 148 | 149 | /** 150 | * Redux store 151 | **/ 152 | get store() { 153 | return this._store; 154 | } 155 | 156 | get constants() { 157 | if (!this._constants) { 158 | this._constants = Object.assign({}, Storage.constants) 159 | } 160 | return this._constants; 161 | } 162 | 163 | /** 164 | * Set the redux store helper 165 | */ 166 | setStore(store) { 167 | if (store) { 168 | this.log.info('Setting the store for Firestack instance'); 169 | this._store = store; 170 | } 171 | } 172 | 173 | /** 174 | * Global event handlers for the single Firestack instance 175 | */ 176 | on(name, cb, nativeModule) { 177 | if (!this.eventHandlers[name]) { 178 | this.eventHandlers[name] = []; 179 | } 180 | if (!nativeModule) { 181 | nativeModule = FirestackModuleEvt; 182 | } 183 | const sub = nativeModule.addListener(name, cb); 184 | this.eventHandlers[name].push(sub); 185 | return sub; 186 | } 187 | 188 | off(name) { 189 | if (this.eventHandlers[name]) { 190 | this.eventHandlers[name] 191 | .forEach(subscription => subscription.remove()); 192 | } 193 | } 194 | } 195 | 196 | export default Firestack 197 | -------------------------------------------------------------------------------- /lib/firestackModule.js: -------------------------------------------------------------------------------- 1 | import invariant from 'invariant' 2 | 3 | const createTypes = (prefix) => { 4 | const c = (str) => `${prefix.toUpperCase()}_${str.toUpperCase()}` 5 | return { 6 | ACTION_CALL: c('call_function'), 7 | ACTION_SUCCESS: c('call_function_success'), 8 | ACTION_FAIL: c('call_function_failure'), 9 | 10 | ACTION_LISTEN: c('listen'), 11 | ACTION_UNLISTEN: c('unlisten'), 12 | ACTION_REMOVE: c('remove'), 13 | ACTION_UPDATE: c('update'), 14 | ACTION_SET: c('set'), 15 | ACTION_GET: c('get'), 16 | ITEM_VALUE: c('value'), 17 | ITEM_ADDED: c('added'), 18 | ITEM_REMOVED: c('remove'), 19 | ITEM_CHANGED: c('changed'), 20 | UPDATED: c('updated') 21 | } 22 | } 23 | 24 | const defaultToObject = child => ({_key: child.key, ...child.val()}) 25 | const identity = (i) => i 26 | const defaultSortFn = (a, b) => a.timestamp < b.timestamp 27 | const defaultInitialState = { 28 | items: [], 29 | } 30 | 31 | export class FirestackModule { 32 | constructor(refName, opts={}) { 33 | invariant(refName && typeof refName !== 'undefined', 'No ref name passed'); 34 | 35 | this._refName = refName; 36 | this._makeRef = opts.makeRef || identity; 37 | 38 | const initialState = Object.assign({}, opts.initialState || defaultInitialState, { 39 | listening: false, 40 | items: [] 41 | }) 42 | 43 | this._localState = initialState; 44 | 45 | this._types = createTypes(this._refName); 46 | this._toObject = opts.toObject || defaultToObject 47 | this._sortFn = opts.sortFn || defaultSortFn 48 | this._onChange = opts.onChange || identity; 49 | 50 | if (opts.firestack) { 51 | this.setFirestack(opts.firestack); 52 | } else if (opts.store) { 53 | this.setStore(opts.store); 54 | } 55 | } 56 | 57 | makeRef(path) { 58 | const refName = [this._refName, path] 59 | const ref = this._firestack.database.ref(...refName); 60 | return this._makeRef(ref); 61 | } 62 | 63 | setFirestack(firestack) { 64 | if (firestack) { 65 | this._firestack = firestack; 66 | } 67 | } 68 | 69 | setStore(store) { 70 | if (store) { 71 | this._store = store; 72 | } 73 | } 74 | 75 | /* 76 | * Actions 77 | */ 78 | listen(cb) { 79 | let store = this._getStore(); 80 | invariant(store, 'Please set the store'); 81 | 82 | const T = this._types; 83 | const listenRef = this.makeRef(); 84 | const toObject = this._toObject; 85 | 86 | const _itemAdded = (snapshot, prevKey) => { 87 | const state = this._getState(); // local state 88 | const newItem = toObject(snapshot, state); 89 | let list = state.items || []; 90 | list.push(newItem) 91 | list = list.sort(this._sortFn) 92 | return this._handleUpdate(T.ITEM_ADDED, {items: list}, cb); 93 | } 94 | const _itemRemoved = (snapshot, prevKey) => { 95 | const state = this._getState(); // local state 96 | const itemKeys = state.items.map(i => i._key); 97 | const itemIndex = itemKeys.indexOf(snapshot.key); 98 | let newItems = [].concat(state.items); 99 | newItems.splice(itemIndex, 1); 100 | let list = newItems.sort(this._sortFn) 101 | return this._handleUpdate(T.ITEM_REMOVED, {items: list}, cb); 102 | } 103 | const _itemChanged = (snapshot, prevKey) => { 104 | const state = this._getState() 105 | const existingItem = toObject(snapshot, state); 106 | 107 | let list = state.items; 108 | let listIds = state.items.map(i => i._key); 109 | const itemIdx = listIds.indexOf(existingItem._key); 110 | list.splice(itemIdx, 1, existingItem); 111 | 112 | return this._handleUpdate(T.ITEM_CHANGED, {items: list}, cb); 113 | } 114 | 115 | return new Promise((resolve, reject) => { 116 | listenRef.on('child_added', _itemAdded); 117 | listenRef.on('child_removed', _itemRemoved); 118 | listenRef.on('child_changed', _itemChanged); 119 | 120 | this._handleUpdate(T.ACTION_LISTEN, null, (state) => { 121 | resolve(state) 122 | }) 123 | }) 124 | } 125 | 126 | unlisten() { 127 | const T = this._types; 128 | const ref = this.makeRef(); 129 | 130 | return new Promise((resolve, reject) => { 131 | ref.off() 132 | .then((success) => { 133 | this._handleUpdate(T.ACTION_UNLISTEN, null, (state) => { 134 | resolve(state) 135 | }) 136 | }); 137 | }) 138 | } 139 | 140 | // TODO: Untested 141 | getAt(path, cb) { 142 | const T = this._types; 143 | const ref = this.makeRef(path); 144 | const toObject = this._toObject; 145 | 146 | return new Promise((resolve, reject) => { 147 | ref.once('value', snapshot => { 148 | this._handleUpdate(T.ACTION_GET, null, (state) => { 149 | if (cb) { 150 | cb(toObject(snapshot, state)); 151 | } 152 | resolve(state) 153 | }) 154 | }, reject); 155 | }); 156 | } 157 | 158 | setAt(path, value, cb) { 159 | const T = this._types; 160 | const ref = this.makeRef(path); 161 | const toObject = this._toObject; 162 | 163 | return new Promise((resolve, reject) => { 164 | ref.setAt(value, (error) => { 165 | this._handleUpdate(T.ACTION_SET, null, (state) => { 166 | if (cb) { 167 | cb(error, value); 168 | } 169 | return error ? reject(error) : resolve(value) 170 | }); 171 | }) 172 | }); 173 | } 174 | 175 | updateAt(path, value, cb) { 176 | const T = this._types; 177 | const ref = this.makeRef(path); 178 | const toObject = this._toObject; 179 | 180 | return new Promise((resolve, reject) => { 181 | ref.updateAt(value, (error, snapshot) => { 182 | this._handleUpdate(T.ACTION_UPDATE, null, (state) => { 183 | if (cb) { 184 | cb(toObject(snapshot, state)); 185 | } 186 | return error ? reject(error) : resolve(value) 187 | }); 188 | }); 189 | }); 190 | } 191 | 192 | removeAt(path, cb) { 193 | const T = this._types; 194 | const ref = this.makeRef(path); 195 | const toObject = this._toObject; 196 | 197 | return new Promise((resolve, reject) => { 198 | ref.removeAt((error, snapshot) => { 199 | this._handleUpdate(T.ACTION_SET, null, (state) => { 200 | if (cb) { 201 | cb(toObject(snapshot, state)); 202 | } 203 | return error ? reject(error) : resolve(value) 204 | }); 205 | }); 206 | }); 207 | } 208 | 209 | // hackish, for now 210 | get actions() { 211 | const T = this._types; 212 | 213 | const wrap = (fn) => (...args) => { 214 | const params = args && args.length > 0 ? args : []; 215 | const promise = fn.bind(this)(...params) 216 | return {type: T.ACTION_CALL, payload: promise} 217 | } 218 | 219 | return [ 220 | 'listen', 'unlisten', 221 | 'getAt', 'setAt', 'updateAt', 'removeAt' 222 | ].reduce((sum, name) => { 223 | return { 224 | ...sum, 225 | [name]: wrap(this[name]) 226 | } 227 | }, {}) 228 | } 229 | 230 | get initialState() { 231 | return this._initialState; 232 | } 233 | 234 | get types() { 235 | return this._types 236 | } 237 | 238 | get reducer() { 239 | const T = this._types; 240 | return (state = this._localState, {type, payload, meta}) => { 241 | if (meta && meta.module && meta.module === this._refName) { 242 | switch (type) { 243 | case T.ACTION_LISTEN: 244 | return ({...state, listening: true}); 245 | case T.ACTION_UNLISTEN: 246 | return ({...state, listening: false}); 247 | default: 248 | return {...state, ...payload}; 249 | } 250 | } 251 | return state; 252 | } 253 | } 254 | 255 | /** 256 | * Helpers 257 | **/ 258 | 259 | _handleUpdate(type, newState = {}, cb = identity) { 260 | const store = this._getStore(); 261 | if (store && store.dispatch && typeof store.dispatch === 'function') { 262 | store.dispatch({type, payload: newState, meta: { module: this._refName }}) 263 | } 264 | return cb(newState); 265 | } 266 | 267 | _getStore() { 268 | return this._store ? 269 | this._store : 270 | (this._firestack ? this._firestack.store : null); 271 | } 272 | 273 | _getState() { 274 | const store = this._getStore(); 275 | return store.getState()[this._refName]; 276 | } 277 | 278 | } 279 | 280 | export default FirestackModule 281 | -------------------------------------------------------------------------------- /lib/modules/analytics.js: -------------------------------------------------------------------------------- 1 | import {NativeModules, NativeAppEventEmitter} from 'react-native'; 2 | const FirestackAnalytics = NativeModules.FirestackAnalytics; 3 | 4 | import promisify from '../utils/promisify' 5 | import { Base } from './base' 6 | 7 | export class Analytics extends Base { 8 | constructor(firestack, options={}) { 9 | super(firestack, options); 10 | 11 | this._addToFirestackInstance( 12 | 'logEventWithName' 13 | ) 14 | } 15 | /** 16 | * Log an event 17 | * @param {string} name The name of the event 18 | * @param {object} props An object containing string-keys 19 | * @return {Promise} 20 | */ 21 | logEventWithName(name, props) { 22 | return promisify('logEventWithName', FirestackAnalytics)(name, props); 23 | } 24 | 25 | enable() { 26 | return promisify('setEnabled', FirestackAnalytics)(true); 27 | } 28 | 29 | disable() { 30 | return promisify('setEnabled', FirestackAnalytics)(false); 31 | } 32 | 33 | setUser(id, properties={}) { 34 | return promisify('setUserId', FirestackAnalytics)(id, properties); 35 | } 36 | 37 | get namespace() { 38 | return 'firestack:analytics' 39 | } 40 | } 41 | 42 | export default Analytics -------------------------------------------------------------------------------- /lib/modules/authentication.js: -------------------------------------------------------------------------------- 1 | 2 | import {NativeModules, NativeEventEmitter} from 'react-native'; 3 | const FirestackAuth = NativeModules.FirestackAuth 4 | const FirestackAuthEvt = new NativeEventEmitter(FirestackAuth); 5 | 6 | import promisify from '../utils/promisify' 7 | import { Base } from './base' 8 | 9 | export class Authentication extends Base { 10 | constructor(firestack, options={}) { 11 | super(firestack, options); 12 | } 13 | 14 | // Auth 15 | listenForAuth(callback) { 16 | this.log.info('Setting up listenForAuth callback'); 17 | const sub = this._on('listenForAuth', callback, FirestackAuthEvt); 18 | FirestackAuth.listenForAuth(); 19 | this.log.info('Listening for auth...'); 20 | return promisify(() => sub, FirestackAuth)(sub); 21 | } 22 | 23 | unlistenForAuth() { 24 | this.log.info('Unlistening for auth'); 25 | this._off('listenForAuth'); 26 | return promisify('unlistenForAuth', FirestackAuth)(); 27 | } 28 | 29 | /** 30 | * Create a user with the email/password functionality 31 | * @param {string} email The user's email 32 | * @param {string} password The user's password 33 | * @return {Promise} A promise indicating the completion 34 | */ 35 | createUserWithEmail(email, password) { 36 | this.log.info('Creating user with email', email); 37 | return promisify('createUserWithEmail', FirestackAuth)(email, password); 38 | } 39 | 40 | /** 41 | * Sign a user in with email/password 42 | * @param {string} email The user's email 43 | * @param {string} password The user's password 44 | * @return {Promise} A promise that is resolved upon completion 45 | */ 46 | signInWithEmail(email, password) { 47 | return promisify('signInWithEmail', FirestackAuth)(email, password) 48 | } 49 | 50 | /** 51 | * Sign the user in with a third-party authentication provider 52 | * @param {string} provider The name of the provider to use for login 53 | * @param {string} authToken The authToken granted by the provider 54 | * @param {string} authSecret The authToken secret granted by the provider 55 | * @return {Promise} A promise resolved upon completion 56 | */ 57 | signInWithProvider(provider, authToken, authSecret) { 58 | return promisify('signInWithProvider', FirestackAuth)(provider, authToken, authSecret) 59 | } 60 | 61 | /** 62 | * Sign the user in with a custom auth token 63 | * @param {string} customToken A self-signed custom auth token. 64 | * @return {Promise} A promise resolved upon completion 65 | */ 66 | signInWithCustomToken(customToken) { 67 | return promisify('signInWithCustomToken', FirestackAuth)(customToken) 68 | } 69 | 70 | /** 71 | * Sign a user in anonymously 72 | * @return {Promise} A promise resolved upon completion 73 | */ 74 | signInAnonymously() { 75 | return promisify('signInAnonymously', FirestackAuth)(); 76 | } 77 | 78 | /** 79 | * Reauthenticate a user with a third-party authentication provider 80 | * @param {string} provider The provider name 81 | * @param {string} token The authToken granted by the provider 82 | * @param {string} secret The authTokenSecret granted by the provider 83 | * @return {Promise} A promise resolved upon completion 84 | */ 85 | reauthenticateWithCredentialForProvider(provider, token, secret) { 86 | return promisify('reauthenticateWithCredentialForProvider', FirestackAuth)(provider, token, secret) 87 | } 88 | 89 | /** 90 | * Update the current user's email 91 | * @param {string} email The user's _new_ email 92 | * @return {Promise} A promise resolved upon completion 93 | */ 94 | updateUserEmail(email) { 95 | return promisify('updateUserEmail', FirestackAuth)(email); 96 | } 97 | 98 | /** 99 | * Update the current user's password 100 | * @param {string} email the new password 101 | * @return {Promise} 102 | */ 103 | updatePassword(password) { 104 | return promisify('updateUserPassword', FirestackAuth)(password); 105 | } 106 | 107 | /** 108 | * Send reset password instructions via email 109 | * @param {string} email The email to send password reset instructions 110 | */ 111 | sendPasswordResetWithEmail(email) { 112 | return promisify('sendPasswordResetWithEmail', FirestackAuth)(email); 113 | } 114 | 115 | /** 116 | * Delete the current user 117 | * @return {Promise} 118 | */ 119 | deleteUser() { 120 | return promisify('deleteUser', FirestackAuth)() 121 | } 122 | /** 123 | * get the token of current user 124 | * @return {Promise} 125 | */ 126 | getToken() { 127 | return promisify('getToken', FirestackAuth)() 128 | } 129 | 130 | /** 131 | * Update the current user's profile 132 | * @param {Object} obj An object containing the keys listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile) 133 | * @return {Promise} 134 | */ 135 | updateUserProfile(obj) { 136 | return promisify('updateUserProfile', FirestackAuth)(obj); 137 | } 138 | 139 | /** 140 | * Sign the current user out 141 | * @return {Promise} 142 | */ 143 | signOut() { 144 | return promisify('signOut', FirestackAuth)(); 145 | } 146 | 147 | /** 148 | * Get the currently signed in user 149 | * @return {Promise} 150 | */ 151 | getCurrentUser() { 152 | return promisify('getCurrentUser', FirestackAuth)(); 153 | } 154 | 155 | get namespace() { 156 | return 'firestack:auth'; 157 | } 158 | } 159 | 160 | export default Authentication 161 | -------------------------------------------------------------------------------- /lib/modules/base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | */ 4 | import Log from '../utils/log' 5 | 6 | import {NativeModules, NativeEventEmitter, AsyncStorage} from 'react-native'; 7 | const FirestackModule = NativeModules.Firestack; 8 | const FirestackModuleEvt = new NativeEventEmitter(FirestackModule); 9 | 10 | import promisify from '../utils/promisify' 11 | 12 | let logs = {}; 13 | export class Base { 14 | constructor(firestack, options={}) { 15 | this.firestack = firestack; 16 | this.eventHandlers = {}; 17 | 18 | // Extend custom options with default options 19 | this.options = Object.assign({}, firestack.options, options); 20 | } 21 | 22 | // Logger 23 | get log() { 24 | if (!logs[this.namespace]) { 25 | const debug = this.firestack._debug; 26 | logs[this.namespace] = new Log(this.namespace, debug); 27 | } 28 | return logs[this.namespace]; 29 | } 30 | 31 | _addConstantExports(constants) { 32 | Object.keys(constants).forEach(name => { 33 | FirestackModule[name] = constants[name]; 34 | }); 35 | } 36 | 37 | _addToFirestackInstance(...methods) { 38 | methods.forEach(name => { 39 | this.firestack[name] = this[name].bind(this); 40 | }) 41 | } 42 | 43 | /** 44 | * app instance 45 | **/ 46 | get app() { 47 | return this.firestack.app; 48 | } 49 | 50 | whenReady(fn) { 51 | return this.firestack.configurePromise.then(fn); 52 | } 53 | 54 | get namespace() { 55 | return 'firestack:base'; 56 | } 57 | 58 | // Event handlers 59 | // proxy to firestack instance 60 | _on(name, cb, nativeModule) { 61 | return new Promise((resolve) => { 62 | // if (!this.eventHandlers[name]) { 63 | // this.eventHandlers[name] = {}; 64 | // } 65 | if (!nativeModule) { 66 | nativeModule = FirestackModuleEvt; 67 | } 68 | const sub = nativeModule.addListener(name, cb); 69 | this.eventHandlers[name] = sub; 70 | resolve(sub); 71 | }) 72 | } 73 | 74 | _off(name) { 75 | return new Promise((resolve, reject) => { 76 | if (this.eventHandlers[name]) { 77 | const subscription = this.eventHandlers[name]; 78 | subscription.remove(); // Remove subscription 79 | delete this.eventHandlers[name]; 80 | resolve(subscription) 81 | } 82 | }); 83 | } 84 | } 85 | 86 | export class ReferenceBase extends Base { 87 | constructor(firestack, path) { 88 | super(firestack); 89 | 90 | this.path = Array.isArray(path) ? 91 | path : 92 | (typeof path == 'string' ? 93 | [path] : []); 94 | 95 | // sanitize path, just in case 96 | this.path = this.path 97 | .filter(str => str !== "" ); 98 | } 99 | 100 | get key() { 101 | const path = this.path; 102 | return path.length === 0 ? '/' : path[path.length - 1]; 103 | } 104 | 105 | pathToString() { 106 | let path = this.path; 107 | let pathStr = (path.length > 0 ? path.join('/') : '/'); 108 | if (pathStr[0] != '/') { 109 | pathStr = `/${pathStr}` 110 | } 111 | return pathStr; 112 | } 113 | } -------------------------------------------------------------------------------- /lib/modules/cloudmessaging.js: -------------------------------------------------------------------------------- 1 | import {Platform, NativeModules, NativeEventEmitter} from 'react-native'; 2 | const FirestackCloudMessaging = NativeModules.FirestackCloudMessaging; 3 | const FirestackCloudMessagingEvt = new NativeEventEmitter(FirestackCloudMessaging); 4 | 5 | import promisify from '../utils/promisify' 6 | import { Base, ReferenceBase } from './base' 7 | 8 | const defaultPermissions = { 9 | 'badge': 1, 10 | 'sound': 2, 11 | 'alert': 3 12 | } 13 | export class CloudMessaging extends Base { 14 | constructor(firestack, options = {}) { 15 | super(firestack, options); 16 | 17 | this.requestedPermissions = Object.assign({}, defaultPermissions, options.permissions); 18 | } 19 | get namespace() { 20 | return 'firestack:cloudMessaging' 21 | } 22 | getToken() { 23 | this.log.info('getToken for cloudMessaging'); 24 | return promisify('getToken', FirestackCloudMessaging)(); 25 | } 26 | 27 | // Request FCM permissions 28 | requestPermissions(requestedPermissions = {}) { 29 | if (Platform.OS === 'ios') { 30 | const mergedRequestedPermissions = Object.assign({}, 31 | this.requestedPermissions, 32 | requestedPermissions); 33 | return promisify('requestPermissions', FirestackCloudMessaging)(mergedRequestedPermissions) 34 | .then(perms => { 35 | 36 | return perms; 37 | }); 38 | } 39 | } 40 | 41 | sendMessage(details:Object = {}, type:string='local') { 42 | const methodName = `send${type == 'local' ? 'Local' : 'Remote'}` 43 | this.log.info('sendMessage', methodName, details); 44 | return promisify(methodName, FirestackCloudMessaging)(details); 45 | } 46 | scheduleMessage(details:Object = {}, type:string='local') { 47 | const methodName = `schedule${type == 'local' ? 'Local' : 'Remote'}` 48 | return promisify(methodName, FirestackCloudMessaging)(details); 49 | } 50 | // OLD 51 | send(senderId, messageId, messageType, msg){ 52 | return promisify('send', FirestackCloudMessaging)(senderId, messageId, messageType, msg); 53 | } 54 | // 55 | listenForTokenRefresh(callback) { 56 | this.log.info('Setting up listenForTokenRefresh callback'); 57 | const sub = this._on('FirestackRefreshToken', callback, FirestackCloudMessagingEvt); 58 | return promisify(() => sub, FirestackCloudMessaging)(sub); 59 | } 60 | unlistenForTokenRefresh() { 61 | this.log.info('Unlistening for TokenRefresh'); 62 | this._off('FirestackRefreshToken'); 63 | } 64 | subscribeToTopic(topic) { 65 | this.log.info('subscribeToTopic ' + topic); 66 | const finalTopic = `/topics/${topic}` 67 | return promisify('subscribeToTopic', FirestackCloudMessaging)(finalTopic); 68 | } 69 | unsubscribeFromTopic(topic) { 70 | this.log.info('unsubscribeFromTopic ' + topic); 71 | const finalTopic = `/topics/${topic}` 72 | return promisify('unsubscribeFromTopic', FirestackCloudMessaging)(finalTopic); 73 | } 74 | // New api 75 | onRemoteMessage(callback) { 76 | this.log.info('On remote message callback'); 77 | const sub = this._on('messaging_remote_event_received', callback, FirestackCloudMessagingEvt); 78 | return promisify(() => sub, FirestackCloudMessaging)(sub); 79 | } 80 | 81 | onLocalMessage(callback) { 82 | this.log.info('on local callback'); 83 | const sub = this._on('messaging_local_event_received', callback, FirestackCloudMessagingEvt); 84 | return promisify(() => sub, FirestackCloudMessaging)(sub); 85 | } 86 | 87 | // Original API 88 | listenForReceiveNotification(callback) { 89 | this.log.info('Setting up listenForReceiveNotification callback'); 90 | const sub = this._on('FirestackReceiveNotification', callback, FirestackCloudMessagingEvt); 91 | return promisify(() => sub, FirestackCloudMessaging)(sub); 92 | } 93 | unlistenForReceiveNotification() { 94 | this.log.info('Unlistening for ReceiveNotification'); 95 | this._off('FirestackRefreshToken'); 96 | } 97 | listenForReceiveUpstreamSend(callback) { 98 | this.log.info('Setting up send callback'); 99 | const sub = this._on('FirestackUpstreamSend', callback, FirestackCloudMessagingEvt); 100 | return promisify(() => sub, FirestackCloudMessaging)(sub); 101 | } 102 | unlistenForReceiveUpstreamSend() { 103 | this.log.info('Unlistening for send'); 104 | this._off('FirestackUpstreamSend'); 105 | } 106 | } 107 | 108 | export default CloudMessaging -------------------------------------------------------------------------------- /lib/modules/database.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Database representation wrapper 3 | */ 4 | import {NativeModules, NativeEventEmitter} from 'react-native'; 5 | const FirestackDatabase = NativeModules.FirestackDatabase; 6 | const FirestackDatabaseEvt = new NativeEventEmitter(FirestackDatabase); 7 | 8 | import promisify from '../utils/promisify' 9 | import { Base, ReferenceBase } from './base' 10 | 11 | let dbSubscriptions = {}; 12 | 13 | class DataSnapshot { 14 | static key:String; 15 | static value:Object; 16 | static exists:boolean; 17 | static hasChildren:boolean; 18 | static childrenCount:Number; 19 | static childKeys:String[]; 20 | 21 | constructor(ref, snapshot) { 22 | this.ref = ref; 23 | this.key = snapshot.key; 24 | this.value = snapshot.value; 25 | this.exists = snapshot.exists || true; 26 | this.priority = snapshot.priority; 27 | this.hasChildren = snapshot.hasChildren || false; 28 | this.childrenCount = snapshot.childrenCount || 0; 29 | this.childKeys = snapshot.childKeys || []; 30 | } 31 | 32 | val() { 33 | return this.value; 34 | } 35 | 36 | forEach(fn) { 37 | (this.childKeys || []) 38 | .forEach(key => fn({key: key, value: this.value[key]})) 39 | } 40 | 41 | map(fn) { 42 | let arr = []; 43 | this.forEach(item => arr.push(fn(item))) 44 | return arr; 45 | } 46 | 47 | reverseMap(fn) { 48 | return this.map(fn).reverse(); 49 | } 50 | } 51 | 52 | class DatabaseOnDisconnect { 53 | constructor(ref) { 54 | this.ref = ref; 55 | } 56 | 57 | setValue(val) { 58 | const path = this.ref.dbPath(); 59 | if (typeof val == 'string') { 60 | return promisify('onDisconnectSetString', FirestackDatabase)(path, val); 61 | } else if (typeof val == 'object') { 62 | return promisify('onDisconnectSetObject', FirestackDatabase)(path, val); 63 | } 64 | } 65 | 66 | remove() { 67 | const path = this.ref.dbPath(); 68 | return promisify('onDisconnectRemove', FirestackDatabase)(path); 69 | } 70 | 71 | cancel() { 72 | const path = this.ref.dbPath(); 73 | return promisify('onDisconnectCancel', FirestackDatabase)(path); 74 | } 75 | } 76 | 77 | class DatabaseQuery { 78 | static ref: DatabaseRef; 79 | static orderBy: String[]; 80 | static limit: String[]; 81 | static filters: Object; 82 | 83 | constructor(ref) { 84 | this.ref = ref; 85 | this.reset(); 86 | } 87 | 88 | setOrderBy(name, ...args) { 89 | this.orderBy = [name].concat(args); 90 | return this.ref; 91 | } 92 | 93 | setLimit(name, ...args) { 94 | this.limit = [name].concat(args); 95 | return this.ref; 96 | } 97 | 98 | setFilter(name, ...args) { 99 | this.filters[name] = args.filter(n => n != undefined); 100 | return this.ref; 101 | } 102 | 103 | build() { 104 | const argsSeparator = ':' 105 | let modifiers = []; 106 | if (this.orderBy) { 107 | modifiers.push(this.orderBy.join(argsSeparator)); 108 | } 109 | if (this.limit) { 110 | modifiers.push(this.limit.join(argsSeparator)); 111 | } 112 | Object.keys(this.filters) 113 | .forEach(key => { 114 | let filter = this.filters[key]; 115 | if (filter) { 116 | const cleanFilters = filter.filter((f) => typeof f !== "undefined"); 117 | const filterArgs = ([key].concat(cleanFilters)).join(argsSeparator); 118 | modifiers.push(filterArgs); 119 | } 120 | }) 121 | return modifiers; 122 | } 123 | 124 | reset() { 125 | this.orderBy = null; 126 | this.limit = null; 127 | this.filters = {}; 128 | ['startAt', 'endAt', 'equalTo'] 129 | .forEach(key => this.filters[key] = null); 130 | return this.ref; 131 | } 132 | } 133 | 134 | // https://firebase.google.com/docs/reference/js/firebase.database.Reference 135 | const separator = '/'; 136 | class DatabaseRef extends ReferenceBase { 137 | constructor(db, path) { 138 | super(db.firestack, path); 139 | 140 | this.db = db; 141 | this.query = new DatabaseQuery(this); 142 | this.listeners = {}; 143 | 144 | // Aliases 145 | this.get = this.getAt; 146 | this.set = this.setAt; 147 | this.update = this.updateAt; 148 | this.remove = this.removeAt; 149 | 150 | this.log.debug('Created new DatabaseRef', this.dbPath()); 151 | } 152 | 153 | // Parent roots 154 | parent() { 155 | const parentPaths = this.path.slice(0, -1); 156 | return new DatabaseRef(this.db, parentPaths); 157 | } 158 | 159 | root() { 160 | return new DatabaseRef(this.db, []); 161 | } 162 | 163 | child(...paths) { 164 | return new DatabaseRef(this.db, this.path.concat(paths)); 165 | } 166 | 167 | keepSynced(bool) { 168 | const path = this.dbPath(); 169 | return promisify('keepSynced', FirestackDatabase)(path, bool); 170 | } 171 | 172 | // Get the value of a ref either with a key 173 | getAt() { 174 | const path = this.dbPath(); 175 | const modifiers = this.dbModifiers(); 176 | return promisify('onOnce', FirestackDatabase)(path, modifiers, 'value'); 177 | } 178 | 179 | setAt(val) { 180 | const path = this.dbPath(); 181 | const value = this._serializeValue(val); 182 | return promisify('set', FirestackDatabase)(path, value) 183 | } 184 | 185 | updateAt(val) { 186 | const path = this.dbPath(); 187 | const value = this._serializeValue(val); 188 | return promisify('update', FirestackDatabase)(path, value) 189 | } 190 | 191 | removeAt(key) { 192 | const path = this.dbPath(); 193 | return promisify('remove', FirestackDatabase)(path) 194 | } 195 | 196 | push(val={}) { 197 | const path = this.dbPath(); 198 | const value = this._serializeValue(val); 199 | return promisify('push', FirestackDatabase)(path, value) 200 | .then(({ref}) => { 201 | return new DatabaseRef(this.db, ref.split(separator)) 202 | }) 203 | } 204 | 205 | on(evt, cb) { 206 | const path = this.dbPath(); 207 | const modifiers = this.dbModifiers(); 208 | return this.db.on(path, evt, cb) 209 | .then(({callback, subscriptions}) => { 210 | return promisify('on', FirestackDatabase)(path, modifiers, evt) 211 | .then(() => { 212 | this.listeners[evt] = subscriptions; 213 | callback(this); 214 | return subscriptions; 215 | }) 216 | }); 217 | } 218 | 219 | once(evt='once', cb) { 220 | const path = this.dbPath(); 221 | const modifiers = this.dbModifiers(); 222 | return promisify('onOnce', FirestackDatabase)(path, modifiers, evt) 223 | .then(({snapshot}) => new DataSnapshot(this, snapshot)) 224 | .then(snapshot => { 225 | if (cb && typeof cb === 'function') { 226 | cb(snapshot); 227 | } 228 | return snapshot; 229 | }) 230 | } 231 | 232 | off(evt='', origCB) { 233 | const path = this.dbPath(); 234 | return this.db.off(path, evt, origCB) 235 | .then(({callback, subscriptions}) => { 236 | if (dbSubscriptions[path] && dbSubscriptions[path][evt] && dbSubscriptions[path][evt].length > 0) { 237 | return subscriptions; 238 | } 239 | 240 | return promisify('off', FirestackDatabase)(path, evt) 241 | .then(() => { 242 | // subscriptions.forEach(sub => sub.remove()); 243 | delete this.listeners[evt]; 244 | callback(this); 245 | return subscriptions; 246 | }) 247 | }) 248 | .catch(err => { 249 | console.error('Never get here', err); 250 | }) 251 | } 252 | 253 | cleanup() { 254 | let promises = Object.keys(this.listeners) 255 | .map(key => this.off(key)) 256 | return Promise.all(promises); 257 | } 258 | 259 | // Sanitize value 260 | // As Firebase cannot store date objects. 261 | _serializeValue(obj={}) { 262 | return Object.keys(obj).reduce((sum, key) => { 263 | let val = obj[key]; 264 | if (val instanceof Date) { 265 | val = val.toISOString(); 266 | } 267 | return { 268 | ...sum, 269 | [key]: val 270 | } 271 | }, {}); 272 | } 273 | 274 | _deserializeValue(obj={}) { 275 | return Object.keys(obj).reduce((sum, key) => { 276 | let val = obj[key]; 277 | if (val instanceof Date) { 278 | val = val.getTime(); 279 | } 280 | return { 281 | ...sum, 282 | [key]: val 283 | } 284 | }, {}); 285 | } 286 | 287 | // Modifiers 288 | orderByKey() { 289 | return this.query.setOrderBy('orderByKey'); 290 | } 291 | 292 | orderByPriority() { 293 | return this.query.setOrderBy('orderByPriority'); 294 | } 295 | 296 | orderByValue() { 297 | return this.query.setOrderBy('orderByValue'); 298 | } 299 | 300 | orderByChild(key) { 301 | return this.query.setOrderBy('orderByChild', key); 302 | } 303 | 304 | // Limits 305 | limitToLast(limit) { 306 | return this.query.setLimit('limitToLast', limit); 307 | } 308 | 309 | limitToFirst(limit) { 310 | return this.query.setLimit('limitToFirst', limit); 311 | } 312 | 313 | // Filters 314 | equalTo(value, key) { 315 | return this.query.setFilter('equalTo', value, key); 316 | } 317 | 318 | endAt(value, key) { 319 | return this.query.setFilter('endAt', value, key); 320 | } 321 | 322 | startAt(value, key) { 323 | return this.query.setFilter('startAt', value, key); 324 | } 325 | 326 | presence(path) { 327 | const presence = this.firestack.presence; 328 | const ref = path ? this.child(path) : this; 329 | return presence.ref(ref, this.dbPath()); 330 | } 331 | 332 | // onDisconnect 333 | onDisconnect() { 334 | return new DatabaseOnDisconnect(this); 335 | } 336 | 337 | // attributes 338 | get fullPath() { 339 | return this.dbPath(); 340 | } 341 | 342 | get name() { 343 | return this.path.splice(-1); 344 | } 345 | 346 | dbPath() { 347 | let path = this.path; 348 | let pathStr = (path.length > 0 ? path.join('/') : '/'); 349 | if (pathStr[0] != '/') { 350 | pathStr = `/${pathStr}` 351 | } 352 | return pathStr; 353 | } 354 | 355 | dbModifiers() { 356 | const modifiers = this.query.build(); 357 | this.query.reset(); // should we reset this 358 | return modifiers; 359 | } 360 | 361 | get namespace() { 362 | return `firestack:dbRef` 363 | } 364 | } 365 | 366 | export class Database extends Base { 367 | 368 | constructor(firestack, options={}) { 369 | super(firestack, options); 370 | this.log.debug('Created new Database instance', this.options); 371 | 372 | this.persistenceEnabled = false; 373 | this.successListener = null; 374 | this.errorListener = null; 375 | this.refs = {}; 376 | } 377 | 378 | ref(...path) { 379 | const key = this._pathKey(path); 380 | if (!this.refs[key]) { 381 | const ref = new DatabaseRef(this, path); 382 | this.refs[key] = ref; 383 | } 384 | return this.refs[key]; 385 | } 386 | 387 | setPersistence(enable=true) { 388 | let promise; 389 | if (this.persistenceEnabled !== enable) { 390 | this.log.debug(`${enable ? 'Enabling' : 'Disabling'} persistence`); 391 | promise = this.whenReady(promisify('enablePersistence', FirestackDatabase)(enable)); 392 | this.persistenceEnabled = enable; 393 | } else { 394 | promise = this.whenReady(Promise.resolve({status: "Already enabled"})) 395 | } 396 | 397 | return promise; 398 | } 399 | 400 | handleDatabaseEvent(evt) { 401 | const body = evt.body; 402 | const path = body.path; 403 | const evtName = body.eventName; 404 | 405 | const subscriptions = dbSubscriptions[path]; 406 | 407 | if (subscriptions) { 408 | const cbs = subscriptions[evtName]; 409 | cbs.forEach(cb => { 410 | if (cb && typeof(cb) === 'function') { 411 | const snap = new DataSnapshot(this, body.snapshot); 412 | this.log.debug('database_event received', path, evtName); 413 | cb(snap, body); 414 | } 415 | }); 416 | } 417 | } 418 | 419 | handleDatabaseError(evt) { 420 | this.log.debug('handleDatabaseError ->', evt); 421 | } 422 | 423 | on(path, evt, cb) { 424 | const key = this._pathKey(path); 425 | 426 | if (!dbSubscriptions[key]) { 427 | dbSubscriptions[key] = {}; 428 | } 429 | 430 | if (!dbSubscriptions[key][evt]) { 431 | dbSubscriptions[key][evt] = []; 432 | } 433 | dbSubscriptions[key][evt].push(cb); 434 | 435 | if (!this.successListener) { 436 | this.successListener = FirestackDatabaseEvt 437 | .addListener( 438 | 'database_event', 439 | this.handleDatabaseEvent.bind(this)); 440 | } 441 | 442 | if (!this.errorListener) { 443 | this.errorListener = FirestackDatabaseEvt 444 | .addListener( 445 | 'database_error', 446 | this.handleDatabaseError.bind(this)); 447 | } 448 | 449 | const callback = (ref) => { 450 | const key = this._pathKey(ref.path); 451 | this.refs[key] = ref; 452 | } 453 | const subscriptions = [this.successListener, this.errorListener]; 454 | return Promise.resolve({callback, subscriptions}); 455 | } 456 | 457 | off(path, evt, origCB) { 458 | const key = this._pathKey(path); 459 | // Remove subscription 460 | if (dbSubscriptions[key]) { 461 | if (!evt || evt === "") { 462 | dbSubscriptions[key] = {}; 463 | } else if (dbSubscriptions[key][evt]) { 464 | if (origCB) { 465 | dbSubscriptions[key][evt].splice(dbSubscriptions[key][evt].indexOf(origCB), 1); 466 | } else { 467 | delete dbSubscriptions[key][evt]; 468 | } 469 | } 470 | 471 | if (Object.keys(dbSubscriptions[key]).length <= 0) { 472 | // there are no more subscriptions 473 | // so we can unwatch 474 | delete dbSubscriptions[key] 475 | } 476 | if (Object.keys(dbSubscriptions).length == 0) { 477 | if (this.successListener) { 478 | this.successListener.remove(); 479 | this.successListener = null; 480 | } 481 | if (this.errorListener) { 482 | this.errorListener.remove(); 483 | this.errorListener = null; 484 | } 485 | } 486 | } 487 | const callback = (ref) => { 488 | const key = this._pathKey(ref.path); 489 | delete this.refs[key]; 490 | } 491 | const subscriptions = [this.successListener, this.errorListener]; 492 | return Promise.resolve({callback, subscriptions}); 493 | } 494 | 495 | cleanup() { 496 | let promises = Object.keys(this.refs) 497 | .map(key => this.refs[key]) 498 | .map(ref => ref.cleanup()) 499 | return Promise.all(promises); 500 | } 501 | 502 | release(...path) { 503 | const key = this._pathKey(path); 504 | if (this.refs[key]) { 505 | delete this.refs[key]; 506 | } 507 | } 508 | 509 | _pathKey(...path) { 510 | return path.join('-'); 511 | } 512 | 513 | get namespace() { 514 | return 'firestack:database' 515 | } 516 | } 517 | 518 | export default Database 519 | -------------------------------------------------------------------------------- /lib/modules/presence.js: -------------------------------------------------------------------------------- 1 | import invariant from 'invariant' 2 | import promisify from '../utils/promisify' 3 | import { Base, ReferenceBase } from './base' 4 | 5 | class PresenceRef extends ReferenceBase { 6 | constructor(presence, ref, pathParts) { 7 | super(presence.firestack); 8 | 9 | this.presence = presence; 10 | const db = this.firestack.database; 11 | this.ref = ref; 12 | this.lastOnlineRef = this.ref.child('lastOnline'); 13 | 14 | this._connectedRef = db.ref('.info/connected'); 15 | this._pathParts = pathParts; 16 | 17 | this._onConnect = []; 18 | } 19 | 20 | setOnline() { 21 | this.ref.setAt({online: true}) 22 | this._connectedRef.on('value', (snapshot) => { 23 | const val = snapshot.val(); 24 | if (val) { 25 | // add self to connection list 26 | // this.ref.push() 27 | this.ref.setAt({ 28 | online: true 29 | }) 30 | .then(() => { 31 | this._disconnect(); 32 | 33 | this._onConnect.forEach(fn => { 34 | if (fn && typeof fn === 'function') { 35 | fn.bind(this)(this.ref); 36 | } 37 | }) 38 | }) 39 | } 40 | }); 41 | return this; 42 | } 43 | 44 | setOffline() { 45 | if (this.ref) { 46 | this.ref.setAt({online: false}) 47 | .then(() => this.ref.off('value')) 48 | this.presence.off(this._pathParts); 49 | } 50 | return this; 51 | } 52 | 53 | _disconnect() { 54 | if (this.ref) { 55 | this.ref.onDisconnect() 56 | .setValue({online: false}); 57 | // set last online time 58 | this.lastOnlineRef.onDisconnect() 59 | .setValue(this.firestack.ServerValue.TIMESTAMP) 60 | } 61 | } 62 | 63 | _pathKey() { 64 | return this._pathParts.join('/'); 65 | } 66 | 67 | onConnect(cb) { 68 | this._onConnect.push(cb); 69 | return this; 70 | } 71 | 72 | } 73 | 74 | export class Presence extends Base { 75 | constructor(firestack, options={}) { 76 | super(firestack, options); 77 | 78 | this.instances = {}; 79 | this.path = ['/presence/connections']; 80 | } 81 | 82 | on(key) { 83 | invariant(key, 'You must supply a key for presence'); 84 | const path = this.path.concat(key); 85 | const pathKey = this._presenceKey(path); 86 | if (!this.instances[pathKey]) { 87 | const _ref = this.firestack.database.ref(pathKey); 88 | this.log.debug('Created new presence object for ', pathKey) 89 | const inst = new PresenceRef(this, _ref, path); 90 | 91 | this.instances[pathKey] = inst; 92 | } 93 | 94 | return this.instances[pathKey]; 95 | } 96 | 97 | off(path) { 98 | const pathKey = this._presenceKey(path); 99 | if (this.instances[pathKey]) { 100 | delete this.instances[pathKey]; 101 | } 102 | } 103 | 104 | ref(dbRef, path) { 105 | return new PresenceRef(this, dbRef, path); 106 | } 107 | 108 | _presenceKey(path) { 109 | return (path || this.path).join('/'); 110 | } 111 | 112 | get namespace() { 113 | return 'firestack:presence' 114 | } 115 | } 116 | 117 | export default Presence; -------------------------------------------------------------------------------- /lib/modules/remoteConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration class 3 | */ 4 | const defaultExpiration = 60 * 60 * 24; // one day 5 | export class RemoteConfig { 6 | constructor(options) { 7 | this.config = options || {}; 8 | 9 | this.setDefaultRemoteConfig(options) 10 | .then(() => this.configured = true); 11 | } 12 | 13 | setDefaultRemoteConfig(options) { 14 | return promisify('setDefaultRemoteConfig')(options); 15 | } 16 | 17 | fetchWithExpiration(expirationSeconds=defaultExpiration) { 18 | return promisify('fetchWithExpiration')(expirationSeconds) 19 | } 20 | 21 | config(name) { 22 | return promisify('configValueForKey')(name); 23 | } 24 | 25 | setDev() { 26 | return promisify('setDev')(); 27 | } 28 | } 29 | 30 | export default RemoteConfig; -------------------------------------------------------------------------------- /lib/modules/storage.js: -------------------------------------------------------------------------------- 1 | 2 | import {NativeModules, NativeEventEmitter} from 'react-native'; 3 | const FirestackStorage = NativeModules.FirestackStorage; 4 | const FirestackStorageEvt = new NativeEventEmitter(FirestackStorage); 5 | 6 | import promisify from '../utils/promisify' 7 | import { Base, ReferenceBase } from './base' 8 | 9 | console.log('FirestackStorage ---->', FirestackStorage); 10 | 11 | class StorageRef extends ReferenceBase { 12 | constructor(storage, path) { 13 | super(storage.firestack, path); 14 | 15 | this.storage = storage; 16 | } 17 | 18 | downloadUrl() { 19 | const path = this.pathToString(); 20 | return promisify('downloadUrl', FirestackStorage)(this.storage.storageUrl, path); 21 | } 22 | 23 | /** 24 | * Downloads a reference to the device 25 | * @param {String} downloadPath Where to store the file 26 | * @return {Promise} 27 | */ 28 | download (downloadPath, cb) { 29 | let callback = cb; 30 | if (!callback || typeof callback !== 'function') { 31 | callback = (evt) => {}; 32 | } 33 | 34 | const listeners = []; 35 | listeners.push(this.storage._addListener('download_progress', callback)); 36 | listeners.push(this.storage._addListener('download_paused', callback)); 37 | listeners.push(this.storage._addListener('download_resumed', callback)); 38 | 39 | const path = this.pathToString(); 40 | return promisify('downloadFile', FirestackStorage)(this.storage.storageUrl, path, downloadPath) 41 | .then((res) => { 42 | console.log('res --->', res); 43 | listeners.forEach(this.storage._removeListener); 44 | return res; 45 | }) 46 | .catch(err => { 47 | console.log('Got an error ->', err); 48 | }) 49 | } 50 | } 51 | 52 | export class Storage extends Base { 53 | constructor(firestack, options={}) { 54 | super(firestack, options); 55 | 56 | if (this.options.storageBucket) { 57 | this.setStorageUrl(this.options.storageBucket); 58 | } 59 | 60 | this.refs = {}; 61 | } 62 | 63 | ref(...path) { 64 | const key = this._pathKey(path); 65 | if (!this.refs[key]) { 66 | const ref = new StorageRef(this, path); 67 | this.refs[key] = ref; 68 | } 69 | return this.refs[key]; 70 | } 71 | 72 | /** 73 | * Upload a filepath 74 | * @param {string} name The destination for the file 75 | * @param {string} filepath The local path of the file 76 | * @param {object} metadata An object containing metadata 77 | * @return {Promise} 78 | */ 79 | uploadFile(name, filepath, metadata={}, cb) { 80 | let callback = cb; 81 | if (!callback || typeof callback !== 'function') { 82 | callback = (evt) => {} 83 | } 84 | 85 | filepath = filepath.replace("file://", ""); 86 | 87 | const listeners = []; 88 | listeners.push(this._addListener('upload_progress', callback)); 89 | listeners.push(this._addListener('upload_paused', callback)); 90 | listeners.push(this._addListener('upload_resumed', callback)); 91 | return promisify('uploadFile', FirestackStorage)(this.storageUrl, name, filepath, metadata) 92 | .then((res) => { 93 | listeners.forEach(this._removeListener); 94 | return res; 95 | }); 96 | } 97 | 98 | getRealPathFromURI(uri) { 99 | return promisify('getRealPathFromURI', FirestackStorage)(uri); 100 | } 101 | 102 | _addListener(evt, cb) { 103 | return FirestackStorageEvt.addListener(evt, cb); 104 | } 105 | 106 | _removeListener(evt) { 107 | return FirestackStorageEvt.removeListener(evt); 108 | } 109 | 110 | setStorageUrl(url) { 111 | // return promisify('setStorageUrl', FirestackStorage)(url); 112 | this.storageUrl = `gs://${url}`; 113 | } 114 | 115 | _pathKey(...path) { 116 | return path.join('-'); 117 | } 118 | 119 | static constants = { 120 | 'MAIN_BUNDLE_PATH': FirestackStorage.MAIN_BUNDLE_PATH, 121 | 'CACHES_DIRECTORY_PATH': FirestackStorage.CACHES_DIRECTORY_PATH, 122 | 'DOCUMENT_DIRECTORY_PATH': FirestackStorage.DOCUMENT_DIRECTORY_PATH, 123 | 'EXTERNAL_DIRECTORY_PATH': FirestackStorage.EXTERNAL_DIRECTORY_PATH, 124 | 'EXTERNAL_STORAGE_DIRECTORY_PATH': FirestackStorage.EXTERNAL_STORAGE_DIRECTORY_PATH, 125 | 'TEMP_DIRECTORY_PATH': FirestackStorage.TEMP_DIRECTORY_PATH, 126 | 'LIBRARY_DIRECTORY_PATH': FirestackStorage.LIBRARY_DIRECTORY_PATH, 127 | 'FILETYPE_REGULAR': FirestackStorage.FILETYPE_REGULAR, 128 | 'FILETYPE_DIRECTORY': FirestackStorage.FILETYPE_DIRECTORY 129 | }; 130 | 131 | get namespace() { 132 | return 'firestack:storage' 133 | } 134 | } 135 | 136 | export default Storage 137 | -------------------------------------------------------------------------------- /lib/utils/__tests__/log-test.js: -------------------------------------------------------------------------------- 1 | jest.unmock('../log'); 2 | jest.unmock('../window-or-global') 3 | import Log from '../log'; 4 | import root from '../window-or-global'; 5 | 6 | describe('Log', () => { 7 | let log; 8 | 9 | beforeEach(() => { 10 | root.localStorage = {}; 11 | }) 12 | 13 | it('does not explode on import', () => { 14 | log = new Log('test'); 15 | expect(log).toBeDefined(); 16 | }); 17 | 18 | it('can be enabled', () => { 19 | log = new Log('test', true); 20 | expect(log.enabled).toBeTruthy(); 21 | }); 22 | 23 | it('can be disabled', () => { 24 | log = new Log('test', true); 25 | expect(log.enabled).toBeTruthy(); 26 | log.enable(false); 27 | expect(log.enabled).toBeFalsy(); 28 | }); 29 | 30 | describe('levels', () => { 31 | beforeEach(() => { 32 | log = new Log('test', true); 33 | }); 34 | 35 | it('has an info level', () => { 36 | expect(() => { 37 | log.info('Testing') 38 | }).not.toThrow(); 39 | }); 40 | 41 | it('has a debug level', () => { 42 | expect(() => { 43 | log.debug('Testing') 44 | }).not.toThrow(); 45 | }); 46 | 47 | it('has an error level', () => { 48 | expect(() => { 49 | log.error('Testing') 50 | }).not.toThrow(); 51 | }); 52 | 53 | }) 54 | 55 | }) -------------------------------------------------------------------------------- /lib/utils/__tests__/promisify-test.js: -------------------------------------------------------------------------------- 1 | jest.unmock('../promisify'); 2 | 3 | import promisify from '../promisify'; 4 | import sinon from 'sinon' 5 | 6 | describe('promisify', () => { 7 | let NativeModule; 8 | 9 | beforeEach(() => { 10 | NativeModule = { 11 | nativeFn: sinon.spy(), 12 | nativeRet: (callback) => callback(null, true), 13 | nativeErr: (callback) => callback({type: 'error'}) 14 | } 15 | }); 16 | 17 | it('returns a function to be called', () => { 18 | expect( 19 | typeof promisify('nativeFn', NativeModule) 20 | ).toEqual('function') 21 | }) 22 | 23 | it('returns a function which returns a promise', () => { 24 | const promise = promisify('nativeFn', NativeModule)(); 25 | expect(promise instanceof Promise).toBeTruthy(); 26 | }); 27 | 28 | it('calls the native function when called', () => { 29 | const promise = promisify('nativeFn', NativeModule)(); 30 | expect(NativeModule.nativeFn.called).toBeTruthy(); 31 | }); 32 | 33 | it('resolves its promise when the native function returns', (done) => { 34 | const promise = promisify('nativeRet', NativeModule)(); 35 | promise.then((res) => { 36 | expect(res).toBeTruthy(); 37 | done(); 38 | }); 39 | }); 40 | 41 | it('rejects when the natie function returns an error', (done) => { 42 | const promise = promisify('nativeErr', NativeModule)(); 43 | promise.catch((err) => { 44 | expect(err.type).toBe('error') 45 | done(); 46 | }) 47 | }) 48 | 49 | }) 50 | -------------------------------------------------------------------------------- /lib/utils/__tests__/singleton-test.js: -------------------------------------------------------------------------------- 1 | jest.unmock('../singleton'); 2 | 3 | import Singleton from '../singleton'; 4 | import sinon from 'sinon' 5 | 6 | class TestClass extends Singleton { 7 | static _createdCount; 8 | 9 | constructor(num) { 10 | super(); 11 | TestClass._createdCount += TestClass._createdCount + 1; 12 | } 13 | 14 | get namespace() { 15 | return 'firestack:TestClass' 16 | } 17 | 18 | static get createdCount() { 19 | return TestClass._createdCount || 0; 20 | } 21 | 22 | static reset() { 23 | TestClass._createdCount = 0; 24 | } 25 | } 26 | 27 | describe('singleton', () => { 28 | let tc; 29 | 30 | beforeEach(() => { 31 | TestClass.reset(); 32 | }) 33 | 34 | it('creates an instance of the class', () => { 35 | expect(TestClass.createdCount).toBe(0); 36 | new TestClass(); 37 | expect(TestClass.createdCount).toBe(1); 38 | }) 39 | 40 | it('returns the singleton instance of the class when called a subsequent times', () => { 41 | expect(TestClass.createdCount).toBe(0); 42 | tc = new TestClass() 43 | let tc2 = new TestClass() 44 | expect(tc).toEqual(tc2); 45 | }) 46 | 47 | }); -------------------------------------------------------------------------------- /lib/utils/log.js: -------------------------------------------------------------------------------- 1 | // document hack 2 | import root from './window-or-global' 3 | 4 | let bows; 5 | (function (base) { 6 | window = base || window 7 | if(!window.localStorage) window.localStorage = {}; 8 | })(root); 9 | 10 | const levels = [ 11 | 'warn', 'info', 'error', 'debug' 12 | ]; 13 | 14 | export class Log { 15 | constructor(namespace) { 16 | this._namespace = namespace || 'firestack'; 17 | this.loggers = {}; 18 | // Add the logging levels for each level 19 | levels 20 | .forEach(level => this[level] = (...args) => this._log(level)(...args)); 21 | } 22 | 23 | static enable(booleanOrStringDebug) { 24 | window.localStorage.debug = 25 | typeof booleanOrStringDebug === 'string' ? 26 | (booleanOrStringDebug === '*' ? true : booleanOrStringDebug) : 27 | (booleanOrStringDebug instanceof RegExp ? booleanOrStringDebug.toString() : booleanOrStringDebug); 28 | 29 | window.localStorage.debugColors = !!window.localStorage.debug; 30 | } 31 | 32 | _log(level) { 33 | if (!this.loggers[level]) { 34 | (function() { 35 | const bows = require('bows'); 36 | bows.config({ padLength: 20 }); 37 | this.loggers[level] = bows(this._namespace, `[${level}]`); 38 | }.bind(this))(); 39 | } 40 | return this.loggers[level]; 41 | } 42 | } 43 | 44 | export default Log; -------------------------------------------------------------------------------- /lib/utils/promisify.js: -------------------------------------------------------------------------------- 1 | export const promisify = (fn, NativeModule) => (...args) => { 2 | return new Promise((resolve, reject) => { 3 | const handler = (err, resp) => { 4 | err ? reject(err) : resolve(resp); 5 | } 6 | args.push(handler); 7 | (typeof fn === 'function' ? fn : NativeModule[fn]) 8 | .call(NativeModule, ...args); 9 | }); 10 | }; 11 | 12 | export default promisify -------------------------------------------------------------------------------- /lib/utils/singleton.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Symbol = require('es6-symbol'); 4 | 5 | class Singleton { 6 | constructor() { 7 | let Class = this.constructor; 8 | 9 | if(!Class[this.singleton]) { 10 | Class[this.singleton] = this; 11 | } 12 | 13 | return Class[this.singleton]; 14 | } 15 | 16 | static get instance() { 17 | if(!this[this.singleton]) { 18 | this[this.singleton] = new this; 19 | } 20 | 21 | return this[this.singleton]; 22 | } 23 | 24 | static set instance(instance) { 25 | this[this.singleton] = instance; 26 | return this[this.singleton]; 27 | } 28 | 29 | static get singleton() { 30 | return Symbol(this.namespace); 31 | } 32 | 33 | static reset() { 34 | delete this[this.singleton] 35 | } 36 | } 37 | 38 | export default Singleton; -------------------------------------------------------------------------------- /lib/utils/window-or-global.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // https://github.com/purposeindustries/window-or-global 3 | module.exports = (typeof self === 'object' && self.self === self && self) || 4 | (typeof global === 'object' && global.global === global && global) || 5 | this 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-firestack", 3 | "version": "2.3.8", 4 | "author": "Ari Lerner (https://fullstackreact.com)", 5 | "description": "A firebase v3 adapter", 6 | "main": "index", 7 | "scripts": { 8 | "start": "node node_modules/react-native/local-cli/cli.js start", 9 | "build": "./node_modules/.bin/babel --source-maps=true --out-dir=dist .", 10 | "dev": "npm run compile -- --watch", 11 | "lint": "eslint ./src", 12 | "publish_pages": "gh-pages -d public/", 13 | "test": "./node_modules/.bin/mocha" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/fullstackreact/react-native-firestack.git" 18 | }, 19 | "jest": { 20 | "preset": "jest-react-native", 21 | "setupFiles": [], 22 | "unmockedModulePathPatterns": [ 23 | "./node_modules/react", 24 | "./node_modules/react-native", 25 | "./node_modules/react-native-mock", 26 | "./node_modules/react-addons-test-utils" 27 | ] 28 | }, 29 | "license": "ISC", 30 | "keywords": [ 31 | "react", 32 | "react-native", 33 | "react-native-firestack", 34 | "firestack", 35 | "firebase" 36 | ], 37 | "peerDependencies": { 38 | "react": "*", 39 | "react-native": "*" 40 | }, 41 | "rnpm": { 42 | "commands": { 43 | "prelink": "node_modules/react-native-firestack/bin/prepare.sh", 44 | "postlink": "node_modules/react-native-firestack/bin/cocoapods.sh" 45 | }, 46 | "ios": { 47 | "project": "ios/Firestack.xcodeproj" 48 | }, 49 | "android": { 50 | "packageInstance": "new FirestackPackage()" 51 | } 52 | }, 53 | "devDependencies": { 54 | "babel-jest": "^14.1.0", 55 | "babel-preset-react-native": "^1.9.0", 56 | "debug": "^2.2.0", 57 | "enzyme": "^2.4.1", 58 | "jest": "^14.1.0", 59 | "jest-react-native": "^14.1.3", 60 | "mocha": "^3.0.2", 61 | "react": "^15.3.0", 62 | "react-dom": "^15.3.0", 63 | "react-native-mock": "^0.2.6", 64 | "react-test-renderer": "^15.3.0", 65 | "should": "^11.1.0", 66 | "sinon": "^2.0.0-pre.2" 67 | }, 68 | "dependencies": { 69 | "bows": "^1.6.0", 70 | "es6-symbol": "^3.1.0", 71 | "invariant": "^2.2.2" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/firestack.test.js: -------------------------------------------------------------------------------- 1 | const should = require('should'); 2 | const { shallow } = require('enzyme'); 3 | const Firestack = require('..'); 4 | const React = require('react'); 5 | 6 | describe('Firestack', () => { 7 | 8 | let firestackInstance; 9 | 10 | before(() => { 11 | firestackInstance = new Firestack({ 12 | 13 | }); 14 | }); 15 | 16 | it('can be configured', () => { 17 | true.should.be.false; 18 | }); 19 | }); -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:babel-core/register 2 | --require react-native-mock/mock -------------------------------------------------------------------------------- /test/test-helper.js: -------------------------------------------------------------------------------- 1 | require('react-native-mock/mock'); 2 | 3 | var { NativeEventEmitter, NativeModules } = require('react-native'); 4 | 5 | NativeModules.Firestack = { 6 | 7 | } --------------------------------------------------------------------------------