Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: MegaManSec
90 changes: 90 additions & 0 deletions Magic Switch.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,66 @@
};
name = Release;
};
0A0000A000000000000000C1 /* AppStore */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = LA3A27SJGR;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_STORE $(inherited)";
SWIFT_COMPILATION_MODE = wholemodule;
VERSIONING_SYSTEM = "";
};
name = AppStore;
};
0774741A2CD8426F00571393 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
Expand Down Expand Up @@ -524,6 +584,34 @@
};
name = Release;
};
0A0000A000000000000000C2 /* AppStore */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Magic Switch/Magic_Switch.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"Magic Switch/Preview Content\"";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = "Magic Switch/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = 0.0.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.megamansec.magic-switch";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = AppStore;
};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
Expand All @@ -532,6 +620,7 @@
buildConfigurations = (
077474172CD8426F00571393 /* Debug */,
077474182CD8426F00571393 /* Release */,
0A0000A000000000000000C1 /* AppStore */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
Expand All @@ -541,6 +630,7 @@
buildConfigurations = (
0774741A2CD8426F00571393 /* Debug */,
0774741B2CD8426F00571393 /* Release */,
0A0000A000000000000000C2 /* AppStore */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
buildConfiguration = "AppStore"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
15 changes: 14 additions & 1 deletion Magic Switch/Manager/UpdateChecker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,22 @@ final class UpdateChecker: ObservableObject {
return Self.isNewer(latest, than: currentVersion)
}

/// App Store builds must update through the App Store, so they never check GitHub.
static let isEnabled: Bool = {
#if APP_STORE
return false
#else
return true
#endif
}()

// MARK: - Initialization

private init() {
guard Self.isEnabled else {
NotificationManager.removeNotification(identifier: Constants.updateNotificationID)
return
}
// Surface the cached result immediately so the menu / Settings reflect the
// last successful check without waiting for a network round trip.
latestVersion = UserDefaults.standard.string(forKey: Constants.latestVersionKey)
Expand Down Expand Up @@ -161,7 +174,7 @@ final class UpdateChecker: ObservableObject {
/// The actual network fetch. A `manual` check surfaces success/failure on the
/// button; automatic ones stay silent. One in-flight request at a time.
private func performCheck(manual: Bool) {
guard !isChecking else { return }
guard Self.isEnabled, !isChecking else { return }
guard let url = URL(string: Constants.latestReleaseAPI) else { return }
isChecking = true
if manual { lastCheckFailed = false }
Expand Down
44 changes: 28 additions & 16 deletions Magic Switch/View/Settings/OtherSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,25 +116,32 @@ struct OtherSettingsView: View {
Text(updateChecker.currentVersion)
.foregroundColor(.secondary)
}
HStack {
Button {
updateChecker.checkNow()
} label: {
if updateChecker.isChecking {
HStack(spacing: 6) {
ProgressView()
.controlSize(.small)
Text("Checking…")
#if !APP_STORE
HStack {
Button {
updateChecker.checkNow()
} label: {
if updateChecker.isChecking {
HStack(spacing: 6) {
ProgressView()
.controlSize(.small)
Text("Checking…")
}
} else {
Text("Check for Updates")
}
} else {
Text("Check for Updates")
}
.disabled(updateChecker.isChecking)
.help("Check GitHub for a newer release right now.")
Spacer()
updateStatus
}
.disabled(updateChecker.isChecking)
.help("Check GitHub for a newer release right now.")
Spacer()
updateStatus
}
SettingsRowView(
title: "Sponsor on GitHub",
help: "Support Magic Switch's development. Opens GitHub Sponsors in your browser.",
action: openSponsorPage
)
#endif
}
}
.onAppear(perform: refreshOnAppear)
Expand Down Expand Up @@ -246,6 +253,11 @@ struct OtherSettingsView: View {
openURL(url)
}

private func openSponsorPage() {
guard let url = URL(string: "https://github.com/sponsors/MegaManSec") else { return }
openURL(url)
}

private func openLatestRelease() {
guard let url = updateChecker.releasePageURL else { return }
openURL(url)
Expand Down
6 changes: 4 additions & 2 deletions PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ So your Macs can find each other, the app announces itself on the local network

## Update checks

At most once a day while the app is running, and whenever you click **Check for Updates**, the app asks GitHub (`api.github.com`) for the latest Magic Switch release. The request contains no identifiers or personal data, but GitHub sees your IP address as it would for any web request. GitHub's handling of that is covered by the [GitHub General Privacy Statement](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement).
The version of Magic Switch downloaded from GitHub checks for updates; the Mac App Store version doesn't, since the App Store updates it.

Links in the app, such as the release page and the license, open in your web browser.
At most once a day while the app is running, and whenever you click **Check for Updates**, the GitHub version asks GitHub (`api.github.com`) for the latest Magic Switch release. The request contains no identifiers or personal data, but GitHub sees your IP address as it would for any web request. GitHub's handling of that is covered by the [GitHub General Privacy Statement](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement).

Links in the app, such as the release page, the license and GitHub Sponsors, open in your web browser.

## Permissions

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ Known limits:
- On some systems an ad-hoc-signed build can't register with Notification Center at all, so failure notifications never fire no matter what the permission says; the Pairing and Macs tabs surface their errors inline as a fallback.
- Sixty bits of entropy in the pairing code is fine against an online attacker (rate limit makes brute force infeasible) but theoretically grindable offline if someone captures ciphertext. PBKDF2 stretching pushes the cost up but doesn't eliminate it; a PAKE would close the gap and is the obvious next step.

## Support

If Magic Switch is useful to you, you can support its development by [sponsoring me on GitHub](https://github.com/sponsors/MegaManSec).

## License

GNU GPL v3.0. See [LICENSE](LICENSE).