yaver-feedback-react-native 0.9.2 → 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 (103) hide show
  1. package/README.md +102 -1
  2. package/dist/AuthOverlay.d.ts +1 -14
  3. package/dist/AuthOverlay.js +9 -62
  4. package/dist/Discovery.js +0 -6
  5. package/dist/DogfoodRuntime.d.ts +124 -0
  6. package/dist/DogfoodRuntime.js +273 -0
  7. package/dist/FeedbackModal.js +347 -61
  8. package/dist/LoginScreen.d.ts +1 -5
  9. package/dist/LoginScreen.js +2 -6
  10. package/dist/MachinePickerScreen.d.ts +1 -3
  11. package/dist/MachinePickerScreen.js +6 -18
  12. package/dist/P2PClient.d.ts +116 -3
  13. package/dist/P2PClient.js +273 -3
  14. package/dist/P2PDogfoodDriver.d.ts +12 -0
  15. package/dist/P2PDogfoodDriver.js +118 -0
  16. package/dist/PairDeviceModal.d.ts +2 -3
  17. package/dist/VibeChatScreen.d.ts +11 -1
  18. package/dist/VibeChatScreen.js +200 -41
  19. package/dist/YaverFeedback.d.ts +34 -0
  20. package/dist/YaverFeedback.js +124 -19
  21. package/dist/YaverModeBadge.d.ts +22 -0
  22. package/dist/YaverModeBadge.js +219 -0
  23. package/dist/__tests__/AuthDevices.test.js +1 -48
  24. package/dist/__tests__/DogfoodRuntime.test.d.ts +1 -0
  25. package/dist/__tests__/DogfoodRuntime.test.js +116 -0
  26. package/dist/__tests__/P2PDogfoodDriver.test.d.ts +1 -0
  27. package/dist/__tests__/P2PDogfoodDriver.test.js +64 -0
  28. package/dist/__tests__/ReportIdentity.test.d.ts +25 -1
  29. package/dist/__tests__/ReportIdentity.test.js +34 -22
  30. package/dist/__tests__/YaverFeedback.test.js +13 -3
  31. package/dist/__tests__/deviceDogfood.test.d.ts +1 -0
  32. package/dist/__tests__/deviceDogfood.test.js +86 -0
  33. package/dist/__tests__/dogfoodPolicy.test.d.ts +1 -0
  34. package/dist/__tests__/dogfoodPolicy.test.js +33 -0
  35. package/dist/_core/ansi.d.ts +117 -0
  36. package/dist/_core/ansi.js +468 -0
  37. package/dist/_core/ansi.test.d.ts +1 -0
  38. package/dist/_core/ansi.test.js +225 -0
  39. package/dist/_core/buildFeedbackPrompt.d.ts +4 -9
  40. package/dist/_core/buildFeedbackPrompt.js +18 -72
  41. package/dist/_core/constants.d.ts +19 -6
  42. package/dist/_core/constants.js +20 -7
  43. package/dist/_core/device.d.ts +9 -23
  44. package/dist/_core/device.js +13 -28
  45. package/dist/_core/endpoints.d.ts +0 -7
  46. package/dist/_core/endpoints.js +0 -7
  47. package/dist/_core/index.d.ts +4 -0
  48. package/dist/_core/index.js +4 -0
  49. package/dist/_core/remoteless.d.ts +44 -0
  50. package/dist/_core/remoteless.js +75 -0
  51. package/dist/_core/trace.d.ts +47 -0
  52. package/dist/_core/trace.js +38 -0
  53. package/dist/_core/trace.test.d.ts +1 -0
  54. package/dist/_core/trace.test.js +60 -0
  55. package/dist/auth.d.ts +3 -60
  56. package/dist/auth.js +6 -88
  57. package/dist/deviceDogfood.d.ts +53 -0
  58. package/dist/deviceDogfood.js +137 -0
  59. package/dist/dogfoodPolicy.d.ts +25 -0
  60. package/dist/dogfoodPolicy.js +24 -0
  61. package/dist/index.d.ts +13 -4
  62. package/dist/index.js +24 -8
  63. package/dist/reloadActions.js +2 -2
  64. package/dist/types.d.ts +50 -7
  65. package/package.json +12 -3
  66. package/src/AuthOverlay.tsx +20 -106
  67. package/src/Discovery.ts +0 -6
  68. package/src/DogfoodRuntime.ts +373 -0
  69. package/src/FeedbackModal.tsx +445 -67
  70. package/src/LoginScreen.tsx +2 -22
  71. package/src/MachinePickerScreen.tsx +6 -21
  72. package/src/P2PClient.ts +347 -4
  73. package/src/P2PDogfoodDriver.ts +132 -0
  74. package/src/PairDeviceModal.tsx +2 -3
  75. package/src/VibeChatScreen.tsx +232 -42
  76. package/src/YaverFeedback.ts +133 -22
  77. package/src/YaverModeBadge.tsx +234 -0
  78. package/src/__tests__/AuthDevices.test.ts +1 -52
  79. package/src/__tests__/DogfoodRuntime.test.ts +135 -0
  80. package/src/__tests__/P2PDogfoodDriver.test.ts +80 -0
  81. package/src/__tests__/ReportIdentity.test.ts +36 -27
  82. package/src/__tests__/YaverFeedback.test.ts +15 -3
  83. package/src/__tests__/deviceDogfood.test.ts +77 -0
  84. package/src/__tests__/dogfoodPolicy.test.ts +34 -0
  85. package/src/_core/ansi.test.ts +250 -0
  86. package/src/_core/ansi.ts +475 -0
  87. package/src/_core/buildFeedbackPrompt.ts +18 -95
  88. package/src/_core/constants.ts +20 -6
  89. package/src/_core/device.ts +13 -30
  90. package/src/_core/endpoints.ts +0 -7
  91. package/src/_core/index.ts +4 -0
  92. package/src/_core/remoteless.ts +110 -0
  93. package/src/_core/trace.test.ts +58 -0
  94. package/src/_core/trace.ts +75 -0
  95. package/src/auth.ts +7 -156
  96. package/src/deviceDogfood.ts +171 -0
  97. package/src/dogfoodPolicy.ts +40 -0
  98. package/src/index.ts +40 -11
  99. package/src/reloadActions.ts +2 -2
  100. package/src/types.ts +45 -7
  101. package/dist/GuestOnboardingScreen.d.ts +0 -8
  102. package/dist/GuestOnboardingScreen.js +0 -282
  103. package/src/GuestOnboardingScreen.tsx +0 -307
@@ -9,6 +9,8 @@ const ShakeDetector_1 = require("./ShakeDetector");
9
9
  const storeShots_1 = require("./storeShots");
10
10
  const auth_1 = require("./auth");
11
11
  const preferences_1 = require("./preferences");
12
+ const dogfoodPolicy_1 = require("./dogfoodPolicy");
13
+ const deviceDogfood_1 = require("./deviceDogfood");
12
14
  function unrefTimer(timer) {
13
15
  const maybeNodeTimer = timer;
14
16
  if (typeof maybeNodeTimer.unref === 'function') {
@@ -140,6 +142,7 @@ if (IS_HOST_MODE) {
140
142
  let config = null;
141
143
  let enabled = false;
142
144
  let p2pClient = null;
145
+ let renderP2PClient = null;
143
146
  let shakeDetector = null;
144
147
  /** Unsubscribe for the BlackBox command handler registered by init(). Held so
145
148
  * a re-init (or destroy) can drop the old one instead of stacking. */
@@ -151,6 +154,7 @@ let p2pRelayPassword = '';
151
154
  let autoStartTimer = null;
152
155
  let reportLaunchInFlight = false;
153
156
  let crashReportInFlight = false;
157
+ let dogfoodOnboarding = null;
154
158
  /** Resolve the user's relay password by validating their auth token
155
159
  * against Convex. Used whenever we (re)build the P2PClient so a
156
160
  * relay-routed agentUrl carries a valid X-Relay-Password — without
@@ -218,40 +222,37 @@ const flagCache = new Map();
218
222
  */
219
223
  class YaverFeedback {
220
224
  static async resolveP2PAuthToken() {
221
- if (!config?.authToken)
222
- return null;
223
- if (!config.preferredDeviceId)
224
- return config.authToken;
225
- const devices = await (0, auth_1.listReachableDevices)(config.authToken);
226
- const all = [...devices.owned, ...devices.shared];
227
- const selected = all.find((device) => device.deviceId === config?.preferredDeviceId);
228
- if (!selected || !selected.isGuest || selected.accessScope !== 'shared-scoped') {
229
- return config.authToken;
230
- }
231
- if (!selected.hostUserId) {
232
- return config.authToken;
233
- }
234
- const delegated = await (0, auth_1.mintGuestSdkToken)(config.authToken, selected.hostUserId, selected.deviceId);
235
- return delegated.token;
225
+ return config?.authToken ?? null;
236
226
  }
237
227
  static async rebuildP2PClient(agentUrl) {
238
- if (!config)
228
+ const currentConfig = config;
229
+ if (!currentConfig)
239
230
  return;
240
- const effectiveUrl = agentUrl ?? config.agentUrl;
231
+ const effectiveUrl = agentUrl ?? currentConfig.agentUrl;
241
232
  if (!effectiveUrl) {
242
233
  p2pClient = null;
234
+ renderP2PClient = null;
243
235
  p2pAuthToken = null;
244
236
  return;
245
237
  }
246
238
  const token = await YaverFeedback.resolveP2PAuthToken();
239
+ // reset() can clear config while relay-password resolution is awaiting.
240
+ // Do not resurrect a client from that stale init generation.
241
+ if (config !== currentConfig)
242
+ return;
247
243
  if (!token) {
248
244
  p2pClient = null;
245
+ renderP2PClient = null;
249
246
  p2pAuthToken = null;
250
247
  return;
251
248
  }
252
249
  p2pAuthToken = token;
253
250
  const rp = await resolveRelayPassword(token);
251
+ if (config !== currentConfig)
252
+ return;
254
253
  p2pClient = new P2PClient_1.P2PClient(effectiveUrl, token, rp);
254
+ const renderUrl = currentConfig.renderAgentUrl || effectiveUrl;
255
+ renderP2PClient = renderUrl === effectiveUrl ? p2pClient : new P2PClient_1.P2PClient(renderUrl, token, rp);
255
256
  }
256
257
  /**
257
258
  * Initialize the feedback SDK with the given configuration.
@@ -378,6 +379,9 @@ class YaverFeedback {
378
379
  maxRecordingDuration: 120,
379
380
  autoLogin: true,
380
381
  ...cfg,
382
+ agentUrl: cfg.codingAgentUrl || cfg.agentUrl,
383
+ codingDeviceId: cfg.codingDeviceId || cfg.preferredDeviceId,
384
+ renderDeviceId: cfg.renderDeviceId || cfg.codingDeviceId || cfg.preferredDeviceId,
381
385
  };
382
386
  if (IS_HOST_MODE) {
383
387
  // sfmg / talos / etc. running inside Yaver mobile (compile-time
@@ -451,12 +455,17 @@ class YaverFeedback {
451
455
  // is set, so set a placeholder header here that won't 401 a
452
456
  // direct-LAN url and will be overwritten before any relay hop.
453
457
  p2pClient = new P2PClient_1.P2PClient(config.agentUrl, config.authToken ?? '', p2pRelayPassword);
458
+ const renderUrl = config.renderAgentUrl || config.agentUrl;
459
+ renderP2PClient = renderUrl === config.agentUrl
460
+ ? p2pClient
461
+ : new P2PClient_1.P2PClient(renderUrl, config.authToken ?? '', p2pRelayPassword);
454
462
  if (config.authToken) {
455
463
  void YaverFeedback.rebuildP2PClient(config.agentUrl);
456
464
  }
457
465
  }
458
466
  else {
459
467
  p2pClient = null;
468
+ renderP2PClient = null;
460
469
  // Auto-discover agent in the background when convexUrl or LAN is available
461
470
  if (enabled && (config.authToken || config.preferredDeviceId)) {
462
471
  YaverFeedback.discoverAgent();
@@ -803,6 +812,25 @@ class YaverFeedback {
803
812
  const { DeviceEventEmitter } = require('react-native');
804
813
  DeviceEventEmitter.emit('yaverFeedback:startMachinePicker');
805
814
  }
815
+ /** Begin the reusable host-app Dogfood wizard. Owner OAuth and machine
816
+ * selection intentionally happen before installation enrollment/runtime
817
+ * controls because starting builds or runners is owner-level authority. */
818
+ static beginDogfoodOnboarding(options) {
819
+ dogfoodOnboarding = options;
820
+ if (!config)
821
+ YaverFeedback.init({ autoLogin: true });
822
+ if (!YaverFeedback.isAuthed()) {
823
+ YaverFeedback.showLogin();
824
+ return;
825
+ }
826
+ YaverFeedback.showMachinePicker();
827
+ }
828
+ static getDogfoodOnboarding() {
829
+ return dogfoodOnboarding;
830
+ }
831
+ static clearDogfoodOnboarding() {
832
+ dogfoodOnboarding = null;
833
+ }
806
834
  /**
807
835
  * Update the selected remote device. Resets the cached agent URL so the
808
836
  * next `startReport()` (or FloatingButton press) rediscovers against the
@@ -811,9 +839,19 @@ class YaverFeedback {
811
839
  static async setPreferredDevice(deviceId) {
812
840
  if (!config)
813
841
  return;
842
+ const wasUnified = !config.renderDeviceId
843
+ || config.renderDeviceId === config.codingDeviceId
844
+ || config.renderDeviceId === config.preferredDeviceId;
814
845
  config.preferredDeviceId = deviceId;
846
+ config.codingDeviceId = deviceId;
847
+ if (wasUnified)
848
+ config.renderDeviceId = deviceId;
815
849
  config.agentUrl = undefined;
850
+ config.codingAgentUrl = undefined;
851
+ if (wasUnified)
852
+ config.renderAgentUrl = undefined;
816
853
  p2pClient = null;
854
+ renderP2PClient = null;
817
855
  p2pAuthToken = null;
818
856
  await YaverFeedback.discoverAgent();
819
857
  }
@@ -823,8 +861,7 @@ class YaverFeedback {
823
861
  return null;
824
862
  const preferredDeviceId = config.preferredDeviceId;
825
863
  const devices = await (0, auth_1.listReachableDevices)(config.authToken);
826
- const all = [...devices.owned, ...devices.shared];
827
- return all.find((device) => device.deviceId === preferredDeviceId) ?? null;
864
+ return devices.owned.find((device) => device.deviceId === preferredDeviceId) ?? null;
828
865
  }
829
866
  /**
830
867
  * Trigger remote device-auth for a CLI runner on the selected agent
@@ -874,6 +911,7 @@ class YaverFeedback {
874
911
  config.agentUrl = undefined;
875
912
  }
876
913
  p2pClient = null;
914
+ renderP2PClient = null;
877
915
  p2pAuthToken = null;
878
916
  }
879
917
  /**
@@ -1119,6 +1157,61 @@ class YaverFeedback {
1119
1157
  static getConfig() {
1120
1158
  return config;
1121
1159
  }
1160
+ /** Current third-party SDK mode. Fails closed unless enabled + account match. */
1161
+ static getDogfoodStatus() {
1162
+ return (0, dogfoodPolicy_1.resolveSDKDogfood)(config?.dogfood);
1163
+ }
1164
+ /** One-call, account-free Dogfood bootstrap for third-party apps. On first
1165
+ * launch it creates/proves the installation key and returns pending; after
1166
+ * owner approval the same call obtains a short-lived scoped Yaver session
1167
+ * and enables Dogfood UX without the host app implementing OAuth. */
1168
+ static async enableDeviceDogfood(options) {
1169
+ const client = new deviceDogfood_1.YaverDeviceDogfood(options);
1170
+ let status = await client.status();
1171
+ if (status === 'unregistered' || status === 'pending' || status === 'cancelled' || status === 'revoked' || status === 'superseded') {
1172
+ const enrolled = status === 'unregistered' || status === 'pending'
1173
+ ? await client.enroll(react_native_1.Platform.OS)
1174
+ : await client.reRegister(react_native_1.Platform.OS);
1175
+ status = enrolled.status;
1176
+ }
1177
+ const info = await client.enrollmentInfo();
1178
+ const session = status === 'active' ? await client.session() : null;
1179
+ if (!config)
1180
+ YaverFeedback.init({ autoLogin: false });
1181
+ if (config) {
1182
+ config.dogfood = {
1183
+ enabled: status === 'active' && !!session,
1184
+ appId: options.appId,
1185
+ installationId: info.installationId,
1186
+ installationStatus: status === 'active' && session ? 'active' : status === 'unregistered' ? 'pending' : status,
1187
+ label: options.label,
1188
+ };
1189
+ // A normal Yaver OAuth session remains authoritative for the runtime
1190
+ // wizard. Only account-free hosts adopt the narrow installation token.
1191
+ if (session && !config.authToken)
1192
+ await YaverFeedback.setAuthToken(session.token);
1193
+ }
1194
+ try {
1195
+ const { DeviceEventEmitter } = require('react-native');
1196
+ DeviceEventEmitter.emit('yaverFeedback:dogfoodChanged', { active: !!session, status });
1197
+ }
1198
+ catch { /* noop */ }
1199
+ return { status, installationId: info.installationId, session };
1200
+ }
1201
+ /** Confirmed by the Y badge/UI before this is called. Reverts this SDK to
1202
+ * normal Feedback mode for the remainder of the app run. */
1203
+ static async exitDogfoodMode() {
1204
+ const cfg = config?.dogfood;
1205
+ if (!cfg)
1206
+ return;
1207
+ cfg.enabled = false;
1208
+ await cfg.onExit?.();
1209
+ try {
1210
+ const { DeviceEventEmitter } = require('react-native');
1211
+ DeviceEventEmitter.emit('yaverFeedback:dogfoodChanged', { active: false, exited: true });
1212
+ }
1213
+ catch { /* noop */ }
1214
+ }
1122
1215
  /** Returns the resolved relay password the SDK is currently using.
1123
1216
  * Empty string when no relay routing is in play (direct LAN agent
1124
1217
  * URLs need no password). Callers attaching it to relay-routed
@@ -1286,6 +1379,16 @@ class YaverFeedback {
1286
1379
  static getP2PClient() {
1287
1380
  return p2pClient;
1288
1381
  }
1382
+ /** Renderer/reload route; intentionally distinct from the coding client. */
1383
+ static getRenderP2PClient() {
1384
+ return renderP2PClient ?? p2pClient;
1385
+ }
1386
+ static getMachineRouting() {
1387
+ return {
1388
+ codingDeviceId: config?.codingDeviceId || config?.preferredDeviceId,
1389
+ renderDeviceId: config?.renderDeviceId || config?.codingDeviceId || config?.preferredDeviceId,
1390
+ };
1391
+ }
1289
1392
  // ─── One-stop SaaS replacement methods ─────────────────────────
1290
1393
  //
1291
1394
  // These are the three solo-dev SaaS-replacement entry points
@@ -1396,6 +1499,7 @@ class YaverFeedback {
1396
1499
  config.agentUrl = result.url;
1397
1500
  const rp = await resolveRelayPassword(config.authToken ?? '');
1398
1501
  p2pClient = new P2PClient_1.P2PClient(result.url, config.authToken ?? '', rp);
1502
+ renderP2PClient = p2pClient;
1399
1503
  }
1400
1504
  }
1401
1505
  catch { }
@@ -1610,6 +1714,7 @@ class YaverFeedback {
1610
1714
  enabled = false;
1611
1715
  config = null;
1612
1716
  p2pClient = null;
1717
+ renderP2PClient = null;
1613
1718
  errorBuffer = [];
1614
1719
  }
1615
1720
  }
@@ -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 {};