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 = {
|
|
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.
|
|
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: {
|
|
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');
|