mirror of
https://github.com/priyanshujain/margin.git
synced 2026-10-02 11:07:04 +00:00
send app store builds to the store for updates
This commit is contained in:
1 parent
e8772e6699
commit
e8555c7ef9
10 files changed
+199
-54
No files matched your search
Generated
+1
@@ -4723,6 +4723,7 @@ dependencies = [
|
||||
"objc2-foundation",
|
||||
"rand 0.8.6",
|
||||
"reqwest 0.12.28",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
|
||||
@@ -38,6 +38,7 @@ typst-as-lib = "0.15.5"
|
||||
harper-core = { version = "=2.5.0", features = ["concurrent"] }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] }
|
||||
sha2 = "0.10"
|
||||
semver = "1"
|
||||
rand = "0.8"
|
||||
url = "2"
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
|
||||
<!-- Google Drive backup talks to googleapis.com. Nothing else leaves the machine. -->
|
||||
<!-- Google Drive backup talks to googleapis.com, and the update check asks itunes.apple.com
|
||||
which version is live on the store. Nothing else leaves the machine. -->
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ mod macspell;
|
||||
mod pdf;
|
||||
mod project;
|
||||
mod proofing;
|
||||
mod updates;
|
||||
mod writingtools;
|
||||
|
||||
#[cfg(desktop)]
|
||||
@@ -30,11 +31,7 @@ fn build_menu<R: Runtime>(handle: &tauri::AppHandle<R>) -> tauri::Result<Menu<R>
|
||||
let export_epub = MenuItemBuilder::with_id("export-epub", "Export as EPUB…")
|
||||
.accelerator("CmdOrCtrl+Shift+E")
|
||||
.build(handle)?;
|
||||
let check_updates = handle
|
||||
.config()
|
||||
.plugins
|
||||
.0
|
||||
.contains_key("updater")
|
||||
let check_updates = (updates::channel(handle) != "none")
|
||||
.then(|| MenuItemBuilder::with_id("check-updates", "Check for Updates…").build(handle))
|
||||
.transpose()?;
|
||||
let settings = MenuItemBuilder::with_id("settings", "Settings…")
|
||||
@@ -223,7 +220,10 @@ pub fn run() {
|
||||
gdrive::gdrive_backup,
|
||||
gdrive::gdrive_sync,
|
||||
gdrive::gdrive_restore,
|
||||
gdrive::gdrive_list_backups
|
||||
gdrive::gdrive_list_backups,
|
||||
updates::update_channel,
|
||||
updates::appstore_latest,
|
||||
updates::open_appstore
|
||||
])
|
||||
.build(context)
|
||||
.expect("error while building margin");
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
use std::sync::LazyLock;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use tauri::{AppHandle, Runtime};
|
||||
|
||||
static HTTP: LazyLock<reqwest::Client> = LazyLock::new(reqwest::Client::new);
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AppStoreRelease {
|
||||
version: String,
|
||||
track_id: u64,
|
||||
}
|
||||
|
||||
// The build flavour decides this, but a Mac App Store receipt overrides it either way: a bundle
|
||||
// carrying one must never self-update, whatever config it was built against.
|
||||
pub fn channel<R: Runtime>(handle: &AppHandle<R>) -> &'static str {
|
||||
let plugins = &handle.config().plugins.0;
|
||||
if plugins.contains_key("updater") && !mas_receipt() {
|
||||
"direct"
|
||||
} else if mas_receipt() || plugins.contains_key("appstore") {
|
||||
"appstore"
|
||||
} else {
|
||||
"none"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn mas_receipt() -> bool {
|
||||
let Ok(exe) = std::env::current_exe() else {
|
||||
return false;
|
||||
};
|
||||
exe.parent()
|
||||
.and_then(|macos| macos.parent())
|
||||
.map(|contents| contents.join("_MASReceipt").join("receipt").exists())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn mas_receipt() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn update_channel(app: AppHandle) -> &'static str {
|
||||
channel(&app)
|
||||
}
|
||||
|
||||
// Apple's lookup endpoint is edge cached, so a release can take hours to show up here. The
|
||||
// timestamp is the usual way past that.
|
||||
#[tauri::command]
|
||||
pub async fn appstore_latest(app: AppHandle) -> Result<Option<AppStoreRelease>, String> {
|
||||
let bundle_id = app.config().identifier.clone();
|
||||
let current = app.package_info().version.clone();
|
||||
let stamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0);
|
||||
let url = format!("https://itunes.apple.com/lookup?bundleId={bundle_id}&t={stamp}");
|
||||
|
||||
let body: serde_json::Value = HTTP
|
||||
.get(url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let Some(result) = body["results"].get(0) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let (Some(version), Some(track_id)) = (result["version"].as_str(), result["trackId"].as_u64())
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Ok(latest) = semver::Version::parse(version) else {
|
||||
return Ok(None);
|
||||
};
|
||||
if latest <= current {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some(AppStoreRelease { version: version.to_string(), track_id }))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn open_appstore(track_id: u64) -> Result<(), String> {
|
||||
let url = format!("macappstore://apps.apple.com/app/id{track_id}");
|
||||
tauri_plugin_opener::open_url(url, None::<&str>).map_err(|e| e.to_string())
|
||||
}
|
||||
@@ -5,5 +5,8 @@
|
||||
"macOS": {
|
||||
"hardenedRuntime": true
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"appstore": {}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user