Hi,
I have a question about On-Demand Resources, I tried to put some bundles into the Initial Install Tags and Download Only On Demand, and I uploaded the build to TestFlight, then I tried to make 2 builds, and the sample is like this:
Build A:
Initial Install Tags have 100 Tags.
Download Only On Demand have 5 Tags.
Build B:
all contents of Initial Install Tags are the same as in Build A.
Download Only On Demand are the same as Build A but I added 5 more tags, so the total is 10 tags, 5 tags that are the same as Build A and 5 new tags.
Then I tried to download Build A for the first time which is in TestFlight, and it runs normally, Initial Install Tags are downloaded when the main app is downloaded and Download Only On Demand is downloaded when I request the tag.
However, when I tried to update to Build B which is in TestFlight, why are the Initial Install Tags deleted? and why should it be downloaded via request? not when the main app is downloaded? has anyone ever experienced something like this?
Thanks!
Delve into the world of built-in app and system services available to developers. Discuss leveraging these services to enhance your app's functionality and user experience.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I'm currently integrating Apple Pay with my payment provider, and I'm encountering a signature validation error during the payment flow.
Here's the setup:
I’ve verified that my Merchant Certificate is valid, and I'm able to initialize the Apple Pay session without any issues. Also this curl works fine
The Payment Processing Certificate was created by my PSP.
PSP claims that the payment token signature is invalid during the transaction phase, which prevents payment completion.
The parsed signature starts like this
0:d=0 hl=2 l=inf cons: SEQUENCE
2:d=1 hl=2 l= 9 prim: OBJECT :pkcs7-signedData
13:d=1 hl=2 l=inf cons: cont [ 0 ]
15:d=2 hl=2 l=inf cons: SEQUENCE
17:d=3 hl=2 l= 1 prim: INTEGER :01
20:d=3 hl=2 l= 13 cons: SET
22:d=4 hl=2 l= 11 cons: SEQUENCE
24:d=5 hl=2 l= 9 prim: OBJECT :sha256
35:d=3 hl=2 l=inf cons: SEQUENCE
37:d=4 hl=2 l= 9 prim: OBJECT :pkcs7-data
48:d=4 hl=2 l= 0 prim: EOC
50:d=3 hl=2 l=inf cons: cont [ 0 ]
52:d=4 hl=4 l= 995 cons: SEQUENCE
56:d=5 hl=4 l= 904 cons: SEQUENCE
60:d=6 hl=2 l= 3 cons: cont [ 0 ]
62:d=7 hl=2 l= 1 prim: INTEGER :02
65:d=6 hl=2 l= 8 prim: INTEGER :16634C8B0E305717
75:d=6 hl=2 l= 10 cons: SEQUENCE
77:d=7 hl=2 l= 8 prim: OBJECT :ecdsa-with-SHA256
87:d=6 hl=2 l= 122 cons: SEQUENCE
89:d=7 hl=2 l= 46 cons: SET
91:d=8 hl=2 l= 44 cons: SEQUENCE
93:d=9 hl=2 l= 3 prim: OBJECT :commonName
98:d=9 hl=2 l= 37 prim: UTF8STRING :Apple Application Integration CA - G3
I'm looking for guidance on what could be causing this signature failure.
Does anyone know what else I can check regarding the merchant or payment processing certificates, private keys, or key usage that might cause Apple Pay signature validation to fail, even if the session initializes successfully? Domains are also verified.
Any help or suggestions would be greatly appreciated.
My question is simple, I do not have much experience in writing swift code, I am only doing it to create a small executable that I can call from my python application which completes Subcription Management.
I was hoping someone with more experience could point out my flaws along with giving me tips on how to verify that the check is working for my applicaiton. Any inight is appreciated, thank you.
import Foundation
import StoreKit
class SubscriptionValidator {
static func getReceiptURL() -> URL? {
guard let appStoreReceiptURL = Bundle.main.appStoreReceiptURL else {
print("No receipt found.")
return nil
}
return appStoreReceiptURL
}
static func validateReceipt() -> Bool {
guard let receiptURL = getReceiptURL(),
let receiptData = try? Data(contentsOf: receiptURL) else {
print("Could not read receipt.")
return false
}
let receiptString = receiptData.base64EncodedString()
let validationResult = sendReceiptToApple(receiptString: receiptString)
return validationResult
}
static func sendReceiptToApple(receiptString: String) -> Bool {
let isSandbox = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
let urlString = isSandbox ? "https://sandbox.itunes.apple.com/verifyReceipt" : "https://buy.itunes.apple.com/verifyReceipt"
let url = URL(string: urlString)!
let requestData: [String: Any] = [
"receipt-data": receiptString,
"password": "0b7f88907b77443997838c72be52f5fc"
]
guard let requestBody = try? JSONSerialization.data(withJSONObject: requestData) else {
print("Error creating request body.")
return false
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = requestBody
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let semaphore = DispatchSemaphore(value: 0)
var isValid = false
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil,
let jsonResponse = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let status = jsonResponse["status"] as? Int else {
print("Receipt validation failed.")
semaphore.signal()
return
}
if status == 0, let receipt = jsonResponse["receipt"] as? [String: Any],
let inApp = receipt["in_app"] as? [[String: Any]] {
for purchase in inApp {
if let expiresDateMS = purchase["expires_date_ms"] as? String,
let expiresDate = Double(expiresDateMS) {
let expiryDate = Date(timeIntervalSince1970: expiresDate / 1000.0)
if expiryDate > Date() {
isValid = true
}
}
}
}
semaphore.signal()
}
task.resume()
semaphore.wait()
return isValid
}
}
I am currently working on decrypting Apple Pay tokens with Laravel PHP, and I have encountered a few uncertainties regarding the decryption process and the usage of AES-GCM.
Could you please clarify the following points:
Algorithm Confirmation:
Am I using the correct algorithm for decrypting the data key? Specifically, I am utilizing AES-256-GCM with the algorithm ID "id-aes256-GCM" (2.16.840.1.101.3.4.1.46), as specified in the documentation.
Is this the recommended algorithm for decrypting the Apple Pay token's data key?
Authentication Tag:
In the decryption process, it seems that an authentication tag is required, but I am not sure where to obtain it from. Could you confirm how the authentication tag is generated or provided during the encryption process?
If the tag is part of the token or is transmitted separately, could you clarify where I can retrieve it in order to proceed with the decryption successfully?
IV and Other Parameters:
I am using an initialization vector (IV) of 16 null bytes (00000000000000000000000000000000) as specified in the documentation. Could you confirm that this is correct and aligns with the expected parameters for the AES-GCM decryption?
Are there any other specific parameters or considerations I should be aware of when implementing the decryption of Apple Pay tokens?
GCM vs Other Encryption Modes:
Can you confirm that AES-GCM is the preferred and required encryption mode, or is there any flexibility to use other modes (e.g., AES-CBC) without compromising security?
Your guidance would be greatly appreciated to ensure I am following the correct decryption procedure for Apple Pay tokens.
Thank you in advance for your support.
Topic:
App & System Services
SubTopic:
Apple Pay
I am facing an issue with Apple Pay js while doing the integration
we are using reference
https://applepaydemo.apple.com/apple-pay-js-api
In this I can generate the merchantSession correctly
But when I pass that merchantSession in
session.completeMerchantValidation(merchantValidation) as per documentation
It is getting failed and also no appropriate error is being shown in the console
Hello,
I am writing this because the behavior of the App Store Server Notification that our server receives is problematic in the Sandbox environment.
I have two questions in total.
When purchasing a Free Trial subscription, after receiving the SUBSCRIBED / INITAL_BUY Notification, DID_RENEW should be sent when it expires, but DID_FAIL_TO_RENEW/GRACE_PERIOD is sent.
The EXPIRE Notification is sent after the subscription expires or DID_CHANGE_RENEWAL_STATUS/AUTO_RENEW_DISABLED is sent, but it does not arrive.
The first problem is that I recently heard that automatic payments after a free trial require the user's consent via email. Is this the reason?
If so, I am curious about how I can test it in the Sandbox environment.
Is the second problem a bug?
Topic:
App & System Services
SubTopic:
Notifications
Tags:
Subscriptions
In-App Purchase
App Store Server Notifications
Document based SwiftData apps do not autosave changes to the ModelContext at all. This issue has been around since the first release of this SwiftData feature.
In fact, the Apple WWDC sample project (https://developer.apple.com/documentation/swiftui/building-a-document-based-app-using-swiftdata) does not persist any data in its current state, unless one inserts modelContext.save() calls after every data change.
I have reported this under the feedback ID FB16503154, as it seemed to me that there is no feedback report about the fundamental issue yet.
Other posts related to this problem:
https://forums.developer.apple.com/forums/thread/757172
https://forums.developer.apple.com/forums/thread/768906
https://developer.apple.com/forums/thread/764189
I am building a widget with configurable options (dynamic option) where the options are pull from api (ultimately the options are return from a server, but during my development, the response is constructed on the fly from locally).
Right now, I am able to display the widget and able to pull out the widget configuration screen where I can choose my config option . I am constantly having an issue where the loading the available options when selected a particular option (e.g. Category) and display them on the UI. Sometime, when I tap on the option "Category" and the loading indicator keeps spinning for while before it can populate the list of topics (return from methods in NewsCategoryQuery struct via fetchCategoriesFromAPI ). Notice that I already made my fetchCategoriesFromAPI call to return the result on the fly and however the widget configuration UI stills take a very long time to display the result. Even worst, the loading (loading indicator keep spinning) sometime will just kill itself after a while and my guess there are some time threshold where the widget extension or app intent is allow to run, not sure on this?
My questions:
How can I improve the loading time to populate the dynamic options in widget configuration via App Intent
Here is my sample code for my current setup
struct NewsFeedConfigurationIntent: AppIntent, WidgetConfigurationIntent {
static let title: LocalizedStringResource = "Configure News Topic Options"
static let description = IntentDescription("Select a topic for your news.")
@Parameter(title: "Category", default: nil)
var category: NewsCategory?
}
struct NewsCategory: AppEntity, Identifiable {
let id: String
let code: String
let name: String
static let typeDisplayRepresentation: TypeDisplayRepresentation = "News Topic"
static let defaultQuery = NewsCategoryQuery()
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: LocalizedStringResource(stringLiteral: name))
}
}
struct NewsCategoryQuery: EntityQuery {
func entities(for identifiers: [NewsCategory.ID]) async throws -> [NewsCategory] {
let categories = fetchCategoriesFromAPI()
return categories.filter { identifiers.contains($0.id) }
}
func suggestedEntities() async throws -> [NewsCategory] {
fetchCategoriesFromAPI()
}
}
func fetchCategoriesFromAPI() -> [NewsCategory] {
let list = [
"TopicA",
"TopicB",
"TopicC",
.......
]
return list.map { item in
NewsCategory(id: item, code: item, name: item.capitalized)
}
}
Topic:
App & System Services
SubTopic:
Widgets & Live Activities
Tags:
WidgetKit
Intents
App Intents
The UIApplication background task mechanism allows you to prevent your app from being suspended for short periods of time. While the API involved is quite small, there’s still a bunch of things to watch out for.
The name background task is somewhat misappropriate. Specifically, beginBackgroundTask(expirationHandler:) doesn’t actually start any sort of background task, but rather it tells the system that you have started some ongoing work that you want to continue even if your app is in the background. You still have to write the code to create and manage that work. So it’s best to think of the background task API as raising a “don’t suspend me” assertion.
You must end every background task that you begin. Failure to do so will result in your app being killed by the watchdog. For this reason I recommend that you attach a name to each background task you start (by calling beginBackgroundTask(withName:expirationHandler:) rather than beginBackgroundTask(expirationHandler:)). A good name is critical for tracking down problems when things go wrong.
IMPORTANT Failing to end a background task is the number one cause of background task problems on iOS. This usually involves some easy-to-overlook error in bookkeeping that results in the app begining a background task and not ending it. For example, you might have a property that stores your current background task identifier (of type UIBackgroundTaskIdentifier). If you accidentally creates a second background task and store it in that property without calling endBackgroundTask on the identifier that’s currently stored there, the app will ‘leak’ a background task, something that will get it killed by the watchdog. One way to avoid this is to wrap the background task in an object; see the QRunInBackgroundAssertion post on this thread for an example.
Background tasks can end in one of two ways:
When your app has finished doing whatever it set out to do.
When the system calls the task’s expiry handler.
Your code is responsible for calling endBackgroundTask(_:) in both cases.
All background tasks must have an expiry handler that the system can use to ‘call in’ the task. The background task API allows the system to do that at any time.
Your expiry handler is your opportunity to clean things up. It should not return until everything is actually cleaned up. It must run quickly, that is, in less than a second or so. If it takes too long, your app will be killed by the watchdog.
Your expiry handler is called on the main thread.
It is legal to begin and end background tasks on any thread, but doing this from a secondary thread can be tricky because you have to coordinate that work with the expiry handler, which is always called on the main thread.
The system puts strict limits on the total amount of time that you can prevent suspension using background tasks. On current systems you can expect about 30 seconds.
IMPORTANT I’m quoting these numbers just to give you a rough idea of what to expect. The target values have changed in the past and may well change in the future, and the amount of time you actually get depends on the state of the system. The thing to remember here is that the exact value doesn’t matter as long as your background tasks have a functional expiry handler.
You can get a rough estimate of the amount of time available to you by looking at UIApplication’s backgroundTimeRemaining property.
IMPORTANT The value returned by backgroundTimeRemaining is an estimate and can change at any time. You must design your app to function correctly regardless of the value returned. It’s reasonable to use this property for debugging but we strongly recommend that you avoid using as part of your app’s logic.
IMPORTANT Basing app behaviour on the value returned by backgroundTimeRemaining is the number two cause of background task problems on iOS.
The system does not guarantee any background task execution time. It’s possible (albeit unlikely, as covered in the next point) that you’ll be unable to create a background task. And even if you do manage to create one, its expiry handler can be called at any time.
beginBackgroundTask(expirationHandler:) can fail, returning UIBackgroundTaskInvalid, to indicate that you the system is unable to create a background task. While this was a real possibility when background tasks were first introduced, where some devices did not support multitasking, you’re unlikely to see this on modern systems.
The background time ‘clock’ only starts to tick when the background task becomes effective. For example, if you start a background task while the app is in the foreground and then stay in the foreground, the background task remains dormant until your app moves to the background. This can help simplify your background task tracking logic.
The amount of background execution time you get is a property of your app, not a property of the background tasks themselves. For example, starting two background task in a row won’t give you 60 seconds of background execution time.
Notwithstanding the previous point, it can still make sense to create multiple background tasks, just to help with your tracking logic. For example, it’s common to create a background task for each job being done by your app, ending the task when the job is done.
Do not create too many background tasks. How many is too many? It’s absolutely fine to create tens of background tasks but creating thousands is not a good idea.
IMPORTANT iOS 11 introduced a hard limit on the number of background task assertions a process can have (currently about 1000, but the specific value may change in the future). If you see a crash report with the exception code 0xbada5e47, you’ve hit that limit.
Note The practical limit that you’re most likely to see here is the time taken to call your expiry handlers. The watchdog has a strict limit (a few seconds) on the total amount of time taken to run background task expiry handlers. If you have thousands of handlers, you may well run into this limit.
If you’re working in a context where you don’t have access to UIApplication (an app extension or on watchOS) you can achieve a similar effect using the performExpiringActivity(withReason:using:) method on ProcessInfo.
If your app ‘leaks’ a background task, it may end up being killed by the watchdog. This results in a crash report with the exception code 0x8badf00d (“ate bad food”).
IMPORTANT A leaked background task is not the only reason for an 0x8badf00d crash. You should look at the backtrace of the main thread to see if the main thread is stuck in your code, for example, in a synchronous networking request. If, however, the main thread is happily blocked in the run loop, a leaked background task should be your primary suspect.
Prior to iOS 11 information about any outstanding background tasks would appear in the resulting crash report (look for the text BKProcessAssertion). This information is not included by iOS 11 and later, but you can find equivalent information in the system log.
The system log is very noisy so it’s important that you give each of your background tasks an easy-to-find name.
For more system log hints and tips, see Your Friend the System Log.
iOS 13 introduced the Background Tasks framework. This supports two type of requests:
The BGAppRefreshTaskRequest class subsumes UIKit’s older background app refresh functionality.
The BGProcessingTaskRequest class lets you request extended background execution time, typically overnight.
WWDC 2020 Session 10063 Background execution demystified is an excellent summary of iOS’s background execution model. Watch it, learn it, love it!
For more background execution hints and tips, see Background Tasks Resources.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Revision History
2023-06-16 Added a link to my QRunInBackgroundAssertion post.
2022-06-08 Corrected a serious error in the discussion of BGProcessingTaskRequest. Replaced the basic system log info with a reference to Your Friend the System Log. Added a link to Background Tasks Resources. Made other minor editorial changes.
2021-02-27 Fixed the formatting. Added a reference to the Background Tasks framework and the Background execution demystified WWDC presentation. Minor editorial changes.
2019-01-20 Added a note about changes in the iOS 13 beta. Added a short discussion about beginning and ending background tasks on a secondary thread.
2018-02-28 Updated the task name discussion to account for iOS 11 changes. Added a section on how to debug ‘leaked’ background tasks.
2017-10-31 Added a note about iOS 11’s background task limit.
2017-09-12 Numerous updates to clarify various points.
2017-08-17 First posted.
I am able to block apps using FamilyControl and Shield. Unblocking is also simple—just assign nil to store.shield.applications. However, I want to unblock them even when the app is not open.
Use case: Let's say the app allows users to create a session where a particular app is blocked for a specific duration. Once the session starts, the app should remain blocked, and as soon as the session time ends, it should automatically be unblocked.
Please help me with this. Thank you!
Hi,
I’m trying to get an array of strings from the user using AppIntents, but I’m encountering an issue. The shortcut ends without prompting the user for input or saving the value, though it doesn’t crash. I need to get the user to input multiple tasks in an array, but the current approach isn’t working as expected.
Here’s the current method I’m using:
// Short code snippet showing the current method
private func collectTasks() async throws -> [String] {
var collectedTasks: [String] = tasks ?? []
while true {
if !collectedTasks.isEmpty {
let addMore = try await $input.requestConfirmation("Would you like to add another task?")
if !addMore {
break
}
}
let newTask = try await $input.requestValue("Please enter a task:")
collectedTasks.append(newTask)
}
return collectedTasks
}
The Call
func perform() async throws -> some IntentResult {
let finalTasks = try await collectTasks()
// Some more Code
}
Any advice or suggestions would be appreciated. Thanks in advance!
Hi everyone,
I'm working on an app for parents and kids where parents can define screen time goals or restrict usage of certain app categories (like social media or games). If the kid follows those rules—for example, by using their device less or avoiding restricted categories—they would earn points or rewards in the app.
I’ve been exploring if the Apple Screen Time API allows developers to access this kind of data (like total screen time, app usage by category, etc.) so that I can track the kid’s behavior and reward them accordingly.
Is it possible to programmatically access this data and implement such a reward system within my app? If so, what’s the best way to get started or which APIs should I look into?
Thanks in advance for your help!
I am trying to get a pass reader certified through the mFi / WPC certification process. The problem I have is that the Certifier app will not allow testing results to be submitted due to some missing information. I need support to discuss the missing information, but I have received no replies from the email provided for WPC Certification Representative.
Does anyone here know how to get support for the WPC Certification process?
Topic:
App & System Services
SubTopic:
Apple Pay
If I run an app with a message filter extension, it's triggered for all the prepaid unknown numbers and its not triggered for all the unknown postpaid numbers. Any idea, how to trigger for postpaid unknown numbers?.
Our product is using IOKit framework for monitoring USB device activities. We have used IOKit framework for getting the notification for USB plugin and un-plugins. With the macOS version 15.3 we are started seeing issue with it. When the notification is received during USB plugin/connection, we are unable to get IOUSBDeviceInterface object which will be used for further processing.
Currently we are seeing the below error every time, while trying to create the IO plugin interface using IOCreatePlugInInterfaceForService API:
create plugin Error: (0xe00002be): (iokit/common) resource shortage
Due to this the we are unable to proceed with the flow further and the entire flow is broken.
These logics work fine in macOS version 15.2 and lower versions without any issues.
logic used:
USBDevice::initInterfaceInterfaceByIOService(io_service_t entry)
{
IOCFPlugInInterface** plugInInterface = NULL;
IOUSBInterfaceInterface** interface = NULL;
SInt32 score = 0;
mach_error_code err =
IOCreatePlugInInterfaceForService(entry, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score);
if ((err != 0) || (!plugInInterface)) {
os_log_error(OS_LOG_DEFAULT, "Unable to create plugin \n");
return nullptr;
}
auto result = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID), (LPVOID*)&interface);
(*plugInInterface)->Release(plugInInterface);
if (result || !interface) {
os_log_error(OS_LOG_DEFAULT, "Unable to create interface \n");
return nullptr;
}
return interface;
}
We are experiencing an Unknown Error (error code 0) when testing purchases in the Apple Sandbox environment.
The error does not occur every time, but it fails with an Unknown Error in approximately 8 out of 20 attempts.
Due to this issue, our app is failing to pass Apple’s review.
Issue Details
This issue only occurs in the Apple Sandbox environment.
After attempting a purchase, the Apple payment API returns SKPaymentTransaction Unknown Error.
Returned error code: SKErrorUnknown (error.code 0).
This problem is preventing us from conducting proper payment tests and getting approval from Apple.
Would it be possible to receive support or any solutions for this issue?
My MacOS swift app [myStuckApp5] refuses to close when running on Monterrey (The app becomes unresponsive after finishing its work, and needs to be forcefully closed). However, it closes as expected when running MacOS 13 and above. How can I troubleshoot this error?
I'm attaching the content of the sys Log related to the app while it was stuck (too long to copy here...)
This is the content of the related sys Log
Topic:
App & System Services
SubTopic:
Core OS
Tags:
Developer Tools
macOS
Custom Apps
Xcode Sanitizers and Runtime Issues
Greetings,
I recently submitted a request for the Location Push Service Extension Entitlement.
Does anybody have insight into how long I would have to wait until Apple responds?
Thanks
I'm working on an app the has implemented inapp purchases. They have been working so far, and they keep working currently.
But just a couple of days ago, a specific user sent us a support ticket stating that when he purchases and item the bank charges it for the purchase, but within the app, the purchase fails and he doesn't receive the item.
He sent us screenshots showing:
The iOS native modal when a purchases has been finished correctly ("You're all set - Your purchase was successful ").
Right after that modal, the app shows an internal modal showing "The purchase failed, please, try again later".
Checking the app logs, that failure modal was triggered by "The operation couldn’t be completed. (StoreKit.StoreKitError error 1.)".
Reading docs about this error leads me to think about device or user restrictions (parental controls, usage limits, etc...). It seems that in theses cases the bank charge could be issued but refunded later once Apple ultimately declines the purchase.
However the user says that he doesn't have any kind of restriction. The only related thing is a "this device is also restricted by a profile" message, but everyone seems to have this message.
What could it be causing this issue? In what scenariowould the app show a native OK modal but a storekit error 1?
I'm pretty sure the app is well configured because I keep receiving purchases of all kind, from different users with any problem.
Topic:
App & System Services
SubTopic:
StoreKit
Howdy,
I'm trying to figure out how to replicate the following behavior for our app:
The system is able to ascertain that the Mac equivalent of some iOS app is installed locally, and it prevents notifications from being mirrored. However, I am unable to determine how this association is inferred. When I check our iOS app under this prefpane, the switch remains enabled and toggleable—we'd like to act like Slack here.
My initial assumption is that an app group containing both the Mac and iOS apps can be used to create the association; however, I would like to confirm that this is indeed the case before doing so. I'm not terribly confident about this.
Details:
The bundle identifiers of both apps do not match. This also applies to Slack; its iOS app is com.tinyspeck.chatlyio while its Mac app is com.tinyspeck.slackmacgap.
In our case, the iOS app's identifier is like com.company.app while the Mac app's identifier is com.company.app.desktop.
Both apps are signed with certificates that have matching team identifiers. The com.apple.developer.team-identifier entitlement is present on the Mac app.
The Mac app shares a keychain access group with the iOS app.
The Mac app is not sandboxed.
The Mac app is an Electron app.
The Mac app does not use APNs. It sends notifications "locally".
I currently only have the iOS app installed on my iPhone via TestFlight, if that matters.
Notification mirroring does work, but we'd like to forcibly disable this by associating the apps together.
To my knowledge, the iOS app makes use of both a UNNotificationServiceExtension and a UNNotificationContentExtension.
The iOS app currently doesn't have an assigned category (at least in Xcode). The Mac app is currently miscategorized as a developer tool (LSApplicationCategoryType = "public.app-category.developer-tools";), but that should be fixed.
(Redacted) bundle information for the Mac app:
CFBundleDisplayName = App;
CFBundleExecutable = "App Desktop";
CFBundleName = App;
Note that our CFBundleExecutable differs from the bundle's display name/name because we're currently migrating our users to a new version of the app that they'd likely want to live alongside the new one. The filename of the bundle itself is, similarly, App Desktop.app.
For the iOS app, to my knowledge, the CFBundleName and CFBundleDisplayName are App.