├── .babelrc
├── test
├── mocha.opts
├── test-helper.js
└── firestack.test.js
├── index.js
├── firestack.android.js
├── firestack.ios.js
├── android
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── io
│ │ └── fullstack
│ │ └── firestack
│ │ ├── FirestackInstanceIdService.java
│ │ ├── FirestackPackage.java
│ │ ├── FirestackMessagingService.java
│ │ ├── FirestackModule.java
│ │ ├── FirestackUtils.java
│ │ ├── FirestackCloudMessaging.java
│ │ ├── FirestackAnalytics.java
│ │ └── FirestackStorage.java
├── .idea
│ └── gradle.xml
└── build.gradle
├── .npmignore
├── ios
├── Firestack.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ ├── xcuserdata
│ │ ├── Cheol.xcuserdatad
│ │ │ └── xcschemes
│ │ │ │ ├── xcschememanagement.plist
│ │ │ │ └── Firestack.xcscheme
│ │ └── auser.xcuserdatad
│ │ │ └── xcschemes
│ │ │ ├── xcschememanagement.plist
│ │ │ └── Firestack.xcscheme
│ └── project.pbxproj
├── Podfile.template
├── Firestack
│ ├── FirestackAnalytics.h
│ ├── FirestackStorage.h
│ ├── FirestackAuth.h
│ ├── FirestackCloudMessaging.h
│ ├── FirestackDatabase.h
│ ├── FirestackErrors.h
│ ├── Firestack.h
│ ├── FirestackAnalytics.m
│ ├── FirestackErrors.m
│ ├── FirestackEvents.h
│ ├── FirestackCloudMessaging.m
│ ├── FirestackStorage.m
│ └── Firestack.m
├── buildScript.sh
└── Podfile
├── lib
├── utils
│ ├── window-or-global.js
│ ├── promisify.js
│ ├── singleton.js
│ ├── __tests__
│ │ ├── singleton-test.js
│ │ ├── log-test.js
│ │ └── promisify-test.js
│ └── log.js
├── modules
│ ├── remoteConfig.js
│ ├── analytics.js
│ ├── base.js
│ ├── presence.js
│ ├── storage.js
│ ├── cloudmessaging.js
│ ├── authentication.js
│ └── database.js
├── firestack.js
└── firestackModule.js
├── bin
├── prepare.sh
└── cocoapods.sh
├── LICENSE
├── .gitignore
├── Firestack.podspec
├── package.json
└── Contributing.md
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react-native"]
3 | }
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --compilers js:babel-core/register
2 | --require react-native-mock/mock
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import Firestack from './firestack'
2 | export { FirestackModule } from './lib/firestackModule'
3 |
4 | export default Firestack
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/test/test-helper.js:
--------------------------------------------------------------------------------
1 | require('react-native-mock/mock');
2 |
3 | var { NativeEventEmitter, NativeModules } = require('react-native');
4 |
5 | NativeModules.Firestack = {
6 |
7 | }
--------------------------------------------------------------------------------
/.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/
--------------------------------------------------------------------------------
/ios/Firestack.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 | });
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/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/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/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.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/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/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
--------------------------------------------------------------------------------
/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/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;
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/__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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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;
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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