yaver-feedback-react-native 0.7.12 → 0.7.14

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 (2) hide show
  1. package/app.plugin.js +75 -79
  2. package/package.json +1 -1
package/app.plugin.js CHANGED
@@ -113,65 +113,42 @@ function withYaverHotReloadNativeModule(config) {
113
113
  // them into the app target. Without this, the files exist on disk
114
114
  // but are invisible to the build system.
115
115
  config = withXcodeProject(config, (config) => {
116
- const pbx = config.modResults;
116
+ const proj = config.modResults;
117
117
  const appName = config.modRequest.projectName;
118
118
  if (!appName) return config;
119
119
 
120
- // Ensure the PBX group we'll attach files to exists (it's the
121
- // app's main Sources group — same one that holds AppDelegate.swift).
122
- const group = pbx.pbxGroupByName(appName);
123
- if (!group) return config;
124
-
125
- // Find the group UUID by walking the pbxGroup section.
126
- const groupSection = pbx.pbxGroupSection();
127
- let groupUuid = null;
128
- for (const uuid of Object.keys(groupSection)) {
129
- if (uuid.endsWith("_comment")) continue;
130
- if (groupSection[uuid] === group) {
131
- groupUuid = uuid;
132
- break;
133
- }
134
- }
135
- if (!groupUuid) return config;
136
-
137
- // Locate the app's "Sources" build phase so we can attach
138
- // YaverHotReload.swift as a compile unit. AppDelegate.swift is
139
- // already therefind its PBXBuildFile to get the build phase UUID.
140
- const nativeTargetSection = pbx.pbxNativeTargetSection();
141
- let sourcesBuildPhase = null;
142
- for (const uuid of Object.keys(nativeTargetSection)) {
143
- if (uuid.endsWith("_comment")) continue;
144
- const target = nativeTargetSection[uuid];
145
- if (target.name !== appName && target.productReference_comment !== appName) continue;
146
- for (const phase of target.buildPhases || []) {
147
- const phaseComment = (phase.comment || "").toLowerCase();
148
- if (phaseComment.includes("sources")) {
149
- sourcesBuildPhase = phase.value;
150
- break;
120
+ // Look up the group key for the app's source folder (same group
121
+ // that holds AppDelegate.swift). Expo's naming is consistent —
122
+ // projectName === group name in the tree.
123
+ const groupKey =
124
+ proj.findPBXGroupKey({ name: appName }) ||
125
+ proj.findPBXGroupKey({ path: appName });
126
+ if (!groupKey) return config;
127
+
128
+ // Target UUID getFirstTarget is the app target in a standard
129
+ // Expo project. For multi-target projects, users can opt out via
130
+ // enableHotReload: false.
131
+ const target = proj.getFirstTarget();
132
+ if (!target || !target.uuid) return config;
133
+
134
+ const filesToAdd = ["YaverHotReload.swift", "YaverHotReload.m"];
135
+ for (const fileName of filesToAdd) {
136
+ const relPath = `${appName}/${fileName}`;
137
+ // addSourceFile registers PBXFileReference + PBXBuildFile, adds
138
+ // the file to the group, and wires it into the target's
139
+ // Sources build phase which is exactly what we need. It's
140
+ // idempotent in practice because the sdk-level copyFileSync
141
+ // step always writes the file, and addSourceFile is a no-op if
142
+ // the reference already exists.
143
+ if (!proj.hasFile || !proj.hasFile(relPath)) {
144
+ try {
145
+ proj.addSourceFile(relPath, { target: target.uuid }, groupKey);
146
+ } catch (e) {
147
+ // If a duplicate slips through, xcode-lib throws; safe to
148
+ // ignore since the file already being registered is the
149
+ // desired state.
151
150
  }
152
151
  }
153
- if (sourcesBuildPhase) break;
154
- }
155
-
156
- const swiftName = "YaverHotReload.swift";
157
- const objcName = "YaverHotReload.m";
158
-
159
- // addSourceFile registers the file as a PBXFileReference, adds a
160
- // PBXBuildFile entry, drops it into the pbx group, and adds it to
161
- // the sources build phase — exactly what we need.
162
- if (!pbxHasFile(pbx, swiftName)) {
163
- pbx.addSourceFile(
164
- `${appName}/${swiftName}`,
165
- { target: findTargetUuidByName(pbx, appName) },
166
- groupUuid,
167
- );
168
- }
169
- if (!pbxHasFile(pbx, objcName)) {
170
- pbx.addSourceFile(
171
- `${appName}/${objcName}`,
172
- { target: findTargetUuidByName(pbx, appName) },
173
- groupUuid,
174
- );
175
152
  }
176
153
 
177
154
  return config;
@@ -180,28 +157,6 @@ function withYaverHotReloadNativeModule(config) {
180
157
  return config;
181
158
  }
182
159
 
183
- function pbxHasFile(pbx, basename) {
184
- const refs = pbx.pbxFileReferenceSection();
185
- for (const uuid of Object.keys(refs)) {
186
- if (uuid.endsWith("_comment")) continue;
187
- const ref = refs[uuid];
188
- if (!ref || typeof ref !== "object") continue;
189
- const pathValue = (ref.path || "").replace(/"/g, "");
190
- if (pathValue.endsWith(basename)) return true;
191
- }
192
- return false;
193
- }
194
-
195
- function findTargetUuidByName(pbx, appName) {
196
- const section = pbx.pbxNativeTargetSection();
197
- for (const uuid of Object.keys(section)) {
198
- if (uuid.endsWith("_comment")) continue;
199
- const target = section[uuid];
200
- if (target && target.name === appName) return uuid;
201
- }
202
- return undefined;
203
- }
204
-
205
160
  /**
206
161
  * Patch AppDelegate to:
207
162
  * 1. Return hot-reloaded bundle URL on startup (so reloaded bundle persists)
@@ -233,9 +188,22 @@ function withYaverAppDelegateHook(config) {
233
188
  );
234
189
  }
235
190
 
236
- // 2. Add reload notification handler and bridge recreation logic
237
- // Insert before the closing brace of the class
238
- const classCloseIndex = patched.lastIndexOf("}");
191
+ // 2. Add reload notification handler and bridge recreation logic.
192
+ //
193
+ // Must be inserted into the AppDelegate class — NOT ReactNativeDelegate
194
+ // (a separate class below it in the same file). The handler accesses
195
+ // `self.window`, `self.reactNativeDelegate`, `self.reactNativeFactory`,
196
+ // and `self.bindReactNativeFactory(...)` — all of which only exist
197
+ // on AppDelegate. An earlier version of this plugin used
198
+ // lastIndexOf("}"), which in modern Expo templates lands inside
199
+ // ReactNativeDelegate, producing build errors like
200
+ // "cannot find 'setupYaverHotReload' in scope"
201
+ // "value of type 'ReactNativeDelegate' has no member 'window'"
202
+ //
203
+ // The anchor we want is the closing brace of the AppDelegate class
204
+ // declaration itself. Find its opening, then walk braces to its
205
+ // matching close.
206
+ const classCloseIndex = findAppDelegateClassClose(patched);
239
207
  if (classCloseIndex > 0) {
240
208
  const reloadHandler = `
241
209
  // MARK: - Yaver Feedback SDK Hot Reload
@@ -343,6 +311,34 @@ function withYaverAppDelegateHook(config) {
343
311
  });
344
312
  }
345
313
 
314
+ // Locate the closing brace of `class AppDelegate: ...` in an Expo
315
+ // Swift AppDelegate file. Returns the index of the matching `}` for
316
+ // the AppDelegate class's opening `{`, or -1 if not found. We need
317
+ // this because Expo's modern template declares TWO classes in the
318
+ // same file (AppDelegate + ReactNativeDelegate), and the SDK's
319
+ // reload handler only makes sense on the AppDelegate one.
320
+ function findAppDelegateClassClose(contents) {
321
+ // Match both `public class AppDelegate` and `class AppDelegate`,
322
+ // with either inheritance colon or a plain body.
323
+ const headerMatch = contents.match(/class\s+AppDelegate\s*[:{][^{]*\{/);
324
+ if (!headerMatch) return -1;
325
+ const bodyStart = headerMatch.index + headerMatch[0].length - 1; // index of the opening `{`
326
+ // Walk braces to find the matching close. Minimal — we don't try
327
+ // to parse strings/comments because the file is machine-generated
328
+ // by Expo, so braces inside strings are not a realistic concern
329
+ // at this layer.
330
+ let depth = 0;
331
+ for (let i = bodyStart; i < contents.length; i++) {
332
+ const ch = contents[i];
333
+ if (ch === "{") depth++;
334
+ else if (ch === "}") {
335
+ depth--;
336
+ if (depth === 0) return i;
337
+ }
338
+ }
339
+ return -1;
340
+ }
341
+
346
342
  /**
347
343
  * Copy Android native module source files and register the package.
348
344
  * Also patches MainApplication to use hot-reloaded bundle on startup.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaver-feedback-react-native",
3
- "version": "0.7.12",
3
+ "version": "0.7.14",
4
4
  "description": "Visual feedback SDK for Yaver — bug reports, screen recording, voice annotations, and local-first developer workflows",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",