@breeztech/breez-sdk-spark-react-native 0.15.0 → 0.16.1-dev1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/cpp/generated/breez_sdk_spark.cpp +4947 -2629
  2. package/cpp/generated/breez_sdk_spark.hpp +257 -110
  3. package/lib/commonjs/generated/breez_sdk_spark-ffi.js.map +1 -1
  4. package/lib/commonjs/generated/breez_sdk_spark.js +1304 -580
  5. package/lib/commonjs/generated/breez_sdk_spark.js.map +1 -1
  6. package/lib/commonjs/passkey-prf-provider.js +300 -0
  7. package/lib/commonjs/passkey-prf-provider.js.map +1 -0
  8. package/lib/module/generated/breez_sdk_spark-ffi.js.map +1 -1
  9. package/lib/module/generated/breez_sdk_spark.js +1304 -580
  10. package/lib/module/generated/breez_sdk_spark.js.map +1 -1
  11. package/lib/module/passkey-prf-provider.js +293 -0
  12. package/lib/module/passkey-prf-provider.js.map +1 -0
  13. package/lib/typescript/commonjs/src/generated/breez_sdk_spark-ffi.d.ts +199 -140
  14. package/lib/typescript/commonjs/src/generated/breez_sdk_spark-ffi.d.ts.map +1 -1
  15. package/lib/typescript/commonjs/src/generated/breez_sdk_spark.d.ts +6149 -3695
  16. package/lib/typescript/commonjs/src/generated/breez_sdk_spark.d.ts.map +1 -1
  17. package/lib/typescript/commonjs/src/passkey-prf-provider.d.ts +135 -0
  18. package/lib/typescript/commonjs/src/passkey-prf-provider.d.ts.map +1 -0
  19. package/lib/typescript/module/src/generated/breez_sdk_spark-ffi.d.ts +199 -140
  20. package/lib/typescript/module/src/generated/breez_sdk_spark-ffi.d.ts.map +1 -1
  21. package/lib/typescript/module/src/generated/breez_sdk_spark.d.ts +6149 -3695
  22. package/lib/typescript/module/src/generated/breez_sdk_spark.d.ts.map +1 -1
  23. package/lib/typescript/module/src/passkey-prf-provider.d.ts +135 -0
  24. package/lib/typescript/module/src/passkey-prf-provider.d.ts.map +1 -0
  25. package/package.json +17 -5
  26. package/scripts/post-ubrn.js +227 -0
  27. package/src/generated/breez_sdk_spark-ffi.ts +366 -198
  28. package/src/generated/breez_sdk_spark.ts +12343 -7056
  29. package/src/passkey-prf-provider.ts +372 -0
@@ -0,0 +1,135 @@
1
+ import { PasskeyClient as SdkPasskeyClient, PasskeyProviderOptions, type PasskeyConfig, type PrfProvider } from './generated/breez_sdk_spark';
2
+ /**
3
+ * A passkey credential from a register or sign-in ceremony. `credentialId`
4
+ * is always set; the attestation fields are populated on registration and
5
+ * absent on sign-in (an assertion carries no attestation). Persist
6
+ * `credentialId` to drive `excludeCredentials` / `allowCredentials` on later
7
+ * calls. Treat `aaguid` as an unverified display hint, never a trust decision.
8
+ * `userId` is the user handle minted by the native plugin (never host-supplied).
9
+ */
10
+ export interface PasskeyCredential {
11
+ credentialId: Uint8Array;
12
+ userId: Uint8Array | null;
13
+ aaguid: Uint8Array | null;
14
+ backupEligible: boolean | null;
15
+ }
16
+ /**
17
+ * Result of {@link PasskeyProvider.checkDomainAssociation}. Switch on `kind`
18
+ * to handle each outcome.
19
+ */
20
+ export type DomainAssociation = {
21
+ kind: 'Associated';
22
+ } | {
23
+ kind: 'NotAssociated';
24
+ source: string;
25
+ reason: string;
26
+ } | {
27
+ kind: 'Skipped';
28
+ reason: string;
29
+ };
30
+ /**
31
+ * Error thrown when a passkey operation fails, with a structured `code` for
32
+ * programmatic handling: `userCancelled`, `userTimedOut`, `prfNotSupported`,
33
+ * `noCredential`, `configuration`, `credentialAlreadyExists`, `unknown`.
34
+ * `userTimedOut` is the OS biometric inactivity timeout (distinct from the
35
+ * user dismissing the prompt), so hosts may safely auto-retry it.
36
+ */
37
+ export declare class PasskeyPrfException extends Error {
38
+ readonly code: string;
39
+ constructor(code: string, message: string);
40
+ }
41
+ /**
42
+ * Built-in React Native passkey PRF provider, backed by AuthenticationServices
43
+ * on iOS and Credential Manager on Android. The default {@link PrfProvider};
44
+ * inject a configured instance through {@link PasskeyClientBuilder}. Requires
45
+ * iOS 18+ or Android 14+ (API 34) plus the Associated Domains entitlement
46
+ * (iOS) or assetlinks.json (Android) for the RP domain.
47
+ */
48
+ export declare class PasskeyProvider {
49
+ /**
50
+ * Breez's shared `keys.breez.technology` RP. Pass as `rpId` to opt in
51
+ * (only valid for apps registered with Breez); apps with their own RP
52
+ * domain pass their own string.
53
+ */
54
+ static readonly BREEZ_RP_ID: string;
55
+ /** Default `rpName` for the zero-config client when none is supplied. */
56
+ static readonly DEFAULT_RP_NAME: string;
57
+ private rpId;
58
+ private rpName;
59
+ private userName;
60
+ private userDisplayName;
61
+ constructor(options: PasskeyProviderOptions);
62
+ /**
63
+ * Derive one 32-byte seed per salt from passkey PRF, in as few OS prompts
64
+ * as the platform supports. `allowCredentials` restricts the assertion to
65
+ * specific credential IDs (mainly for reauthentication) when non-empty;
66
+ * `preferImmediatelyAvailableCredentials` overrides the platform default
67
+ * when set. Returns the seeds plus the asserted credential ID.
68
+ */
69
+ deriveSeeds(request: {
70
+ salts: string[];
71
+ allowCredentials?: Uint8Array[];
72
+ preferImmediatelyAvailableCredentials?: boolean | null;
73
+ }): Promise<{
74
+ seeds: Uint8Array[];
75
+ credentialId?: Uint8Array;
76
+ }>;
77
+ /**
78
+ * Register a new PRF-capable passkey (one prompt, no seed derivation): use
79
+ * it to split credential creation from derivation in multi-step onboarding.
80
+ * `excludeCredentials` blocks re-registering a device that already holds a
81
+ * credential, surfaced as a `credentialAlreadyExists` failure. The returned
82
+ * user handle is minted fresh per call (never host-supplied).
83
+ */
84
+ createPasskey(excludeCredentials?: Uint8Array[]): Promise<PasskeyCredential>;
85
+ /** Whether this device supports passkeys with the PRF extension. */
86
+ isSupported(): Promise<boolean>;
87
+ /**
88
+ * Verify the app is associated with the configured `rpId` for WebAuthn.
89
+ * Android always returns `Skipped` rather than `NotAssociated`: Credential
90
+ * Manager runs its own check internally against fresher data.
91
+ */
92
+ checkDomainAssociation(): Promise<DomainAssociation>;
93
+ }
94
+ /**
95
+ * Builds a `PasskeyClient` backed by a caller-supplied provider. Use this
96
+ * for a custom PRF backend; omit the provider for the zero-config Breez-RP
97
+ * default and set `providerOptions` on the config to use your own RP.
98
+ */
99
+ export declare class PasskeyClientBuilder {
100
+ private readonly breezApiKey?;
101
+ private readonly config?;
102
+ private provider?;
103
+ /**
104
+ * @param breezApiKey Breez relay key for authenticated (NIP-42) label
105
+ * storage. Omit for public relays only.
106
+ * @param config Passkey client config. `providerOptions` configures the
107
+ * default provider (ignored when a provider is injected via
108
+ * {@link withPrfProvider}, which owns its RP); `defaultLabel` is the
109
+ * label-store default.
110
+ */
111
+ constructor(breezApiKey?: string | undefined, config?: PasskeyConfig | undefined);
112
+ /**
113
+ * Inject the provider the client derives seeds through: the built-in
114
+ * {@link PasskeyProvider} or any custom `PrfProvider` implementation.
115
+ * Supersedes the config's `providerOptions` (the injected provider owns
116
+ * its RP).
117
+ */
118
+ withPrfProvider(provider: PrfProvider): this;
119
+ /**
120
+ * Construct the client. Falls back to a default {@link PasskeyProvider}
121
+ * on the config's `providerOptions` (default: the Breez RP) when no
122
+ * provider was injected.
123
+ */
124
+ build(): SdkPasskeyClient;
125
+ }
126
+ /**
127
+ * Zero-config passkey client on the Breez shared RP (`keys.breez.technology`),
128
+ * so a Breez-registered app needs only its relay key; set `providerOptions` on
129
+ * the config to use your own RP. For a custom PRF backend, build the provider
130
+ * and inject it via {@link PasskeyClientBuilder}.
131
+ */
132
+ export declare const PasskeyClient: {
133
+ new (breezApiKey?: string, config?: PasskeyConfig): SdkPasskeyClient;
134
+ };
135
+ //# sourceMappingURL=passkey-prf-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passkey-prf-provider.d.ts","sourceRoot":"","sources":["../../../../src/passkey-prf-provider.ts"],"names":[],"mappings":"AACA,OAAO,EACL,aAAa,IAAI,gBAAgB,EACjC,sBAAsB,EACtB,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,6BAA6B,CAAC;AAwCrC;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,UAAU,CAAC;IACzB,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,cAAc,EAAE,OAAO,GAAG,IAAI,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AA+BD;;;;;;GAMG;AACH,qBAAa,eAAe;IAC1B;;;;OAIG;IACH,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAA2B;IAE9D,yEAAyE;IACzE,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAW;IAElD,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,eAAe,CAAS;gBAEpB,OAAO,EAAE,sBAAsB;IAO3C;;;;;;OAMG;IACG,WAAW,CAAC,OAAO,EAAE;QACzB,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,gBAAgB,CAAC,EAAE,UAAU,EAAE,CAAC;QAChC,qCAAqC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;KACxD,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,UAAU,EAAE,CAAC;QAAC,YAAY,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;IA2C/D;;;;;;OAMG;IACG,aAAa,CAAC,kBAAkB,GAAE,UAAU,EAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAgCtF,oEAAoE;IAC9D,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAQrC;;;;OAIG;IACG,sBAAsB,IAAI,OAAO,CAAC,iBAAiB,CAAC;CA6B3D;AAqBD;;;;GAIG;AACH,qBAAa,oBAAoB;IAY7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAZ1B,OAAO,CAAC,QAAQ,CAAC,CAAc;IAE/B;;;;;;;OAOG;gBAEgB,WAAW,CAAC,EAAE,MAAM,YAAA,EACpB,MAAM,CAAC,EAAE,aAAa,YAAA;IAGzC;;;;;OAKG;IACH,eAAe,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI;IAK5C;;;;OAIG;IACH,KAAK,IAAI,gBAAgB;CAU1B;AAUD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE;IAC1B,KAAK,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,gBAAgB,CAAC;CAGtE,CAAC"}
package/package.json CHANGED
@@ -1,12 +1,23 @@
1
1
  {
2
2
  "name": "@breeztech/breez-sdk-spark-react-native",
3
- "version": "0.15.0",
3
+ "version": "0.16.1-dev1",
4
4
  "description": "React Native bindings for the Breez SDK - Nodeless (Spark Implementation)",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/commonjs/index.js",
7
+ "types": "./lib/typescript/commonjs/src/index.d.ts",
7
8
  "module": "./lib/module/index.js",
8
9
  "exports": {
9
10
  "./app.plugin.js": "./app.plugin.js",
11
+ "./passkey-prf-provider": {
12
+ "import": {
13
+ "types": "./lib/typescript/module/src/passkey-prf-provider.d.ts",
14
+ "default": "./lib/module/passkey-prf-provider.js"
15
+ },
16
+ "require": {
17
+ "types": "./lib/typescript/commonjs/src/passkey-prf-provider.d.ts",
18
+ "default": "./lib/commonjs/passkey-prf-provider.js"
19
+ }
20
+ },
10
21
  ".": {
11
22
  "import": {
12
23
  "types": "./lib/typescript/module/src/index.d.ts",
@@ -52,11 +63,12 @@
52
63
  "prepare": "bob build && yarn plugin:build",
53
64
  "release": "release-it --only-version",
54
65
  "typecheck": "tsc",
55
- "ubrn:android": "ubrn build android --release --config ubrn.config.yaml --and-generate",
66
+ "ubrn:android": "ubrn build android --release --config ubrn.config.yaml --and-generate && yarn post-ubrn",
56
67
  "ubrn:build": "yarn ubrn:ios && yarn ubrn:android",
57
68
  "ubrn:checkout": "ubrn checkout --config ubrn.config.yaml",
58
69
  "ubrn:clean": "rm -rfv cpp/ android/CMakeLists.txt android/src/main/java android/*.cpp ios/ src/Native* src/index.*ts* src/generated/",
59
- "ubrn:ios": "ubrn build ios --release --config ubrn.config.yaml --and-generate"
70
+ "ubrn:ios": "ubrn build ios --release --config ubrn.config.yaml --and-generate && yarn post-ubrn",
71
+ "post-ubrn": "node ./scripts/post-ubrn.js"
60
72
  },
61
73
  "keywords": [
62
74
  "react-native",
@@ -197,7 +209,7 @@
197
209
  "version": "0.49.0"
198
210
  },
199
211
  "checksums": {
200
- "android": "8575e29841c19347cc3d490914f155f9bc8db704144f6c6e1d5cb43cd33af39c",
201
- "ios": "44b0cccd549afb6f6955148b0f701e9648617af0eadd8e107245d726dfc6a3b5"
212
+ "android": "221bd9a358381e424f56018a81c4e359fde5799382bacea4cff86cb9d3f19607",
213
+ "ios": "46fdfca17557a6a5b65785c1c65a1375e1b4b2c754c0402dda809150c6e144bd"
202
214
  }
203
215
  }
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Apply hand-edits to uniffi-bindgen-react-native-generated files after
4
+ * running `yarn ubrn:android`, `yarn ubrn:ios`, or `yarn ubrn:checkout`.
5
+ *
6
+ * The code generator is unaware of the built-in passkey PRF provider, so
7
+ * each regeneration would otherwise silently drop the few lines required
8
+ * to:
9
+ *
10
+ * 1. android/build.gradle
11
+ * - add androidx.credentials / kotlinx-coroutines dependencies
12
+ * (needed by the hand-written BreezSdkSparkPasskeyModule.kt and
13
+ * CredentialManagerPrfCore.kt)
14
+ * - add `src/main/kotlin` to the Android gradle sourceSets so
15
+ * those hand-written files (which live under
16
+ * `android/src/main/kotlin/...` to survive `yarn ubrn:clean`)
17
+ * are actually compiled
18
+ *
19
+ * 2. android/src/main/java/.../BreezSdkSparkReactNativePackage.kt
20
+ * - register BreezSdkSparkPasskeyModule alongside the generated
21
+ * UniFFI TurboModule so React Native can find it at runtime
22
+ *
23
+ * The PasskeyProvider class is exposed via a subpath export
24
+ * (`@breeztech/breez-sdk-spark-react-native/passkey-prf-provider`) declared
25
+ * in package.json `exports`, so no edit to the generated `src/index.tsx`
26
+ * is required.
27
+ *
28
+ * Each patch is idempotent (runs as a no-op if already applied) and
29
+ * errors loudly if its anchor cannot be found. If uniffi-bindgen-react-native
30
+ * changes its output format, this script fails fast with an actionable
31
+ * message instead of silently producing a broken package.
32
+ *
33
+ * Invoked automatically by the `ubrn:*` npm scripts; can also be run
34
+ * manually via `yarn post-ubrn`.
35
+ */
36
+
37
+ 'use strict';
38
+
39
+ const fs = require('fs');
40
+ const path = require('path');
41
+
42
+ const args = process.argv.slice(2);
43
+ const checkMode = args.includes('--check') || args.includes('--dry-run');
44
+
45
+ const repoRoot = path.resolve(__dirname, '..');
46
+ const drift = [];
47
+ const errors = [];
48
+
49
+ function patchFile(relPath, label, patcher) {
50
+ const filePath = path.join(repoRoot, relPath);
51
+ if (!fs.existsSync(filePath)) {
52
+ console.log(`[post-ubrn] ${label}: skipping, file not yet generated (${relPath})`);
53
+ return;
54
+ }
55
+ let before, after;
56
+ try {
57
+ before = fs.readFileSync(filePath, 'utf8');
58
+ after = patcher(before, label, relPath);
59
+ } catch (err) {
60
+ errors.push({ label, relPath, message: err.message });
61
+ return;
62
+ }
63
+ if (before === after) {
64
+ console.log(`[post-ubrn] ${label}: already patched (${relPath})`);
65
+ return;
66
+ }
67
+ if (checkMode) {
68
+ drift.push({ label, relPath });
69
+ console.log(`[post-ubrn] ${label}: WOULD PATCH (${relPath})`);
70
+ return;
71
+ }
72
+ fs.writeFileSync(filePath, after);
73
+ console.log(`[post-ubrn] ${label}: patched (${relPath})`);
74
+ }
75
+
76
+ function requireAnchor(content, anchor, label, relPath) {
77
+ if (!content.includes(anchor)) {
78
+ throw new Error(
79
+ `anchor not found. The uniffi-bindgen-react-native output format may ` +
80
+ `have changed (check the installed version against the one this ` +
81
+ `script was written for). See CLAUDE.md "Generated Files Policy" ` +
82
+ `for how to update the patches. Expected anchor text:\n${anchor}`
83
+ );
84
+ }
85
+ }
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // 1. android/build.gradle
89
+ // ---------------------------------------------------------------------------
90
+
91
+ patchFile(
92
+ 'android/build.gradle',
93
+ 'androidx.credentials dependencies',
94
+ (content, label, relPath) => {
95
+ if (content.includes('androidx.credentials:credentials:1.3.0')) {
96
+ return content;
97
+ }
98
+ const anchor = ' implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"';
99
+ requireAnchor(content, anchor, label, relPath);
100
+ const injected = [
101
+ anchor,
102
+ ' implementation "androidx.credentials:credentials:1.3.0"',
103
+ ' implementation "androidx.credentials:credentials-play-services-auth:1.3.0"',
104
+ ' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0"',
105
+ ].join('\n');
106
+ return content.replace(anchor, injected);
107
+ }
108
+ );
109
+
110
+ patchFile(
111
+ 'android/build.gradle',
112
+ 'src/main/kotlin sourceSet',
113
+ (content, label, relPath) => {
114
+ if (content.includes("main.kotlin.srcDirs += 'src/main/kotlin'")) {
115
+ return content;
116
+ }
117
+ // The generated build.gradle already has a `sourceSets { main { ... } }`
118
+ // block (for the new-architecture jni dirs). Extend it by adding the
119
+ // hand-written kotlin source dir right after the opening `main {`.
120
+ const anchor = ` sourceSets {
121
+ main {
122
+ if (isNewArchitectureEnabled()) {`;
123
+ requireAnchor(content, anchor, label, relPath);
124
+ return content.replace(
125
+ anchor,
126
+ ` sourceSets {
127
+ main {
128
+ main.kotlin.srcDirs += 'src/main/kotlin'
129
+ if (isNewArchitectureEnabled()) {`
130
+ );
131
+ }
132
+ );
133
+
134
+ // ---------------------------------------------------------------------------
135
+ // 2. android/src/main/java/.../BreezSdkSparkReactNativePackage.kt
136
+ // ---------------------------------------------------------------------------
137
+
138
+ const PACKAGE_KT_REL =
139
+ 'android/src/main/java/com/breeztech/breezsdkspark/BreezSdkSparkReactNativePackage.kt';
140
+
141
+ patchFile(
142
+ PACKAGE_KT_REL,
143
+ 'BreezSdkSparkPasskeyModule getModule() registration',
144
+ (content, label, relPath) => {
145
+ if (content.includes('BreezSdkSparkPasskeyModule.NAME ->')) {
146
+ return content;
147
+ }
148
+ const anchor = ` override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
149
+ return if (name == BreezSdkSparkReactNativeModule.NAME) {
150
+ BreezSdkSparkReactNativeModule(reactContext)
151
+ } else {
152
+ null
153
+ }
154
+ }`;
155
+ const replacement = ` override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
156
+ return when (name) {
157
+ BreezSdkSparkReactNativeModule.NAME -> BreezSdkSparkReactNativeModule(reactContext)
158
+ BreezSdkSparkPasskeyModule.NAME -> BreezSdkSparkPasskeyModule(reactContext)
159
+ else -> null
160
+ }
161
+ }`;
162
+ requireAnchor(content, anchor, label, relPath);
163
+ return content.replace(anchor, replacement);
164
+ }
165
+ );
166
+
167
+ patchFile(
168
+ PACKAGE_KT_REL,
169
+ 'BreezSdkSparkPasskeyModule ReactModuleInfo registration',
170
+ (content, label, relPath) => {
171
+ if (content.includes('moduleInfos[BreezSdkSparkPasskeyModule.NAME]')) {
172
+ return content;
173
+ }
174
+ const anchor = ` true // isTurboModule
175
+ )
176
+ moduleInfos
177
+ }
178
+ }
179
+ }`;
180
+ const replacement = ` true // isTurboModule
181
+ )
182
+ moduleInfos[BreezSdkSparkPasskeyModule.NAME] = ReactModuleInfo(
183
+ BreezSdkSparkPasskeyModule.NAME,
184
+ BreezSdkSparkPasskeyModule.NAME,
185
+ false, // canOverrideExistingModule
186
+ false, // needsEagerInit
187
+ false, // isCxxModule
188
+ false // isTurboModule (standard native module)
189
+ )
190
+ moduleInfos
191
+ }
192
+ }
193
+ }`;
194
+ requireAnchor(content, anchor, label, relPath);
195
+ return content.replace(anchor, replacement);
196
+ }
197
+ );
198
+
199
+ if (errors.length > 0) {
200
+ console.error('');
201
+ console.error(`[post-ubrn] ${errors.length} patch(es) failed:`);
202
+ for (const { label, relPath, message } of errors) {
203
+ console.error(` - ${label} (${relPath})`);
204
+ console.error(` ${message.split('\n').join('\n ')}`);
205
+ }
206
+ process.exit(2);
207
+ }
208
+
209
+ if (checkMode && drift.length > 0) {
210
+ console.error('');
211
+ console.error(
212
+ `[post-ubrn] ${drift.length} generated file(s) are out of sync with the ` +
213
+ `committed hand-edits:`
214
+ );
215
+ for (const { label, relPath } of drift) {
216
+ console.error(` - ${label} (${relPath})`);
217
+ }
218
+ console.error('');
219
+ console.error(
220
+ 'Run `yarn post-ubrn` inside packages/react-native and commit the diff, ' +
221
+ 'or revert whatever change dropped the patches. See CLAUDE.md ' +
222
+ '"Generated Files Policy" for context.'
223
+ );
224
+ process.exit(1);
225
+ }
226
+
227
+ console.log('[post-ubrn] done.');