yaver-feedback-react-native 0.9.6 → 0.9.7

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.
package/app.plugin.js CHANGED
@@ -407,6 +407,88 @@ ${setupAnchor}`
407
407
  return patched;
408
408
  }
409
409
 
410
+ /** Scene-based iOS apps receive Home Screen quick actions through their
411
+ * UIWindowSceneDelegate, not AppDelegate. A CarPlay scene manifest is enough
412
+ * to opt an otherwise ordinary Expo app into that lifecycle, so patch the
413
+ * phone scene delegate when a host has one. */
414
+ function withYaverSceneDelegateShortcut(config) {
415
+ return withDangerousMod(config, [
416
+ "ios",
417
+ (config) => {
418
+ const appName = config.modRequest.projectName;
419
+ if (!appName) return config;
420
+ const sourceDir = path.join(config.modRequest.platformProjectRoot, appName);
421
+ if (!fs.existsSync(sourceDir)) return config;
422
+
423
+ for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
424
+ if (!entry.isFile() || !entry.name.endsWith("SceneDelegate.swift")) continue;
425
+ const sourcePath = path.join(sourceDir, entry.name);
426
+ const source = fs.readFileSync(sourcePath, "utf8");
427
+ if (!source.includes("UIWindowSceneDelegate")) continue;
428
+ const patched = patchDogfoodSceneDelegate(source);
429
+ if (patched !== source) fs.writeFileSync(sourcePath, patched);
430
+ }
431
+ return config;
432
+ },
433
+ ]);
434
+ }
435
+
436
+ function patchDogfoodSceneDelegate(contents) {
437
+ if (contents.includes("Yaver Feedback SDK Dogfood Scene Shortcut")) return contents;
438
+
439
+ let patched = insertAtEndOfMethod(
440
+ contents,
441
+ "options connectionOptions: UIScene.ConnectionOptions",
442
+ `
443
+ // Yaver Feedback SDK Dogfood Scene Shortcut: cold launch.
444
+ if let yaverShortcut = connectionOptions.shortcutItem,
445
+ yaverShortcut.type == YaverHotReload.dogfoodShortcutType {
446
+ YaverHotReload.markDogfoodShortcutPending()
447
+ }
448
+ `
449
+ );
450
+
451
+ // A custom scene delegate may already forward quick actions to the patched
452
+ // AppDelegate. Keep that host-owned behavior instead of declaring a second
453
+ // method with the same Swift selector.
454
+ if (patched.includes("performActionFor shortcutItem")) return patched;
455
+
456
+ const classCloseIndex = findWindowSceneDelegateClassClose(patched);
457
+ if (classCloseIndex < 0) return patched;
458
+ const handler = `
459
+ // MARK: - Yaver Feedback SDK Dogfood Scene Shortcut
460
+
461
+ func windowScene(
462
+ _ windowScene: UIWindowScene,
463
+ performActionFor shortcutItem: UIApplicationShortcutItem,
464
+ completionHandler: @escaping (Bool) -> Void
465
+ ) {
466
+ guard shortcutItem.type == YaverHotReload.dogfoodShortcutType else {
467
+ completionHandler(false)
468
+ return
469
+ }
470
+ YaverHotReload.markDogfoodShortcutPending()
471
+ completionHandler(true)
472
+ }
473
+ `;
474
+ return patched.slice(0, classCloseIndex) + handler + patched.slice(classCloseIndex);
475
+ }
476
+
477
+ function findWindowSceneDelegateClassClose(contents) {
478
+ const headerMatch = contents.match(/class\s+\w+\s*:[^{]*UIWindowSceneDelegate[^{]*\{/);
479
+ if (!headerMatch) return -1;
480
+ const bodyStart = headerMatch.index + headerMatch[0].length - 1;
481
+ let depth = 0;
482
+ for (let i = bodyStart; i < contents.length; i++) {
483
+ if (contents[i] === "{") depth++;
484
+ else if (contents[i] === "}") {
485
+ depth--;
486
+ if (depth === 0) return i;
487
+ }
488
+ }
489
+ return -1;
490
+ }
491
+
410
492
  // Locate the closing brace of `class AppDelegate: ...` in an Expo
411
493
  // Swift AppDelegate file. Returns the index of the matching `}` for
412
494
  // the AppDelegate class's opening `{`, or -1 if not found. We need
@@ -711,6 +793,7 @@ function withYaverFeedback(config, props) {
711
793
  if (enableHotReload) {
712
794
  config = withYaverHotReloadNativeModule(config);
713
795
  config = withYaverAppDelegateHook(config);
796
+ config = withYaverSceneDelegateShortcut(config);
714
797
  config = withYaverAndroidHotReload(config);
715
798
  }
716
799
 
@@ -726,5 +809,10 @@ const yaverFeedbackPlugin = createRunOncePlugin(
726
809
  // Pure transforms are exposed for contract tests only. Keeping the test at the
727
810
  // config-plugin seam catches template drift before a consumer discovers it in
728
811
  // an archive build.
729
- yaverFeedbackPlugin.__test = { patchDogfoodAppShortcut, findAppDelegateClassClose };
812
+ yaverFeedbackPlugin.__test = {
813
+ patchDogfoodAppShortcut,
814
+ patchDogfoodSceneDelegate,
815
+ findAppDelegateClassClose,
816
+ findWindowSceneDelegateClassClose,
817
+ };
730
818
  module.exports = yaverFeedbackPlugin;
@@ -22,6 +22,24 @@ public class AppDelegate: ExpoAppDelegate {
22
22
  expect(once).toContain('markDogfoodShortcutPending()');
23
23
  expect(plugin.__test.patchDogfoodAppShortcut(once)).toBe(once);
24
24
  });
25
+ it('patches cold and warm scene-lifecycle delivery idempotently', () => {
26
+ const source = `
27
+ final class TalosSceneDelegate: UIResponder, UIWindowSceneDelegate {
28
+ func scene(
29
+ _ scene: UIScene,
30
+ willConnectTo session: UISceneSession,
31
+ options connectionOptions: UIScene.ConnectionOptions
32
+ ) {
33
+ guard scene is UIWindowScene else { return }
34
+ }
35
+ }
36
+ `;
37
+ const once = plugin.__test.patchDogfoodSceneDelegate(source);
38
+ expect(once).toContain('connectionOptions.shortcutItem');
39
+ expect(once).toContain('func windowScene(');
40
+ expect(once).toContain('markDogfoodShortcutPending()');
41
+ expect(plugin.__test.patchDogfoodSceneDelegate(once)).toBe(once);
42
+ });
25
43
  it('uses dynamic native shortcuts on both platforms', () => {
26
44
  const ios = (0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../../ios/YaverHotReload.swift'), 'utf8');
27
45
  const android = (0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../../android/src/main/java/io/yaver/feedback/YaverHotReloadModule.java'), 'utf8');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaver-feedback-react-native",
3
- "version": "0.9.6",
3
+ "version": "0.9.7",
4
4
  "description": "Visual feedback SDK for Yaver — bug reports, screen recording, voice vibe coding, and local-first developer workflows",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -2,7 +2,10 @@ import { readFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
 
4
4
  const plugin = require('../../app.plugin.js') as {
5
- __test: { patchDogfoodAppShortcut(contents: string): string };
5
+ __test: {
6
+ patchDogfoodAppShortcut(contents: string): string;
7
+ patchDogfoodSceneDelegate(contents: string): string;
8
+ };
6
9
  };
7
10
 
8
11
  describe('native Dogfood shortcut contract', () => {
@@ -25,6 +28,25 @@ public class AppDelegate: ExpoAppDelegate {
25
28
  expect(plugin.__test.patchDogfoodAppShortcut(once)).toBe(once);
26
29
  });
27
30
 
31
+ it('patches cold and warm scene-lifecycle delivery idempotently', () => {
32
+ const source = `
33
+ final class TalosSceneDelegate: UIResponder, UIWindowSceneDelegate {
34
+ func scene(
35
+ _ scene: UIScene,
36
+ willConnectTo session: UISceneSession,
37
+ options connectionOptions: UIScene.ConnectionOptions
38
+ ) {
39
+ guard scene is UIWindowScene else { return }
40
+ }
41
+ }
42
+ `;
43
+ const once = plugin.__test.patchDogfoodSceneDelegate(source);
44
+ expect(once).toContain('connectionOptions.shortcutItem');
45
+ expect(once).toContain('func windowScene(');
46
+ expect(once).toContain('markDogfoodShortcutPending()');
47
+ expect(plugin.__test.patchDogfoodSceneDelegate(once)).toBe(once);
48
+ });
49
+
28
50
  it('uses dynamic native shortcuts on both platforms', () => {
29
51
  const ios = readFileSync(join(__dirname, '../../ios/YaverHotReload.swift'), 'utf8');
30
52
  const android = readFileSync(join(__dirname, '../../android/src/main/java/io/yaver/feedback/YaverHotReloadModule.java'), 'utf8');