diff --git a/.eslintrc b/.eslintrc index 56b7f846c80..a652156d18c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -97,7 +97,7 @@ "jsonp", "otp", "Otp", "ascii", "Ascii", "substr", "Util", "actionsheet", "unmute", "otp", "Otp", "ascii", "Ascii", "substr", "Util", "actionsheet", "jsonp", "actionsheet", "ionicons", "denmark", "copenhagen", "unregister", "gcm", "unstarMessage", "Unstar", "wildcard_mentioned", - "lightbox", "resize", "remobile", "multiline", "uniqby", "zoe", "localizable", + "lightbox", "resize", "remobile", "multiline", "uniqby", "zoe", "localizable", "appid", "apns" ], "skipIfMatch": [ "http://[^s]*", diff --git a/ios/ZulipMobile.xcodeproj/project.pbxproj b/ios/ZulipMobile.xcodeproj/project.pbxproj index 291ac7cc08d..81f2b2e5a7c 100644 --- a/ios/ZulipMobile.xcodeproj/project.pbxproj +++ b/ios/ZulipMobile.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; 17449DB932594E96A837970F /* MaterialIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 51E1EDA028654E17A35C5BCA /* MaterialIcons.ttf */; }; 3556F3DAC2424EBE9ABF0A31 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2E41C86FA25744F998C2BB02 /* Zocial.ttf */; }; + 3C4249EB1EF6E09F00D245F1 /* libRNNotifications.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C4249E61EF6E05C00D245F1 /* libRNNotifications.a */; }; 48D1FC73615948D79D3BD31E /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A7F287C57D8A4354A8B6A3E7 /* Octicons.ttf */; }; 49692FC0562C40F3946EC6D4 /* Foundation.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A752523B8D8E49E792F653E1 /* Foundation.ttf */; }; 4B12DC82ECA043809695F227 /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9DB2E7AE39A448A98E7A4E4A /* Ionicons.ttf */; }; @@ -118,6 +119,13 @@ remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; remoteInfo = React; }; + 3C4249E51EF6E05C00D245F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3C4249C61EF6E05C00D245F1 /* RNNotifications.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RNNotifications; + }; 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; @@ -319,6 +327,8 @@ 2CFB39BBD9094475BB03A3E4 /* libRCTToast.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTToast.a; sourceTree = ""; }; 2E41C86FA25744F998C2BB02 /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = ""; }; 3074EDA2D113487A925E187C /* libRNVectorIcons.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNVectorIcons.a; sourceTree = ""; }; + 3C4249C61EF6E05C00D245F1 /* RNNotifications.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNNotifications.xcodeproj; path = "../node_modules/react-native-notifications/RNNotifications/RNNotifications.xcodeproj"; sourceTree = ""; }; + 3C4249EC1EF6E16500D245F1 /* ZulipMobile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = ZulipMobile.entitlements; path = ZulipMobile/ZulipMobile.entitlements; sourceTree = ""; }; 51E1EDA028654E17A35C5BCA /* MaterialIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"; sourceTree = ""; }; 6922647B10C64653A76BF3E5 /* RCTToast.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RCTToast.xcodeproj; path = "../node_modules/@remobile/react-native-toast/ios/RCTToast.xcodeproj"; sourceTree = ""; }; 6DA76D9085EA458BB979ED8D /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = ""; }; @@ -382,6 +392,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3C4249EB1EF6E09F00D245F1 /* libRNNotifications.a in Frameworks */, CFCFE5491F00158500C295CF /* libRCTCameraRoll.a in Frameworks */, A14EA8771EACE522009D9E83 /* libRCTAnimation.a in Frameworks */, 146834051AC3E58100842450 /* libReact.a in Frameworks */, @@ -488,6 +499,7 @@ 13B07FAE1A68108700A75B9A /* ZulipMobile */ = { isa = PBXGroup; children = ( + 3C4249EC1EF6E16500D245F1 /* ZulipMobile.entitlements */, 008F07F21AC5B25A0029DE68 /* main.jsbundle */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.m */, @@ -535,6 +547,14 @@ name = Frameworks; sourceTree = ""; }; + 3C4249C71EF6E05C00D245F1 /* Products */ = { + isa = PBXGroup; + children = ( + 3C4249E61EF6E05C00D245F1 /* libRNNotifications.a */, + ); + name = Products; + sourceTree = ""; + }; 58A37319FEF489FE2CFCFA3F /* Pods */ = { isa = PBXGroup; children = ( @@ -558,6 +578,7 @@ 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( + 3C4249C61EF6E05C00D245F1 /* RNNotifications.xcodeproj */, A14EA8541EACE4CE009D9E83 /* RCTAnimation.xcodeproj */, A155BFE71DD8E54100A8B695 /* RCTCameraRoll.xcodeproj */, 146833FF1AC3E56700842450 /* React.xcodeproj */, @@ -766,7 +787,16 @@ TestTargetID = 13B07F861A680F5B00A75B9A; }; 13B07F861A680F5B00A75B9A = { - DevelopmentTeam = 66KHCWMEYB; + DevelopmentTeam = 6GJ3LH2XZ2; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.BackgroundModes = { + enabled = 0; + }; + com.apple.Push = { + enabled = 1; + }; + }; }; }; }; @@ -842,6 +872,10 @@ ProductGroup = CFCFE50D1F0011FA00C295CF /* Products */; ProjectRef = FC7EC6A752C243FB9F1B8442 /* RNFetchBlob.xcodeproj */; }, + { + ProductGroup = 3C4249C71EF6E05C00D245F1 /* Products */; + ProjectRef = 3C4249C61EF6E05C00D245F1 /* RNNotifications.xcodeproj */; + }, { ProductGroup = A148FEDC1E9D8C9600479280 /* Products */; ProjectRef = AF03F5DAC0924A13BDD49574 /* RNSound.xcodeproj */; @@ -924,6 +958,13 @@ remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 3C4249E61EF6E05C00D245F1 /* libRNNotifications.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRNNotifications.a; + remoteRef = 3C4249E51EF6E05C00D245F1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -1390,7 +1431,10 @@ baseConfigurationReference = 2BBAAB1FE905B6D60DE187DD /* Pods-ZulipMobile.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = ZulipMobile/ZulipMobile.entitlements; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEAD_CODE_STRIPPING = NO; + DEVELOPMENT_TEAM = 6GJ3LH2XZ2; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; HEADER_SEARCH_PATHS = ( "$(inherited)", @@ -1403,6 +1447,7 @@ "$(SRCROOT)/../node_modules/react-native-safari-view", "$(SRCROOT)/../node_modules/@remobile/react-native-toast/ios/RCTToast", "$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**", + "$(SRCROOT)/../node_modules/react-native-notifications/**", ); INFOPLIST_FILE = ZulipMobile/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1412,8 +1457,9 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = org.zulip.Zulip; + PRODUCT_BUNDLE_IDENTIFIER = com.zulip.Zulip; PRODUCT_NAME = ZulipMobile; + PROVISIONING_PROFILE_SPECIFIER = ""; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -1423,7 +1469,8 @@ baseConfigurationReference = 8FC8D6AFD281F51DCA6E1C99 /* Pods-ZulipMobile.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = D47VARCYH7; + CODE_SIGN_ENTITLEMENTS = ZulipMobile/ZulipMobile.entitlements; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; HEADER_SEARCH_PATHS = ( "$(inherited)", @@ -1436,6 +1483,7 @@ "$(SRCROOT)/../node_modules/react-native-safari-view", "$(SRCROOT)/../node_modules/@remobile/react-native-toast/ios/RCTToast", "$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**", + "$(SRCROOT)/../node_modules/react-native-notifications/**", ); INFOPLIST_FILE = ZulipMobile/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1445,8 +1493,9 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = org.zulip.Zulip; + PRODUCT_BUNDLE_IDENTIFIER = com.zulip.Zulip; PRODUCT_NAME = ZulipMobile; + PROVISIONING_PROFILE_SPECIFIER = ""; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; diff --git a/ios/ZulipMobile/AppDelegate.m b/ios/ZulipMobile/AppDelegate.m index 250883a3527..76ba2fb69c9 100644 --- a/ios/ZulipMobile/AppDelegate.m +++ b/ios/ZulipMobile/AppDelegate.m @@ -16,6 +16,7 @@ #import #import #import "RCTLog.h" +#import "RNNotifications.h" @implementation AppDelegate @@ -99,5 +100,29 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserAct continueUserActivity:userActivity restorationHandler:restorationHandler]; } +// Required to register for notifications +- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings +{ + [RNNotifications didRegisterUserNotificationSettings:notificationSettings]; +} + +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken +{ + [RNNotifications didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; +} +- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + [RNNotifications didFailToRegisterForRemoteNotificationsWithError:error]; +} + +// Required for the notification event. +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification { + [RNNotifications didReceiveRemoteNotification:notification]; +} + +// Required for the localNotification event. +- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification +{ + [RNNotifications didReceiveLocalNotification:notification]; +} @end diff --git a/ios/ZulipMobile/ZulipMobile.entitlements b/ios/ZulipMobile/ZulipMobile.entitlements new file mode 100644 index 00000000000..903def2af53 --- /dev/null +++ b/ios/ZulipMobile/ZulipMobile.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/src/account-info/LogoutButton.js b/src/account-info/LogoutButton.js index 9197cd4b743..74c09514572 100644 --- a/src/account-info/LogoutButton.js +++ b/src/account-info/LogoutButton.js @@ -7,7 +7,7 @@ import { InitRouteAction, Auth } from '../types'; import boundActions from '../boundActions'; import { getInitialRoutes } from '../nav/routingSelectors'; import { ZulipButton } from '../common'; -import unregisterGCM from '../api/unregisterGCM'; +import unregisterPush from '../api/unregisterPush'; import { getAuth } from '../account/accountSelectors'; const styles = StyleSheet.create({ @@ -23,22 +23,22 @@ class LogoutButton extends Component { accounts: any[], initRoutes: InitRouteAction, logout: (accounts: any[]) => void, - deleteTokenGCM: () => void, + deleteTokenPush: () => void, auth: Auth, - gcmToken: string, + pushToken: string, }; - shutdownGCM = async () => { - const { auth, deleteTokenGCM, gcmToken } = this.props; - if (gcmToken !== '') { - await unregisterGCM(auth, gcmToken); - deleteTokenGCM(); + shutdownPUSH = async () => { + const { auth, deleteTokenPush, pushToken } = this.props; + if (pushToken !== '') { + await unregisterPush(auth, pushToken); + deleteTokenPush(); } } logout = () => { const { accounts } = this.props; - this.shutdownGCM(); + this.shutdownPUSH(); this.props.logout(accounts); const accountsLoggedOut = accounts.slice(); accountsLoggedOut[0].apiKey = ''; @@ -61,7 +61,7 @@ export default connect( (state) => ({ auth: getAuth(state), accounts: state.accounts, - gcmToken: state.realm.gcmToken + pushToken: state.realm.pushToken }), boundActions, )(LogoutButton); diff --git a/src/account-info/SwitchAccountButton.js b/src/account-info/SwitchAccountButton.js index 5d781abebc4..0a7bc6c1ee8 100644 --- a/src/account-info/SwitchAccountButton.js +++ b/src/account-info/SwitchAccountButton.js @@ -6,7 +6,7 @@ import { connect } from 'react-redux'; import boundActions from '../boundActions'; import { ZulipButton } from '../common'; import { getAuth } from '../account/accountSelectors'; -import unregisterGCM from '../api/unregisterGCM'; +import unregisterPush from '../api/unregisterPush'; const styles = StyleSheet.create({ button: { @@ -21,16 +21,16 @@ class SwitchAccountButton extends Component { drawer: () => null, }; - shutdownGCM = async () => { - const { auth, deleteTokenGCM, gcmToken } = this.props; - if (gcmToken !== '') { - await unregisterGCM(auth, gcmToken); - deleteTokenGCM(); + shutdownPUSH = async () => { + const { auth, deleteTokenPush, pushToken } = this.props; + if (pushToken !== '') { + await unregisterPush(auth, pushToken); + deleteTokenPush(); } } switchAccount = () => { - this.shutdownGCM(); + this.shutdownPUSH(); this.context.drawer.close(); this.props.pushRoute('account'); } @@ -51,7 +51,7 @@ export default connect( (state) => ({ auth: getAuth(state), accounts: state.accounts, - gcmToken: state.realm.gcmToken + pushToken: state.realm.pushToken }), boundActions, )(SwitchAccountButton); diff --git a/src/actionConstants.js b/src/actionConstants.js index 63e54634781..558d3130bbb 100644 --- a/src/actionConstants.js +++ b/src/actionConstants.js @@ -59,8 +59,8 @@ export const GET_USER_RESPONSE = 'GET_USER_RESPONSE'; export const MARK_MESSAGES_READ = 'MARK_MESSAGES_READ'; export const SETTINGS_CHANGE = 'SETTINGS_CHANGE'; -export const SAVE_TOKEN_GCM = 'SAVE_TOKEN_GCM'; -export const DELETE_TOKEN_GCM = 'DELETE_TOKEN_GCM'; +export const SAVE_TOKEN_PUSH = 'SAVE_TOKEN_PUSH'; +export const DELETE_TOKEN_PUSH = 'DELETE_TOKEN_PUSH'; export const EVENT_REALM_EMOJI_UPDATE = 'EVENT_REALM_EMOJI_UPDATE'; diff --git a/src/api/registerGCM.js b/src/api/registerPush.android.js similarity index 85% rename from src/api/registerGCM.js rename to src/api/registerPush.android.js index d1e7c17ac86..817e3c5650f 100644 --- a/src/api/registerGCM.js +++ b/src/api/registerPush.android.js @@ -5,7 +5,7 @@ import { apiPost } from './apiFetch'; export default async (auth: Auth, token: string) => apiPost( auth, - 'users/me/android_gcm_reg_id', + 'users/me/android_push_reg_id', res => res.api_key, { token, diff --git a/src/api/registerPush.ios.js b/src/api/registerPush.ios.js new file mode 100644 index 00000000000..5f4d38ec88d --- /dev/null +++ b/src/api/registerPush.ios.js @@ -0,0 +1,15 @@ +/* @flow */ +import { Auth } from '../types'; +import { apiPost } from './apiFetch'; + +export default async (auth: Auth, token: string) => { + apiPost( + auth, + 'users/me/apns_device_token', + res => res, + { + token, + appid: 'com.zulip.Zulip' + }, + ); +}; diff --git a/src/api/unregisterPush.android.js b/src/api/unregisterPush.android.js new file mode 100644 index 00000000000..a29a4e57930 --- /dev/null +++ b/src/api/unregisterPush.android.js @@ -0,0 +1,11 @@ +/* @flow */ +import { Auth } from '../types'; +import { apiDelete } from './apiFetch'; + +export default (auth: Auth, token: string) => + apiDelete( + auth, + 'users/me/android_push_reg_id', + res => res.api_key, + { token }, + ); diff --git a/src/api/unregisterGCM.js b/src/api/unregisterPush.ios.js similarity index 85% rename from src/api/unregisterGCM.js rename to src/api/unregisterPush.ios.js index c8cfde14324..0e072c23ea1 100644 --- a/src/api/unregisterGCM.js +++ b/src/api/unregisterPush.ios.js @@ -5,7 +5,7 @@ import { apiDelete } from './apiFetch'; export default (auth: Auth, token: string) => apiDelete( auth, - 'users/me/android_gcm_reg_id', + 'users/me/apns_device_token', res => res.api_key, { token }, ); diff --git a/src/app/appReducers.js b/src/app/appReducers.js index baef2e420a2..584bed1e789 100644 --- a/src/app/appReducers.js +++ b/src/app/appReducers.js @@ -22,7 +22,7 @@ const initialState: AppState = { isOnline: true, isActive: true, needsInitialFetch: false, - gcmToken: '', + pushToken: '', eventQueueId: null, }; diff --git a/src/main/MainScreenContainer.js b/src/main/MainScreenContainer.js index c952578dcdf..fdfbedfa016 100644 --- a/src/main/MainScreenContainer.js +++ b/src/main/MainScreenContainer.js @@ -16,8 +16,8 @@ import { initializeNotifications } from '../utils/notifications'; class MainScreenContainer extends React.Component { componentWillMount() { - const { auth, saveTokenGCM, switchNarrow } = this.props; - initializeNotifications(auth, saveTokenGCM, switchNarrow); + const { auth, saveTokenPush, switchNarrow } = this.props; + initializeNotifications(auth, saveTokenPush, switchNarrow); } componentWillReceiveProps(nextProps) { diff --git a/src/nav/NavigationContainer.js b/src/nav/NavigationContainer.js index 0d0d71fe63e..ed2c1a9f607 100644 --- a/src/nav/NavigationContainer.js +++ b/src/nav/NavigationContainer.js @@ -60,10 +60,10 @@ class NavigationContainer extends React.PureComponent { componentWillReceiveProps(nextProps) { const { needsInitialFetch, auth, - fetchEvents, fetchEssentialInitialData, fetchRestOfInitialData, gcmToken } = nextProps; + fetchEvents, fetchEssentialInitialData, fetchRestOfInitialData, pushToken } = nextProps; if (needsInitialFetch) { fetchEssentialInitialData(auth); - fetchRestOfInitialData(auth, gcmToken); + fetchRestOfInitialData(auth, pushToken); fetchEvents(auth); } } @@ -87,7 +87,7 @@ export default connect( needsInitialFetch: state.app.needsInitialFetch, accounts: state.accounts, navigation: state.nav, - gcmToken: state.realm.gcmToken, + pushToken: state.realm.pushToken, }), boundActions, )(NavigationContainer); diff --git a/src/realm/__tests__/realmReducers-test.js b/src/realm/__tests__/realmReducers-test.js index 5eb3d568f00..9942d6fc508 100644 --- a/src/realm/__tests__/realmReducers-test.js +++ b/src/realm/__tests__/realmReducers-test.js @@ -1,10 +1,11 @@ import realmReducers from '../realmReducers'; import { ACCOUNT_SWITCH, - SAVE_TOKEN_GCM, - DELETE_TOKEN_GCM, EVENT_REALM_EMOJI_UPDATE, EVENT_UPDATE_DISPLAY_SETTINGS, + SAVE_TOKEN_PUSH, + DELETE_TOKEN_PUSH, + EVENT_REALM_EMOJI_UPDATE } from '../../actionConstants'; describe('realmReducers', () => { @@ -16,7 +17,7 @@ describe('realmReducers', () => { }; const expectedState = { twentyFourHourTime: false, - 'gcmToken': '', + 'pushToken': '', emoji: {}, }; @@ -26,20 +27,20 @@ describe('realmReducers', () => { }); }); - describe('SAVE_TOKEN_GCM', () => { - test('save a new GCM token', () => { + describe('SAVE_TOKEN_PUSH', () => { + test('save a new PUSH token', () => { const initialState = { twentyFourHourTime: false, - 'gcmToken': '', + 'pushToken': '', emoji: { customEmoji1: {} }, }; const action = { - type: SAVE_TOKEN_GCM, - gcmToken: 'new-key' + type: SAVE_TOKEN_PUSH, + pushToken: 'new-key' }; const expectedState = { twentyFourHourTime: false, - 'gcmToken': 'new-key', + 'pushToken': 'new-key', emoji: { customEmoji1: {} }, }; @@ -50,19 +51,19 @@ describe('realmReducers', () => { }); - describe('DELETE_TOKEN_GCM', () => { - test('delete the GCM token', () => { + describe('DELETE_TOKEN_PUSH', () => { + test('delete the PUSH token', () => { const initialState = { twentyFourHourTime: false, - 'gcmToken': 'old-key', + 'pushToken': 'old-key', emoji: { customEmoji1: {} }, }; const action = { - type: DELETE_TOKEN_GCM, + type: DELETE_TOKEN_PUSH, }; const expectedState = { twentyFourHourTime: false, - 'gcmToken': '', + 'pushToken': '', emoji: { customEmoji1: {} }, }; @@ -103,7 +104,7 @@ describe('realmReducers', () => { test('update state to new realm_emoji', () => { const prevState = { twentyFourHourTime: false, - 'gcmToken': 'key', + 'pushToken': 'key', emoji: {}, }; const action = { @@ -118,7 +119,7 @@ describe('realmReducers', () => { }; const expectedState = { twentyFourHourTime: false, - 'gcmToken': 'key', + 'pushToken': 'key', emoji: { customEmoji1: {}, customEmoji2: {}, diff --git a/src/realm/realmActions.js b/src/realm/realmActions.js index 270152f7b37..791aa25d575 100644 --- a/src/realm/realmActions.js +++ b/src/realm/realmActions.js @@ -11,8 +11,8 @@ import { initStreams } from '../streamlist/streamsActions'; import { initUsers } from '../users/usersActions'; import { INITIAL_FETCH_COMPLETE, - SAVE_TOKEN_GCM, - DELETE_TOKEN_GCM, + SAVE_TOKEN_PUSH, + DELETE_TOKEN_PUSH, } from '../actionConstants'; export const initialFetchComplete = () => ({ @@ -37,7 +37,7 @@ export const fetchEssentialInitialData = (auth: Auth) => dispatch(initialFetchComplete()); }; -export const fetchRestOfInitialData = (auth: Auth, gcmToken: string) => +export const fetchRestOfInitialData = (auth: Auth, pushToken: string) => async (dispatch: Dispatch) => { const [streams, users, messages, realmEmoji] = await Promise.all([ await tryUntilSuccessful(() => getStreams(auth)), @@ -51,17 +51,17 @@ export const fetchRestOfInitialData = (auth: Auth, gcmToken: string) => dispatch(initStreams(streams)); dispatch(initUsers(users)); dispatch(initRealmEmojis(realmEmoji)); - if (auth.apiKey !== '' && gcmToken === '') { + if (auth.apiKey !== '' && pushToken === '') { refreshNotificationToken(); } }; -export const deleteTokenGCM = () => ({ - type: DELETE_TOKEN_GCM +export const deleteTokenPush = () => ({ + type: DELETE_TOKEN_PUSH }); -export const saveTokenGCM = (gcmToken: string) => ({ - type: SAVE_TOKEN_GCM, - gcmToken +export const saveTokenPush = (pushToken: string) => ({ + type: SAVE_TOKEN_PUSH, + pushToken }); diff --git a/src/realm/realmReducers.js b/src/realm/realmReducers.js index 7b5b4e16723..c2aa776a8f9 100644 --- a/src/realm/realmReducers.js +++ b/src/realm/realmReducers.js @@ -7,15 +7,15 @@ import { LOGIN_SUCCESS, ACCOUNT_SWITCH, INIT_REALM_EMOJI, - SAVE_TOKEN_GCM, - DELETE_TOKEN_GCM, EVENT_UPDATE_DISPLAY_SETTINGS, + SAVE_TOKEN_PUSH, + DELETE_TOKEN_PUSH, } from '../actionConstants'; // Initial state const initialState = { twentyFourHourTime: false, - gcmToken: '', + pushToken: '', emoji: {}, }; @@ -29,16 +29,16 @@ const reducer = (state: RealmState = initialState, action: Action): RealmState = case ACCOUNT_SWITCH: return initialState; - case SAVE_TOKEN_GCM: { + case SAVE_TOKEN_PUSH: { return { ...state, - gcmToken: action.gcmToken + pushToken: action.pushToken }; } - case DELETE_TOKEN_GCM: { + case DELETE_TOKEN_PUSH: { return { ...state, - gcmToken: '' + pushToken: '' }; } case LOGOUT: diff --git a/src/types.js b/src/types.js index 95607dee20d..0f738b5921f 100644 --- a/src/types.js +++ b/src/types.js @@ -133,7 +133,7 @@ export type AppState = { isOnline: boolean, isActive: boolean, needsInitialFetch: boolean, - gcmToken: string, + pushToken: string, eventQueueId: number, }; @@ -173,7 +173,7 @@ export type NavigationState = { export type RealmState = { twentyFourHourTime: boolean, - gcmToken: string, + pushToken: string, }; export type SettingsState = { diff --git a/src/utils/notifications.android.js b/src/utils/notifications.android.js index f007fa993d1..d455bdf10b0 100644 --- a/src/utils/notifications.android.js +++ b/src/utils/notifications.android.js @@ -1,7 +1,7 @@ /* @flow */ import { NotificationsAndroid, PendingNotifications } from 'react-native-notifications'; -import registerGCM from '../api/registerGCM'; +import registerPush from '../api/registerPush'; import { streamNarrow, privateNarrow } from '../utils/narrow'; import { Auth } from '../types'; @@ -19,20 +19,20 @@ const handlePendingNotifications = async (switchNarrow) => { } }; -const handleRegistrationUpdates = (auth: Auth, saveTokenGCM) => { +const handleRegistrationUpdates = (auth: Auth, saveTokenPush) => { NotificationsAndroid.setRegistrationTokenUpdateListener(async (deviceToken) => { try { - await registerGCM(auth, deviceToken); + await registerPush(auth, deviceToken); } catch (e) { console.log('error ', e); //eslint-disable-line } - saveTokenGCM(deviceToken); + saveTokenPush(deviceToken); }); }; -export const initializeNotifications = (auth: Auth, saveTokenGCM: string, doNarrow) => { +export const initializeNotifications = (auth: Auth, saveTokenPush: string, doNarrow) => { handlePendingNotifications(doNarrow); - handleRegistrationUpdates(auth, saveTokenGCM); + handleRegistrationUpdates(auth, saveTokenPush); }; export const refreshNotificationToken = () => { diff --git a/src/utils/notifications.ios.js b/src/utils/notifications.ios.js index 973e17a0855..d29dd1091e0 100644 --- a/src/utils/notifications.ios.js +++ b/src/utils/notifications.ios.js @@ -1,4 +1,24 @@ -export const initializeNotifications = () => { +import NotificationsIOS from 'react-native-notifications'; +import registerPush from '../api/registerPush'; + +const register = async (auth, deviceToken) => { + await registerPush(auth, deviceToken); +}; + +const onPushRegistered = (auth, deviceToken, saveTokenPush) => { + console.log('Device Token Received', auth, deviceToken); + register(auth, deviceToken); + saveTokenPush(deviceToken); +}; + +const onPushRegistrationFailed = (error) => { + console.error('pushNotification', error); +}; + +export const initializeNotifications = (auth, saveTokenPush) => { + NotificationsIOS.addEventListener('remoteNotificationsRegistered', (deviceToken) => onPushRegistered(auth, deviceToken, saveTokenPush)); + NotificationsIOS.addEventListener('remoteNotificationsRegistrationFailed', onPushRegistrationFailed.bind(null, auth)); + NotificationsIOS.requestPermissions(); }; export const refreshNotificationToken = () => {