yaver-feedback-react-native 0.9.0 → 0.9.3

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 (144) hide show
  1. package/README.md +102 -1
  2. package/app.plugin.js +126 -9
  3. package/dist/AuthOverlay.d.ts +1 -14
  4. package/dist/AuthOverlay.js +9 -62
  5. package/dist/BlackBox.js +9 -1
  6. package/dist/DeployPanel.js +65 -0
  7. package/dist/Discovery.js +13 -8
  8. package/dist/DogfoodRuntime.d.ts +124 -0
  9. package/dist/DogfoodRuntime.js +273 -0
  10. package/dist/FeedbackModal.js +505 -65
  11. package/dist/LoginScreen.d.ts +1 -5
  12. package/dist/LoginScreen.js +2 -6
  13. package/dist/MachinePickerScreen.d.ts +1 -3
  14. package/dist/MachinePickerScreen.js +6 -18
  15. package/dist/P2PClient.d.ts +178 -2
  16. package/dist/P2PClient.js +493 -3
  17. package/dist/P2PDogfoodDriver.d.ts +12 -0
  18. package/dist/P2PDogfoodDriver.js +118 -0
  19. package/dist/PairDeviceModal.d.ts +2 -3
  20. package/dist/ShakeDetector.js +2 -0
  21. package/dist/VibeChatScreen.d.ts +11 -1
  22. package/dist/VibeChatScreen.js +200 -41
  23. package/dist/YaverFeedback.d.ts +132 -0
  24. package/dist/YaverFeedback.js +633 -65
  25. package/dist/YaverModeBadge.d.ts +22 -0
  26. package/dist/YaverModeBadge.js +219 -0
  27. package/dist/__tests__/AuthDevices.test.js +1 -48
  28. package/dist/__tests__/BlackBox.relayPassword.test.d.ts +1 -0
  29. package/dist/__tests__/BlackBox.relayPassword.test.js +105 -0
  30. package/dist/__tests__/BlackBoxAutoStart.test.d.ts +1 -0
  31. package/dist/__tests__/BlackBoxAutoStart.test.js +91 -0
  32. package/dist/__tests__/BlackBoxAutoStartColdStart.test.d.ts +1 -0
  33. package/dist/__tests__/BlackBoxAutoStartColdStart.test.js +156 -0
  34. package/dist/__tests__/BrowserLaneIcon.test.d.ts +3 -0
  35. package/dist/__tests__/BrowserLaneIcon.test.js +80 -0
  36. package/dist/__tests__/DogfoodRuntime.test.d.ts +1 -0
  37. package/dist/__tests__/DogfoodRuntime.test.js +116 -0
  38. package/dist/__tests__/P2PClient.test.js +2 -2
  39. package/dist/__tests__/P2PDogfoodDriver.test.d.ts +1 -0
  40. package/dist/__tests__/P2PDogfoodDriver.test.js +64 -0
  41. package/dist/__tests__/ReportIdentity.test.d.ts +25 -0
  42. package/dist/__tests__/ReportIdentity.test.js +180 -0
  43. package/dist/__tests__/SDKToken.test.js +1 -1
  44. package/dist/__tests__/ShakeToggle.test.d.ts +1 -0
  45. package/dist/__tests__/ShakeToggle.test.js +121 -0
  46. package/dist/__tests__/YaverFeedback.test.js +13 -3
  47. package/dist/__tests__/deviceDogfood.test.d.ts +1 -0
  48. package/dist/__tests__/deviceDogfood.test.js +86 -0
  49. package/dist/__tests__/dogfoodPolicy.test.d.ts +1 -0
  50. package/dist/__tests__/dogfoodPolicy.test.js +33 -0
  51. package/dist/__tests__/pickTargetDevice.test.d.ts +1 -0
  52. package/dist/__tests__/pickTargetDevice.test.js +89 -0
  53. package/dist/__tests__/reloadActions.test.d.ts +1 -0
  54. package/dist/__tests__/reloadActions.test.js +129 -0
  55. package/dist/__tests__/reloadActionsParity.test.d.ts +1 -0
  56. package/dist/__tests__/reloadActionsParity.test.js +42 -0
  57. package/dist/__tests__/types.test.js +5 -5
  58. package/dist/_core/ansi.d.ts +117 -0
  59. package/dist/_core/ansi.js +468 -0
  60. package/dist/_core/ansi.test.d.ts +1 -0
  61. package/dist/_core/ansi.test.js +225 -0
  62. package/dist/_core/buildFeedbackPrompt.d.ts +4 -9
  63. package/dist/_core/buildFeedbackPrompt.js +18 -72
  64. package/dist/_core/constants.d.ts +19 -6
  65. package/dist/_core/constants.js +20 -7
  66. package/dist/_core/device.d.ts +1 -5
  67. package/dist/_core/device.js +5 -14
  68. package/dist/_core/endpoints.d.ts +0 -7
  69. package/dist/_core/endpoints.js +0 -7
  70. package/dist/_core/index.d.ts +4 -0
  71. package/dist/_core/index.js +4 -0
  72. package/dist/_core/remoteless.d.ts +44 -0
  73. package/dist/_core/remoteless.js +75 -0
  74. package/dist/_core/trace.d.ts +47 -0
  75. package/dist/_core/trace.js +38 -0
  76. package/dist/_core/trace.test.d.ts +1 -0
  77. package/dist/_core/trace.test.js +60 -0
  78. package/dist/auth.d.ts +3 -60
  79. package/dist/auth.js +6 -88
  80. package/dist/deviceDogfood.d.ts +53 -0
  81. package/dist/deviceDogfood.js +137 -0
  82. package/dist/dogfoodPolicy.d.ts +25 -0
  83. package/dist/dogfoodPolicy.js +24 -0
  84. package/dist/index.d.ts +17 -4
  85. package/dist/index.js +33 -7
  86. package/dist/reloadActions.d.ts +88 -0
  87. package/dist/reloadActions.js +200 -0
  88. package/dist/storeShots.d.ts +67 -0
  89. package/dist/storeShots.js +137 -0
  90. package/dist/types.d.ts +208 -8
  91. package/package.json +13 -4
  92. package/src/AuthOverlay.tsx +20 -106
  93. package/src/BlackBox.ts +10 -1
  94. package/src/DeployPanel.tsx +74 -0
  95. package/src/Discovery.ts +13 -8
  96. package/src/DogfoodRuntime.ts +373 -0
  97. package/src/FeedbackModal.tsx +619 -79
  98. package/src/LoginScreen.tsx +2 -22
  99. package/src/MachinePickerScreen.tsx +6 -21
  100. package/src/P2PClient.ts +580 -4
  101. package/src/P2PDogfoodDriver.ts +132 -0
  102. package/src/PairDeviceModal.tsx +2 -3
  103. package/src/ShakeDetector.ts +1 -0
  104. package/src/VibeChatScreen.tsx +232 -42
  105. package/src/YaverFeedback.ts +631 -68
  106. package/src/YaverModeBadge.tsx +234 -0
  107. package/src/__tests__/AuthDevices.test.ts +1 -52
  108. package/src/__tests__/BlackBox.relayPassword.test.ts +129 -0
  109. package/src/__tests__/BlackBoxAutoStart.test.ts +111 -0
  110. package/src/__tests__/BlackBoxAutoStartColdStart.test.ts +191 -0
  111. package/src/__tests__/BrowserLaneIcon.test.ts +85 -0
  112. package/src/__tests__/DogfoodRuntime.test.ts +135 -0
  113. package/src/__tests__/P2PClient.test.ts +2 -2
  114. package/src/__tests__/P2PDogfoodDriver.test.ts +80 -0
  115. package/src/__tests__/ReportIdentity.test.ts +212 -0
  116. package/src/__tests__/SDKToken.test.ts +1 -1
  117. package/src/__tests__/ShakeToggle.test.ts +153 -0
  118. package/src/__tests__/YaverFeedback.test.ts +15 -3
  119. package/src/__tests__/deviceDogfood.test.ts +77 -0
  120. package/src/__tests__/dogfoodPolicy.test.ts +34 -0
  121. package/src/__tests__/pickTargetDevice.test.ts +101 -0
  122. package/src/__tests__/reloadActions.test.ts +171 -0
  123. package/src/__tests__/reloadActionsParity.test.ts +49 -0
  124. package/src/__tests__/types.test.ts +5 -5
  125. package/src/_core/ansi.test.ts +250 -0
  126. package/src/_core/ansi.ts +475 -0
  127. package/src/_core/buildFeedbackPrompt.ts +18 -95
  128. package/src/_core/constants.ts +20 -6
  129. package/src/_core/device.ts +5 -16
  130. package/src/_core/endpoints.ts +0 -7
  131. package/src/_core/index.ts +4 -0
  132. package/src/_core/remoteless.ts +110 -0
  133. package/src/_core/trace.test.ts +58 -0
  134. package/src/_core/trace.ts +75 -0
  135. package/src/auth.ts +7 -156
  136. package/src/deviceDogfood.ts +171 -0
  137. package/src/dogfoodPolicy.ts +40 -0
  138. package/src/index.ts +61 -11
  139. package/src/reloadActions.ts +273 -0
  140. package/src/storeShots.ts +189 -0
  141. package/src/types.ts +209 -8
  142. package/dist/GuestOnboardingScreen.d.ts +0 -8
  143. package/dist/GuestOnboardingScreen.js +0 -282
  144. package/src/GuestOnboardingScreen.tsx +0 -307
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ /** Hide the mark for the rest of this run. Exposed on YaverFeedback so a host
3
+ * can wire its own "hide" control, and used by the sheet's own button. */
4
+ export declare function hideYaverModeBadge(): void;
5
+ /** Bring it back — e.g. when a NEW guest bundle loads and the context changed. */
6
+ export declare function showYaverModeBadge(): void;
7
+ export declare function isYaverModeBadgeHidden(): boolean;
8
+ export interface YaverModeBadgeProps {
9
+ /** Corner to sit in. Default bottom-left: bottom-right is where most apps
10
+ * put a FAB, and the badge must never compete with the app's own action. */
11
+ position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
12
+ /** Render even outside Yaver. Development aid only — the badge is meaningless
13
+ * in a standalone build and would be wallpaper. */
14
+ force?: boolean;
15
+ /** Account-scoped third-party Dogfood mode. Keeps the same Yaver Y mark but
16
+ * changes the explanation and offers a confirmed exit to normal Feedback. */
17
+ dogfood?: boolean;
18
+ dogfoodLabel?: string;
19
+ onExitDogfood?: () => void | Promise<void>;
20
+ }
21
+ export declare function YaverModeBadge({ position, force, dogfood, dogfoodLabel, onExitDogfood, }: YaverModeBadgeProps): React.JSX.Element | null;
22
+ export default YaverModeBadge;
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ // YaverModeBadge.tsx — the polite "you're running inside Yaver" mark, for
3
+ // THIRD-PARTY apps.
4
+ //
5
+ // ── Why a third-party app needs this ────────────────────────────────────────
6
+ //
7
+ // When your app is pushed into Yaver's container as a Hermes bundle, it looks
8
+ // exactly like your installed build. Same icon, same launch, same everything —
9
+ // except it is a development bundle from someone's branch. Testers have chased
10
+ // "bugs" that were only unbuilt work, and have been unable to find their way
11
+ // back to the real app because nothing on screen said there was anywhere to go.
12
+ //
13
+ // So the SDK shows one small Y, low contrast, in a corner. Tapping it says what
14
+ // you're in and how to leave. Nothing else.
15
+ //
16
+ // ── Defaults, and why ───────────────────────────────────────────────────────
17
+ //
18
+ // DEFAULT ENABLED (`modeBadge: true`), because the failure it prevents is
19
+ // silent and lands on a tester rather than the developer — exactly the class
20
+ // that should not be opt-in. Turn it off with `modeBadge: false` when your app
21
+ // has its own in-container indicator, or when a screenshot-perfect surface
22
+ // genuinely cannot afford 22 points in a corner.
23
+ //
24
+ // ── What it is NOT ──────────────────────────────────────────────────────────
25
+ //
26
+ // It is NOT the escape. Yaver's container owns that: shake opens Yaver's own
27
+ // overlay with "Back to Yaver". This badge only tells you the gesture exists.
28
+ // Making it the exit would put the way out inside the previewed app, which is
29
+ // the trap the container's escape-ownership rules exist to prevent — the guest
30
+ // could style over it, or unmount it, and the tester would be stuck.
31
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
32
+ if (k2 === undefined) k2 = k;
33
+ var desc = Object.getOwnPropertyDescriptor(m, k);
34
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
35
+ desc = { enumerable: true, get: function() { return m[k]; } };
36
+ }
37
+ Object.defineProperty(o, k2, desc);
38
+ }) : (function(o, m, k, k2) {
39
+ if (k2 === undefined) k2 = k;
40
+ o[k2] = m[k];
41
+ }));
42
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
43
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
44
+ }) : function(o, v) {
45
+ o["default"] = v;
46
+ });
47
+ var __importStar = (this && this.__importStar) || (function () {
48
+ var ownKeys = function(o) {
49
+ ownKeys = Object.getOwnPropertyNames || function (o) {
50
+ var ar = [];
51
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
52
+ return ar;
53
+ };
54
+ return ownKeys(o);
55
+ };
56
+ return function (mod) {
57
+ if (mod && mod.__esModule) return mod;
58
+ var result = {};
59
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
60
+ __setModuleDefault(result, mod);
61
+ return result;
62
+ };
63
+ })();
64
+ Object.defineProperty(exports, "__esModule", { value: true });
65
+ exports.hideYaverModeBadge = hideYaverModeBadge;
66
+ exports.showYaverModeBadge = showYaverModeBadge;
67
+ exports.isYaverModeBadgeHidden = isYaverModeBadgeHidden;
68
+ exports.YaverModeBadge = YaverModeBadge;
69
+ const react_1 = __importStar(require("react"));
70
+ const react_native_1 = require("react-native");
71
+ /** True only inside Yaver's container: the YaverInfo native module is
72
+ * registered by Yaver's app and by nothing else. Same probe ShakeDetector
73
+ * and P2PClient use — one answer, not three. */
74
+ function isInsideYaver() {
75
+ try {
76
+ return !!react_native_1.NativeModules?.YaverInfo;
77
+ }
78
+ catch {
79
+ return false;
80
+ }
81
+ }
82
+ /**
83
+ * Per-RUN dismissal, shared by every mounted badge.
84
+ *
85
+ * Deliberately in memory, not storage. A permanently hidden badge recreates
86
+ * exactly the problem the badge exists to prevent: a tester who cannot tell an
87
+ * unbuilt branch from the installed app, and cannot find the way back. Polite
88
+ * means not nagging within a session — it does not mean permanent amnesia
89
+ * about which build you are looking at. A relaunch is a new context.
90
+ *
91
+ * An APP that wants it gone for good passes `modeBadge: false` at init. That is
92
+ * a developer making an informed choice, which is a different thing from a
93
+ * tester clearing their screen for a minute.
94
+ */
95
+ let hiddenThisRun = false;
96
+ const hideListeners = new Set();
97
+ /** Hide the mark for the rest of this run. Exposed on YaverFeedback so a host
98
+ * can wire its own "hide" control, and used by the sheet's own button. */
99
+ function hideYaverModeBadge() {
100
+ hiddenThisRun = true;
101
+ hideListeners.forEach((l) => {
102
+ try {
103
+ l();
104
+ }
105
+ catch {
106
+ /* one bad listener mustn't block the others */
107
+ }
108
+ });
109
+ }
110
+ /** Bring it back — e.g. when a NEW guest bundle loads and the context changed. */
111
+ function showYaverModeBadge() {
112
+ hiddenThisRun = false;
113
+ hideListeners.forEach((l) => {
114
+ try {
115
+ l();
116
+ }
117
+ catch {
118
+ /* noop */
119
+ }
120
+ });
121
+ }
122
+ function isYaverModeBadgeHidden() {
123
+ return hiddenThisRun;
124
+ }
125
+ function YaverModeBadge({ position = 'bottom-left', force = false, dogfood = false, dogfoodLabel = 'Dogfood', onExitDogfood, }) {
126
+ const [open, setOpen] = (0, react_1.useState)(false);
127
+ const [, bump] = (0, react_1.useState)(0);
128
+ (0, react_1.useEffect)(() => {
129
+ const listener = () => bump((n) => n + 1);
130
+ hideListeners.add(listener);
131
+ return () => {
132
+ hideListeners.delete(listener);
133
+ };
134
+ }, []);
135
+ // Standalone builds render nothing at all — zero cost, zero pixels.
136
+ if (!force && !dogfood && !isInsideYaver())
137
+ return null;
138
+ if (hiddenThisRun)
139
+ return null;
140
+ const vertical = position.startsWith('top') ? { top: 44 } : { bottom: 28 };
141
+ const horizontal = position.endsWith('left') ? { left: 12 } : { right: 12 };
142
+ return (<>
143
+ <react_native_1.View
144
+ // The wrapper never intercepts touches; only the 22pt mark does. A
145
+ // full-screen overlay that swallowed taps would break the app it is
146
+ // supposed to be politely annotating.
147
+ pointerEvents="box-none" style={{ position: 'absolute', ...vertical, ...horizontal, zIndex: 9998 }}>
148
+ <react_native_1.Pressable onPress={() => setOpen(true)} accessibilityRole="button" accessibilityLabel={dogfood ? `Exit ${dogfoodLabel} Dogfood mode` : 'Running inside Yaver'} hitSlop={10} style={({ pressed }) => ({
149
+ width: 22,
150
+ height: 22,
151
+ borderRadius: 11,
152
+ alignItems: 'center',
153
+ justifyContent: 'center',
154
+ borderWidth: 1,
155
+ borderColor: 'rgba(124,92,255,0.45)',
156
+ backgroundColor: 'rgba(124,92,255,0.14)',
157
+ opacity: pressed ? 0.6 : 0.9,
158
+ })}>
159
+ <react_native_1.Text style={{ color: '#7C5CFF', fontSize: 12, fontWeight: '700', lineHeight: 14 }}>Y</react_native_1.Text>
160
+ </react_native_1.Pressable>
161
+ </react_native_1.View>
162
+
163
+ <react_native_1.Modal visible={open} transparent animationType="fade" onRequestClose={() => setOpen(false)}>
164
+ <react_native_1.Pressable style={{ flex: 1, backgroundColor: '#0008' }} onPress={() => setOpen(false)}>
165
+ <react_native_1.Pressable onPress={(e) => e.stopPropagation()} style={{
166
+ margin: 24,
167
+ marginTop: 'auto',
168
+ marginBottom: 48,
169
+ borderRadius: 16,
170
+ padding: 18,
171
+ backgroundColor: '#151519',
172
+ borderWidth: 1,
173
+ borderColor: '#2a2a32',
174
+ gap: 10,
175
+ }}>
176
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
177
+ <react_native_1.Text style={{ color: '#7C5CFF', fontSize: 15, fontWeight: '800' }}>Y</react_native_1.Text>
178
+ <react_native_1.Text style={{ color: '#fff', fontSize: 15, fontWeight: '700' }}>
179
+ {dogfood ? `${dogfoodLabel} Dogfood mode` : 'Running inside Yaver'}
180
+ </react_native_1.Text>
181
+ </react_native_1.View>
182
+ <react_native_1.Text style={{ color: '#b6b6c0', fontSize: 13, lineHeight: 19 }}>
183
+ {dogfood
184
+ ? 'This approved account is improving the app directly through Yaver. Tasks and re-render actions target this app instead of creating a feedback report.'
185
+ : 'This is a development build loaded into Yaver — not the version installed on this device. Anything unfinished here is work in progress, not a released bug.'}
186
+ </react_native_1.Text>
187
+ {dogfood ? (<react_native_1.Pressable onPress={() => react_native_1.Alert.alert('Exit Dogfood mode?', 'Return this SDK to normal Feedback mode. Your coding tasks and source changes are untouched.', [
188
+ { text: 'Keep Dogfooding', style: 'cancel' },
189
+ { text: 'Exit Dogfood', style: 'destructive', onPress: () => {
190
+ void onExitDogfood?.();
191
+ setOpen(false);
192
+ } },
193
+ ])} style={{ paddingVertical: 10, alignItems: 'center' }}>
194
+ <react_native_1.Text style={{ color: '#fca5a5', fontSize: 13, fontWeight: '700' }}>Exit Dogfood mode</react_native_1.Text>
195
+ </react_native_1.Pressable>) : (<react_native_1.Text style={{ color: '#b6b6c0', fontSize: 13, lineHeight: 19 }}>
196
+ Shake the device to open Yaver's overlay, where "Back to Yaver" returns you to the installed app.
197
+ </react_native_1.Text>)}
198
+ {/* Polite means closeable. "for now", never "don't show again":
199
+ the badge returns next launch so nobody forgets which build
200
+ they're testing. */}
201
+ <react_native_1.Pressable onPress={() => {
202
+ hideYaverModeBadge();
203
+ setOpen(false);
204
+ }} style={{ paddingVertical: 10, alignItems: 'center' }}>
205
+ <react_native_1.Text style={{ color: '#8a8a96', fontSize: 13 }}>Hide for now</react_native_1.Text>
206
+ </react_native_1.Pressable>
207
+ <react_native_1.Text style={{ color: '#6b6b76', fontSize: 11, textAlign: 'center', lineHeight: 15 }}>
208
+ Hidden until you next launch this build.
209
+ </react_native_1.Text>
210
+
211
+ <react_native_1.Pressable onPress={() => setOpen(false)} style={{ paddingVertical: 10, alignItems: 'center' }}>
212
+ <react_native_1.Text style={{ color: '#8a8a96', fontSize: 13 }}>Close</react_native_1.Text>
213
+ </react_native_1.Pressable>
214
+ </react_native_1.Pressable>
215
+ </react_native_1.Pressable>
216
+ </react_native_1.Modal>
217
+ </>);
218
+ }
219
+ exports.default = YaverModeBadge;
@@ -8,7 +8,7 @@ describe('auth device listing', () => {
8
8
  jest.clearAllMocks();
9
9
  mockFetch.mockReset();
10
10
  });
11
- it('keeps guest-shared devices in the shared bucket', async () => {
11
+ it('returns the owner devices emitted by /devices/list', async () => {
12
12
  mockFetch.mockResolvedValue({
13
13
  ok: true,
14
14
  json: () => Promise.resolve({
@@ -18,24 +18,10 @@ describe('auth device listing', () => {
18
18
  name: 'My Mac',
19
19
  platform: 'macos',
20
20
  isOnline: true,
21
- isGuest: false,
22
21
  quicHost: '10.0.0.10',
23
22
  quicPort: 18080,
24
23
  lastHeartbeat: 123,
25
24
  },
26
- {
27
- deviceId: 'guest-1',
28
- name: 'yaver-test-ephemeral',
29
- platform: 'linux',
30
- isOnline: true,
31
- isGuest: true,
32
- hostName: 'Host User',
33
- hostEmail: 'host@example.com',
34
- accessScope: 'shared-scoped',
35
- quicHost: '198.51.100.20',
36
- quicPort: 18080,
37
- lastHeartbeat: 456,
38
- },
39
25
  ],
40
26
  }),
41
27
  });
@@ -44,39 +30,6 @@ describe('auth device listing', () => {
44
30
  headers: { Authorization: 'Bearer sdk-user-token' },
45
31
  }));
46
32
  expect(result.owned).toHaveLength(1);
47
- expect(result.shared).toHaveLength(1);
48
33
  expect(result.owned[0].deviceId).toBe('own-1');
49
- expect(result.shared[0]).toMatchObject({
50
- deviceId: 'guest-1',
51
- isGuest: true,
52
- hostEmail: 'host@example.com',
53
- accessScope: 'shared-scoped',
54
- });
55
- });
56
- it('shows shared devices even when the guest owns no machines', async () => {
57
- mockFetch.mockResolvedValue({
58
- ok: true,
59
- json: () => Promise.resolve({
60
- devices: [
61
- {
62
- deviceId: 'guest-only',
63
- name: 'yaver-test-ephemeral',
64
- platform: 'linux',
65
- isOnline: true,
66
- isGuest: true,
67
- hostName: 'Host User',
68
- hostEmail: 'host@example.com',
69
- accessScope: 'shared-scoped',
70
- quicHost: '198.51.100.20',
71
- quicPort: 18080,
72
- lastHeartbeat: 789,
73
- },
74
- ],
75
- }),
76
- });
77
- const result = await (0, auth_1.listReachableDevices)('guest-only-token');
78
- expect(result.owned).toEqual([]);
79
- expect(result.shared).toHaveLength(1);
80
- expect(result.shared[0].deviceId).toBe('guest-only');
81
34
  });
82
35
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ // BlackBox relay-password sourcing.
3
+ //
4
+ // This exists because BlackBox.start() used to read the relay password off
5
+ // the feedback config:
6
+ //
7
+ // BlackBox.relayPassword =
8
+ // (feedbackConfig as { relayPassword?: string }).relayPassword ?? '';
9
+ //
10
+ // `relayPassword` is not a key on FeedbackConfig. The cast is what stopped
11
+ // the compiler from saying so, and the field was therefore permanently ''.
12
+ // Since every X-Relay-Password header is written behind an
13
+ // `if (BlackBox.relayPassword)` guard, the header was never sent, and the
14
+ // relay 401s an empty password (relay/server.go). Net effect: on cellular
15
+ // (the only time the relay is used at all) the SSE command channel died,
16
+ // so the reload that lands after an agent fix never fired. On-LAN the
17
+ // password is unused, so this was invisible in local dev — it only ever
18
+ // broke remote users.
19
+ //
20
+ // The contract pinned here: BlackBox takes its relay password from
21
+ // YaverFeedback.getRelayPassword(), the same source P2PClient uses.
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ const BlackBox_1 = require("../BlackBox");
24
+ const YaverFeedback_1 = require("../YaverFeedback");
25
+ jest.mock('react-native', () => ({
26
+ Platform: { OS: 'ios' },
27
+ }));
28
+ jest.useFakeTimers();
29
+ const mockFetch = jest.fn();
30
+ global.fetch = mockFetch;
31
+ class MockAbortController {
32
+ constructor() {
33
+ this.signal = { aborted: false };
34
+ this.abort = jest.fn(() => {
35
+ this.signal.aborted = true;
36
+ });
37
+ }
38
+ }
39
+ global.AbortController = MockAbortController;
40
+ function emptySse() {
41
+ return new ReadableStream({
42
+ start(controller) {
43
+ controller.close();
44
+ },
45
+ });
46
+ }
47
+ beforeEach(() => {
48
+ jest.clearAllMocks();
49
+ mockFetch.mockImplementation((url) => {
50
+ if (String(url).includes('/blackbox/command-stream')) {
51
+ return Promise.resolve({ ok: true, body: emptySse() });
52
+ }
53
+ return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
54
+ });
55
+ // A relay-routed agent URL — the shape discovery produces off-LAN.
56
+ YaverFeedback_1.YaverFeedback.init({
57
+ agentUrl: 'https://public.yaver.io/d/macmini-1',
58
+ authToken: 'tok',
59
+ });
60
+ });
61
+ afterEach(() => {
62
+ BlackBox_1.BlackBox.stop();
63
+ jest.restoreAllMocks();
64
+ });
65
+ describe('BlackBox relay password', () => {
66
+ it('sends X-Relay-Password on the SSE command stream, sourced from getRelayPassword()', async () => {
67
+ jest.spyOn(YaverFeedback_1.YaverFeedback, 'getRelayPassword').mockReturnValue('s3cret-relay-pw');
68
+ BlackBox_1.BlackBox.start({ deviceId: 'dev-abc', appName: 'talos-mobile' });
69
+ await Promise.resolve();
70
+ await Promise.resolve();
71
+ const sseCall = mockFetch.mock.calls.find(([url]) => String(url).includes('/blackbox/command-stream'));
72
+ expect(sseCall).toBeDefined();
73
+ const [, init] = sseCall;
74
+ expect(init.headers).toEqual(expect.objectContaining({ 'X-Relay-Password': 's3cret-relay-pw' }));
75
+ });
76
+ it('sends X-Relay-Password on the event flush', async () => {
77
+ jest.spyOn(YaverFeedback_1.YaverFeedback, 'getRelayPassword').mockReturnValue('s3cret-relay-pw');
78
+ // maxBufferSize:1 makes push() flush synchronously on the first event,
79
+ // so this doesn't hang on interval semantics.
80
+ BlackBox_1.BlackBox.start({ deviceId: 'dev-abc', maxBufferSize: 1 });
81
+ BlackBox_1.BlackBox.log('hello');
82
+ await Promise.resolve();
83
+ const flush = mockFetch.mock.calls.find(([url]) => String(url).includes('/blackbox/events'));
84
+ expect(flush).toBeDefined();
85
+ const [, init] = flush;
86
+ expect(init.headers).toEqual(expect.objectContaining({ 'X-Relay-Password': 's3cret-relay-pw' }));
87
+ });
88
+ it('omits the header entirely when there is no relay password (direct LAN)', async () => {
89
+ // An empty header value is itself a 401 at the relay, so "no password"
90
+ // must mean "no header", not "empty header".
91
+ jest.spyOn(YaverFeedback_1.YaverFeedback, 'getRelayPassword').mockReturnValue('');
92
+ BlackBox_1.BlackBox.start({ deviceId: 'dev-abc' });
93
+ await Promise.resolve();
94
+ await Promise.resolve();
95
+ const sseCall = mockFetch.mock.calls.find(([url]) => String(url).includes('/blackbox/command-stream'));
96
+ expect(sseCall).toBeDefined();
97
+ const [, init] = sseCall;
98
+ expect(init.headers).not.toHaveProperty('X-Relay-Password');
99
+ });
100
+ it('does not read a relayPassword key off the feedback config', () => {
101
+ // The original bug in one assertion: FeedbackConfig has no such key, so
102
+ // anyone reintroducing that read gets '' and silently breaks the relay.
103
+ expect(YaverFeedback_1.YaverFeedback.getConfig()).not.toHaveProperty('relayPassword');
104
+ });
105
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ // BlackBox auto-start config passthrough.
3
+ //
4
+ // The natural way to configure the flight recorder — and the one the README's
5
+ // own snippet shows — is:
6
+ //
7
+ // YaverFeedback.init({ trigger: 'shake' });
8
+ // BlackBox.start({ appName: 'my-app' });
9
+ //
10
+ // That start() early-returns: a zero-config init has no agentUrl yet, because
11
+ // discovery resolves it asynchronously. The SDK's autoStartBlackBox then fires
12
+ // ~500ms later once the agent and token exist — and used to call start() with
13
+ // no arguments, quietly replacing the host's config with defaults and leaving
14
+ // appName as ''. Hosts had no way to tell: the recorder streamed, just
15
+ // anonymously.
16
+ //
17
+ // config.blackBox is now the supported channel, and auto-start honours it.
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ const BlackBox_1 = require("../BlackBox");
20
+ const YaverFeedback_1 = require("../YaverFeedback");
21
+ jest.mock('react-native', () => ({
22
+ Platform: { OS: 'ios' },
23
+ DeviceEventEmitter: { emit: jest.fn(), addListener: jest.fn() },
24
+ NativeModules: {},
25
+ }));
26
+ jest.mock('../ShakeDetector', () => ({
27
+ ShakeDetector: jest.fn().mockImplementation(() => ({
28
+ start: jest.fn(),
29
+ stop: jest.fn(),
30
+ })),
31
+ }));
32
+ jest.useFakeTimers();
33
+ const mockFetch = jest.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve({}), body: null }));
34
+ global.fetch = mockFetch;
35
+ class MockAbortController {
36
+ constructor() {
37
+ this.signal = { aborted: false };
38
+ this.abort = jest.fn();
39
+ }
40
+ }
41
+ global.AbortController = MockAbortController;
42
+ beforeEach(() => {
43
+ jest.clearAllMocks();
44
+ YaverFeedback_1.YaverFeedback.destroy();
45
+ });
46
+ afterEach(() => {
47
+ BlackBox_1.BlackBox.stop();
48
+ YaverFeedback_1.YaverFeedback.destroy();
49
+ });
50
+ describe('autoStartBlackBox config passthrough', () => {
51
+ it('starts BlackBox with the config passed on init', async () => {
52
+ const startSpy = jest.spyOn(BlackBox_1.BlackBox, 'start').mockImplementation(() => { });
53
+ YaverFeedback_1.YaverFeedback.init({
54
+ trigger: 'shake',
55
+ agentUrl: 'http://localhost:18080',
56
+ authToken: 'tok',
57
+ blackBox: { appName: 'talos-mobile', flushInterval: 3000, maxBufferSize: 25 },
58
+ });
59
+ await jest.advanceTimersByTimeAsync(600);
60
+ expect(startSpy).toHaveBeenCalledWith(expect.objectContaining({
61
+ appName: 'talos-mobile',
62
+ flushInterval: 3000,
63
+ maxBufferSize: 25,
64
+ }));
65
+ startSpy.mockRestore();
66
+ });
67
+ it('still auto-starts when no blackBox config is given', async () => {
68
+ const startSpy = jest.spyOn(BlackBox_1.BlackBox, 'start').mockImplementation(() => { });
69
+ YaverFeedback_1.YaverFeedback.init({
70
+ trigger: 'shake',
71
+ agentUrl: 'http://localhost:18080',
72
+ authToken: 'tok',
73
+ });
74
+ await jest.advanceTimersByTimeAsync(600);
75
+ expect(startSpy).toHaveBeenCalled();
76
+ startSpy.mockRestore();
77
+ });
78
+ it('does not auto-start when autoStartBlackBox is false', async () => {
79
+ const startSpy = jest.spyOn(BlackBox_1.BlackBox, 'start').mockImplementation(() => { });
80
+ YaverFeedback_1.YaverFeedback.init({
81
+ trigger: 'shake',
82
+ agentUrl: 'http://localhost:18080',
83
+ authToken: 'tok',
84
+ autoStartBlackBox: false,
85
+ blackBox: { appName: 'talos-mobile' },
86
+ });
87
+ await jest.advanceTimersByTimeAsync(600);
88
+ expect(startSpy).not.toHaveBeenCalled();
89
+ startSpy.mockRestore();
90
+ });
91
+ });