Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ Relay is a native macOS app built with SwiftUI and Xcode.

- macOS 26.0 (Tahoe) or later
- Xcode 26.0 or later
- An Apple ID added to Xcode (**Settings > Accounts**). A free account is
enough -- no paid Apple Developer Program membership required. This is
needed because Relay's app sandbox and app group entitlements require a
real development certificate; there's no way to build and run the app
without signing in to at least a free "Personal Team".

### Steps

Expand All @@ -35,8 +40,12 @@ Relay is a native macOS app built with SwiftUI and Xcode.

Open `Secrets.xcconfig` and fill in your values:

- **`BUNDLE_PREFIX`** -- a custom prefix for the app bundle. Xcode *will*
try to register the bundle identifier to your account and they're
globally unique.
- **`DEVELOPMENT_TEAM`** -- Your Apple Developer Team ID. Find it in
Xcode under **Settings > Accounts**, or at
Xcode under **Settings > Accounts** (select your Apple ID, then your
team -- a free "Personal Team" works fine), or at
<https://developer.apple.com/account>.
- **`GIPHY_API_KEY`** -- Required for GIF search functionality. Create
one at <https://developers.giphy.com/dashboard/>.
Expand Down
50 changes: 24 additions & 26 deletions Relay.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@
);
target = 3B4AFD882F638A35001F0EA1 /* Relay */;
};
3BRK00232FC00023001F0EA1 /* Exceptions for "RelayKit" folder in "RelayKit" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 3BRK00102FC00010001F0EA1 /* RelayKit */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
Expand Down Expand Up @@ -126,6 +133,9 @@
};
3BRK00112FC00011001F0EA1 /* RelayKit */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
3BRK00232FC00023001F0EA1 /* Exceptions for "RelayKit" folder in "RelayKit" target */,
);
path = RelayKit;
sourceTree = "<group>";
};
Expand Down Expand Up @@ -491,13 +501,12 @@
/* Begin XCBuildConfiguration section */
3B11E08E2F9A3F600051F7B3 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3BSEC0012FEF0001001F0EA1 /* Secrets.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 46UKXDYHHC;
GENERATE_INFOPLIST_FILE = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.subpop.RelayTests;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_PREFIX).RelayTests";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
Expand All @@ -510,13 +519,12 @@
};
3B11E08F2F9A3F600051F7B3 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3BSEC0012FEF0001001F0EA1 /* Secrets.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 46UKXDYHHC;
GENERATE_INFOPLIST_FILE = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.subpop.RelayTests;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_PREFIX).RelayTests";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
Expand All @@ -529,10 +537,9 @@
};
3B3B82C62FC69A9F008DCEDC /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3BSEC0012FEF0001001F0EA1 /* Secrets.xcconfig */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = RelayShareExtension/RelayShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -546,7 +553,7 @@
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = app.subpop.Relay.ShareExtension;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_PREFIX).Relay.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
Expand All @@ -559,10 +566,9 @@
};
3B3B82C72FC69A9F008DCEDC /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3BSEC0012FEF0001001F0EA1 /* Secrets.xcconfig */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = RelayShareExtension/RelayShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -576,7 +582,7 @@
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = app.subpop.Relay.ShareExtension;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_PREFIX).Relay.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
Expand Down Expand Up @@ -625,7 +631,6 @@
CURRENT_PROJECT_VERSION = 24;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 46UKXDYHHC;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
Expand Down Expand Up @@ -694,7 +699,6 @@
CURRENT_PROJECT_VERSION = 24;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 46UKXDYHHC;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
Expand Down Expand Up @@ -724,8 +728,6 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDev;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Relay/Relay.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
ENABLE_APP_SANDBOX = YES;
Expand All @@ -750,7 +752,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = app.subpop.Relay;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_PREFIX).Relay";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
REGISTER_APP_GROUPS = YES;
Expand All @@ -769,8 +771,6 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Relay/Relay.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
ENABLE_APP_SANDBOX = YES;
Expand All @@ -795,7 +795,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = app.subpop.Relay;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_PREFIX).Relay";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
REGISTER_APP_GROUPS = YES;
Expand All @@ -811,15 +811,14 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 3BSEC0012FEF0001001F0EA1 /* Secrets.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_MODULE_VERIFIER = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = RelayKit/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -828,7 +827,7 @@
"@loader_path/Frameworks",
);
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
PRODUCT_BUNDLE_IDENTIFIER = app.subpop.RelayKit;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_PREFIX).RelayKit";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
Expand All @@ -842,15 +841,14 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 3BSEC0012FEF0001001F0EA1 /* Secrets.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_MODULE_VERIFIER = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = RelayKit/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -859,7 +857,7 @@
"@loader_path/Frameworks",
);
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
PRODUCT_BUNDLE_IDENTIFIER = app.subpop.RelayKit;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_PREFIX).RelayKit";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
Expand Down
2 changes: 2 additions & 0 deletions Relay/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>RelayAppGroupIdentifier</key>
<string>group.$(DEVELOPMENT_TEAM).$(BUNDLE_PREFIX).Relay</string>
<key>CFBundleHelpBookFolder</key>
<string>Relay.help</string>
<key>CFBundleHelpBookName</key>
Expand Down
12 changes: 6 additions & 6 deletions Relay/Relay.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.46UKXDYHHC.app.subpop.Relay</string>
<string>group.$(DEVELOPMENT_TEAM).$(BUNDLE_PREFIX).Relay</string>
</array>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>
9 changes: 8 additions & 1 deletion RelayKit/Core/AppGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@

import Foundation

private final class BundleAnchor {}

public enum AppGroup: Sendable {
nonisolated public static let identifier = "group.46UKXDYHHC.app.subpop.Relay"
nonisolated public static let identifier: String = {
guard let identifier = Bundle(for: BundleAnchor.self).object(forInfoDictionaryKey: "RelayAppGroupIdentifier") as? String else {
fatalError("RelayAppGroupIdentifier missing from RelayKit's Info.plist")
}
return identifier
}()

nonisolated public static var containerURL: URL? {
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: identifier)
Expand Down
8 changes: 8 additions & 0 deletions RelayKit/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>RelayAppGroupIdentifier</key>
<string>group.$(DEVELOPMENT_TEAM).$(BUNDLE_PREFIX).Relay</string>
</dict>
</plist>
7 changes: 6 additions & 1 deletion RelayShareExtension/AppGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
import Foundation

enum AppGroup {
static let identifier = "group.46UKXDYHHC.app.subpop.Relay"
static let identifier: String = {
guard let identifier = Bundle.main.object(forInfoDictionaryKey: "RelayAppGroupIdentifier") as? String else {
fatalError("RelayAppGroupIdentifier missing from RelayShareExtension's Info.plist")
}
return identifier
}()

static var containerURL: URL? {
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: identifier)
Expand Down
4 changes: 4 additions & 0 deletions RelayShareExtension/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>RelayAppGroupIdentifier</key>
<string>group.$(DEVELOPMENT_TEAM).$(BUNDLE_PREFIX).Relay</string>
<key>RelayMainAppBundleIdentifier</key>
<string>$(BUNDLE_PREFIX).Relay</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
Expand Down
2 changes: 1 addition & 1 deletion RelayShareExtension/RelayShareExtension.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.46UKXDYHHC.app.subpop.Relay</string>
<string>group.$(DEVELOPMENT_TEAM).$(BUNDLE_PREFIX).Relay</string>
</array>
</dict>
</plist>
4 changes: 3 additions & 1 deletion RelayShareExtension/ShareViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ class ShareViewController: NSViewController {
// bundle ID to avoid the URL-based dispatch that creates a new window.
writeLatestShareId(share.id)

let bundleId = "app.subpop.Relay"
guard let bundleId = Bundle.main.object(forInfoDictionaryKey: "RelayMainAppBundleIdentifier") as? String else {
fatalError("RelayMainAppBundleIdentifier missing from RelayShareExtension's Info.plist")
}
if let app = NSRunningApplication.runningApplications(withBundleIdentifier: bundleId).first {
app.activate()
} else if let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleId) {
Expand Down
17 changes: 16 additions & 1 deletion Secrets.xcconfig.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,26 @@
// Copy this file to Secrets.xcconfig and fill in your values.
// Secrets.xcconfig is gitignored and will not be committed.
//
// BUNDLE_PREFIX: A valid prefix for the bundle identifier (default would be app.subpop)
// Xcode tries to register the bundle identifier to your account and this are globally unique.
//
// DEVELOPMENT_TEAM: Your Apple Developer Team ID. You can find it in Xcode
// under Settings > Accounts, or at https://developer.apple.com/account
// under Settings > Accounts, or at https://developer.apple.com/account. A
// free "Personal Team" is fine -- no paid Apple Developer Program membership
// required. Relay's app sandbox and app group entitlements require a real
// development certificate, so some Apple ID signed in to Xcode is required;
// there's no way to build without one.
//
// GIPHY_API_KEY: Required for GIF search. Obtain one at:
// https://developers.giphy.com/dashboard/
//
// CODE_SIGN_STYLE / CODE_SIGN_IDENTITY: How the app is signed. The defaults
// below (Automatic + Apple Development) work with any team, free or paid.
// You shouldn't need to change these.

BUNDLE_PREFIX = your_bundle_prefix
DEVELOPMENT_TEAM = your_team_id_here
GIPHY_API_KEY = your_giphy_api_key_here

CODE_SIGN_STYLE = Automatic
CODE_SIGN_IDENTITY = Apple Development