[CHORE] Memory leaks investigation (#1675)

This commit is contained in:
Diego Mello 2020-02-17 16:06:18 -03:00 committed by GitHub
parent 82fd91d2f4
commit 6a40d4cefb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
615 changed files with 25195 additions and 10062 deletions

View File

@ -60,6 +60,8 @@ const emojiCount = (str) => {
return counter; return counter;
}; };
const parser = new Parser();
class Markdown extends PureComponent { class Markdown extends PureComponent {
static propTypes = { static propTypes = {
msg: PropTypes.string, msg: PropTypes.string,
@ -81,13 +83,9 @@ class Markdown extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.parser = this.createParser();
this.renderer = this.createRenderer(props.preview); this.renderer = this.createRenderer(props.preview);
} }
createParser = () => new Parser();
createRenderer = (preview = false) => new Renderer({ createRenderer = (preview = false) => new Renderer({
renderers: { renderers: {
text: this.renderText, text: this.renderText,
@ -385,7 +383,7 @@ class Markdown extends PureComponent {
if (preview) { if (preview) {
m = m.split('\n').reduce((lines, line) => `${ lines } ${ line }`, ''); m = m.split('\n').reduce((lines, line) => `${ lines } ${ line }`, '');
const ast = this.parser.parse(m); const ast = parser.parse(m);
return this.renderer.render(ast); return this.renderer.render(ast);
} }
@ -393,7 +391,7 @@ class Markdown extends PureComponent {
return <Text style={[styles.text, { color: themes[theme].bodyText }]} numberOfLines={numberOfLines}>{m}</Text>; return <Text style={[styles.text, { color: themes[theme].bodyText }]} numberOfLines={numberOfLines}>{m}</Text>;
} }
const ast = this.parser.parse(m); const ast = parser.parse(m);
this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3; this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3;
this.editedMessage(ast); this.editedMessage(ast);

View File

@ -43,6 +43,9 @@ import Tablet, { initTabletNav } from './tablet';
import sharedStyles from './views/Styles'; import sharedStyles from './views/Styles';
import { SplitContext } from './split'; import { SplitContext } from './split';
import RoomsListView from './views/RoomsListView';
import RoomView from './views/RoomView';
if (isIOS) { if (isIOS) {
const RNScreens = require('react-native-screens'); const RNScreens = require('react-native-screens');
RNScreens.useScreens(); RNScreens.useScreens();
@ -111,9 +114,7 @@ const OutsideStackModal = createStackNavigator({
}); });
const RoomRoutes = { const RoomRoutes = {
RoomView: { RoomView,
getScreen: () => require('./views/RoomView').default
},
ThreadMessagesView: { ThreadMessagesView: {
getScreen: () => require('./views/ThreadMessagesView').default getScreen: () => require('./views/ThreadMessagesView').default
}, },
@ -127,9 +128,7 @@ const RoomRoutes = {
// Inside // Inside
const ChatsStack = createStackNavigator({ const ChatsStack = createStackNavigator({
RoomsListView: { RoomsListView,
getScreen: () => require('./views/RoomsListView').default
},
RoomActionsView: { RoomActionsView: {
getScreen: () => require('./views/RoomActionsView').default getScreen: () => require('./views/RoomActionsView').default
}, },

View File

@ -1,4 +1,5 @@
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { InteractionManager } from 'react-native';
import database from '../../database'; import database from '../../database';
import { merge } from '../helpers/mergeSubscriptionsRooms'; import { merge } from '../helpers/mergeSubscriptionsRooms';
@ -23,7 +24,7 @@ let subQueue = {};
let subTimer = null; let subTimer = null;
let roomQueue = {}; let roomQueue = {};
let roomTimer = null; let roomTimer = null;
const WINDOW_TIME = 1000; const WINDOW_TIME = 500;
const createOrUpdateSubscription = async(subscription, room) => { const createOrUpdateSubscription = async(subscription, room) => {
try { try {
@ -176,7 +177,9 @@ const debouncedUpdateSub = (subscription) => {
subQueue = {}; subQueue = {};
subTimer = null; subTimer = null;
Object.keys(subBatch).forEach((key) => { Object.keys(subBatch).forEach((key) => {
createOrUpdateSubscription(subBatch[key]); InteractionManager.runAfterInteractions(() => {
createOrUpdateSubscription(subBatch[key]);
});
}); });
}, WINDOW_TIME); }, WINDOW_TIME);
} }
@ -190,7 +193,9 @@ const debouncedUpdateRoom = (room) => {
roomQueue = {}; roomQueue = {};
roomTimer = null; roomTimer = null;
Object.keys(roomBatch).forEach((key) => { Object.keys(roomBatch).forEach((key) => {
createOrUpdateSubscription(null, roomBatch[key]); InteractionManager.runAfterInteractions(() => {
createOrUpdateSubscription(null, roomBatch[key]);
});
}); });
}, WINDOW_TIME); }, WINDOW_TIME);
} }

View File

@ -25,9 +25,9 @@ class List extends React.Component {
rid: PropTypes.string, rid: PropTypes.string,
t: PropTypes.string, t: PropTypes.string,
tmid: PropTypes.string, tmid: PropTypes.string,
animated: PropTypes.bool,
theme: PropTypes.string, theme: PropTypes.string,
listRef: PropTypes.func listRef: PropTypes.func,
navigation: PropTypes.object
}; };
constructor(props) { constructor(props) {
@ -40,9 +40,17 @@ class List extends React.Component {
loading: true, loading: true,
end: false, end: false,
messages: [], messages: [],
refreshing: false refreshing: false,
animated: false
}; };
this.init(); this.init();
this.didFocusListener = props.navigation.addListener('didFocus', () => {
if (this.mounted) {
this.setState({ animated: true });
} else {
this.state.animated = true;
}
});
console.timeEnd(`${ this.constructor.name } init`); console.timeEnd(`${ this.constructor.name } init`);
} }
@ -130,6 +138,9 @@ class List extends React.Component {
if (this.onEndReached && this.onEndReached.stop) { if (this.onEndReached && this.onEndReached.stop) {
this.onEndReached.stop(); this.onEndReached.stop();
} }
if (this.didFocusListener && this.didFocusListener.remove) {
this.didFocusListener.remove();
}
console.countReset(`${ this.constructor.name }.render calls`); console.countReset(`${ this.constructor.name }.render calls`);
} }
@ -180,7 +191,10 @@ class List extends React.Component {
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
update = () => { update = () => {
animateNextTransition(); const { animated } = this.state;
if (animated) {
animateNextTransition();
}
this.forceUpdate(); this.forceUpdate();
}; };

View File

@ -182,8 +182,6 @@ class RoomView extends React.Component {
this.findAndObserveRoom(this.rid); this.findAndObserveRoom(this.rid);
} }
this.beginAnimating = false;
this.didFocusListener = props.navigation.addListener('didFocus', () => this.beginAnimating = true);
this.messagebox = React.createRef(); this.messagebox = React.createRef();
this.list = React.createRef(); this.list = React.createRef();
this.willBlurListener = props.navigation.addListener('willBlur', () => this.mounted = false); this.willBlurListener = props.navigation.addListener('willBlur', () => this.mounted = false);
@ -289,9 +287,6 @@ class RoomView extends React.Component {
} }
} }
this.unsubscribe(); this.unsubscribe();
if (this.didFocusListener && this.didFocusListener.remove) {
this.didFocusListener.remove();
}
if (this.didMountInteraction && this.didMountInteraction.cancel) { if (this.didMountInteraction && this.didMountInteraction.cancel) {
this.didMountInteraction.cancel(); this.didMountInteraction.cancel();
} }
@ -321,7 +316,6 @@ class RoomView extends React.Component {
navigation.navigate('RoomActionsView', { rid: this.rid, t: this.t, room }); navigation.navigate('RoomActionsView', { rid: this.rid, t: this.t, room });
} }
// eslint-disable-next-line react/sort-comp
init = async() => { init = async() => {
try { try {
this.setState({ loading: true }); this.setState({ loading: true });
@ -893,7 +887,9 @@ class RoomView extends React.Component {
const { const {
room, reactionsModalVisible, selectedMessage, loading, reacting room, reactionsModalVisible, selectedMessage, loading, reacting
} = this.state; } = this.state;
const { user, baseUrl, theme } = this.props; const {
user, baseUrl, theme, navigation
} = this.props;
const { rid, t } = room; const { rid, t } = room;
return ( return (
@ -916,7 +912,7 @@ class RoomView extends React.Component {
room={room} room={room}
renderRow={this.renderItem} renderRow={this.renderItem}
loading={loading} loading={loading}
animated={this.beginAnimating} navigation={navigation}
/> />
{this.renderFooter()} {this.renderFooter()}
{this.renderActions()} {this.renderActions()}

View File

@ -182,6 +182,7 @@ class RoomsListView extends React.Component {
console.time(`${ this.constructor.name } mount`); console.time(`${ this.constructor.name } mount`);
this.gotSubscriptions = false; this.gotSubscriptions = false;
this.animated = false;
const { width } = Dimensions.get('window'); const { width } = Dimensions.get('window');
this.state = { this.state = {
searching: false, searching: false,
@ -215,9 +216,11 @@ class RoomsListView extends React.Component {
} }
}); });
this.didFocusListener = navigation.addListener('didFocus', () => { this.didFocusListener = navigation.addListener('didFocus', () => {
this.animated = true;
this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackPress); this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
}); });
this.willBlurListener = navigation.addListener('willBlur', () => { this.willBlurListener = navigation.addListener('willBlur', () => {
this.animated = false;
closeServerDropdown(); closeServerDropdown();
if (this.backHandler && this.backHandler.remove) { if (this.backHandler && this.backHandler.remove) {
this.backHandler.remove(); this.backHandler.remove();
@ -338,12 +341,11 @@ class RoomsListView extends React.Component {
console.countReset(`${ this.constructor.name }.render calls`); console.countReset(`${ this.constructor.name }.render calls`);
} }
// eslint-disable-next-line react/sort-comp
onDimensionsChange = ({ window: { width } }) => this.setState({ width }); onDimensionsChange = ({ window: { width } }) => this.setState({ width });
// eslint-disable-next-line react/sort-comp
internalSetState = (...args) => { internalSetState = (...args) => {
const { navigation } = this.props; if (this.animated) {
if (navigation.isFocused()) {
animateNextTransition(); animateNextTransition();
} }
this.setState(...args); this.setState(...args);
@ -532,7 +534,7 @@ class RoomsListView extends React.Component {
prid: item.prid, prid: item.prid,
room: item room: item
}); });
}; }
_onPressItem = async(item = {}) => { _onPressItem = async(item = {}) => {
if (!item.search) { if (!item.search) {

View File

@ -34,35 +34,41 @@ PODS:
- React-Core (= 0.61.5) - React-Core (= 0.61.5)
- React-jsi (= 0.61.5) - React-jsi (= 0.61.5)
- ReactCommon/turbomodule/core (= 0.61.5) - ReactCommon/turbomodule/core (= 0.61.5)
- Firebase/Core (6.8.1): - Firebase/Core (6.16.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseAnalytics (= 6.1.1) - FirebaseAnalytics (= 6.2.2)
- Firebase/CoreOnly (6.8.1): - Firebase/CoreOnly (6.16.0):
- FirebaseCore (= 6.2.3) - FirebaseCore (= 6.6.1)
- FirebaseAnalytics (6.1.1): - FirebaseAnalytics (6.2.2):
- FirebaseCore (~> 6.2) - FirebaseCore (~> 6.6)
- FirebaseInstanceID (~> 4.2) - FirebaseInstanceID (~> 4.3)
- GoogleAppMeasurement (= 6.1.1) - GoogleAppMeasurement (= 6.2.2)
- GoogleUtilities/AppDelegateSwizzler (~> 6.0) - GoogleUtilities/AppDelegateSwizzler (~> 6.0)
- GoogleUtilities/MethodSwizzler (~> 6.0) - GoogleUtilities/MethodSwizzler (~> 6.0)
- GoogleUtilities/Network (~> 6.0) - GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)" - "GoogleUtilities/NSData+zlib (~> 6.0)"
- nanopb (~> 0.3) - nanopb (= 0.3.9011)
- FirebaseCore (6.2.3): - FirebaseCore (6.6.1):
- FirebaseCoreDiagnostics (~> 1.0) - FirebaseCoreDiagnostics (~> 1.2)
- FirebaseCoreDiagnosticsInterop (~> 1.0) - FirebaseCoreDiagnosticsInterop (~> 1.2)
- GoogleUtilities/Environment (~> 6.2) - GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/Logger (~> 6.2) - GoogleUtilities/Logger (~> 6.5)
- FirebaseCoreDiagnostics (1.0.1): - FirebaseCoreDiagnostics (1.2.0):
- FirebaseCoreDiagnosticsInterop (~> 1.0) - FirebaseCoreDiagnosticsInterop (~> 1.2)
- GoogleDataTransportCCTSupport (~> 1.0) - GoogleDataTransportCCTSupport (~> 1.3)
- GoogleUtilities/Environment (~> 6.2) - GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/Logger (~> 6.2) - GoogleUtilities/Logger (~> 6.5)
- FirebaseCoreDiagnosticsInterop (1.0.0) - nanopb (~> 0.3.901)
- FirebaseInstanceID (4.2.5): - FirebaseCoreDiagnosticsInterop (1.2.0)
- FirebaseCore (~> 6.0) - FirebaseInstallations (1.1.0):
- GoogleUtilities/Environment (~> 6.0) - FirebaseCore (~> 6.6)
- GoogleUtilities/UserDefaults (~> 6.0) - GoogleUtilities/UserDefaults (~> 6.5)
- PromisesObjC (~> 1.2)
- FirebaseInstanceID (4.3.0):
- FirebaseCore (~> 6.6)
- FirebaseInstallations (~> 1.0)
- GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/UserDefaults (~> 6.5)
- Folly (2018.10.22.00): - Folly (2018.10.22.00):
- boost-for-react-native - boost-for-react-native
- DoubleConversion - DoubleConversion
@ -73,51 +79,52 @@ PODS:
- DoubleConversion - DoubleConversion
- glog - glog
- glog (0.3.5) - glog (0.3.5)
- GoogleAppMeasurement (6.1.1): - GoogleAppMeasurement (6.2.2):
- GoogleUtilities/AppDelegateSwizzler (~> 6.0) - GoogleUtilities/AppDelegateSwizzler (~> 6.0)
- GoogleUtilities/MethodSwizzler (~> 6.0) - GoogleUtilities/MethodSwizzler (~> 6.0)
- GoogleUtilities/Network (~> 6.0) - GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)" - "GoogleUtilities/NSData+zlib (~> 6.0)"
- nanopb (~> 0.3) - nanopb (= 0.3.9011)
- GoogleDataTransport (1.2.0) - GoogleDataTransport (3.3.1)
- GoogleDataTransportCCTSupport (1.0.4): - GoogleDataTransportCCTSupport (1.3.1):
- GoogleDataTransport (~> 1.2) - GoogleDataTransport (~> 3.3)
- nanopb - nanopb (~> 0.3.901)
- GoogleUtilities/AppDelegateSwizzler (6.3.0): - GoogleUtilities/AppDelegateSwizzler (6.5.1):
- GoogleUtilities/Environment - GoogleUtilities/Environment
- GoogleUtilities/Logger - GoogleUtilities/Logger
- GoogleUtilities/Network - GoogleUtilities/Network
- GoogleUtilities/Environment (6.3.0) - GoogleUtilities/Environment (6.5.1)
- GoogleUtilities/Logger (6.3.0): - GoogleUtilities/Logger (6.5.1):
- GoogleUtilities/Environment - GoogleUtilities/Environment
- GoogleUtilities/MethodSwizzler (6.3.0): - GoogleUtilities/MethodSwizzler (6.5.1):
- GoogleUtilities/Logger - GoogleUtilities/Logger
- GoogleUtilities/Network (6.3.0): - GoogleUtilities/Network (6.5.1):
- GoogleUtilities/Logger - GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib" - "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability - GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (6.3.0)" - "GoogleUtilities/NSData+zlib (6.5.1)"
- GoogleUtilities/Reachability (6.3.0): - GoogleUtilities/Reachability (6.5.1):
- GoogleUtilities/Logger - GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (6.3.0): - GoogleUtilities/UserDefaults (6.5.1):
- GoogleUtilities/Logger - GoogleUtilities/Logger
- JitsiMeetSDK (2.4.0) - JitsiMeetSDK (2.4.0)
- KeyCommands (2.0.3): - KeyCommands (2.0.3):
- React - React
- libwebp (1.0.3): - libwebp (1.1.0):
- libwebp/demux (= 1.0.3) - libwebp/demux (= 1.1.0)
- libwebp/mux (= 1.0.3) - libwebp/mux (= 1.1.0)
- libwebp/webp (= 1.0.3) - libwebp/webp (= 1.1.0)
- libwebp/demux (1.0.3): - libwebp/demux (1.1.0):
- libwebp/webp - libwebp/webp
- libwebp/mux (1.0.3): - libwebp/mux (1.1.0):
- libwebp/demux - libwebp/demux
- libwebp/webp (1.0.3) - libwebp/webp (1.1.0)
- nanopb (0.3.901): - nanopb (0.3.9011):
- nanopb/decode (= 0.3.901) - nanopb/decode (= 0.3.9011)
- nanopb/encode (= 0.3.901) - nanopb/encode (= 0.3.9011)
- nanopb/decode (0.3.901) - nanopb/decode (0.3.9011)
- nanopb/encode (0.3.901) - nanopb/encode (0.3.9011)
- PromisesObjC (1.2.8)
- RCTRequired (0.61.5) - RCTRequired (0.61.5)
- RCTTypeSafety (0.61.5): - RCTTypeSafety (0.61.5):
- FBLazyVector (= 0.61.5) - FBLazyVector (= 0.61.5)
@ -395,10 +402,10 @@ PODS:
- RNVectorIcons (6.6.0): - RNVectorIcons (6.6.0):
- React - React
- RSKImageCropper (2.2.3) - RSKImageCropper (2.2.3)
- SDWebImage (5.1.1): - SDWebImage (5.5.2):
- SDWebImage/Core (= 5.1.1) - SDWebImage/Core (= 5.5.2)
- SDWebImage/Core (5.1.1) - SDWebImage/Core (5.5.2)
- SDWebImageWebPCoder (0.2.4): - SDWebImageWebPCoder (0.2.5):
- libwebp (~> 1.0) - libwebp (~> 1.0)
- SDWebImage/Core (~> 5.0) - SDWebImage/Core (~> 5.0)
- UMBarCodeScannerInterface (3.0.0) - UMBarCodeScannerInterface (3.0.0)
@ -500,7 +507,7 @@ DEPENDENCIES:
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS: SPEC REPOS:
https://github.com/CocoaPods/Specs.git: trunk:
- boost-for-react-native - boost-for-react-native
- Crashlytics - Crashlytics
- Fabric - Fabric
@ -509,6 +516,7 @@ SPEC REPOS:
- FirebaseCore - FirebaseCore
- FirebaseCoreDiagnostics - FirebaseCoreDiagnostics
- FirebaseCoreDiagnosticsInterop - FirebaseCoreDiagnosticsInterop
- FirebaseInstallations
- FirebaseInstanceID - FirebaseInstanceID
- GoogleAppMeasurement - GoogleAppMeasurement
- GoogleDataTransport - GoogleDataTransport
@ -516,6 +524,7 @@ SPEC REPOS:
- GoogleUtilities - GoogleUtilities
- libwebp - libwebp
- nanopb - nanopb
- PromisesObjC
- RSKImageCropper - RSKImageCropper
- SDWebImage - SDWebImage
- SDWebImageWebPCoder - SDWebImageWebPCoder
@ -713,22 +722,24 @@ SPEC CHECKSUMS:
Fabric: 706c8b8098fff96c33c0db69cbf81f9c551d0d74 Fabric: 706c8b8098fff96c33c0db69cbf81f9c551d0d74
FBLazyVector: aaeaf388755e4f29cd74acbc9e3b8da6d807c37f FBLazyVector: aaeaf388755e4f29cd74acbc9e3b8da6d807c37f
FBReactNativeSpec: 118d0d177724c2d67f08a59136eb29ef5943ec75 FBReactNativeSpec: 118d0d177724c2d67f08a59136eb29ef5943ec75
Firebase: 9cbe4e5b5eaafa05dc932be58b7c8c3820d71e88 Firebase: 497158b816d0a86fc31babbd05546fcd7e6083ff
FirebaseAnalytics: 843c7f64a8f9c79f0d03281197ebe7bb1d58d477 FirebaseAnalytics: cf95d3aab897612783020fbd98401d5366f135ee
FirebaseCore: e9d9bd1dae61c1e82bc1e0e617a9d832392086a0 FirebaseCore: 85064903ed6c28e47fec9c7bd149d94ba1b6b6e7
FirebaseCoreDiagnostics: 4c04ae09d0ab027c30179828c6bb47764df1bd13 FirebaseCoreDiagnostics: 5e78803ab276bc5b50340e3c539c06c3de35c649
FirebaseCoreDiagnosticsInterop: 6829da2b8d1fc795ff1bd99df751d3788035d2cb FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850
FirebaseInstanceID: 550df9be1f99f751d8fcde3ac342a1e21a0e6c42 FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b
FirebaseInstanceID: 6668efc1655a4052c083f287a7141f1ead12f9c2
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51 Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28 glog: 1f3da668190260b06b429bb211bfbee5cd790c28
GoogleAppMeasurement: 86a82f0e1f20b8eedf8e20326530138fd71409de GoogleAppMeasurement: d0560d915abf15e692e8538ba1d58442217b6aff
GoogleDataTransport: 8f9897b8e073687f24ca8d3c3a8013dec7d2d1cc GoogleDataTransport: 0048df6388dab1c254799f2a30365b1dffe20422
GoogleDataTransportCCTSupport: 7455d07b98851aa63e4c05a34dad356ca588479e GoogleDataTransportCCTSupport: f880d70972efa2ed1be4e9173a0f4c5f3dc2d176
GoogleUtilities: 9c2c544202301110b29f7974a82e77fdcf12bf51 GoogleUtilities: 06eb53bb579efe7099152735900dd04bf09e7275
JitsiMeetSDK: d4a3aeed1a75fd57e6a78e5d202b6051dfcb9320 JitsiMeetSDK: d4a3aeed1a75fd57e6a78e5d202b6051dfcb9320
KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e
libwebp: 057912d6d0abfb6357d8bb05c0ea470301f5d61e libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
nanopb: 2901f78ea1b7b4015c860c2fdd1ea2fee1a18d48 nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd
PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6
RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1 RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1
RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320 RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320
React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78 React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78
@ -778,8 +789,8 @@ SPEC CHECKSUMS:
RNUserDefaults: af71a1cdf1c12baf8210bc741c65f5faba9826d6 RNUserDefaults: af71a1cdf1c12baf8210bc741c65f5faba9826d6
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4 RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
RSKImageCropper: a446db0e8444a036b34f3c43db01b2373baa4b2a RSKImageCropper: a446db0e8444a036b34f3c43db01b2373baa4b2a
SDWebImage: 96d7f03415ccb28d299d765f93557ff8a617abd8 SDWebImage: 4d5c027c935438f341ed33dbac53ff9f479922ca
SDWebImageWebPCoder: cc72085bb20368b2f8dbb21b7e355c667e1309b7 SDWebImageWebPCoder: 947093edd1349d820c40afbd9f42acb6cdecd987
UMBarCodeScannerInterface: 84ea2d6b58ff0dc27ef9b68bab71286be18ee020 UMBarCodeScannerInterface: 84ea2d6b58ff0dc27ef9b68bab71286be18ee020
UMCameraInterface: 26b26005d1756a0d5f4f04f1e168e39ea9154535 UMCameraInterface: 26b26005d1756a0d5f4f04f1e168e39ea9154535
UMConstantsInterface: 038bacb19de12b6fd328c589122c8dc977cccf61 UMConstantsInterface: 038bacb19de12b6fd328c589122c8dc977cccf61

View File

@ -1,3 +1,17 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <FirebaseCore/FirebaseCore.h> #import <FirebaseCore/FirebaseCore.h>
#if !defined(__has_include) #if !defined(__has_include)
@ -12,6 +26,10 @@
#import <FirebaseAuth/FirebaseAuth.h> #import <FirebaseAuth/FirebaseAuth.h>
#endif #endif
#if __has_include(<FirebaseCrashlytics/FirebaseCrashlytics.h>)
#import <FirebaseCrashlytics/FirebaseCrashlytics.h>
#endif
#if __has_include(<FirebaseDatabase/FirebaseDatabase.h>) #if __has_include(<FirebaseDatabase/FirebaseDatabase.h>)
#import <FirebaseDatabase/FirebaseDatabase.h> #import <FirebaseDatabase/FirebaseDatabase.h>
#endif #endif

299
ios/Pods/Firebase/README.md generated Executable file → Normal file
View File

@ -1,93 +1,254 @@
# Firebase APIs for iOS # Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk)
Simplify your iOS development, grow your user base, and monetize more This repository contains a subset of the Firebase iOS SDK source. It currently
effectively with Firebase services. includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase,
FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging,
FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and
FirebaseStorage.
Much more information can be found at [https://firebase.google.com](https://firebase.google.com). The repository also includes GoogleUtilities source. The
[GoogleUtilities](GoogleUtilities/README.md) pod is
a set of utilities used by Firebase and other Google products.
## Install a Firebase SDK using CocoaPods Firebase is an app development platform with tools to help you build, grow and
monetize your app. More information about Firebase can be found at
[https://firebase.google.com](https://firebase.google.com).
Firebase distributes several iOS specific APIs and SDKs via CocoaPods. ## Installation
You can install the CocoaPods tool on OS X by running the following command from
the terminal. Detailed information is available in the [Getting Started
guide](https://guides.cocoapods.org/using/getting-started.html#getting-started).
See the three subsections for details about three different installation methods.
1. [Standard pod install](README.md#standard-pod-install)
1. [Installing from the GitHub repo](README.md#installing-from-github)
1. [Experimental Carthage](README.md#carthage-ios-only)
### Standard pod install
Go to
[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup).
### Installing from GitHub
For releases starting with 5.0.0, the source for each release is also deployed
to CocoaPods master and available via standard
[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod).
These instructions can be used to access the Firebase repo at other branches,
tags, or commits.
#### Background
See
[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod)
for instructions and options about overriding pod source locations.
#### Accessing Firebase Source Snapshots
All of the official releases are tagged in this repo and available via CocoaPods. To access a local
source snapshot or unreleased branch, use Podfile directives like the following:
To access FirebaseFirestore via a branch:
``` ```
$ sudo gem install cocoapods pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
``` ```
## Try out an SDK To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do:
You can try any of the SDKs with `pod try`. Run the following command and select
the SDK you are interested in when prompted:
``` ```
$ pod try Firebase pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk'
pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk'
``` ```
Note that some SDKs may require credentials. More information is available in ### Carthage (iOS only)
the SDK-specific documentation at [https://firebase.google.com/docs/](https://firebase.google.com/docs/).
## Add a Firebase SDK to your iOS app Instructions for the experimental Carthage distribution are at
[Carthage](Carthage.md).
CocoaPods is used to install and manage dependencies in existing Xcode projects. ### Rome
1. Create an Xcode project, and save it to your local machine. Instructions for installing binary frameworks via
2. Create a file named `Podfile` in your project directory. This file defines [Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md).
your project's dependencies, and is commonly referred to as a Podspec.
3. Open `Podfile`, and add your dependencies. A simple Podspec is shown here:
``` ## Development
platform :ios, '8.0'
pod 'Firebase'
```
4. Save the file. To develop Firebase software in this repository, ensure that you have at least
the following software:
5. Open a terminal and `cd` to the directory containing the Podfile. * Xcode 10.1 (or later)
* CocoaPods 1.7.2 (or later)
* [CocoaPods generate](https://github.com/square/cocoapods-generate)
``` For the pod that you want to develop:
$ cd <path-to-project>/project/
```
6. Run the `pod install` command. This will install the SDKs specified in the `pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios`
Podspec, along with any dependencies they may have.
``` Note: If the CocoaPods cache is out of date, you may need to run
$ pod install `pod repo update` before the `pod gen` command.
```
7. Open your app's `.xcworkspace` file to launch Xcode. Use this file for all Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for
development on your app. those platforms. Since 10.2, Xcode does not properly handle multi-platform
CocoaPods workspaces.
8. You can also install other Firebase SDKs by adding the subspecs in the Firestore has a self contained Xcode project. See
Podfile. [Firestore/README.md](Firestore/README.md).
``` ### Development for Catalyst
pod 'Firebase/AdMob' * `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios`
pod 'Firebase/Analytics' * Check the Mac box in the App-iOS Build Settings
pod 'Firebase/Auth' * Sign the App in the Settings Signing & Capabilities tab
pod 'Firebase/Database' * Click Pods in the Project Manager
pod 'Firebase/DynamicLinks' * Add Signing to the iOS host app and unit test targets
pod 'Firebase/Firestore' * Select the Unit-unit scheme
pod 'Firebase/Functions' * Run it to build and test
pod 'Firebase/InAppMessaging'
pod 'Firebase/InAppMessagingDisplay' ### Adding a New Firebase Pod
pod 'Firebase/Messaging'
pod 'Firebase/MLCommon' See [AddNewPod.md](AddNewPod.md).
pod 'Firebase/MLModelInterpreter'
pod 'Firebase/MLNLLanguageID' ### Code Formatting
pod 'Firebase/MLNLSmartReply'
pod 'Firebase/MLNLTranslate' To ensure that the code is formatted consistently, run the script
pod 'Firebase/MLNaturalLanguage' [./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh)
pod 'Firebase/MLVision' before creating a PR.
pod 'Firebase/MLVisionAutoML'
pod 'Firebase/MLVisionBarcodeModel' Travis will verify that any code changes are done in a style compliant way. Install
pod 'Firebase/MLVisionFaceModel' `clang-format` and `swiftformat`.
pod 'Firebase/MLVisionLabelModel' These commands will get the right versions:
pod 'Firebase/MLVisionObjectDetection'
pod 'Firebase/MLVisionTextModel' ```
pod 'Firebase/Performance' brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb
pod 'Firebase/RemoteConfig' brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb
pod 'Firebase/Storage' ```
```
Note: if you already have a newer version of these installed you may need to
`brew switch` to this version.
To update this section, find the versions of clang-format and swiftformat.rb to
match the versions in the CI failure logs
[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula).
### Running Unit Tests
Select a scheme and press Command-u to build a component and run its unit tests.
#### Viewing Code Coverage
First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`.
After running the `AllUnitTests_iOS` scheme in Xcode, execute
`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output`
at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results.
### Running Sample Apps
In order to run the sample apps and integration tests, you'll need valid
`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist
files without real values, but can be replaced with real plist files. To get your own
`GoogleService-Info.plist` files:
1. Go to the [Firebase Console](https://console.firebase.google.com/)
2. Create a new Firebase project, if you don't already have one
3. For each sample app you want to test, create a new Firebase app with the sample app's bundle
identifier (e.g. `com.google.Database-Example`)
4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file
(e.g. in [Example/Database/App/](Example/Database/App/));
Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require
special Apple capabilities, and you will have to change the sample app to use a unique bundle
identifier that you can control in your own Apple Developer account.
## Specific Component Instructions
See the sections below for any special instructions for those components.
### Firebase Auth
If you're doing specific Firebase Auth development, see
[the Auth Sample README](Example/Auth/README.md) for instructions about
building and running the FirebaseAuth pod along with various samples and tests.
### Firebase Database
To run the Database Integration tests, make your database authentication rules
[public](https://firebase.google.com/docs/database/security/quickstart).
### Firebase Storage
To run the Storage Integration tests, follow the instructions in
[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m).
#### Push Notifications
Push notifications can only be delivered to specially provisioned App IDs in the developer portal.
In order to actually test receiving push notifications, you will need to:
1. Change the bundle identifier of the sample app to something you own in your Apple Developer
account, and enable that App ID for push notifications.
2. You'll also need to
[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs)
at **Project Settings > Cloud Messaging > [Your Firebase App]**.
3. Ensure your iOS device is added to your Apple Developer portal as a test device.
#### iOS Simulator
The iOS Simulator cannot register for remote notifications, and will not receive push notifications.
In order to receive push notifications, you'll have to follow the steps above and run the app on a
physical device.
## Community Supported Efforts
We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are
very grateful! We'd like to empower as many developers as we can to be able to use Firebase and
participate in the Firebase community.
### tvOS, macOS, and Catalyst
Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and work on
tvOS, macOS, and Catalyst.
For tvOS, checkout the [Sample](Example/tvOSSample).
Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this
repository is actively developed primarily for iOS. While we can catch basic unit test issues with
Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you
encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues).
During app setup in the console, you may get to a step that mentions something like "Checking if the app
has communicated with our servers". This relies on Analytics and will not work on macOS/tvOS/Catalyst.
**It's safe to ignore the message and continue**, the rest of the SDKs will work as expected.
To install, add a subset of the following to the Podfile:
```
pod 'Firebase/ABTesting'
pod 'Firebase/Auth'
pod 'Firebase/Crashlytics'
pod 'Firebase/Database'
pod 'Firebase/Firestore'
pod 'Firebase/Functions'
pod 'Firebase/Messaging'
pod 'Firebase/RemoteConfig'
pod 'Firebase/Storage'
```
#### Additional Catalyst Notes
* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability`
to Build Settings.
* FirebaseFirestore requires signing the
[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681).
## Roadmap
See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source
plans and directions.
## Contributing
See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase
iOS SDK.
## License
The contents of this repository is licensed under the
[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
Your use of Firebase is governed by the
[Terms of Service for Firebase Services](https://firebase.google.com/terms/).

View File

@ -9,6 +9,10 @@ NS_ASSUME_NONNULL_BEGIN
/// The top level Firebase Analytics singleton that provides methods for logging events and setting /// The top level Firebase Analytics singleton that provides methods for logging events and setting
/// user properties. See <a href="http://goo.gl/gz8SLz">the developer guides</a> for general /// user properties. See <a href="http://goo.gl/gz8SLz">the developer guides</a> for general
/// information on using Firebase Analytics in your apps. /// information on using Firebase Analytics in your apps.
///
/// @note The Analytics SDK uses SQLite to persist events and other app-specific data. Calling
/// certain thread-unsafe global SQLite methods like `sqlite3_shutdown()` can result in
/// unexpected crashes at runtime.
NS_SWIFT_NAME(Analytics) NS_SWIFT_NAME(Analytics)
@interface FIRAnalytics : NSObject @interface FIRAnalytics : NSObject

View File

@ -14,7 +14,7 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "Private/FIRAnalyticsConfiguration.h" #import "FirebaseCore/Sources/Private/FIRAnalyticsConfiguration.h"
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations" #pragma clang diagnostic ignored "-Wdeprecated-implementations"

View File

@ -22,17 +22,22 @@
#import <AppKit/AppKit.h> #import <AppKit/AppKit.h>
#endif #endif
#import "FIRApp.h" #import <FirebaseCore/FIRApp.h>
#import "Private/FIRAnalyticsConfiguration.h" #import "FirebaseCore/Sources/FIRBundleUtil.h"
#import "Private/FIRAppInternal.h" #import "FirebaseCore/Sources/FIRVersion.h"
#import "Private/FIRBundleUtil.h" #import "FirebaseCore/Sources/Private/FIRAnalyticsConfiguration.h"
#import "Private/FIRComponentContainerInternal.h" #import "FirebaseCore/Sources/Private/FIRAppInternal.h"
#import "Private/FIRConfigurationInternal.h" #import "FirebaseCore/Sources/Private/FIRComponentContainerInternal.h"
#import "Private/FIRCoreDiagnosticsConnector.h" #import "FirebaseCore/Sources/Private/FIRConfigurationInternal.h"
#import "Private/FIRLibrary.h" #import "FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h"
#import "Private/FIRLogger.h" #import "FirebaseCore/Sources/Private/FIRLibrary.h"
#import "Private/FIROptionsInternal.h" #import "FirebaseCore/Sources/Private/FIRLogger.h"
#import "FirebaseCore/Sources/Private/FIROptionsInternal.h"
#import <GoogleUtilities/GULAppEnvironmentUtil.h>
#import <objc/runtime.h>
// The kFIRService strings are only here while transitioning CoreDiagnostics from the Analytics // The kFIRService strings are only here while transitioning CoreDiagnostics from the Analytics
// pod to a Core dependency. These symbols are not used and should be deleted after the transition. // pod to a Core dependency. These symbols are not used and should be deleted after the transition.
@ -111,6 +116,7 @@ static NSMutableArray<Class<FIRLibrary>> *sRegisteredAsConfigurable;
static NSMutableDictionary *sAllApps; static NSMutableDictionary *sAllApps;
static FIRApp *sDefaultApp; static FIRApp *sDefaultApp;
static NSMutableDictionary *sLibraryVersions; static NSMutableDictionary *sLibraryVersions;
static dispatch_once_t sFirebaseUserAgentOnceToken;
+ (void)configure { + (void)configure {
FIROptions *options = [FIROptions defaultOptions]; FIROptions *options = [FIROptions defaultOptions];
@ -159,8 +165,9 @@ static NSMutableDictionary *sLibraryVersions;
if ([name isEqualToString:kFIRDefaultAppName]) { if ([name isEqualToString:kFIRDefaultAppName]) {
if (sDefaultApp) { if (sDefaultApp) {
[NSException raise:kFirebaseCoreErrorDomain // The default app already exixts. Handle duplicate `configure` calls and return.
format:@"Default app has already been configured."]; [self appWasConfiguredTwice:sDefaultApp usingOptions:options];
return;
} }
FIRLogDebug(kFIRLoggerCore, @"I-COR000001", @"Configuring the default app."); FIRLogDebug(kFIRLoggerCore, @"I-COR000001", @"Configuring the default app.");
@ -176,8 +183,9 @@ static NSMutableDictionary *sLibraryVersions;
@synchronized(self) { @synchronized(self) {
if (sAllApps && sAllApps[name]) { if (sAllApps && sAllApps[name]) {
[NSException raise:kFirebaseCoreErrorDomain // The app already exists. Handle a duplicate `configure` call and return.
format:@"App named %@ has already been configured.", name]; [self appWasConfiguredTwice:sAllApps[name] usingOptions:options];
return;
} }
} }
@ -191,10 +199,44 @@ static NSMutableDictionary *sLibraryVersions;
} }
[FIRApp addAppToAppDictionary:app]; [FIRApp addAppToAppDictionary:app];
// The FIRApp instance is ready to go, `sDefaultApp` is assigned, other SDKs are now ready to be
// instantiated.
[app.container instantiateEagerComponents];
[FIRApp sendNotificationsToSDKs:app]; [FIRApp sendNotificationsToSDKs:app];
} }
} }
/// Called when `configure` has been called multiple times for the same app. This can either throw
/// an exception (most cases) or ignore the duplicate configuration in situations where it's allowed
/// like an extension.
+ (void)appWasConfiguredTwice:(FIRApp *)app usingOptions:(FIROptions *)options {
// Only extensions should potentially be able to call `configure` more than once.
if (![GULAppEnvironmentUtil isAppExtension]) {
// Throw an exception since this is now an invalid state.
if (app.isDefaultApp) {
[NSException raise:kFirebaseCoreErrorDomain
format:@"Default app has already been configured."];
} else {
[NSException raise:kFirebaseCoreErrorDomain
format:@"App named %@ has already been configured.", app.name];
}
}
// In an extension, the entry point could be called multiple times. As long as the options are
// identical we should allow multiple `configure` calls.
if ([options isEqual:app.options]) {
// Everything is identical but the extension's lifecycle triggered `configure` twice.
// Ignore duplicate calls and return since everything should still be in a valid state.
FIRLogDebug(kFIRLoggerCore, @"I-COR000035",
@"Ignoring second `configure` call in an extension.");
return;
} else {
[NSException raise:kFirebaseCoreErrorDomain
format:@"App named %@ has already been configured.", app.name];
}
}
+ (FIRApp *)defaultApp { + (FIRApp *)defaultApp {
if (sDefaultApp) { if (sDefaultApp) {
return sDefaultApp; return sDefaultApp;
@ -236,6 +278,7 @@ static NSMutableDictionary *sLibraryVersions;
sAllApps = nil; sAllApps = nil;
[sLibraryVersions removeAllObjects]; [sLibraryVersions removeAllObjects];
sLibraryVersions = nil; sLibraryVersions = nil;
sFirebaseUserAgentOnceToken = 0;
} }
} }
@ -352,7 +395,7 @@ static NSMutableDictionary *sLibraryVersions;
} }
// Check if the Analytics flag is explicitly set. If so, no further actions are necessary. // Check if the Analytics flag is explicitly set. If so, no further actions are necessary.
if ([self.options isAnalyticsCollectionExpicitlySet]) { if ([self.options isAnalyticsCollectionExplicitlySet]) {
return; return;
} }
@ -517,6 +560,25 @@ static NSMutableDictionary *sLibraryVersions;
+ (NSString *)firebaseUserAgent { + (NSString *)firebaseUserAgent {
@synchronized(self) { @synchronized(self) {
dispatch_once(&sFirebaseUserAgentOnceToken, ^{
// Report FirebaseCore version for useragent string
[FIRApp registerLibrary:@"fire-ios"
withVersion:[NSString stringWithUTF8String:FIRCoreVersionString]];
NSDictionary<NSString *, id> *info = [[NSBundle mainBundle] infoDictionary];
NSString *xcodeVersion = info[@"DTXcodeBuild"];
NSString *sdkVersion = info[@"DTSDKBuild"];
if (xcodeVersion) {
[FIRApp registerLibrary:@"xcode" withVersion:xcodeVersion];
}
if (sdkVersion) {
[FIRApp registerLibrary:@"apple-sdk" withVersion:sdkVersion];
}
NSString *swiftFlagValue = [self hasSwiftRuntime] ? @"true" : @"false";
[FIRApp registerLibrary:@"swift" withVersion:swiftFlagValue];
});
NSMutableArray<NSString *> *libraries = NSMutableArray<NSString *> *libraries =
[[NSMutableArray<NSString *> alloc] initWithCapacity:sLibraryVersions.count]; [[NSMutableArray<NSString *> alloc] initWithCapacity:sLibraryVersions.count];
for (NSString *libraryName in sLibraryVersions) { for (NSString *libraryName in sLibraryVersions) {
@ -528,6 +590,20 @@ static NSMutableDictionary *sLibraryVersions;
} }
} }
+ (BOOL)hasSwiftRuntime {
// The class
// [Swift._SwiftObject](https://github.com/apple/swift/blob/5eac3e2818eb340b11232aff83edfbd1c307fa03/stdlib/public/runtime/SwiftObject.h#L35)
// is a part of Swift runtime, so it should be present if Swift runtime is available.
BOOL hasSwiftRuntime =
objc_lookUpClass("Swift._SwiftObject") != nil ||
// Swift object class name before
// https://github.com/apple/swift/commit/9637b4a6e11ddca72f5f6dbe528efc7c92f14d01
objc_getClass("_TtCs12_SwiftObject") != nil;
return hasSwiftRuntime;
}
- (void)checkExpectedBundleID { - (void)checkExpectedBundleID {
NSArray *bundles = [FIRBundleUtil relevantBundles]; NSArray *bundles = [FIRBundleUtil relevantBundles];
NSString *expectedBundleID = [self expectedBundleID]; NSString *expectedBundleID = [self expectedBundleID];
@ -811,16 +887,16 @@ static NSMutableDictionary *sLibraryVersions;
- (void)subscribeForAppDidBecomeActiveNotifications { - (void)subscribeForAppDidBecomeActiveNotifications {
#if TARGET_OS_IOS || TARGET_OS_TV #if TARGET_OS_IOS || TARGET_OS_TV
NSNotificationName notificationName = UIApplicationDidBecomeActiveNotification; NSNotificationName notificationName = UIApplicationDidBecomeActiveNotification;
#endif #elif TARGET_OS_OSX
#if TARGET_OS_OSX
NSNotificationName notificationName = NSApplicationDidBecomeActiveNotification; NSNotificationName notificationName = NSApplicationDidBecomeActiveNotification;
#endif #endif
#if !TARGET_OS_WATCH
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appDidBecomeActive:) selector:@selector(appDidBecomeActive:)
name:notificationName name:notificationName
object:nil]; object:nil];
#endif
} }
- (void)appDidBecomeActive:(NSNotification *)notification { - (void)appDidBecomeActive:(NSNotification *)notification {

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#import "Private/FIRAppAssociationRegistration.h" #import "FirebaseCore/Sources/Private/FIRAppAssociationRegistration.h"
#import <objc/runtime.h> #import <objc/runtime.h>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#import "Private/FIRBundleUtil.h" #import "FirebaseCore/Sources/FIRBundleUtil.h"
#import <GoogleUtilities/GULAppEnvironmentUtil.h> #import <GoogleUtilities/GULAppEnvironmentUtil.h>

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
#import "Private/FIRComponent.h" #import "FirebaseCore/Sources/Private/FIRComponent.h"
#import "Private/FIRComponentContainer.h" #import "FirebaseCore/Sources/Private/FIRComponentContainer.h"
#import "Private/FIRDependency.h" #import "FirebaseCore/Sources/Private/FIRDependency.h"
@interface FIRComponent () @interface FIRComponent ()

View File

@ -14,26 +14,27 @@
* limitations under the License. * limitations under the License.
*/ */
#import "Private/FIRComponentContainer.h" #import "FirebaseCore/Sources/Private/FIRComponentContainer.h"
#import "Private/FIRAppInternal.h" #import "FirebaseCore/Sources/Private/FIRAppInternal.h"
#import "Private/FIRComponent.h" #import "FirebaseCore/Sources/Private/FIRComponent.h"
#import "Private/FIRLibrary.h" #import "FirebaseCore/Sources/Private/FIRLibrary.h"
#import "Private/FIRLogger.h" #import "FirebaseCore/Sources/Private/FIRLogger.h"
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@interface FIRComponentContainer () { @interface FIRComponentContainer ()
dispatch_queue_t _containerQueue;
}
/// The dictionary of components that are registered for a particular app. The key is an NSString /// The dictionary of components that are registered for a particular app. The key is an `NSString`
/// of the protocol. /// of the protocol.
@property(nonatomic, strong) NSMutableDictionary<NSString *, FIRComponentCreationBlock> *components; @property(nonatomic, strong) NSMutableDictionary<NSString *, FIRComponentCreationBlock> *components;
/// Cached instances of components that requested to be cached. /// Cached instances of components that requested to be cached.
@property(nonatomic, strong) NSMutableDictionary<NSString *, id> *cachedInstances; @property(nonatomic, strong) NSMutableDictionary<NSString *, id> *cachedInstances;
/// Protocols of components that have requested to be eagerly instantiated.
@property(nonatomic, strong, nullable) NSMutableArray<Protocol *> *eagerProtocolsToInstantiate;
@end @end
@implementation FIRComponentContainer @implementation FIRComponentContainer
@ -69,8 +70,6 @@ static NSMutableSet<Class> *sFIRComponentRegistrants;
_app = app; _app = app;
_cachedInstances = [NSMutableDictionary<NSString *, id> dictionary]; _cachedInstances = [NSMutableDictionary<NSString *, id> dictionary];
_components = [NSMutableDictionary<NSString *, FIRComponentCreationBlock> dictionary]; _components = [NSMutableDictionary<NSString *, FIRComponentCreationBlock> dictionary];
_containerQueue =
dispatch_queue_create("com.google.FirebaseComponentContainer", DISPATCH_QUEUE_SERIAL);
[self populateComponentsFromRegisteredClasses:allRegistrants forApp:app]; [self populateComponentsFromRegisteredClasses:allRegistrants forApp:app];
} }
@ -78,6 +77,9 @@ static NSMutableSet<Class> *sFIRComponentRegistrants;
} }
- (void)populateComponentsFromRegisteredClasses:(NSSet<Class> *)classes forApp:(FIRApp *)app { - (void)populateComponentsFromRegisteredClasses:(NSSet<Class> *)classes forApp:(FIRApp *)app {
// Keep track of any components that need to eagerly instantiate after all components are added.
self.eagerProtocolsToInstantiate = [[NSMutableArray alloc] init];
// Loop through the verified component registrants and populate the components array. // Loop through the verified component registrants and populate the components array.
for (Class<FIRLibrary> klass in classes) { for (Class<FIRLibrary> klass in classes) {
// Loop through all the components being registered and store them as appropriate. // Loop through all the components being registered and store them as appropriate.
@ -96,14 +98,16 @@ static NSMutableSet<Class> *sFIRComponentRegistrants;
// Store the creation block for later usage. // Store the creation block for later usage.
self.components[protocolName] = component.creationBlock; self.components[protocolName] = component.creationBlock;
// Instantiate the instance if it has requested to be instantiated. // Queue any protocols that should be eagerly instantiated. Don't instantiate them yet
// because they could depend on other components that haven't been added to the components
// array yet.
BOOL shouldInstantiateEager = BOOL shouldInstantiateEager =
(component.instantiationTiming == FIRInstantiationTimingAlwaysEager); (component.instantiationTiming == FIRInstantiationTimingAlwaysEager);
BOOL shouldInstantiateDefaultEager = BOOL shouldInstantiateDefaultEager =
(component.instantiationTiming == FIRInstantiationTimingEagerInDefaultApp && (component.instantiationTiming == FIRInstantiationTimingEagerInDefaultApp &&
[app isDefaultApp]); [app isDefaultApp]);
if (shouldInstantiateEager || shouldInstantiateDefaultEager) { if (shouldInstantiateEager || shouldInstantiateDefaultEager) {
[self instantiateInstanceForProtocol:component.protocol withBlock:component.creationBlock]; [self.eagerProtocolsToInstantiate addObject:component.protocol];
} }
} }
} }
@ -111,11 +115,28 @@ static NSMutableSet<Class> *sFIRComponentRegistrants;
#pragma mark - Instance Creation #pragma mark - Instance Creation
- (void)instantiateEagerComponents {
// After all components are registered, instantiate the ones that are requesting eager
// instantiation.
@synchronized(self) {
for (Protocol *protocol in self.eagerProtocolsToInstantiate) {
// Get an instance for the protocol, which will instantiate it since it couldn't have been
// cached yet. Ignore the instance coming back since we don't need it.
__unused id unusedInstance = [self instanceForProtocol:protocol];
}
// All eager instantiation is complete, clear the stored property now.
self.eagerProtocolsToInstantiate = nil;
}
}
/// Instantiate an instance of a class that conforms to the specified protocol. /// Instantiate an instance of a class that conforms to the specified protocol.
/// This will: /// This will:
/// - Call the block to create an instance if possible, /// - Call the block to create an instance if possible,
/// - Validate that the instance returned conforms to the protocol it claims to, /// - Validate that the instance returned conforms to the protocol it claims to,
/// - Cache the instance if the block requests it /// - Cache the instance if the block requests it
///
/// Note that this method assumes the caller already has @sychronized on self.
- (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol - (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol
withBlock:(FIRComponentCreationBlock)creationBlock { withBlock:(FIRComponentCreationBlock)creationBlock {
if (!creationBlock) { if (!creationBlock) {
@ -140,9 +161,7 @@ static NSMutableSet<Class> *sFIRComponentRegistrants;
// The instance is ready to be returned, but check if it should be cached first before returning. // The instance is ready to be returned, but check if it should be cached first before returning.
if (shouldCache) { if (shouldCache) {
dispatch_sync(_containerQueue, ^{ self.cachedInstances[protocolName] = instance;
self.cachedInstances[protocolName] = instance;
});
} }
return instance; return instance;
@ -153,47 +172,35 @@ static NSMutableSet<Class> *sFIRComponentRegistrants;
- (nullable id)instanceForProtocol:(Protocol *)protocol { - (nullable id)instanceForProtocol:(Protocol *)protocol {
// Check if there is a cached instance, and return it if so. // Check if there is a cached instance, and return it if so.
NSString *protocolName = NSStringFromProtocol(protocol); NSString *protocolName = NSStringFromProtocol(protocol);
__block id cachedInstance;
dispatch_sync(_containerQueue, ^{ id cachedInstance;
@synchronized(self) {
cachedInstance = self.cachedInstances[protocolName]; cachedInstance = self.cachedInstances[protocolName];
}); if (!cachedInstance) {
// Use the creation block to instantiate an instance and return it.
if (cachedInstance) { FIRComponentCreationBlock creationBlock = self.components[protocolName];
return cachedInstance; cachedInstance = [self instantiateInstanceForProtocol:protocol withBlock:creationBlock];
}
} }
return cachedInstance;
// Use the creation block to instantiate an instance and return it.
FIRComponentCreationBlock creationBlock = self.components[protocolName];
return [self instantiateInstanceForProtocol:protocol withBlock:creationBlock];
} }
#pragma mark - Lifecycle #pragma mark - Lifecycle
- (void)removeAllCachedInstances { - (void)removeAllCachedInstances {
// Loop through the cache and notify each instance that is a maintainer to clean up after itself. @synchronized(self) {
// Design note: we're getting a copy here, unlocking the cached instances, iterating over the // Loop through the cache and notify each instance that is a maintainer to clean up after
// copy, then locking and removing all cached instances. A race condition *could* exist where a // itself.
// new cached instance is created between the copy and the removal, but the chances are slim and for (id instance in self.cachedInstances.allValues) {
// side-effects are significantly smaller than including the entire loop in the `dispatch_sync` if ([instance conformsToProtocol:@protocol(FIRComponentLifecycleMaintainer)] &&
// block (access to the cache from inside the block would deadlock and crash). [instance respondsToSelector:@selector(appWillBeDeleted:)]) {
__block NSDictionary<NSString *, id> *instancesCopy; [instance appWillBeDeleted:self.app];
dispatch_sync(_containerQueue, ^{ }
instancesCopy = [self.cachedInstances copy];
});
for (id instance in instancesCopy.allValues) {
if ([instance conformsToProtocol:@protocol(FIRComponentLifecycleMaintainer)] &&
[instance respondsToSelector:@selector(appWillBeDeleted:)]) {
[instance appWillBeDeleted:self.app];
} }
}
instancesCopy = nil; // Empty the cache.
// Empty the cache.
dispatch_sync(_containerQueue, ^{
[self.cachedInstances removeAllObjects]; [self.cachedInstances removeAllObjects];
}); }
} }
@end @end

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
#import "Private/FIRComponentType.h" #import "FirebaseCore/Sources/Private/FIRComponentType.h"
#import "Private/FIRComponentContainerInternal.h" #import "FirebaseCore/Sources/Private/FIRComponentContainerInternal.h"
@implementation FIRComponentType @implementation FIRComponentType

View File

@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#import "Private/FIRConfigurationInternal.h" #import "FirebaseCore/Sources/Private/FIRConfigurationInternal.h"
#import "Private/FIRAnalyticsConfiguration.h" #import "FirebaseCore/Sources/Private/FIRAnalyticsConfiguration.h"
extern void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel); extern void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel);

View File

@ -14,15 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
#import "Private/FIRCoreDiagnosticsConnector.h" #import "FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h"
#import <FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsInterop.h> #import <FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsInterop.h>
#import <FirebaseCore/FIROptions.h> #import <FirebaseCore/FIROptions.h>
#import "Private/FIRAppInternal.h" #import "FirebaseCore/Sources/Private/FIRAppInternal.h"
#import "Private/FIRDiagnosticsData.h" #import "FirebaseCore/Sources/Private/FIRDiagnosticsData.h"
#import "Private/FIROptionsInternal.h" #import "FirebaseCore/Sources/Private/FIROptionsInternal.h"
// Define the interop class symbol declared as an extern in FIRCoreDiagnosticsInterop. // Define the interop class symbol declared as an extern in FIRCoreDiagnosticsInterop.
Class<FIRCoreDiagnosticsInterop> FIRCoreDiagnosticsImplementation; Class<FIRCoreDiagnosticsInterop> FIRCoreDiagnosticsImplementation;

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
#import "Private/FIRDependency.h" #import "FirebaseCore/Sources/Private/FIRDependency.h"
@interface FIRDependency () @interface FIRDependency ()

View File

@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
#import "Private/FIRDiagnosticsData.h" #import "FirebaseCore/Sources/Private/FIRDiagnosticsData.h"
#import <FirebaseCore/FIRApp.h> #import <FirebaseCore/FIRApp.h>
#import "Private/FIRAppInternal.h" #import "FirebaseCore/Sources/Private/FIRAppInternal.h"
#import "Private/FIROptionsInternal.h" #import "FirebaseCore/Sources/Private/FIROptionsInternal.h"
@implementation FIRDiagnosticsData { @implementation FIRDiagnosticsData {
/** Backing ivar for the diagnosticObjects property. */ /** Backing ivar for the diagnosticObjects property. */

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#import "Private/FIRErrors.h" #import "FirebaseCore/Sources/Private/FIRErrors.h"
NSString *const kFirebaseErrorDomain = @"com.firebase"; NSString *const kFirebaseErrorDomain = @"com.firebase";
NSString *const kFirebaseConfigErrorDomain = @"com.firebase.config"; NSString *const kFirebaseConfigErrorDomain = @"com.firebase.config";

View File

@ -0,0 +1,61 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "FirebaseCore/Sources/Private/FIRHeartbeatInfo.h"
#import <GoogleUtilities/GULHeartbeatDateStorage.h>
#import <GoogleUtilities/GULLogger.h>
const static long secondsInDay = 864000;
@implementation FIRHeartbeatInfo : NSObject
/** Updates the storage with the heartbeat information corresponding to this tag.
* @param heartbeatTag Tag which could either be sdk specific tag or the global tag.
* @return Boolean representing whether the heartbeat needs to be sent for this tag or not.
*/
+ (BOOL)updateIfNeededHeartbeatDateForTag:(NSString *)heartbeatTag {
@synchronized(self) {
NSString *const kHeartbeatStorageFile = @"HEARTBEAT_INFO_STORAGE";
GULHeartbeatDateStorage *dataStorage =
[[GULHeartbeatDateStorage alloc] initWithFileName:kHeartbeatStorageFile];
NSDate *heartbeatTime = [dataStorage heartbeatDateForTag:heartbeatTag];
NSDate *currentDate = [NSDate date];
if (heartbeatTime != nil) {
NSTimeInterval secondsBetween = [currentDate timeIntervalSinceDate:heartbeatTime];
if (secondsBetween < secondsInDay) {
return false;
}
}
return [dataStorage setHearbeatDate:currentDate forTag:heartbeatTag];
}
}
+ (FIRHeartbeatInfoCode)heartbeatCodeForTag:(NSString *)heartbeatTag {
NSString *globalTag = @"GLOBAL";
BOOL isSdkHeartbeatNeeded = [FIRHeartbeatInfo updateIfNeededHeartbeatDateForTag:heartbeatTag];
BOOL isGlobalHeartbeatNeeded = [FIRHeartbeatInfo updateIfNeededHeartbeatDateForTag:globalTag];
if (!isSdkHeartbeatNeeded && !isGlobalHeartbeatNeeded) {
// Both sdk and global heartbeat not needed.
return FIRHeartbeatInfoCodeNone;
} else if (isSdkHeartbeatNeeded && !isGlobalHeartbeatNeeded) {
// Only SDK heartbeat needed.
return FIRHeartbeatInfoCodeSDK;
} else if (!isSdkHeartbeatNeeded && isGlobalHeartbeatNeeded) {
// Only global heartbeat needed.
return FIRHeartbeatInfoCodeGlobal;
} else {
// Both sdk and global heartbeat are needed.
return FIRHeartbeatInfoCodeCombined;
}
}
@end

View File

@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#import "Private/FIRLogger.h" #import "FirebaseCore/Sources/Private/FIRLogger.h"
#import <FirebaseCore/FIRLoggerLevel.h> #import <FirebaseCore/FIRLoggerLevel.h>
#import <GoogleUtilities/GULAppEnvironmentUtil.h> #import <GoogleUtilities/GULAppEnvironmentUtil.h>
#import <GoogleUtilities/GULLogger.h> #import <GoogleUtilities/GULLogger.h>
#import "Private/FIRVersion.h" #import "FirebaseCore/Sources/FIRVersion.h"
FIRLoggerService kFIRLoggerCore = @"[Firebase/Core]"; FIRLoggerService kFIRLoggerCore = @"[Firebase/Core]";

View File

@ -12,12 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#import "Private/FIRAppInternal.h" #import "FirebaseCore/Sources/FIRBundleUtil.h"
#import "Private/FIRBundleUtil.h" #import "FirebaseCore/Sources/FIRVersion.h"
#import "Private/FIRErrors.h" #import "FirebaseCore/Sources/Private/FIRAppInternal.h"
#import "Private/FIRLogger.h" #import "FirebaseCore/Sources/Private/FIRLogger.h"
#import "Private/FIROptionsInternal.h" #import "FirebaseCore/Sources/Private/FIROptionsInternal.h"
#import "Private/FIRVersion.h"
// Keys for the strings in the plist file. // Keys for the strings in the plist file.
NSString *const kFIRAPIKey = @"API_KEY"; NSString *const kFIRAPIKey = @"API_KEY";
@ -110,22 +109,6 @@ static NSDictionary *sDefaultOptionsDictionary = nil;
#pragma mark - Private class methods #pragma mark - Private class methods
+ (void)initialize {
// Report FirebaseCore version for useragent string
[FIRApp registerLibrary:@"fire-ios"
withVersion:[NSString stringWithUTF8String:FIRCoreVersionString]];
NSDictionary<NSString *, id> *info = [[NSBundle mainBundle] infoDictionary];
NSString *xcodeVersion = info[@"DTXcodeBuild"];
NSString *sdkVersion = info[@"DTSDKBuild"];
if (xcodeVersion) {
[FIRApp registerLibrary:@"xcode" withVersion:xcodeVersion];
}
if (sdkVersion) {
[FIRApp registerLibrary:@"apple-sdk" withVersion:sdkVersion];
}
}
+ (NSDictionary *)defaultOptionsDictionary { + (NSDictionary *)defaultOptionsDictionary {
if (sDefaultOptionsDictionary != nil) { if (sDefaultOptionsDictionary != nil) {
return sDefaultOptionsDictionary; return sDefaultOptionsDictionary;
@ -346,6 +329,59 @@ static NSDictionary *sDefaultOptionsDictionary = nil;
_appGroupID = [appGroupID copy]; _appGroupID = [appGroupID copy];
} }
#pragma mark - Equality
- (BOOL)isEqual:(id)object {
if (!object || ![object isKindOfClass:[FIROptions class]]) {
return NO;
}
return [self isEqualToOptions:(FIROptions *)object];
}
- (BOOL)isEqualToOptions:(FIROptions *)options {
// Skip any non-FIROptions classes.
if (![options isKindOfClass:[FIROptions class]]) {
return NO;
}
// Check the internal dictionary and custom properties for differences.
if (![options.optionsDictionary isEqualToDictionary:self.optionsDictionary]) {
return NO;
}
// Validate extra properties not contained in the dictionary. Only validate it if one of the
// objects has the property set.
if ((options.deepLinkURLScheme != nil || self.deepLinkURLScheme != nil) &&
![options.deepLinkURLScheme isEqualToString:self.deepLinkURLScheme]) {
return NO;
}
if ((options.appGroupID != nil || self.appGroupID != nil) &&
![options.appGroupID isEqualToString:self.appGroupID]) {
return NO;
}
// Validate the Analytics options haven't changed with the Info.plist.
if (![options.analyticsOptionsDictionary isEqualToDictionary:self.analyticsOptionsDictionary]) {
return NO;
}
// We don't care about the `editingLocked` or `usingOptionsFromDefaultPlist` properties since
// those relate to lifecycle and construction, we only care if the contents of the options
// themselves are equal.
return YES;
}
- (NSUInteger)hash {
// This is strongly recommended for any object that implements a custom `isEqual:` method to
// ensure that dictionary and set behavior matches other `isEqual:` checks.
// Note: `self.analyticsOptionsDictionary` was left out here since it solely relies on the
// contents of the main bundle's `Info.plist`. We should avoid reading that file and the contents
// should be identical.
return self.optionsDictionary.hash ^ self.deepLinkURLScheme.hash ^ self.appGroupID.hash;
}
#pragma mark - Internal instance methods #pragma mark - Internal instance methods
- (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary { - (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary {
@ -399,7 +435,7 @@ static NSDictionary *sDefaultOptionsDictionary = nil;
return [value boolValue]; return [value boolValue];
} }
- (BOOL)isAnalyticsCollectionExpicitlySet { - (BOOL)isAnalyticsCollectionExplicitlySet {
// If it's de-activated, it classifies as explicity set. If not, it's not a good enough indication // If it's de-activated, it classifies as explicity set. If not, it's not a good enough indication
// that the developer wants FirebaseAnalytics enabled so continue checking. // that the developer wants FirebaseAnalytics enabled so continue checking.
if (self.isAnalyticsCollectionDeactivated) { if (self.isAnalyticsCollectionDeactivated) {

View File

@ -15,8 +15,8 @@
*/ */
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "FIRComponentType.h" #import <FirebaseCore/FIRComponentType.h>
#import "FIRLibrary.h" #import <FirebaseCore/FIRLibrary.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN

View File

@ -15,8 +15,8 @@
*/ */
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "FIRComponent.h" #import <FirebaseCore/FIRComponent.h>
#import "FIRComponentContainer.h" #import <FirebaseCore/FIRComponentContainer.h>
@class FIRApp; @class FIRApp;
@ -24,13 +24,16 @@ NS_ASSUME_NONNULL_BEGIN
@interface FIRComponentContainer (Private) @interface FIRComponentContainer (Private)
/// Initializes a contain for a given app. This should only be called by the app itself. /// Initializes a container for a given app. This should only be called by the app itself.
- (instancetype)initWithApp:(FIRApp *)app; - (instancetype)initWithApp:(FIRApp *)app;
/// Retrieves an instance that conforms to the specified protocol. This will return `nil` if the /// Retrieves an instance that conforms to the specified protocol. This will return `nil` if the
/// protocol wasn't registered, or if the instance couldn't instantiate for the provided app. /// protocol wasn't registered, or if the instance couldn't be instantiated for the provided app.
- (nullable id)instanceForProtocol:(Protocol *)protocol NS_SWIFT_NAME(instance(for:)); - (nullable id)instanceForProtocol:(Protocol *)protocol NS_SWIFT_NAME(instance(for:));
/// Instantiates all the components that have registered as "eager" after initialization.
- (void)instantiateEagerComponents;
/// Remove all of the cached instances stored and allow them to clean up after themselves. /// Remove all of the cached instances stored and allow them to clean up after themselves.
- (void)removeAllCachedInstances; - (void)removeAllCachedInstances;

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
#import "FIRConfiguration.h" #import <FirebaseCore/FIRConfiguration.h>
@class FIRAnalyticsConfiguration; @class FIRAnalyticsConfiguration;

View File

@ -0,0 +1,39 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FIRHeartbeatInfo : NSObject
// Enum representing the different heartbeat codes.
typedef NS_ENUM(NSInteger, FIRHeartbeatInfoCode) {
FIRHeartbeatInfoCodeNone = 0,
FIRHeartbeatInfoCodeSDK = 1,
FIRHeartbeatInfoCodeGlobal = 2,
FIRHeartbeatInfoCodeCombined = 3,
};
/**
* Get heartbeat code requred for the sdk.
* @param heartbeatTag String representing the sdk heartbeat tag.
* @return Heartbeat code indicating whether or not an sdk/global heartbeat
* needs to be sent
*/
+ (FIRHeartbeatInfoCode)heartbeatCodeForTag:(NSString *)heartbeatTag;
@end
NS_ASSUME_NONNULL_END

View File

@ -18,7 +18,8 @@
#define FIRLibrary_h #define FIRLibrary_h
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "FIRComponent.h"
#import <FirebaseCore/FIRComponent.h>
@class FIRApp; @class FIRApp;

View File

@ -65,7 +65,7 @@ extern NSString *const kServiceInfoFileType;
* Indicates whether or not Analytics collection was explicitly enabled via a plist flag or at * Indicates whether or not Analytics collection was explicitly enabled via a plist flag or at
* runtime. * runtime.
*/ */
@property(nonatomic, readonly) BOOL isAnalyticsCollectionExpicitlySet; @property(nonatomic, readonly) BOOL isAnalyticsCollectionExplicitlySet;
/** /**
* Whether or not Analytics Collection was enabled. Analytics Collection is enabled unless * Whether or not Analytics Collection was enabled. Analytics Collection is enabled unless

View File

@ -3,7 +3,8 @@
This repository contains a subset of the Firebase iOS SDK source. It currently This repository contains a subset of the Firebase iOS SDK source. It currently
includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase, includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase,
FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging,
FirebaseInAppMessagingDisplay, FirebaseMessaging and FirebaseStorage. FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and
FirebaseStorage.
The repository also includes GoogleUtilities source. The The repository also includes GoogleUtilities source. The
[GoogleUtilities](GoogleUtilities/README.md) pod is [GoogleUtilities](GoogleUtilities/README.md) pod is
@ -75,14 +76,31 @@ the following software:
* Xcode 10.1 (or later) * Xcode 10.1 (or later)
* CocoaPods 1.7.2 (or later) * CocoaPods 1.7.2 (or later)
* [CocoaPods generate](https://github.com/square/cocoapods-generate)
For the pod that you want to develop: For the pod that you want to develop:
`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open` `pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios`
Note: If the CocoaPods cache is out of date, you may need to run
`pod repo update` before the `pod gen` command.
Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for
those platforms. Since 10.2, Xcode does not properly handle multi-platform
CocoaPods workspaces.
Firestore has a self contained Xcode project. See Firestore has a self contained Xcode project. See
[Firestore/README.md](Firestore/README.md). [Firestore/README.md](Firestore/README.md).
### Development for Catalyst
* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios`
* Check the Mac box in the App-iOS Build Settings
* Sign the App in the Settings Signing & Capabilities tab
* Click Pods in the Project Manager
* Add Signing to the iOS host app and unit test targets
* Select the Unit-unit scheme
* Run it to build and test
### Adding a New Firebase Pod ### Adding a New Firebase Pod
See [AddNewPod.md](AddNewPod.md). See [AddNewPod.md](AddNewPod.md).
@ -98,13 +116,17 @@ Travis will verify that any code changes are done in a style compliant way. Inst
These commands will get the right versions: These commands will get the right versions:
``` ```
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/773cb75d360b58f32048f5964038d09825a507c8/Formula/clang-format.rb brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/3dfea1004e0736754bbf49673cca8aaed8a94089/Formula/swiftformat.rb brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb
``` ```
Note: if you already have a newer version of these installed you may need to Note: if you already have a newer version of these installed you may need to
`brew switch` to this version. `brew switch` to this version.
To update this section, find the versions of clang-format and swiftformat.rb to
match the versions in the CI failure logs
[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula).
### Running Unit Tests ### Running Unit Tests
Select a scheme and press Command-u to build a component and run its unit tests. Select a scheme and press Command-u to build a component and run its unit tests.
@ -177,34 +199,42 @@ We've seen an amazing amount of interest and contributions to improve the Fireba
very grateful! We'd like to empower as many developers as we can to be able to use Firebase and very grateful! We'd like to empower as many developers as we can to be able to use Firebase and
participate in the Firebase community. participate in the Firebase community.
### macOS and tvOS ### tvOS, macOS, and Catalyst
Thanks to contributions from the community, FirebaseABTesting, FirebaseAuth, FirebaseCore, Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and work on
FirebaseDatabase, FirebaseMessaging, tvOS, macOS, and Catalyst.
FirebaseFirestore, FirebaseFunctions and FirebaseStorage now compile, run unit tests, and work on
macOS and tvOS.
For tvOS, checkout the [Sample](Example/tvOSSample). For tvOS, checkout the [Sample](Example/tvOSSample).
Keep in mind that macOS and tvOS are not officially supported by Firebase, and this repository is Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this
actively developed primarily for iOS. While we can catch basic unit test issues with Travis, there repository is actively developed primarily for iOS. While we can catch basic unit test issues with
may be some changes where the SDK no longer works as expected on macOS or tvOS. If you encounter Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you
this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues).
Note that the Firebase pod is not available for macOS and tvOS. During app setup in the console, you may get to a step that mentions something like "Checking if the app
has communicated with our servers". This relies on Analytics and will not work on macOS/tvOS/Catalyst.
**It's safe to ignore the message and continue**, the rest of the SDKs will work as expected.
To install, add a subset of the following to the Podfile: To install, add a subset of the following to the Podfile:
``` ```
pod 'FirebaseABTesting' pod 'Firebase/ABTesting'
pod 'FirebaseAuth' pod 'Firebase/Auth'
pod 'FirebaseCore' pod 'Firebase/Crashlytics'
pod 'FirebaseDatabase' pod 'Firebase/Database'
pod 'FirebaseFirestore' pod 'Firebase/Firestore'
pod 'FirebaseFunctions' pod 'Firebase/Functions'
pod 'FirebaseMessaging' pod 'Firebase/Messaging'
pod 'FirebaseStorage' pod 'Firebase/RemoteConfig'
pod 'Firebase/Storage'
``` ```
#### Additional Catalyst Notes
* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability`
to Build Settings.
* FirebaseFirestore requires signing the
[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681).
## Roadmap ## Roadmap
See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source

View File

@ -17,12 +17,13 @@
#import <objc/runtime.h> #import <objc/runtime.h>
#include <sys/utsname.h> #include <sys/utsname.h>
#import <GoogleDataTransport/GDTConsoleLogger.h> #import <GoogleDataTransport/GDTCORConsoleLogger.h>
#import <GoogleDataTransport/GDTEvent.h> #import <GoogleDataTransport/GDTCOREvent.h>
#import <GoogleDataTransport/GDTTargets.h> #import <GoogleDataTransport/GDTCORTargets.h>
#import <GoogleDataTransport/GDTTransport.h> #import <GoogleDataTransport/GDTCORTransport.h>
#import <GoogleUtilities/GULAppEnvironmentUtil.h> #import <GoogleUtilities/GULAppEnvironmentUtil.h>
#import <GoogleUtilities/GULHeartbeatDateStorage.h>
#import <GoogleUtilities/GULLogger.h> #import <GoogleUtilities/GULLogger.h>
#import <FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsData.h> #import <FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsData.h>
@ -34,8 +35,6 @@
#import "FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h" #import "FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h"
#import "FIRCDLibrary/FIRCoreDiagnosticsDateFileStorage.h"
/** The logger service string to use when printing to the console. */ /** The logger service string to use when printing to the console. */
static GULLoggerService kFIRCoreDiagnostics = @"[FirebaseCoreDiagnostics/FIRCoreDiagnostics]"; static GULLoggerService kFIRCoreDiagnostics = @"[FirebaseCoreDiagnostics/FIRCoreDiagnostics]";
@ -85,6 +84,7 @@ static NSString *const kFIRAppDiagnosticsConfigurationTypeKey =
static NSString *const kFIRAppDiagnosticsFIRAppKey = @"FIRAppDiagnosticsFIRAppKey"; static NSString *const kFIRAppDiagnosticsFIRAppKey = @"FIRAppDiagnosticsFIRAppKey";
static NSString *const kFIRAppDiagnosticsSDKNameKey = @"FIRAppDiagnosticsSDKNameKey"; static NSString *const kFIRAppDiagnosticsSDKNameKey = @"FIRAppDiagnosticsSDKNameKey";
static NSString *const kFIRAppDiagnosticsSDKVersionKey = @"FIRAppDiagnosticsSDKVersionKey"; static NSString *const kFIRAppDiagnosticsSDKVersionKey = @"FIRAppDiagnosticsSDKVersionKey";
static NSString *const kFIRCoreDiagnosticsHeartbeatTag = @"FIRCoreDiagnostics";
/** /**
* The file name to the recent heartbeat date. * The file name to the recent heartbeat date.
@ -92,7 +92,8 @@ static NSString *const kFIRAppDiagnosticsSDKVersionKey = @"FIRAppDiagnosticsSDKV
NSString *const kFIRCoreDiagnosticsHeartbeatDateFileName = @"FIREBASE_DIAGNOSTICS_HEARTBEAT_DATE"; NSString *const kFIRCoreDiagnosticsHeartbeatDateFileName = @"FIREBASE_DIAGNOSTICS_HEARTBEAT_DATE";
/** /**
* @note This should implement the GDTEventDataObject protocol, but can't because of weak-linking. * @note This should implement the GDTCOREventDataObject protocol, but can't because of
* weak-linking.
*/ */
@interface FIRCoreDiagnosticsLog : NSObject @interface FIRCoreDiagnosticsLog : NSObject
@ -111,14 +112,14 @@ NSString *const kFIRCoreDiagnosticsHeartbeatDateFileName = @"FIREBASE_DIAGNOSTIC
return self; return self;
} }
// Provided and required by the GDTEventDataObject protocol. // Provided and required by the GDTCOREventDataObject protocol.
- (NSData *)transportBytes { - (NSData *)transportBytes {
pb_ostream_t sizestream = PB_OSTREAM_SIZING; pb_ostream_t sizestream = PB_OSTREAM_SIZING;
// Encode 1 time to determine the size. // Encode 1 time to determine the size.
if (!pb_encode(&sizestream, logs_proto_mobilesdk_ios_ICoreConfiguration_fields, &_config)) { if (!pb_encode(&sizestream, logs_proto_mobilesdk_ios_ICoreConfiguration_fields, &_config)) {
GDTLogError(GDTMCETransportBytesError, @"Error in nanopb encoding for size: %s", GDTCORLogError(GDTCORMCETransportBytesError, @"Error in nanopb encoding for size: %s",
PB_GET_ERROR(&sizestream)); PB_GET_ERROR(&sizestream));
} }
// Encode a 2nd time to actually get the bytes from it. // Encode a 2nd time to actually get the bytes from it.
@ -126,8 +127,8 @@ NSString *const kFIRCoreDiagnosticsHeartbeatDateFileName = @"FIREBASE_DIAGNOSTIC
CFMutableDataRef dataRef = CFDataCreateMutable(CFAllocatorGetDefault(), bufferSize); CFMutableDataRef dataRef = CFDataCreateMutable(CFAllocatorGetDefault(), bufferSize);
pb_ostream_t ostream = pb_ostream_from_buffer((void *)CFDataGetBytePtr(dataRef), bufferSize); pb_ostream_t ostream = pb_ostream_from_buffer((void *)CFDataGetBytePtr(dataRef), bufferSize);
if (!pb_encode(&ostream, logs_proto_mobilesdk_ios_ICoreConfiguration_fields, &_config)) { if (!pb_encode(&ostream, logs_proto_mobilesdk_ios_ICoreConfiguration_fields, &_config)) {
GDTLogError(GDTMCETransportBytesError, @"Error in nanopb encoding for bytes: %s", GDTCORLogError(GDTCORMCETransportBytesError, @"Error in nanopb encoding for bytes: %s",
PB_GET_ERROR(&ostream)); PB_GET_ERROR(&ostream));
} }
CFDataSetLength(dataRef, ostream.bytes_written); CFDataSetLength(dataRef, ostream.bytes_written);
@ -149,10 +150,10 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, readonly) dispatch_queue_t diagnosticsQueue; @property(nonatomic, readonly) dispatch_queue_t diagnosticsQueue;
/** The transport object used to send data. */ /** The transport object used to send data. */
@property(nonatomic, readonly) GDTTransport *transport; @property(nonatomic, readonly) GDTCORTransport *transport;
/** The storage to store the date of the last sent heartbeat. */ /** The storage to store the date of the last sent heartbeat. */
@property(nonatomic, readonly) FIRCoreDiagnosticsDateFileStorage *heartbeatDateStorage; @property(nonatomic, readonly) GULHeartbeatDateStorage *heartbeatDateStorage;
@end @end
@ -170,24 +171,24 @@ NS_ASSUME_NONNULL_END
} }
- (instancetype)init { - (instancetype)init {
GDTTransport *transport = [[GDTTransport alloc] initWithMappingID:@"137" GDTCORTransport *transport = [[GDTCORTransport alloc] initWithMappingID:@"137"
transformers:nil transformers:nil
target:kGDTTargetCCT]; target:kGDTCORTargetFLL];
FIRCoreDiagnosticsDateFileStorage *dateStorage = [[FIRCoreDiagnosticsDateFileStorage alloc] GULHeartbeatDateStorage *dateStorage =
initWithFileURL:[[self class] filePathURLWithName:kFIRCoreDiagnosticsHeartbeatDateFileName]]; [[GULHeartbeatDateStorage alloc] initWithFileName:kFIRCoreDiagnosticsHeartbeatDateFileName];
return [self initWithTransport:transport heartbeatDateStorage:dateStorage]; return [self initWithTransport:transport heartbeatDateStorage:dateStorage];
} }
/** Initializer for unit tests. /** Initializer for unit tests.
* *
* @param transport A `GDTTransport` instance which that be used to send event. * @param transport A `GDTCORTransport` instance which that be used to send event.
* @param heartbeatDateStorage An instanse of date storage to track heartbeat sending. * @param heartbeatDateStorage An instanse of date storage to track heartbeat sending.
* @return Returns the initialized `FIRCoreDiagnostics` instance. * @return Returns the initialized `FIRCoreDiagnostics` instance.
*/ */
- (instancetype)initWithTransport:(GDTTransport *)transport - (instancetype)initWithTransport:(GDTCORTransport *)transport
heartbeatDateStorage:(FIRCoreDiagnosticsDateFileStorage *)heartbeatDateStorage { heartbeatDateStorage:(GULHeartbeatDateStorage *)heartbeatDateStorage {
self = [super init]; self = [super init];
if (self) { if (self) {
_diagnosticsQueue = _diagnosticsQueue =
@ -198,37 +199,6 @@ NS_ASSUME_NONNULL_END
return self; return self;
} }
#pragma mark - File path helpers
/** Returns the URL path of the file with name fileName under the Application Support folder for
* local logging. Creates the Application Support folder if the folder doesn't exist.
*
* @return the URL path of the file with the name fileName in Application Support.
*/
+ (NSURL *)filePathURLWithName:(NSString *)fileName {
@synchronized(self) {
NSArray<NSString *> *paths =
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSArray<NSString *> *components = @[ paths.lastObject, @"Google/FIRApp" ];
NSString *directoryString = [NSString pathWithComponents:components];
NSURL *directoryURL = [NSURL fileURLWithPath:directoryString];
NSError *error;
if (![directoryURL checkResourceIsReachableAndReturnError:&error]) {
// If fail creating the Application Support directory, return nil.
if (![[NSFileManager defaultManager] createDirectoryAtURL:directoryURL
withIntermediateDirectories:YES
attributes:nil
error:&error]) {
GULLogWarning(kFIRCoreDiagnostics, YES, @"I-COR100001",
@"Unable to create internal state storage: %@", error);
return nil;
}
}
return [directoryURL URLByAppendingPathComponent:fileName];
}
}
#pragma mark - Metadata helpers #pragma mark - Metadata helpers
/** Returns the model of iOS device. Sample platform strings are @"iPhone7,1" for iPhone 6 Plus, /** Returns the model of iOS device. Sample platform strings are @"iPhone7,1" for iPhone 6 Plus,
@ -374,7 +344,8 @@ void FIRPopulateProtoWithCommonInfoFromApp(logs_proto_mobilesdk_ios_ICoreConfigu
config->has_pod_name = 1; config->has_pod_name = 1;
if (!diagnosticObjects[kFIRCDllAppsCountKey]) { if (!diagnosticObjects[kFIRCDllAppsCountKey]) {
GDTLogError(GDTMCEGeneralError, @"%@", @"App count is a required value in the data dict."); GDTCORLogError(GDTCORMCEGeneralError, @"%@",
@"App count is a required value in the data dict.");
} }
config->app_count = (int32_t)[diagnosticObjects[kFIRCDllAppsCountKey] integerValue]; config->app_count = (int32_t)[diagnosticObjects[kFIRCDllAppsCountKey] integerValue];
config->has_app_count = 1; config->has_app_count = 1;
@ -635,8 +606,8 @@ void FIRPopulateProtoWithInfoPlistValues(logs_proto_mobilesdk_ios_ICoreConfigura
FIRCoreDiagnosticsLog *log = [[FIRCoreDiagnosticsLog alloc] initWithConfig:icore_config]; FIRCoreDiagnosticsLog *log = [[FIRCoreDiagnosticsLog alloc] initWithConfig:icore_config];
// Send the log as a telemetry event. // Send the log as a telemetry event.
GDTEvent *event = [self.transport eventForTransport]; GDTCOREvent *event = [self.transport eventForTransport];
event.dataObject = (id<GDTEventDataObject>)log; event.dataObject = (id<GDTCOREventDataObject>)log;
[self.transport sendTelemetryEvent:event]; [self.transport sendTelemetryEvent:event];
}); });
} }
@ -646,7 +617,8 @@ void FIRPopulateProtoWithInfoPlistValues(logs_proto_mobilesdk_ios_ICoreConfigura
- (void)setHeartbeatFlagIfNeededToConfig:(logs_proto_mobilesdk_ios_ICoreConfiguration *)config { - (void)setHeartbeatFlagIfNeededToConfig:(logs_proto_mobilesdk_ios_ICoreConfiguration *)config {
// Check if need to send a heartbeat. // Check if need to send a heartbeat.
NSDate *currentDate = [NSDate date]; NSDate *currentDate = [NSDate date];
NSDate *lastCheckin = [self.heartbeatDateStorage date]; NSDate *lastCheckin =
[self.heartbeatDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag];
if (lastCheckin) { if (lastCheckin) {
// Ensure the previous checkin was on a different date in the past. // Ensure the previous checkin was on a different date in the past.
if ([self isDate:currentDate inSameDayOrBeforeThan:lastCheckin]) { if ([self isDate:currentDate inSameDayOrBeforeThan:lastCheckin]) {
@ -655,12 +627,7 @@ void FIRPopulateProtoWithInfoPlistValues(logs_proto_mobilesdk_ios_ICoreConfigura
} }
// Update heartbeat sent date. // Update heartbeat sent date.
NSError *error; [self.heartbeatDateStorage setHearbeatDate:currentDate forTag:kFIRCoreDiagnosticsHeartbeatTag];
if (![self.heartbeatDateStorage setDate:currentDate error:&error]) {
GULLogError(kFIRCoreDiagnostics, NO, @"I-COR100004", @"Unable to persist internal state: %@",
error);
}
// Set the flag. // Set the flag.
config->sdk_name = logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ICORE; config->sdk_name = logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ICORE;
config->has_sdk_name = 1; config->has_sdk_name = 1;

View File

@ -1,64 +0,0 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRCDLibrary/FIRCoreDiagnosticsDateFileStorage.h"
@interface FIRCoreDiagnosticsDateFileStorage ()
@property(nonatomic, readonly) NSURL *fileURL;
@end
@implementation FIRCoreDiagnosticsDateFileStorage
- (instancetype)initWithFileURL:(NSURL *)fileURL {
if (fileURL == nil) {
return nil;
}
self = [super init];
if (self) {
_fileURL = fileURL;
}
return self;
}
- (BOOL)setDate:(nullable NSDate *)date error:(NSError **)outError {
NSString *stringToSave = @"";
if (date != nil) {
NSTimeInterval timestamp = [date timeIntervalSinceReferenceDate];
stringToSave = [NSString stringWithFormat:@"%f", timestamp];
}
return [stringToSave writeToURL:self.fileURL
atomically:YES
encoding:NSUTF8StringEncoding
error:outError];
}
- (nullable NSDate *)date {
NSString *timestampString = [NSString stringWithContentsOfURL:self.fileURL
encoding:NSUTF8StringEncoding
error:nil];
if (timestampString.length == 0) {
return nil;
}
NSTimeInterval timestamp = timestampString.doubleValue;
return [NSDate dateWithTimeIntervalSinceReferenceDate:timestamp];
}
@end

View File

@ -15,7 +15,7 @@
*/ */
/* Automatically generated nanopb constant definitions */ /* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.3.9.2 */ /* Generated by nanopb-0.3.9.3 */
#include "firebasecore.nanopb.h" #include "firebasecore.nanopb.h"
@ -26,28 +26,19 @@
const pb_field_t logs_proto_mobilesdk_ios_ICoreConfiguration_fields[34] = { const pb_field_t logs_proto_mobilesdk_ios_ICoreConfiguration_fields[22] = {
PB_FIELD( 1, UENUM , OPTIONAL, STATIC , FIRST, logs_proto_mobilesdk_ios_ICoreConfiguration, configuration_type, configuration_type, 0), PB_FIELD( 1, UENUM , OPTIONAL, STATIC , FIRST, logs_proto_mobilesdk_ios_ICoreConfiguration, configuration_type, configuration_type, 0),
PB_FIELD( 2, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, version_name, configuration_type, 0), PB_FIELD( 7, UENUM , REPEATED, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, sdk_service_installed, configuration_type, 0),
PB_FIELD( 3, INT64 , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, build_number, version_name, 0),
PB_FIELD( 4, UENUM , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, build_type, build_number, 0),
PB_FIELD( 5, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, plist_version, build_type, 0),
PB_FIELD( 6, UENUM , REPEATED, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, sdk_service_enabled, plist_version, 0),
PB_FIELD( 7, UENUM , REPEATED, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, sdk_service_installed, sdk_service_enabled, 0),
PB_FIELD( 9, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, device_model, sdk_service_installed, 0), PB_FIELD( 9, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, device_model, sdk_service_installed, 0),
PB_FIELD( 10, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, app_id, device_model, 0), PB_FIELD( 10, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, app_id, device_model, 0),
PB_FIELD( 11, INT64 , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, project_number, app_id, 0), PB_FIELD( 12, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, bundle_id, app_id, 0),
PB_FIELD( 12, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, bundle_id, project_number, 0), PB_FIELD( 16, UENUM , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, pod_name, bundle_id, 0),
PB_FIELD( 13, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, client_id, bundle_id, 0),
PB_FIELD( 14, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, install, client_id, 0),
PB_FIELD( 16, UENUM , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, pod_name, install, 0),
PB_FIELD( 18, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, icore_version, pod_name, 0), PB_FIELD( 18, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, icore_version, pod_name, 0),
PB_FIELD( 19, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, sdk_version, icore_version, 0), PB_FIELD( 19, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, sdk_version, icore_version, 0),
PB_FIELD( 20, UENUM , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, sdk_name, sdk_version, 0), PB_FIELD( 20, UENUM , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, sdk_name, sdk_version, 0),
PB_FIELD( 21, INT32 , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, app_count, sdk_name, 0), PB_FIELD( 21, INT32 , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, app_count, sdk_name, 0),
PB_FIELD( 22, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, os_version, app_count, 0), PB_FIELD( 22, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, os_version, app_count, 0),
PB_FIELD( 23, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, itunes_id, os_version, 0), PB_FIELD( 24, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, min_supported_ios_version, os_version, 0),
PB_FIELD( 24, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, min_supported_ios_version, itunes_id, 0),
PB_FIELD( 25, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, use_default_app, min_supported_ios_version, 0), PB_FIELD( 25, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, use_default_app, min_supported_ios_version, 0),
PB_FIELD( 26, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, deployed_in_app_store, use_default_app, 0), PB_FIELD( 26, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, deployed_in_app_store, use_default_app, 0),
PB_FIELD( 27, INT32 , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, dynamic_framework_count, deployed_in_app_store, 0), PB_FIELD( 27, INT32 , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, dynamic_framework_count, deployed_in_app_store, 0),
@ -55,11 +46,8 @@ const pb_field_t logs_proto_mobilesdk_ios_ICoreConfiguration_fields[34] = {
PB_FIELD( 29, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, using_zip_file, apple_framework_version, 0), PB_FIELD( 29, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, using_zip_file, apple_framework_version, 0),
PB_FIELD( 30, UENUM , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, deployment_type, using_zip_file, 0), PB_FIELD( 30, UENUM , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, deployment_type, using_zip_file, 0),
PB_FIELD( 31, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, platform_info, deployment_type, 0), PB_FIELD( 31, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, platform_info, deployment_type, 0),
PB_FIELD( 32, INT64 , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, app_extensions, platform_info, 0), PB_FIELD( 33, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, swizzling_enabled, platform_info, 0),
PB_FIELD( 33, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, swizzling_enabled, app_extensions, 0), PB_FIELD( 36, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, using_gdt, swizzling_enabled, 0),
PB_FIELD( 34, INT32 , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, log_error_count, swizzling_enabled, 0),
PB_FIELD( 35, INT32 , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, log_warning_count, log_error_count, 0),
PB_FIELD( 36, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, using_gdt, log_warning_count, 0),
PB_LAST_FIELD PB_LAST_FIELD
}; };

View File

@ -15,7 +15,7 @@
*/ */
/* Automatically generated nanopb header */ /* Automatically generated nanopb header */
/* Generated by nanopb-0.3.9.2 */ /* Generated by nanopb-0.3.9.3 */
#ifndef PB_LOGS_PROTO_MOBILESDK_IOS_FIREBASECORE_NANOPB_H_INCLUDED #ifndef PB_LOGS_PROTO_MOBILESDK_IOS_FIREBASECORE_NANOPB_H_INCLUDED
#define PB_LOGS_PROTO_MOBILESDK_IOS_FIREBASECORE_NANOPB_H_INCLUDED #define PB_LOGS_PROTO_MOBILESDK_IOS_FIREBASECORE_NANOPB_H_INCLUDED
@ -111,23 +111,11 @@ typedef enum _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType {
typedef struct _logs_proto_mobilesdk_ios_ICoreConfiguration { typedef struct _logs_proto_mobilesdk_ios_ICoreConfiguration {
bool has_configuration_type; bool has_configuration_type;
logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType configuration_type; logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType configuration_type;
pb_bytes_array_t *version_name;
bool has_build_number;
int64_t build_number;
bool has_build_type;
logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType build_type;
pb_bytes_array_t *plist_version;
pb_size_t sdk_service_enabled_count;
logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType *sdk_service_enabled;
pb_size_t sdk_service_installed_count; pb_size_t sdk_service_installed_count;
logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType *sdk_service_installed; logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType *sdk_service_installed;
pb_bytes_array_t *device_model; pb_bytes_array_t *device_model;
pb_bytes_array_t *app_id; pb_bytes_array_t *app_id;
bool has_project_number;
int64_t project_number;
pb_bytes_array_t *bundle_id; pb_bytes_array_t *bundle_id;
pb_bytes_array_t *client_id;
pb_bytes_array_t *install;
bool has_pod_name; bool has_pod_name;
logs_proto_mobilesdk_ios_ICoreConfiguration_PodName pod_name; logs_proto_mobilesdk_ios_ICoreConfiguration_PodName pod_name;
pb_bytes_array_t *icore_version; pb_bytes_array_t *icore_version;
@ -137,7 +125,6 @@ typedef struct _logs_proto_mobilesdk_ios_ICoreConfiguration {
bool has_app_count; bool has_app_count;
int32_t app_count; int32_t app_count;
pb_bytes_array_t *os_version; pb_bytes_array_t *os_version;
pb_bytes_array_t *itunes_id;
pb_bytes_array_t *min_supported_ios_version; pb_bytes_array_t *min_supported_ios_version;
bool has_use_default_app; bool has_use_default_app;
bool use_default_app; bool use_default_app;
@ -151,14 +138,8 @@ typedef struct _logs_proto_mobilesdk_ios_ICoreConfiguration {
bool has_deployment_type; bool has_deployment_type;
logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType deployment_type; logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType deployment_type;
pb_bytes_array_t *platform_info; pb_bytes_array_t *platform_info;
bool has_app_extensions;
int64_t app_extensions;
bool has_swizzling_enabled; bool has_swizzling_enabled;
bool swizzling_enabled; bool swizzling_enabled;
bool has_log_error_count;
int32_t log_error_count;
bool has_log_warning_count;
int32_t log_warning_count;
bool has_using_gdt; bool has_using_gdt;
bool using_gdt; bool using_gdt;
/* @@protoc_insertion_point(struct:logs_proto_mobilesdk_ios_ICoreConfiguration) */ /* @@protoc_insertion_point(struct:logs_proto_mobilesdk_ios_ICoreConfiguration) */
@ -167,30 +148,21 @@ typedef struct _logs_proto_mobilesdk_ios_ICoreConfiguration {
/* Default values for struct fields */ /* Default values for struct fields */
/* Initializer values for message structs */ /* Initializer values for message structs */
#define logs_proto_mobilesdk_ios_ICoreConfiguration_init_default {false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_MIN, NULL, false, 0, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_MIN, NULL, 0, NULL, 0, NULL, NULL, NULL, false, 0, NULL, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_MIN, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MIN, false, 0, NULL, NULL, NULL, false, 0, false, 0, false, 0, NULL, false, 0, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_MIN, NULL, false, 0, false, 0, false, 0, false, 0, false, 0} #define logs_proto_mobilesdk_ios_ICoreConfiguration_init_default {false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_MIN, 0, NULL, NULL, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_MIN, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MIN, false, 0, NULL, NULL, false, 0, false, 0, false, 0, NULL, false, 0, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_MIN, NULL, false, 0, false, 0}
#define logs_proto_mobilesdk_ios_ICoreConfiguration_init_zero {false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_MIN, NULL, false, 0, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_MIN, NULL, 0, NULL, 0, NULL, NULL, NULL, false, 0, NULL, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_MIN, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MIN, false, 0, NULL, NULL, NULL, false, 0, false, 0, false, 0, NULL, false, 0, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_MIN, NULL, false, 0, false, 0, false, 0, false, 0, false, 0} #define logs_proto_mobilesdk_ios_ICoreConfiguration_init_zero {false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_MIN, 0, NULL, NULL, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_MIN, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MIN, false, 0, NULL, NULL, false, 0, false, 0, false, 0, NULL, false, 0, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_MIN, NULL, false, 0, false, 0}
/* Field tags (for use in manual encoding/decoding) */ /* Field tags (for use in manual encoding/decoding) */
#define logs_proto_mobilesdk_ios_ICoreConfiguration_pod_name_tag 16 #define logs_proto_mobilesdk_ios_ICoreConfiguration_pod_name_tag 16
#define logs_proto_mobilesdk_ios_ICoreConfiguration_configuration_type_tag 1 #define logs_proto_mobilesdk_ios_ICoreConfiguration_configuration_type_tag 1
#define logs_proto_mobilesdk_ios_ICoreConfiguration_version_name_tag 2
#define logs_proto_mobilesdk_ios_ICoreConfiguration_icore_version_tag 18 #define logs_proto_mobilesdk_ios_ICoreConfiguration_icore_version_tag 18
#define logs_proto_mobilesdk_ios_ICoreConfiguration_sdk_version_tag 19 #define logs_proto_mobilesdk_ios_ICoreConfiguration_sdk_version_tag 19
#define logs_proto_mobilesdk_ios_ICoreConfiguration_build_number_tag 3
#define logs_proto_mobilesdk_ios_ICoreConfiguration_build_type_tag 4
#define logs_proto_mobilesdk_ios_ICoreConfiguration_plist_version_tag 5
#define logs_proto_mobilesdk_ios_ICoreConfiguration_sdk_service_enabled_tag 6
#define logs_proto_mobilesdk_ios_ICoreConfiguration_sdk_service_installed_tag 7 #define logs_proto_mobilesdk_ios_ICoreConfiguration_sdk_service_installed_tag 7
#define logs_proto_mobilesdk_ios_ICoreConfiguration_sdk_name_tag 20 #define logs_proto_mobilesdk_ios_ICoreConfiguration_sdk_name_tag 20
#define logs_proto_mobilesdk_ios_ICoreConfiguration_device_model_tag 9 #define logs_proto_mobilesdk_ios_ICoreConfiguration_device_model_tag 9
#define logs_proto_mobilesdk_ios_ICoreConfiguration_os_version_tag 22 #define logs_proto_mobilesdk_ios_ICoreConfiguration_os_version_tag 22
#define logs_proto_mobilesdk_ios_ICoreConfiguration_app_id_tag 10 #define logs_proto_mobilesdk_ios_ICoreConfiguration_app_id_tag 10
#define logs_proto_mobilesdk_ios_ICoreConfiguration_project_number_tag 11
#define logs_proto_mobilesdk_ios_ICoreConfiguration_bundle_id_tag 12 #define logs_proto_mobilesdk_ios_ICoreConfiguration_bundle_id_tag 12
#define logs_proto_mobilesdk_ios_ICoreConfiguration_client_id_tag 13
#define logs_proto_mobilesdk_ios_ICoreConfiguration_itunes_id_tag 23
#define logs_proto_mobilesdk_ios_ICoreConfiguration_min_supported_ios_version_tag 24 #define logs_proto_mobilesdk_ios_ICoreConfiguration_min_supported_ios_version_tag 24
#define logs_proto_mobilesdk_ios_ICoreConfiguration_install_tag 14
#define logs_proto_mobilesdk_ios_ICoreConfiguration_use_default_app_tag 25 #define logs_proto_mobilesdk_ios_ICoreConfiguration_use_default_app_tag 25
#define logs_proto_mobilesdk_ios_ICoreConfiguration_app_count_tag 21 #define logs_proto_mobilesdk_ios_ICoreConfiguration_app_count_tag 21
#define logs_proto_mobilesdk_ios_ICoreConfiguration_deployed_in_app_store_tag 26 #define logs_proto_mobilesdk_ios_ICoreConfiguration_deployed_in_app_store_tag 26
@ -199,14 +171,11 @@ typedef struct _logs_proto_mobilesdk_ios_ICoreConfiguration {
#define logs_proto_mobilesdk_ios_ICoreConfiguration_using_zip_file_tag 29 #define logs_proto_mobilesdk_ios_ICoreConfiguration_using_zip_file_tag 29
#define logs_proto_mobilesdk_ios_ICoreConfiguration_deployment_type_tag 30 #define logs_proto_mobilesdk_ios_ICoreConfiguration_deployment_type_tag 30
#define logs_proto_mobilesdk_ios_ICoreConfiguration_platform_info_tag 31 #define logs_proto_mobilesdk_ios_ICoreConfiguration_platform_info_tag 31
#define logs_proto_mobilesdk_ios_ICoreConfiguration_app_extensions_tag 32
#define logs_proto_mobilesdk_ios_ICoreConfiguration_swizzling_enabled_tag 33 #define logs_proto_mobilesdk_ios_ICoreConfiguration_swizzling_enabled_tag 33
#define logs_proto_mobilesdk_ios_ICoreConfiguration_log_error_count_tag 34
#define logs_proto_mobilesdk_ios_ICoreConfiguration_log_warning_count_tag 35
#define logs_proto_mobilesdk_ios_ICoreConfiguration_using_gdt_tag 36 #define logs_proto_mobilesdk_ios_ICoreConfiguration_using_gdt_tag 36
/* Struct field encoding specification for nanopb */ /* Struct field encoding specification for nanopb */
extern const pb_field_t logs_proto_mobilesdk_ios_ICoreConfiguration_fields[34]; extern const pb_field_t logs_proto_mobilesdk_ios_ICoreConfiguration_fields[22];
/* Maximum encoded size of messages (where known) */ /* Maximum encoded size of messages (where known) */
/* logs_proto_mobilesdk_ios_ICoreConfiguration_size depends on runtime parameters */ /* logs_proto_mobilesdk_ios_ICoreConfiguration_size depends on runtime parameters */

View File

@ -1,9 +1,10 @@
# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) # Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk)
This repository contains a subset of the Firebase iOS SDK source. It currently This repository contains a subset of the Firebase iOS SDK source. It currently
includes FirebaseCore, FirebaseAuth, FirebaseDatabase, FirebaseFirestore, includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase,
FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging,
FirebaseInAppMessagingDisplay, FirebaseMessaging and FirebaseStorage. FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and
FirebaseStorage.
The repository also includes GoogleUtilities source. The The repository also includes GoogleUtilities source. The
[GoogleUtilities](GoogleUtilities/README.md) pod is [GoogleUtilities](GoogleUtilities/README.md) pod is
@ -75,14 +76,30 @@ the following software:
* Xcode 10.1 (or later) * Xcode 10.1 (or later)
* CocoaPods 1.7.2 (or later) * CocoaPods 1.7.2 (or later)
* [CocoaPods generate](https://github.com/square/cocoapods-generate)
For the pod that you want to develop: For the pod that you want to develop:
`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open` `pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios`
Firestore and Functions have self contained Xcode projects. See Note: If the CocoaPods cache is out of date, you may need to run
[Firestore/README.md](Firestore/README.md) and `pod repo update` before the `pod gen` command.
[Functions/README.md](Functions/README.md).
Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for
those platforms. Since 10.2, Xcode does not properly handle multi-platform
CocoaPods workspaces.
Firestore has a self contained Xcode project. See
[Firestore/README.md](Firestore/README.md).
### Development for Catalyst
* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios`
* Check the Mac box in the App-iOS Build Settings
* Sign the App in the Settings Signing & Capabilities tab
* Click Pods in the Project Manager
* Add Signing to the iOS host app and unit test targets
* Select the Unit-unit scheme
* Run it to build and test
### Adding a New Firebase Pod ### Adding a New Firebase Pod
@ -99,13 +116,17 @@ Travis will verify that any code changes are done in a style compliant way. Inst
These commands will get the right versions: These commands will get the right versions:
``` ```
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/773cb75d360b58f32048f5964038d09825a507c8/Formula/clang-format.rb brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/3dfea1004e0736754bbf49673cca8aaed8a94089/Formula/swiftformat.rb brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb
``` ```
Note: if you already have a newer version of these installed you may need to Note: if you already have a newer version of these installed you may need to
`brew switch` to this version. `brew switch` to this version.
To update this section, find the versions of clang-format and swiftformat.rb to
match the versions in the CI failure logs
[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula).
### Running Unit Tests ### Running Unit Tests
Select a scheme and press Command-u to build a component and run its unit tests. Select a scheme and press Command-u to build a component and run its unit tests.
@ -178,32 +199,39 @@ We've seen an amazing amount of interest and contributions to improve the Fireba
very grateful! We'd like to empower as many developers as we can to be able to use Firebase and very grateful! We'd like to empower as many developers as we can to be able to use Firebase and
participate in the Firebase community. participate in the Firebase community.
### macOS and tvOS ### tvOS, macOS, and Catalyst
Thanks to contributions from the community, FirebaseAuth, FirebaseCore, FirebaseDatabase, FirebaseMessaging, Thanks to contributions from the community, FirebaseABTesting, FirebaseAuth, FirebaseCore,
FirebaseFirestore, FirebaseFunctions and FirebaseStorage now compile, run unit tests, and work on FirebaseDatabase, FirebaseMessaging, FirebaseFirestore,
macOS and tvOS. FirebaseFunctions, FirebaseRemoteConfig, and FirebaseStorage now compile, run unit tests, and work on
tvOS, macOS, and Catalyst.
For tvOS, checkout the [Sample](Example/tvOSSample). For tvOS, checkout the [Sample](Example/tvOSSample).
Keep in mind that macOS and tvOS are not officially supported by Firebase, and this repository is Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this
actively developed primarily for iOS. While we can catch basic unit test issues with Travis, there repository is actively developed primarily for iOS. While we can catch basic unit test issues with
may be some changes where the SDK no longer works as expected on macOS or tvOS. If you encounter Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you
this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues).
Note that the Firebase pod is not available for macOS and tvOS.
To install, add a subset of the following to the Podfile: To install, add a subset of the following to the Podfile:
``` ```
pod 'FirebaseAuth' pod 'Firebase/ABTesting'
pod 'FirebaseCore' pod 'Firebase/Auth'
pod 'FirebaseDatabase' pod 'Firebase/Database'
pod 'FirebaseFirestore' pod 'Firebase/Firestore'
pod 'FirebaseFunctions' pod 'Firebase/Functions'
pod 'FirebaseMessaging' pod 'Firebase/Messaging'
pod 'FirebaseStorage' pod 'Firebase/RemoteConfig'
pod 'Firebase/Storage'
``` ```
#### Additional Catalyst Notes
* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability`
to Build Settings.
* FirebaseFirestore requires signing the
[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681).
## Roadmap ## Roadmap
See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source

View File

@ -19,36 +19,34 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
/** If present, is a BOOL wrapped in an NSNumber. */ /** If present, is a BOOL wrapped in an NSNumber. */
static NSString *const kFIRCDIsDataCollectionDefaultEnabledKey = #define kFIRCDIsDataCollectionDefaultEnabledKey @"FIRCDIsDataCollectionDefaultEnabledKey"
@"FIRCDIsDataCollectionDefaultEnabledKey";
/** If present, is an int32_t wrapped in an NSNumber. */ /** If present, is an int32_t wrapped in an NSNumber. */
static NSString *const kFIRCDConfigurationTypeKey = @"FIRCDConfigurationTypeKey"; #define kFIRCDConfigurationTypeKey @"FIRCDConfigurationTypeKey"
/** If present, is an NSString. */ /** If present, is an NSString. */
static NSString *const kFIRCDSdkNameKey = @"FIRCDSdkNameKey"; #define kFIRCDSdkNameKey @"FIRCDSdkNameKey"
/** If present, is an NSString. */ /** If present, is an NSString. */
static NSString *const kFIRCDSdkVersionKey = @"FIRCDSdkVersionKey"; #define kFIRCDSdkVersionKey @"FIRCDSdkVersionKey"
/** If present, is an int32_t wrapped in an NSNumber. */ /** If present, is an int32_t wrapped in an NSNumber. */
static NSString *const kFIRCDllAppsCountKey = @"FIRCDllAppsCountKey"; #define kFIRCDllAppsCountKey @"FIRCDllAppsCountKey"
/** If present, is an NSString. */ /** If present, is an NSString. */
static NSString *const kFIRCDGoogleAppIDKey = @"FIRCDGoogleAppIDKey"; #define kFIRCDGoogleAppIDKey @"FIRCDGoogleAppIDKey"
/** If present, is an NSString. */ /** If present, is an NSString. */
static NSString *const kFIRCDBundleIDKey = @"FIRCDBundleID"; #define kFIRCDBundleIDKey @"FIRCDBundleID"
/** If present, is a BOOL wrapped in an NSNumber. */ /** If present, is a BOOL wrapped in an NSNumber. */
static NSString *const kFIRCDUsingOptionsFromDefaultPlistKey = #define kFIRCDUsingOptionsFromDefaultPlistKey @"FIRCDUsingOptionsFromDefaultPlistKey"
@"FIRCDUsingOptionsFromDefaultPlistKey";
/** If present, is an NSString. */ /** If present, is an NSString. */
static NSString *const kFIRCDLibraryVersionIDKey = @"FIRCDLibraryVersionIDKey"; #define kFIRCDLibraryVersionIDKey @"FIRCDLibraryVersionIDKey"
/** If present, is an NSString. */ /** If present, is an NSString. */
static NSString *const kFIRCDFirebaseUserAgentKey = @"FIRCDFirebaseUserAgentKey"; #define kFIRCDFirebaseUserAgentKey @"FIRCDFirebaseUserAgentKey"
/** Defines the interface of a data object needed to log diagnostics data. */ /** Defines the interface of a data object needed to log diagnostics data. */
@protocol FIRCoreDiagnosticsData <NSObject> @protocol FIRCoreDiagnosticsData <NSObject>

View File

@ -1,9 +1,10 @@
# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) # Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk)
This repository contains a subset of the Firebase iOS SDK source. It currently This repository contains a subset of the Firebase iOS SDK source. It currently
includes FirebaseCore, FirebaseAuth, FirebaseDatabase, FirebaseFirestore, includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase,
FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging, FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging,
FirebaseInAppMessagingDisplay, FirebaseMessaging and FirebaseStorage. FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and
FirebaseStorage.
The repository also includes GoogleUtilities source. The The repository also includes GoogleUtilities source. The
[GoogleUtilities](GoogleUtilities/README.md) pod is [GoogleUtilities](GoogleUtilities/README.md) pod is
@ -75,14 +76,30 @@ the following software:
* Xcode 10.1 (or later) * Xcode 10.1 (or later)
* CocoaPods 1.7.2 (or later) * CocoaPods 1.7.2 (or later)
* [CocoaPods generate](https://github.com/square/cocoapods-generate)
For the pod that you want to develop: For the pod that you want to develop:
`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open` `pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios`
Firestore and Functions have self contained Xcode projects. See Note: If the CocoaPods cache is out of date, you may need to run
[Firestore/README.md](Firestore/README.md) and `pod repo update` before the `pod gen` command.
[Functions/README.md](Functions/README.md).
Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for
those platforms. Since 10.2, Xcode does not properly handle multi-platform
CocoaPods workspaces.
Firestore has a self contained Xcode project. See
[Firestore/README.md](Firestore/README.md).
### Development for Catalyst
* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios`
* Check the Mac box in the App-iOS Build Settings
* Sign the App in the Settings Signing & Capabilities tab
* Click Pods in the Project Manager
* Add Signing to the iOS host app and unit test targets
* Select the Unit-unit scheme
* Run it to build and test
### Adding a New Firebase Pod ### Adding a New Firebase Pod
@ -99,13 +116,17 @@ Travis will verify that any code changes are done in a style compliant way. Inst
These commands will get the right versions: These commands will get the right versions:
``` ```
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/773cb75d360b58f32048f5964038d09825a507c8/Formula/clang-format.rb brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/3dfea1004e0736754bbf49673cca8aaed8a94089/Formula/swiftformat.rb brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb
``` ```
Note: if you already have a newer version of these installed you may need to Note: if you already have a newer version of these installed you may need to
`brew switch` to this version. `brew switch` to this version.
To update this section, find the versions of clang-format and swiftformat.rb to
match the versions in the CI failure logs
[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula).
### Running Unit Tests ### Running Unit Tests
Select a scheme and press Command-u to build a component and run its unit tests. Select a scheme and press Command-u to build a component and run its unit tests.
@ -178,32 +199,39 @@ We've seen an amazing amount of interest and contributions to improve the Fireba
very grateful! We'd like to empower as many developers as we can to be able to use Firebase and very grateful! We'd like to empower as many developers as we can to be able to use Firebase and
participate in the Firebase community. participate in the Firebase community.
### macOS and tvOS ### tvOS, macOS, and Catalyst
Thanks to contributions from the community, FirebaseAuth, FirebaseCore, FirebaseDatabase, FirebaseMessaging, Thanks to contributions from the community, FirebaseABTesting, FirebaseAuth, FirebaseCore,
FirebaseFirestore, FirebaseFunctions and FirebaseStorage now compile, run unit tests, and work on FirebaseDatabase, FirebaseMessaging, FirebaseFirestore,
macOS and tvOS. FirebaseFunctions, FirebaseRemoteConfig, and FirebaseStorage now compile, run unit tests, and work on
tvOS, macOS, and Catalyst.
For tvOS, checkout the [Sample](Example/tvOSSample). For tvOS, checkout the [Sample](Example/tvOSSample).
Keep in mind that macOS and tvOS are not officially supported by Firebase, and this repository is Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this
actively developed primarily for iOS. While we can catch basic unit test issues with Travis, there repository is actively developed primarily for iOS. While we can catch basic unit test issues with
may be some changes where the SDK no longer works as expected on macOS or tvOS. If you encounter Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you
this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues).
Note that the Firebase pod is not available for macOS and tvOS.
To install, add a subset of the following to the Podfile: To install, add a subset of the following to the Podfile:
``` ```
pod 'FirebaseAuth' pod 'Firebase/ABTesting'
pod 'FirebaseCore' pod 'Firebase/Auth'
pod 'FirebaseDatabase' pod 'Firebase/Database'
pod 'FirebaseFirestore' pod 'Firebase/Firestore'
pod 'FirebaseFunctions' pod 'Firebase/Functions'
pod 'FirebaseMessaging' pod 'Firebase/Messaging'
pod 'FirebaseStorage' pod 'Firebase/RemoteConfig'
pod 'Firebase/Storage'
``` ```
#### Additional Catalyst Notes
* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability`
to Build Settings.
* FirebaseFirestore requires signing the
[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681).
## Roadmap ## Roadmap
See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source

View File

@ -0,0 +1,56 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <FirebaseInstallations/FIRInstallationsErrors.h>
@class FIRInstallationsHTTPError;
NS_ASSUME_NONNULL_BEGIN
void FIRInstallationsItemSetErrorToPointer(NSError *error, NSError **pointer);
@interface FIRInstallationsErrorUtil : NSObject
+ (NSError *)keyedArchiverErrorWithException:(NSException *)exception;
+ (NSError *)keyedArchiverErrorWithError:(NSError *)error;
+ (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status;
+ (NSError *)installationItemNotFoundForAppID:(NSString *)appID appName:(NSString *)appName;
+ (NSError *)JSONSerializationError:(NSError *)error;
+ (NSError *)networkErrorWithError:(NSError *)error;
+ (NSError *)FIDRegistrationErrorWithResponseMissingField:(NSString *)missingFieldName;
+ (NSError *)corruptedIIDTokenData;
+ (FIRInstallationsHTTPError *)APIErrorWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse
data:(nullable NSData *)data;
+ (BOOL)isAPIError:(NSError *)error withHTTPCode:(NSInteger)HTTPCode;
/**
* Returns the passed error if it is already in the public domain or a new error with the passed
* error at `NSUnderlyingErrorKey`.
*/
+ (NSError *)publicDomainErrorWithError:(NSError *)error;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,124 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsErrorUtil.h"
#import "FIRInstallationsHTTPError.h"
NSString *const kFirebaseInstallationsErrorDomain = @"com.firebase.installations";
void FIRInstallationsItemSetErrorToPointer(NSError *error, NSError **pointer) {
if (pointer != NULL) {
*pointer = error;
}
}
@implementation FIRInstallationsErrorUtil
+ (NSError *)keyedArchiverErrorWithException:(NSException *)exception {
NSString *failureReason = [NSString
stringWithFormat:@"NSKeyedArchiver exception with name: %@, reason: %@, userInfo: %@",
exception.name, exception.reason, exception.userInfo];
return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown
failureReason:failureReason
underlyingError:nil];
}
+ (NSError *)keyedArchiverErrorWithError:(NSError *)error {
NSString *failureReason = [NSString stringWithFormat:@"NSKeyedArchiver error."];
return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown
failureReason:failureReason
underlyingError:error];
}
+ (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status {
NSString *failureReason = [NSString stringWithFormat:@"%@ (%li)", keychainFunction, (long)status];
return [self installationsErrorWithCode:FIRInstallationsErrorCodeKeychain
failureReason:failureReason
underlyingError:nil];
}
+ (NSError *)installationItemNotFoundForAppID:(NSString *)appID appName:(NSString *)appName {
NSString *failureReason =
[NSString stringWithFormat:@"Installation for appID %@ appName %@ not found", appID, appName];
return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown
failureReason:failureReason
underlyingError:nil];
}
+ (NSError *)corruptedIIDTokenData {
NSString *failureReason =
@"IID token data stored in Keychain is corrupted or in an incompatible format.";
return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown
failureReason:failureReason
underlyingError:nil];
}
+ (FIRInstallationsHTTPError *)APIErrorWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse
data:(nullable NSData *)data {
return [[FIRInstallationsHTTPError alloc] initWithHTTPResponse:HTTPResponse data:data];
}
+ (BOOL)isAPIError:(NSError *)error withHTTPCode:(NSInteger)HTTPCode {
if (![error isKindOfClass:[FIRInstallationsHTTPError class]]) {
return NO;
}
return [(FIRInstallationsHTTPError *)error HTTPResponse].statusCode == HTTPCode;
}
+ (NSError *)JSONSerializationError:(NSError *)error {
NSString *failureReason = [NSString stringWithFormat:@"Failed to serialize JSON data."];
return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown
failureReason:failureReason
underlyingError:nil];
}
+ (NSError *)FIDRegistrationErrorWithResponseMissingField:(NSString *)missingFieldName {
NSString *failureReason = [NSString
stringWithFormat:@"A required response field with name %@ is missing", missingFieldName];
return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown
failureReason:failureReason
underlyingError:nil];
}
+ (NSError *)networkErrorWithError:(NSError *)error {
return [self installationsErrorWithCode:FIRInstallationsErrorCodeServerUnreachable
failureReason:@"Network connection error."
underlyingError:error];
}
+ (NSError *)publicDomainErrorWithError:(NSError *)error {
if ([error.domain isEqualToString:kFirebaseInstallationsErrorDomain]) {
return error;
}
return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown
failureReason:nil
underlyingError:error];
}
+ (NSError *)installationsErrorWithCode:(FIRInstallationsErrorCode)code
failureReason:(nullable NSString *)failureReason
underlyingError:(nullable NSError *)underlyingError {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[NSUnderlyingErrorKey] = underlyingError;
userInfo[NSLocalizedFailureReasonErrorKey] = failureReason;
return [NSError errorWithDomain:kFirebaseInstallationsErrorDomain code:code userInfo:userInfo];
}
@end

View File

@ -0,0 +1,54 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** Represents an error caused by an unexpected API response. */
@interface FIRInstallationsHTTPError : NSError
@property(nonatomic, readonly) NSHTTPURLResponse *HTTPResponse;
@property(nonatomic, readonly, nonnull) NSData *data;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse data:(nullable NSData *)data;
@end
NS_ASSUME_NONNULL_END
typedef NS_ENUM(NSInteger, FIRInstallationsHTTPCodes) {
FIRInstallationsHTTPCodesTooManyRequests = 429,
FIRInstallationsHTTPCodesServerInternalError = 500,
};
/** Possible response HTTP codes for `CreateInstallation` API request. */
typedef NS_ENUM(NSInteger, FIRInstallationsRegistrationHTTPCode) {
FIRInstallationsRegistrationHTTPCodeSuccess = 201,
FIRInstallationsRegistrationHTTPCodeInvalidArgument = 400,
FIRInstallationsRegistrationHTTPCodeInvalidAPIKey = 401,
FIRInstallationsRegistrationHTTPCodeAPIKeyToProjectIDMismatch = 403,
FIRInstallationsRegistrationHTTPCodeProjectNotFound = 404,
FIRInstallationsRegistrationHTTPCodeTooManyRequests = 429,
FIRInstallationsRegistrationHTTPCodeServerInternalError = 500
};
typedef NS_ENUM(NSInteger, FIRInstallationsAuthTokenHTTPCode) {
FIRInstallationsAuthTokenHTTPCodeInvalidAuthentication = 401,
FIRInstallationsAuthTokenHTTPCodeFIDNotFound = 404,
};

View File

@ -0,0 +1,78 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsHTTPError.h"
#import "FIRInstallationsErrorUtil.h"
@implementation FIRInstallationsHTTPError
- (instancetype)initWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse
data:(nullable NSData *)data {
NSDictionary *userInfo = [FIRInstallationsHTTPError userInfoWithHTTPResponse:HTTPResponse
data:data];
self = [super
initWithDomain:kFirebaseInstallationsErrorDomain
code:[FIRInstallationsHTTPError errorCodeWithHTTPCode:HTTPResponse.statusCode]
userInfo:userInfo];
if (self) {
_HTTPResponse = HTTPResponse;
_data = data;
}
return self;
}
+ (FIRInstallationsErrorCode)errorCodeWithHTTPCode:(NSInteger)HTTPCode {
return FIRInstallationsErrorCodeUnknown;
}
+ (NSDictionary *)userInfoWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse
data:(nullable NSData *)data {
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSString *failureReason = [NSString
stringWithFormat:@"The server responded with an error. HTTP response: %@\nResponse body: %@",
HTTPResponse, responseString];
return @{NSLocalizedFailureReasonErrorKey : failureReason};
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
return [[FIRInstallationsHTTPError alloc] initWithHTTPResponse:self.HTTPResponse data:self.data];
}
#pragma mark - NSSecureCoding
- (nullable instancetype)initWithCoder:(NSCoder *)coder {
NSHTTPURLResponse *HTTPResponse = [coder decodeObjectOfClass:[NSHTTPURLResponse class]
forKey:@"HTTPResponse"];
if (!HTTPResponse) {
return nil;
}
NSData *data = [coder decodeObjectOfClass:[NSData class] forKey:@"data"];
return [self initWithHTTPResponse:HTTPResponse data:data];
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.HTTPResponse forKey:@"HTTPResponse"];
[coder encodeObject:self.data forKey:@"data"];
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end

View File

@ -0,0 +1,248 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallations.h"
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import <FirebaseCore/FIRAppInternal.h>
#import <FirebaseCore/FIRComponent.h>
#import <FirebaseCore/FIRComponentContainer.h>
#import <FirebaseCore/FIRLibrary.h>
#import <FirebaseCore/FIRLogger.h>
#import <FirebaseCore/FIROptions.h>
#import "FIRInstallationsAuthTokenResultInternal.h"
#import "FIRInstallationsErrorUtil.h"
#import "FIRInstallationsIDController.h"
#import "FIRInstallationsItem.h"
#import "FIRInstallationsLogger.h"
#import "FIRInstallationsStoredAuthToken.h"
#import "FIRInstallationsVersion.h"
NS_ASSUME_NONNULL_BEGIN
@protocol FIRInstallationsInstanceProvider <FIRLibrary>
@end
@interface FIRInstallations () <FIRInstallationsInstanceProvider>
@property(nonatomic, readonly) FIROptions *appOptions;
@property(nonatomic, readonly) NSString *appName;
@property(nonatomic, readonly) FIRInstallationsIDController *installationsIDController;
@end
@implementation FIRInstallations
#pragma mark - Firebase component
+ (void)load {
[FIRApp registerInternalLibrary:(Class<FIRLibrary>)self
withName:@"fire-install"
withVersion:[NSString stringWithUTF8String:FIRInstallationsVersionStr]];
}
+ (nonnull NSArray<FIRComponent *> *)componentsToRegister {
FIRComponentCreationBlock creationBlock =
^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
*isCacheable = YES;
FIRInstallations *installations = [[FIRInstallations alloc] initWithApp:container.app];
return installations;
};
FIRComponent *installationsProvider =
[FIRComponent componentWithProtocol:@protocol(FIRInstallationsInstanceProvider)
instantiationTiming:FIRInstantiationTimingAlwaysEager
dependencies:@[]
creationBlock:creationBlock];
return @[ installationsProvider ];
}
- (instancetype)initWithApp:(FIRApp *)app {
return [self initWitAppOptions:app.options appName:app.name];
}
- (instancetype)initWitAppOptions:(FIROptions *)appOptions appName:(NSString *)appName {
FIRInstallationsIDController *IDController =
[[FIRInstallationsIDController alloc] initWithGoogleAppID:appOptions.googleAppID
appName:appName
APIKey:appOptions.APIKey
projectID:appOptions.projectID
GCMSenderID:appOptions.GCMSenderID
accessGroup:appOptions.appGroupID];
return [self initWithAppOptions:appOptions
appName:appName
installationsIDController:IDController
prefetchAuthToken:YES];
}
/// The initializer is supposed to be used by tests to inject `installationsStore`.
- (instancetype)initWithAppOptions:(FIROptions *)appOptions
appName:(NSString *)appName
installationsIDController:(FIRInstallationsIDController *)installationsIDController
prefetchAuthToken:(BOOL)prefetchAuthToken {
self = [super init];
if (self) {
[[self class] validateAppOptions:appOptions appName:appName];
[[self class] assertCompatibleIIDVersion];
_appOptions = [appOptions copy];
_appName = [appName copy];
_installationsIDController = installationsIDController;
// Pre-fetch auth token.
if (prefetchAuthToken) {
[self authTokenWithCompletion:^(FIRInstallationsAuthTokenResult *_Nullable tokenResult,
NSError *_Nullable error){
}];
}
}
return self;
}
+ (void)validateAppOptions:(FIROptions *)appOptions appName:(NSString *)appName {
NSMutableArray *missingFields = [NSMutableArray array];
if (appName.length < 1) {
[missingFields addObject:@"`FirebaseApp.name`"];
}
if (appOptions.APIKey.length < 1) {
[missingFields addObject:@"`FirebaseOptions.APIKey`"];
}
if (appOptions.googleAppID.length < 1) {
[missingFields addObject:@"`FirebaseOptions.googleAppID`"];
}
// TODO(#4692): Check for `appOptions.projectID.length < 1` only.
// We can use `GCMSenderID` instead of `projectID` temporary.
if (appOptions.projectID.length < 1 && appOptions.GCMSenderID.length < 1) {
[missingFields addObject:@"`FirebaseOptions.projectID`"];
}
if (missingFields.count > 0) {
[NSException
raise:kFirebaseInstallationsErrorDomain
format:
@"%@[%@] Could not configure Firebase Installations due to invalid FirebaseApp "
@"options. The following parameters are nil or empty: %@. If you use "
@"GoogleServices-Info.plist please download the most recent version from the Firebase "
@"Console. If you configure Firebase in code, please make sure you specify all "
@"required parameters.",
kFIRLoggerInstallations, kFIRInstallationsMessageCodeInvalidFirebaseAppOptions,
[missingFields componentsJoinedByString:@", "]];
}
}
#pragma mark - Public
+ (FIRInstallations *)installations {
FIRApp *defaultApp = [FIRApp defaultApp];
if (!defaultApp) {
[NSException raise:kFirebaseInstallationsErrorDomain
format:@"The default FirebaseApp instance must be configured before the default"
@"FirebaseApp instance can be initialized. One way to ensure that is to "
@"call `[FIRApp configure];` (`FirebaseApp.configure()` in Swift) in the App"
@" Delegate's `application:didFinishLaunchingWithOptions:` "
@"(`application(_:didFinishLaunchingWithOptions:)` in Swift)."];
}
return [self installationsWithApp:defaultApp];
}
+ (FIRInstallations *)installationsWithApp:(FIRApp *)app {
id<FIRInstallationsInstanceProvider> installations =
FIR_COMPONENT(FIRInstallationsInstanceProvider, app.container);
return (FIRInstallations *)installations;
}
- (void)installationIDWithCompletion:(FIRInstallationsIDHandler)completion {
[self.installationsIDController getInstallationItem]
.then(^id(FIRInstallationsItem *installation) {
completion(installation.firebaseInstallationID, nil);
return nil;
})
.catch(^(NSError *error) {
completion(nil, [FIRInstallationsErrorUtil publicDomainErrorWithError:error]);
});
}
- (void)authTokenWithCompletion:(FIRInstallationsTokenHandler)completion {
[self authTokenForcingRefresh:NO completion:completion];
}
- (void)authTokenForcingRefresh:(BOOL)forceRefresh
completion:(FIRInstallationsTokenHandler)completion {
[self.installationsIDController getAuthTokenForcingRefresh:forceRefresh]
.then(^FIRInstallationsAuthTokenResult *(FIRInstallationsItem *installation) {
FIRInstallationsAuthTokenResult *result = [[FIRInstallationsAuthTokenResult alloc]
initWithToken:installation.authToken.token
expirationDate:installation.authToken.expirationDate];
return result;
})
.then(^id(FIRInstallationsAuthTokenResult *token) {
completion(token, nil);
return nil;
})
.catch(^void(NSError *error) {
completion(nil, [FIRInstallationsErrorUtil publicDomainErrorWithError:error]);
});
}
- (void)deleteWithCompletion:(void (^)(NSError *__nullable error))completion {
[self.installationsIDController deleteInstallation]
.then(^id(id result) {
completion(nil);
return nil;
})
.catch(^void(NSError *error) {
completion([FIRInstallationsErrorUtil publicDomainErrorWithError:error]);
});
}
#pragma mark - IID version compatibility
+ (void)assertCompatibleIIDVersion {
// We use this flag to disable IID compatibility exception for unit tests.
#ifdef FIR_INSTALLATIONS_ALLOWS_INCOMPATIBLE_IID_VERSION
return;
#else
if (![self isIIDVersionCompatible]) {
[NSException raise:kFirebaseInstallationsErrorDomain
format:@"FirebaseInstallations will not work correctly with current version of "
@"Firebase Instance ID. Please update your Firebase Instance ID version."];
}
#endif
}
+ (BOOL)isIIDVersionCompatible {
Class IIDClass = NSClassFromString(@"FIRInstanceID");
if (IIDClass == nil) {
// It is OK if there is no IID at all.
return YES;
}
// We expect a compatible version having the method `+[FIRInstanceID usesFIS]` defined.
BOOL isCompatibleVersion = [IIDClass respondsToSelector:NSSelectorFromString(@"usesFIS")];
return isCompatibleVersion;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,30 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsAuthTokenResultInternal.h"
@implementation FIRInstallationsAuthTokenResult
- (instancetype)initWithToken:(NSString *)token expirationDate:(NSDate *)expirationDate {
self = [super init];
if (self) {
_authToken = [token copy];
_expirationDate = expirationDate;
}
return self;
}
@end

View File

@ -0,0 +1,27 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <FirebaseInstallations/FIRInstallationsAuthTokenResult.h>
NS_ASSUME_NONNULL_BEGIN
@interface FIRInstallationsAuthTokenResult (Internal)
- (instancetype)initWithToken:(NSString *)token expirationDate:(NSDate *)expirationTime;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,86 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "FIRInstallationsStatus.h"
@class FIRInstallationsStoredItem;
@class FIRInstallationsStoredAuthToken;
@class FIRInstallationsStoredIIDCheckin;
NS_ASSUME_NONNULL_BEGIN
/**
* The class represents the required installation ID and auth token data including possible states.
* The data is stored to Keychain via `FIRInstallationsStoredItem` which has only the storage
* relevant data and does not contain any logic. `FIRInstallationsItem` must be used on the logic
* level (not `FIRInstallationsStoredItem`).
*/
@interface FIRInstallationsItem : NSObject <NSCopying>
/// A `FirebaseApp` identifier.
@property(nonatomic, readonly) NSString *appID;
/// A `FirebaseApp` name.
@property(nonatomic, readonly) NSString *firebaseAppName;
/// A stable identifier that uniquely identifies the app instance.
@property(nonatomic, copy, nullable) NSString *firebaseInstallationID;
/// The `refreshToken` is used to authorize the auth token requests.
@property(nonatomic, copy, nullable) NSString *refreshToken;
@property(nonatomic, nullable) FIRInstallationsStoredAuthToken *authToken;
@property(nonatomic, assign) FIRInstallationsStatus registrationStatus;
/// Instance ID default token imported from IID store as a part of IID migration.
@property(nonatomic, nullable) NSString *IIDDefaultToken;
- (instancetype)initWithAppID:(NSString *)appID firebaseAppName:(NSString *)firebaseAppName;
/**
* Populates `FIRInstallationsItem` properties with data from `FIRInstallationsStoredItem`.
* @param item An instance of `FIRInstallationsStoredItem` to get data from.
*/
- (void)updateWithStoredItem:(FIRInstallationsStoredItem *)item;
/**
* Creates a stored item with data from the object.
* @return Returns a `FIRInstallationsStoredItem` instance with the data from the object.
*/
- (FIRInstallationsStoredItem *)storedItem;
/**
* The installation identifier.
* @return Returns a string uniquely identifying the installation.
*/
- (NSString *)identifier;
/**
* The installation identifier.
* @param appID A `FirebaseApp` identifier.
* @param appName A `FirebaseApp` name.
* @return Returns a string uniquely identifying the installation.
*/
+ (NSString *)identifierWithAppID:(NSString *)appID appName:(NSString *)appName;
/**
* Generate a new Firebase Installation Identifier.
* @return Returns a 22 characters long globally unique string created based on UUID.
*/
+ (NSString *)generateFID;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,104 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsItem.h"
#import "FIRInstallationsStoredAuthToken.h"
#import "FIRInstallationsStoredItem.h"
@implementation FIRInstallationsItem
- (instancetype)initWithAppID:(NSString *)appID firebaseAppName:(NSString *)firebaseAppName {
self = [super init];
if (self) {
_appID = [appID copy];
_firebaseAppName = [firebaseAppName copy];
}
return self;
}
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
FIRInstallationsItem *clone = [[FIRInstallationsItem alloc] initWithAppID:self.appID
firebaseAppName:self.firebaseAppName];
clone.firebaseInstallationID = [self.firebaseInstallationID copy];
clone.refreshToken = [self.refreshToken copy];
clone.authToken = [self.authToken copy];
clone.registrationStatus = self.registrationStatus;
return clone;
}
- (void)updateWithStoredItem:(FIRInstallationsStoredItem *)item {
self.firebaseInstallationID = item.firebaseInstallationID;
self.refreshToken = item.refreshToken;
self.authToken = item.authToken;
self.registrationStatus = item.registrationStatus;
self.IIDDefaultToken = item.IIDDefaultToken;
}
- (FIRInstallationsStoredItem *)storedItem {
FIRInstallationsStoredItem *storedItem = [[FIRInstallationsStoredItem alloc] init];
storedItem.firebaseInstallationID = self.firebaseInstallationID;
storedItem.refreshToken = self.refreshToken;
storedItem.authToken = self.authToken;
storedItem.registrationStatus = self.registrationStatus;
storedItem.IIDDefaultToken = self.IIDDefaultToken;
return storedItem;
}
- (nonnull NSString *)identifier {
return [[self class] identifierWithAppID:self.appID appName:self.firebaseAppName];
}
+ (NSString *)identifierWithAppID:(NSString *)appID appName:(NSString *)appName {
return [appID stringByAppendingString:appName];
}
+ (NSString *)generateFID {
NSUUID *UUID = [NSUUID UUID];
uuid_t UUIDBytes;
[UUID getUUIDBytes:UUIDBytes];
NSUInteger UUIDLength = sizeof(uuid_t);
NSData *UUIDData = [NSData dataWithBytes:UUIDBytes length:UUIDLength];
uint8_t UUIDLast4Bits = UUIDBytes[UUIDLength - 1] & 0b00001111;
// FID first 4 bits must be `0111`. The last 4 UUID bits will be cut later to form a proper FID.
// To keep 16 random bytes we copy these last 4 UUID to the FID 1st byte after `0111` prefix.
uint8_t FIDPrefix = 0b01110000 | UUIDLast4Bits;
NSMutableData *FIDData = [NSMutableData dataWithBytes:&FIDPrefix length:1];
[FIDData appendData:UUIDData];
NSString *FIDString = [self base64URLEncodedStringWithData:FIDData];
// A valid FID has exactly 22 base64 characters, which is 132 bits, or 16.5 bytes.
// Our generated ID has 16 bytes UUID + 1 byte prefix which after encoding with base64 will become
// 23 characters plus 1 character for "=" padding.
// Remove the 23rd character that was added because of the extra 4 bits at the
// end of our 17 byte data and the '=' padding.
return [FIDString substringWithRange:NSMakeRange(0, 22)];
}
+ (NSString *)base64URLEncodedStringWithData:(NSData *)data {
NSString *string = [data base64EncodedStringWithOptions:0];
string = [string stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
string = [string stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
return string;
}
@end

View File

@ -0,0 +1,51 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <FirebaseCore/FIRLogger.h>
extern FIRLoggerService kFIRLoggerInstallations;
// FIRInstallationsAPIService.m
extern NSString *const kFIRInstallationsMessageCodeSendAPIRequest;
extern NSString *const kFIRInstallationsMessageCodeAPIRequestNetworkError;
extern NSString *const kFIRInstallationsMessageCodeAPIRequestResponse;
extern NSString *const kFIRInstallationsMessageCodeUnexpectedAPIRequestResponse;
extern NSString *const kFIRInstallationsMessageCodeParsingAPIResponse;
extern NSString *const kFIRInstallationsMessageCodeAPIResponseParsingInstallationFailed;
extern NSString *const kFIRInstallationsMessageCodeAPIResponseParsingInstallationSucceed;
extern NSString *const kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenFailed;
extern NSString *const kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenSucceed;
// FIRInstallationsIDController.m
extern NSString *const kFIRInstallationsMessageCodeNewGetInstallationOperationCreated;
extern NSString *const kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated;
extern NSString *const kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated;
extern NSString *const kFIRInstallationsMessageCodeInvalidFirebaseConfiguration;
// FIRInstallationsStoredItem.m
extern NSString *const kFIRInstallationsMessageCodeInstallationCoderVersionMismatch;
// FIRInstallationsStoredAuthToken.m
extern NSString *const kFIRInstallationsMessageCodeAuthTokenCoderVersionMismatch;
// FIRInstallationsStoredIIDCheckin.m
extern NSString *const kFIRInstallationsMessageCodeIIDCheckinCoderVersionMismatch;
extern NSString *const kFIRInstallationsMessageCodeIIDCheckinFailedToDecode;
// FIRInstallations.m
extern NSString *const kFIRInstallationsMessageCodeInvalidFirebaseAppOptions;

View File

@ -0,0 +1,49 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsLogger.h"
FIRLoggerService kFIRLoggerInstallations = @"[Firebase/Installations]";
// FIRInstallationsAPIService.m
NSString *const kFIRInstallationsMessageCodeSendAPIRequest = @"I-FIS001001";
NSString *const kFIRInstallationsMessageCodeAPIRequestNetworkError = @"I-FIS001002";
NSString *const kFIRInstallationsMessageCodeAPIRequestResponse = @"I-FIS001003";
NSString *const kFIRInstallationsMessageCodeUnexpectedAPIRequestResponse = @"I-FIS001004";
NSString *const kFIRInstallationsMessageCodeParsingAPIResponse = @"I-FIS001005";
NSString *const kFIRInstallationsMessageCodeAPIResponseParsingInstallationFailed = @"I-FIS001006";
NSString *const kFIRInstallationsMessageCodeAPIResponseParsingInstallationSucceed = @"I-FIS001007";
NSString *const kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenFailed = @"I-FIS001008";
NSString *const kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenSucceed = @"I-FIS001009";
// FIRInstallationsIDController.m
NSString *const kFIRInstallationsMessageCodeNewGetInstallationOperationCreated = @"I-FIS002000";
NSString *const kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated = @"I-FIS002001";
NSString *const kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated = @"I-FIS002002";
NSString *const kFIRInstallationsMessageCodeInvalidFirebaseConfiguration = @"I-FIS002003";
// FIRInstallationsStoredItem.m
NSString *const kFIRInstallationsMessageCodeInstallationCoderVersionMismatch = @"I-FIS003000";
// FIRInstallationsStoredAuthToken.m
NSString *const kFIRInstallationsMessageCodeAuthTokenCoderVersionMismatch = @"I-FIS004000";
// FIRInstallationsStoredIIDCheckin.m
NSString *const kFIRInstallationsMessageCodeIIDCheckinCoderVersionMismatch = @"I-FIS007000";
NSString *const kFIRInstallationsMessageCodeIIDCheckinFailedToDecode = @"I-FIS007001";
// FIRInstallations.m
NSString *const kFIRInstallationsMessageCodeInvalidFirebaseAppOptions = @"I-FIS008000";

View File

@ -0,0 +1,23 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsVersion.h"
// Convert the macro to a string
#define STR(x) STR_EXPAND(x)
#define STR_EXPAND(x) #x
const char *const FIRInstallationsVersionStr = (const char *const)STR(FIRInstallations_LIB_VERSION);

View File

@ -0,0 +1,48 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class FBLPromise<ValueType>;
NS_ASSUME_NONNULL_BEGIN
/** The class encapsulates a port of a piece FirebaseInstanceID logic required to migrate IID. */
@interface FIRInstallationsIIDStore : NSObject
/**
* Retrieves existing IID if present.
* @return Returns a promise that is resolved with IID string if IID has been found or rejected with
* an error otherwise.
*/
- (FBLPromise<NSString *> *)existingIID;
/**
* Deletes existing IID if present.
* @return Returns a promise that is resolved with `[NSNull null]` if the IID was successfully.
* deleted or was not found. The promise is rejected otherwise.
*/
- (FBLPromise<NSNull *> *)deleteExistingIID;
#if TARGET_OS_OSX
/// If not `nil`, then only this keychain will be used to save and read data (see
/// `kSecMatchSearchList` and `kSecUseKeychain`. It is mostly intended to be used by unit tests.
@property(nonatomic, nullable) SecKeychainRef keychainRef;
#endif // TARGET_OSX
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,236 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsIIDStore.h"
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import <CommonCrypto/CommonDigest.h>
#import "FIRInstallationsErrorUtil.h"
static NSString *const kFIRInstallationsIIDKeyPairPublicTagPrefix =
@"com.google.iid.keypair.public-";
static NSString *const kFIRInstallationsIIDKeyPairPrivateTagPrefix =
@"com.google.iid.keypair.private-";
static NSString *const kFIRInstallationsIIDCreationTimePlistKey = @"|S|cre";
@implementation FIRInstallationsIIDStore
- (FBLPromise<NSString *> *)existingIID {
return [FBLPromise onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
do:^id _Nullable {
if (![self hasPlistIIDFlag]) {
return nil;
}
NSData *IIDPublicKeyData = [self IIDPublicKeyData];
return [self IIDWithPublicKeyData:IIDPublicKeyData];
}]
.validate(^BOOL(NSString *_Nullable IID) {
return IID.length > 0;
});
}
- (FBLPromise<NSNull *> *)deleteExistingIID {
return [FBLPromise onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
do:^id _Nullable {
NSError *error;
if (![self deleteIIDFlagFromPlist:&error]) {
return error;
}
if (![self deleteIID:&error]) {
return error;
}
return [NSNull null];
}];
}
#pragma mark - IID decoding
- (NSString *)IIDWithPublicKeyData:(NSData *)publicKeyData {
NSData *publicKeySHA1 = [self sha1WithData:publicKeyData];
const uint8_t *bytes = publicKeySHA1.bytes;
NSMutableData *identityData = [NSMutableData dataWithData:publicKeySHA1];
uint8_t b0 = bytes[0];
// Take the first byte and make the initial four 7 by initially making the initial 4 bits 0
// and then adding 0x70 to it.
b0 = 0x70 + (0xF & b0);
// failsafe should give you back b0 itself
b0 = (b0 & 0xFF);
[identityData replaceBytesInRange:NSMakeRange(0, 1) withBytes:&b0];
NSData *data = [identityData subdataWithRange:NSMakeRange(0, 8 * sizeof(Byte))];
return [self base64URLEncodedStringWithData:data];
}
- (NSData *)sha1WithData:(NSData *)data {
unsigned char output[CC_SHA1_DIGEST_LENGTH];
unsigned int length = (unsigned int)[data length];
CC_SHA1(data.bytes, length, output);
return [NSData dataWithBytes:output length:CC_SHA1_DIGEST_LENGTH];
}
- (NSString *)base64URLEncodedStringWithData:(NSData *)data {
NSString *string = [data base64EncodedStringWithOptions:0];
string = [string stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
string = [string stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
string = [string stringByReplacingOccurrencesOfString:@"=" withString:@""];
return string;
}
#pragma mark - Keychain
- (NSData *)IIDPublicKeyData {
NSString *tag = [self keychainKeyTagWithPrefix:kFIRInstallationsIIDKeyPairPublicTagPrefix];
NSDictionary *query = [self keyPairQueryWithTag:tag returnData:YES];
CFTypeRef keyRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&keyRef);
if (status != noErr) {
if (keyRef) {
CFRelease(keyRef);
}
return nil;
}
return (__bridge NSData *)keyRef;
}
- (BOOL)deleteIID:(NSError **)outError {
if (![self deleteKeychainKeyWithTagPrefix:kFIRInstallationsIIDKeyPairPublicTagPrefix
error:outError]) {
return NO;
}
if (![self deleteKeychainKeyWithTagPrefix:kFIRInstallationsIIDKeyPairPrivateTagPrefix
error:outError]) {
return NO;
}
return YES;
}
- (BOOL)deleteKeychainKeyWithTagPrefix:(NSString *)tagPrefix error:(NSError **)outError {
NSString *keyTag = [self keychainKeyTagWithPrefix:kFIRInstallationsIIDKeyPairPublicTagPrefix];
NSDictionary *keyQuery = [self keyPairQueryWithTag:keyTag returnData:NO];
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)keyQuery);
// When item is not found, it should NOT be considered as an error. The operation should
// continue.
if (status != noErr && status != errSecItemNotFound) {
FIRInstallationsItemSetErrorToPointer(
[FIRInstallationsErrorUtil keychainErrorWithFunction:@"SecItemDelete" status:status],
outError);
return NO;
}
return YES;
}
- (NSDictionary *)keyPairQueryWithTag:(NSString *)tag returnData:(BOOL)shouldReturnData {
NSMutableDictionary *query = [NSMutableDictionary dictionary];
NSData *tagData = [tag dataUsingEncoding:NSUTF8StringEncoding];
query[(__bridge id)kSecClass] = (__bridge id)kSecClassKey;
query[(__bridge id)kSecAttrApplicationTag] = tagData;
query[(__bridge id)kSecAttrKeyType] = (__bridge id)kSecAttrKeyTypeRSA;
if (shouldReturnData) {
query[(__bridge id)kSecReturnData] = @(YES);
}
#if TARGET_OS_OSX
if (self.keychainRef) {
query[(__bridge NSString *)kSecMatchSearchList] = @[ (__bridge id)(self.keychainRef) ];
}
#endif // TARGET_OSX
return query;
}
- (NSString *)keychainKeyTagWithPrefix:(NSString *)prefix {
NSString *mainAppBundleID = [[NSBundle mainBundle] bundleIdentifier];
if (mainAppBundleID.length == 0) {
return nil;
}
return [NSString stringWithFormat:@"%@%@", prefix, mainAppBundleID];
}
- (NSString *)mainbundleIdentifier {
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
if (!bundleIdentifier.length) {
return nil;
}
return bundleIdentifier;
}
#pragma mark - Plist
- (BOOL)deleteIIDFlagFromPlist:(NSError **)outError {
NSString *path = [self plistPath];
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
return YES;
}
NSMutableDictionary *plistContent = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
plistContent[kFIRInstallationsIIDCreationTimePlistKey] = nil;
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
return [plistContent writeToURL:[NSURL fileURLWithPath:path] error:outError];
}
return [plistContent writeToFile:path atomically:YES];
}
- (BOOL)hasPlistIIDFlag {
NSString *path = [self plistPath];
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
return NO;
}
NSDictionary *plistContent = [[NSDictionary alloc] initWithContentsOfFile:path];
return plistContent[kFIRInstallationsIIDCreationTimePlistKey] != nil;
}
- (NSString *)plistPath {
NSString *plistNameWithExtension = @"com.google.iid-keypair.plist";
NSString *_subDirectoryName = @"Google/FirebaseInstanceID";
NSArray *directoryPaths =
NSSearchPathForDirectoriesInDomains([self supportedDirectory], NSUserDomainMask, YES);
NSArray *components = @[ directoryPaths.lastObject, _subDirectoryName, plistNameWithExtension ];
return [NSString pathWithComponents:components];
}
- (NSSearchPathDirectory)supportedDirectory {
#if TARGET_OS_TV
return NSCachesDirectory;
#else
return NSApplicationSupportDirectory;
#endif
}
@end

View File

@ -0,0 +1,36 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class FBLPromise<ValueType>;
NS_ASSUME_NONNULL_BEGIN
/**
* The class reads a default IID token from IID store if available.
*/
@interface FIRInstallationsIIDTokenStore : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithGCMSenderID:(NSString *)GCMSenderID;
- (FBLPromise<NSString *> *)existingIIDDefaultToken;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,157 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsIIDTokenStore.h"
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import "FIRInstallationsErrorUtil.h"
#import "FIRInstallationsKeychainUtils.h"
static NSString *const kFIRInstallationsIIDTokenKeychainId = @"com.google.iid-tokens";
@interface FIRInstallationsIIDTokenInfo : NSObject <NSSecureCoding>
@property(nonatomic, nullable, copy) NSString *token;
@end
@implementation FIRInstallationsIIDTokenInfo
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
self = [super init];
if (self) {
_token = [coder decodeObjectOfClass:[NSString class] forKey:@"token"];
}
return self;
}
@end
@interface FIRInstallationsIIDTokenStore ()
@property(nonatomic, readonly) NSString *GCMSenderID;
@end
@implementation FIRInstallationsIIDTokenStore
- (instancetype)initWithGCMSenderID:(NSString *)GCMSenderID {
self = [super init];
if (self) {
_GCMSenderID = GCMSenderID;
}
return self;
}
- (FBLPromise<NSString *> *)existingIIDDefaultToken {
return [[FBLPromise onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
do:^id _Nullable {
return [self IIDDefaultTokenData];
}] onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
then:^id _Nullable(NSData *_Nullable keychainData) {
return [self IIDCheckinWithData:keychainData];
}];
}
- (FBLPromise<NSString *> *)IIDCheckinWithData:(NSData *)data {
FBLPromise<NSString *> *resultPromise = [FBLPromise pendingPromise];
NSError *archiverError;
NSKeyedUnarchiver *unarchiver;
if (@available(iOS 11.0, tvOS 11.0, macOS 10.13, *)) {
unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:&archiverError];
} else {
@try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
#pragma clang diagnostic pop
} @catch (NSException *exception) {
archiverError = [FIRInstallationsErrorUtil keyedArchiverErrorWithException:exception];
}
}
if (!unarchiver) {
NSError *error = archiverError ?: [FIRInstallationsErrorUtil corruptedIIDTokenData];
[resultPromise reject:error];
return resultPromise;
}
[unarchiver setClass:[FIRInstallationsIIDTokenInfo class] forClassName:@"FIRInstanceIDTokenInfo"];
FIRInstallationsIIDTokenInfo *IIDTokenInfo =
[unarchiver decodeObjectOfClass:[FIRInstallationsIIDTokenInfo class]
forKey:NSKeyedArchiveRootObjectKey];
if (IIDTokenInfo.token.length < 1) {
[resultPromise reject:[FIRInstallationsErrorUtil corruptedIIDTokenData]];
return resultPromise;
}
[resultPromise fulfill:IIDTokenInfo.token];
return resultPromise;
}
- (FBLPromise<NSData *> *)IIDDefaultTokenData {
FBLPromise<NSData *> *resultPromise = [FBLPromise pendingPromise];
NSMutableDictionary *keychainQuery = [self IIDDefaultTokenDataKeychainQuery];
NSError *error;
NSData *data = [FIRInstallationsKeychainUtils getItemWithQuery:keychainQuery error:&error];
if (data) {
[resultPromise fulfill:data];
return resultPromise;
} else {
NSError *outError = error ?: [FIRInstallationsErrorUtil corruptedIIDTokenData];
[resultPromise reject:outError];
return resultPromise;
}
}
- (NSMutableDictionary *)IIDDefaultTokenDataKeychainQuery {
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword};
NSMutableDictionary *finalQuery = [NSMutableDictionary dictionaryWithDictionary:query];
finalQuery[(__bridge NSString *)kSecAttrGeneric] = kFIRInstallationsIIDTokenKeychainId;
NSString *account = [self IIDAppIdentifier];
if ([account length]) {
finalQuery[(__bridge NSString *)kSecAttrAccount] = account;
}
finalQuery[(__bridge NSString *)kSecAttrService] =
[self serviceKeyForAuthorizedEntity:self.GCMSenderID scope:@"*"];
return finalQuery;
}
- (NSString *)IIDAppIdentifier {
return [[NSBundle mainBundle] bundleIdentifier] ?: @"";
}
- (NSString *)serviceKeyForAuthorizedEntity:(NSString *)authorizedEntity scope:(NSString *)scope {
return [NSString stringWithFormat:@"%@:%@", authorizedEntity, scope];
}
@end

View File

@ -0,0 +1,62 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class FBLPromise<ValueType>;
@class FIRInstallationsItem;
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXPORT NSString *const kFIRInstallationsUserAgentKey;
FOUNDATION_EXPORT NSString *const kFIRInstallationsHeartbeatKey;
/**
* The class is responsible for interacting with HTTP REST API for Installations.
*/
@interface FIRInstallationsAPIService : NSObject
/**
* The default initializer.
* @param APIKey The Firebase project API key (see `FIROptions.APIKey`).
* @param projectID The Firebase project ID (see `FIROptions.projectID`).
*/
- (instancetype)initWithAPIKey:(NSString *)APIKey projectID:(NSString *)projectID;
/**
* Sends a request to register a new FID to get auth and refresh tokens.
* @param installation The `FIRInstallationsItem` instance with the FID to register.
* @return A promise that is resolved with a new `FIRInstallationsItem` instance with valid tokens.
* It is rejected with an error in case of a failure.
*/
- (FBLPromise<FIRInstallationsItem *> *)registerInstallation:(FIRInstallationsItem *)installation;
- (FBLPromise<FIRInstallationsItem *> *)refreshAuthTokenForInstallation:
(FIRInstallationsItem *)installation;
/**
* Sends a request to delete the installation, related auth tokens and all related data from the
* server.
* @param installation The installation to delete.
* @return Returns a promise that is resolved with the passed installation on successful deletion or
* is rejected with an error otherwise.
*/
- (FBLPromise<FIRInstallationsItem *> *)deleteInstallation:(FIRInstallationsItem *)installation;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,346 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsAPIService.h"
#import <FirebaseInstallations/FIRInstallationsVersion.h>
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import <FirebaseCore/FIRAppInternal.h>
#import <FirebaseCore/FIRHeartbeatInfo.h>
#import "FIRInstallationsErrorUtil.h"
#import "FIRInstallationsItem+RegisterInstallationAPI.h"
#import "FIRInstallationsLogger.h"
NSString *const kFIRInstallationsAPIBaseURL = @"https://firebaseinstallations.googleapis.com";
NSString *const kFIRInstallationsAPIKey = @"X-Goog-Api-Key";
NSString *const kFIRInstallationsBundleId = @"X-Ios-Bundle-Identifier";
NSString *const kFIRInstallationsIIDMigrationAuthHeader = @"x-goog-fis-ios-iid-migration-auth";
NSString *const kFIRInstallationsHeartbeatKey = @"X-firebase-client-log-type";
NSString *const kFIRInstallationsHeartbeatTag = @"fire-installations";
NSString *const kFIRInstallationsUserAgentKey = @"X-firebase-client";
NS_ASSUME_NONNULL_BEGIN
@interface FIRInstallationsURLSessionResponse : NSObject
@property(nonatomic) NSHTTPURLResponse *HTTPResponse;
@property(nonatomic) NSData *data;
- (instancetype)initWithResponse:(NSHTTPURLResponse *)response data:(nullable NSData *)data;
@end
@implementation FIRInstallationsURLSessionResponse
- (instancetype)initWithResponse:(NSHTTPURLResponse *)response data:(nullable NSData *)data {
self = [super init];
if (self) {
_HTTPResponse = response;
_data = data ?: [NSData data];
}
return self;
}
@end
@interface FIRInstallationsAPIService ()
@property(nonatomic, readonly) NSURLSession *URLSession;
@property(nonatomic, readonly) NSString *APIKey;
@property(nonatomic, readonly) NSString *projectID;
@end
NS_ASSUME_NONNULL_END
@implementation FIRInstallationsAPIService
- (instancetype)initWithAPIKey:(NSString *)APIKey projectID:(NSString *)projectID {
NSURLSession *URLSession = [NSURLSession
sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
return [self initWithURLSession:URLSession APIKey:APIKey projectID:projectID];
}
/// The initializer for tests.
- (instancetype)initWithURLSession:(NSURLSession *)URLSession
APIKey:(NSString *)APIKey
projectID:(NSString *)projectID {
self = [super init];
if (self) {
_URLSession = URLSession;
_APIKey = [APIKey copy];
_projectID = [projectID copy];
}
return self;
}
#pragma mark - Public
- (FBLPromise<FIRInstallationsItem *> *)registerInstallation:(FIRInstallationsItem *)installation {
NSURLRequest *request = [self registerRequestWithInstallation:installation];
return [self sendURLRequest:request].then(
^id _Nullable(FIRInstallationsURLSessionResponse *response) {
return [self registeredInstallationWithInstallation:installation serverResponse:response];
});
}
- (FBLPromise<FIRInstallationsItem *> *)refreshAuthTokenForInstallation:
(FIRInstallationsItem *)installation {
NSURLRequest *request = [self authTokenRequestWithInstallation:installation];
return [self sendURLRequest:request]
.then(^FBLPromise<FIRInstallationsStoredAuthToken *> *(
FIRInstallationsURLSessionResponse *response) {
return [self authTokenWithServerResponse:response];
})
.then(^FIRInstallationsItem *(FIRInstallationsStoredAuthToken *authToken) {
FIRInstallationsItem *updatedInstallation = [installation copy];
updatedInstallation.authToken = authToken;
return updatedInstallation;
});
}
- (FBLPromise<FIRInstallationsItem *> *)deleteInstallation:(FIRInstallationsItem *)installation {
NSURLRequest *request = [self deleteInstallationRequestWithInstallation:installation];
return [[self sendURLRequest:request]
then:^id _Nullable(FIRInstallationsURLSessionResponse *_Nullable value) {
// Return the original installation on success.
return installation;
}];
}
#pragma mark - Register Installation
- (NSURLRequest *)registerRequestWithInstallation:(FIRInstallationsItem *)installation {
NSString *URLString = [NSString stringWithFormat:@"%@/v1/projects/%@/installations/",
kFIRInstallationsAPIBaseURL, self.projectID];
NSURL *URL = [NSURL URLWithString:URLString];
NSDictionary *bodyDict = @{
@"fid" : installation.firebaseInstallationID,
@"authVersion" : @"FIS_v2",
@"appId" : installation.appID,
@"sdkVersion" : [self SDKVersion]
};
NSDictionary *headers;
if (installation.IIDDefaultToken) {
headers = @{kFIRInstallationsIIDMigrationAuthHeader : installation.IIDDefaultToken};
}
return [self requestWithURL:URL
HTTPMethod:@"POST"
bodyDict:bodyDict
refreshToken:nil
additionalHeaders:headers];
}
- (FBLPromise<FIRInstallationsItem *> *)
registeredInstallationWithInstallation:(FIRInstallationsItem *)installation
serverResponse:(FIRInstallationsURLSessionResponse *)response {
return [FBLPromise do:^id {
FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeParsingAPIResponse,
@"Parsing server response for %@.", response.HTTPResponse.URL);
NSError *error;
FIRInstallationsItem *registeredInstallation =
[installation registeredInstallationWithJSONData:response.data
date:[NSDate date]
error:&error];
if (registeredInstallation == nil) {
FIRLogDebug(kFIRLoggerInstallations,
kFIRInstallationsMessageCodeAPIResponseParsingInstallationFailed,
@"Failed to parse FIRInstallationsItem: %@.", error);
return error;
}
FIRLogDebug(kFIRLoggerInstallations,
kFIRInstallationsMessageCodeAPIResponseParsingInstallationSucceed,
@"FIRInstallationsItem parsed successfully.");
return registeredInstallation;
}];
}
#pragma mark - Auth token
- (NSURLRequest *)authTokenRequestWithInstallation:(FIRInstallationsItem *)installation {
NSString *URLString =
[NSString stringWithFormat:@"%@/v1/projects/%@/installations/%@/authTokens:generate",
kFIRInstallationsAPIBaseURL, self.projectID,
installation.firebaseInstallationID];
NSURL *URL = [NSURL URLWithString:URLString];
NSDictionary *bodyDict = @{@"installation" : @{@"sdkVersion" : [self SDKVersion]}};
return [self requestWithURL:URL
HTTPMethod:@"POST"
bodyDict:bodyDict
refreshToken:installation.refreshToken];
}
- (FBLPromise<FIRInstallationsStoredAuthToken *> *)authTokenWithServerResponse:
(FIRInstallationsURLSessionResponse *)response {
return [FBLPromise do:^id {
FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeParsingAPIResponse,
@"Parsing server response for %@.", response.HTTPResponse.URL);
NSError *error;
FIRInstallationsStoredAuthToken *token =
[FIRInstallationsItem authTokenWithGenerateTokenAPIJSONData:response.data
date:[NSDate date]
error:&error];
if (token == nil) {
FIRLogDebug(kFIRLoggerInstallations,
kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenFailed,
@"Failed to parse FIRInstallationsStoredAuthToken: %@.", error);
return error;
}
FIRLogDebug(kFIRLoggerInstallations,
kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenSucceed,
@"FIRInstallationsStoredAuthToken parsed successfully.");
return token;
}];
}
#pragma mark - Delete Installation
- (NSURLRequest *)deleteInstallationRequestWithInstallation:(FIRInstallationsItem *)installation {
NSString *URLString = [NSString stringWithFormat:@"%@/v1/projects/%@/installations/%@/",
kFIRInstallationsAPIBaseURL, self.projectID,
installation.firebaseInstallationID];
NSURL *URL = [NSURL URLWithString:URLString];
return [self requestWithURL:URL
HTTPMethod:@"DELETE"
bodyDict:@{}
refreshToken:installation.refreshToken];
}
#pragma mark - URL Request
- (NSURLRequest *)requestWithURL:(NSURL *)requestURL
HTTPMethod:(NSString *)HTTPMethod
bodyDict:(NSDictionary *)bodyDict
refreshToken:(nullable NSString *)refreshToken {
return [self requestWithURL:requestURL
HTTPMethod:HTTPMethod
bodyDict:bodyDict
refreshToken:refreshToken
additionalHeaders:nil];
}
- (NSURLRequest *)requestWithURL:(NSURL *)requestURL
HTTPMethod:(NSString *)HTTPMethod
bodyDict:(NSDictionary *)bodyDict
refreshToken:(nullable NSString *)refreshToken
additionalHeaders:
(nullable NSDictionary<NSString *, NSString *> *)additionalHeaders {
__block NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL];
request.HTTPMethod = HTTPMethod;
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
[request addValue:self.APIKey forHTTPHeaderField:kFIRInstallationsAPIKey];
[request addValue:bundleIdentifier forHTTPHeaderField:kFIRInstallationsBundleId];
[self setJSONHTTPBody:bodyDict forRequest:request];
if (refreshToken) {
NSString *authHeader = [NSString stringWithFormat:@"FIS_v2 %@", refreshToken];
[request setValue:authHeader forHTTPHeaderField:@"Authorization"];
}
// User agent Header.
[request setValue:[FIRApp firebaseUserAgent] forHTTPHeaderField:kFIRInstallationsUserAgentKey];
// Heartbeat Header.
[request setValue:@([FIRHeartbeatInfo heartbeatCodeForTag:kFIRInstallationsHeartbeatTag])
.stringValue
forHTTPHeaderField:kFIRInstallationsHeartbeatKey];
[additionalHeaders enumerateKeysAndObjectsUsingBlock:^(
NSString *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) {
[request setValue:obj forHTTPHeaderField:key];
}];
return [request copy];
}
- (FBLPromise<FIRInstallationsURLSessionResponse *> *)URLRequestPromise:(NSURLRequest *)request {
return [[FBLPromise async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) {
FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeSendAPIRequest,
@"Sending request: %@, body:%@, headers: %@.", request,
[[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding],
request.allHTTPHeaderFields);
[[self.URLSession
dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
FIRLogDebug(kFIRLoggerInstallations,
kFIRInstallationsMessageCodeAPIRequestNetworkError,
@"Request failed: %@, error: %@.", request, error);
reject(error);
} else {
FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeAPIRequestResponse,
@"Request response received: %@, error: %@, body: %@.", request, error,
[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
fulfill([[FIRInstallationsURLSessionResponse alloc]
initWithResponse:(NSHTTPURLResponse *)response
data:data]);
}
}] resume];
}] then:^id _Nullable(FIRInstallationsURLSessionResponse *response) {
return [self validateHTTPResponseStatusCode:response];
}];
}
- (FBLPromise<FIRInstallationsURLSessionResponse *> *)validateHTTPResponseStatusCode:
(FIRInstallationsURLSessionResponse *)response {
NSInteger statusCode = response.HTTPResponse.statusCode;
return [FBLPromise do:^id _Nullable {
if (statusCode < 200 || statusCode >= 300) {
FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeUnexpectedAPIRequestResponse,
@"Unexpected API response: %@, body: %@.", response.HTTPResponse,
[[NSString alloc] initWithData:response.data encoding:NSUTF8StringEncoding]);
return [FIRInstallationsErrorUtil APIErrorWithHTTPResponse:response.HTTPResponse
data:response.data];
}
return response;
}];
}
- (FBLPromise<FIRInstallationsURLSessionResponse *> *)sendURLRequest:(NSURLRequest *)request {
return [FBLPromise attempts:1
delay:1
condition:^BOOL(NSInteger remainingAttempts, NSError *_Nonnull error) {
return [FIRInstallationsErrorUtil isAPIError:error withHTTPCode:500];
}
retry:^id _Nullable {
return [self URLRequestPromise:request];
}];
}
- (NSString *)SDKVersion {
return [NSString stringWithFormat:@"i:%s", FIRInstallationsVersionStr];
}
#pragma mark - JSON
- (void)setJSONHTTPBody:(NSDictionary<NSString *, id> *)body
forRequest:(NSMutableURLRequest *)request {
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSError *error;
NSData *JSONData = [NSJSONSerialization dataWithJSONObject:body options:0 error:&error];
if (JSONData == nil) {
// TODO: Log or return an error.
}
request.HTTPBody = JSONData;
}
@end

View File

@ -0,0 +1,53 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsItem.h"
@class FIRInstallationsStoredAuthToken;
NS_ASSUME_NONNULL_BEGIN
@interface FIRInstallationsItem (RegisterInstallationAPI)
/**
* Parses and validates the Register Installation API response and returns a corresponding
* `FIRInstallationsItem` instance on success.
* @param JSONData The data with JSON encoded API response.
* @param date The Auth Token expiration date will be calculated as `date` +
* `response.authToken.expiresIn`. For most of the cases `[NSDate date]` should be passed there. A
* different value may be passed e.g. for unit tests.
* @param outError A pointer to assign a specific `NSError` instance in case of failure. No error is
* assigned in case of success.
* @return Returns a new `FIRInstallationsItem` instance in the success case or `nil` otherwise.
*/
- (nullable FIRInstallationsItem *)registeredInstallationWithJSONData:(NSData *)JSONData
date:(NSDate *)date
error:
(NSError *_Nullable *)outError;
+ (nullable FIRInstallationsStoredAuthToken *)authTokenWithGenerateTokenAPIJSONData:(NSData *)data
date:(NSDate *)date
error:(NSError **)
outError;
+ (nullable FIRInstallationsStoredAuthToken *)authTokenWithJSONDict:
(NSDictionary<NSString *, id> *)dict
date:(NSDate *)date
error:(NSError **)outError;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,142 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsItem+RegisterInstallationAPI.h"
#import "FIRInstallationsErrorUtil.h"
#import "FIRInstallationsStoredAuthToken.h"
@implementation FIRInstallationsItem (RegisterInstallationAPI)
- (nullable FIRInstallationsItem *)
registeredInstallationWithJSONData:(NSData *)data
date:(NSDate *)date
error:(NSError *__autoreleasing _Nullable *_Nullable)outError {
NSDictionary *responseJSON = [FIRInstallationsItem dictionaryFromJSONData:data error:outError];
if (!responseJSON) {
return nil;
}
NSString *refreshToken = [FIRInstallationsItem validStringOrNilForKey:@"refreshToken"
fromDict:responseJSON];
if (refreshToken == nil) {
FIRInstallationsItemSetErrorToPointer(
[FIRInstallationsErrorUtil FIDRegistrationErrorWithResponseMissingField:@"refreshToken"],
outError);
return nil;
}
NSDictionary *authTokenDict = responseJSON[@"authToken"];
if (![authTokenDict isKindOfClass:[NSDictionary class]]) {
FIRInstallationsItemSetErrorToPointer(
[FIRInstallationsErrorUtil FIDRegistrationErrorWithResponseMissingField:@"authToken"],
outError);
return nil;
}
FIRInstallationsStoredAuthToken *authToken =
[FIRInstallationsItem authTokenWithJSONDict:authTokenDict date:date error:outError];
if (authToken == nil) {
return nil;
}
FIRInstallationsItem *installation =
[[FIRInstallationsItem alloc] initWithAppID:self.appID firebaseAppName:self.firebaseAppName];
NSString *installationID = [FIRInstallationsItem validStringOrNilForKey:@"fid"
fromDict:responseJSON];
installation.firebaseInstallationID = installationID ?: self.firebaseInstallationID;
installation.refreshToken = refreshToken;
installation.authToken = authToken;
installation.registrationStatus = FIRInstallationStatusRegistered;
return installation;
}
#pragma mark - Auth token
+ (nullable FIRInstallationsStoredAuthToken *)authTokenWithGenerateTokenAPIJSONData:(NSData *)data
date:(NSDate *)date
error:(NSError **)
outError {
NSDictionary *dict = [self dictionaryFromJSONData:data error:outError];
if (!dict) {
return nil;
}
return [self authTokenWithJSONDict:dict date:date error:outError];
}
+ (nullable FIRInstallationsStoredAuthToken *)authTokenWithJSONDict:
(NSDictionary<NSString *, id> *)dict
date:(NSDate *)date
error:(NSError **)outError {
NSString *token = [self validStringOrNilForKey:@"token" fromDict:dict];
if (token == nil) {
FIRInstallationsItemSetErrorToPointer(
[FIRInstallationsErrorUtil FIDRegistrationErrorWithResponseMissingField:@"authToken.token"],
outError);
return nil;
}
NSString *expiresInString = [self validStringOrNilForKey:@"expiresIn" fromDict:dict];
if (expiresInString == nil) {
FIRInstallationsItemSetErrorToPointer(
[FIRInstallationsErrorUtil
FIDRegistrationErrorWithResponseMissingField:@"authToken.expiresIn"],
outError);
return nil;
}
// The response should contain the string in format like "604800s".
// The server should never response with anything else except seconds.
// Just drop the last character and parse a number from string.
NSString *expiresInSeconds = [expiresInString substringToIndex:expiresInString.length - 1];
NSTimeInterval expiresIn = [expiresInSeconds doubleValue];
NSDate *expirationDate = [date dateByAddingTimeInterval:expiresIn];
FIRInstallationsStoredAuthToken *authToken = [[FIRInstallationsStoredAuthToken alloc] init];
authToken.status = FIRInstallationsAuthTokenStatusTokenReceived;
authToken.token = token;
authToken.expirationDate = expirationDate;
return authToken;
}
#pragma mark - JSON
+ (nullable NSDictionary<NSString *, id> *)dictionaryFromJSONData:(NSData *)data
error:(NSError **)outError {
NSError *error;
NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (![responseJSON isKindOfClass:[NSDictionary class]]) {
FIRInstallationsItemSetErrorToPointer([FIRInstallationsErrorUtil JSONSerializationError:error],
outError);
return nil;
}
return responseJSON;
}
+ (NSString *)validStringOrNilForKey:(NSString *)key fromDict:(NSDictionary *)dict {
NSString *string = dict[key];
if ([string isKindOfClass:[NSString class]] && string.length > 0) {
return string;
}
return nil;
}
@end

View File

@ -0,0 +1,44 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class FBLPromise<ValueType>;
@class FIRInstallationsItem;
/**
* The class is responsible for managing FID for a given `FIRApp`.
*/
@interface FIRInstallationsIDController : NSObject
- (instancetype)initWithGoogleAppID:(NSString *)appID
appName:(NSString *)appName
APIKey:(NSString *)APIKey
projectID:(NSString *)projectID
GCMSenderID:(NSString *)GCMSenderID
accessGroup:(nullable NSString *)accessGroup;
- (FBLPromise<FIRInstallationsItem *> *)getInstallationItem;
- (FBLPromise<FIRInstallationsItem *> *)getAuthTokenForcingRefresh:(BOOL)forceRefresh;
- (FBLPromise<NSNull *> *)deleteInstallation;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,458 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsIDController.h"
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import <FirebaseCore/FIRAppInternal.h>
#import "FIRInstallationsAPIService.h"
#import "FIRInstallationsErrorUtil.h"
#import "FIRInstallationsIIDStore.h"
#import "FIRInstallationsIIDTokenStore.h"
#import "FIRInstallationsItem.h"
#import "FIRInstallationsLogger.h"
#import "FIRInstallationsSingleOperationPromiseCache.h"
#import "FIRInstallationsStore.h"
#import "FIRSecureStorage.h"
#import "FIRInstallationsHTTPError.h"
#import "FIRInstallationsStoredAuthToken.h"
const NSNotificationName FIRInstallationIDDidChangeNotification =
@"FIRInstallationIDDidChangeNotification";
NSString *const kFIRInstallationIDDidChangeNotificationAppNameKey =
@"FIRInstallationIDDidChangeNotification";
NSTimeInterval const kFIRInstallationsTokenExpirationThreshold = 60 * 60; // 1 hour.
@interface FIRInstallationsIDController ()
@property(nonatomic, readonly) NSString *appID;
@property(nonatomic, readonly) NSString *appName;
@property(nonatomic, readonly) FIRInstallationsStore *installationsStore;
@property(nonatomic, readonly) FIRInstallationsIIDStore *IIDStore;
@property(nonatomic, readonly) FIRInstallationsIIDTokenStore *IIDTokenStore;
@property(nonatomic, readonly) FIRInstallationsAPIService *APIService;
@property(nonatomic, readonly) FIRInstallationsSingleOperationPromiseCache<FIRInstallationsItem *>
*getInstallationPromiseCache;
@property(nonatomic, readonly)
FIRInstallationsSingleOperationPromiseCache<FIRInstallationsItem *> *authTokenPromiseCache;
@property(nonatomic, readonly) FIRInstallationsSingleOperationPromiseCache<FIRInstallationsItem *>
*authTokenForcingRefreshPromiseCache;
@property(nonatomic, readonly)
FIRInstallationsSingleOperationPromiseCache<NSNull *> *deleteInstallationPromiseCache;
@end
@implementation FIRInstallationsIDController
- (instancetype)initWithGoogleAppID:(NSString *)appID
appName:(NSString *)appName
APIKey:(NSString *)APIKey
projectID:(NSString *)projectID
GCMSenderID:(NSString *)GCMSenderID
accessGroup:(NSString *)accessGroup {
FIRSecureStorage *secureStorage = [[FIRSecureStorage alloc] init];
FIRInstallationsStore *installationsStore =
[[FIRInstallationsStore alloc] initWithSecureStorage:secureStorage accessGroup:accessGroup];
// Use `GCMSenderID` as project identifier when `projectID` is not available.
NSString *APIServiceProjectID = (projectID.length > 0) ? projectID : GCMSenderID;
FIRInstallationsAPIService *apiService =
[[FIRInstallationsAPIService alloc] initWithAPIKey:APIKey projectID:APIServiceProjectID];
FIRInstallationsIIDStore *IIDStore = [[FIRInstallationsIIDStore alloc] init];
FIRInstallationsIIDTokenStore *IIDCheckingStore =
[[FIRInstallationsIIDTokenStore alloc] initWithGCMSenderID:GCMSenderID];
return [self initWithGoogleAppID:appID
appName:appName
installationsStore:installationsStore
APIService:apiService
IIDStore:IIDStore
IIDTokenStore:IIDCheckingStore];
}
/// The initializer is supposed to be used by tests to inject `installationsStore`.
- (instancetype)initWithGoogleAppID:(NSString *)appID
appName:(NSString *)appName
installationsStore:(FIRInstallationsStore *)installationsStore
APIService:(FIRInstallationsAPIService *)APIService
IIDStore:(FIRInstallationsIIDStore *)IIDStore
IIDTokenStore:(FIRInstallationsIIDTokenStore *)IIDTokenStore {
self = [super init];
if (self) {
_appID = appID;
_appName = appName;
_installationsStore = installationsStore;
_APIService = APIService;
_IIDStore = IIDStore;
_IIDTokenStore = IIDTokenStore;
__weak FIRInstallationsIDController *weakSelf = self;
_getInstallationPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc]
initWithNewOperationHandler:^FBLPromise *_Nonnull {
FIRInstallationsIDController *strongSelf = weakSelf;
return [strongSelf createGetInstallationItemPromise];
}];
_authTokenPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc]
initWithNewOperationHandler:^FBLPromise *_Nonnull {
FIRInstallationsIDController *strongSelf = weakSelf;
return [strongSelf installationWithValidAuthTokenForcingRefresh:NO];
}];
_authTokenForcingRefreshPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc]
initWithNewOperationHandler:^FBLPromise *_Nonnull {
FIRInstallationsIDController *strongSelf = weakSelf;
return [strongSelf installationWithValidAuthTokenForcingRefresh:YES];
}];
_deleteInstallationPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc]
initWithNewOperationHandler:^FBLPromise *_Nonnull {
FIRInstallationsIDController *strongSelf = weakSelf;
return [strongSelf createDeleteInstallationPromise];
}];
}
return self;
}
#pragma mark - Get Installation.
- (FBLPromise<FIRInstallationsItem *> *)getInstallationItem {
return [self.getInstallationPromiseCache getExistingPendingOrCreateNewPromise];
}
- (FBLPromise<FIRInstallationsItem *> *)createGetInstallationItemPromise {
FIRLogDebug(kFIRLoggerInstallations,
kFIRInstallationsMessageCodeNewGetInstallationOperationCreated, @"%s, appName: %@",
__PRETTY_FUNCTION__, self.appName);
FBLPromise<FIRInstallationsItem *> *installationItemPromise =
[self getStoredInstallation].recover(^id(NSError *error) {
return [self createAndSaveFID];
});
// Initiate registration process on success if needed, but return the installation without waiting
// for it.
installationItemPromise.then(^id(FIRInstallationsItem *installation) {
[self getAuthTokenForcingRefresh:NO];
return nil;
});
return installationItemPromise;
}
- (FBLPromise<FIRInstallationsItem *> *)getStoredInstallation {
return [self.installationsStore installationForAppID:self.appID appName:self.appName].validate(
^BOOL(FIRInstallationsItem *installation) {
BOOL isValid = NO;
switch (installation.registrationStatus) {
case FIRInstallationStatusUnregistered:
case FIRInstallationStatusRegistered:
isValid = YES;
break;
case FIRInstallationStatusUnknown:
isValid = NO;
break;
}
return isValid;
});
}
- (FBLPromise<FIRInstallationsItem *> *)createAndSaveFID {
return [self migrateOrGenerateInstallation]
.then(^FBLPromise<FIRInstallationsItem *> *(FIRInstallationsItem *installation) {
return [self saveInstallation:installation];
})
.then(^FIRInstallationsItem *(FIRInstallationsItem *installation) {
[self postFIDDidChangeNotification];
return installation;
});
}
- (FBLPromise<FIRInstallationsItem *> *)saveInstallation:(FIRInstallationsItem *)installation {
return [self.installationsStore saveInstallation:installation].then(
^FIRInstallationsItem *(NSNull *result) {
return installation;
});
}
/**
* Tries to migrate IID data stored by FirebaseInstanceID SDK or generates a new Installation ID if
* not found.
*/
- (FBLPromise<FIRInstallationsItem *> *)migrateOrGenerateInstallation {
if (![self isDefaultApp]) {
// Existing IID should be used only for default FirebaseApp.
FIRInstallationsItem *installation =
[self createInstallationWithFID:[FIRInstallationsItem generateFID] IIDDefaultToken:nil];
return [FBLPromise resolvedWith:installation];
}
return [[[FBLPromise
all:@[ [self.IIDStore existingIID], [self.IIDTokenStore existingIIDDefaultToken] ]]
then:^id _Nullable(NSArray *_Nullable results) {
NSString *existingIID = results[0];
NSString *IIDDefaultToken = results[1];
return [self createInstallationWithFID:existingIID IIDDefaultToken:IIDDefaultToken];
}] recover:^id _Nullable(NSError *_Nonnull error) {
return [self createInstallationWithFID:[FIRInstallationsItem generateFID] IIDDefaultToken:nil];
}];
}
- (FIRInstallationsItem *)createInstallationWithFID:(NSString *)FID
IIDDefaultToken:(nullable NSString *)IIDDefaultToken {
FIRInstallationsItem *installation = [[FIRInstallationsItem alloc] initWithAppID:self.appID
firebaseAppName:self.appName];
installation.firebaseInstallationID = FID;
installation.IIDDefaultToken = IIDDefaultToken;
installation.registrationStatus = FIRInstallationStatusUnregistered;
return installation;
}
#pragma mark - FID registration
- (FBLPromise<FIRInstallationsItem *> *)registerInstallationIfNeeded:
(FIRInstallationsItem *)installation {
switch (installation.registrationStatus) {
case FIRInstallationStatusRegistered:
// Already registered. Do nothing.
return [FBLPromise resolvedWith:installation];
case FIRInstallationStatusUnknown:
case FIRInstallationStatusUnregistered:
// Registration required. Proceed.
break;
}
return [self.APIService registerInstallation:installation]
.catch(^(NSError *_Nonnull error) {
if ([self doesRegistrationErrorRequireConfigChange:error]) {
FIRLogError(kFIRLoggerInstallations,
kFIRInstallationsMessageCodeInvalidFirebaseConfiguration,
@"Firebase Installation registration failed for app with name: %@, error: "
@"%@\nPlease make sure you use valid GoogleService-Info.plist",
self.appName, error);
}
})
.then(^id(FIRInstallationsItem *registeredInstallation) {
return [self saveInstallation:registeredInstallation];
})
.then(^FIRInstallationsItem *(FIRInstallationsItem *registeredInstallation) {
// Server may respond with a different FID if the sent one cannot be accepted.
if (![registeredInstallation.firebaseInstallationID
isEqualToString:installation.firebaseInstallationID]) {
[self postFIDDidChangeNotification];
}
return registeredInstallation;
});
}
- (BOOL)doesRegistrationErrorRequireConfigChange:(NSError *)error {
FIRInstallationsHTTPError *HTTPError = (FIRInstallationsHTTPError *)error;
if (![HTTPError isKindOfClass:[FIRInstallationsHTTPError class]]) {
return NO;
}
switch (HTTPError.HTTPResponse.statusCode) {
// These are the errors that require Firebase configuration change.
case FIRInstallationsRegistrationHTTPCodeInvalidArgument:
case FIRInstallationsRegistrationHTTPCodeInvalidAPIKey:
case FIRInstallationsRegistrationHTTPCodeAPIKeyToProjectIDMismatch:
case FIRInstallationsRegistrationHTTPCodeProjectNotFound:
return YES;
default:
return NO;
}
}
#pragma mark - Auth Token
- (FBLPromise<FIRInstallationsItem *> *)getAuthTokenForcingRefresh:(BOOL)forceRefresh {
if (forceRefresh || [self.authTokenForcingRefreshPromiseCache getExistingPendingPromise] != nil) {
return [self.authTokenForcingRefreshPromiseCache getExistingPendingOrCreateNewPromise];
} else {
return [self.authTokenPromiseCache getExistingPendingOrCreateNewPromise];
}
}
- (FBLPromise<FIRInstallationsItem *> *)installationWithValidAuthTokenForcingRefresh:
(BOOL)forceRefresh {
FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated,
@"-[FIRInstallationsIDController installationWithValidAuthTokenForcingRefresh:%@], "
@"appName: %@",
@(forceRefresh), self.appName);
return [self getInstallationItem]
.then(^FBLPromise<FIRInstallationsItem *> *(FIRInstallationsItem *installation) {
return [self registerInstallationIfNeeded:installation];
})
.then(^id(FIRInstallationsItem *registeredInstallation) {
BOOL isTokenExpiredOrExpiresSoon =
[registeredInstallation.authToken.expirationDate timeIntervalSinceDate:[NSDate date]] <
kFIRInstallationsTokenExpirationThreshold;
if (forceRefresh || isTokenExpiredOrExpiresSoon) {
return [self refreshAuthTokenForInstallation:registeredInstallation];
} else {
return registeredInstallation;
}
})
.recover(^id(NSError *error) {
return [self regenerateFIDOnRefreshTokenErrorIfNeeded:error];
});
}
- (FBLPromise<FIRInstallationsItem *> *)refreshAuthTokenForInstallation:
(FIRInstallationsItem *)installation {
return [[self.APIService refreshAuthTokenForInstallation:installation]
then:^id _Nullable(FIRInstallationsItem *_Nullable refreshedInstallation) {
return [self saveInstallation:refreshedInstallation];
}];
}
- (id)regenerateFIDOnRefreshTokenErrorIfNeeded:(NSError *)error {
if (![error isKindOfClass:[FIRInstallationsHTTPError class]]) {
// No recovery possible. Return the same error.
return error;
}
FIRInstallationsHTTPError *HTTPError = (FIRInstallationsHTTPError *)error;
switch (HTTPError.HTTPResponse.statusCode) {
case FIRInstallationsAuthTokenHTTPCodeInvalidAuthentication:
case FIRInstallationsAuthTokenHTTPCodeFIDNotFound:
// The stored installation was damaged or blocked by the server.
// Delete the stored installation then generate and register a new one.
return [self getInstallationItem]
.then(^FBLPromise<NSNull *> *(FIRInstallationsItem *installation) {
return [self deleteInstallationLocally:installation];
})
.then(^FBLPromise<FIRInstallationsItem *> *(id result) {
return [self installationWithValidAuthTokenForcingRefresh:NO];
});
default:
// No recovery possible. Return the same error.
return error;
}
}
#pragma mark - Delete FID
- (FBLPromise<NSNull *> *)deleteInstallation {
return [self.deleteInstallationPromiseCache getExistingPendingOrCreateNewPromise];
}
- (FBLPromise<NSNull *> *)createDeleteInstallationPromise {
FIRLogDebug(kFIRLoggerInstallations,
kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated, @"%s, appName: %@",
__PRETTY_FUNCTION__, self.appName);
// Check for ongoing requests first, if there is no a request, then check local storage for
// existing installation.
FBLPromise<FIRInstallationsItem *> *currentInstallationPromise =
[self mostRecentInstallationOperation] ?: [self getStoredInstallation];
return currentInstallationPromise
.then(^id(FIRInstallationsItem *installation) {
return [self sendDeleteInstallationRequestIfNeeded:installation];
})
.then(^id(FIRInstallationsItem *installation) {
// Remove the installation from the local storage.
return [self deleteInstallationLocally:installation];
});
}
- (FBLPromise<NSNull *> *)deleteInstallationLocally:(FIRInstallationsItem *)installation {
return [self.installationsStore removeInstallationForAppID:installation.appID
appName:installation.firebaseAppName]
.then(^FBLPromise<NSNull *> *(NSNull *result) {
return [self deleteExistingIIDIfNeeded];
})
.then(^NSNull *(NSNull *result) {
[self postFIDDidChangeNotification];
return result;
});
}
- (FBLPromise<FIRInstallationsItem *> *)sendDeleteInstallationRequestIfNeeded:
(FIRInstallationsItem *)installation {
switch (installation.registrationStatus) {
case FIRInstallationStatusUnknown:
case FIRInstallationStatusUnregistered:
// The installation is not registered, so it is safe to be deleted as is, so return early.
return [FBLPromise resolvedWith:installation];
break;
case FIRInstallationStatusRegistered:
// Proceed to de-register the installation on the server.
break;
}
return [self.APIService deleteInstallation:installation].recover(^id(NSError *APIError) {
if ([FIRInstallationsErrorUtil isAPIError:APIError withHTTPCode:404]) {
// The installation was not found on the server.
// Return success.
return installation;
} else {
// Re-throw the error otherwise.
return APIError;
}
});
}
- (FBLPromise<NSNull *> *)deleteExistingIIDIfNeeded {
if ([self isDefaultApp]) {
return [self.IIDStore deleteExistingIID];
} else {
return [FBLPromise resolvedWith:[NSNull null]];
}
}
- (nullable FBLPromise<FIRInstallationsItem *> *)mostRecentInstallationOperation {
return [self.authTokenForcingRefreshPromiseCache getExistingPendingPromise]
?: [self.authTokenPromiseCache getExistingPendingPromise]
?: [self.getInstallationPromiseCache getExistingPendingPromise];
}
#pragma mark - Notifications
- (void)postFIDDidChangeNotification {
[[NSNotificationCenter defaultCenter]
postNotificationName:FIRInstallationIDDidChangeNotification
object:nil
userInfo:@{kFIRInstallationIDDidChangeNotificationAppNameKey : self.appName}];
}
#pragma mark - Default App
- (BOOL)isDefaultApp {
return [self.appName isEqualToString:kFIRDefaultAppName];
}
@end

View File

@ -0,0 +1,58 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class FBLPromise<ValueType>;
NS_ASSUME_NONNULL_BEGIN
/**
* The class makes sure the a single operation (represented by a promise) is performed at a time. If
* there is an ongoing operation, then its existing corresponding promise will be returned instead
* of starting a new operation.
*/
@interface FIRInstallationsSingleOperationPromiseCache<__covariant ResultType> : NSObject
- (instancetype)init NS_UNAVAILABLE;
/**
* The designated initializer.
* @param newOperationHandler The block that must return a new promise representing the
* single-at-a-time operation. The promise should be fulfilled when the operation is completed. The
* factory block will be used to create a new promise when needed.
*/
- (instancetype)initWithNewOperationHandler:
(FBLPromise<ResultType> *_Nonnull (^)(void))newOperationHandler NS_DESIGNATED_INITIALIZER;
/**
* Creates a new promise or returns an existing pending one.
* @return Returns and existing pending promise if exists. If the pending promise does not exist
* then a new one will be created using the `factory` block passed in the initializer. Once the
* pending promise gets resolved, it is removed, so calling the method again will lead to creating
* and caching another promise.
*/
- (FBLPromise<ResultType> *)getExistingPendingOrCreateNewPromise;
/**
* Returns an existing pending promise or `nil`.
* @return Returns an existing pending promise if there is one or `nil` otherwise.
*/
- (nullable FBLPromise<ResultType> *)getExistingPendingPromise;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,75 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsSingleOperationPromiseCache.h"
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
@interface FIRInstallationsSingleOperationPromiseCache <ResultType>()
@property(nonatomic, readonly) FBLPromise *_Nonnull (^newOperationHandler)(void);
@property(nonatomic, nullable) FBLPromise *pendingPromise;
@end
@implementation FIRInstallationsSingleOperationPromiseCache
- (instancetype)initWithNewOperationHandler:
(FBLPromise<id> *_Nonnull (^)(void))newOperationHandler {
if (newOperationHandler == nil) {
[NSException raise:NSInvalidArgumentException
format:@"`newOperationHandler` must not be `nil`."];
}
self = [super init];
if (self) {
_newOperationHandler = [newOperationHandler copy];
}
return self;
}
- (FBLPromise *)getExistingPendingOrCreateNewPromise {
@synchronized(self) {
if (!self.pendingPromise) {
self.pendingPromise = self.newOperationHandler();
self.pendingPromise
.then(^id(id result) {
@synchronized(self) {
self.pendingPromise = nil;
return nil;
}
})
.catch(^void(NSError *error) {
@synchronized(self) {
self.pendingPromise = nil;
}
});
}
return self.pendingPromise;
}
}
- (nullable FBLPromise *)getExistingPendingPromise {
@synchronized(self) {
return self.pendingPromise;
}
}
@end

View File

@ -0,0 +1,35 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
/**
* The enum represent possible states of the installation ID.
*
* WARNING: The enum is stored to Keychain as a part of `FIRInstallationsStoredItem`. Modification
* of it can lead to incompatibility with previous version. Any modification must be evaluated and,
* if it is really needed, the `storageVersion` must be bumped and proper migration code added.
*/
typedef NS_ENUM(NSInteger, FIRInstallationsStatus) {
/** Represents either an initial status when a FIRInstallationsItem instance was created but not
* stored to Keychain or an undefined status (e.g. when the status failed to deserialize).
*/
FIRInstallationStatusUnknown,
/// The Firebase Installation has not yet been registered with FIS.
FIRInstallationStatusUnregistered,
/// The Firebase Installation has successfully been registered with FIS.
FIRInstallationStatusRegistered,
};

View File

@ -0,0 +1,71 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class FBLPromise<ValueType>;
@class FIRInstallationsItem;
@class FIRSecureStorage;
NS_ASSUME_NONNULL_BEGIN
/// The user defaults suite name used to store data.
extern NSString *const kFIRInstallationsStoreUserDefaultsID;
/// The class is responsible for storing and accessing the installations data.
@interface FIRInstallationsStore : NSObject
/**
* The default initializer.
* @param storage The secure storage to save installations data.
* @param accessGroup The Keychain Access Group to store and request the installations data.
*/
- (instancetype)initWithSecureStorage:(FIRSecureStorage *)storage
accessGroup:(nullable NSString *)accessGroup;
/**
* Retrieves existing installation ID if there is.
* @param appID The Firebase(Google) Application ID.
* @param appName The Firebase Application Name.
*
* @return Returns a `FBLPromise` instance. The promise is resolved with a FIRInstallationsItem
* instance if there is a valid installation stored for `appID` and `appName`. The promise is
* rejected with a specific error when the installation has not been found or with another possible
* error.
*/
- (FBLPromise<FIRInstallationsItem *> *)installationForAppID:(NSString *)appID
appName:(NSString *)appName;
/**
* Saves the given installation.
*
* @param installationItem The installation data.
* @return Returns a promise that is resolved with `[NSNull null]` on success.
*/
- (FBLPromise<NSNull *> *)saveInstallation:(FIRInstallationsItem *)installationItem;
/**
* Removes installation data for the given app parameters.
* @param appID The Firebase(Google) Application ID.
* @param appName The Firebase Application Name.
*
* @return Returns a promise that is resolved with `[NSNull null]` on success.
*/
- (FBLPromise<NSNull *> *)removeInstallationForAppID:(NSString *)appID appName:(NSString *)appName;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,125 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsStore.h"
#import <GoogleUtilities/GULUserDefaults.h>
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import "FIRInstallationsErrorUtil.h"
#import "FIRInstallationsItem.h"
#import "FIRInstallationsStoredItem.h"
#import "FIRSecureStorage.h"
NSString *const kFIRInstallationsStoreUserDefaultsID = @"com.firebase.FIRInstallations";
@interface FIRInstallationsStore ()
@property(nonatomic, readonly) FIRSecureStorage *secureStorage;
@property(nonatomic, readonly, nullable) NSString *accessGroup;
@property(nonatomic, readonly) dispatch_queue_t queue;
@property(nonatomic, readonly) GULUserDefaults *userDefaults;
@end
@implementation FIRInstallationsStore
- (instancetype)initWithSecureStorage:(FIRSecureStorage *)storage
accessGroup:(NSString *)accessGroup {
self = [super init];
if (self) {
_secureStorage = storage;
_accessGroup = [accessGroup copy];
_queue = dispatch_queue_create("com.firebase.FIRInstallationsStore", DISPATCH_QUEUE_SERIAL);
NSString *userDefaultsSuiteName = _accessGroup ?: kFIRInstallationsStoreUserDefaultsID;
_userDefaults = [[GULUserDefaults alloc] initWithSuiteName:userDefaultsSuiteName];
}
return self;
}
- (FBLPromise<FIRInstallationsItem *> *)installationForAppID:(NSString *)appID
appName:(NSString *)appName {
NSString *itemID = [FIRInstallationsItem identifierWithAppID:appID appName:appName];
return [self installationExistsForAppID:appID appName:appName]
.then(^id(id result) {
return [self.secureStorage getObjectForKey:itemID
objectClass:[FIRInstallationsStoredItem class]
accessGroup:self.accessGroup];
})
.then(^id(FIRInstallationsStoredItem *_Nullable storedItem) {
if (storedItem == nil) {
return [FIRInstallationsErrorUtil installationItemNotFoundForAppID:appID appName:appName];
}
FIRInstallationsItem *item = [[FIRInstallationsItem alloc] initWithAppID:appID
firebaseAppName:appName];
[item updateWithStoredItem:storedItem];
return item;
});
}
- (FBLPromise<NSNull *> *)saveInstallation:(FIRInstallationsItem *)installationItem {
FIRInstallationsStoredItem *storedItem = [installationItem storedItem];
NSString *identifier = [installationItem identifier];
return
[self.secureStorage setObject:storedItem forKey:identifier accessGroup:self.accessGroup].then(
^id(id result) {
return [self setInstallationExists:YES forItemWithIdentifier:identifier];
});
}
- (FBLPromise<NSNull *> *)removeInstallationForAppID:(NSString *)appID appName:(NSString *)appName {
NSString *identifier = [FIRInstallationsItem identifierWithAppID:appID appName:appName];
return [self.secureStorage removeObjectForKey:identifier accessGroup:self.accessGroup].then(
^id(id result) {
return [self setInstallationExists:NO forItemWithIdentifier:identifier];
});
}
#pragma mark - User defaults
- (FBLPromise<NSNull *> *)installationExistsForAppID:(NSString *)appID appName:(NSString *)appName {
NSString *identifier = [FIRInstallationsItem identifierWithAppID:appID appName:appName];
return [FBLPromise onQueue:self.queue
do:^id _Nullable {
return [[self userDefaults] objectForKey:identifier] != nil
? [NSNull null]
: [FIRInstallationsErrorUtil
installationItemNotFoundForAppID:appID
appName:appName];
}];
}
- (FBLPromise<NSNull *> *)setInstallationExists:(BOOL)exists
forItemWithIdentifier:(NSString *)identifier {
return [FBLPromise onQueue:self.queue
do:^id _Nullable {
if (exists) {
[[self userDefaults] setBool:YES forKey:identifier];
} else {
[[self userDefaults] removeObjectForKey:identifier];
}
return [NSNull null];
}];
}
@end

View File

@ -0,0 +1,58 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The enum represent possible states of the installation auth token.
*
* WARNING: The enum is stored to Keychain as a part of `FIRInstallationsStoredAuthToken`.
* Modification of it can lead to incompatibility with previous version. Any modification must be
* evaluated and, if it is really needed, the `storageVersion` must be bumped and proper migration
* code added.
*/
typedef NS_ENUM(NSInteger, FIRInstallationsAuthTokenStatus) {
/// An initial status or an undefined value.
FIRInstallationsAuthTokenStatusUnknown,
/// The auth token has been received from the server.
FIRInstallationsAuthTokenStatusTokenReceived
};
/**
* This class serializes and deserializes the installation data into/from `NSData` to be stored in
* Keychain. This class is primarily used by `FIRInstallationsStore`. It is also used on the logic
* level as a data object (see `FIRInstallationsItem.authToken`).
*
* WARNING: Modification of the class properties can lead to incompatibility with the stored data
* encoded by the previous class versions. Any modification must be evaluated and, if it is really
* needed, the `storageVersion` must be bumped and proper migration code added.
*/
@interface FIRInstallationsStoredAuthToken : NSObject <NSSecureCoding, NSCopying>
@property FIRInstallationsAuthTokenStatus status;
/// The token that can be used to authorize requests to Firebase backend.
@property(nullable, copy) NSString *token;
/// The date when the auth token expires.
@property(nullable, copy) NSDate *expirationDate;
/// The version of local storage.
@property(nonatomic, readonly) NSInteger storageVersion;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,77 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsStoredAuthToken.h"
#import "FIRInstallationsLogger.h"
NSString *const kFIRInstallationsStoredAuthTokenStatusKey = @"status";
NSString *const kFIRInstallationsStoredAuthTokenTokenKey = @"token";
NSString *const kFIRInstallationsStoredAuthTokenExpirationDateKey = @"expirationDate";
NSString *const kFIRInstallationsStoredAuthTokenStorageVersionKey = @"storageVersion";
NSInteger const kFIRInstallationsStoredAuthTokenStorageVersion = 1;
@implementation FIRInstallationsStoredAuthToken
- (NSInteger)storageVersion {
return kFIRInstallationsStoredAuthTokenStorageVersion;
}
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
FIRInstallationsStoredAuthToken *clone = [[FIRInstallationsStoredAuthToken alloc] init];
clone.status = self.status;
clone.token = [self.token copy];
clone.expirationDate = self.expirationDate;
return clone;
}
- (void)encodeWithCoder:(nonnull NSCoder *)aCoder {
[aCoder encodeInteger:self.status forKey:kFIRInstallationsStoredAuthTokenStatusKey];
[aCoder encodeObject:self.token forKey:kFIRInstallationsStoredAuthTokenTokenKey];
[aCoder encodeObject:self.expirationDate
forKey:kFIRInstallationsStoredAuthTokenExpirationDateKey];
[aCoder encodeInteger:self.storageVersion
forKey:kFIRInstallationsStoredAuthTokenStorageVersionKey];
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder {
NSInteger storageVersion =
[aDecoder decodeIntegerForKey:kFIRInstallationsStoredAuthTokenStorageVersionKey];
if (storageVersion > kFIRInstallationsStoredAuthTokenStorageVersion) {
FIRLogWarning(kFIRLoggerInstallations,
kFIRInstallationsMessageCodeAuthTokenCoderVersionMismatch,
@"FIRInstallationsStoredAuthToken was encoded by a newer coder version %ld. "
@"Current coder version is %ld. Some auth token data may be lost.",
(long)storageVersion, (long)kFIRInstallationsStoredAuthTokenStorageVersion);
}
FIRInstallationsStoredAuthToken *object = [[FIRInstallationsStoredAuthToken alloc] init];
object.status = [aDecoder decodeIntegerForKey:kFIRInstallationsStoredAuthTokenStatusKey];
object.token = [aDecoder decodeObjectOfClass:[NSString class]
forKey:kFIRInstallationsStoredAuthTokenTokenKey];
object.expirationDate =
[aDecoder decodeObjectOfClass:[NSDate class]
forKey:kFIRInstallationsStoredAuthTokenExpirationDateKey];
return object;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end

View File

@ -0,0 +1,51 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "FIRInstallationsStatus.h"
@class FIRInstallationsStoredAuthToken;
@class FIRInstallationsStoredIIDCheckin;
NS_ASSUME_NONNULL_BEGIN
/**
* The class is supposed to be used by `FIRInstallationsStore` only. It is required to
* serialize/deserialize the installation data into/from `NSData` to be stored in Keychain.
*
* WARNING: Modification of the class properties can lead to incompatibility with the stored data
* encoded by the previous class versions. Any modification must be evaluated and, if it is really
* needed, the `storageVersion` must be bumped and proper migration code added.
*/
@interface FIRInstallationsStoredItem : NSObject <NSSecureCoding>
/// A stable identifier that uniquely identifies the app instance.
@property(nonatomic, copy, nullable) NSString *firebaseInstallationID;
/// The `refreshToken` is used to authorize the auth token requests.
@property(nonatomic, copy, nullable) NSString *refreshToken;
@property(nonatomic, nullable) FIRInstallationsStoredAuthToken *authToken;
@property(nonatomic) FIRInstallationsStatus registrationStatus;
/// Instance ID default auth token imported from IID store as a part of IID migration.
@property(nonatomic, nullable) NSString *IIDDefaultToken;
/// The version of local storage.
@property(nonatomic, readonly) NSInteger storageVersion;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,80 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsStoredItem.h"
#import "FIRInstallationsLogger.h"
#import "FIRInstallationsStoredAuthToken.h"
NSString *const kFIRInstallationsStoredItemFirebaseInstallationIDKey = @"firebaseInstallationID";
NSString *const kFIRInstallationsStoredItemRefreshTokenKey = @"refreshToken";
NSString *const kFIRInstallationsStoredItemAuthTokenKey = @"authToken";
NSString *const kFIRInstallationsStoredItemRegistrationStatusKey = @"registrationStatus";
NSString *const kFIRInstallationsStoredItemIIDDefaultTokenKey = @"IIDDefaultToken";
NSString *const kFIRInstallationsStoredItemStorageVersionKey = @"storageVersion";
NSInteger const kFIRInstallationsStoredItemStorageVersion = 1;
@implementation FIRInstallationsStoredItem
- (NSInteger)storageVersion {
return kFIRInstallationsStoredItemStorageVersion;
}
- (void)encodeWithCoder:(nonnull NSCoder *)aCoder {
[aCoder encodeObject:self.firebaseInstallationID
forKey:kFIRInstallationsStoredItemFirebaseInstallationIDKey];
[aCoder encodeObject:self.refreshToken forKey:kFIRInstallationsStoredItemRefreshTokenKey];
[aCoder encodeObject:self.authToken forKey:kFIRInstallationsStoredItemAuthTokenKey];
[aCoder encodeInteger:self.registrationStatus
forKey:kFIRInstallationsStoredItemRegistrationStatusKey];
[aCoder encodeObject:self.IIDDefaultToken forKey:kFIRInstallationsStoredItemIIDDefaultTokenKey];
[aCoder encodeInteger:self.storageVersion forKey:kFIRInstallationsStoredItemStorageVersionKey];
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder {
NSInteger storageVersion =
[aDecoder decodeIntegerForKey:kFIRInstallationsStoredItemStorageVersionKey];
if (storageVersion > self.storageVersion) {
FIRLogWarning(kFIRLoggerInstallations,
kFIRInstallationsMessageCodeInstallationCoderVersionMismatch,
@"FIRInstallationsStoredItem was encoded by a newer coder version %ld. Current "
@"coder version is %ld. Some installation data may be lost.",
(long)storageVersion, (long)kFIRInstallationsStoredItemStorageVersion);
}
FIRInstallationsStoredItem *item = [[FIRInstallationsStoredItem alloc] init];
item.firebaseInstallationID =
[aDecoder decodeObjectOfClass:[NSString class]
forKey:kFIRInstallationsStoredItemFirebaseInstallationIDKey];
item.refreshToken = [aDecoder decodeObjectOfClass:[NSString class]
forKey:kFIRInstallationsStoredItemRefreshTokenKey];
item.authToken = [aDecoder decodeObjectOfClass:[FIRInstallationsStoredAuthToken class]
forKey:kFIRInstallationsStoredItemAuthTokenKey];
item.registrationStatus =
[aDecoder decodeIntegerForKey:kFIRInstallationsStoredItemRegistrationStatusKey];
item.IIDDefaultToken =
[aDecoder decodeObjectOfClass:[NSString class]
forKey:kFIRInstallationsStoredItemIIDDefaultTokenKey];
return item;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end

View File

@ -0,0 +1,120 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class FIRApp;
@class FIRInstallationsAuthTokenResult;
NS_ASSUME_NONNULL_BEGIN
/** A notification with this name is sent each time an installation is created or deleted. */
FOUNDATION_EXPORT const NSNotificationName FIRInstallationIDDidChangeNotification;
/** `userInfo` key for the `FirebaseApp.name` in `FIRInstallationIDDidChangeNotification`. */
FOUNDATION_EXPORT NSString *const kFIRInstallationIDDidChangeNotificationAppNameKey;
/**
* An installation ID handler block.
* @param identifier The installation ID string if exists or `nil` otherwise.
* @param error The error when `identifier == nil` or `nil` otherwise.
*/
typedef void (^FIRInstallationsIDHandler)(NSString *__nullable identifier,
NSError *__nullable error)
NS_SWIFT_NAME(InstallationsIDHandler);
/**
* An authorization token handler block.
* @param tokenResult An instance of `InstallationsAuthTokenResult` in case of success or `nil`
* otherwise.
* @param error The error when `tokenResult == nil` or `nil` otherwise.
*/
typedef void (^FIRInstallationsTokenHandler)(
FIRInstallationsAuthTokenResult *__nullable tokenResult, NSError *__nullable error)
NS_SWIFT_NAME(InstallationsTokenHandler);
/**
* The class provides API for Firebase Installations.
* Each configured `FirebaseApp` has a corresponding single instance of `Installations`.
* An instance of the class provides access to the installation info for the `FirebaseApp` as well
* as the ability to delete it. A Firebase Installation is unique by `FirebaseApp.name` and
* `FirebaseApp.options.googleAppID` .
*/
NS_SWIFT_NAME(Installations)
@interface FIRInstallations : NSObject
- (instancetype)init NS_UNAVAILABLE;
/**
* Returns a default instance of `Installations`.
* @returns An instance of `Installations` for `FirebaseApp.defaultApp().
* @throw Throws an exception if the default app is not configured yet or required `FirebaseApp`
* options are missing.
*/
+ (FIRInstallations *)installations NS_SWIFT_NAME(installations());
/**
* Returns an instance of `Installations` for an application.
* @param application A configured `FirebaseApp` instance.
* @returns An instance of `Installations` corresponding to the passed application.
* @throw Throws an exception if required `FirebaseApp` options are missing.
*/
+ (FIRInstallations *)installationsWithApp:(FIRApp *)application NS_SWIFT_NAME(installations(app:));
/**
* The method creates or retrieves an installation ID. The installation ID is a stable identifier
* that uniquely identifies the app instance. NOTE: If the application already has an existing
* FirebaseInstanceID then the InstanceID identifier will be used.
* @param completion A completion handler which is invoked when the operation completes. See
* `InstallationsIDHandler` for additional details.
*/
- (void)installationIDWithCompletion:(FIRInstallationsIDHandler)completion;
/**
* Retrieves (locally if it exists or from the server) a valid authorization token. An existing
* token may be invalidated or expired, so it is recommended to fetch the auth token before each
* server request. The method does the same as `Installations.authTokenForcingRefresh(:,
* completion:)` with forcing refresh `NO`.
* @param completion A completion handler which is invoked when the operation completes. See
* `InstallationsTokenHandler` for additional details.
*/
- (void)authTokenWithCompletion:(FIRInstallationsTokenHandler)completion;
/**
* Retrieves (locally or from the server depending on `forceRefresh` value) a valid authorization
* token. An existing token may be invalidated or expire, so it is recommended to fetch the auth
* token before each server request. This method should be used with `forceRefresh == YES` when e.g.
* a request with the previously fetched auth token failed with "Not Authorized" error.
* @param forceRefresh If `YES` then the locally cached auth token will be ignored and a new one
* will be requested from the server. If `NO`, then the locally cached auth token will be returned
* if exists and has not expired yet.
* @param completion A completion handler which is invoked when the operation completes. See
* `InstallationsTokenHandler` for additional details.
*/
- (void)authTokenForcingRefresh:(BOOL)forceRefresh
completion:(FIRInstallationsTokenHandler)completion;
/**
* Deletes all the installation data including the unique identifier, auth tokens and
* all related data on the server side. A network connection is required for the method to
* succeed. If fails, the existing installation data remains untouched.
* @param completion A completion handler which is invoked when the operation completes. `error ==
* nil` indicates success.
*/
- (void)deleteWithCompletion:(void (^)(NSError *__nullable error))completion;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,33 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** The class represents a result of the auth token request. */
NS_SWIFT_NAME(InstallationsAuthTokenResult)
@interface FIRInstallationsAuthTokenResult : NSObject
/** The authorization token string. */
@property(nonatomic, readonly) NSString *authToken;
/** The auth token expiration date. */
@property(nonatomic, readonly) NSDate *expirationDate;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,34 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
extern NSString *const kFirebaseInstallationsErrorDomain;
typedef NS_ENUM(NSUInteger, FIRInstallationsErrorCode) {
/** Unknown error. See `userInfo` for details. */
FIRInstallationsErrorCodeUnknown = 0,
/** Keychain error. See `userInfo` for details. */
FIRInstallationsErrorCodeKeychain = 1,
/** Server unreachable. A network error or server is unavailable. See `userInfo` for details. */
FIRInstallationsErrorCodeServerUnreachable = 2,
/** FirebaseApp configuration issues e.g. invalid GMP-App-ID, etc. See `userInfo` for details. */
FIRInstallationsErrorCodeInvalidConfiguration = 3,
} NS_SWIFT_NAME(InstallationsErrorCode);

View File

@ -0,0 +1,19 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
FOUNDATION_EXPORT const char *const FIRInstallationsVersionStr;

View File

@ -0,0 +1,20 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallations.h"
#import "FIRInstallationsAuthTokenResult.h"
#import "FIRInstallationsErrors.h"
#import "FIRInstallationsVersion.h"

View File

@ -0,0 +1,35 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// Helper functions to access Keychain.
@interface FIRInstallationsKeychainUtils : NSObject
+ (nullable NSData *)getItemWithQuery:(NSDictionary *)query
error:(NSError *_Nullable *_Nullable)outError;
+ (BOOL)setItem:(NSData *)item
withQuery:(NSDictionary *)query
error:(NSError *_Nullable *_Nullable)outError;
+ (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,107 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstallationsKeychainUtils.h"
#import "FIRInstallationsErrorUtil.h"
@implementation FIRInstallationsKeychainUtils
+ (nullable NSData *)getItemWithQuery:(NSDictionary *)query
error:(NSError *_Nullable *_Nullable)outError {
NSMutableDictionary *mutableQuery = [query mutableCopy];
mutableQuery[(__bridge id)kSecReturnData] = @YES;
mutableQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
CFDataRef result = NULL;
OSStatus status =
SecItemCopyMatching((__bridge CFDictionaryRef)mutableQuery, (CFTypeRef *)&result);
if (status == errSecSuccess && result != NULL) {
if (outError) {
*outError = nil;
}
return (__bridge_transfer NSData *)result;
}
if (status == errSecItemNotFound) {
if (outError) {
*outError = nil;
}
} else {
if (outError) {
*outError = [FIRInstallationsErrorUtil keychainErrorWithFunction:@"SecItemCopyMatching"
status:status];
}
}
return nil;
}
+ (BOOL)setItem:(NSData *)item
withQuery:(NSDictionary *)query
error:(NSError *_Nullable *_Nullable)outError {
NSData *existingItem = [self getItemWithQuery:query error:outError];
if (outError && *outError) {
return NO;
}
NSMutableDictionary *mutableQuery = [query mutableCopy];
mutableQuery[(__bridge id)kSecAttrAccessible] =
(__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
OSStatus status;
if (!existingItem) {
mutableQuery[(__bridge id)kSecValueData] = item;
status = SecItemAdd((__bridge CFDictionaryRef)mutableQuery, NULL);
} else {
NSDictionary *attributes = @{(__bridge id)kSecValueData : item};
status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes);
}
if (status == noErr) {
if (outError) {
*outError = nil;
}
return YES;
}
NSString *function = existingItem ? @"SecItemUpdate" : @"SecItemAdd";
if (outError) {
*outError = [FIRInstallationsErrorUtil keychainErrorWithFunction:function status:status];
}
return NO;
}
+ (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError {
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
if (status == noErr || status == errSecItemNotFound) {
if (outError) {
*outError = nil;
}
return YES;
}
if (outError) {
*outError = [FIRInstallationsErrorUtil keychainErrorWithFunction:@"SecItemDelete"
status:status];
}
return NO;
}
@end

View File

@ -0,0 +1,71 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class FBLPromise<ValueType>;
NS_ASSUME_NONNULL_BEGIN
/// The class provides a convenient abstraction on top of the iOS Keychain API to save data.
@interface FIRSecureStorage : NSObject
/**
* Get an object by key.
* @param key The key.
* @param objectClass The expected object class required by `NSSecureCoding`.
* @param accessGroup The Keychain Access Group.
*
* @return Returns a promise. It is resolved with an object stored by key if exists. It is resolved
* with `nil` when the object not found. It fails on a Keychain error.
*/
- (FBLPromise<id<NSSecureCoding>> *)getObjectForKey:(NSString *)key
objectClass:(Class)objectClass
accessGroup:(nullable NSString *)accessGroup;
/**
* Saves the given object by the given key.
* @param object The object to store.
* @param key The key to store the object. If there is an existing object by the key, it will be
* overridden.
* @param accessGroup The Keychain Access Group.
*
* @return Returns which is resolved with `[NSNull null]` on success.
*/
- (FBLPromise<NSNull *> *)setObject:(id<NSSecureCoding>)object
forKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup;
/**
* Removes the object by the given key.
* @param key The key to store the object. If there is an existing object by the key, it will be
* overridden.
* @param accessGroup The Keychain Access Group.
*
* @return Returns which is resolved with `[NSNull null]` on success.
*/
- (FBLPromise<NSNull *> *)removeObjectForKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup;
#if TARGET_OS_OSX
/// If not `nil`, then only this keychain will be used to save and read data (see
/// `kSecMatchSearchList` and `kSecUseKeychain`. It is mostly intended to be used by unit tests.
@property(nonatomic, nullable) SecKeychainRef keychainRef;
#endif // TARGET_OSX
@end
NS_ASSUME_NONNULL_END

Some files were not shown because too many files have changed in this diff Show More