diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 000000000..48354a3df --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,101 @@ +# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore + +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# Android Profiling +*.hprof + +# Cordova plugins for Capacitor +capacitor-cordova-android-plugins + +# Copied web assets +app/src/main/assets/public + +# Generated Config files +app/src/main/assets/capacitor.config.json +app/src/main/assets/capacitor.plugins.json +app/src/main/res/xml/config.xml diff --git a/android/app/.gitignore b/android/app/.gitignore new file mode 100644 index 000000000..043df802a --- /dev/null +++ b/android/app/.gitignore @@ -0,0 +1,2 @@ +/build/* +!/build/.npmkeep diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 000000000..6936b9be2 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,59 @@ +apply plugin: 'com.android.application' + +android { + namespace = "com.peanut.app" + compileSdk = rootProject.ext.compileSdkVersion + defaultConfig { + applicationId "com.peanut.app" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + aaptOptions { + // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. + // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 + ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +repositories { + flatDir{ + dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" + implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" + implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion" + implementation project(':capacitor-android') + + // credential manager provider — required for capacitor-webauthn passkeys + implementation "androidx.credentials:credentials:1.5.0" + implementation "androidx.credentials:credentials-play-services-auth:1.5.0" + + testImplementation "junit:junit:$junitVersion" + androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" + androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" + implementation project(':capacitor-cordova-android-plugins') +} + +apply from: 'capacitor.build.gradle' + +try { + def servicesJSON = file('google-services.json') + if (servicesJSON.text) { + apply plugin: 'com.google.gms.google-services' + } +} catch(Exception e) { + logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work") +} diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle new file mode 100644 index 000000000..ddda33877 --- /dev/null +++ b/android/app/capacitor.build.gradle @@ -0,0 +1,19 @@ +// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN + +android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 + } +} + +apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" +dependencies { + implementation project(':capacitor-webauthn') + +} + + +if (hasProperty('postBuildExtras')) { + postBuildExtras() +} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java b/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java new file mode 100644 index 000000000..570457a31 --- /dev/null +++ b/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.getcapacitor.myapp; + +import static org.junit.Assert.*; + +import android.content.Context; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("com.peanut.app", appContext.getPackageName()); + } +} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..90c21fa76 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/assets/capacitor.config.json b/android/app/src/main/assets/capacitor.config.json new file mode 100644 index 000000000..a08e4541d --- /dev/null +++ b/android/app/src/main/assets/capacitor.config.json @@ -0,0 +1,14 @@ +{ + "appId": "com.peanut.app", + "appName": "Peanut", + "webDir": "out", + "server": { + "url": "https://peanutdev.site", + "cleartext": false + }, + "android": { + "allowMixedContent": true, + "webContentsDebuggingEnabled": true + }, + "plugins": {} +} diff --git a/android/app/src/main/assets/capacitor.plugins.json b/android/app/src/main/assets/capacitor.plugins.json new file mode 100644 index 000000000..7f5490a59 --- /dev/null +++ b/android/app/src/main/assets/capacitor.plugins.json @@ -0,0 +1,6 @@ +[ + { + "pkg": "capacitor-webauthn", + "classpath": "com.headcount.plugins.webauthn.WebauthnPlugin" + } +] diff --git a/android/app/src/main/assets/public/cordova.js b/android/app/src/main/assets/public/cordova.js new file mode 100644 index 000000000..e69de29bb diff --git a/android/app/src/main/assets/public/cordova_plugins.js b/android/app/src/main/assets/public/cordova_plugins.js new file mode 100644 index 000000000..e69de29bb diff --git a/android/app/src/main/java/com/peanut/app/MainActivity.java b/android/app/src/main/java/com/peanut/app/MainActivity.java new file mode 100644 index 000000000..07b6a9422 --- /dev/null +++ b/android/app/src/main/java/com/peanut/app/MainActivity.java @@ -0,0 +1,5 @@ +package com.peanut.app; + +import com.getcapacitor.BridgeActivity; + +public class MainActivity extends BridgeActivity {} diff --git a/android/app/src/main/res/drawable-land-hdpi/splash.png b/android/app/src/main/res/drawable-land-hdpi/splash.png new file mode 100644 index 000000000..e31573b4f Binary files /dev/null and b/android/app/src/main/res/drawable-land-hdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-land-mdpi/splash.png b/android/app/src/main/res/drawable-land-mdpi/splash.png new file mode 100644 index 000000000..f7a64923e Binary files /dev/null and b/android/app/src/main/res/drawable-land-mdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-land-xhdpi/splash.png b/android/app/src/main/res/drawable-land-xhdpi/splash.png new file mode 100644 index 000000000..807725501 Binary files /dev/null and b/android/app/src/main/res/drawable-land-xhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-land-xxhdpi/splash.png b/android/app/src/main/res/drawable-land-xxhdpi/splash.png new file mode 100644 index 000000000..14c6c8fe3 Binary files /dev/null and b/android/app/src/main/res/drawable-land-xxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-land-xxxhdpi/splash.png b/android/app/src/main/res/drawable-land-xxxhdpi/splash.png new file mode 100644 index 000000000..244ca2506 Binary files /dev/null and b/android/app/src/main/res/drawable-land-xxxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-port-hdpi/splash.png b/android/app/src/main/res/drawable-port-hdpi/splash.png new file mode 100644 index 000000000..74faaa583 Binary files /dev/null and b/android/app/src/main/res/drawable-port-hdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-port-mdpi/splash.png b/android/app/src/main/res/drawable-port-mdpi/splash.png new file mode 100644 index 000000000..e944f4ad4 Binary files /dev/null and b/android/app/src/main/res/drawable-port-mdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-port-xhdpi/splash.png b/android/app/src/main/res/drawable-port-xhdpi/splash.png new file mode 100644 index 000000000..564a82ff9 Binary files /dev/null and b/android/app/src/main/res/drawable-port-xhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-port-xxhdpi/splash.png b/android/app/src/main/res/drawable-port-xxhdpi/splash.png new file mode 100644 index 000000000..bfabe6871 Binary files /dev/null and b/android/app/src/main/res/drawable-port-xxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-port-xxxhdpi/splash.png b/android/app/src/main/res/drawable-port-xxxhdpi/splash.png new file mode 100644 index 000000000..692907126 Binary files /dev/null and b/android/app/src/main/res/drawable-port-xxxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..c7bd21dbd --- /dev/null +++ b/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..d5fccc538 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/splash.png b/android/app/src/main/res/drawable/splash.png new file mode 100644 index 000000000..f7a64923e Binary files /dev/null and b/android/app/src/main/res/drawable/splash.png differ diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..b5ad13870 --- /dev/null +++ b/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..036d09bc5 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..036d09bc5 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..c023e5059 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..2127973b2 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..b441f37d6 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..72905b854 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..8ed0605c2 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..9502e47a2 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..4d1e07710 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..df0f15880 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..853db043d Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..6cdf97c11 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..2960cbb61 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..8e3093a86 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..46de6e255 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..d2ea9abed Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..a40d73e9c Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 000000000..c5d5899fd --- /dev/null +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..6a21ae8eb --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + Peanut + Peanut + com.peanut.app + com.peanut.app + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..be874e54a --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/xml/config.xml b/android/app/src/main/res/xml/config.xml new file mode 100644 index 000000000..1b1b0e0dc --- /dev/null +++ b/android/app/src/main/res/xml/config.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/xml/file_paths.xml b/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 000000000..bd0c4d80d --- /dev/null +++ b/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java b/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java new file mode 100644 index 000000000..029732784 --- /dev/null +++ b/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java @@ -0,0 +1,18 @@ +package com.getcapacitor.myapp; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 000000000..f8f0e43b6 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,29 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.13.0' + classpath 'com.google.gms:google-services:4.4.4' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +apply from: "variables.gradle" + +allprojects { + repositories { + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/capacitor-cordova-android-plugins/build.gradle b/android/capacitor-cordova-android-plugins/build.gradle new file mode 100644 index 000000000..b2e25ddd6 --- /dev/null +++ b/android/capacitor-cordova-android-plugins/build.gradle @@ -0,0 +1,59 @@ +ext { + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + cordovaAndroidVersion = project.hasProperty('cordovaAndroidVersion') ? rootProject.ext.cordovaAndroidVersion : '14.0.1' +} + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.13.0' + } +} + +apply plugin: 'com.android.library' + +android { + namespace = "capacitor.cordova.android.plugins" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 + defaultConfig { + minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24 + targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36 + versionCode 1 + versionName "1.0" + } + lintOptions { + abortOnError = false + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 + } +} + +repositories { + google() + mavenCentral() + flatDir{ + dirs 'src/main/libs', 'libs' + } +} + +dependencies { + implementation fileTree(dir: 'src/main/libs', include: ['*.jar']) + implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" + implementation "org.apache.cordova:framework:$cordovaAndroidVersion" + // SUB-PROJECT DEPENDENCIES START + + // SUB-PROJECT DEPENDENCIES END +} + +// PLUGIN GRADLE EXTENSIONS START +apply from: "cordova.variables.gradle" +// PLUGIN GRADLE EXTENSIONS END + +for (def func : cdvPluginPostBuildExtras) { + func() +} \ No newline at end of file diff --git a/android/capacitor-cordova-android-plugins/cordova.variables.gradle b/android/capacitor-cordova-android-plugins/cordova.variables.gradle new file mode 100644 index 000000000..b806d8ad6 --- /dev/null +++ b/android/capacitor-cordova-android-plugins/cordova.variables.gradle @@ -0,0 +1,7 @@ +// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN +ext { + cdvMinSdkVersion = project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24 + // Plugin gradle extensions can append to this to have code run at the end. + cdvPluginPostBuildExtras = [] + cordovaConfig = [:] +} \ No newline at end of file diff --git a/android/capacitor-cordova-android-plugins/src/main/AndroidManifest.xml b/android/capacitor-cordova-android-plugins/src/main/AndroidManifest.xml new file mode 100644 index 000000000..cb9c8aa35 --- /dev/null +++ b/android/capacitor-cordova-android-plugins/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/android/capacitor-cordova-android-plugins/src/main/java/.gitkeep b/android/capacitor-cordova-android-plugins/src/main/java/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/android/capacitor-cordova-android-plugins/src/main/res/.gitkeep b/android/capacitor-cordova-android-plugins/src/main/res/.gitkeep new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/android/capacitor-cordova-android-plugins/src/main/res/.gitkeep @@ -0,0 +1 @@ + diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle new file mode 100644 index 000000000..da0a63ec1 --- /dev/null +++ b/android/capacitor.settings.gradle @@ -0,0 +1,6 @@ +// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN +include ':capacitor-android' +project(':capacitor-android').projectDir = new File('../node_modules/.pnpm/@capacitor+android@8.2.0_@capacitor+core@8.2.0/node_modules/@capacitor/android/capacitor') + +include ':capacitor-webauthn' +project(':capacitor-webauthn').projectDir = new File('../node_modules/.pnpm/capacitor-webauthn@0.0.12_@capacitor+core@8.2.0/node_modules/capacitor-webauthn/android') diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 000000000..2e87c52f8 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,22 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..1b33c55ba Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..7705927e9 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 000000000..23d15a936 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 000000000..5eed7ee84 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 000000000..3b4431d77 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,5 @@ +include ':app' +include ':capacitor-cordova-android-plugins' +project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') + +apply from: 'capacitor.settings.gradle' \ No newline at end of file diff --git a/android/variables.gradle b/android/variables.gradle new file mode 100644 index 000000000..ee4ba41c4 --- /dev/null +++ b/android/variables.gradle @@ -0,0 +1,16 @@ +ext { + minSdkVersion = 24 + compileSdkVersion = 36 + targetSdkVersion = 36 + androidxActivityVersion = '1.11.0' + androidxAppCompatVersion = '1.7.1' + androidxCoordinatorLayoutVersion = '1.3.0' + androidxCoreVersion = '1.17.0' + androidxFragmentVersion = '1.8.9' + coreSplashScreenVersion = '1.2.0' + androidxWebkitVersion = '1.14.0' + junitVersion = '4.13.2' + androidxJunitVersion = '1.3.0' + androidxEspressoCoreVersion = '3.7.0' + cordovaAndroidVersion = '14.0.1' +} \ No newline at end of file diff --git a/capacitor.config.ts b/capacitor.config.ts new file mode 100644 index 000000000..455dc7573 --- /dev/null +++ b/capacitor.config.ts @@ -0,0 +1,23 @@ +import type { CapacitorConfig } from '@capacitor/cli' + +const config: CapacitorConfig = { + appId: 'com.peanut.app', + appName: 'Peanut', + webDir: 'out', + + // cloudflare tunnel for dev testing (passkeys need HTTPS + stable domain) + // TODO: remove server.url for production (static export loads from local out/ directory) + server: { + url: 'https://peanutdev.site', + cleartext: false, + }, + + android: { + allowMixedContent: true, + webContentsDebuggingEnabled: true, + }, + + plugins: {}, +} + +export default config diff --git a/next.config.js b/next.config.js index d212a939d..92ad0d8e9 100644 --- a/next.config.js +++ b/next.config.js @@ -107,10 +107,13 @@ let nextConfig = { source: '/.well-known/apple-app-site-association', destination: '/api/apple-app-site-association', }, - { - source: '/.well-known/assetLinks.json', - destination: '/api/assetLinks', - }, + // disabled for native dev testing — serve local public/.well-known/assetlinks.json + // which has the debug signing key fingerprint + // TODO: re-enable for production (backend should have the release key) + // { + // source: '/.well-known/assetLinks.json', + // destination: '/api/assetLinks', + // }, ], afterFiles: [ // PostHog reverse proxy — bypasses ad blockers diff --git a/next.config.native.js b/next.config.native.js new file mode 100644 index 000000000..2b7180c2b --- /dev/null +++ b/next.config.native.js @@ -0,0 +1,73 @@ +const os = require('os') +const { execSync } = require('child_process') +const withBundleAnalyzer = require('@next/bundle-analyzer')({ + enabled: false, // Disable for native builds +}) + +// Get git commit hash at build time +let gitCommitHash = 'unknown' +try { + gitCommitHash = execSync('git rev-parse --short=7 HEAD').toString().trim() +} catch (error) { + console.warn('Could not get git commit hash:', error.message) +} + +/** @type {import('next').NextConfig} */ +let nextConfig = { + // STATIC EXPORT FOR CAPACITOR + output: 'export', + + // Disable image optimization (requires server) + images: { + unoptimized: true, + }, + + // Required for Capacitor - assets must use relative paths + assetPrefix: '', + + // Trailing slashes help with static file serving + trailingSlash: true, + + env: { + NEXT_PUBLIC_GIT_COMMIT_HASH: gitCommitHash, + // Flag to detect native context in code + NEXT_PUBLIC_IS_NATIVE_BUILD: 'true', + }, + + // Transpile packages for better compatibility + transpilePackages: ['@squirrel-labs/peanut-sdk'], + + // Experimental features for optimization + experimental: { + optimizePackageImports: [ + '@chakra-ui/react', + 'framer-motion', + '@headlessui/react', + '@radix-ui/react-accordion', + '@radix-ui/react-select', + '@radix-ui/react-slider', + '@reduxjs/toolkit', + 'react-redux', + 'lodash', + 'date-fns', + 'react-hook-form', + '@mui/icons-material', + ], + }, + + webpack: (config, { isServer, dev }) => { + if (!dev) { + if (isServer) { + config.ignoreWarnings = [{ module: /@opentelemetry\/instrumentation/, message: /Critical dependency/ }] + } + } + return config + }, + + reactStrictMode: false, + + // Note: rewrites, redirects, and headers don't work with static export + // These would need to be handled by your backend or Capacitor plugins +} + +module.exports = withBundleAnalyzer(nextConfig) diff --git a/package.json b/package.json index a7308b244..79eaaea10 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,9 @@ "verify-content": "tsx scripts/verify-content.ts" }, "dependencies": { + "@capacitor/android": "8.2.0", + "@capacitor/cli": "8.2.0", + "@capacitor/core": "8.2.0", "@dicebear/collection": "^9.2.2", "@dicebear/core": "^9.2.2", "@emotion/react": "^11.14.0", @@ -39,6 +42,7 @@ "@justaname.id/sdk": "0.2.177", "@mui/icons-material": "^7.3.6", "@mui/material": "^7.3.6", + "@noble/curves": "1.9.7", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slider": "^1.3.5", @@ -59,6 +63,7 @@ "@zerodev/sdk": "5.5.7", "autoprefixer": "^10.4.20", "canvas-confetti": "^1.9.3", + "capacitor-webauthn": "0.0.12", "classnames": "^2.5.1", "d3-force": "^3.0.0", "embla-carousel-react": "^8.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 671ed5bda..e297172a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,15 @@ importers: .: dependencies: + '@capacitor/android': + specifier: 8.2.0 + version: 8.2.0(@capacitor/core@8.2.0) + '@capacitor/cli': + specifier: 8.2.0 + version: 8.2.0 + '@capacitor/core': + specifier: 8.2.0 + version: 8.2.0 '@dicebear/collection': specifier: ^9.2.2 version: 9.3.1(@dicebear/core@9.3.1) @@ -43,6 +52,9 @@ importers: '@mui/material': specifier: ^7.3.6 version: 7.3.7(@emotion/react@11.14.0(@types/react@18.3.27)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.27)(react@19.2.4))(@types/react@18.3.27)(react@19.2.4))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@noble/curves': + specifier: 1.9.7 + version: 1.9.7 '@radix-ui/react-accordion': specifier: ^1.2.12 version: 1.2.12(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -69,7 +81,7 @@ importers: version: 9.1.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6) '@sentry/nextjs': specifier: ^8.39.0 - version: 8.55.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(next@16.0.10(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.104.1) + version: 8.55.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@16.0.10(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.104.1) '@serwist/next': specifier: ^9.0.10 version: 9.5.0(next@16.0.10(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(webpack@5.104.1) @@ -103,6 +115,9 @@ importers: canvas-confetti: specifier: ^1.9.3 version: 1.9.4 + capacitor-webauthn: + specifier: 0.0.12 + version: 0.0.12(@capacitor/core@8.2.0) classnames: specifier: ^2.5.1 version: 2.5.1 @@ -490,6 +505,19 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@capacitor/android@8.2.0': + resolution: {integrity: sha512-XLm5OsWLPfXQxDxzFS7SOdMEgGvW+2c7TGLXkTR2cSKdkWK5Abns4imlT5qghKYhjM9r74IrDkBWg/9ALUGNKQ==} + peerDependencies: + '@capacitor/core': ^8.2.0 + + '@capacitor/cli@8.2.0': + resolution: {integrity: sha512-1cMEk0d/I6tl1U+v/lnJR5Oylpx8ZBIHrvQxD5zK0MkjYOUyQAAGJgh97rkhGJqjAUvrGpa8H4BmyhNQN9a17A==} + engines: {node: '>=22.0.0'} + hasBin: true + + '@capacitor/core@8.2.0': + resolution: {integrity: sha512-oKaoNeNtH2iIZMDFVrb1atoyRECDGHcfLMunJ5KWN8DtvpVBeeA4c41e20NTuhMxw1cSYbpq2PV2hb+/9CJxlQ==} + '@coinbase/wallet-sdk@3.9.3': resolution: {integrity: sha512-N/A2DRIf0Y3PHc1XAMvbBUu4zisna6qAdqABMZwBMNEfWrXpAwx16pZGkYCLGE+Rvv1edbcB2LYDRnACNcmCiw==} @@ -1226,10 +1254,46 @@ packages: cpu: [x64] os: [win32] + '@ionic/cli-framework-output@2.2.8': + resolution: {integrity: sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-array@2.1.6': + resolution: {integrity: sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-fs@3.1.7': + resolution: {integrity: sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-object@2.1.6': + resolution: {integrity: sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-process@2.1.12': + resolution: {integrity: sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-stream@3.1.7': + resolution: {integrity: sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-subprocess@3.0.1': + resolution: {integrity: sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-terminal@2.3.5': + resolution: {integrity: sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==} + engines: {node: '>=16.0.0'} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@istanbuljs/load-nyc-config@1.1.0': resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -2958,6 +3022,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/fs-extra@8.1.5': + resolution: {integrity: sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -3037,6 +3104,9 @@ packages: '@types/shimmer@1.2.0': resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} + '@types/slice-ansi@4.0.0': + resolution: {integrity: sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==} + '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -3311,6 +3381,10 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + '@xmldom/xmldom@0.8.12': + resolution: {integrity: sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==} + engines: {node: '>=10.0.0'} + '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -3466,6 +3540,10 @@ packages: resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} engines: {node: '>=4'} + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + astring@1.9.0: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true @@ -3476,6 +3554,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} @@ -3537,6 +3619,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + bare-events@2.8.2: resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} peerDependencies: @@ -3595,6 +3681,10 @@ packages: bezier-js@6.1.4: resolution: {integrity: sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==} + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + big.js@6.2.2: resolution: {integrity: sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==} @@ -3611,12 +3701,20 @@ packages: bowser@2.13.1: resolution: {integrity: sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==} + bplist-parser@0.3.2: + resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} + engines: {node: '>= 5.10.0'} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -3694,6 +3792,11 @@ packages: canvas-confetti@1.9.4: resolution: {integrity: sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==} + capacitor-webauthn@0.0.12: + resolution: {integrity: sha512-sOgk2fyeSgzB+mCd+81J+dXpoqgLBuqjyX6Now9ujjAYQehjPwWvSSlJUxDUhosz6I2yFcXnyunAQK3NkWz6/g==} + peerDependencies: + '@capacitor/core': '>=7.0.0' + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -3733,6 +3836,10 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} @@ -3798,6 +3905,10 @@ packages: resolution: {integrity: sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==} engines: {node: '>=18'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -4022,6 +4133,10 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} @@ -4127,6 +4242,10 @@ packages: electron-to-chromium@1.5.282: resolution: {integrity: sha512-FCPkJtpst28UmFzd903iU7PdeVTfY0KAeJy+Lk0GLZRwgwYHn/irRcaCbQQOmr5Vytc/7rcavsYLvTM8RiHYhQ==} + elementtree@0.1.7: + resolution: {integrity: sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==} + engines: {node: '>= 0.4.0'} + elliptic@6.5.4: resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} @@ -4177,6 +4296,10 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -4482,6 +4605,14 @@ packages: react-dom: optional: true + fs-extra@11.3.4: + resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + engines: {node: '>=14.14'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -4556,6 +4687,10 @@ packages: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} hasBin: true + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -4726,6 +4861,10 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@4.1.3: + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} @@ -4768,6 +4907,11 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + is-extendable@0.1.1: resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} engines: {node: '>=0.10.0'} @@ -4825,6 +4969,10 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -5073,6 +5221,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jsqr@1.4.0: resolution: {integrity: sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==} @@ -5095,6 +5246,10 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + knip@5.82.1: resolution: {integrity: sha512-1nQk+5AcnkqL40kGQXfouzAEXkTR+eSrgo/8m1d0BMei4eAzFwghoXC4gOKbACgBiCof7hE8wkBVDsEvznf85w==} engines: {node: '>=18.18.0'} @@ -5410,6 +5565,10 @@ packages: minimalistic-crypto-utils@1.0.1: resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -5432,6 +5591,14 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + mipd@0.0.7: resolution: {integrity: sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==} peerDependencies: @@ -5478,6 +5645,11 @@ packages: nanospinner@1.2.2: resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} + native-run@2.0.3: + resolution: {integrity: sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q==} + engines: {node: '>=16.0.0'} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -5609,6 +5781,10 @@ packages: oniguruma-to-es@4.3.4: resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==} + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -5699,6 +5875,10 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -5772,6 +5952,10 @@ packages: engines: {node: '>=18'} hasBin: true + plist@3.1.0: + resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} + engines: {node: '>=10.4.0'} + pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} @@ -6271,6 +6455,11 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@6.1.3: + resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} + engines: {node: 20 || >=22} + hasBin: true + rollup@3.29.5: resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} @@ -6296,6 +6485,13 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sax@1.1.4: + resolution: {integrity: sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==} + + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -6408,6 +6604,10 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -6639,6 +6839,10 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + tar@7.5.13: + resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==} + engines: {node: '>=18'} + terser-webpack-plugin@5.3.16: resolution: {integrity: sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==} engines: {node: '>= 10.13.0'} @@ -6677,6 +6881,9 @@ packages: thread-stream@0.15.2: resolution: {integrity: sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==} + through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} @@ -6713,6 +6920,10 @@ packages: resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} engines: {node: '>=12'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -6836,6 +7047,10 @@ packages: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + unplugin@1.0.1: resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==} @@ -6901,6 +7116,10 @@ packages: uploadthing: optional: true + untildify@4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -7187,6 +7406,18 @@ packages: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} + xml2js@0.6.2: + resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + + xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} @@ -7208,6 +7439,10 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -7512,6 +7747,36 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@capacitor/android@8.2.0(@capacitor/core@8.2.0)': + dependencies: + '@capacitor/core': 8.2.0 + + '@capacitor/cli@8.2.0': + dependencies: + '@ionic/cli-framework-output': 2.2.8 + '@ionic/utils-subprocess': 3.0.1 + '@ionic/utils-terminal': 2.3.5 + commander: 12.1.0 + debug: 4.4.3 + env-paths: 2.2.1 + fs-extra: 11.3.4 + kleur: 4.1.5 + native-run: 2.0.3 + open: 8.4.2 + plist: 3.1.0 + prompts: 2.4.2 + rimraf: 6.1.3 + semver: 7.7.3 + tar: 7.5.13 + tslib: 2.8.1 + xml2js: 0.6.2 + transitivePeerDependencies: + - supports-color + + '@capacitor/core@8.2.0': + dependencies: + tslib: 2.8.1 + '@coinbase/wallet-sdk@3.9.3': dependencies: bn.js: 5.2.2 @@ -8357,6 +8622,82 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@ionic/cli-framework-output@2.2.8': + dependencies: + '@ionic/utils-terminal': 2.3.5 + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-array@2.1.6': + dependencies: + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-fs@3.1.7': + dependencies: + '@types/fs-extra': 8.1.5 + debug: 4.4.3 + fs-extra: 9.1.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-object@2.1.6': + dependencies: + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-process@2.1.12': + dependencies: + '@ionic/utils-object': 2.1.6 + '@ionic/utils-terminal': 2.3.5 + debug: 4.4.3 + signal-exit: 3.0.7 + tree-kill: 1.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-stream@3.1.7': + dependencies: + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-subprocess@3.0.1': + dependencies: + '@ionic/utils-array': 2.1.6 + '@ionic/utils-fs': 3.1.7 + '@ionic/utils-process': 2.1.12 + '@ionic/utils-stream': 3.1.7 + '@ionic/utils-terminal': 2.3.5 + cross-spawn: 7.0.6 + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-terminal@2.3.5': + dependencies: + '@types/slice-ansi': 4.0.0 + debug: 4.4.3 + signal-exit: 3.0.7 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + tslib: 2.8.1 + untildify: 4.0.0 + wrap-ansi: 7.0.0 + transitivePeerDependencies: + - supports-color + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -8366,6 +8707,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + '@istanbuljs/load-nyc-config@1.1.0': dependencies: camelcase: 5.3.1 @@ -10442,7 +10787,7 @@ snapshots: '@scure/bip32@1.7.0': dependencies: - '@noble/curves': 1.9.1 + '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 @@ -10540,7 +10885,7 @@ snapshots: '@sentry/core@8.55.0': {} - '@sentry/nextjs@8.55.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(next@16.0.10(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.104.1)': + '@sentry/nextjs@8.55.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@16.0.10(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.104.1)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.39.0 @@ -10548,7 +10893,7 @@ snapshots: '@sentry-internal/browser-utils': 8.55.0 '@sentry/core': 8.55.0 '@sentry/node': 8.55.0 - '@sentry/opentelemetry': 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) + '@sentry/opentelemetry': 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) '@sentry/react': 8.55.0(react@19.2.4) '@sentry/vercel-edge': 8.55.0 '@sentry/webpack-plugin': 2.22.7(webpack@5.104.1) @@ -10617,16 +10962,6 @@ snapshots: '@opentelemetry/semantic-conventions': 1.39.0 '@sentry/core': 8.55.0 - '@sentry/opentelemetry@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 - '@sentry/core': 8.55.0 - '@sentry/react@8.55.0(react@19.2.4)': dependencies: '@sentry/browser': 8.55.0 @@ -10942,6 +11277,10 @@ snapshots: '@types/estree@1.0.8': {} + '@types/fs-extra@8.1.5': + dependencies: + '@types/node': 20.4.2 + '@types/graceful-fs@4.1.9': dependencies: '@types/node': 20.4.2 @@ -11024,6 +11363,8 @@ snapshots: '@types/shimmer@1.2.0': {} + '@types/slice-ansi@4.0.0': {} + '@types/stack-utils@2.0.3': {} '@types/tedious@4.0.14': @@ -11927,6 +12268,8 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 + '@xmldom/xmldom@0.8.12': {} + '@xtuc/ieee754@1.2.0': {} '@xtuc/long@4.2.2': {} @@ -12061,6 +12404,8 @@ snapshots: dependencies: tslib: 2.8.1 + astral-regex@2.0.0: {} + astring@1.9.0: {} async-mutex@0.2.6: @@ -12069,6 +12414,8 @@ snapshots: asynckit@0.4.0: {} + at-least-node@1.0.0: {} + atomic-sleep@1.0.0: {} autoprefixer@10.4.23(postcss@8.5.6): @@ -12159,6 +12506,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + bare-events@2.8.2: {} bare-fs@4.5.3: @@ -12208,6 +12557,8 @@ snapshots: bezier-js@6.1.4: {} + big-integer@1.6.52: {} + big.js@6.2.2: {} binary-extensions@2.3.0: {} @@ -12218,6 +12569,10 @@ snapshots: bowser@2.13.1: {} + bplist-parser@0.3.2: + dependencies: + big-integer: 1.6.52 + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -12227,6 +12582,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -12301,6 +12660,10 @@ snapshots: canvas-confetti@1.9.4: {} + capacitor-webauthn@0.0.12(@capacitor/core@8.2.0): + dependencies: + '@capacitor/core': 8.2.0 + ccount@2.0.1: {} chalk@3.0.0: @@ -12343,6 +12706,8 @@ snapshots: dependencies: readdirp: 5.0.0 + chownr@3.0.0: {} + chrome-trace-event@1.0.4: {} chromium-bidi@8.0.0(devtools-protocol@0.0.1495869): @@ -12395,6 +12760,8 @@ snapshots: commander@12.0.0: {} + commander@12.1.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -12605,6 +12972,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + define-lazy-prop@2.0.0: {} + defu@6.1.4: {} degenerator@5.0.1: @@ -12697,6 +13066,10 @@ snapshots: electron-to-chromium@1.5.282: {} + elementtree@0.1.7: + dependencies: + sax: 1.1.4 + elliptic@6.5.4: dependencies: bn.js: 4.12.2 @@ -12762,6 +13135,8 @@ snapshots: entities@6.0.1: {} + env-paths@2.2.1: {} + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -13174,6 +13549,19 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + fs-extra@11.3.4: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + fs.realpath@1.0.0: {} fsevents@2.3.2: @@ -13249,6 +13637,12 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -13494,6 +13888,8 @@ snapshots: inherits@2.0.4: {} + ini@4.1.3: {} + inline-style-parser@0.2.7: {} internmap@2.0.3: {} @@ -13528,6 +13924,8 @@ snapshots: is-decimal@2.0.1: {} + is-docker@2.2.1: {} + is-extendable@0.1.1: {} is-extglob@2.1.1: {} @@ -13575,6 +13973,10 @@ snapshots: dependencies: which-typed-array: 1.1.20 + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + isarray@1.0.0: {} isarray@2.0.5: {} @@ -14032,6 +14434,12 @@ snapshots: json5@2.2.3: {} + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + jsqr@1.4.0: {} kapsule@1.16.3: @@ -14050,6 +14458,8 @@ snapshots: kleur@3.0.3: {} + kleur@4.1.5: {} + knip@5.82.1(@types/node@20.4.2)(typescript@5.9.3): dependencies: '@nodelib/fs.walk': 1.2.8 @@ -14617,6 +15027,10 @@ snapshots: minimalistic-crypto-utils@1.0.1: {} + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -14635,6 +15049,12 @@ snapshots: minipass@7.1.2: {} + minipass@7.1.3: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.2 + mipd@0.0.7(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -14669,6 +15089,22 @@ snapshots: dependencies: picocolors: 1.1.1 + native-run@2.0.3: + dependencies: + '@ionic/utils-fs': 3.1.7 + '@ionic/utils-terminal': 2.3.5 + bplist-parser: 0.3.2 + debug: 4.4.3 + elementtree: 0.1.7 + ini: 4.1.3 + plist: 3.1.0 + split2: 4.2.0 + through2: 4.0.2 + tslib: 2.8.1 + yauzl: 2.10.0 + transitivePeerDependencies: + - supports-color + natural-compare@1.4.0: {} neo-async@2.6.2: {} @@ -14781,6 +15217,12 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + opener@1.5.2: {} ox@0.11.3(typescript@5.9.3)(zod@3.22.4): @@ -14928,6 +15370,11 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-scurry@2.0.2: + dependencies: + lru-cache: 11.2.5 + minipass: 7.1.3 + path-type@4.0.0: {} pend@1.2.0: {} @@ -14999,6 +15446,12 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + plist@3.1.0: + dependencies: + '@xmldom/xmldom': 0.8.12 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + pngjs@5.0.0: {} pony-cause@2.1.11: {} @@ -15503,6 +15956,11 @@ snapshots: reusify@1.1.0: {} + rimraf@6.1.3: + dependencies: + glob: 13.0.6 + package-json-from-dist: 1.0.1 + rollup@3.29.5: optionalDependencies: fsevents: 2.3.3 @@ -15525,6 +15983,10 @@ snapshots: safer-buffer@2.1.2: {} + sax@1.1.4: {} + + sax@1.6.0: {} + saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -15688,6 +16150,12 @@ snapshots: slash@3.0.0: {} + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + smart-buffer@4.2.0: {} smol-toml@1.6.0: {} @@ -15952,6 +16420,14 @@ snapshots: - bare-abort-controller - react-native-b4a + tar@7.5.13: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 + terser-webpack-plugin@5.3.16(webpack@5.104.1): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -15992,6 +16468,10 @@ snapshots: dependencies: real-require: 0.1.0 + through2@4.0.2: + dependencies: + readable-stream: 3.6.2 + tinycolor2@1.6.0: {} tinyglobby@0.2.15: @@ -16030,6 +16510,8 @@ snapshots: dependencies: punycode: 2.3.1 + tree-kill@1.2.2: {} + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -16147,6 +16629,8 @@ snapshots: universalify@0.2.0: {} + universalify@2.0.1: {} + unplugin@1.0.1: dependencies: acorn: 8.15.0 @@ -16167,6 +16651,8 @@ snapshots: optionalDependencies: idb-keyval: 6.2.2 + untildify@4.0.0: {} + update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: browserslist: 4.28.1 @@ -16517,6 +17003,15 @@ snapshots: xml-name-validator@4.0.0: {} + xml2js@0.6.2: + dependencies: + sax: 1.6.0 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + + xmlbuilder@15.1.1: {} + xmlchars@2.2.0: {} xmlhttprequest-ssl@2.1.2: {} @@ -16529,6 +17024,8 @@ snapshots: yallist@3.1.1: {} + yallist@5.0.0: {} + yaml@1.10.2: {} yaml@2.8.2: {} diff --git a/public/.well-known/assetlinks.json b/public/.well-known/assetlinks.json index 4c36b4165..040d0e389 100644 --- a/public/.well-known/assetlinks.json +++ b/public/.well-known/assetlinks.json @@ -1,16 +1,13 @@ [ { - "relation": ["delegate_permission/common.query_webapk"], + "relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"], "target": { - "namespace": "web", - "site": "https://peanut.me/manifest.webmanifest" - } - }, - { - "relation": ["delegate_permission/common.handle_all_urls"], - "target": { - "namespace": "web", - "site": "https://peanut.me" + "namespace": "android_app", + "package_name": "com.peanut.app", + "sha256_cert_fingerprints": [ + "11:94:75:B7:2F:74:28:DC:D8:B2:FF:FF:A9:A0:6B:2D:78:13:17:F1:15:F6:24:BD:BE:F3:C8:16:38:92:0E:98", + "29:10:BA:ED:60:53:6D:60:A9:B2:D5:DB:7D:AD:9B:04:A5:49:FE:17:6E:89:CF:A4:85:17:19:D1:14:99:F1:EB" + ] } } ] diff --git a/scripts/native-build.js b/scripts/native-build.js new file mode 100644 index 000000000..639bcc873 --- /dev/null +++ b/scripts/native-build.js @@ -0,0 +1,232 @@ +#!/usr/bin/env node + +/** + * Native Build Script + * + * Temporarily disables server-only features (API routes, sitemap, robots, manifest) + * and wraps client dynamic routes for static export compatibility. + */ + +const { execSync } = require('child_process') +const fs = require('fs') +const path = require('path') + +const APP_DIR = path.join(__dirname, '..', 'src', 'app') + +// Files/folders to temporarily disable during native build +const ITEMS_TO_DISABLE = [ + { path: 'api', type: 'dir' }, + { path: 'sitemap.ts', type: 'file' }, + { path: 'robots.ts', type: 'file' }, + { path: 'manifest.ts', type: 'file' }, + { path: 'jobs/route.ts', type: 'file' }, +] + +const MODIFIED_FILES = [] +const WRAPPER_FILES = [] + +function getDisabledPath(itemPath) { + const dir = path.dirname(itemPath) + const base = path.basename(itemPath) + if (dir === '.') { + return `_${base}.disabled` + } + return path.join(dir, `_${base}.disabled`) +} + +function disableItems() { + console.log('📦 Disabling server-only features...') + for (const item of ITEMS_TO_DISABLE) { + const src = path.join(APP_DIR, item.path) + const dest = path.join(APP_DIR, getDisabledPath(item.path)) + if (fs.existsSync(src)) { + fs.renameSync(src, dest) + console.log(` ↳ Disabled: ${item.path}`) + } + } +} + +function restoreItems() { + console.log('🔄 Restoring disabled items...') + for (const item of ITEMS_TO_DISABLE) { + const src = path.join(APP_DIR, getDisabledPath(item.path)) + const dest = path.join(APP_DIR, item.path) + if (fs.existsSync(src)) { + fs.renameSync(src, dest) + console.log(` ↳ Restored: ${item.path}`) + } + } +} + +function findDynamicRoutes(dir, routes = []) { + const items = fs.readdirSync(dir, { withFileTypes: true }) + + for (const item of items) { + if (item.name.includes('.disabled') || item.name.startsWith('_')) continue + + const fullPath = path.join(dir, item.name) + + if (item.isDirectory()) { + if (item.name.includes('[')) { + const pagePath = path.join(fullPath, 'page.tsx') + if (fs.existsSync(pagePath)) { + routes.push(pagePath) + } + } + findDynamicRoutes(fullPath, routes) + } + } + + return routes +} + +function wrapClientComponents() { + console.log('📝 Wrapping client components for static export...') + const dynamicRoutes = findDynamicRoutes(APP_DIR) + + for (const pagePath of dynamicRoutes) { + const content = fs.readFileSync(pagePath, 'utf-8') + + // Skip if already has generateStaticParams (server component) + if (content.includes('generateStaticParams')) { + console.log(` ↳ Skipped (already has generateStaticParams): ${path.relative(APP_DIR, pagePath)}`) + continue + } + + // Check if it's a client component + const isClientComponent = + content.trimStart().startsWith("'use client'") || content.trimStart().startsWith('"use client"') + + if (isClientComponent) { + // For client components, we need to create a wrapper + const dir = path.dirname(pagePath) + + // Find the default export name + const exportMatch = content.match(/export\s+default\s+(?:function\s+)?(\w+)/) + if (!exportMatch) { + console.log(` ↳ Skipped (no default export found): ${path.relative(APP_DIR, pagePath)}`) + continue + } + const componentName = exportMatch[1] + + // Rename original to _ClientPage.tsx + const clientPagePath = path.join(dir, '_ClientPage.tsx') + fs.renameSync(pagePath, clientPagePath) + MODIFIED_FILES.push({ original: pagePath, renamed: clientPagePath }) + + // Check if the component expects params + const hasParams = content.includes('params:') || content.includes('PageProps') + + // Create wrapper page.tsx + const wrapperContent = hasParams + ? `// Auto-generated wrapper for static export +import ClientPage from './_ClientPage' + +export function generateStaticParams() { + return [] +} + +export default function Page(props: any) { + return +} +` + : `// Auto-generated wrapper for static export +import ClientPage from './_ClientPage' + +export function generateStaticParams() { + return [] +} + +export default function Page() { + return +} +` + fs.writeFileSync(pagePath, wrapperContent) + WRAPPER_FILES.push(pagePath) + + console.log(` ↳ Wrapped: ${path.relative(APP_DIR, pagePath)}`) + } else { + // Server component - just add generateStaticParams + MODIFIED_FILES.push({ path: pagePath, original: content, type: 'content' }) + + const staticParamsExport = `// Added for static export compatibility +export function generateStaticParams() { + return [] +} + +` + const newContent = staticParamsExport + content + fs.writeFileSync(pagePath, newContent) + + console.log(` ↳ Added generateStaticParams: ${path.relative(APP_DIR, pagePath)}`) + } + } +} + +function restoreModifiedFiles() { + if (MODIFIED_FILES.length === 0 && WRAPPER_FILES.length === 0) return + + console.log('🔄 Restoring modified files...') + + // Remove wrapper files + for (const wrapperPath of WRAPPER_FILES) { + if (fs.existsSync(wrapperPath)) { + fs.unlinkSync(wrapperPath) + } + } + + // Restore renamed/modified files + for (const file of MODIFIED_FILES) { + if (file.type === 'content') { + // Restore content + fs.writeFileSync(file.path, file.original) + console.log(` ↳ Restored content: ${path.relative(APP_DIR, file.path)}`) + } else { + // Restore renamed file + if (fs.existsSync(file.renamed)) { + fs.renameSync(file.renamed, file.original) + console.log(` ↳ Restored: ${path.relative(APP_DIR, file.original)}`) + } + } + } +} + +async function main() { + let buildSucceeded = false + + try { + disableItems() + wrapClientComponents() + + // Clean build cache to ensure fresh build + console.log('🧹 Cleaning build cache...') + if (fs.existsSync(path.join(__dirname, '..', '.next'))) { + fs.rmSync(path.join(__dirname, '..', '.next'), { recursive: true }) + } + + console.log('\n🏗️ Building static export...\n') + execSync('NATIVE_BUILD=true next build --webpack', { + stdio: 'inherit', + cwd: path.join(__dirname, '..'), + env: { ...process.env, NATIVE_BUILD: 'true' }, + }) + + buildSucceeded = true + console.log('\n✅ Native build completed successfully!') + console.log(' Output directory: ./out') + } catch (error) { + console.error('\n❌ Build failed:', error.message) + process.exitCode = 1 + } finally { + restoreModifiedFiles() + restoreItems() + } + + if (buildSucceeded) { + console.log('\n📱 Next steps:') + console.log(' pnpm cap:sync # Sync with native projects') + console.log(' pnpm cap:open:android # Open in Android Studio') + } +} + +main() diff --git a/src/app/(mobile-ui)/layout.tsx b/src/app/(mobile-ui)/layout.tsx index 24aea9d87..4a938f2aa 100644 --- a/src/app/(mobile-ui)/layout.tsx +++ b/src/app/(mobile-ui)/layout.tsx @@ -24,8 +24,10 @@ import { IS_DEV } from '@/constants/general.consts' import { usePullToRefresh } from '@/hooks/usePullToRefresh' import { useNetworkStatus } from '@/hooks/useNetworkStatus' import { useAccountSetupRedirect } from '@/hooks/useAccountSetupRedirect' +import { useNativePlugins } from '@/hooks/useNativePlugins' const Layout = ({ children }: { children: React.ReactNode }) => { + useNativePlugins() const pathName = usePathname() // Allow access to public paths without authentication diff --git a/src/app/(mobile-ui)/qr/[code]/page.tsx b/src/app/(mobile-ui)/qr/[code]/page.tsx index ec6994235..c19439c4f 100644 --- a/src/app/(mobile-ui)/qr/[code]/page.tsx +++ b/src/app/(mobile-ui)/qr/[code]/page.tsx @@ -1,5 +1,6 @@ 'use client' +import { getAuthToken } from '@/utils/auth-token' import { Button } from '@/components/0_Bruddle/Button' import Card from '@/components/Global/Card' import NavHeader from '@/components/Global/NavHeader' @@ -12,7 +13,6 @@ import ErrorAlert from '@/components/Global/ErrorAlert' import { Icon } from '@/components/Global/Icons/Icon' import { saveRedirectUrl, generateInviteCodeLink, sanitizeRedirectURL } from '@/utils/general.utils' import { getShakeClass } from '@/utils/perk.utils' -import Cookies from 'js-cookie' import { useRedirectQrStatus } from '@/hooks/useRedirectQrStatus' import { useHoldToClaim } from '@/hooks/useHoldToClaim' @@ -91,7 +91,7 @@ export default function RedirectQrClaimPage() { method: 'POST', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${Cookies.get('jwt-token')}`, + Authorization: `Bearer ${getAuthToken()}`, }, body: JSON.stringify({ targetUrl: inviteLink, // Pass the correctly formatted invite link diff --git a/src/app/(setup)/setup/page.tsx b/src/app/(setup)/setup/page.tsx index 2056c9f14..c7b3ecc81 100644 --- a/src/app/(setup)/setup/page.tsx +++ b/src/app/(setup)/setup/page.tsx @@ -10,6 +10,7 @@ import { Suspense, useEffect, useState } from 'react' import { setupSteps as masterSetupSteps } from '../../../components/Setup/Setup.consts' import UnsupportedBrowserModal from '@/components/Global/UnsupportedBrowserModal' import { isLikelyWebview, isDeviceOsSupported } from '@/components/Setup/Setup.utils' +import { isCapacitor } from '@/utils/capacitor' import { getFromCookie } from '@/utils/general.utils' import { DeviceType, useDeviceType } from '@/hooks/useGetDeviceType' import { useAuth } from '@/context/authContext' @@ -41,17 +42,40 @@ function SetupPageContent() { setIsLoading(true) await new Promise((resolve) => setTimeout(resolve, 100)) // ensure other initializations can complete - // check for native passkey support + const localDeviceType = detectedDeviceType + + // in capacitor, passkeys are handled natively — skip all browser/webview/os/pwa checks + // and go straight to the landing (signup) flow + if (isCapacitor()) { + setDeviceType(localDeviceType) + // check for invite code — if present, go to signup instead of landing + const inviteCodeFromCookie = getFromCookie('inviteCode') + const userInviteCode = inviteCode || inviteCodeFromCookie + const targetStep = userInviteCode ? 'signup' : 'landing' + const stepIndex = steps.findIndex((s: ISetupStep) => s.screenId === targetStep) + if (stepIndex !== -1) { + dispatch(setupActions.setStep(stepIndex + 1)) + } + setIsLoading(false) + return + } + + // check if device has a platform authenticator (biometric/pin) + // using UVPA instead of isConditionalMediationAvailable because: + // - conditional mediation is about autofill ui, not actual passkey support + // - isConditionalMediationAvailable returns false in android webviews even when passkeys work + // - UVPA correctly detects whether the device can do passkeys let passkeySupport = true try { - passkeySupport = await PublicKeyCredential.isConditionalMediationAvailable() + if (PublicKeyCredential?.isUserVerifyingPlatformAuthenticatorAvailable) { + passkeySupport = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() + } } catch (e) { passkeySupport = false console.error('Error checking passkey support:', e) } const ua = typeof navigator !== 'undefined' ? navigator.userAgent : '' - const localDeviceType = detectedDeviceType const osSupportedByVersion = isDeviceOsSupported(ua) const webviewByUASignature = isLikelyWebview() // initial webview check based on ua signatures diff --git a/src/app/actions/bridge/get-customer.ts b/src/app/actions/bridge/get-customer.ts index 94e5fccf5..bf5bf265d 100644 --- a/src/app/actions/bridge/get-customer.ts +++ b/src/app/actions/bridge/get-customer.ts @@ -1,8 +1,7 @@ -'use server' - -import { unstable_cache } from 'next/cache' +import { unstable_cache } from '@/utils/no-cache' import { countryData } from '@/components/AddMoney/consts' -import { PEANUT_API_KEY, PEANUT_API_URL } from '@/constants/general.consts' +import { PEANUT_API_URL } from '@/constants/general.consts' +import { getAuthHeaders } from '@/utils/auth-token' type BridgeCustomer = { id: string @@ -41,10 +40,7 @@ export const getBridgeCustomerCountry = async ( const runner = unstable_cache( async () => { const response = await fetch(`${PEANUT_API_URL}/bridge/customers/${bridgeCustomerId}` as string, { - headers: { - 'Content-Type': 'application/json', - 'api-key': PEANUT_API_KEY, - }, + headers: getAuthHeaders(), cache: 'no-store', }) diff --git a/src/app/actions/card.ts b/src/app/actions/card.ts index b6436ea3d..c2892e02a 100644 --- a/src/app/actions/card.ts +++ b/src/app/actions/card.ts @@ -1,13 +1,9 @@ -'use server' +// card api calls — works in both web (server action) and native (client-side) +// migrated from 'use server' to support capacitor static export import { PEANUT_API_URL } from '@/constants/general.consts' import { fetchWithSentry } from '@/utils/sentry.utils' -import { getJWTCookie } from '@/utils/cookie-migration.utils' - -const API_KEY = process.env.PEANUT_API_KEY -if (!API_KEY) { - throw new Error('PEANUT_API_KEY environment variable is not set') -} +import { getAuthHeaders } from '@/utils/auth-token' export interface CardInfoResponse { hasPurchased: boolean @@ -43,18 +39,10 @@ export interface CardErrorResponse { * Get card pioneer info for the authenticated user */ export const getCardInfo = async (): Promise<{ data?: CardInfoResponse; error?: string }> => { - const jwtToken = (await getJWTCookie())?.value - if (!jwtToken) { - return { error: 'Authentication required' } - } - try { const response = await fetchWithSentry(`${PEANUT_API_URL}/card`, { method: 'GET', - headers: { - Authorization: `Bearer ${jwtToken}`, - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), }) if (!response.ok) { @@ -73,19 +61,10 @@ export const getCardInfo = async (): Promise<{ data?: CardInfoResponse; error?: * Initiate card pioneer purchase */ export const purchaseCard = async (): Promise<{ data?: CardPurchaseResponse; error?: string; errorCode?: string }> => { - const jwtToken = (await getJWTCookie())?.value - if (!jwtToken) { - return { error: 'Authentication required', errorCode: 'NOT_AUTHENTICATED' } - } - try { const response = await fetchWithSentry(`${PEANUT_API_URL}/card/purchase`, { method: 'POST', - headers: { - Authorization: `Bearer ${jwtToken}`, - 'api-key': API_KEY, - 'Content-Type': 'application/json', - }, + headers: getAuthHeaders(), body: JSON.stringify({}), }) diff --git a/src/app/actions/claimLinks.ts b/src/app/actions/claimLinks.ts index e3feeeffe..2007f32b2 100644 --- a/src/app/actions/claimLinks.ts +++ b/src/app/actions/claimLinks.ts @@ -1,5 +1,4 @@ -'use server' -import { unstable_cache } from 'next/cache' +import { unstable_cache } from '@/utils/no-cache' import peanut, { interfaces as peanutInterfaces } from '@squirrel-labs/peanut-sdk' import type { Address, Hash } from 'viem' import { getContract } from 'viem' diff --git a/src/app/actions/currency.ts b/src/app/actions/currency.ts index 39a1d84be..bcaa5883c 100644 --- a/src/app/actions/currency.ts +++ b/src/app/actions/currency.ts @@ -1,4 +1,3 @@ -'use server' import { getExchangeRate } from './exchange-rate' import { AccountType } from '@/interfaces' import { mantecaApi } from '@/services/manteca' diff --git a/src/app/actions/ens.ts b/src/app/actions/ens.ts index 6e9998134..fca5a7883 100644 --- a/src/app/actions/ens.ts +++ b/src/app/actions/ens.ts @@ -1,16 +1,12 @@ -'use server' -import { unstable_cache } from 'next/cache' +import { unstable_cache } from '@/utils/no-cache' import { fetchWithSentry } from '@/utils/sentry.utils' import { PEANUT_API_URL } from '@/constants/general.consts' - -const API_KEY = process.env.PEANUT_API_KEY! +import { getAuthHeaders } from '@/utils/auth-token' export const resolveEns = unstable_cache( async (ensName: string): Promise => { const response = await fetchWithSentry(`${PEANUT_API_URL}/ens/${ensName}`, { - headers: { - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), }) if (response.status === 404) return undefined diff --git a/src/app/actions/exchange-rate.ts b/src/app/actions/exchange-rate.ts index 0ed97f55b..273b5370c 100644 --- a/src/app/actions/exchange-rate.ts +++ b/src/app/actions/exchange-rate.ts @@ -1,9 +1,7 @@ -'use server' - import { fetchWithSentry } from '@/utils/sentry.utils' import { AccountType } from '@/interfaces' - -const API_KEY = process.env.PEANUT_API_KEY! +import { PEANUT_API_URL } from '@/constants/general.consts' +import { getAuthHeaders } from '@/utils/auth-token' export interface ExchangeRateResponse { from: string @@ -15,7 +13,7 @@ export interface ExchangeRateResponse { } /** - * Server Action to fetch the current exchange rate for a given bank account type. + * Fetch the current exchange rate for a given bank account type. * * This calls the `/bridge/exchange-rate` API endpoint. * @@ -25,23 +23,13 @@ export interface ExchangeRateResponse { export async function getExchangeRate( accountType: AccountType ): Promise<{ data?: ExchangeRateResponse; error?: string }> { - const apiUrl = process.env.PEANUT_API_URL - - if (!apiUrl || !API_KEY) { - console.error('API URL or API Key is not configured.') - return { error: 'Server configuration error.' } - } - try { - const url = new URL(`${apiUrl}/bridge/exchange-rate`) + const url = new URL(`${PEANUT_API_URL}/bridge/exchange-rate`) url.searchParams.append('accountType', accountType) const response = await fetchWithSentry(url.toString(), { method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), }) const data = await response.json() diff --git a/src/app/actions/external-accounts.ts b/src/app/actions/external-accounts.ts index bc47f33bb..d97ad533f 100644 --- a/src/app/actions/external-accounts.ts +++ b/src/app/actions/external-accounts.ts @@ -1,23 +1,17 @@ -'use server' - import { fetchWithSentry } from '@/utils/sentry.utils' import { type AddBankAccountPayload } from './types/users.types' import { type IBridgeAccount } from '@/interfaces' - -const API_KEY = process.env.PEANUT_API_KEY! -const API_URL = process.env.PEANUT_API_URL! +import { PEANUT_API_URL } from '@/constants/general.consts' +import { getAuthHeaders } from '@/utils/auth-token' export async function createBridgeExternalAccountForGuest( customerId: string, accountDetails: AddBankAccountPayload ): Promise { try { - const response = await fetchWithSentry(`${API_URL}/bridge/customers/${customerId}/external-accounts`, { + const response = await fetchWithSentry(`${PEANUT_API_URL}/bridge/customers/${customerId}/external-accounts`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), body: JSON.stringify({ ...accountDetails, reuseOnError: true }), // note: reuseOnError is used to avoid showing errors for duplicate accounts on guest flow }) diff --git a/src/app/actions/history.ts b/src/app/actions/history.ts index fc6d163ef..e92441982 100644 --- a/src/app/actions/history.ts +++ b/src/app/actions/history.ts @@ -1,5 +1,3 @@ -'use server' - import { EHistoryEntryType, completeHistoryEntry } from '@/utils/history.utils' import type { HistoryEntry } from '@/utils/history.utils' import { PEANUT_API_URL } from '@/constants/general.consts' diff --git a/src/app/actions/ibanToBic.ts b/src/app/actions/ibanToBic.ts index 830182065..cfa3188bb 100644 --- a/src/app/actions/ibanToBic.ts +++ b/src/app/actions/ibanToBic.ts @@ -1,5 +1,3 @@ -'use server' - // @ts-ignore: CommonJS module without types import { ibanToBic } from 'iban-to-bic' diff --git a/src/app/actions/invites.ts b/src/app/actions/invites.ts index eff8d6d0e..7dc17083a 100644 --- a/src/app/actions/invites.ts +++ b/src/app/actions/invites.ts @@ -1,27 +1,14 @@ -'use server' - import { fetchWithSentry } from '@/utils/sentry.utils' import { PEANUT_API_URL } from '@/constants/general.consts' - -const API_KEY = process.env.PEANUT_API_KEY! +import { getAuthHeaders } from '@/utils/auth-token' export async function validateInviteCode( inviteCode: string ): Promise<{ data?: { success: boolean; username: string }; error?: string }> { - const apiUrl = PEANUT_API_URL - - if (!apiUrl || !API_KEY) { - console.error('API URL or API Key is not configured.') - return { error: 'Server configuration error.' } - } - try { - const response = await fetchWithSentry(`${apiUrl}/invites/validate`, { + const response = await fetchWithSentry(`${PEANUT_API_URL}/invites/validate`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), body: JSON.stringify({ inviteCode }), }) diff --git a/src/app/actions/offramp.ts b/src/app/actions/offramp.ts index b4d9a45dd..c9f0c91cf 100644 --- a/src/app/actions/offramp.ts +++ b/src/app/actions/offramp.ts @@ -1,10 +1,7 @@ -'use server' - import { type TCreateOfframpRequest } from '../../services/services.types' import { fetchWithSentry } from '@/utils/sentry.utils' -import { getJWTCookie } from '@/utils/cookie-migration.utils' - -const API_KEY = process.env.PEANUT_API_KEY! +import { PEANUT_API_URL } from '@/constants/general.consts' +import { getAuthHeaders } from '@/utils/auth-token' export type CreateOfframpSuccessResponse = { transferId: string @@ -15,7 +12,7 @@ export type CreateOfframpSuccessResponse = { } /** - * server Action to initiate an off-ramp transfer. + * Initiate an off-ramp transfer. * * calls the `/bridge/offramp/create` API endpoint to create the transfer * and returns the provider's instructions for the user to deposit funds @@ -26,27 +23,10 @@ export type CreateOfframpSuccessResponse = { export async function createOfframp( params: TCreateOfframpRequest ): Promise<{ data?: CreateOfframpSuccessResponse; error?: string }> { - const apiUrl = process.env.PEANUT_API_URL - - if (!apiUrl || !API_KEY) { - console.error('API URL or API Key is not configured.') - return { error: 'Server configuration error.' } - } - try { - const jwtToken = (await getJWTCookie())?.value - - if (!jwtToken) { - return { error: 'Authentication token not found.' } - } - - const response = await fetchWithSentry(`${apiUrl}/bridge/offramp/create`, { + const response = await fetchWithSentry(`${PEANUT_API_URL}/bridge/offramp/create`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${jwtToken}`, - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), body: JSON.stringify({ ...params, provider: 'bridge', // note: bridge is currently the only provider @@ -72,20 +52,10 @@ export async function createOfframp( export async function createOfframpForGuest( params: TCreateOfframpRequest ): Promise<{ data?: CreateOfframpSuccessResponse; error?: string }> { - const apiUrl = process.env.PEANUT_API_URL - - if (!apiUrl || !API_KEY) { - console.error('API URL or API Key is not configured.') - return { error: 'Server configuration error.' } - } - try { - const response = await fetchWithSentry(`${apiUrl}/bridge/offramp/create-for-guest`, { + const response = await fetchWithSentry(`${PEANUT_API_URL}/bridge/offramp/create-for-guest`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), body: JSON.stringify({ ...params, provider: 'bridge', @@ -109,7 +79,7 @@ export async function createOfframpForGuest( } /** - * Server Action to confirm an off-ramp transfer after the user has sent funds. + * Confirm an off-ramp transfer after the user has sent funds. * * this calls the `/bridge/transfers/:transferId/confirm` API endpoint, providing * the on-chain transaction hash. This makes the transfer visible in the user's history. @@ -122,27 +92,10 @@ export async function confirmOfframp( transferId: string, txHash: string ): Promise<{ data?: { success: boolean }; error?: string }> { - const apiUrl = process.env.PEANUT_API_URL - - if (!apiUrl || !API_KEY) { - console.error('API URL or API Key is not configured.') - return { error: 'Server configuration error.' } - } - try { - const jwtToken = (await getJWTCookie())?.value - - if (!jwtToken) { - return { error: 'Authentication token not found.' } - } - - const response = await fetchWithSentry(`${apiUrl}/bridge/transfers/${transferId}/confirm`, { + const response = await fetchWithSentry(`${PEANUT_API_URL}/bridge/transfers/${transferId}/confirm`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${jwtToken}`, - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), body: JSON.stringify({ txHash }), }) diff --git a/src/app/actions/onramp.ts b/src/app/actions/onramp.ts index d4b4a4434..7fbec274f 100644 --- a/src/app/actions/onramp.ts +++ b/src/app/actions/onramp.ts @@ -1,12 +1,9 @@ -'use server' - import { fetchWithSentry } from '@/utils/sentry.utils' import { type CountryData } from '@/components/AddMoney/consts' import { getCurrencyConfig } from '@/utils/bridge.utils' import { getCurrencyPrice } from '@/app/actions/currency' -import { getJWTCookie } from '@/utils/cookie-migration.utils' - -const API_KEY = process.env.PEANUT_API_KEY! +import { PEANUT_API_URL } from '@/constants/general.consts' +import { getAuthHeaders } from '@/utils/auth-token' export interface CreateOnrampGuestParams { amount: string @@ -16,7 +13,7 @@ export interface CreateOnrampGuestParams { } /** - * Server Action to cancel an on-ramp transfer. + * Cancel an on-ramp transfer. * * calls the `/bridge/onramp/:transferId/cancel` API endpoint to cancel the transfer * and returns the success status or error message. @@ -25,27 +22,10 @@ export interface CreateOnrampGuestParams { * @returns An object containing either the successful response data or an error. */ export async function cancelOnramp(transferId: string): Promise<{ data?: { success: boolean }; error?: string }> { - const apiUrl = process.env.PEANUT_API_URL - - if (!apiUrl || !API_KEY) { - console.error('API URL or API Key is not configured.') - return { error: 'Server configuration error.' } - } - try { - const jwtToken = (await getJWTCookie())?.value - - if (!jwtToken) { - return { error: 'Authentication token not found.' } - } - - const response = await fetchWithSentry(`${apiUrl}/bridge/onramp/${transferId}/cancel`, { + const response = await fetchWithSentry(`${PEANUT_API_URL}/bridge/onramp/${transferId}/cancel`, { method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${jwtToken}`, - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), }) if (!response.ok) { @@ -66,24 +46,14 @@ export async function cancelOnramp(transferId: string): Promise<{ data?: { succe export async function createOnrampForGuest( params: CreateOnrampGuestParams ): Promise<{ data?: { success: boolean }; error?: string }> { - const apiUrl = process.env.PEANUT_API_URL - - if (!apiUrl || !API_KEY) { - console.error('API URL or API Key is not configured.') - return { error: 'Server configuration error.' } - } - try { const { currency, paymentRail } = getCurrencyConfig(params.country.id, 'onramp') const price = await getCurrencyPrice(currency) const amount = (Number(params.amount) * price.buy).toFixed(2) - const response = await fetchWithSentry(`${apiUrl}/bridge/onramp/create-for-guest`, { + const response = await fetchWithSentry(`${PEANUT_API_URL}/bridge/onramp/create-for-guest`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), body: JSON.stringify({ amount, userId: params.userId, diff --git a/src/app/actions/squid.ts b/src/app/actions/squid.ts index 38b7777c0..ef249f4c7 100644 --- a/src/app/actions/squid.ts +++ b/src/app/actions/squid.ts @@ -1,7 +1,5 @@ -'use server' - import { getSquidChains, getSquidTokens } from '@squirrel-labs/peanut-sdk' -import { unstable_cache } from 'next/cache' +import { unstable_cache } from '@/utils/no-cache' import { interfaces } from '@squirrel-labs/peanut-sdk' import { supportedPeanutChains } from '@/constants/general.consts' diff --git a/src/app/actions/sumsub.ts b/src/app/actions/sumsub.ts index 7222004c6..c8332ce08 100644 --- a/src/app/actions/sumsub.ts +++ b/src/app/actions/sumsub.ts @@ -1,23 +1,13 @@ -'use server' - import { type InitiateSumsubKycResponse, type KYCRegionIntent } from './types/sumsub.types' import { fetchWithSentry } from '@/utils/sentry.utils' import { PEANUT_API_URL } from '@/constants/general.consts' -import { getJWTCookie } from '@/utils/cookie-migration.utils' - -const API_KEY = process.env.PEANUT_API_KEY! +import { getAuthHeaders } from '@/utils/auth-token' // initiate kyc flow (using sumsub) and get websdk access token export const initiateSumsubKyc = async (params?: { regionIntent?: KYCRegionIntent levelName?: string }): Promise<{ data?: InitiateSumsubKycResponse; error?: string }> => { - const jwtToken = (await getJWTCookie())?.value - - if (!jwtToken) { - return { error: 'Authentication required' } - } - const body: Record = { regionIntent: params?.regionIntent, levelName: params?.levelName, @@ -26,11 +16,7 @@ export const initiateSumsubKyc = async (params?: { try { const response = await fetchWithSentry(`${PEANUT_API_URL}/users/identity`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${jwtToken}`, - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), body: JSON.stringify(body), }) diff --git a/src/app/actions/tokens.ts b/src/app/actions/tokens.ts index d84632811..a8881155a 100644 --- a/src/app/actions/tokens.ts +++ b/src/app/actions/tokens.ts @@ -1,5 +1,4 @@ -'use server' -import { unstable_cache } from 'next/cache' +import { unstable_cache } from '@/utils/no-cache' import { isAddressZero, estimateIfIsStableCoinFromPrice, diff --git a/src/app/actions/users.ts b/src/app/actions/users.ts index 07e1498ef..630ac5e96 100644 --- a/src/app/actions/users.ts +++ b/src/app/actions/users.ts @@ -1,26 +1,16 @@ -'use server' - import { type ApiUser } from '@/services/users' import { fetchWithSentry } from '@/utils/sentry.utils' import { type AddBankAccountPayload, BridgeEndorsementType, type InitiateKycResponse } from './types/users.types' import { type User } from '@/interfaces' import { type ContactsResponse } from '@/interfaces' import { PEANUT_API_URL } from '@/constants/general.consts' -import { getJWTCookie } from '@/utils/cookie-migration.utils' - -const API_KEY = process.env.PEANUT_API_KEY! +import { getAuthHeaders } from '@/utils/auth-token' export const updateUserById = async (payload: Record): Promise<{ data?: ApiUser; error?: string }> => { - const jwtToken = (await getJWTCookie())?.value - try { const response = await fetchWithSentry(`${PEANUT_API_URL}/update-user`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${jwtToken}`, - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), body: JSON.stringify(payload), }) @@ -38,15 +28,10 @@ export const updateUserById = async (payload: Record): Promise<{ da export const getKycDetails = async (params?: { endorsements: BridgeEndorsementType[] }): Promise<{ data?: InitiateKycResponse; error?: string }> => { - const jwtToken = (await getJWTCookie())?.value try { const response = await fetchWithSentry(`${PEANUT_API_URL}/users/initiate-kyc`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${jwtToken}`, - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), body: JSON.stringify(params || {}), }) @@ -66,16 +51,10 @@ export const getKycDetails = async (params?: { } export const addBankAccount = async (payload: AddBankAccountPayload): Promise<{ data?: any; error?: string }> => { - const jwtToken = (await getJWTCookie())?.value - try { const response = await fetchWithSentry(`${PEANUT_API_URL}/users/accounts`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${jwtToken}`, - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), body: JSON.stringify(payload), }) @@ -98,10 +77,7 @@ export async function getUserById(userId: string): Promise { try { const response = await fetchWithSentry(`${PEANUT_API_URL}/users/${userId}`, { method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), }) if (!response.ok) { @@ -123,12 +99,6 @@ export async function getContacts(params: { offset: number search?: string }): Promise<{ data?: ContactsResponse; error?: string }> { - const jwtToken = (await getJWTCookie())?.value - - if (!jwtToken) { - throw new Error('Not authenticated') - } - try { const queryParams = new URLSearchParams({ limit: params.limit.toString(), @@ -142,11 +112,7 @@ export async function getContacts(params: { const response = await fetchWithSentry(`${PEANUT_API_URL}/users/contacts?${queryParams}`, { method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'api-key': API_KEY, - Authorization: `Bearer ${jwtToken}`, - }, + headers: getAuthHeaders(), }) if (!response.ok) { @@ -163,15 +129,10 @@ export async function getContacts(params: { // fetch bridge ToS acceptance link for users with pending ToS export const getBridgeTosLink = async (): Promise<{ data?: { tosLink: string }; error?: string }> => { - const jwtToken = (await getJWTCookie())?.value try { const response = await fetchWithSentry(`${PEANUT_API_URL}/users/bridge-tos-link`, { method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${jwtToken}`, - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), }) const responseJson = await response.json() if (!response.ok) { @@ -185,15 +146,10 @@ export const getBridgeTosLink = async (): Promise<{ data?: { tosLink: string }; // confirm bridge ToS acceptance after user closes the ToS iframe export const confirmBridgeTos = async (): Promise<{ data?: { accepted: boolean }; error?: string }> => { - const jwtToken = (await getJWTCookie())?.value try { const response = await fetchWithSentry(`${PEANUT_API_URL}/users/bridge-tos-confirm`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${jwtToken}`, - 'api-key': API_KEY, - }, + headers: getAuthHeaders(), body: JSON.stringify({}), }) const responseJson = await response.json() diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4e88336ff..07e233c46 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -161,7 +161,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) {process.env.NODE_ENV !== 'development' && (