@livx.cc/appwrap 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livx.cc/appwrap",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "Wrap any PWA into a native app with native capabilities (appwrap runtime + @livx.cc/native-kit).",
5
5
  "license": "MIT",
6
6
  "author": "Elya Livshitz",
@@ -141,6 +141,10 @@ export const MODULES: ModuleManifest[] = [
141
141
  {
142
142
  name: 'billing', group: 'billing',
143
143
  capabilities: { billing: { ios: true, android: false } },
144
+ // iOS-only Swift shim (AppwrapManageSubscriptions.swift) bridging StoreKit 2's Swift-async
145
+ // showManageSubscriptions(in:) to an @objc completion the ObjC bridge can call. Compiled into
146
+ // the app target only when `billing` is active (NS auto-compiles App_Resources/iOS/src/*.swift).
147
+ nativeSrc: 'billing',
144
148
  },
145
149
 
146
150
  // ── reviews — in-app store review prompt — opt-in, STRIPPABLE (own handler + group) ──
@@ -1,6 +1,10 @@
1
1
  import { Utils, isIOS } from '@nativescript/core';
2
2
  import { bridge } from './bridge';
3
3
 
4
+ // @objc Swift shim (AppwrapManageSubscriptions.swift, billing nativeSrc) — no NS types; bridges
5
+ // StoreKit 2's Swift-async showManageSubscriptions(in:) to a completion the ObjC bridge can call.
6
+ declare const AppwrapManageSubscriptions: { present(completion: (message: string | null) => void): void };
7
+
4
8
  /**
5
9
  * In-app purchases — iOS StoreKit 1 (SKPaymentQueue). StoreKit 2 (Product/Transaction)
6
10
  * is Swift-only and unreachable via the ObjC bridge, so we use the delegate-based
@@ -199,9 +203,23 @@ export function registerBillingHandlers(): void {
199
203
  });
200
204
 
201
205
  bridge.register('billing.manageSubscriptions', () => {
202
- // StoreKit 2's AppStore.showManageSubscriptions is Swift-only; deep-link instead.
206
+ // DEFAULT: StoreKit 1 deep-link — correct for production, but leaves the app and doesn't show
207
+ // sandbox/TestFlight subs. The in-app sheet is the opt-in `billing.manageSubscriptionsSheet` below.
203
208
  Utils.openUrl('itms-apps://apps.apple.com/account/subscriptions');
204
209
  });
210
+
211
+ // OPT-IN (iOS 15+): StoreKit 2's in-app management sheet via the AppwrapManageSubscriptions Swift
212
+ // shim — shows the user's subs (incl. sandbox/TestFlight) and lets them cancel without leaving the
213
+ // app. Reached only when the app calls kit.billing.showManageSubscriptionsSheet(), so the default
214
+ // deep-link path above is untouched. Rejects on iOS <15, no scene, or a StoreKit error.
215
+ bridge.register('billing.manageSubscriptionsSheet', () => {
216
+ return new Promise<void>((resolve, reject) => {
217
+ AppwrapManageSubscriptions.present((message: string | null) => {
218
+ if (message) reject(err('NATIVE_ERROR', message));
219
+ else resolve();
220
+ });
221
+ });
222
+ });
205
223
  }
206
224
 
207
225
  function restoreTransactions(): Promise<any[]> {
@@ -0,0 +1,39 @@
1
+ import Foundation
2
+ import StoreKit
3
+ import UIKit
4
+
5
+ // StoreKit 2's `AppStore.showManageSubscriptions(in:)` is Swift-only + async, so it can't be reached
6
+ // from the ObjC bridge NativeScript uses. This @objc wrapper bridges the async call to a completion
7
+ // callback (NativeScript can't await Swift async) and runs it on the main actor against the active
8
+ // foreground UIWindowScene. Shipped via the billing module's nativeSrc → compiled into the app target
9
+ // only when `billing` is active. iOS 15+ (StoreKit 2); the JS layer guards the deep-link fallback.
10
+ @objc(AppwrapManageSubscriptions)
11
+ public final class AppwrapManageSubscriptions: NSObject {
12
+ // completion(nil) on success; completion(message) on failure. Always invoked on the main thread
13
+ // so the NativeScript JS continuation (and WebView response delivery) resumes on the UI thread.
14
+ @objc public static func present(_ completion: @escaping (String?) -> Void) {
15
+ guard #available(iOS 15.0, *) else {
16
+ completion("StoreKit 2 manage-subscriptions sheet requires iOS 15+")
17
+ return
18
+ }
19
+ Task { @MainActor in
20
+ guard let scene = AppwrapManageSubscriptions.activeWindowScene() else {
21
+ completion("No active UIWindowScene to present the subscriptions sheet")
22
+ return
23
+ }
24
+ do {
25
+ try await AppStore.showManageSubscriptions(in: scene)
26
+ completion(nil)
27
+ } catch {
28
+ completion(error.localizedDescription)
29
+ }
30
+ }
31
+ }
32
+
33
+ // The foreground-active window scene (the one allowed to present UI). Falls back to any connected
34
+ // window scene so a not-yet-foregroundActive launch still finds a presenter.
35
+ @MainActor private static func activeWindowScene() -> UIWindowScene? {
36
+ let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
37
+ return scenes.first { $0.activationState == .foregroundActive } ?? scenes.first
38
+ }
39
+ }