chore(capacitor): base plugins

This commit is contained in:
jubnl
2026-08-24 00:23:51 +02:00
parent b2c029f02a
commit 9f1f33213b
24 changed files with 847 additions and 8 deletions
+7 -1
View File
@@ -62,7 +62,13 @@ captures/
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Deliberately ignored: this file pins the build to one Firebase project.
# Committing it would make every fork's builds register against the upstream
# FCM project, and rotating the project would become a repo change. Official
# builds inject it from a CI secret; see google-services.json.example.
# app/build.gradle already tolerates its absence (push is simply inert).
google-services.json
app/google-services.json
# Freeline
freeline.py
+18 -1
View File
@@ -9,7 +9,24 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-app')
implementation project(':capacitor-browser')
implementation project(':capacitor-calendar')
implementation project(':capacitor-camera')
implementation project(':capacitor-file-transfer')
implementation project(':capacitor-filesystem')
implementation project(':capacitor-geolocation')
implementation project(':capacitor-haptics')
implementation project(':capacitor-inappbrowser')
implementation project(':capacitor-keyboard')
implementation project(':capacitor-local-notifications')
implementation project(':capacitor-network')
implementation project(':capacitor-preferences')
implementation project(':capacitor-push-notifications')
implementation project(':capacitor-share')
implementation project(':capacitor-splash-screen')
implementation project(':capacitor-status-bar')
implementation project(':capacitor-toast')
}
+120 -2
View File
@@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
@@ -22,6 +24,29 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Deep links (@capacitor/app appUrlOpen).
The scheme is @string/custom_url_scheme (com.liketrek.trek).
launchMode is singleTask above, so a link delivers into the
running task via onNewIntent rather than starting a second copy
of the app on top of the first.
This is the return path for native OIDC/OAuth login: the flow
opens in a Custom Tab and the provider redirects back to
com.liketrek.trek://… , which lands here.
Not an App Link (autoVerify + https). App Links are verified
against a specific domain via assetlinks.json, and TREK is
self-hosted — there is no single domain to verify against. A
custom scheme is the only form that works for every user's
server. -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/custom_url_scheme" />
</intent-filter>
</activity>
<provider
@@ -33,9 +58,102 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>
<!-- Notification icon, shared by @capacitor/push-notifications (via this
FCM meta-data) and @capacitor/local-notifications (via the smallIcon
option in capacitor.config.ts).
It must be a white silhouette on transparency; the launcher icon is a
full-colour square and Android would render it as a white blob.
notification_icon is generated from public/icons/icon-white.svg by
scripts/generate-native-icons.mjs.
default_notification_channel_id names the channel FCM uses for any
message that does not carry its own. The channel itself still has to
be created at runtime with PushNotifications.createChannel() using
this exact id — Android drops notifications for a channel that was
never created, and FCM silently falls back to its own channel.
No default_notification_color on purpose. TREK's palette is neutral
and user-selectable, so there is no one brand colour to tint with;
left unset, Android tints the small icon with the system accent,
which stays legible in both the light and dark shade. -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/notification_icon" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
<!-- Asks Play services to install the backported Android photo picker on
Android 11/12 devices, so @capacitor/camera gets the modern picker
instead of falling back to ACTION_OPEN_DOCUMENT. Disabled by design:
it is a dependency declaration, not a service that runs.
Drop this block if TREK ever ships a Play-services-free build
(F-Droid); the fallback picker keeps working without it. -->
<service
android:name="com.google.android.gms.metadata.ModuleDependencies"
android:enabled="false"
android:exported="false"
tools:ignore="MissingClass">
<intent-filter>
<action android:name="com.google.android.gms.metadata.MODULE_DEPENDENCIES" />
</intent-filter>
<meta-data android:name="photopicker_activity:0:required" android:value="" />
</service>
</application>
<!-- Permissions -->
<!-- Permissions.
Declared here only where the app itself needs them. Plugins that ship
their own manifest contribute theirs through the merge and are NOT
repeated: @capacitor/haptics (VIBRATE), @capacitor/calendar
(READ_CALENDAR, WRITE_CALENDAR) and @capacitor/local-notifications
(RECEIVE_BOOT_COMPLETED, WAKE_LOCK, SCHEDULE_EXACT_ALARM). -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Reachability: connectivity.ts probes the server on an interval, and on
native we want to skip the probe entirely when there is no transport.
@capacitor/network declares this too; kept explicit because
connectivity.ts depends on it directly. -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Android 13+ gates notifications behind a runtime grant. firebase-messaging
and @capacitor/local-notifications declare this too, but relying on a
transitive manifest merge for a permission the app must request by hand is
a silent-failure waiting to happen — it must be requested via
requestPermissions() before register()/schedule(). -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- @capacitor/geolocation. Declares no permissions of its own, so both are
required here. COARSE is listed first because it is the weaker grant and
is enough for "what city am I in" — request FINE only where the map
genuinely needs it. -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- required="false" on purpose: TREK is a planner, and everything except
"locate me" works without GPS hardware. Marking it required would have
Play hide the app from tablets and other GPS-less devices. -->
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
<!-- @capacitor/camera. Not strictly required — the plugin launches the system
camera activity, which carries its own permission — but io.ionic.libs's
inappbrowser transitively declares CAMERA anyway, and once it is declared
Android routes the plugin through the runtime-grant path. Declared here
explicitly so it is a decision rather than something inherited. -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- Stripped from the merge. io.ionic.libs:ioninappbrowser-android declares
RECORD_AUDIO and MODIFY_AUDIO_SETTINGS so that pages opened in its WebView
mode can use getUserMedia for microphone capture. TREK records no audio
anywhere, and shipping these would put "Microphone" on the store listing
and in the app's permission screen for a capability that does not exist.
Revisit only if a feature genuinely needs mic input in an embedded page. -->
<uses-permission android:name="android.permission.RECORD_AUDIO" tools:node="remove" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" tools:node="remove" />
<!-- Deliberately NOT declared: READ/WRITE_EXTERNAL_STORAGE.
@capacitor/camera needs storage permissions only for saveToGallery, and
TREK uploads photos to the server rather than writing them to the device
gallery. Add them the day a feature actually needs them. -->
</manifest>
Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -4,4 +4,8 @@
<string name="title_activity_main">TREK - Plan your trips!</string>
<string name="package_name">com.liketrek.trek</string>
<string name="custom_url_scheme">com.liketrek.trek</string>
<!-- Referenced by the FCM default_notification_channel_id meta-data in
AndroidManifest.xml. A channel with this exact id must also be created
at runtime via PushNotifications.createChannel(). -->
<string name="default_notification_channel_id">trek_default</string>
</resources>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
TREK is self-hosted: the user points the app at their own server, so we cannot
know its scheme or its certificate authority ahead of time. Two deliberate
relaxations, both required for that to work at all:
1. cleartextTrafficPermitted="true" — Android blocks http:// by default from
targetSdk 28 on. A bare LAN server (http://192.168.1.10:3001) and a
Tailscale host (100.64.0.0/10, which is NOT RFC1918, so a private-range
allowlist would not cover it) both need this. https:// is unaffected and
remains the recommended setup.
2. <certificates src="user" /> — since Android 7 apps ignore user-installed
CAs unless they opt in. Self-hosters running their own CA (step-ca, mkcert,
an internal PKI) install its root on the device; without this the app would
reject their perfectly valid https:// server. System CAs stay first so
public certs keep validating normally.
The tradeoff is that a user-installed CA can also be a MITM proxy's. That is
the same tradeoff the browser-based PWA already makes, and it is the price of
letting people run their own infrastructure.
-->
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
+54
View File
@@ -1,3 +1,57 @@
// 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/@capacitor/android/capacitor')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../../node_modules/@capacitor/app/android')
include ':capacitor-browser'
project(':capacitor-browser').projectDir = new File('../../node_modules/@capacitor/browser/android')
include ':capacitor-calendar'
project(':capacitor-calendar').projectDir = new File('../../node_modules/@capacitor/calendar/android')
include ':capacitor-camera'
project(':capacitor-camera').projectDir = new File('../../node_modules/@capacitor/camera/android')
include ':capacitor-file-transfer'
project(':capacitor-file-transfer').projectDir = new File('../../node_modules/@capacitor/file-transfer/android')
include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../../node_modules/@capacitor/filesystem/android')
include ':capacitor-geolocation'
project(':capacitor-geolocation').projectDir = new File('../../node_modules/@capacitor/geolocation/android')
include ':capacitor-haptics'
project(':capacitor-haptics').projectDir = new File('../../node_modules/@capacitor/haptics/android')
include ':capacitor-inappbrowser'
project(':capacitor-inappbrowser').projectDir = new File('../../node_modules/@capacitor/inappbrowser/android')
include ':capacitor-keyboard'
project(':capacitor-keyboard').projectDir = new File('../../node_modules/@capacitor/keyboard/android')
include ':capacitor-local-notifications'
project(':capacitor-local-notifications').projectDir = new File('../../node_modules/@capacitor/local-notifications/android')
include ':capacitor-network'
project(':capacitor-network').projectDir = new File('../../node_modules/@capacitor/network/android')
include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../../node_modules/@capacitor/preferences/android')
include ':capacitor-push-notifications'
project(':capacitor-push-notifications').projectDir = new File('../../node_modules/@capacitor/push-notifications/android')
include ':capacitor-share'
project(':capacitor-share').projectDir = new File('../../node_modules/@capacitor/share/android')
include ':capacitor-splash-screen'
project(':capacitor-splash-screen').projectDir = new File('../../node_modules/@capacitor/splash-screen/android')
include ':capacitor-status-bar'
project(':capacitor-status-bar').projectDir = new File('../../node_modules/@capacitor/status-bar/android')
include ':capacitor-toast'
project(':capacitor-toast').projectDir = new File('../../node_modules/@capacitor/toast/android')
@@ -0,0 +1,44 @@
{
"_comment": [
"Template only — this file is not read by the build.",
"",
"To enable Android push notifications:",
" 1. Create a Firebase project at https://console.firebase.google.com",
" 2. Add an Android app with package name com.liketrek.trek",
" 3. Download the generated google-services.json",
" 4. Place it at client/android/app/google-services.json (gitignored)",
"",
"Without it, app/build.gradle skips the google-services plugin and the app",
"still builds and runs — PushNotifications.register() simply fails at runtime.",
"",
"The server that SENDS the notifications needs a service-account key from the",
"SAME Firebase project. See docs for the self-hosting implications."
],
"project_info": {
"project_number": "000000000000",
"project_id": "your-firebase-project-id",
"storage_bucket": "your-firebase-project-id.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:000000000000:android:0000000000000000000000",
"android_client_info": {
"package_name": "com.liketrek.trek"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "REPLACE_WITH_YOUR_ANDROID_API_KEY"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
+9 -1
View File
@@ -1,5 +1,10 @@
ext {
minSdkVersion = 24
// 26, not Capacitor's default 24: @capacitor/inappbrowser requires a minimum
// target of 26. This drops Android 7.0/7.1 (~1% of devices and no longer
// receiving security updates). Removing @capacitor/inappbrowser — its system-
// browser mode overlaps @capacitor/browser almost exactly — would allow 24
// again.
minSdkVersion = 26
compileSdkVersion = 36
targetSdkVersion = 36
androidxActivityVersion = '1.11.0'
@@ -13,4 +18,7 @@ ext {
androidxJunitVersion = '1.3.0'
androidxEspressoCoreVersion = '3.7.0'
cordovaAndroidVersion = '14.0.1'
// Consumed by @capacitor/push-notifications. Pinned rather than left to the
// plugin's default so an FCM SDK bump is a visible, reviewable change here.
firebaseMessagingVersion = '25.0.1'
}
+59 -1
View File
@@ -1,9 +1,67 @@
/// <reference types="@capacitor/push-notifications" />
/// <reference types="@capacitor/local-notifications" />
/// <reference types="@capacitor/splash-screen" />
/// <reference types="@capacitor/keyboard" />
import type { CapacitorConfig } from '@capacitor/cli';
import { KeyboardResize } from '@capacitor/keyboard';
const config: CapacitorConfig = {
appId: 'com.liketrek.trek',
appName: 'TREK - Plan your trips!',
webDir: 'dist'
webDir: 'dist',
// TREK is self-hosted: the server the app talks to is whatever the user
// enters, so every request is cross-origin. Both schemes stay https so the
// webview origin is a secure context (crypto.subtle, WebAuthn, service
// workers, IndexedDB persistence all require it).
server: {
androidScheme: 'https',
iosScheme: 'https',
},
plugins: {
PushNotifications: {
// No `badge`: TREK has no unread count the OS could keep honest, and a
// badge that only ever grows is worse than none.
presentationOptions: ['sound', 'banner', 'list'],
},
LocalNotifications: {
// Same white-on-transparent mask as FCM uses; generated by
// scripts/generate-native-icons.mjs. Without it Android falls back to the
// launcher icon and renders a white blob.
smallIcon: 'notification_icon',
presentationOptions: ['sound', 'banner', 'list'],
},
SplashScreen: {
// Matches <meta name="theme-color"> in index.html, so the launch image,
// the splash and the first painted frame are the same colour and there is
// no white flash between them.
backgroundColor: '#09090b',
launchShowDuration: 500,
// Deliberately true. With `false` the splash stays up until JS calls
// SplashScreen.hide(), and until that call exists a failure to boot the
// bundle looks like a frozen app. Flip this to false — and drop
// launchShowDuration — in the same change that adds the hide() call once
// the React tree has mounted.
launchAutoHide: true,
launchFadeOutDuration: 200,
showSpinner: false,
androidScaleType: 'CENTER_CROP',
},
Keyboard: {
// The mobile shell is built from bottom sheets (mobile/screens/**/M*Sheet).
// `body` resizes the webview itself, so a sheet's own scroll container
// keeps working; `native` resizes the whole view and fights the sheet.
resize: KeyboardResize.Body,
// Android-only workaround: with an edge-to-edge status bar the webview
// otherwise does not resize at all when the keyboard opens.
resizeOnFullScreen: true,
// No `style`: TREK has a real appearance system, so the keyboard should
// follow the device/app theme rather than being pinned here.
},
// No StatusBar block on purpose. From Android 15 / Capacitor 8 the app is
// always edge-to-edge, so `overlaysWebView` and `backgroundColor` no longer
// do anything. The status-bar style has to follow the user's chosen scheme
// at runtime — call StatusBar.setStyle() from applyAppearance().
},
};
export default config;
+8
View File
@@ -30,6 +30,14 @@ export default tseslint.config(
'public',
'test-results',
'playwright-report',
// Capacitor native shells. `npx cap sync` copies the built bundle from
// dist/ into both platforms, so without these ESLint lints the minified
// output and `npm run lint` reports tens of thousands of errors that have
// nothing to do with the source. gitignore() does not cover them: the iOS
// and Android .gitignore files live one level down, and this config's
// gitignore integration only reads client/.gitignore.
'android/**',
'ios/**',
'e2e/**',
'scripts/**',
'**/*.config.js',
@@ -16,6 +16,7 @@
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
9582B6832FE993A70072D4E8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9582B6822FE993A50072D4E8 /* SceneDelegate.swift */; };
7A1E00032C0000000000A003 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 7A1E00022C0000000000A002 /* PrivacyInfo.xcprivacy */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -30,6 +31,8 @@
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
9582B6822FE993A50072D4E8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; };
7A1E00012C0000000000A001 /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
7A1E00022C0000000000A002 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -71,6 +74,8 @@
504EC30E1FED79650016851F /* Assets.xcassets */,
504EC3101FED79650016851F /* LaunchScreen.storyboard */,
504EC3131FED79650016851F /* Info.plist */,
7A1E00012C0000000000A001 /* App.entitlements */,
7A1E00022C0000000000A002 /* PrivacyInfo.xcprivacy */,
2FAD9762203C412B000D30F8 /* config.xml */,
50B271D01FEDC1A000F3C39B /* public */,
);
@@ -113,6 +118,11 @@
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Push = {
enabled = 1;
};
};
};
};
};
@@ -148,6 +158,7 @@
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */,
504EC30D1FED79650016851F /* Main.storyboard in Resources */,
2FAD9763203C412B000D30F8 /* config.xml in Resources */,
7A1E00032C0000000000A003 /* PrivacyInfo.xcprivacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -299,6 +310,7 @@
baseConfigurationReference = 958DCC722DB07C7200EA8C5F /* debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
@@ -321,6 +333,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!--
The Push Notifications capability. Xcode normally writes this when you tick
"Push Notifications" under Signing & Capabilities; it is committed here so a
clean checkout builds with push already enabled.
`development` is correct for debug builds and for TestFlight. Xcode's
automatic signing rewrites this to `production` when archiving for
distribution, so it does not need a second value here.
Enabling the capability in the Apple Developer portal for the App ID
com.liketrek.trek is a separate, manual step — the entitlement alone will
fail to sign until the App ID has it too.
-->
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>
+16
View File
@@ -33,6 +33,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
// MARK: - Push notifications
//
// @capacitor/push-notifications does not swizzle the APNs callbacks; it listens
// on NotificationCenter instead, so these two forwarders are mandatory. Without
// them PushNotifications.register() resolves and then nothing ever arrives —
// neither the `registration` nor the `registrationError` listener fires, which
// makes it look like a server problem rather than a missing 6 lines here.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken)
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
}
func application(_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
+78
View File
@@ -24,6 +24,84 @@
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<!-- TREK is self-hosted: the user types in their own server address, so the
app cannot know its scheme, hostname or certificate authority at build
time. NSAllowsArbitraryLoads is the only ATS setting that covers all of
https://trek.example.com, http://192.168.1.10:3001 and a Tailscale host
on 100.64.0.0/10 (which is NOT RFC1918, so NSAllowsLocalNetworking alone
would not reach it).
Deliberately the ONLY key in this dict. From iOS 10 on, the presence of
NSAllowsLocalNetworking, NSAllowsArbitraryLoadsInWebContent or
NSAllowsArbitraryLoadsForMedia makes the system IGNORE
NSAllowsArbitraryLoads — adding one of them "to be safe" would silently
re-block every cleartext server outside the local subnet.
App Review will ask for a justification; the answer is that the app is a
client for a server the user hosts and names themselves. https remains
the documented recommendation for anything past a LAN. -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<!-- Local Network Privacy is a separate gate from ATS: since iOS 14 any
connection to a local-network address prompts the user, and no ATS key
waives it. Without this string the prompt has no explanation and iOS
logs "Local network prohibited" instead of a useful error. -->
<key>NSLocalNetworkUsageDescription</key>
<string>TREK connects to the TREK server you choose, which is often running on your home or office network.</string>
<!-- Permission usage descriptions. iOS does not merely deny a missing one —
it terminates the app the first time the API is touched, so every key
below is load-bearing even where the feature is optional.
@capacitor/camera -->
<key>NSCameraUsageDescription</key>
<string>TREK uses the camera so you can photograph a place, a journey entry or a booking document and add it to your trip.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>TREK needs access to your photos so you can add them to trips, places and journey entries.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>TREK saves photos and exports you download from your trips back to your photo library.</string>
<!-- @capacitor/geolocation. NSLocationAlwaysAndWhenInUse is required by
Apple because the underlying ion-ios-geolocation library is capable of
background reporting; TREK never asks for it, so the user only ever sees
the when-in-use prompt. -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>TREK uses your location to centre the map on you and to find places near you.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>TREK uses your location to centre the map on you and to find places near you.</string>
<!-- @capacitor/calendar. iOS 17 split calendar access into full and
write-only, each with its own key; the pre-17 key stays for older
devices. Adding a trip to the device calendar needs only write-only
access, which is the weaker prompt. -->
<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
<string>TREK adds your trip days and bookings to your calendar.</string>
<key>NSCalendarsFullAccessUsageDescription</key>
<string>TREK reads your calendar to show existing commitments alongside your trip, and adds trip events to it.</string>
<key>NSCalendarsUsageDescription</key>
<string>TREK adds your trip days and bookings to your calendar.</string>
<!-- Deep links (@capacitor/app appUrlOpen), matching the intent-filter in
AndroidManifest.xml and @string/custom_url_scheme there. This is the
return path for native OIDC/OAuth login, which opens in an
SFSafariViewController and redirects back to com.liketrek.trek://…
Not a Universal Link: those are verified against one specific domain via
apple-app-site-association, and a self-hosted app has no single domain to
verify against. SceneDelegate already forwards openURLContexts to
Capacitor, so no Swift change is needed. -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.liketrek.trek</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.liketrek.trek</string>
</array>
</dict>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
+47
View File
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!--
Apple's required-reason API declaration. App Store Connect rejects uploads
that call these APIs without an entry here.
NSPrivacyAccessedAPICategoryFileTimestamp / C617.1 is required by
@capacitor/filesystem, which @capacitor/file-transfer depends on to resolve
a native path before it can download or upload. Capacitor's own frameworks
ship their own PrivacyInfo; this file covers the app target only.
Add to this file when adding a plugin that touches a required-reason API —
UserDefaults (CA92.1) for @capacitor/preferences, disk space (E174.1),
system boot time (35F9.1).
-->
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<!-- @capacitor/preferences, which backs the saved server profiles. -->
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
<!-- TREK sends no data to third parties and runs no trackers: all traffic
goes to the server the user chose. -->
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
</dict>
</plist>
+38 -2
View File
@@ -11,14 +11,50 @@ let package = Package(
targets: ["CapApp-SPM"])
],
dependencies: [
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", exact: "8.5.0")
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", exact: "8.5.0"),
.package(name: "CapacitorApp", path: "../../../../node_modules/@capacitor/app"),
.package(name: "CapacitorBrowser", path: "../../../../node_modules/@capacitor/browser"),
.package(name: "CapacitorCalendar", path: "../../../../node_modules/@capacitor/calendar"),
.package(name: "CapacitorCamera", path: "../../../../node_modules/@capacitor/camera"),
.package(name: "CapacitorFileTransfer", path: "../../../../node_modules/@capacitor/file-transfer"),
.package(name: "CapacitorFilesystem", path: "../../../../node_modules/@capacitor/filesystem"),
.package(name: "CapacitorGeolocation", path: "../../../../node_modules/@capacitor/geolocation"),
.package(name: "CapacitorHaptics", path: "../../../../node_modules/@capacitor/haptics"),
.package(name: "CapacitorInappbrowser", path: "../../../../node_modules/@capacitor/inappbrowser"),
.package(name: "CapacitorKeyboard", path: "../../../../node_modules/@capacitor/keyboard"),
.package(name: "CapacitorLocalNotifications", path: "../../../../node_modules/@capacitor/local-notifications"),
.package(name: "CapacitorNetwork", path: "../../../../node_modules/@capacitor/network"),
.package(name: "CapacitorPreferences", path: "../../../../node_modules/@capacitor/preferences"),
.package(name: "CapacitorPushNotifications", path: "../../../../node_modules/@capacitor/push-notifications"),
.package(name: "CapacitorShare", path: "../../../../node_modules/@capacitor/share"),
.package(name: "CapacitorSplashScreen", path: "../../../../node_modules/@capacitor/splash-screen"),
.package(name: "CapacitorStatusBar", path: "../../../../node_modules/@capacitor/status-bar"),
.package(name: "CapacitorToast", path: "../../../../node_modules/@capacitor/toast")
],
targets: [
.target(
name: "CapApp-SPM",
dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm")
.product(name: "Cordova", package: "capacitor-swift-pm"),
.product(name: "CapacitorApp", package: "CapacitorApp"),
.product(name: "CapacitorBrowser", package: "CapacitorBrowser"),
.product(name: "CapacitorCalendar", package: "CapacitorCalendar"),
.product(name: "CapacitorCamera", package: "CapacitorCamera"),
.product(name: "CapacitorFileTransfer", package: "CapacitorFileTransfer"),
.product(name: "CapacitorFilesystem", package: "CapacitorFilesystem"),
.product(name: "CapacitorGeolocation", package: "CapacitorGeolocation"),
.product(name: "CapacitorHaptics", package: "CapacitorHaptics"),
.product(name: "CapacitorInappbrowser", package: "CapacitorInappbrowser"),
.product(name: "CapacitorKeyboard", package: "CapacitorKeyboard"),
.product(name: "CapacitorLocalNotifications", package: "CapacitorLocalNotifications"),
.product(name: "CapacitorNetwork", package: "CapacitorNetwork"),
.product(name: "CapacitorPreferences", package: "CapacitorPreferences"),
.product(name: "CapacitorPushNotifications", package: "CapacitorPushNotifications"),
.product(name: "CapacitorShare", package: "CapacitorShare"),
.product(name: "CapacitorSplashScreen", package: "CapacitorSplashScreen"),
.product(name: "CapacitorStatusBar", package: "CapacitorStatusBar"),
.product(name: "CapacitorToast", package: "CapacitorToast")
]
)
]
+19
View File
@@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"prebuild": "node scripts/generate-icons.mjs",
"icons:native": "node scripts/generate-native-icons.mjs",
"build": "vite build",
"build:analyze": "vite build --mode analyze",
"preview": "vite preview",
@@ -30,8 +31,26 @@
},
"dependencies": {
"@capacitor/android": "^8.5.0",
"@capacitor/app": "^8.1.1",
"@capacitor/browser": "^8.0.4",
"@capacitor/calendar": "^1.0.1",
"@capacitor/camera": "^8.2.3",
"@capacitor/core": "^8.5.0",
"@capacitor/file-transfer": "^2.0.5",
"@capacitor/filesystem": "^8.1.3",
"@capacitor/geolocation": "^8.2.2",
"@capacitor/haptics": "^8.0.2",
"@capacitor/inappbrowser": "^4.0.3",
"@capacitor/ios": "^8.5.0",
"@capacitor/keyboard": "^8.0.5",
"@capacitor/local-notifications": "^8.3.1",
"@capacitor/network": "^8.0.1",
"@capacitor/preferences": "^8.0.1",
"@capacitor/push-notifications": "^8.1.2",
"@capacitor/share": "^8.0.1",
"@capacitor/splash-screen": "^8.0.2",
"@capacitor/status-bar": "^8.0.3",
"@capacitor/toast": "^8.0.1",
"@fontsource/bebas-neue": "^5.3.0",
"@fontsource/eb-garamond": "^5.3.0",
"@fontsource/geist-sans": "^5.2.5",
+67
View File
@@ -0,0 +1,67 @@
/**
* Generates the Android status-bar notification icon from the master
* white-silhouette SVG.
*
* Android renders the small notification icon as a mask: every non-transparent
* pixel becomes the tint colour and everything else is dropped. Feeding it the
* launcher icon (a full-bleed coloured square) therefore produces a solid white
* blob, which is the single most common Capacitor push-notification complaint.
* public/icons/icon-white.svg is already white-on-transparent, so we only have
* to rasterise it at the five densities and flatten the colour to pure white so
* any future edit to that SVG's fill cannot silently change the mask.
*
* Referenced from AndroidManifest.xml as @drawable/notification_icon.
* Run: node scripts/generate-native-icons.mjs
*/
import { mkdirSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import sharp from 'sharp';
const __dirname = dirname(fileURLToPath(import.meta.url));
const clientDir = join(__dirname, '..');
const source = join(clientDir, 'public', 'icons', 'icon-white.svg');
const resDir = join(clientDir, 'android', 'app', 'src', 'main', 'res');
// Android notification icon sizes, in px, per density bucket (24dp baseline).
const densities = [
{ dir: 'drawable-mdpi', size: 24 },
{ dir: 'drawable-hdpi', size: 36 },
{ dir: 'drawable-xhdpi', size: 48 },
{ dir: 'drawable-xxhdpi', size: 72 },
{ dir: 'drawable-xxxhdpi', size: 96 },
];
for (const { dir, size } of densities) {
const outDir = join(resDir, dir);
mkdirSync(outDir, { recursive: true });
// Inset the glyph slightly: Android's status bar clips tight to the 24dp box
// and a mark that touches the edge reads as cropped.
const glyph = Math.round(size * 0.86);
const mask = await sharp(source, { density: 512 })
.resize(glyph, glyph, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
.extend({
top: Math.floor((size - glyph) / 2),
bottom: Math.ceil((size - glyph) / 2),
left: Math.floor((size - glyph) / 2),
right: Math.ceil((size - glyph) / 2),
background: { r: 0, g: 0, b: 0, alpha: 0 },
})
.ensureAlpha()
.toBuffer();
// Keep the alpha, force every visible pixel to pure white. `dest-in` composites
// the white plate through the glyph's alpha channel.
await sharp({
create: { width: size, height: size, channels: 4, background: { r: 255, g: 255, b: 255, alpha: 1 } },
})
.composite([{ input: mask, blend: 'dest-in' }])
.png({ compressionLevel: 9 })
.toFile(join(outDir, 'notification_icon.png'));
console.log(` ✓ ${dir}/notification_icon.png (${size}x${size})`);
}
console.log('Android notification icons generated.');
+195
View File
@@ -28,8 +28,26 @@
"version": "3.4.1",
"dependencies": {
"@capacitor/android": "^8.5.0",
"@capacitor/app": "^8.1.1",
"@capacitor/browser": "^8.0.4",
"@capacitor/calendar": "^1.0.1",
"@capacitor/camera": "^8.2.3",
"@capacitor/core": "^8.5.0",
"@capacitor/file-transfer": "^2.0.5",
"@capacitor/filesystem": "^8.1.3",
"@capacitor/geolocation": "^8.2.2",
"@capacitor/haptics": "^8.0.2",
"@capacitor/inappbrowser": "^4.0.3",
"@capacitor/ios": "^8.5.0",
"@capacitor/keyboard": "^8.0.5",
"@capacitor/local-notifications": "^8.3.1",
"@capacitor/network": "^8.0.1",
"@capacitor/preferences": "^8.0.1",
"@capacitor/push-notifications": "^8.1.2",
"@capacitor/share": "^8.0.1",
"@capacitor/splash-screen": "^8.0.2",
"@capacitor/status-bar": "^8.0.3",
"@capacitor/toast": "^8.0.1",
"@fontsource/bebas-neue": "^5.3.0",
"@fontsource/eb-garamond": "^5.3.0",
"@fontsource/geist-sans": "^5.2.5",
@@ -2379,6 +2397,42 @@
"@capacitor/core": "^8.5.0"
}
},
"node_modules/@capacitor/app": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@capacitor/app/-/app-8.1.1.tgz",
"integrity": "sha512-xM2ZTX5jK60tFtmjsmJv+Cdj1Qsb6NcavkqmdEnCPQBeya9oKhamZJxYOnQNj1ZvcJM7sWfG6BEtSLx42pisbA==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/browser": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@capacitor/browser/-/browser-8.0.4.tgz",
"integrity": "sha512-ORZ4u/y+7aMuu6yVuk6SP529fUZyqcbnuHB1q5McpA3o2SYfVoi28iB8rsQjI2ad1qlKJd29ZUuGtspXLBDlWg==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/calendar": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/calendar/-/calendar-1.0.1.tgz",
"integrity": "sha512-2ogW+Jlamy6BJkZmaggB/Jx282G+o3aBPfgx3GxmjlLjl8hqcorQKFhlCiveDui6xEKoVy13AQpKhukHQ8MrCQ==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/camera": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/@capacitor/camera/-/camera-8.2.3.tgz",
"integrity": "sha512-x9Pl5UgvrZyU5Hl53Fly9f6u019hhgN+gSdDgi+WygxK9DUVuYKjigplOXz3VJQ9m8ttUSjqN/rAC3JVnyEzcA==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/cli": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-8.5.0.tgz",
@@ -2503,6 +2557,60 @@
"tslib": "^2.1.0"
}
},
"node_modules/@capacitor/file-transfer": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@capacitor/file-transfer/-/file-transfer-2.0.5.tgz",
"integrity": "sha512-G2a/+NKvADNcmxMSqy6HPp+Ij3trCy0+EVNMrRWmp8ueyCNaJEi96LpfEIztv+vqK9+xLRskQ7iVl+Pj8qt4hg==",
"license": "MIT",
"dependencies": {
"@capacitor/synapse": "^1.0.4"
},
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/filesystem": {
"version": "8.1.3",
"resolved": "https://registry.npmjs.org/@capacitor/filesystem/-/filesystem-8.1.3.tgz",
"integrity": "sha512-GNBlRkaXEEb+6WP/YsDXr2zygyOHoieuye6viyd2ZkEdBwfseG41jxfarVxLWH0r0owhyNUbwJ/Qi8fubqr6wA==",
"license": "MIT",
"dependencies": {
"@capacitor/synapse": "^1.0.4"
},
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/geolocation": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/@capacitor/geolocation/-/geolocation-8.2.2.tgz",
"integrity": "sha512-X8ryw6CfL4LspJnnfVL4++DUKKZVfdUIA9JljWVRljEDIQxZQZtz6xf7qstht+ymc+le6oa5EaUChVA6KsCYlA==",
"license": "MIT",
"dependencies": {
"@capacitor/synapse": "^1.0.4"
},
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/haptics": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-8.0.2.tgz",
"integrity": "sha512-c2hZzRR5Fk1tbTvhG1jhh2XBAf3EhnIerMIb2sl7Mt41Gxx1fhBJFDa0/BI1IbY4loVepyyuqNC9820/GZuoWQ==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/inappbrowser": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@capacitor/inappbrowser/-/inappbrowser-4.0.3.tgz",
"integrity": "sha512-Jnl6Ssp2QZByncO6QhDKzNpi3yeum1aSQM+70VMVh8TJ16HOwOrcTnf8E4+7FpT9ruSRGyhqQu1yBSrpl+wD1g==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/ios": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@capacitor/ios/-/ios-8.5.0.tgz",
@@ -2512,6 +2620,93 @@
"@capacitor/core": "^8.5.0"
}
},
"node_modules/@capacitor/keyboard": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-8.0.5.tgz",
"integrity": "sha512-oFXygC4eKYA5l2MdpTR06L2M/4x6e2SLD5yS1T9+UBDKTkzyvhWKEhbYLUaTIBPpLKqlfGudJw1X73S1H9eUzQ==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/local-notifications": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/@capacitor/local-notifications/-/local-notifications-8.3.1.tgz",
"integrity": "sha512-F7zu/+p/mIR958YP4axOkeI5hBKRaXDyYjbNPrAil2wWfd7OxScPSTvP9t85NpQ0eULVRu0/sntoxOg6ck477A==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/network": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/network/-/network-8.0.1.tgz",
"integrity": "sha512-9xK/FHFmzKGanB6BdoSZOzXk8vF0OFVQSQ4PAsIrzAzLuXHryO317qy8dcHVpgxYeuZq2noI0My9z1DvVDi/9w==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/preferences": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/preferences/-/preferences-8.0.1.tgz",
"integrity": "sha512-T6no3ebi79XJCk91U3Jp/liJUwgBdvHR+s6vhvPkPxSuch7z3zx5Rv1bdWM6sWruNx+pViuEGqZvbfCdyBvcHQ==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/push-notifications": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/@capacitor/push-notifications/-/push-notifications-8.1.2.tgz",
"integrity": "sha512-TQl4XxqJfNF+KbSYBFMXYYT5WXUaleVKXlByhh/8+KySMVNjp+l5OqAUoGe/sY1IAnGXEuUeCjA/WLZEVvGwrA==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/share": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/share/-/share-8.0.1.tgz",
"integrity": "sha512-3cSBKBCJVon54rKDROP2rqGyeGks4pBh9TbaEk9S375Kbek/ZHe72N50zIa0Vn9Eac/SuhwgehO/mmA4CsUOiw==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/splash-screen": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/splash-screen/-/splash-screen-8.0.2.tgz",
"integrity": "sha512-0C6rYScr13WfAE9nPl4ScOQynmjFv9NT3Th+RZkD4ezYHmER6mcl1/lDYsv7jg3Rj3FOvLrlTlmNkUrr83kZzQ==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/status-bar": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-8.0.3.tgz",
"integrity": "sha512-csSpfNeN49Hx9JaQBSJEIiEbOLtXg3kcc2IpScq2fu5L520h3AWEvsxoH8Srk1jxfRKepaJ4S4sqSC3foI4AgA==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/synapse": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@capacitor/synapse/-/synapse-1.0.4.tgz",
"integrity": "sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==",
"license": "ISC"
},
"node_modules/@capacitor/toast": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/toast/-/toast-8.0.1.tgz",
"integrity": "sha512-0uoyftoAeFjtOMiozBo7YXEo+zcpQCAxtBONSvrljJ05SKdlb1A8OFHaV/DZfIpGt+19kzUGaEaUemLdll4wUw==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@csstools/color-helpers": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",