mirror of
https://github.com/luxfi/safe-wallet.git
synced 2026-10-30 15:19:21 +00:00
* feat(mobile): upgrade Expo SDK to v55 Upgrade Expo SDK from v54 to v55, along with React Native 0.83.4, React 19.2, and all related dependencies. Key changes: - Refactored @safe-global/theme to use sub-path exports to fix RN import resolution issues - Added react-native patch for Appearance.setColorScheme bug (facebook/react-native#08d176453) not yet in 0.83.4 release - Replaced Tamagui Skeleton with custom SafeSkeleton component - Updated expo config plugins for SDK 55 compatibility * fix: downgrade eslint-config-next to v15 to match next.js version eslint-config-next v16 requires next v16 but the project uses next v15.5.8. The v16 package tries to import next/dist/compiled/babel/ eslint-parser which doesn't exist in next v15. * fix: migrate packages away from eslint-config-next eslint-config-next requires next at runtime for its babel parser, but next is only installed in apps/web. After the Expo SDK v55 upgrade changed dependency hoisting, the parser became unreachable. - Create shared config/eslint/base.mjs using direct plugin imports - Migrate store, theme, utils, and tx-builder to the shared config - Add packageExtensions in .yarnrc.yml so eslint-config-next in apps/web can still resolve next as a peer dependency * chore: align react 19.2 across workspaces and fix MUI peer dep - Bump react/react-dom to 19.2.0 in web and tx-builder to match mobile - Add @mui/material as optional peer dependency in theme package to avoid duplicate installations causing type conflicts * chore(mobile): update storybook on mobile to v10 * fix(web): resolve @emotion/react hoisting issue after Expo SDK 55 upgrade Yarn 4's node-modules hoisting shifted after dependency graph changes, leaving @emotion/react only in apps/web/node_modules while @mui/styled-engine remained at root. Add webpack resolve aliases to ensure Next.js finds emotion packages from the web workspace. * fix(web): resolve @emotion hoisting issue in Jest config Same hoisting fix as next.config.mjs but for Jest's module resolution. Add moduleNameMapper entries for @emotion/react and @emotion/styled. * fix(tx-builder): prettier * fix: resolve type-check errors after Expo SDK 55 upgrade - Fix BlurView tint prop: map ColorSchemeName to valid BlurTint values - Replace @storybook/test imports with storybook/test (merged in v10) - Align @types/react (~19.2.10) and @types/react-dom (~19.2.0) across all workspaces to prevent duplicate @mui/material type mismatches - Spread visuallyHidden into sx prop for @types/react 19.2.x compat - Add @types/react, @types/react-dom, storybook to yarn version checker * chore: align more dependencies * chore(web): update storybook snapshot * fix(mobile): normalize color scheme and deduplicate MUI/emotion packages - Normalize useTheme colorScheme to 'light' | 'dark', never 'unspecified' so TamaguiProvider always receives a valid theme name - Update ColorScheme type to exclude 'unspecified' - Remove redundant ?? 'light' fallbacks in SafeThemeProvider - Align @emotion/* and @mui/icons-material versions across workspaces to prevent Yarn from creating duplicate lockfile resolutions - Remove emotion webpack/Jest aliases (no longer needed after dedup) - Add @emotion/react, @emotion/styled, @mui/material, @mui/icons-material to yarn version consistency checker --------- Co-authored-by: Hanzo Dev <[email protected]>
188 lines
6.1 KiB
JavaScript
188 lines
6.1 KiB
JavaScript
/* eslint-disable no-undef */
|
|
const { withPodfile, withAppDelegate, withMainApplication, withDangerousMod } = require('expo/config-plugins')
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
// SSL Pinning Configuration will be passed from app.config.js
|
|
|
|
function withIOSSSLPinning(config, { domains }) {
|
|
// Add TrustKit to Podfile
|
|
config = withPodfile(config, (config) => {
|
|
const podfileContent = config.modResults.contents
|
|
|
|
// Check if TrustKit is already added
|
|
if (!podfileContent.includes('TrustKit')) {
|
|
// Add TrustKit pod
|
|
const podfileLines = podfileContent.split('\n')
|
|
const targetIndex = podfileLines.findIndex((line) => line.includes('use_expo_modules!'))
|
|
|
|
if (targetIndex !== -1) {
|
|
podfileLines.splice(targetIndex + 1, 0, " pod 'TrustKit'")
|
|
config.modResults.contents = podfileLines.join('\n')
|
|
}
|
|
}
|
|
|
|
return config
|
|
})
|
|
|
|
// Modify AppDelegate.swift to include SSL pinning
|
|
config = withAppDelegate(config, (config) => {
|
|
const appDelegateContent = config.modResults.contents
|
|
|
|
const domainConfigs = Object.entries(domains)
|
|
.map(([domain, hashes]) => {
|
|
const hashesString = hashes.map((hash) => `"${hash}"`).join(', ')
|
|
return ` "${domain}": [
|
|
kTSKPublicKeyHashes: [${hashesString}],
|
|
kTSKEnforcePinning: true,
|
|
kTSKIncludeSubdomains: true,
|
|
kTSKReportUris: []
|
|
]`
|
|
})
|
|
.join(',\n')
|
|
|
|
const trustKitConfig = `
|
|
// SSL Pinning Configuration
|
|
let trustKitConfig: [String: Any] = [
|
|
kTSKSwizzleNetworkDelegates: true,
|
|
kTSKPinnedDomains: [
|
|
${domainConfigs}
|
|
]
|
|
]
|
|
|
|
TrustKit.initSharedInstance(withConfiguration: trustKitConfig)
|
|
print("SSL Pinning initialized successfully")
|
|
`
|
|
|
|
// Add TrustKit import if not present
|
|
if (!appDelegateContent.includes('import TrustKit')) {
|
|
config.modResults.contents = appDelegateContent.replace(
|
|
'import ReactAppDependencyProvider',
|
|
'import ReactAppDependencyProvider\nimport TrustKit',
|
|
)
|
|
}
|
|
|
|
// Add TrustKit initialization before React Native starts
|
|
if (!appDelegateContent.includes('TrustKit.initSharedInstance')) {
|
|
// Primary injection point: before factory.startReactNative
|
|
const startReactNativePattern = /([ ]+)(factory\.startReactNative\(\s*withModuleName:)/
|
|
|
|
if (startReactNativePattern.test(appDelegateContent)) {
|
|
config.modResults.contents = config.modResults.contents.replace(
|
|
startReactNativePattern,
|
|
`${trustKitConfig}
|
|
$1$2`,
|
|
)
|
|
} else {
|
|
// Fallback: after window initialization
|
|
const windowPattern = /(window = UIWindow\(frame: UIScreen\.main\.bounds\))/
|
|
|
|
if (windowPattern.test(appDelegateContent)) {
|
|
config.modResults.contents = config.modResults.contents.replace(windowPattern, `$1${trustKitConfig}`)
|
|
} else {
|
|
console.warn('⚠️ Could not find suitable injection point for SSL Pinning in AppDelegate.swift')
|
|
}
|
|
}
|
|
}
|
|
|
|
return config
|
|
})
|
|
|
|
return config
|
|
}
|
|
|
|
function withAndroidSSLPinning(config, { domains }) {
|
|
// Create SSLPinningFactory.kt
|
|
config = withDangerousMod(config, [
|
|
'android',
|
|
async (config) => {
|
|
const packageName = config.android?.package || 'global.safe.mobileapp'
|
|
const packagePath = packageName.replace(/\./g, '/')
|
|
const kotlinDir = path.join(config.modRequest.platformProjectRoot, 'app/src/main/java', packagePath)
|
|
|
|
// Ensure directory exists
|
|
if (!fs.existsSync(kotlinDir)) {
|
|
fs.mkdirSync(kotlinDir, { recursive: true })
|
|
}
|
|
|
|
// domains passed from configuration
|
|
const pinningConfig = Object.entries(domains)
|
|
.map(([domain, hashes]) => {
|
|
const hashesString = hashes.map((hash) => `"sha256/${hash}"`).join(', ')
|
|
return ` .add("${domain}", ${hashesString})`
|
|
})
|
|
.join('\n')
|
|
|
|
const sslPinningFactoryContent = `package ${packageName}
|
|
|
|
import com.facebook.react.modules.network.OkHttpClientFactory
|
|
import com.facebook.react.modules.network.ReactCookieJarContainer
|
|
import okhttp3.CertificatePinner
|
|
import okhttp3.OkHttpClient
|
|
|
|
class SSLPinningFactory : OkHttpClientFactory {
|
|
|
|
override fun createNewNetworkModuleClient(): OkHttpClient {
|
|
val certificatePinner = CertificatePinner.Builder()
|
|
${pinningConfig}
|
|
.build()
|
|
|
|
return OkHttpClient.Builder()
|
|
.certificatePinner(certificatePinner)
|
|
.cookieJar(ReactCookieJarContainer())
|
|
.build()
|
|
}
|
|
}`
|
|
|
|
const sslPinningFactoryPath = path.join(kotlinDir, 'SSLPinningFactory.kt')
|
|
fs.writeFileSync(sslPinningFactoryPath, sslPinningFactoryContent)
|
|
|
|
return config
|
|
},
|
|
])
|
|
|
|
// Modify MainApplication.kt
|
|
config = withMainApplication(config, (config) => {
|
|
const mainApplicationContent = config.modResults.contents
|
|
const packageName = config.android?.package || 'global.safe.mobileapp'
|
|
|
|
// Add imports if not present
|
|
if (!mainApplicationContent.includes('import com.facebook.react.modules.network.OkHttpClientProvider')) {
|
|
config.modResults.contents = mainApplicationContent.replace(
|
|
'import com.facebook.react.ReactApplication',
|
|
`import com.facebook.react.ReactApplication
|
|
import com.facebook.react.modules.network.OkHttpClientProvider
|
|
import ${packageName}.SSLPinningFactory`,
|
|
)
|
|
}
|
|
|
|
// Add SSL pinning initialization in onCreate
|
|
if (!mainApplicationContent.includes('OkHttpClientProvider.setOkHttpClientFactory')) {
|
|
const onCreatePattern = /(override fun onCreate\(\) {\s*super\.onCreate\(\))/s
|
|
|
|
config.modResults.contents = config.modResults.contents.replace(
|
|
onCreatePattern,
|
|
`$1
|
|
|
|
// Initialize SSL Pinning
|
|
OkHttpClientProvider.setOkHttpClientFactory(SSLPinningFactory())`,
|
|
)
|
|
}
|
|
|
|
return config
|
|
})
|
|
|
|
return config
|
|
}
|
|
|
|
module.exports = function withSSLPinning(config, options = {}) {
|
|
const { domains = {} } = options
|
|
|
|
// Apply iOS SSL pinning
|
|
config = withIOSSSLPinning(config, { domains })
|
|
|
|
// Apply Android SSL pinning
|
|
config = withAndroidSSLPinning(config, { domains })
|
|
|
|
return config
|
|
}
|