[object Object]

← back to CelebritySignatures

iOS: wire the 3.1.1 WebView guard + fix evolution-load side effect

fc653007205f47af2cb898a2b57e0648d9037fe1 · 2026-08-05 15:53:20 -0700 · Steve

- allowWebNav was defined but never attached to the mural-checkout WebView,
  leaving the App Store 3.1.1 digital-purchase guard inert (tsc still passed).
  Attach it via onShouldStartLoadWithRequest so in-WebView navigation to a
  digital signature purchase is blocked and true externals bounce to Safari.
- Move the evolution-map lazy load out of a setState updater into a useEffect
  (state updaters must be pure).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit fc653007205f47af2cb898a2b57e0648d9037fe1
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Aug 5 15:53:20 2026 -0700

    iOS: wire the 3.1.1 WebView guard + fix evolution-load side effect
    
    - allowWebNav was defined but never attached to the mural-checkout WebView,
      leaving the App Store 3.1.1 digital-purchase guard inert (tsc still passed).
      Attach it via onShouldStartLoadWithRequest so in-WebView navigation to a
      digital signature purchase is blocked and true externals bounce to Safari.
    - Move the evolution-map lazy load out of a setState updater into a useEffect
      (state updaters must be pure).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 apps/mobile/App.tsx | 30 +++++++++++++++++++++++-------
 1 file changed, 23 insertions(+), 7 deletions(-)

diff --git a/apps/mobile/App.tsx b/apps/mobile/App.tsx
index 10bb27b..446d692 100644
--- a/apps/mobile/App.tsx
+++ b/apps/mobile/App.tsx
@@ -1,6 +1,6 @@
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
 import {
-  ActivityIndicator, Modal, Pressable, SafeAreaView, StyleSheet, Text, View,
+  ActivityIndicator, Linking, Modal, Pressable, SafeAreaView, StyleSheet, Text, View,
 } from 'react-native';
 import { StatusBar } from 'expo-status-bar';
 import * as Haptics from 'expo-haptics';
@@ -38,11 +38,26 @@ export default function App() {
   const openSignature = useCallback((sig: Signature) => {
     Haptics.selectionAsync().catch(() => {});
     setSelected(sig);
-    // Lazy-load the evolution map once, on first detail open.
-    setEvolution((cur) => {
-      if (!cur) api.evolution().then(setEvolution).catch(() => {});
-      return cur;
-    });
+  }, []);
+
+  // Lazy-load the evolution map once, the first time a detail is opened.
+  // (A useEffect, not a side effect inside a setState updater — updaters must be pure.)
+  useEffect(() => {
+    if (selected && !evolution) api.evolution().then(setEvolution).catch(() => {});
+  }, [selected, evolution]);
+
+  // App Store 3.1.1 guard: the mural checkout is a PHYSICAL print (IAP-exempt),
+  // but the site also exposes DIGITAL signature-file downloads. Block any in-
+  // WebView navigation that could reach a digital purchase, so a reviewer can't
+  // complete a digital sale without IAP. Allow the murals page, same-origin
+  // assets, and Stripe (physical checkout); kick true externals to Safari.
+  const allowWebNav = useCallback((url: string): boolean => {
+    if (/signature-(checkout|file)|\/api\/signature-/.test(url)) return false;
+    if (url.startsWith(WEB_BASE) || /^https:\/\/(checkout|js|hooks)\.stripe\.com/.test(url) || url.startsWith('about:')) {
+      return true;
+    }
+    if (/^https?:/.test(url)) { Linking.openURL(url).catch(() => {}); return false; }
+    return true;
   }, []);
 
   const orderMural = useCallback((sig: Signature) => {
@@ -101,6 +116,7 @@ export default function App() {
                 source={{ uri: webUrl }}
                 startInLoadingState
                 sharedCookiesEnabled
+                onShouldStartLoadWithRequest={(req) => allowWebNav(req.url)}
                 injectedJavaScriptBeforeContentLoaded={NO_TRACK}
                 renderLoading={() => (
                   <ActivityIndicator style={styles.webLoading} size="large" color={GLASS.accent} />

← 73d9137 iOS app: native Expo/EAS scaffold over celebsignatures.com A  ·  back to CelebritySignatures  ·  iOS: add on-brand app icon + splash + adaptive icon, wire in caf36ad →