Detecting if app is ad hoc - ios

I have this URL in my server that is used for testing and another one that is used for production.
At some point in my code I did this:
#ifdef DEBUG
static NSString * const url = "http://sandbox.myserver.com";
#else
static NSString * const url = "http://live.myserver.com";
#endif
this works fine when I am debugging the app on Xcode but if I send ad hoc versions of my app to beta testers it will fail. Ad hoc apps will use the production URL instead of the sandbox one.
What is the correct way of doing this test?

in short:
var data = String($.NSString.stringWithContentsOfFileEncodingError($.NSBundle.mainBundle.pathForResourceOfType('embedded', 'mobileprovision'), $.NSISOLatin1StringEncoding, null));
data = data.slice(data.indexOf('<plist'));
data = data.slice(0, data.indexOf('</plist>')+8);
data = $.NSString.stringWithString(data).dataUsingEncoding($.NSISOLatin1StringEncoding);
var mobileProvision = $.NSPropertyListSerialization.propertyListWithDataOptionsFormatError(data, $.NSPropertyListImmutable, null, null);
if (mobileProvision.valueForKey('ProvisionedDevices')) {
res.debug = 'adhoc';
} else {
res.debug = false;
}

Related

Deep Link does not contain valid required params - Flutter with Firebase Dynamic Link

I am using Firebase dynamic link in my flutter app. I am allowing user to generate dynamic link in app that can be shared via sms/email/whatsApp etc etc.
This link is working fine for Android but for iOS i am getting this exception.
Thanks in advance.
Below is my Xcode log.
[Firebase/Analytics][I-ACS023001] Deep Link does not contain valid required params. URL params: {
"_cpb" = 1;
"_cpt" = cpit;
"_fpb" = "XXAHEJ4DGgVlbXXXXX==";
"_iumchkactval" = 1;
"_iumenbl" = 1;
"_osl" = "https://app.XXXXXXX.com/PPZbgKsKpKvukDWZ8";
"_plt" = 1400;
"_uit" = 1400;
amv = 1;
apn = "com.xxx.xxxx";
cid = 000000;
ibi = "com.xxx.xxxx";
imv = 1;
isi = X4967XXXXX;
link = "https://app.xxxxx.com/data?userid=Bzhm1TScavV2&refcode=3DWIN11329206";
}
below are my firebase dynamic link details
Link name
Invite Friends
Deep link
https://app.xxxxx.com/data?userid=Bzhm1TScavV2&refcode=WIN11329206
Android app
com.xxx.xxxx
iOS app
com.xxx.xxxx
Long Dynamic Link
https://app.xxxxx.com/?link=https://app.xxxxx.com/data?userid%3DBzhm1TScavV2%26refcode%3DWIN11329206&apn=com.xxx.xxxx&isi=X4967XXXXX&ibi= com.xxx.xxxx
Short Dynamic Link
https://app.xxxxx.com/invitefriends
This issue seems to be present on older version of firebase_dynamic_links as discussed on this issue ticket. A workaround for this issue is to use app_links when the link is launched in iOS.
import 'dart:io' show Platform;
...
if (Platform.isIOS) {
// Handle link using app_links in iOS
initDynamicLinksIOS();
}
else {
// Use the firebase_dynamic_links per usual
initDynamicLinksAndroid();
}
Then use app_links to handle the deep link in iOS.
initDynamicLinkIOS() async {
final _appLinks = AppLinks(
// Called when a new uri has been redirected to the app
onAppLink: (Uri deepLink) async {
final PendingDynamicLinkData data =
await FirebaseDynamicLinks.instance.getDynamicLink(deepLink);
deepLink = data?.link;
if (deepLink != null) {
debugPrint('$deepLink');
// Handle the deep link
}
},
);
}

iOS push notifications and Testflight using ..p8 certificate and apn

I'm trying to send Push Notifications to my ios app. They work fine for the app I download via the App store, but they don't work when I install a beta version using Testflight. This sort of defeats the purpose, as I want to test out new types of notifications and do the right thing on the app.
I think I'm doing all the right things:
Register for notifications on the app and get a device token
send the device token to my server
send the notification to the device using APNS in node.
The problem is that when I send the notification from the server to an app downloaded from Testflight, I get BadDeviceToken error. From the App Store, works perfectly.
The code in Node looks something like this:
let util = require('util');
let apn = require('apn');
class PushNotification {
constructor(production) {
production = !!production;
this.apnProvider = new apn.Provider({
token: {
key: './apns.p8', // Path to the key p8 file
keyId: 'xxxxxxxxx', // The Key ID of the p8 file (available at https://developer.apple.com/account/ios/certificate/key)
teamId: 'YYYYYYYY' // The Team ID of your Apple Developer Account (available at https://developer.apple.com/account/#/membership/)
},
production: production // Set to true if sending a notification to a production iOS app
});
if (production) {
process.env.NODE_ENV = "production";
}
}
sendNotificationApple(deviceToken, alert, payload, badge) {
if (deviceToken && (alert || badge)) {
let note = new apn.Notification();
note.topic = 'com.xxx.xxx';
note.expiry = Math.floor(Date.now() / 1000) + 3600 * 24 * 2; // Expires 2 days from now
if (badge != undefined && badge != -1) {
note.badge = badge;
}
note.alert = alert;
if (payload) {
note.payload = payload;
}
this.apnProvider.send(note, deviceToken).then(function (result) {
console.log(util.inspect(result, false, null));
});
}
}
}

User id in iOS to store profiles and check IAP

My app has in-app currency and non-consumable products, it stores user profiles and posts values to leaderboards on my server.
In Android (pure java) I have LVL user ID - it is unique for pair developer-customer, so I easily manage user profiles on all his devices, and I can distinguish between devices using IMEI or Android ID.
In Windows/Windows Phone (monogame) I have LiveID, but devices have no id except self-generated UUID for statistics/ads. Can't be sure it persists reinstalls and updates.
And what about iOS (and maybe OSX) (Xamarin.iOS/monogame)?
As far as I remember in iOS was device id, but then api was deprecated.
What do you use as device/user id?
Maybe there is some user-unique-id that StoreKit has behind the scenes?
Or something related to cloud id, to distinguish users, not devices?
If none is available - is there a way to keep random UUID persistent on device, even if user reinstalls app?
When Apple removed the UUID, they provided the identifierForVendor method (In UIDevice) to replace it. It provides a UUID that is unique for you (the developer) for a particular device. I can't tell you how to call that from xamarin, but would assume it's possible.
If you want something that will persist across app deletes you could create your own UUID and save it to the keychain. You can use app groups to have a shared keychain for all of your apps, and keychain entries DO persist if you delete and reinstall an app.
Tertium: Here is the example code (tested). If you store it in cloud you can use it on all user's devices.
void SaveValueToKeychain(string key, String value)
{
var s = new SecRecord(SecKind.GenericPassword)
{
ValueData = NSData.FromString(value),
Generic = NSData.FromString(key),
Invisible = true,
CreationDate = NSDate.Now
};
var err = SecKeyChain.Add(s);
}
public String GetValueFromKeychain(string key)
{
String ret = null;
SecStatusCode res;
var rec = new SecRecord(SecKind.GenericPassword)
{
Generic = NSData.FromString(key)
};
var match = SecKeyChain.QueryAsRecord(rec, out res);
if (match != null)
{
ret = match.ValueData.ToString();
}
return ret;
}
...
string UUID_KEY = "com.my.app";
String id = GetValueFromKeychain(UUID_KEY);
if (id == null)
{
Guid g = Guid.NewGuid();
String gs = g.ToString().Replace("-", "");
Debug.Write("ID not found, generating: " + gs);
SaveValueToKeychain(UUID_KEY, gs);
id = GetValueFromKeychain(UUID_KEY);
}
else
{
Debug.Write("ID found: " + id);
}

How to tell at runtime whether an iOS app is running through a TestFlight Beta install

Is it possible to detect at runtime that an application has been installed through TestFlight Beta (submitted through iTunes Connect) vs the App Store? You can submit a single app bundle and have it available through both. Is there an API that can detect which way it was installed? Or does the receipt contain information that allows this to be determined?
For an application installed through TestFlight Beta the receipt file is named StoreKit/sandboxReceipt vs the usual StoreKit/receipt. Using [NSBundle appStoreReceiptURL] you can look for sandboxReceipt at the end of the URL.
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSString *receiptURLString = [receiptURL path];
BOOL isRunningTestFlightBeta = ([receiptURLString rangeOfString:#"sandboxReceipt"].location != NSNotFound);
Note that sandboxReceipt is also the name of the receipt file when running builds locally and for builds run in the simulator.
Swift Version:
let isTestFlight = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
Based on combinatorial's answer I created the following SWIFT helper class. With this class you can determine if it's a debug, testflight or appstore build.
enum AppConfiguration {
case Debug
case TestFlight
case AppStore
}
struct Config {
// This is private because the use of 'appConfiguration' is preferred.
private static let isTestFlight = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
// This can be used to add debug statements.
static var isDebug: Bool {
#if DEBUG
return true
#else
return false
#endif
}
static var appConfiguration: AppConfiguration {
if isDebug {
return .Debug
} else if isTestFlight {
return .TestFlight
} else {
return .AppStore
}
}
}
We use these methods in our project to supply different tracking id's or connection string per environment:
func getURL(path: String) -> String {
switch (Config.appConfiguration) {
case .Debug:
return host + "://" + debugBaseUrl + path
default:
return host + "://" + baseUrl + path
}
}
OR:
static var trackingKey: String {
switch (Config.appConfiguration) {
case .Debug:
return debugKey
case .TestFlight:
return testflightKey
default:
return appstoreKey
}
}
UPDATE 05-02-2016:
A prerequisite to use a preprocessor macro like #if DEBUG is to set some Swift Compiler Custom Flags. More information in this answer: https://stackoverflow.com/a/24112024/639227
Modern Swift version, which accounts for Simulators (based on accepted answer):
private func isSimulatorOrTestFlight() -> Bool {
guard let path = Bundle.main.appStoreReceiptURL?.path else {
return false
}
return path.contains("CoreSimulator") || path.contains("sandboxReceipt")
}
I use extension Bundle+isProduction on Swift 5.2:
import Foundation
extension Bundle {
var isProduction: Bool {
#if DEBUG
return false
#else
guard let path = self.appStoreReceiptURL?.path else {
return true
}
return !path.contains("sandboxReceipt")
#endif
}
}
Then:
if Bundle.main.isProduction {
// do something
}
There is one way that I use it for my projects. Here are the steps.
In Xcode, go to the the project settings (project, not target) and add "beta" configuration to the list:
Then you need to create new scheme that will run project in "beta" configuration. To create scheme go here:
Name this scheme whatever you want. The you should edit settings for this scheme. To do this, tap here:
Select Archive tab where you can select Build configuration
Then you need to add a key Config with value $(CONFIGURATION) the projects info property list like this:
Then its just the matter what you need in code to do something specific to beta build:
let config = Bundle.main.object(forInfoDictionaryKey: "Config") as! String
if config == "Debug" {
// app running in debug configuration
}
else if config == "Release" {
// app running in release configuration
}
else if config == "Beta" {
// app running in beta configuration
}

How to set FBPLISTAppIDKey and FBPLISTAppSecretKey with FBTestSession

I am getting an exception using FBTestSession when using FBTestSession *fbSession = [FBTestSession sessionWithSharedUserWithPermissions:#[#"email"]];
If you look at the code below you can see Facebook decided to load app settings from NSProcessInfo:
https://github.com/facebook/facebook-ios-sdk/blob/master/src/FBTestSession.m#L506
NSDictionary *environment = [[NSProcessInfo processInfo] environment];
NSString *appID = [environment objectForKey:FBPLISTAppIDKey];
NSString *appSecret = [environment objectForKey:FBPLISTAppSecretKey];
if (!appID || !appSecret || appID.length == 0 || appSecret.length == 0) {
[[NSException exceptionWithName:FBInvalidOperationException
reason:
#"FBTestSession: Missing App ID or Secret; ensure that you have an .xcconfig file at:\n"
#"\t${REPO_ROOT}/src/tests/TestAppIdAndSecret.xcconfig\n"
#"containing your unit-testing Facebook Application's ID and Secret in this format:\n"
#"\tIOS_SDK_TEST_APP_ID = // your app ID, e.g.: 1234567890\n"
#"\tIOS_SDK_TEST_APP_SECRET = // your app secret, e.g.: 1234567890abcdef\n"
#"To create a Facebook AppID, visit https://developers.facebook.com/apps"
userInfo:nil]
raise];
}
The problem is, I'm not able to figure out how to set FBPLISTAppIDKey and FBPLISTAppSecretKey correctly and there is no other setter to use.
You need to add them as environment variables in your scheme.
{scheme} >> Run >> Arguments (Tab) >> add the test app id and test app secret.

Resources