From 9bc8e3d1a6b10c6c2e81b2ef9b3d3583a4e74b98 Mon Sep 17 00:00:00 2001 From: Miguel Diaz Date: Thu, 26 Mar 2026 13:54:12 +0100 Subject: [PATCH 1/7] =?UTF-8?q?=E2=9E=95=20dependencies:=20add=20meawallet?= =?UTF-8?q?=20mpp=20sdk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .npmrc | 15 +++++++++++++++ package.json | 1 + pnpm-lock.yaml | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..9bd3c48ea --- /dev/null +++ b/.npmrc @@ -0,0 +1,15 @@ +# isc license +# +# copyright meawallet +# +# permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby +# granted, provided that the above copyright notice and this permission notice appear in all copies. +# +# the software is provided "as is" and the author disclaims all warranties with regard to this software including +# all implied warranties of merchantability and fitness. in no event shall the author be liable for any special, +# direct, indirect, or consequential damages or any damages whatsoever resulting from loss of use, data or profits, +# whether in an action of contract, negligence or other tortious action, arising out of or in connection with the +# use or performance of this software. +@meawallet:registry=https://nexus.ext.meawallet.com/repository/react-native-mpp/ +//nexus.ext.meawallet.com/repository/react-native-mpp/:username=ext-react-native-mpp +//nexus.ext.meawallet.com/repository/react-native-mpp/:_password=OXJDTVo1ZEg2dHVD diff --git a/package.json b/package.json index f7d7cbe46..2c113aafb 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@intercom/intercom-react-native": "^9.8.0", "@intercom/messenger-js-sdk": "^0.0.18", "@lifi/sdk": "3.7.7", + "@meawallet/react-native-mpp": "^2.2.2", "@peculiar/asn1-ecc": "^2.6.1", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/webcrypto": "^1.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 377e822c0..e42d9db8e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,6 +103,9 @@ patchedDependencies: '@lifi/sdk': hash: ee16233f297d9a6c8a8320b5dc2b4bf47b7be8b481d79e3d54108cd77775b45b path: patches/@lifi__sdk.patch + '@meawallet/react-native-mpp': + hash: 1865bf0eddf3da8572a839f14e03cf34cf4753191421a4bc8704f22c8cdaa33b + path: patches/@meawallet__react-native-mpp.patch embedded-postgres: hash: cb5e37525b1810f2af136570b38d5e0cec4cc2455408896ed1943d27f3f61b38 path: patches/embedded-postgres.patch @@ -153,6 +156,9 @@ importers: '@lifi/sdk': specifier: 3.7.7 version: 3.7.7(patch_hash=ee16233f297d9a6c8a8320b5dc2b4bf47b7be8b481d79e3d54108cd77775b45b)(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(viem@2.47.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6))(zod@4.3.6) + '@meawallet/react-native-mpp': + specifier: ^2.2.2 + version: 2.3.0(patch_hash=1865bf0eddf3da8572a839f14e03cf34cf4753191421a4bc8704f22c8cdaa33b)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.0)(utf-8-validate@5.0.10))(react@19.2.0) '@peculiar/asn1-ecc': specifier: ^2.6.1 version: 2.6.1 @@ -3676,6 +3682,13 @@ packages: '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + '@meawallet/react-native-mpp@2.3.0': + resolution: {integrity: sha512-KuWSjSprQReFQUpcfdl83aX3y9irLkoCRGY3X9AzIUYusFat3PZbNstiRvkeSZ5Cpa6LStzAts+SC+niVd9t8g==} + engines: {node: '>=18'} + peerDependencies: + react: '*' + react-native: '*' + '@mermaid-js/parser@1.1.1': resolution: {integrity: sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==} @@ -17162,6 +17175,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@meawallet/react-native-mpp@2.3.0(patch_hash=1865bf0eddf3da8572a839f14e03cf34cf4753191421a4bc8704f22c8cdaa33b)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.0)(utf-8-validate@5.0.10))(react@19.2.0)': + dependencies: + react: 19.2.0 + react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.0)(utf-8-validate@5.0.10) + '@mermaid-js/parser@1.1.1': dependencies: '@chevrotain/types': 11.1.2 From 8d38e8a0a8bfeafee6e6e5bc0a62adb7336468b7 Mon Sep 17 00:00:00 2001 From: Miguel Diaz Date: Thu, 9 Apr 2026 17:27:55 +0200 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=8D=B1=20app:=20add=20google=20wallet?= =?UTF-8?q?=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/images/google-wallet-button.svg | 12 ++++++++++++ src/assets/images/google-wallet-icon.svg | 8 ++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/assets/images/google-wallet-button.svg create mode 100644 src/assets/images/google-wallet-icon.svg diff --git a/src/assets/images/google-wallet-button.svg b/src/assets/images/google-wallet-button.svg new file mode 100644 index 000000000..67e9346a7 --- /dev/null +++ b/src/assets/images/google-wallet-button.svg @@ -0,0 +1,12 @@ + + + + + + + + + + Add to + Google Wallet + diff --git a/src/assets/images/google-wallet-icon.svg b/src/assets/images/google-wallet-icon.svg new file mode 100644 index 000000000..346bcb22f --- /dev/null +++ b/src/assets/images/google-wallet-icon.svg @@ -0,0 +1,8 @@ + + + + + + + + From 8a19bbe55a79b154c8835e515f94376d3f7837da Mon Sep 17 00:00:00 2001 From: Miguel Diaz Date: Thu, 26 Mar 2026 13:55:24 +0100 Subject: [PATCH 3/7] ^ This is a combination of 2 commits. ^ This is the 1st commit message: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ app: add wallet provisioning ^ The commit message #2 will be skipped: ^ fixup --- .changeset/gentle-cases-fold.md | 5 + .maestro/subflows/activateCard.yaml | 5 +- app.config.ts | 77 +++- cspell.json | 1 + src/components/card/Card.tsx | 603 +++++++++++++++++++++++++++- src/components/card/CardDetails.tsx | 8 +- src/i18n/en.json | 3 +- src/i18n/es-AR.json | 2 + src/i18n/es.json | 3 + src/i18n/pt.json | 3 + src/utils/reportError.ts | 25 +- src/utils/server.ts | 15 + 12 files changed, 735 insertions(+), 15 deletions(-) create mode 100644 .changeset/gentle-cases-fold.md diff --git a/.changeset/gentle-cases-fold.md b/.changeset/gentle-cases-fold.md new file mode 100644 index 000000000..d35b2f747 --- /dev/null +++ b/.changeset/gentle-cases-fold.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +✨ add wallet provisioning diff --git a/.maestro/subflows/activateCard.yaml b/.maestro/subflows/activateCard.yaml index cbba3e087..217a67814 100644 --- a/.maestro/subflows/activateCard.yaml +++ b/.maestro/subflows/activateCard.yaml @@ -8,7 +8,10 @@ appId: ${APP_ID ?? "app.exactly"} commands: - runFlow: { file: scrollTo.yaml, env: { element: Accept and enable card } } - tapOn: Accept and enable card -- assertVisible: Manually add your card to Apple Pay & Google Pay to make contactless payments. +- runFlow: + when: { platform: web } + commands: + - assertVisible: Manually add your card to Apple Pay & Google Pay to make contactless payments. - tapOn: Close - tapOn: Freeze card - tapOn: { text: Freeze card, below: "Freeze your card?" } diff --git a/app.config.ts b/app.config.ts index 1dbe60c11..1485b7132 100644 --- a/app.config.ts +++ b/app.config.ts @@ -1,7 +1,17 @@ import type { PluginConfigType as BuildPropertiesConfig } from "expo-build-properties/build/pluginConfig"; import type { FontProps } from "expo-font/plugin/build/withFonts"; -import { AndroidConfig, withAndroidManifest, withAppBuildGradle, type ConfigPlugin } from "expo/config-plugins"; +import { + AndroidConfig, + IOSConfig, + withAndroidManifest, + withAppBuildGradle, + withDangerousMod, + withXcodeProject, + type ConfigPlugin, +} from "expo/config-plugins"; +import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; import { env } from "node:process"; import metadata from "./package.json"; @@ -40,6 +50,7 @@ export default { associatedDomains: [`webcredentials:${env.APP_DOMAIN ?? "sandbox.exactly.app"}`], supportsTablet: false, buildNumber: String(versionCode), + entitlements: { "com.apple.developer.payment-pass-provisioning": true }, infoPlist: { ITSAppUsesNonExemptEncryption: false, CFBundleAllowMixedLocalizations: true, @@ -110,12 +121,70 @@ export default { }, ], // @ts-expect-error inline plugin + ((config) => { + const withAndroid = withDangerousMod(config, [ + "android", + (c) => { + const source = path.join(c.modRequest.projectRoot, "src/assets/mea_config"); + const destination = path.join(c.modRequest.projectRoot, "android/app/src/main/assets/mea_config"); + mkdirSync(path.dirname(destination), { recursive: true }); + if (existsSync(source)) copyFileSync(source, destination); + return c; + }, + ]); + return withXcodeProject(withAndroid, (c) => { + const source = path.join(c.modRequest.projectRoot, "src/assets/mea_config"); + const destination = path.join(c.modRequest.projectRoot, "ios", c.modRequest.projectName ?? "", "mea_config"); + if (existsSync(source)) { + copyFileSync(source, destination); + c.modResults = IOSConfig.XcodeUtils.addResourceFileToGroup({ + filepath: `${c.modRequest.projectName ?? ""}/mea_config`, + groupName: c.modRequest.projectName ?? "", + project: c.modResults, + isBuildFile: true, + }); + } + return c; + }); + }) satisfies ConfigPlugin, + // @ts-expect-error inline plugin + ((config) => + withDangerousMod(config, [ + "android", + (c) => { + const buildGradle = path.join(c.modRequest.projectRoot, "android/build.gradle"); + const meaRepo = ` maven { + url "https://nexus.ext.meawallet.com/repository/mpp-android-group/" + credentials { + username = "ext-mpp-android" + password = "M1yeJMcuE5TiGW" + } + }`; + let contents = readFileSync(buildGradle, "utf8"); + if (!contents.includes("nexus.ext.meawallet.com")) { + contents = contents.replace(/(allprojects[\s\S]*?repositories\s*\{)/, `$1\n${meaRepo}`); + writeFileSync(buildGradle, contents); + } + return c; + }, + ])) satisfies ConfigPlugin, + // @ts-expect-error inline plugin ((config) => withAndroidManifest( withAppBuildGradle(config, (c) => { - c.modResults.contents = c.modResults.contents.replace( - /defaultConfig\s*\{/, - '$& ndk { debugSymbolLevel "FULL" }', + c.modResults.contents = c.modResults.contents.replaceAll( + /(defaultConfig\s*\{)(?:\s*ndk\s*\{\s*debugSymbolLevel\s*"FULL"\s*\})+/g, + "$1", + ); + if (!c.modResults.contents.includes('debugSymbolLevel "FULL"')) { + c.modResults.contents = c.modResults.contents.replace( + /release\s*\{/, + '$&\n ndk { debugSymbolLevel "FULL" }', + ); + } + c.modResults.contents = c.modResults.contents.replaceAll( + '\nimplementation(enforcedPlatform("com.squareup.okhttp3:okhttp-bom:4.12.0"))', + "", ); c.modResults.contents = c.modResults.contents.replace( /dependencies\s*\{/, diff --git a/cspell.json b/cspell.json index 7e60030e9..d1b60554f 100644 --- a/cspell.json +++ b/cspell.json @@ -102,6 +102,7 @@ "mainqueg", "mateo-soso", "mdpi", + "meawallet", "memester", "miniapp", "mipd", diff --git a/src/components/card/Card.tsx b/src/components/card/Card.tsx index 11830d537..b164ccaf8 100644 --- a/src/components/card/Card.tsx +++ b/src/components/card/Card.tsx @@ -1,6 +1,6 @@ -import React, { useRef, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; -import { RefreshControl } from "react-native"; +import { Alert, NativeEventEmitter, NativeModules, Platform, Pressable, RefreshControl } from "react-native"; import { selectionAsync } from "expo-haptics"; import { useRouter } from "expo-router"; @@ -22,10 +22,12 @@ import CardPIN from "./CardPIN"; import ExaCard from "./exa-card/ExaCard"; import SpendingLimits from "./SpendingLimits"; import VerificationFailure from "./VerificationFailure"; +import GoogleWalletButton from "../../assets/images/google-wallet-button.svg"; +import GoogleWalletIcon from "../../assets/images/google-wallet-icon.svg"; import { presentArticle } from "../../utils/intercom"; import openBrowser from "../../utils/openBrowser"; import queryClient from "../../utils/queryClient"; -import reportError from "../../utils/reportError"; +import reportError, { classifyError } from "../../utils/reportError"; import { APIError, createCard, @@ -50,6 +52,196 @@ import Text from "../shared/Text"; import View from "../shared/View"; import type { Credential } from "@exactly/common/validation"; +import type { GooglePayTokenInfo, TokenInfo } from "@meawallet/react-native-mpp"; + +type GoogleToken = GooglePayTokenInfo | TokenInfo; +type Wallet = { + default: { + ApplePay: { + AddPassButton?: React.ComponentType<{ + addPassButtonStyle: "black"; + onPress: () => void; + style: { height: number; width: number }; + }>; + canAddPaymentPass(): Promise; + canAddPaymentPassWithPrimaryAccountIdentifier(primaryAccountIdentifier: string): Promise; + canAddRemoteSecureElementPassWithPrimaryAccountIdentifier(primaryAccountIdentifier: string): Promise; + canAddSecureElementPassWithPrimaryAccountIdentifier(primaryAccountIdentifier: string): Promise; + initializeOemTokenization(cardData: unknown): Promise<{ + localizedDescription?: string; + networkName?: string; + primaryAccountIdentifier?: string; + primaryAccountSuffix?: string; + tokenizationReceipt?: string; + validFor?: number; + }>; + isPassLibraryAvailable(): Promise; + isWatchPaired(): Promise; + registerDataChangedListener(listener: (data?: unknown) => void): unknown; + remoteSecureElementPassExistsWithPrimaryAccountIdentifier(primaryAccountIdentifier: string): Promise; + removeDataChangedListener(subscription: unknown): void; + secureElementPassExistsWithPrimaryAccountIdentifier(primaryAccountIdentifier: string): Promise; + setDebugLoggingEnabled(enabled: boolean): void; + showAddPaymentPassView(response: unknown): Promise; + }; + GooglePay: { + activateWithTokenInfo(token: TokenInfo): Promise; + checkWalletForCardToken(cardData: unknown): Promise; + isWalletAvailable(): Promise; + push(cardData: unknown, cardDisplayName: string, userAddress: object): Promise; + registerDataChangedListener(listener: () => void): unknown; + removeDataChangedListener(subscription: unknown): void; + tokenize(token: GooglePayTokenInfo, cardDisplayName: string): Promise; + }; + initialize(): Promise; + }; + MppCardDataParameters: { + withCardSecret(cardId: string, cardSecret: string): unknown; + }; +}; +type WalletEligibility = { apple: boolean; google: "added" | "cta" | "hidden"; googleToken: GoogleToken | null }; +const hiddenWallet = { apple: false, google: "hidden", googleToken: null } satisfies WalletEligibility; + +const provisioningSdk = Platform.OS === "web" ? undefined : (require("@meawallet/react-native-mpp") as Wallet); // eslint-disable-line unicorn/prefer-module +let walletInitPromise: Promise | undefined; +const primaryAccountIdentifiers = new Map(); +const redactedWalletKeys = new Set([ + "activationData", + "cardNumber", + "cardSecret", + "certificates", + "cvv", + "encryptedPassData", + "ephemeralPublicKey", + "nonce", + "nonceSignature", + "pan", + "tokenizationReceipt", + "wrappedKey", +]); + +function redactWalletValue(value: unknown, key?: string, seen = new WeakSet()): unknown { + if (value == null || typeof value === "boolean" || typeof value === "number") return value; + if (typeof value === "string") { + if (key !== undefined && redactedWalletKeys.has(key)) return `[redacted:${value.length}]`; + if (key?.toLowerCase().includes("identifier")) return `…${value.slice(-8)}`; + return value.length > 240 ? `${value.slice(0, 237)}...` : value; + } + if (value instanceof Error) { + return redactWalletValue( + { + cause: value.cause, + message: value.message, + name: value.name, + }, + key, + seen, + ); + } + if (Array.isArray(value)) return value.map((item) => redactWalletValue(item, key, seen)); + if (typeof value === "object") { + if (seen.has(value)) return "[circular]"; + seen.add(value); + const result: Record = {}; + for (const [entryKey, entryValue] of Object.entries(value)) { + result[entryKey] = redactWalletValue(entryValue, entryKey, seen); + } + return result; + } + return "[unsupported]"; +} + +function walletLog(event: string, details?: Record) { + console.log(`[wallet] ${JSON.stringify(redactWalletValue(details ? { event, ...details } : { event }))}`); // eslint-disable-line no-console -- temporary provisioning trace +} + +function summarizeWalletError(error: unknown) { + const classification = classifyError(error); + const value = + typeof error === "object" && error !== null + ? (error as { cause?: unknown; code?: unknown; domain?: unknown; message?: unknown; name?: unknown }) + : undefined; + return redactWalletValue({ + cause: value?.cause, + classification: { + known: classification.known, + knownInfo: classification.knownInfo, + knownWarning: classification.knownWarning, + walletCancelled: classification.walletCancelled, + walletRejected: classification.walletRejected, + }, + code: value?.code, + domain: value?.domain, + message: error instanceof Error ? error.message : (value?.message ?? String(error)), + name: error instanceof Error ? error.name : value?.name, + userInfo: "userInfo" in (value ?? {}) ? (value as { userInfo?: unknown }).userInfo : undefined, + }) as Record; +} + +async function traceWalletCall(event: string, work: () => Promise, details?: Record) { + const startedAt = Date.now(); + walletLog(`${event}.start`, details); + try { + const result = await work(); + walletLog(`${event}.success`, { ...details, durationMs: Date.now() - startedAt, result }); + return result; + } catch (error) { + walletLog(`${event}.error`, { ...details, durationMs: Date.now() - startedAt, error: summarizeWalletError(error) }); + throw error; + } +} +function getApplePayEmitter() { + if (Platform.OS !== "ios" || !NativeModules.ApplePay) return; + try { + return new NativeEventEmitter(NativeModules.ApplePay as never); + } catch (error) { + walletLog("apple.nativeEmitter.error", { error: summarizeWalletError(error) }); + } +} + +function enableWalletDebugLogging(wallet: Wallet) { + if (Platform.OS !== "ios") return; + try { + wallet.default.ApplePay.setDebugLoggingEnabled(true); + walletLog("apple.debug.enabled", { enabled: true }); + } catch (error) { + walletLog("apple.debug.enable.error", { error: summarizeWalletError(error) }); + } +} + +function isGoogleToken(token: unknown): token is GoogleToken { + return typeof token === "object" && token !== null && "tokenState" in token && typeof token.tokenState === "string"; +} + +const activeTokenStates = new Set(["TOKEN_STATE_ACTIVE"]); +const pushTokenStates = new Set(["TOKEN_STATE_NOT_FOUND", "TOKEN_STATE_UNTOKENIZED"]); // cspell:ignore untokenized +function isActiveToken(token: GoogleToken) { + return activeTokenStates.has(token.tokenState); +} + +function needsPushToken(token: GoogleToken) { + return pushTokenStates.has(token.tokenState); +} + +function isIssuerToken(token: GoogleToken): token is TokenInfo { + return "issuerTokenId" in token; +} + +function initWallet() { + if (!provisioningSdk) return Promise.reject(new Error("wallet unavailable on web")); + walletInitPromise ??= provisioningSdk.default + .initialize() + .then(() => { + enableWalletDebugLogging(provisioningSdk); + return provisioningSdk; + }) + .catch((error: unknown) => { + walletLog("wallet.initialize.error", { error: summarizeWalletError(error) }); + walletInitPromise = undefined; + throw error; + }); + return walletInitPromise; +} export default function Card() { const toast = useToastController(); @@ -128,6 +320,7 @@ export default function Card() { queryClient.invalidateQueries({ queryKey: ["kyc", "status"], exact: true }).catch(reportError); if (address) refetchMarkets().catch(reportError); if (address && credential) refetchInstalledPlugins().catch(reportError); + if (Platform.OS !== "web" && cardDetails?.lastFour) refetchWalletEligible().catch(reportError); queryClient.refetchQueries({ queryKey }).catch(reportError); }; useTabPress("card", () => { @@ -264,6 +457,351 @@ export default function Card() { }, }); + const [sdk, setSdk] = useState(null); + const [provisioning, setProvisioning] = useState(false); + const walletInFlightRef = useRef(false); + const walletAttemptRef = useRef(undefined); + const { + data: walletEligible, + isPending: isPendingWallet, + refetch: refetchWalletEligible, + } = useQuery({ + queryKey: ["wallet", "eligible", cardDetails?.lastFour], + enabled: Platform.OS !== "web" && cardDetails?.lastFour.length === 4, + queryFn: async () => { + const lastFour = cardDetails?.lastFour; + if (!lastFour || Platform.OS === "web") return hiddenWallet; + walletLog("wallet.eligibility.start", { lastFour }); + const nextWallet = await initWallet(); + if (Platform.OS === "ios") { + try { + const [{ cardId, cardSecret }, available, canAdd, watchPaired] = await Promise.all([ + traceWalletCall( + "apple.provisioning.fetch", + () => + queryClient.fetchQuery<{ cardId: string; cardSecret: string }>({ + queryKey: ["card", "provisioning"], + staleTime: 0, + }), + { reason: "eligibility" }, + ), + traceWalletCall( + "apple.isPassLibraryAvailable", + () => nextWallet.default.ApplePay.isPassLibraryAvailable(), + { + reason: "eligibility", + }, + ), + traceWalletCall("apple.canAddPaymentPass", () => nextWallet.default.ApplePay.canAddPaymentPass(), { + reason: "eligibility", + }), + traceWalletCall("apple.isWatchPaired", () => nextWallet.default.ApplePay.isWatchPaired(), { + reason: "eligibility", + }).catch(() => undefined), + ]); + const cachedPrimaryAccountIdentifier = primaryAccountIdentifiers.get(cardId); + walletLog("apple.eligibility.base", { + available, + canAdd, + cardId, + hasCachedPrimaryAccountIdentifier: cachedPrimaryAccountIdentifier !== undefined, + lastFour, + watchPaired, + }); + if (!available || !canAdd) return hiddenWallet; + const response = + cachedPrimaryAccountIdentifier === undefined + ? await traceWalletCall( + "apple.initializeOemTokenization", + () => + nextWallet.default.ApplePay.initializeOemTokenization( + nextWallet.MppCardDataParameters.withCardSecret(cardId, cardSecret), + ), + { cardId, reason: "eligibility" }, + ) + : { primaryAccountIdentifier: cachedPrimaryAccountIdentifier }; + const primaryAccountIdentifier = response.primaryAccountIdentifier; + if (primaryAccountIdentifier) { + primaryAccountIdentifiers.set(cardId, primaryAccountIdentifier); + walletLog("apple.eligibility.identifier", { + cardId, + lastFour, + primaryAccountIdentifier, + usedCachedPrimaryAccountIdentifier: cachedPrimaryAccountIdentifier !== undefined, + }); + const secureElementPassExists = await traceWalletCall( + "apple.secureElementPassExists", + () => + nextWallet.default.ApplePay.secureElementPassExistsWithPrimaryAccountIdentifier( + primaryAccountIdentifier, + ), + { primaryAccountIdentifier, reason: "eligibility" }, + ); + const [ + canAddByPrimaryAccountIdentifier, + canAddRemoteSecureElement, + canAddSecureElement, + remoteSecureElementPassExists, + ] = await Promise.all([ + traceWalletCall( + "apple.canAddPaymentPassWithPrimaryAccountIdentifier", + () => + nextWallet.default.ApplePay.canAddPaymentPassWithPrimaryAccountIdentifier(primaryAccountIdentifier), + { primaryAccountIdentifier, reason: "eligibility" }, + ).catch(() => undefined), + traceWalletCall( + "apple.canAddRemoteSecureElementPassWithPrimaryAccountIdentifier", + () => + nextWallet.default.ApplePay.canAddRemoteSecureElementPassWithPrimaryAccountIdentifier( + primaryAccountIdentifier, + ), + { primaryAccountIdentifier, reason: "eligibility" }, + ).catch(() => undefined), + traceWalletCall( + "apple.canAddSecureElementPassWithPrimaryAccountIdentifier", + () => + nextWallet.default.ApplePay.canAddSecureElementPassWithPrimaryAccountIdentifier( + primaryAccountIdentifier, + ), + { primaryAccountIdentifier, reason: "eligibility" }, + ).catch(() => undefined), + traceWalletCall( + "apple.remoteSecureElementPassExistsWithPrimaryAccountIdentifier", + () => + nextWallet.default.ApplePay.remoteSecureElementPassExistsWithPrimaryAccountIdentifier( + primaryAccountIdentifier, + ), + { primaryAccountIdentifier, reason: "eligibility" }, + ).catch(() => undefined), + ]); + walletLog("apple.eligibility.diagnostics", { + canAddByPrimaryAccountIdentifier, + canAddRemoteSecureElement, + canAddSecureElement, + lastFour, + remoteSecureElementPassExists, + secureElementPassExists, + }); + return { apple: !secureElementPassExists, google: "hidden", googleToken: null }; + } + walletLog("apple.eligibility.identifier.missing", { + cardId, + keys: Object.keys(response), + lastFour, + primaryAccountSuffix: response.primaryAccountSuffix, + tokenizationReceiptLength: response.tokenizationReceipt?.length, + validFor: response.validFor, + }); + return { apple: true, google: "hidden", googleToken: null }; + } catch (error) { + walletLog("apple.eligibility.error", { error: summarizeWalletError(error), lastFour }); + reportError(error); + return { apple: true, google: "hidden", googleToken: null }; + } + } + if (Platform.OS !== "android") return hiddenWallet; + return nextWallet.default.GooglePay.isWalletAvailable() + .then(async (available) => { + if (!available) return hiddenWallet; + const token = await queryClient + .fetchQuery<{ cardId: string; cardSecret: string }>({ + queryKey: ["card", "provisioning"], + staleTime: 0, + }) + .then(({ cardId, cardSecret }) => + nextWallet.default.GooglePay.checkWalletForCardToken( + nextWallet.MppCardDataParameters.withCardSecret(cardId, cardSecret), + ), + ) + .catch((error: unknown) => { + reportError(error); + }); + if (token === undefined) return hiddenWallet; + const tokenList: GoogleToken[] = []; + if (Array.isArray(token)) { + for (const item of token) if (isGoogleToken(item) && !needsPushToken(item)) tokenList.push(item); + } else if (isGoogleToken(token) && !needsPushToken(token)) { + tokenList.push(token); + } + const google: WalletEligibility["google"] = + tokenList.length === 0 ? "cta" : tokenList.some((item) => isActiveToken(item)) ? "added" : "cta"; + return { + apple: false, + google, + googleToken: tokenList.find((item) => !isActiveToken(item)) ?? null, + }; + }) + .catch((error: unknown) => { + reportError(error); + return hiddenWallet; + }); + }, + }); + + useEffect(() => { + if (Platform.OS === "web" || cardDetails?.lastFour.length !== 4) return; + const lastFour = cardDetails.lastFour; + let mounted = true; + let cleanup: (() => void) | undefined; + initWallet() + .then((nextWallet) => { + if (!mounted) return; + setSdk((current) => current ?? nextWallet); + if (Platform.OS === "ios") { + const traceSubscription = getApplePayEmitter()?.addListener("ApplePayTrace", (data) => { + walletLog("apple.native", { lastFour, trace: data }); + }); + const subscription = nextWallet.default.ApplePay.registerDataChangedListener((data) => { + walletLog("apple.dataChanged", { data, lastFour }); + refetchWalletEligible().catch((error: unknown) => { + walletLog("apple.refetchWalletEligible.error", { + error: summarizeWalletError(error), + lastFour, + }); + reportError(error); + }); + }); + cleanup = () => { + traceSubscription?.remove(); + nextWallet.default.ApplePay.removeDataChangedListener(subscription); + }; + return; + } + const subscription = nextWallet.default.GooglePay.registerDataChangedListener(() => { + walletLog("google.dataChanged", { lastFour }); + refetchWalletEligible().catch((error: unknown) => { + walletLog("google.refetchWalletEligible.error", { + error: summarizeWalletError(error), + lastFour, + }); + reportError(error); + }); + }); + cleanup = () => nextWallet.default.GooglePay.removeDataChangedListener(subscription); + }) + .catch((error: unknown) => { + walletLog("wallet.listener.error", { error: summarizeWalletError(error), lastFour }); + }); + return () => { + mounted = false; + cleanup?.(); + }; + }, [cardDetails?.lastFour, refetchWalletEligible]); + + const withWalletProvisioning = (work: () => Promise) => { + const lastFour = cardDetails?.lastFour; + if (walletInFlightRef.current) { + walletLog("wallet.provisioning.ignored", { lastFour, reason: "in_flight" }); + return Promise.resolve(); + } + const attempt = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; + const startedAt = Date.now(); + walletAttemptRef.current = attempt; + walletInFlightRef.current = true; + setProvisioning(true); + walletLog("wallet.provisioning.start", { attempt, lastFour }); + return work() + .then((result) => { + walletLog("wallet.provisioning.success", { + attempt, + durationMs: Date.now() - startedAt, + lastFour, + }); + return result; + }) + .catch((error: unknown) => { + const classification = classifyError(error); + walletLog("wallet.provisioning.error", { + attempt, + durationMs: Date.now() - startedAt, + error: summarizeWalletError(error), + lastFour, + }); + if (classification.walletCancelled) { + walletLog("wallet.provisioning.cancelled", { + attempt, + durationMs: Date.now() - startedAt, + lastFour, + }); + return; + } + reportError(error); + throw error; + }) + .finally(() => { + walletLog("wallet.provisioning.finish", { + attempt, + durationMs: Date.now() - startedAt, + lastFour, + }); + walletAttemptRef.current = undefined; + walletInFlightRef.current = false; + setProvisioning(false); + }); + }; + + const addToAppleWallet = () => + withWalletProvisioning(async () => { + walletLog("apple.add.start", { hasSdk: sdk !== null, lastFour: cardDetails?.lastFour }); + const nextWallet = sdk ?? (await initWallet()); + const { cardId, cardSecret } = await traceWalletCall( + "apple.provisioning.fetch", + () => + queryClient.fetchQuery<{ cardId: string; cardSecret: string }>({ + queryKey: ["card", "provisioning"], + staleTime: 0, + }), + { reason: "add" }, + ); + const response = await traceWalletCall( + "apple.initializeOemTokenization", + () => + nextWallet.default.ApplePay.initializeOemTokenization( + nextWallet.MppCardDataParameters.withCardSecret(cardId, cardSecret), + ), + { cardId, reason: "add" }, + ); + walletLog("apple.add.tokenization", { + cardId, + hasPrimaryAccountIdentifier: response.primaryAccountIdentifier !== undefined, + keys: Object.keys(response), + primaryAccountIdentifier: response.primaryAccountIdentifier, + primaryAccountSuffix: response.primaryAccountSuffix, + tokenizationReceiptLength: response.tokenizationReceipt?.length, + validFor: response.validFor, + }); + const activationState = await traceWalletCall( + "apple.showAddPaymentPassView", + () => nextWallet.default.ApplePay.showAddPaymentPassView(response), + { cardId, primaryAccountIdentifier: response.primaryAccountIdentifier, validFor: response.validFor }, + ); + walletLog("apple.add.result", { activationState, cardId, lastFour: cardDetails?.lastFour }); + return true; + }); + + const addToGoogleWallet = () => + withWalletProvisioning(async () => { + const nextWallet = sdk ?? (await initWallet()); + const googleToken = walletEligible?.googleToken; + if (googleToken) { + await (isIssuerToken(googleToken) + ? nextWallet.default.GooglePay.activateWithTokenInfo(googleToken) + : nextWallet.default.GooglePay.tokenize(googleToken, cardDetails?.displayName ?? "")); + return true; + } + const { cardId, cardSecret } = await queryClient.fetchQuery<{ cardId: string; cardSecret: string }>({ + queryKey: ["card", "provisioning"], + staleTime: 0, + }); + await nextWallet.default.GooglePay.push( + nextWallet.MppCardDataParameters.withCardSecret(cardId, cardSecret), + cardDetails?.displayName ?? "", + {}, + ); + return true; + }); + + const AddPassButton = Platform.OS === "ios" ? sdk?.default.ApplePay.AddPassButton : undefined; const displayStatus = isSettingCardStatus ? optimisticCardStatus : cardDetails?.status; return ( @@ -320,6 +858,65 @@ export default function Card() { revealCard().catch(reportError); }} /> + {Platform.OS !== "web" && + cardDetails && + !isPendingWallet && + walletEligible && + (walletEligible.apple || walletEligible.google !== "hidden") ? ( + + {provisioning ? ( + + ) : ( + <> + {AddPassButton && walletEligible.apple ? ( + { + addToAppleWallet() + .then((added) => { + if (!added) return; + Alert.alert( + t("Card added"), + t("Your card was added to your wallet. Follow any remaining steps if prompted."), + ); + }) + .catch(() => undefined); + }} + /> + ) : null} + {walletEligible.google === "cta" ? ( + { + addToGoogleWallet() + .then((added) => { + if (!added) return; + Alert.alert( + t("Card added"), + t("Your card was added to your wallet. Follow any remaining steps if prompted."), + ); + }) + .catch(() => undefined); + }} + > + + + ) : null} + {walletEligible.google === "added" ? ( + + + + {t("added to google wallet")} + + + ) : null} + + )} + + ) : null} void; op const theme = useColorScheme(); const toast = useToastController(); const { t } = useTranslation(); - const { data: alertShown } = useQuery({ queryKey: ["settings", "alertShown"] }); const { data: card, isPending } = useQuery({ queryKey: ["card", "details"] }); + const { data: alertShown } = useQuery({ queryKey: ["settings", "alertShown"] }); const [details, setDetails] = useState({ pan: "", cvc: "" }); useEffect(() => { if (card?.encryptedPan && card.encryptedCvc) { @@ -171,7 +171,8 @@ export default function CardDetails({ open, onClose }: { onClose: () => void; op ) : null} - {card && alertShown ? ( + + {Platform.OS === "web" && card && alertShown ? ( { @@ -179,6 +180,7 @@ export default function CardDetails({ open, onClose }: { onClose: () => void; op }} /> ) : null} + diff --git a/src/i18n/en.json b/src/i18n/en.json index 7f3f3f989..6301f4bdc 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -14,5 +14,6 @@ "Pending requests → {{count}}_one": "Pending request → {{count}}", "Pending requests → {{count}}_other": "Pending requests → {{count}}", "You repay {{count}} installments of_one": "You repay {{count}} installment of", - "You repay {{count}} installments of_other": "You repay {{count}} installments of" + "You repay {{count}} installments of_other": "You repay {{count}} installments of", + "added to google wallet": "added to google wallet" } diff --git a/src/i18n/es-AR.json b/src/i18n/es-AR.json index fbeadc018..3f52c2752 100644 --- a/src/i18n/es-AR.json +++ b/src/i18n/es-AR.json @@ -128,6 +128,8 @@ "You must repay each installment manually before its due date.": "Debés pagar cada cuota manualmente antes de su fecha de vencimiento.", "You send": "Enviás", "Your card is awaiting activation. Follow the steps to enable it.": "Tu tarjeta está a la espera de activación. Seguí los pasos para habilitarla.", + "Card added": "Tarjeta agregada", + "Your card was added to your wallet. Follow any remaining steps if prompted.": "Tu tarjeta fue agregada a tu billetera. Seguí cualquier paso restante si se te indica.", "Your password manager does not support passkey backups. Please try a different one": "Tu gestor de contraseñas no admite copias de seguridad de llaves de acceso. Por favor, probá con otro.", "Your spending limit is the maximum amount you can spend on your Exa Card.": "Tu límite de gasto es el monto máximo que podés gastar con tu Exa Card.", "Your transactions will show up here once you get started. Add funds to begin!": "Tus transacciones aparecerán aquí una vez que comiences. ¡Agregá fondos para comenzar!", diff --git a/src/i18n/es.json b/src/i18n/es.json index 62973a661..f125f5743 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -126,6 +126,7 @@ "Card number copied!": "¡Número de tarjeta copiado!", "Card upgraded successfully": "Tarjeta actualizada exitosamente", "Card": "Tarjeta", + "added to google wallet": "agregado a google wallet", "Chain {{id}}": "Cadena {{id}}", "Change the pay mode before each purchase and pay how you want.": "Cambia el modo de pago antes de cada compra y paga como quieras.", "Check out our X or Discord for updates—or report the issue so we can take a closer look.": "Revisa nuestro X o Discord para actualizaciones o reporta el problema para que podamos investigarlo más a fondo.", @@ -697,6 +698,8 @@ "Your Exa account": "Tu cuenta Exa", "Your Exa Card is now upgraded to Visa Signature.": "Tu Exa Card ha sido actualizada a Visa Signature.", "Your funds serve as collateral to increase your spending limits.": "Tus fondos sirven como garantía para aumentar tus límites de gasto.", + "Card added": "Tarjeta agregada", + "Your card was added to your wallet. Follow any remaining steps if prompted.": "Tu tarjeta fue agregada a tu billetera. Sigue cualquier paso restante si se te indica.", "Your funds serve as collateral, increasing your spending limits. The more funds you add, the more you can spend with the Exa Card.": "Tus fondos sirven como garantía, aumentando tus límites de gasto. Cuantos más fondos agregues, más podrás gastar con la Exa Card.", "Your ID needs to be updated": "Tu documento necesita ser actualizado", "Your password manager does not support passkey backups. Please try a different one": "Tu gestor de contraseñas no admite copias de seguridad de llaves de acceso. Por favor, prueba con otro.", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 53ba8bef2..b335cb8a3 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -126,6 +126,7 @@ "Card number copied!": "Número do cartão copiado!", "Card upgraded successfully": "Cartão atualizado com sucesso", "Card": "Cartão", + "added to google wallet": "adicionado ao google wallet", "Chain {{id}}": "Rede {{id}}", "Change the pay mode before each purchase and pay how you want.": "Mude o modo de pagamento antes de cada compra e pague como quiser.", "Check out our X or Discord for updates—or report the issue so we can take a closer look.": "Confira nosso X ou Discord para atualizações — ou reporte o problema para que possamos investigar mais a fundo.", @@ -697,6 +698,8 @@ "Your Exa account": "Sua conta Exa", "Your Exa Card is now upgraded to Visa Signature.": "Seu Exa Card foi atualizado para Visa Signature.", "Your funds serve as collateral to increase your spending limits.": "Seus fundos servem como garantia para aumentar seus limites de gastos.", + "Card added": "Cartão adicionado", + "Your card was added to your wallet. Follow any remaining steps if prompted.": "Seu cartão foi adicionado à sua carteira. Siga qualquer etapa restante se for solicitado.", "Your funds serve as collateral, increasing your spending limits. The more funds you add, the more you can spend with the Exa Card.": "Seus fundos servem como garantia, aumentando seus limites de gastos. Quanto mais fundos você adicionar, mais poderá gastar com o Exa Card.", "Your ID needs to be updated": "Seu documento precisa ser atualizado", "Your password manager does not support passkey backups. Please try a different one": "Seu gerenciador de senhas não suporta backup de chaves de acesso. Por favor, tente outro", diff --git a/src/utils/reportError.ts b/src/utils/reportError.ts index 41bea024f..0320fe60d 100644 --- a/src/utils/reportError.ts +++ b/src/utils/reportError.ts @@ -78,6 +78,14 @@ function parseError(error: unknown) { typeof root === "object" && root !== null && "code" in root && typeof root.code === "string" && root.code.length > 0 ? root.code : undefined; + let domain = + typeof root === "object" && + root !== null && + "domain" in root && + typeof root.domain === "string" && + root.domain.length > 0 + ? root.domain + : undefined; let status: string | undefined; for ( let cause: unknown = error; @@ -105,12 +113,15 @@ function parseError(error: unknown) { root.message.length > 0 ? normalizeMessage(root.message) : undefined; + const passKit = message?.match(/\bError Domain=([^ ]+) Code=(-?\d+)\b/); + domain ??= passKit?.[1]; + status ??= passKit?.[2]; const revert = error instanceof BaseError && error.walk((r) => r instanceof ContractFunctionRevertedError) !== null; const reason = revertReason(error, { fallback: "unknown" }); - return { code, message, name, reason, revert, status }; + return { code, domain, message, name, reason, revert, status }; } -function classify({ code, message, name, reason, revert, status }: ParsedError) { +function classify({ code, domain, message, name, reason, revert, status }: ParsedError) { const passkeyNotAllowed = name === "NotAllowedError" || (message !== undefined && authPrefixes.some((prefix) => message.startsWith(prefix))); const passkeyCancelled = @@ -122,6 +133,7 @@ function classify({ code, message, name, reason, revert, status }: ParsedError) (passkeyKnownPatterns.some((pattern) => pattern.test(message)) || authPrefixes.some((prefix) => message.startsWith(prefix)))); const passkeyWarning = passkeyKnown && !passkeyCancelled && !passkeyNotAllowed; + const walletCancelled = domain === "PKPassKitErrorDomain" && (status === "1" || status === "2"); const biometric = code === "ERR_BIOMETRIC"; const walletRejected = status === "4001" || status === "5000"; const bundleCancelled = status === "5730"; @@ -131,10 +143,16 @@ function classify({ code, message, name, reason, revert, status }: ParsedError) biometric || walletRejected || bundleCancelled || + walletCancelled || message === "invalid operation"; const network = classifyNetwork(message); const knownWarning = - passkeyKnown || biometric || walletRejected || bundleCancelled || message === "invalid operation"; + passkeyKnown || + walletCancelled || + biometric || + walletRejected || + bundleCancelled || + message === "invalid operation"; const knownInfo = network !== undefined; const known = knownWarning || knownInfo; const value = @@ -164,6 +182,7 @@ function classify({ code, message, name, reason, revert, status }: ParsedError) passkeyNotAllowed, passkeyWarning, revert, + walletCancelled, walletRejected, }; } diff --git a/src/utils/server.ts b/src/utils/server.ts index 230ab5c6b..c0aaedc71 100644 --- a/src/utils/server.ts +++ b/src/utils/server.ts @@ -100,6 +100,21 @@ async function getCard() { queryClient.setQueryDefaults(["card", "details"], { queryFn: getCard }); export type CardDetails = Awaited>; +async function getCardProvisioning() { + await auth(); + const { id } = await session(); + const response = await api.card.$get({ header: { sessionid: id }, query: { scope: "provisioning" } }); + if (!response.ok) { + const { code } = await response.json(); + throw new APIError(response.status, code); + } + const card = await parseResponse(response); + if (!card.provisioning) throw new Error("bad card provisioning response"); + return { cardId: card.provisioning.id, cardSecret: card.provisioning.secret }; +} + +queryClient.setQueryDefaults(["card", "provisioning"], { queryFn: getCardProvisioning }); + async function getPIN() { const result = await getCard(); if (!result) return null; From d1beba53c3508558d6fa769035874e67a9be0010 Mon Sep 17 00:00:00 2001 From: Miguel Diaz Date: Thu, 26 Mar 2026 13:55:51 +0100 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=8D=B1=20app:=20add=20meawallet=20con?= =?UTF-8?q?fig=20assets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/rich-months-double.md | 5 +++++ eas.json | 1 + src/assets/mea_config | Bin 0 -> 832 bytes 3 files changed, 6 insertions(+) create mode 100644 .changeset/rich-months-double.md create mode 100644 src/assets/mea_config diff --git a/.changeset/rich-months-double.md b/.changeset/rich-months-double.md new file mode 100644 index 000000000..9ede42ebe --- /dev/null +++ b/.changeset/rich-months-double.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +🍱 add meawallet config assets diff --git a/eas.json b/eas.json index 1f438ca06..d8d4420cb 100644 --- a/eas.json +++ b/eas.json @@ -5,6 +5,7 @@ "resourceClass": "large", "node": "24.14.1", "pnpm": "10.33.0", + "env": { "npm_config_mpp_env": "prod" }, "android": { "image": "latest" }, "ios": { "image": "latest" } }, diff --git a/src/assets/mea_config b/src/assets/mea_config new file mode 100644 index 0000000000000000000000000000000000000000..671a1be2db3613606708e86dcf3576ec59212db4 GIT binary patch literal 832 zcmV-G1Hb%&i7Fg@8_vHL<+?dR+A<`p+rXMzB3Lxr-{%e z>5DS!g}~w=_|of6$D1Rw(soTb%*sVbG67x6%Jw#suiQNTbrxFO2-3XBKHor%d|zo( z3DOw@%daIp$GN4K58RixU*|ZNZS2o*PivB0+NlwB-SG_$$w3N7{smj4thoUSCh0Uk z@$IhXkGd%@*IY&Hc+PojV4QMg{-W_V4L1OFS2;1Q=*`1RpZ%{b&lHqFAE zH}XXW3Hh=3$?^3XDD(Mxl^mlCd{t@3Y1xW)x}v1?bX68mQbs}Hps&|^);#45<;MjF zX{w_)&daC!^12D7HJcQUrNS~SVMH+l*opLgtwtb4>7s|Q*S*;0(b-RHbDOUZ(^F*J$DNj<^E9+*+1rU$gMzd? zK6=~le;YWsS)XYKQXS%y)ru;T3$`M3?qe=-u(lFxo8> z`o&M*a8;+0MSn`7v)>@*#6Pp33+Vy<3?irKbh2YH3{t31yIm?UtgAE!iNvmfjR z?<+YRKU$hs5sQkMPa5!&D5w1oa}e?9JbmeUe<#w+-Cfd$MT=o~TrwCK&M&_tcwfz$ z_!)&qIx@~roOTvpJ~uR=F@NDP1Gp_5Ra9a9S~dW}$^;4IpZY-k#yk0@Yh`T<7-!HF zGEh@5#iPZ1yfvdZYzXIyM%oD6+h!pUDLzqIKM$m KLb>2_hLsLo5T}^{ literal 0 HcmV?d00001 From 729e163dcbf45cf55eb30752f0f46037c3efd525 Mon Sep 17 00:00:00 2001 From: Miguel Diaz Date: Thu, 2 Apr 2026 14:48:47 +0200 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=A9=B9=20eslint:=20fix=20plugin=20re-?= =?UTF-8?q?registration=20conflict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 + pnpm-lock.yaml | 190 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 153 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 2c113aafb..3fec81436 100644 --- a/package.json +++ b/package.json @@ -168,6 +168,9 @@ "packageManager": "pnpm@10.33.0", "pnpm": { "overrides": { + "@typescript-eslint/eslint-plugin": "8.58.0", + "@typescript-eslint/parser": "8.58.0", + "eslint-plugin-n": "17.24.0", "@wagmi/core": "catalog:", "abitype>zod": "^4.0.0", "comlink": "$comlink", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e42d9db8e..1b95106c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ catalogs: version: 3.6.0 overrides: + '@typescript-eslint/eslint-plugin': 8.58.0 + '@typescript-eslint/parser': 8.58.0 + eslint-plugin-n: 17.24.0 '@wagmi/core': ^3.4.1 abitype>zod: ^4.0.0 comlink: ^4.4.2 @@ -417,7 +420,7 @@ importers: version: 22.6.2(@babel/traverse@7.29.0)(@zkochan/js-yaml@0.0.7)(eslint@9.39.4)(nx@22.6.2) '@nx/eslint-plugin': specifier: 22.6.2 - version: 22.6.2(@babel/traverse@7.29.0)(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(nx@22.6.2)(typescript@5.9.3) + version: 22.6.2(@babel/traverse@7.29.0)(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(nx@22.6.2)(typescript@5.9.3) '@nx/js': specifier: 22.6.2 version: 22.6.2(@babel/traverse@7.29.0)(nx@22.6.2) @@ -568,7 +571,7 @@ importers: version: 0.2.22 '@nx/eslint-plugin': specifier: ^22.6.2 - version: 22.6.2(@babel/traverse@7.29.0)(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(nx@22.6.2)(typescript@5.9.3) + version: 22.6.2(@babel/traverse@7.29.0)(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(nx@22.6.2)(typescript@5.9.3) '@tanstack/eslint-plugin-query': specifier: ^5.95.2 version: 5.95.2(eslint@9.39.4)(typescript@5.9.3) @@ -580,7 +583,7 @@ importers: version: 3.0.1 '@vitest/eslint-plugin': specifier: ^1.6.13 - version: 1.6.13(@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)(vitest@4.1.2) + version: 1.6.13(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)(vitest@4.1.2) '@wagmi/cli': specifier: ^2.10.0 version: 2.10.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10) @@ -598,7 +601,7 @@ importers: version: 4.4.4(eslint-plugin-import@2.32.0)(eslint@9.39.4) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) + version: 2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) eslint-plugin-jsdoc: specifier: ^62.8.1 version: 62.8.1(eslint@9.39.4) @@ -606,7 +609,7 @@ importers: specifier: ^6.10.2 version: 6.10.2(eslint@9.39.4) eslint-plugin-n: - specifier: ^17.24.0 + specifier: 17.24.0 version: 17.24.0(eslint@9.39.4)(typescript@5.9.3) eslint-plugin-perfectionist: specifier: ^5.7.0 @@ -3820,7 +3823,7 @@ packages: '@nx/eslint-plugin@22.6.2': resolution: {integrity: sha512-wrq+MwZ2QErQdm7XiI1jLSsJ658Yg7sR12gZLTyRfKvTFZIqBMQrOBd8v1IaRc+ZLUUiv9hqhzteLm/+EGAYuA==} peerDependencies: - '@typescript-eslint/parser': ^6.13.2 || ^7.0.0 || ^8.0.0 + '@typescript-eslint/parser': 8.58.0 eslint-config-prettier: ^10.0.0 peerDependenciesMeta: eslint-config-prettier: @@ -6219,16 +6222,16 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.57.2': - resolution: {integrity: sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==} + '@typescript-eslint/eslint-plugin@8.58.0': + resolution: {integrity: sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.57.2 + '@typescript-eslint/parser': 8.58.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ^5.9.3 - '@typescript-eslint/parser@8.57.2': - resolution: {integrity: sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==} + '@typescript-eslint/parser@8.58.0': + resolution: {integrity: sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -6246,6 +6249,12 @@ packages: peerDependencies: typescript: ^5.9.3 + '@typescript-eslint/project-service@8.58.0': + resolution: {integrity: sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: ^5.9.3 + '@typescript-eslint/scope-manager@8.56.1': resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6254,6 +6263,10 @@ packages: resolution: {integrity: sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.58.0': + resolution: {integrity: sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.56.1': resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6266,6 +6279,12 @@ packages: peerDependencies: typescript: ^5.9.3 + '@typescript-eslint/tsconfig-utils@8.58.0': + resolution: {integrity: sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: ^5.9.3 + '@typescript-eslint/type-utils@8.57.2': resolution: {integrity: sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6273,6 +6292,13 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ^5.9.3 + '@typescript-eslint/type-utils@8.58.0': + resolution: {integrity: sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ^5.9.3 + '@typescript-eslint/types@8.56.1': resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6281,6 +6307,10 @@ packages: resolution: {integrity: sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.58.0': + resolution: {integrity: sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.56.1': resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6293,6 +6323,12 @@ packages: peerDependencies: typescript: ^5.9.3 + '@typescript-eslint/typescript-estree@8.58.0': + resolution: {integrity: sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: ^5.9.3 + '@typescript-eslint/utils@8.56.1': resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6307,6 +6343,13 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ^5.9.3 + '@typescript-eslint/utils@8.58.0': + resolution: {integrity: sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ^5.9.3 + '@typescript-eslint/visitor-keys@8.56.1': resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6315,6 +6358,10 @@ packages: resolution: {integrity: sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.58.0': + resolution: {integrity: sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -6442,7 +6489,7 @@ packages: resolution: {integrity: sha512-ui7JGWBoQpS5NKKW0FDb1eTuFEZ5EupEv2Psemuyfba7DfA5K52SeDLelt6P4pQJJ/4UGkker/BgMk/KrjH3WQ==} engines: {node: '>=18'} peerDependencies: - '@typescript-eslint/eslint-plugin': '*' + '@typescript-eslint/eslint-plugin': 8.58.0 eslint: '>=8.57.0' typescript: ^5.9.3 vitest: '*' @@ -17350,12 +17397,12 @@ snapshots: - supports-color - verdaccio - '@nx/eslint-plugin@22.6.2(@babel/traverse@7.29.0)(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(nx@22.6.2)(typescript@5.9.3)': + '@nx/eslint-plugin@22.6.2(@babel/traverse@7.29.0)(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(nx@22.6.2)(typescript@5.9.3)': dependencies: '@nx/devkit': 22.6.2(nx@22.6.2) '@nx/js': 22.6.2(@babel/traverse@7.29.0)(nx@22.6.2) '@phenomnomnominal/tsquery': 6.1.4(typescript@5.9.3) - '@typescript-eslint/parser': 8.57.2(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.0(eslint@9.39.4)(typescript@5.9.3) '@typescript-eslint/type-utils': 8.57.2(eslint@9.39.4)(typescript@5.9.3) '@typescript-eslint/utils': 8.57.2(eslint@9.39.4)(typescript@5.9.3) chalk: 4.1.2 @@ -20675,14 +20722,14 @@ snapshots: '@types/node': 25.5.0 optional: true - '@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.57.2(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.57.2 - '@typescript-eslint/type-utils': 8.57.2(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.2(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.57.2 + '@typescript-eslint/parser': 8.58.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.58.0 + '@typescript-eslint/type-utils': 8.58.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.58.0 eslint: 9.39.4 ignore: 7.0.5 natural-compare: 1.4.0 @@ -20691,12 +20738,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3)': + '@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.57.2 - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.57.2 + '@typescript-eslint/scope-manager': 8.58.0 + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.58.0 debug: 4.4.3 eslint: 9.39.4 typescript: 5.9.3 @@ -20705,8 +20752,8 @@ snapshots: '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) - '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.9.3) + '@typescript-eslint/types': 8.57.2 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: @@ -20721,6 +20768,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.58.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@5.9.3) + '@typescript-eslint/types': 8.58.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@8.56.1': dependencies: '@typescript-eslint/types': 8.56.1 @@ -20731,6 +20787,11 @@ snapshots: '@typescript-eslint/types': 8.57.2 '@typescript-eslint/visitor-keys': 8.57.2 + '@typescript-eslint/scope-manager@8.58.0': + dependencies: + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/visitor-keys': 8.58.0 + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': dependencies: typescript: 5.9.3 @@ -20739,6 +20800,10 @@ snapshots: dependencies: typescript: 5.9.3 + '@typescript-eslint/tsconfig-utils@8.58.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@typescript-eslint/type-utils@8.57.2(eslint@9.39.4)(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.57.2 @@ -20751,10 +20816,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@8.58.0(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.0(eslint@9.39.4)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@8.56.1': {} '@typescript-eslint/types@8.57.2': {} + '@typescript-eslint/types@8.58.0': {} + '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': dependencies: '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) @@ -20785,6 +20864,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.58.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.58.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@5.9.3) + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/visitor-keys': 8.58.0 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.56.1(eslint@9.39.4)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) @@ -20807,6 +20901,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.58.0(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@typescript-eslint/scope-manager': 8.58.0 + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3) + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@8.56.1': dependencies: '@typescript-eslint/types': 8.56.1 @@ -20817,6 +20922,11 @@ snapshots: '@typescript-eslint/types': 8.57.2 eslint-visitor-keys: 5.0.1 + '@typescript-eslint/visitor-keys@8.58.0': + dependencies: + '@typescript-eslint/types': 8.58.0 + eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.0': {} '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -20901,13 +21011,13 @@ snapshots: tinyrainbow: 3.1.0 vitest: 4.1.2(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(@vitest/ui@4.1.2)(vite@8.0.5(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/eslint-plugin@1.6.13(@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)(vitest@4.1.2)': + '@vitest/eslint-plugin@1.6.13(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)(vitest@4.1.2)': dependencies: '@typescript-eslint/scope-manager': 8.57.2 '@typescript-eslint/utils': 8.57.2(eslint@9.39.4)(typescript@5.9.3) eslint: 9.39.4 optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) typescript: 5.9.3 vitest: 4.1.2(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(@vitest/ui@4.1.2)(vite@8.0.5(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: @@ -23141,11 +23251,11 @@ snapshots: eslint-config-universe@15.0.3(patch_hash=cfe35ddf48bd80b3df25a30d7d8878394712a39321d2855dcb90a64df2ccc280)(@types/eslint@9.6.1)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4)(prettier@3.8.1)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/parser': 8.57.2(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.0(eslint@9.39.4)(typescript@5.9.3) eslint: 9.39.4 eslint-config-prettier: 9.1.2(eslint@9.39.4) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) eslint-plugin-n: 17.24.0(eslint@9.39.4)(typescript@5.9.3) eslint-plugin-node: 11.1.0(eslint@9.39.4) eslint-plugin-prettier: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@9.1.2(eslint@9.39.4))(eslint@9.39.4)(prettier@3.8.1) @@ -23187,15 +23297,15 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.57.2(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.0(eslint@9.39.4)(typescript@5.9.3) eslint: 9.39.4 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import@2.32.0)(eslint@9.39.4) @@ -23233,7 +23343,7 @@ snapshots: eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -23244,7 +23354,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.4 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -23256,7 +23366,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.57.2(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.0(eslint@9.39.4)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -29567,8 +29677,8 @@ snapshots: typescript-eslint@8.57.2(eslint@9.39.4)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/parser': 8.57.2(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.0(eslint@9.39.4)(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) '@typescript-eslint/utils': 8.57.2(eslint@9.39.4)(typescript@5.9.3) eslint: 9.39.4 From 40459d916bb6366923767d3be5aca7695602d771 Mon Sep 17 00:00:00 2001 From: Miguel Diaz Date: Thu, 9 Apr 2026 17:27:31 +0200 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=92=A9=20app:=20patch=20meawallet=20p?= =?UTF-8?q?odspec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.config.ts | 86 ++++++++++++++++++---- package.json | 1 + patches/@meawallet__react-native-mpp.patch | 10 +++ 3 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 patches/@meawallet__react-native-mpp.patch diff --git a/app.config.ts b/app.config.ts index 1485b7132..7c82b3595 100644 --- a/app.config.ts +++ b/app.config.ts @@ -126,24 +126,25 @@ export default { "android", (c) => { const source = path.join(c.modRequest.projectRoot, "src/assets/mea_config"); - const destination = path.join(c.modRequest.projectRoot, "android/app/src/main/assets/mea_config"); + const destination = path.join(c.modRequest.projectRoot, "android/app/src/main/res/raw/mea_config"); + if (!existsSync(source)) throw new Error("meawallet: missing src/assets/mea_config"); mkdirSync(path.dirname(destination), { recursive: true }); - if (existsSync(source)) copyFileSync(source, destination); + copyFileSync(source, destination); return c; }, ]); return withXcodeProject(withAndroid, (c) => { const source = path.join(c.modRequest.projectRoot, "src/assets/mea_config"); - const destination = path.join(c.modRequest.projectRoot, "ios", c.modRequest.projectName ?? "", "mea_config"); - if (existsSync(source)) { - copyFileSync(source, destination); - c.modResults = IOSConfig.XcodeUtils.addResourceFileToGroup({ - filepath: `${c.modRequest.projectName ?? ""}/mea_config`, - groupName: c.modRequest.projectName ?? "", - project: c.modResults, - isBuildFile: true, - }); - } + const projectName = c.modRequest.projectName ?? ""; + const destination = path.join(c.modRequest.projectRoot, "ios", projectName, "mea_config"); + if (!existsSync(source)) throw new Error("meawallet: missing src/assets/mea_config"); + copyFileSync(source, destination); + IOSConfig.XcodeUtils.addResourceFileToGroup({ + filepath: `${projectName}/mea_config`, + groupName: projectName, + project: c.modResults, // eslint-disable-line @typescript-eslint/no-unsafe-assignment -- expo xcode project type + isBuildFile: true, + }); return c; }); }) satisfies ConfigPlugin, @@ -160,10 +161,65 @@ export default { password = "M1yeJMcuE5TiGW" } }`; - let contents = readFileSync(buildGradle, "utf8"); + const contents = readFileSync(buildGradle, "utf8"); if (!contents.includes("nexus.ext.meawallet.com")) { - contents = contents.replace(/(allprojects[\s\S]*?repositories\s*\{)/, `$1\n${meaRepo}`); - writeFileSync(buildGradle, contents); + const replaced = contents.replace(/(allprojects[\s\S]*?repositories\s*\{)/, `$1\n${meaRepo}`); // cspell:ignore allprojects + if (replaced === contents) + throw new Error("meawallet: failed to inject maven repo into android/build.gradle"); + writeFileSync(buildGradle, replaced); + } + return c; + }, + ])) satisfies ConfigPlugin, + // @ts-expect-error inline plugin + ((config) => + withDangerousMod(config, [ + "ios", + (c) => { + const podfile = path.join(c.modRequest.projectRoot, "ios/Podfile"); // cspell:ignore podfile Podfile OBJC RCTJS RCTUI modulemap fmodule + if (!existsSync(podfile)) return c; + const workaround = ` rctHeaders = "#{installer.sandbox.root}/Headers/Public/React-RCTAppDelegate" + Dir.mkdir(rctHeaders) unless Dir.exist?(rctHeaders) + File.write("#{rctHeaders}/React-RCTAppDelegate-umbrella.h", <<~'H') + #ifdef __OBJC__ + #import + #endif + #import "RCTAppDelegate.h" + #import "RCTAppSetupUtils.h" + #import "RCTArchConfiguratorProtocol.h" + #import "RCTDefaultReactNativeFactoryDelegate.h" + #import "RCTDependencyProvider.h" + #import "RCTJSRuntimeConfiguratorProtocol.h" + #import "RCTReactNativeFactory.h" + #import "RCTRootViewFactory.h" + #import "RCTUIConfiguratorProtocol.h" + H + File.write("#{rctHeaders}/React_RCTAppDelegate.modulemap", <<~MAP) + module React_RCTAppDelegate { + umbrella header "React-RCTAppDelegate-umbrella.h" + export * + module * { export * } + } + MAP + installer.pods_project.targets.each do |target| + next unless target.name == "meawallet-react-native-mpp" + target.build_configurations.each do |buildConfiguration| + flags = buildConfiguration.build_settings["OTHER_SWIFT_FLAGS"] || "$(inherited)" + next if flags.include?("React_RCTAppDelegate.modulemap") + buildConfiguration.build_settings["OTHER_SWIFT_FLAGS"] = + "#{flags} -Xcc -fmodule-map-file=\${PODS_ROOT}/Headers/Public/React-RCTAppDelegate/React_RCTAppDelegate.modulemap" + end + end +`; + const contents = readFileSync(podfile, "utf8"); + if (!contents.includes("React_RCTAppDelegate.modulemap")) { + const replaced = contents.replace( + /(\s{4}react_native_post_install\([\s\S]*?\n\s{4}\)\n)/, + `$1${workaround}`, + ); + if (replaced === contents) + throw new Error("meawallet: failed to inject react_native_post_install workaround into ios/Podfile"); + writeFileSync(podfile, replaced); } return c; }, diff --git a/package.json b/package.json index 3fec81436..20e071488 100644 --- a/package.json +++ b/package.json @@ -207,6 +207,7 @@ }, "patchedDependencies": { "@lifi/sdk": "patches/@lifi__sdk.patch", + "@meawallet/react-native-mpp": "patches/@meawallet__react-native-mpp.patch", "embedded-postgres": "patches/embedded-postgres.patch", "eslint-config-universe": "patches/eslint-config-universe.patch" }, diff --git a/patches/@meawallet__react-native-mpp.patch b/patches/@meawallet__react-native-mpp.patch new file mode 100644 index 000000000..bd205d9da --- /dev/null +++ b/patches/@meawallet__react-native-mpp.patch @@ -0,0 +1,10 @@ +diff --git a/meawallet-react-native-mpp.podspec b/meawallet-react-native-mpp.podspec +index 05795f1b..7396ff2b 100644 +--- a/meawallet-react-native-mpp.podspec ++++ b/meawallet-react-native-mpp.podspec +@@ -21,4 +21,5 @@ Pod::Spec.new do |s| + s.xcconfig = { "GCC_PREPROCESSOR_DEFINITIONS" => "REACT_NATIVE_MPP_VERSION=\"@\\\"#{s.version}\\\"\"" } + + install_modules_dependencies(s) ++ s.dependency "React-RCTAppDelegate" + end From a5c94d7b3f1261062d2f6de0e3a409cae23ac46c Mon Sep 17 00:00:00 2001 From: Miguel Diaz Date: Wed, 20 May 2026 18:08:20 +0200 Subject: [PATCH 7/7] =?UTF-8?q?=E2=9A=97=EF=B8=8F=20app:=20wallet=20provis?= =?UTF-8?q?ioning=20logging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- patches/@meawallet__react-native-mpp.patch | 215 ++++++++++++++++++++- pnpm-lock.yaml | 6 +- 2 files changed, 217 insertions(+), 4 deletions(-) diff --git a/patches/@meawallet__react-native-mpp.patch b/patches/@meawallet__react-native-mpp.patch index bd205d9da..06b9d61fb 100644 --- a/patches/@meawallet__react-native-mpp.patch +++ b/patches/@meawallet__react-native-mpp.patch @@ -1,5 +1,218 @@ +diff --git a/ios/ApplePay.mm b/ios/ApplePay.mm +index d5cb54b..394130d 100644 +--- a/ios/ApplePay.mm ++++ b/ios/ApplePay.mm +@@ -36,8 +36,34 @@ @implementation ApplePay + GET_TURBO_MODULE(ApplePay) + + static NSString *APPLE_PAY_EVENT = @"ApplePayDataChanged"; ++static NSString *APPLE_PAY_TRACE_EVENT = @"ApplePayTrace"; + static NSSet *PAYMENT_NETWORKS; + ++static NSString *redactedTail(NSString *value, NSUInteger count) ++{ ++ if (!value) return nil; ++ if (value.length <= count) return [@"…" stringByAppendingString:value]; ++ return [@"…" stringByAppendingString:[value substringFromIndex:value.length - count]]; ++} ++ ++static NSDictionary *traceError(NSError *error) ++{ ++ if (!error) return @{}; ++ ++ NSMutableDictionary *details = [NSMutableDictionary new]; ++ if (error.domain) details[@"domain"] = error.domain; ++ details[@"code"] = @(error.code); ++ if (error.localizedDescription) details[@"description"] = error.localizedDescription; ++ if (error.localizedFailureReason) details[@"failureReason"] = error.localizedFailureReason; ++ if (error.localizedRecoverySuggestion) details[@"recoverySuggestion"] = error.localizedRecoverySuggestion; ++ NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey]; ++ if (underlyingError) details[@"underlyingError"] = traceError(underlyingError); ++ if (error.userInfo.count > 0) { ++ details[@"userInfoKeys"] = error.userInfo.allKeys; ++ details[@"userInfo"] = [NSString stringWithFormat:@"%@", error.userInfo]; ++ } ++ return details; ++} + RCT_EXPORT_MODULE() + + - (instancetype)init +@@ -106,7 +125,21 @@ - (instancetype)init + + - (NSArray *)supportedEvents + { +- return @[APPLE_PAY_EVENT]; ++ return @[APPLE_PAY_EVENT, APPLE_PAY_TRACE_EVENT]; ++} ++ ++- (void)emitTrace:(NSString *)event details:(NSDictionary *)details ++{ ++ NSMutableDictionary *body = [NSMutableDictionary new]; ++ body[@"event"] = event; ++ body[@"timestamp"] = @((long long)([[NSDate date] timeIntervalSince1970] * 1000)); ++ if (details.count > 0) [body addEntriesFromDictionary:details]; ++ NSLog(@"ApplePayTrace %@", body); ++ if (self.hasListeners) { ++ dispatch_async(dispatch_get_main_queue(), ^{ ++ [self sendEventWithName:APPLE_PAY_TRACE_EVENT body:body]; ++ }); ++ } + } + + - (void)startObserving +@@ -127,6 +160,7 @@ + (BOOL)requiresMainQueueSetup + RCT_EXPORT_METHOD(setDebugLoggingEnabled:(BOOL)enabled) + { + RCTLogInfo(@"setDebugLoggingEnabled: %i", enabled); ++ [self emitTrace:@"setDebugLoggingEnabled" details:@{ @"enabled": @(enabled) }]; + + RCTSetLogThreshold(enabled ? RCTLogLevelTrace : RCTLogLevelFatal); + +@@ -163,7 +197,11 @@ + (BOOL)requiresMainQueueSetup + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + { +- RCTLogInfo(@"initializeOemTokenization: %@", card); ++ NSMutableDictionary *requestDetails = [NSMutableDictionary new]; ++ requestDetails[@"cardKeys"] = card.allKeys ?: @[]; ++ requestDetails[@"hasCardSecret"] = @(card[@"cardSecret"] != nil); ++ if (card[@"cardDataType"]) requestDetails[@"cardDataType"] = [NSString stringWithFormat:@"%@", card[@"cardDataType"]]; ++ [self emitTrace:@"initializeOemTokenization.request" details:requestDetails]; + + [MeaPushProvisioning initializeOemTokenization:[Serialization reactToCardDataParameters:card] + completionHandler:^(MppInitializeOemTokenizationResponseData *data, NSError *error) { +@@ -171,6 +209,17 @@ + (BOOL)requiresMainQueueSetup + + if (data && [data isValid]) { + RCTLogInfo(@"initializeOemTokenization:completionHandler: data isValid"); ++ NSMutableDictionary *details = [NSMutableDictionary new]; ++ details[@"hasPrimaryAccountIdentifier"] = @(data.primaryAccountIdentifier.length > 0); ++ if (data.localizedDescription) details[@"localizedDescription"] = data.localizedDescription; ++ if (data.networkName) details[@"networkName"] = data.networkName; ++ NSString *identifier = redactedTail(data.primaryAccountIdentifier, 8); ++ if (identifier) details[@"primaryAccountIdentifier"] = identifier; ++ if (data.primaryAccountIdentifier) details[@"primaryAccountIdentifierLength"] = @(data.primaryAccountIdentifier.length); ++ if (data.primaryAccountSuffix) details[@"primaryAccountSuffix"] = data.primaryAccountSuffix; ++ if (data.tokenizationReceipt) details[@"tokenizationReceiptLength"] = @(data.tokenizationReceipt.length); ++ details[@"validFor"] = @(data.validFor); ++ [self emitTrace:@"initializeOemTokenization.success" details:details]; + + resolve([Serialization initializeOemTokenizationResponseDataToReact:data]); + } +@@ -181,6 +228,11 @@ + (BOOL)requiresMainQueueSetup + userInfo:@{NSLocalizedDescriptionKey:@"Invalid Tokenization Response Data." }]; + } + ++ NSMutableDictionary *details = [NSMutableDictionary new]; ++ details[@"hasData"] = @(data != nil); ++ details[@"isValid"] = @([data isValid]); ++ [details addEntriesFromDictionary:traceError(error)]; ++ [self emitTrace:@"initializeOemTokenization.error" details:details]; + RCTLogError(@"initializeOemTokenization:completionHandler: error: %@", error); + + reject(@(error.code).stringValue, [NSString stringWithFormat:@"%@", error], error); +@@ -299,7 +351,16 @@ + (BOOL)requiresMainQueueSetup + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + { +- RCTLogInfo(@"showAddPaymentPassView: %@", tokenizationData); ++ NSMutableDictionary *requestDetails = [NSMutableDictionary new]; ++ requestDetails[@"hasPrimaryAccountIdentifier"] = @(tokenizationData[@"primaryAccountIdentifier"] != nil); ++ requestDetails[@"keys"] = tokenizationData.allKeys ?: @[]; ++ if (tokenizationData[@"localizedDescription"]) requestDetails[@"localizedDescription"] = tokenizationData[@"localizedDescription"]; ++ if (tokenizationData[@"networkName"]) requestDetails[@"networkName"] = tokenizationData[@"networkName"]; ++ if (tokenizationData[@"primaryAccountIdentifier"]) requestDetails[@"primaryAccountIdentifier"] = redactedTail([NSString stringWithFormat:@"%@", tokenizationData[@"primaryAccountIdentifier"]], 8); ++ if (tokenizationData[@"primaryAccountSuffix"]) requestDetails[@"primaryAccountSuffix"] = tokenizationData[@"primaryAccountSuffix"]; ++ if (tokenizationData[@"tokenizationReceipt"]) requestDetails[@"tokenizationReceiptLength"] = @([[NSString stringWithFormat:@"%@", tokenizationData[@"tokenizationReceipt"]] length]); ++ if (tokenizationData[@"validFor"]) requestDetails[@"validFor"] = tokenizationData[@"validFor"]; ++ [self emitTrace:@"showAddPaymentPassView.request" details:requestDetails]; + + @synchronized (self) { + /* +@@ -324,6 +384,7 @@ + (BOOL)requiresMainQueueSetup + if (self.paymentPassController) { + + [RCTPresentedViewController() presentViewController:self.paymentPassController animated:YES completion:^{ ++ [self emitTrace:@"showAddPaymentPassView.presented" details:@{}]; + RCTLogInfo(@"showAddPaymentPassView: paymentPassController presented"); + }]; + } else { +@@ -331,6 +392,7 @@ + (BOOL)requiresMainQueueSetup + code:MPP_ERROR_API + userInfo:@{NSLocalizedDescriptionKey:@"PKAddPaymentPassViewController can't be created."}]; + ++ [self emitTrace:@"showAddPaymentPassView.error" details:traceError(error)]; + RCTLogError(@"showAddPaymentPassView: error: %@", error); + + self.tokenizationResponseData = nil; +@@ -343,6 +405,7 @@ + (BOOL)requiresMainQueueSetup + code:MPP_ERROR_API + userInfo:@{NSLocalizedDescriptionKey:@"showAddPaymentPassView() already running."}]; + ++ [self emitTrace:@"showAddPaymentPassView.error" details:traceError(error)]; + RCTLogError(@"showAddPaymentPassView: error: %@", error); + + reject(@(error.code).stringValue, [NSString stringWithFormat:@"%@", error], error); +@@ -657,6 +720,12 @@ - (void)addPaymentPassViewController:(PKAddPaymentPassViewController *)controlle + nonceSignature:(NSData *)nonceSignature + completionHandler:(void(^)(PKAddPaymentPassRequest *request))handler + { ++ NSMutableDictionary *details = [NSMutableDictionary new]; ++ details[@"certificateCount"] = @(certificates.count); ++ details[@"nonceLength"] = @(nonce.length); ++ details[@"nonceSignatureLength"] = @(nonceSignature.length); ++ if (self.tokenizationResponseData.tokenizationReceipt) details[@"tokenizationReceiptLength"] = @(self.tokenizationResponseData.tokenizationReceipt.length); ++ [self emitTrace:@"generateRequestWithCertificateChain" details:details]; + RCTLogInfo(@"addPaymentPassViewController:generateRequestWithCertificateChain:"); + + @synchronized (self) { +@@ -670,6 +739,13 @@ - (void)addPaymentPassViewController:(PKAddPaymentPassViewController *)controlle + if (valid && !error) { + PKAddPaymentPassRequest *addPaymentPassRequest = [data addPaymentPassRequest]; + ++ NSMutableDictionary *details = [NSMutableDictionary new]; ++ details[@"hasRequest"] = @(addPaymentPassRequest != nil); ++ details[@"isValid"] = @(valid); ++ if (addPaymentPassRequest.activationData) details[@"activationDataLength"] = @(addPaymentPassRequest.activationData.length); ++ if (addPaymentPassRequest.encryptedPassData) details[@"encryptedPassDataLength"] = @(addPaymentPassRequest.encryptedPassData.length); ++ if (addPaymentPassRequest.ephemeralPublicKey) details[@"ephemeralPublicKeyLength"] = @(addPaymentPassRequest.ephemeralPublicKey.length); ++ [self emitTrace:@"completeOemTokenization.success" details:details]; + RCTLogInfo(@"addPaymentPassViewController:generateRequestWithCertificateChain: addPaymentPassRequest created"); + + self.tokenizationResponseData = nil; +@@ -682,6 +755,11 @@ - (void)addPaymentPassViewController:(PKAddPaymentPassViewController *)controlle + userInfo:@{ NSLocalizedDescriptionKey:@"Can't add payment pass." }]; + } + ++ NSMutableDictionary *details = [NSMutableDictionary new]; ++ details[@"hasData"] = @(data != nil); ++ details[@"isValid"] = @(valid); ++ [details addEntriesFromDictionary:traceError(error)]; ++ [self emitTrace:@"completeOemTokenization.error" details:details]; + RCTLogError(@"addPaymentPassViewController:generateRequestWithCertificateChain: error: %@", error); + } + }]; +@@ -698,11 +776,17 @@ - (void)addPaymentPassViewController:(PKAddPaymentPassViewController *)controlle + self.tokenizationResponseData = nil; + + if (!error) { ++ NSString *activationState = [Serialization getActivateStateString:pass.activationState]; ++ [self emitTrace:@"didFinishAddingPaymentPass.success" details:@{ @"activationState": activationState ?: @"unknown" }]; + RCTLogInfo(@"addPaymentPassViewController:didFinishAddingPaymentPass: pass.activationState: %lu", (unsigned long)pass.activationState); + +- self.resolve([Serialization getActivateStateString:pass.activationState]); ++ self.resolve(activationState); + } + else { ++ NSMutableDictionary *details = [traceError(error) mutableCopy]; ++ details[@"hasPass"] = @(pass != nil); ++ if (pass) details[@"activationState"] = [Serialization getActivateStateString:pass.activationState] ?: @"unknown"; ++ [self emitTrace:@"didFinishAddingPaymentPass.error" details:details]; + RCTLogError(@"addPaymentPassViewController:didFinishAddingPaymentPass: error: %@", error); + + self.reject(@(error.code).stringValue, [NSString stringWithFormat:@"%@", error], error); diff --git a/meawallet-react-native-mpp.podspec b/meawallet-react-native-mpp.podspec -index 05795f1b..7396ff2b 100644 +index 05795f1..7396ff2 100644 --- a/meawallet-react-native-mpp.podspec +++ b/meawallet-react-native-mpp.podspec @@ -21,4 +21,5 @@ Pod::Spec.new do |s| diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b95106c0..1c0efa513 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,7 +107,7 @@ patchedDependencies: hash: ee16233f297d9a6c8a8320b5dc2b4bf47b7be8b481d79e3d54108cd77775b45b path: patches/@lifi__sdk.patch '@meawallet/react-native-mpp': - hash: 1865bf0eddf3da8572a839f14e03cf34cf4753191421a4bc8704f22c8cdaa33b + hash: 7aef5e11a2777ba755c435c4b08bf7081a655da89f1560daf744f8f2c780029c path: patches/@meawallet__react-native-mpp.patch embedded-postgres: hash: cb5e37525b1810f2af136570b38d5e0cec4cc2455408896ed1943d27f3f61b38 @@ -161,7 +161,7 @@ importers: version: 3.7.7(patch_hash=ee16233f297d9a6c8a8320b5dc2b4bf47b7be8b481d79e3d54108cd77775b45b)(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(viem@2.47.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6))(zod@4.3.6) '@meawallet/react-native-mpp': specifier: ^2.2.2 - version: 2.3.0(patch_hash=1865bf0eddf3da8572a839f14e03cf34cf4753191421a4bc8704f22c8cdaa33b)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.0)(utf-8-validate@5.0.10))(react@19.2.0) + version: 2.3.0(patch_hash=7aef5e11a2777ba755c435c4b08bf7081a655da89f1560daf744f8f2c780029c)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.0)(utf-8-validate@5.0.10))(react@19.2.0) '@peculiar/asn1-ecc': specifier: ^2.6.1 version: 2.6.1 @@ -17222,7 +17222,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@meawallet/react-native-mpp@2.3.0(patch_hash=1865bf0eddf3da8572a839f14e03cf34cf4753191421a4bc8704f22c8cdaa33b)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.0)(utf-8-validate@5.0.10))(react@19.2.0)': + '@meawallet/react-native-mpp@2.3.0(patch_hash=7aef5e11a2777ba755c435c4b08bf7081a655da89f1560daf744f8f2c780029c)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.0)(utf-8-validate@5.0.10))(react@19.2.0)': dependencies: react: 19.2.0 react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.0)(utf-8-validate@5.0.10)