@livx.cc/appwrap 0.38.0 → 0.38.2

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.38.0",
3
+ "version": "0.38.2",
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",
@@ -40,6 +40,11 @@ export const SHELL_CONFIG = {
40
40
  * fights the app:// handler / remote-update detection). On by default; set false to opt out and keep
41
41
  * the SW (e.g. for in-WebView web-push). See `serviceWorkerGuardJs`. */
42
42
  neutralizeServiceWorker: true,
43
+ /** iOS App-Bound Domains (from `appwrap.json.appBoundDomains`). When non-empty the shell sets
44
+ * `WKWebViewConfiguration.limitsNavigationsToAppBoundDomains = true` — Apple's gate for running a
45
+ * service worker in the WKWebView (the same hosts are stamped to Info.plist `WKAppBoundDomains`).
46
+ * RESTRICTS the WebView to these domains, so only for single-origin apps. Empty = no restriction. */
47
+ appBoundDomains: [] as string[],
43
48
  /** Open external-origin navigations (`<a>` to another origin, incl. `target="_blank"`, and
44
49
  * `window.open(...)`) in the OS default browser (Safari / Chrome) instead of inside the shell
45
50
  * WebView — regular-native-app behavior. Same-origin SPA navigation is untouched. Off by default.
@@ -180,6 +180,10 @@ export class CustomWebView extends WebView {
180
180
  config.allowsPictureInPictureMediaPlayback = true;
181
181
  config.mediaTypesRequiringUserActionForPlayback = 0; // WKAudiovisualMediaTypeNone
182
182
  config.websiteDataStore = WKWebsiteDataStore.defaultDataStore();
183
+ // Apple's hard requirement for a service worker in a WKWebView: restrict navigation to the
184
+ // declared app-bound domains (also stamped to Info.plist WKAppBoundDomains). Only set when
185
+ // configured — otherwise the WebView stays unrestricted (default behavior).
186
+ if (SHELL_CONFIG.appBoundDomains?.length) config.limitsNavigationsToAppBoundDomains = true;
183
187
 
184
188
  // Env hints + framework globals (backend origin) + a getUserMedia guard for UNDECLARED capabilities
185
189
  // (must reject in JS before WebKit's native capture path — see mediaCaptureGuardJs) + native-feel
package/src/cli.ts CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  resolveBuildNumber,
29
29
  stampAndroidOrientation,
30
30
  stampAndroidQueries,
31
+ stampAppBoundDomains,
31
32
  stampPlistBackgroundTasks,
32
33
  stampPlistOrientations,
33
34
  stampPrivacyTracking,
@@ -410,6 +411,7 @@ export const SHELL_CONFIG = {
410
411
  debugLog: ${JSON.stringify(cfg.debugLog ?? '*')},
411
412
  devMenu: ${JSON.stringify(cfg.devMenu ?? true)},
412
413
  neutralizeServiceWorker: ${JSON.stringify(cfg.neutralizeServiceWorker ?? true)},
414
+ appBoundDomains: ${JSON.stringify(cfg.appBoundDomains ?? [])} as string[],
413
415
  openNewWindowsInBrowser: ${JSON.stringify(cfg.openNewWindowsInBrowser ?? false)},
414
416
  pushIos: ${JSON.stringify(!!cfg.push?.enabled && cfg.push?.ios !== false)},
415
417
  pushAndroid: ${JSON.stringify(!!cfg.push?.enabled && cfg.push?.android !== false)},
@@ -448,6 +450,11 @@ function stampIOSDisplayName(outDir: string, cfg: AppwrapConfig, req: NativeReqs
448
450
  const bgActive = req.activeOptionalGroups.includes('backgroundTask');
449
451
  src = stampPlistBackgroundTasks(src, bgActive ? cfg.backgroundTasks : undefined);
450
452
 
453
+ // iOS App-Bound Domains — gate for a WKWebView service worker (paired with
454
+ // limitsNavigationsToAppBoundDomains in the shell config). Idempotent both ways: empty/undefined
455
+ // strips the WKAppBoundDomains key, so it no-ops when the field is absent.
456
+ src = stampAppBoundDomains(src, cfg.appBoundDomains);
457
+
451
458
  // Permission usage strings + URL scheme + export-compliance — idempotent: strip stamped block, re-add
452
459
  src = src.replace(/\s*<!-- appwrap:begin -->[\s\S]*?<!-- appwrap:end -->/g, '');
453
460
  const extras: string[] = [];
package/src/config.ts CHANGED
@@ -97,6 +97,15 @@ export interface AppwrapConfig {
97
97
  * for in-WebView web-push as a fallback. NOTE: keeping the SW is the only way to get web push; native
98
98
  * push is the separate `push` lane (APNs/FCM) and does NOT need a SW. */
99
99
  neutralizeServiceWorker?: boolean;
100
+ /** iOS App-Bound Domains — the hosts (no scheme, max 10 per Apple) the WebView may navigate to.
101
+ * Setting this stamps `WKAppBoundDomains` into Info.plist AND sets
102
+ * `WKWebViewConfiguration.limitsNavigationsToAppBoundDomains = true`, which is Apple's HARD
103
+ * requirement for a service worker to run inside a WKWebView. Pair with `neutralizeServiceWorker:false`
104
+ * to actually enable a SW (this flag is the iOS gate; Android's SW is a separate lane).
105
+ * TRADEOFF: `limitsNavigationsToAppBoundDomains` RESTRICTS the WebView to exactly these domains —
106
+ * any navigation/OAuth/redirect to another domain breaks. Only for single-origin apps. Empty/unset =
107
+ * no change to current behavior (no Info.plist key, no nav restriction). */
108
+ appBoundDomains?: string[];
100
109
  /** Hand navigations that LEAVE the app's own origin to the OS default browser (Safari / Chrome)
101
110
  * instead of replacing the shell WebView — so external links and `window.open(...)`/`target="_blank"`
102
111
  * behave like a regular native app (open in the system browser) rather than navigating away inside
package/src/derive.ts CHANGED
@@ -228,6 +228,27 @@ export function stampPlistBackgroundTasks(src: string, ids: string[] | undefined
228
228
  return src;
229
229
  }
230
230
 
231
+ /**
232
+ * Stamp (or strip) `WKAppBoundDomains` in an Info.plist string — Apple's gate for running a service
233
+ * worker inside a WKWebView (paired with `limitsNavigationsToAppBoundDomains` on the config). Pure +
234
+ * idempotent: rewrites an `appwrap:appbound` marker block (added before `</dict>`), so a re-sync never
235
+ * duplicates. `domains` empty/undefined → strips the block entirely (no key = current behavior). Apple
236
+ * caps the array at 10; we stamp what's given. Mirrors `stampPlistOrientations` (pure string transform).
237
+ */
238
+ export function stampAppBoundDomains(src: string, domains: string[] | undefined): string {
239
+ src = src.replace(/\s*<!-- appwrap:appbound -->[\s\S]*?<!-- \/appwrap:appbound -->/g, '');
240
+ const list = (domains ?? []).filter(Boolean);
241
+ if (list.length) {
242
+ const items = list.map((d) => ` <string>${d}</string>`).join('\n');
243
+ const block =
244
+ ` <!-- appwrap:appbound -->\n` +
245
+ ` <key>WKAppBoundDomains</key>\n <array>\n${items}\n </array>\n` +
246
+ ` <!-- /appwrap:appbound -->`;
247
+ src = src.replace(/<\/dict>\s*<\/plist>\s*$/, `${block}\n</dict>\n</plist>\n`);
248
+ }
249
+ return src;
250
+ }
251
+
231
252
  /**
232
253
  * Stamp the App Tracking Transparency declarations into PrivacyInfo.xcprivacy. Rewrites the two
233
254
  * tracking keys IN PLACE (the template ships them, so we never restructure the doc — we only flip
@@ -80,7 +80,25 @@ def appwrap_archive_and_sign_ios
80
80
  api_key
81
81
  end
82
82
 
83
+ # Restore the login keychain as the system default and drop fastlane's temp keychain. `setup_ci`/`match`
84
+ # create `fastlane_tmp_keychain` and point the default keychain at it; if a lane dies mid-run before
85
+ # fastlane's own teardown, that stray keychain is left as the DEFAULT — so macOS (com.apple.iCloudHelper
86
+ # etc.) keeps prompting for its password forever. CI runners are ephemeral so this is a no-op there;
87
+ # locally it prevents the recurring keychain prompt. Best-effort: never fail the lane on cleanup.
88
+ def appwrap_keychain_cleanup
89
+ login = File.expand_path('~/Library/Keychains/login.keychain-db')
90
+ return unless File.exist?(login)
91
+ sh("security default-keychain -s '#{login}'", error_callback: ->(_) {})
92
+ sh('security delete-keychain ~/Library/Keychains/fastlane_tmp_keychain-db 2>/dev/null || true',
93
+ error_callback: ->(_) {})
94
+ rescue StandardError => e
95
+ UI.message("keychain cleanup skipped: #{e}")
96
+ end
97
+
83
98
  platform :ios do
99
+ after_all { appwrap_keychain_cleanup }
100
+ error { |_lane, _exception| appwrap_keychain_cleanup }
101
+
84
102
  desc 'Build the NativeScript wrapper and upload to TestFlight'
85
103
  lane :beta do
86
104
  api_key = appwrap_archive_and_sign_ios