use std::ffi::{c_void, CStr};
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use doom_fish_utils::completion::{error_from_cstr, AsyncCompletion, AsyncCompletionFuture};
use doom_fish_utils::panic_safe::catch_user_panic;
use crate::app_transaction::{AppTransaction, AppTransactionPayload};
use crate::error::StoreKitError;
use crate::private::{cstring_from_str, json_cstring, take_string};
use crate::product::{Product, ProductPayload};
use crate::purchase_option::{PurchaseOption, PurchaseResult, PurchaseResultPayload};
use crate::storefront::{Storefront, StorefrontPayload};
use crate::verification_result::{VerificationResult, VerificationResultPayload};
unsafe fn json_from_result_ptr(result: *const c_void) -> String {
CStr::from_ptr(result.cast::<i8>())
.to_string_lossy()
.into_owned()
}
extern "C" fn products_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
catch_user_panic("products_cb", || {
if !error.is_null() {
let msg = unsafe { error_from_cstr(error) };
unsafe { AsyncCompletion::<String>::complete_err(ctx, msg) };
} else if !result.is_null() {
let json = unsafe { json_from_result_ptr(result) };
unsafe { AsyncCompletion::complete_ok(ctx, json) };
} else {
unsafe {
AsyncCompletion::<String>::complete_err(
ctx,
"no result from sk_products_async".into(),
);
};
}
});
}
pub struct ProductsFuture {
inner: AsyncCompletionFuture<String>,
}
impl std::fmt::Debug for ProductsFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ProductsFuture").finish_non_exhaustive()
}
}
impl Future for ProductsFuture {
type Output = Result<Vec<Product>, StoreKitError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner).poll(cx).map(|r| {
let json = r.map_err(StoreKitError::Unknown)?;
let payloads: Vec<ProductPayload> = serde_json::from_str(&json).map_err(|e| {
StoreKitError::InvalidArgument(format!("failed to parse products JSON: {e}"))
})?;
payloads
.into_iter()
.map(ProductPayload::into_product)
.collect()
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct AsyncProducts;
impl AsyncProducts {
pub fn fetch<I, S>(identifiers: I) -> Result<ProductsFuture, StoreKitError>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let ids: Vec<String> = identifiers
.into_iter()
.map(|s| s.as_ref().to_owned())
.collect();
let ids_json = json_cstring(&ids, "product identifiers")?;
let (future, ctx) = AsyncCompletion::create();
unsafe { crate::ffi::sk_products_async(ids_json.as_ptr(), products_cb, ctx) }
Ok(ProductsFuture { inner: future })
}
}
struct RawPurchaseBox(*mut c_void);
unsafe impl Send for RawPurchaseBox {}
impl Drop for RawPurchaseBox {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { crate::ffi::sk_purchase_async_result_release(self.0) };
}
}
}
extern "C" fn purchase_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
catch_user_panic("purchase_cb", || {
if !error.is_null() {
let msg = unsafe { error_from_cstr(error) };
unsafe { AsyncCompletion::<RawPurchaseBox>::complete_err(ctx, msg) };
} else if !result.is_null() {
let boxed = RawPurchaseBox(result.cast_mut());
unsafe { AsyncCompletion::complete_ok(ctx, boxed) };
} else {
unsafe {
AsyncCompletion::<RawPurchaseBox>::complete_err(
ctx,
"no result from sk_product_purchase_async".into(),
);
};
}
});
}
pub struct PurchaseFuture {
inner: AsyncCompletionFuture<RawPurchaseBox>,
}
impl std::fmt::Debug for PurchaseFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PurchaseFuture").finish_non_exhaustive()
}
}
impl Future for PurchaseFuture {
type Output = Result<PurchaseResult, StoreKitError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner).poll(cx).map(|r| {
let raw_box = r.map_err(StoreKitError::Unknown)?;
let ptr = raw_box.0;
let result = unsafe { extract_purchase_result(ptr) };
result
})
}
}
unsafe fn extract_purchase_result(ptr: *mut c_void) -> Result<PurchaseResult, StoreKitError> {
let json_ptr = crate::ffi::sk_purchase_async_result_json(ptr);
let json = take_string(json_ptr).ok_or_else(|| {
StoreKitError::InvalidArgument("missing JSON from purchase async result".into())
})?;
let transaction_handle = crate::ffi::sk_purchase_async_result_take_handle(ptr);
let payload: PurchaseResultPayload = serde_json::from_str(&json).map_err(|e| {
if !transaction_handle.is_null() {
crate::ffi::sk_transaction_release(transaction_handle);
}
StoreKitError::InvalidArgument(format!(
"failed to parse purchase result JSON: {e}; payload={json}"
))
})?;
payload.into_purchase_result(transaction_handle)
}
#[derive(Debug, Clone, Copy)]
pub struct AsyncPurchase;
impl AsyncPurchase {
pub fn buy(product_id: &str, options: &[PurchaseOption]) -> Result<PurchaseFuture, StoreKitError> {
let id = cstring_from_str(product_id, "product id")?;
let opts = json_cstring(options, "purchase options")?;
let (future, ctx) = AsyncCompletion::create();
unsafe { crate::ffi::sk_product_purchase_async(id.as_ptr(), opts.as_ptr(), purchase_cb, ctx) }
Ok(PurchaseFuture { inner: future })
}
}
extern "C" fn void_cb(_result: *const c_void, error: *const i8, ctx: *mut c_void) {
catch_user_panic("void_cb", || {
if error.is_null() {
unsafe { AsyncCompletion::complete_ok(ctx, ()) };
} else {
let msg = unsafe { error_from_cstr(error) };
unsafe { AsyncCompletion::<()>::complete_err(ctx, msg) };
}
});
}
pub struct RequestReviewFuture {
inner: AsyncCompletionFuture<()>,
}
impl std::fmt::Debug for RequestReviewFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RequestReviewFuture").finish_non_exhaustive()
}
}
impl Future for RequestReviewFuture {
type Output = Result<(), StoreKitError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner)
.poll(cx)
.map(|r| r.map_err(StoreKitError::Unknown))
}
}
pub struct ShowManageSubscriptionsFuture {
inner: AsyncCompletionFuture<()>,
}
impl std::fmt::Debug for ShowManageSubscriptionsFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ShowManageSubscriptionsFuture")
.finish_non_exhaustive()
}
}
impl Future for ShowManageSubscriptionsFuture {
type Output = Result<(), StoreKitError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner)
.poll(cx)
.map(|r| r.map_err(StoreKitError::NotSupported))
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct AsyncAppStore;
impl AsyncAppStore {
#[must_use = "futures do nothing unless polled"]
pub fn request_review() -> RequestReviewFuture {
let (future, ctx) = AsyncCompletion::create();
unsafe { crate::ffi::sk_app_store_request_review_async(void_cb, ctx) }
RequestReviewFuture { inner: future }
}
#[must_use = "futures do nothing unless polled"]
pub fn show_manage_subscriptions() -> ShowManageSubscriptionsFuture {
let (future, ctx) = AsyncCompletion::create();
unsafe { crate::ffi::sk_app_store_show_manage_subscriptions_async(void_cb, ctx) }
ShowManageSubscriptionsFuture { inner: future }
}
}
extern "C" fn app_transaction_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
catch_user_panic("app_transaction_cb", || {
if !error.is_null() {
let msg = unsafe { error_from_cstr(error) };
unsafe { AsyncCompletion::<String>::complete_err(ctx, msg) };
} else if !result.is_null() {
let json = unsafe { json_from_result_ptr(result) };
unsafe { AsyncCompletion::complete_ok(ctx, json) };
} else {
unsafe {
AsyncCompletion::<String>::complete_err(
ctx,
"no result from sk_app_transaction_shared_async".into(),
);
};
}
});
}
pub struct AppTransactionFuture {
inner: AsyncCompletionFuture<String>,
}
impl std::fmt::Debug for AppTransactionFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AppTransactionFuture").finish_non_exhaustive()
}
}
impl Future for AppTransactionFuture {
type Output = Result<VerificationResult<AppTransaction>, StoreKitError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner).poll(cx).map(|r| {
let json = r.map_err(StoreKitError::Unknown)?;
let payload: VerificationResultPayload<AppTransactionPayload> =
serde_json::from_str(&json).map_err(|e| {
StoreKitError::InvalidArgument(format!(
"failed to parse app transaction JSON: {e}"
))
})?;
payload.into_result(AppTransactionPayload::into_app_transaction)
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct AsyncAppTransaction;
impl AsyncAppTransaction {
#[must_use = "futures do nothing unless polled"]
pub fn shared() -> AppTransactionFuture {
let (future, ctx) = AsyncCompletion::create();
unsafe { crate::ffi::sk_app_transaction_shared_async(app_transaction_cb, ctx) }
AppTransactionFuture { inner: future }
}
}
struct StorefrontResult(Option<String>);
extern "C" fn storefront_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
catch_user_panic("storefront_cb", || {
if !error.is_null() {
let msg = unsafe { error_from_cstr(error) };
unsafe { AsyncCompletion::<StorefrontResult>::complete_err(ctx, msg) };
} else if !result.is_null() {
let json = unsafe { json_from_result_ptr(result) };
unsafe { AsyncCompletion::complete_ok(ctx, StorefrontResult(Some(json))) };
} else {
unsafe { AsyncCompletion::complete_ok(ctx, StorefrontResult(None)) };
}
});
}
#[derive(serde::Deserialize)]
struct StorefrontCurrentPayload {
storefront: Option<StorefrontPayload>,
}
pub struct StorefrontCurrentFuture {
inner: AsyncCompletionFuture<StorefrontResult>,
}
impl std::fmt::Debug for StorefrontCurrentFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StorefrontCurrentFuture")
.finish_non_exhaustive()
}
}
impl Future for StorefrontCurrentFuture {
type Output = Result<Option<Storefront>, StoreKitError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner).poll(cx).map(|r| {
let StorefrontResult(maybe_json) = r.map_err(StoreKitError::Unknown)?;
let Some(json) = maybe_json else { return Ok(None) };
let wrapper: StorefrontCurrentPayload =
serde_json::from_str(&json).map_err(|e| {
StoreKitError::InvalidArgument(format!(
"failed to parse storefront JSON: {e}"
))
})?;
Ok(wrapper.storefront.map(StorefrontPayload::into_storefront))
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct AsyncStorefront;
impl AsyncStorefront {
#[must_use = "futures do nothing unless polled"]
pub fn current() -> StorefrontCurrentFuture {
let (future, ctx) = AsyncCompletion::create();
unsafe { crate::ffi::sk_storefront_current_async(storefront_cb, ctx) }
StorefrontCurrentFuture { inner: future }
}
}