yaver-feedback-react-native 0.9.2 → 0.9.4

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 (108) 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/ShakeDetector.js +31 -17
  18. package/dist/VibeChatScreen.d.ts +11 -1
  19. package/dist/VibeChatScreen.js +200 -41
  20. package/dist/YaverFeedback.d.ts +34 -0
  21. package/dist/YaverFeedback.js +124 -19
  22. package/dist/YaverModeBadge.d.ts +22 -0
  23. package/dist/YaverModeBadge.js +219 -0
  24. package/dist/__tests__/AuthDevices.test.js +1 -48
  25. package/dist/__tests__/DogfoodRuntime.test.d.ts +1 -0
  26. package/dist/__tests__/DogfoodRuntime.test.js +116 -0
  27. package/dist/__tests__/P2PDogfoodDriver.test.d.ts +1 -0
  28. package/dist/__tests__/P2PDogfoodDriver.test.js +64 -0
  29. package/dist/__tests__/ReportIdentity.test.d.ts +25 -1
  30. package/dist/__tests__/ReportIdentity.test.js +34 -22
  31. package/dist/__tests__/ShakeDetectorWeb.test.d.ts +1 -0
  32. package/dist/__tests__/ShakeDetectorWeb.test.js +27 -0
  33. package/dist/__tests__/YaverFeedback.test.js +13 -3
  34. package/dist/__tests__/deviceDogfood.test.d.ts +1 -0
  35. package/dist/__tests__/deviceDogfood.test.js +86 -0
  36. package/dist/__tests__/dogfoodPolicy.test.d.ts +1 -0
  37. package/dist/__tests__/dogfoodPolicy.test.js +33 -0
  38. package/dist/_core/ansi.d.ts +117 -0
  39. package/dist/_core/ansi.js +468 -0
  40. package/dist/_core/ansi.test.d.ts +1 -0
  41. package/dist/_core/ansi.test.js +225 -0
  42. package/dist/_core/buildFeedbackPrompt.d.ts +4 -9
  43. package/dist/_core/buildFeedbackPrompt.js +18 -72
  44. package/dist/_core/constants.d.ts +19 -6
  45. package/dist/_core/constants.js +20 -7
  46. package/dist/_core/device.d.ts +9 -23
  47. package/dist/_core/device.js +13 -28
  48. package/dist/_core/endpoints.d.ts +0 -7
  49. package/dist/_core/endpoints.js +0 -7
  50. package/dist/_core/index.d.ts +4 -0
  51. package/dist/_core/index.js +4 -0
  52. package/dist/_core/remoteless.d.ts +44 -0
  53. package/dist/_core/remoteless.js +75 -0
  54. package/dist/_core/trace.d.ts +47 -0
  55. package/dist/_core/trace.js +38 -0
  56. package/dist/_core/trace.test.d.ts +1 -0
  57. package/dist/_core/trace.test.js +60 -0
  58. package/dist/auth.d.ts +3 -60
  59. package/dist/auth.js +6 -88
  60. package/dist/deviceDogfood.d.ts +53 -0
  61. package/dist/deviceDogfood.js +137 -0
  62. package/dist/dogfoodPolicy.d.ts +25 -0
  63. package/dist/dogfoodPolicy.js +24 -0
  64. package/dist/index.d.ts +13 -4
  65. package/dist/index.js +24 -8
  66. package/dist/reloadActions.js +2 -2
  67. package/dist/types.d.ts +50 -7
  68. package/package.json +12 -3
  69. package/src/AuthOverlay.tsx +20 -106
  70. package/src/Discovery.ts +0 -6
  71. package/src/DogfoodRuntime.ts +373 -0
  72. package/src/FeedbackModal.tsx +445 -67
  73. package/src/LoginScreen.tsx +2 -22
  74. package/src/MachinePickerScreen.tsx +6 -21
  75. package/src/P2PClient.ts +347 -4
  76. package/src/P2PDogfoodDriver.ts +132 -0
  77. package/src/PairDeviceModal.tsx +2 -3
  78. package/src/ShakeDetector.ts +31 -18
  79. package/src/VibeChatScreen.tsx +232 -42
  80. package/src/YaverFeedback.ts +133 -22
  81. package/src/YaverModeBadge.tsx +234 -0
  82. package/src/__tests__/AuthDevices.test.ts +1 -52
  83. package/src/__tests__/DogfoodRuntime.test.ts +135 -0
  84. package/src/__tests__/P2PDogfoodDriver.test.ts +80 -0
  85. package/src/__tests__/ReportIdentity.test.ts +36 -27
  86. package/src/__tests__/ShakeDetectorWeb.test.ts +30 -0
  87. package/src/__tests__/YaverFeedback.test.ts +15 -3
  88. package/src/__tests__/deviceDogfood.test.ts +77 -0
  89. package/src/__tests__/dogfoodPolicy.test.ts +34 -0
  90. package/src/_core/ansi.test.ts +250 -0
  91. package/src/_core/ansi.ts +475 -0
  92. package/src/_core/buildFeedbackPrompt.ts +18 -95
  93. package/src/_core/constants.ts +20 -6
  94. package/src/_core/device.ts +13 -30
  95. package/src/_core/endpoints.ts +0 -7
  96. package/src/_core/index.ts +4 -0
  97. package/src/_core/remoteless.ts +110 -0
  98. package/src/_core/trace.test.ts +58 -0
  99. package/src/_core/trace.ts +75 -0
  100. package/src/auth.ts +7 -156
  101. package/src/deviceDogfood.ts +171 -0
  102. package/src/dogfoodPolicy.ts +40 -0
  103. package/src/index.ts +40 -11
  104. package/src/reloadActions.ts +2 -2
  105. package/src/types.ts +45 -7
  106. package/dist/GuestOnboardingScreen.d.ts +0 -8
  107. package/dist/GuestOnboardingScreen.js +0 -282
  108. package/src/GuestOnboardingScreen.tsx +0 -307
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ // AUTO-SYNCED from shared/client-core/src/remoteless.ts.
3
+ // DO NOT EDIT IN PLACE. Edit the source and re-run
4
+ // scripts/sync-client-core.sh. CI checks drift via `--check`.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.remotelessCapability = remotelessCapability;
7
+ exports.resolveRemotelessPlacement = resolveRemotelessPlacement;
8
+ const LOCAL_SUPPORT = {
9
+ "analysis-chat": "supported", "code-edit": "bounded", "git-read": "supported", "git-commit": "bounded",
10
+ "git-push": "bounded", "static-preflight": "supported",
11
+ "existing-web-artifact": "supported", "dev-server": "unavailable",
12
+ "web-build": "unavailable", "flutter-render": "unavailable",
13
+ "native-build": "unavailable", simulator: "unavailable", shell: "unavailable",
14
+ test: "unavailable", deploy: "unavailable", container: "unavailable",
15
+ };
16
+ const LABELS = {
17
+ "analysis-chat": "analysis and chat", "code-edit": "editing", "git-read": "Git inspection", "git-commit": "Git commit",
18
+ "git-push": "Git push", "static-preflight": "static preflight",
19
+ "existing-web-artifact": "an existing web artifact", "dev-server": "a dev server",
20
+ "web-build": "a web build", "flutter-render": "Flutter rendering",
21
+ "native-build": "a native build", simulator: "a simulator", shell: "shell commands",
22
+ test: "tests", deploy: "deployment", container: "containers",
23
+ };
24
+ function remotelessCapability(capability, surface) {
25
+ const support = capability === "analysis-chat" ? "supported" : surface === "ios" || surface === "android"
26
+ ? LOCAL_SUPPORT[capability]
27
+ : capability === "existing-web-artifact" ? "supported" : "unavailable";
28
+ const label = LABELS[capability];
29
+ const host = surface === "ios" ? "iPhone/iPad" : surface === "android" ? "Android device" : surface === "web" ? "browser" : "companion device";
30
+ if (support === "supported")
31
+ return {
32
+ capability, support, code: `remoteless.${capability}.supported`,
33
+ summary: `${label} can run on this device`,
34
+ detail: `${label} does not require a remote shell or build toolchain.`,
35
+ route: { label: "Choose a device", path: "/devices" },
36
+ };
37
+ if (support === "bounded")
38
+ return {
39
+ capability, support, code: `remoteless.${capability}.bounded`,
40
+ summary: `${label} is available on this device with limits`,
41
+ detail: `${label} can run locally, but background execution is bounded and interrupted work returns to Review.`,
42
+ route: { label: "Choose a device", path: "/devices" },
43
+ };
44
+ return {
45
+ capability, support, code: `remoteless.${capability}.unavailable`,
46
+ summary: `${label} needs another execution target`,
47
+ detail: capability === "flutter-render"
48
+ ? `The ${host} can display an already-built Flutter web artifact, but Yaver's remoteless runtime has no Flutter SDK, shell, dev server, or simulator. Build and serve it on your primary/secondary device or Cloud Workspace.`
49
+ : `Yaver's remoteless runtime on this ${host} cannot provide ${label}: it has no general shell, package manager, persistent process host, simulator, native SDK, or container runtime. Use an eligible primary/secondary device or Cloud Workspace.`,
50
+ route: { label: "Choose a capable device", path: "/devices" },
51
+ alternateRoute: { label: "Use Cloud Workspace", path: "/cloud-onboarding" },
52
+ };
53
+ }
54
+ function resolveRemotelessPlacement(input) {
55
+ const capability = remotelessCapability(input.capability, input.surface);
56
+ if (!input.forceLocal) {
57
+ // Enforce precedence here instead of trusting every surface to construct
58
+ // its array correctly. Within a role, caller order remains significant
59
+ // (for example project-assigned primary before account-wide primary).
60
+ const target = ["explicit", "primary", "secondary", "focused"]
61
+ .map((role) => input.candidates.find((candidate) => candidate.role === role && candidate.connected))
62
+ .find((candidate) => !!candidate);
63
+ if (target) {
64
+ const degraded = target.role === "secondary";
65
+ return { lane: "remote", target, degraded, banner: degraded ? `Using secondary · ${target.name} · primary unavailable` : null };
66
+ }
67
+ }
68
+ const configured = input.candidates.filter((candidate) => candidate.role !== "focused");
69
+ const reason = configured.length ? "Primary and secondary devices are unavailable." : "No eligible remote device is configured.";
70
+ if (capability.support === "unavailable")
71
+ return { lane: "blocked", capability, banner: `${reason} ${capability.summary}.` };
72
+ if (input.forceLocal)
73
+ return { lane: "remoteless", capability, banner: `No remote box selected · ${capability.summary}.` };
74
+ return { lane: "remoteless", capability, banner: `Remoteless fallback · ${reason} ${capability.summary}.` };
75
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * trace.ts — one shared paste-ready trace assembler for every surface.
3
+ *
4
+ * WHY (2026-08-09): a crash/failure on web, mobile, or the console used to
5
+ * copy different things — RawFailureBanner copied just `failure.raw`, the
6
+ * mobile Logs sheet copied `combinedLogText`, RuntimeLab copied raw console
7
+ * lines. None carried the surface identity, versions, task id, runner/model,
8
+ * or the log tail, so pasting into an issue or a vibing follow-up lost the
9
+ * context that makes a bug reproducible. One assembler here gives every
10
+ * surface the SAME structured blob (surface → versions → device/relay →
11
+ * task → error → log tail), so web and mobile can never drift
12
+ * (AGENTS.md: one shared classifier, no copies). Mirrored into
13
+ * mobile/src/_core and web/lib/_core by scripts/sync-client-core.sh.
14
+ */
15
+ export interface TraceContext {
16
+ /** Surface name: "web" | "mobile" | "console" | "cli" | "watch" ... */
17
+ surface: string;
18
+ /** e.g. web build label, mobile JS bundle version, cli version. */
19
+ surfaceVersion?: string;
20
+ /** Agent version reported by /info (e.g. "1.99.409"). */
21
+ agentVersion?: string;
22
+ /** Device the task ran on (name + id). */
23
+ device?: string;
24
+ /** Relay region / id if known. */
25
+ relay?: string;
26
+ /** Task identity + lifecycle. */
27
+ task?: {
28
+ id: string;
29
+ status?: string;
30
+ runner?: string;
31
+ model?: string;
32
+ title?: string;
33
+ };
34
+ /** The human-readable error (already extracted/named). */
35
+ error?: string;
36
+ /** Raw failure blob (the undecorated original). */
37
+ raw?: string;
38
+ /** Log tail — last N lines of the relevant log (agent/relay/runtime). */
39
+ logTail?: string;
40
+ /** When the trace was captured (ms epoch). */
41
+ ts?: number;
42
+ }
43
+ /**
44
+ * Assemble a paste-ready trace. Every present field becomes one labelled
45
+ * line; nothing is ever invented. Returns a plain-text blob.
46
+ */
47
+ export declare function assembleTrace(ctx: TraceContext): string;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ // AUTO-SYNCED from shared/client-core/src/trace.ts.
3
+ // DO NOT EDIT IN PLACE. Edit the source and re-run
4
+ // scripts/sync-client-core.sh. CI checks drift via `--check`.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.assembleTrace = assembleTrace;
7
+ const MASK = /token\s*[=:]\s*\S+|Bearer\s+\S+|api[_-]?key\s*[=:]\s*\S+|password\s*[=:]\s*\S+/gi;
8
+ function sanitize(s) {
9
+ return String(s || "").replace(MASK, "[redacted]");
10
+ }
11
+ /**
12
+ * Assemble a paste-ready trace. Every present field becomes one labelled
13
+ * line; nothing is ever invented. Returns a plain-text blob.
14
+ */
15
+ function assembleTrace(ctx) {
16
+ const out = [];
17
+ out.push("--- Yaver trace ---");
18
+ out.push(`surface: ${sanitize(ctx.surface)}`);
19
+ if (ctx.surfaceVersion)
20
+ out.push(`surface.version: ${sanitize(ctx.surfaceVersion)}`);
21
+ if (ctx.agentVersion)
22
+ out.push(`agent.version: ${sanitize(ctx.agentVersion)}`);
23
+ if (ctx.device)
24
+ out.push(`device: ${sanitize(ctx.device)}`);
25
+ if (ctx.relay)
26
+ out.push(`relay: ${sanitize(ctx.relay)}`);
27
+ if (ctx.task) {
28
+ out.push(`task: ${sanitize(ctx.task.id)}${ctx.task.status ? ` status=${sanitize(ctx.task.status)}` : ""}${ctx.task.runner ? ` runner=${sanitize(ctx.task.runner)}` : ""}${ctx.task.model ? ` model=${sanitize(ctx.task.model)}` : ""}${ctx.task.title ? ` title=${sanitize(ctx.task.title)}` : ""}`);
29
+ }
30
+ if (ctx.error)
31
+ out.push(`error: ${sanitize(ctx.error)}`);
32
+ if (ctx.raw)
33
+ out.push(`raw: ${sanitize(ctx.raw)}`);
34
+ if (ctx.logTail)
35
+ out.push(`log-tail:\n${sanitize(ctx.logTail)}`);
36
+ out.push(`ts: ${ctx.ts ?? Date.now()}`);
37
+ return out.join("\n");
38
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ // AUTO-SYNCED from shared/client-core/src/trace.test.ts.
3
+ // DO NOT EDIT IN PLACE. Edit the source and re-run
4
+ // scripts/sync-client-core.sh. CI checks drift via `--check`.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ /**
7
+ * trace.test.ts — guards for the shared trace assembler.
8
+ *
9
+ * Run: npx tsx shared/client-core/src/trace.test.ts
10
+ */
11
+ const trace_1 = require("./trace");
12
+ let failures = 0;
13
+ const eq = (got, want, label) => {
14
+ if (JSON.stringify(got) === JSON.stringify(want))
15
+ console.log(`ok ${label}`);
16
+ else {
17
+ console.error(`FAIL ${label}:\n got ${JSON.stringify(got)}\n want ${JSON.stringify(want)}`);
18
+ failures++;
19
+ }
20
+ };
21
+ const ok = (c, label) => eq(Boolean(c), true, label);
22
+ {
23
+ const t = (0, trace_1.assembleTrace)({
24
+ surface: "web",
25
+ surfaceVersion: "1.1.164",
26
+ agentVersion: "1.99.409",
27
+ device: "ubuntu-4gb-hel1-1 (2ed7da41…)",
28
+ relay: "public-free",
29
+ task: { id: "abc123", status: "failed", runner: "opencode", model: "deepseek/deepseek-v4-flash", title: "build" },
30
+ error: "flutter exited before becoming ready",
31
+ raw: "the raw failure bytes",
32
+ logTail: "line1\nline2",
33
+ ts: 1700000000000,
34
+ });
35
+ ok(t.includes("surface: web"), "surface line present");
36
+ ok(t.includes("agent.version: 1.99.409"), "agent version present");
37
+ ok(t.includes("task: abc123 status=failed runner=opencode"), "task line present");
38
+ ok(t.includes("error: flutter exited before becoming ready"), "error present");
39
+ ok(t.includes("log-tail:\nline1\nline2"), "log tail present under its label");
40
+ ok(t.includes("ts: 1700000000000"), "timestamp present");
41
+ }
42
+ {
43
+ // No invented fields when absent.
44
+ const t = (0, trace_1.assembleTrace)({ surface: "mobile", task: { id: "x" } });
45
+ ok(!t.includes("agent.version"), "no agent.version when absent");
46
+ ok(!t.includes("error:"), "no error when absent");
47
+ ok(t.includes("surface: mobile"), "surface still present");
48
+ }
49
+ {
50
+ // Secrets are redacted.
51
+ const t = (0, trace_1.assembleTrace)({ surface: "web", error: "failed with token=abc123xyz" });
52
+ ok(t.includes("[redacted]") && !t.includes("abc123xyz"), "token redacted from error");
53
+ const t2 = (0, trace_1.assembleTrace)({ surface: "mobile", raw: "Authorization: Bearer deadbeef" });
54
+ ok(t2.includes("[redacted]") && !t2.includes("deadbeef"), "bearer redacted from raw");
55
+ }
56
+ if (failures > 0) {
57
+ console.error(`\n${failures} FAILURE(s)`);
58
+ process.exit(1);
59
+ }
60
+ console.log("\nall trace tests pass");
package/dist/auth.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * (`yaver://oauth-callback`) the Yaver mobile app uses.
11
11
  * - Email / password sign-up + login (no 2FA flow — for SDK simplicity).
12
12
  * - Token validation.
13
- * - `/devices/list` → owned + shared (guest) remote dev machines.
13
+ * - `/devices/list` → owner remote dev machines.
14
14
  *
15
15
  * Mobile-only. A web equivalent will ship as a separate `yaver-web-feedback`
16
16
  * package; do not import this module from a browser bundle.
@@ -99,12 +99,6 @@ export interface RemoteDevice {
99
99
  needsAuth: boolean;
100
100
  runnerDown: boolean;
101
101
  lastHeartbeat: number;
102
- isGuest: boolean;
103
- hostUserId?: string;
104
- hostName?: string;
105
- hostEmail?: string;
106
- hostUserIdString?: string;
107
- accessScope: 'owner' | 'shared-scoped' | 'shared-legacy';
108
102
  quicHost: string;
109
103
  quicPort: number;
110
104
  /** Agent HTTP port — preferred over quicPort when present. */
@@ -121,53 +115,14 @@ export interface RemoteDevice {
121
115
  }
122
116
  export interface DeviceList {
123
117
  owned: RemoteDevice[];
124
- shared: RemoteDevice[];
125
118
  }
126
119
  export interface DeviceReachability {
127
120
  reachable: boolean;
128
121
  url?: string;
129
122
  }
130
- export interface GuestInvitation {
131
- hostUserId: string;
132
- hostName: string;
133
- hostEmail: string;
134
- hostUserIdString?: string;
135
- createdAt: number;
136
- expiresAt: number;
137
- inviteCode?: string;
138
- }
139
- export interface ActiveGuestHost {
140
- hostUserId: string;
141
- hostName: string;
142
- hostEmail: string;
143
- grantedAt: number;
144
- }
145
- export interface GuestHostsResponse {
146
- pending: GuestInvitation[];
147
- active: ActiveGuestHost[];
148
- }
149
- export interface InvitationHostDevice {
150
- deviceId: string;
151
- name: string;
152
- platform: string;
153
- lastHeartbeat?: number;
154
- proposed: boolean;
155
- }
156
- export interface InvitationPreview {
157
- inviteCode: string;
158
- hostUserId: string;
159
- hostName: string;
160
- hostEmail: string;
161
- hostUserIdString?: string;
162
- proposedDeviceIds?: string[];
163
- hostDevices: InvitationHostDevice[];
164
- invitedByUserId?: boolean;
165
- expiresAt: number;
166
- createdAt: number;
167
- }
168
123
  /**
169
- * Fetch the set of remote dev machines this user can reach. Splits into
170
- * owned (user is the host) vs shared (host invited them as a guest).
124
+ * Fetch the signed-in owner's remote dev machines. Legacy non-owner rows are
125
+ * discarded even if a stale backend still returns them.
171
126
  *
172
127
  * Collapses duplicate rows before splitting — Convex can return multiple
173
128
  * rows per physical machine after a re-pair or hostname change, and the
@@ -176,15 +131,3 @@ export interface InvitationPreview {
176
131
  export declare function listReachableDevices(token: string): Promise<DeviceList>;
177
132
  export declare function buildDeviceCandidateUrls(device: RemoteDevice): string[];
178
133
  export declare function probeDeviceReachability(device: RemoteDevice, timeoutMs?: number): Promise<DeviceReachability>;
179
- export declare function mintGuestSdkToken(token: string, hostUserId: string, targetDeviceId: string): Promise<{
180
- token: string;
181
- expiresAt: number;
182
- allowedProjects?: string[];
183
- }>;
184
- export declare function fetchGuestHosts(token: string): Promise<GuestHostsResponse>;
185
- export declare function findInviteByCode(token: string, code: string): Promise<InvitationPreview | null>;
186
- export declare function acceptGuestByCode(token: string, code: string, approvedDeviceIds?: string[]): Promise<{
187
- hostName: string;
188
- hostEmail: string;
189
- }>;
190
- export declare function acceptGuestInvitation(token: string, hostUserId: string, approvedDeviceIds?: string[]): Promise<void>;
package/dist/auth.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * (`yaver://oauth-callback`) the Yaver mobile app uses.
12
12
  * - Email / password sign-up + login (no 2FA flow — for SDK simplicity).
13
13
  * - Token validation.
14
- * - `/devices/list` → owned + shared (guest) remote dev machines.
14
+ * - `/devices/list` → owner remote dev machines.
15
15
  *
16
16
  * Mobile-only. A web equivalent will ship as a separate `yaver-web-feedback`
17
17
  * package; do not import this module from a browser bundle.
@@ -39,11 +39,6 @@ exports.loginWithEmail = loginWithEmail;
39
39
  exports.listReachableDevices = listReachableDevices;
40
40
  exports.buildDeviceCandidateUrls = buildDeviceCandidateUrls;
41
41
  exports.probeDeviceReachability = probeDeviceReachability;
42
- exports.mintGuestSdkToken = mintGuestSdkToken;
43
- exports.fetchGuestHosts = fetchGuestHosts;
44
- exports.findInviteByCode = findInviteByCode;
45
- exports.acceptGuestByCode = acceptGuestByCode;
46
- exports.acceptGuestInvitation = acceptGuestInvitation;
47
42
  // AsyncStorage is an optional peer dep — degrade gracefully if missing.
48
43
  let AsyncStorage = null;
49
44
  try {
@@ -358,8 +353,8 @@ async function loginWithEmail(email, password) {
358
353
  return { token: data.token, userId: data.userId };
359
354
  }
360
355
  /**
361
- * Fetch the set of remote dev machines this user can reach. Splits into
362
- * owned (user is the host) vs shared (host invited them as a guest).
356
+ * Fetch the signed-in owner's remote dev machines. Legacy non-owner rows are
357
+ * discarded even if a stale backend still returns them.
363
358
  *
364
359
  * Collapses duplicate rows before splitting — Convex can return multiple
365
360
  * rows per physical machine after a re-pair or hostname change, and the
@@ -371,7 +366,7 @@ async function listReachableDevices(token) {
371
366
  headers: { Authorization: `Bearer ${token}` },
372
367
  });
373
368
  if (!res.ok)
374
- return { owned: [], shared: [] };
369
+ return { owned: [] };
375
370
  const data = await res.json();
376
371
  const raw = (data.devices ?? []);
377
372
  // Normalise Convex field names → SDK's RemoteDevice shape. The
@@ -385,12 +380,6 @@ async function listReachableDevices(token) {
385
380
  needsAuth: !!d.needsAuth,
386
381
  runnerDown: !!d.runnerDown,
387
382
  lastHeartbeat: d.lastHeartbeat ?? 0,
388
- isGuest: !!d.isGuest,
389
- hostUserId: d.hostUserId,
390
- hostName: d.hostName,
391
- hostEmail: d.hostEmail,
392
- hostUserIdString: d.hostUserIdString,
393
- accessScope: d.accessScope ?? 'owner',
394
383
  quicHost: d.quicHost ?? d.host ?? '',
395
384
  quicPort: d.quicPort ?? 0,
396
385
  httpPort: d.httpPort ?? d.quicPort,
@@ -405,13 +394,10 @@ async function listReachableDevices(token) {
405
394
  // Lazy require so Jest + tree-shakers don't choke on a circular import.
406
395
  const { collapseRemoteDevices } = require('./deviceDedup');
407
396
  const deduped = collapseRemoteDevices(normalised);
408
- return {
409
- owned: deduped.filter((d) => !d.isGuest),
410
- shared: deduped.filter((d) => d.isGuest),
411
- };
397
+ return { owned: deduped };
412
398
  }
413
399
  catch {
414
- return { owned: [], shared: [] };
400
+ return { owned: [] };
415
401
  }
416
402
  }
417
403
  function buildDeviceCandidateUrls(device) {
@@ -453,71 +439,3 @@ async function probeDeviceReachability(device, timeoutMs = 2500) {
453
439
  }
454
440
  return { reachable: false };
455
441
  }
456
- async function mintGuestSdkToken(token, hostUserId, targetDeviceId) {
457
- const res = await fetch(`${convexSiteUrl}/guests/sdk-token`, {
458
- method: 'POST',
459
- headers: {
460
- Authorization: `Bearer ${token}`,
461
- 'Content-Type': 'application/json',
462
- },
463
- body: JSON.stringify({ hostUserId, targetDeviceId }),
464
- });
465
- if (!res.ok) {
466
- const data = await res.json().catch(() => ({}));
467
- throw new Error(data.error || 'Failed to mint delegated SDK token');
468
- }
469
- return res.json();
470
- }
471
- async function fetchGuestHosts(token) {
472
- const res = await fetch(`${convexSiteUrl}/guests/hosts`, {
473
- headers: { Authorization: `Bearer ${token}` },
474
- });
475
- if (!res.ok) {
476
- const data = await res.json().catch(() => ({}));
477
- throw new Error(data.error || 'Failed to fetch guest hosts');
478
- }
479
- return res.json();
480
- }
481
- async function findInviteByCode(token, code) {
482
- const cleaned = code.toUpperCase().trim();
483
- const res = await fetch(`${convexSiteUrl}/guests/find-by-code?code=${encodeURIComponent(cleaned)}`, { headers: { Authorization: `Bearer ${token}` } });
484
- if (res.status === 404)
485
- return null;
486
- if (!res.ok) {
487
- const data = await res.json().catch(() => ({}));
488
- throw new Error(data.error || 'Failed to load invite');
489
- }
490
- return res.json();
491
- }
492
- async function acceptGuestByCode(token, code, approvedDeviceIds) {
493
- const res = await fetch(`${convexSiteUrl}/guests/accept-code`, {
494
- method: 'POST',
495
- headers: {
496
- Authorization: `Bearer ${token}`,
497
- 'Content-Type': 'application/json',
498
- },
499
- body: JSON.stringify({
500
- code: code.toUpperCase().trim(),
501
- approvedDeviceIds,
502
- }),
503
- });
504
- if (!res.ok) {
505
- const data = await res.json().catch(() => ({}));
506
- throw new Error(data.error || 'Invalid invite code');
507
- }
508
- return res.json();
509
- }
510
- async function acceptGuestInvitation(token, hostUserId, approvedDeviceIds) {
511
- const res = await fetch(`${convexSiteUrl}/guests/accept`, {
512
- method: 'POST',
513
- headers: {
514
- Authorization: `Bearer ${token}`,
515
- 'Content-Type': 'application/json',
516
- },
517
- body: JSON.stringify({ hostUserId, approvedDeviceIds }),
518
- });
519
- if (!res.ok) {
520
- const data = await res.json().catch(() => ({}));
521
- throw new Error(data.error || 'Failed to accept invitation');
522
- }
523
- }
@@ -0,0 +1,53 @@
1
+ type SecureStoreLike = {
2
+ getItemAsync(key: string): Promise<string | null>;
3
+ setItemAsync(key: string, value: string): Promise<void>;
4
+ deleteItemAsync(key: string): Promise<void>;
5
+ };
6
+ export type DeviceDogfoodState = 'unregistered' | 'pending' | 'active' | 'cancelled' | 'revoked' | 'superseded';
7
+ export interface DeviceDogfoodOptions {
8
+ /** Public identifier registered by the app owner in Yaver Settings. */
9
+ appId: string;
10
+ label?: string;
11
+ backendUrl?: string;
12
+ /** Advanced bare-RN integration. Expo apps use SecureStore automatically. */
13
+ secureStore?: SecureStoreLike;
14
+ }
15
+ export interface DeviceDogfoodSession {
16
+ active: true;
17
+ appId: string;
18
+ installationId: string;
19
+ token: string;
20
+ expiresAt: number;
21
+ scopes: string[];
22
+ projectSlug?: string;
23
+ targetDeviceId?: string;
24
+ }
25
+ /** Account-free third-party Dogfood enrollment. The UUID is a public lookup
26
+ * handle; possession is proven by the private key retained in this app. */
27
+ export declare class YaverDeviceDogfood {
28
+ private readonly options;
29
+ private readonly backendUrl;
30
+ private readonly store;
31
+ private readonly storageKey;
32
+ constructor(options: DeviceDogfoodOptions);
33
+ private identity;
34
+ enrollmentInfo(): Promise<{
35
+ appId: string;
36
+ installationId: string;
37
+ registrationSlot: string;
38
+ publicKey: string;
39
+ }>;
40
+ enroll(platform?: string): Promise<{
41
+ status: DeviceDogfoodState;
42
+ installationId: string;
43
+ }>;
44
+ status(): Promise<DeviceDogfoodState>;
45
+ session(): Promise<DeviceDogfoodSession | null>;
46
+ /** Rotate the key but keep the logical slot. Approval supersedes the prior
47
+ * generation, making its already-issued sessions fail immediately. */
48
+ reRegister(platform?: string): Promise<{
49
+ status: DeviceDogfoodState;
50
+ installationId: string;
51
+ }>;
52
+ }
53
+ export {};
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.YaverDeviceDogfood = void 0;
7
+ const tweetnacl_1 = __importDefault(require("tweetnacl"));
8
+ const buffer_1 = require("buffer");
9
+ const auth_1 = require("./auth");
10
+ function b64(bytes) { return buffer_1.Buffer.from(bytes).toString('base64'); }
11
+ function fromB64(value) { return new Uint8Array(buffer_1.Buffer.from(value, 'base64')); }
12
+ function b64url(bytes) {
13
+ return buffer_1.Buffer.from(bytes).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
14
+ }
15
+ function randomBytes(length) {
16
+ const cryptoObject = globalThis?.crypto;
17
+ if (cryptoObject?.getRandomValues) {
18
+ const out = new Uint8Array(length);
19
+ cryptoObject.getRandomValues(out);
20
+ return out;
21
+ }
22
+ try {
23
+ return require('expo-crypto').getRandomBytes(length);
24
+ }
25
+ catch {
26
+ throw new Error('Secure randomness is unavailable. Install expo-crypto or provide global crypto.getRandomValues.');
27
+ }
28
+ }
29
+ function defaultSecureStore() {
30
+ try {
31
+ const store = require('expo-secure-store');
32
+ if (store?.getItemAsync && store?.setItemAsync)
33
+ return store;
34
+ }
35
+ catch { /* named error below */ }
36
+ throw new Error('Secure Dogfood identity storage is unavailable. Install expo-secure-store or pass secureStore.');
37
+ }
38
+ async function responseJSON(response) {
39
+ const body = await response.json().catch(() => ({}));
40
+ if (!response.ok)
41
+ throw new Error(body?.error || `Yaver Dogfood request failed (HTTP ${response.status})`);
42
+ return body;
43
+ }
44
+ function enrollmentMessage(appId, installationId, challenge) {
45
+ return new TextEncoder().encode(`yaver-dogfood-enroll-v1\n${appId}\n${installationId}\n${challenge}`);
46
+ }
47
+ function sessionMessage(appId, installationId, challenge) {
48
+ return new TextEncoder().encode(`yaver-dogfood-session-v1\n${appId}\n${installationId}\n${challenge}`);
49
+ }
50
+ /** Account-free third-party Dogfood enrollment. The UUID is a public lookup
51
+ * handle; possession is proven by the private key retained in this app. */
52
+ class YaverDeviceDogfood {
53
+ constructor(options) {
54
+ this.options = options;
55
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{2,127}$/.test(options.appId))
56
+ throw new Error('A valid Dogfood appId is required');
57
+ this.backendUrl = (options.backendUrl || auth_1.DEFAULT_CONVEX_SITE_URL).replace(/\/$/, '');
58
+ this.store = options.secureStore || defaultSecureStore();
59
+ this.storageKey = `yaver_feedback_dogfood_v1_${options.appId}`;
60
+ }
61
+ async identity() {
62
+ const raw = await this.store.getItemAsync(this.storageKey);
63
+ if (raw) {
64
+ try {
65
+ const saved = JSON.parse(raw);
66
+ if (fromB64(saved.publicKey).length === tweetnacl_1.default.sign.publicKeyLength && fromB64(saved.secretKey).length === tweetnacl_1.default.sign.secretKeyLength)
67
+ return saved;
68
+ }
69
+ catch { /* rotate corrupt identity below */ }
70
+ }
71
+ const seed = randomBytes(32);
72
+ const pair = tweetnacl_1.default.sign.keyPair.fromSeed(seed);
73
+ seed.fill(0);
74
+ const identity = {
75
+ installationId: b64url(randomBytes(18)), registrationSlot: b64url(randomBytes(18)),
76
+ publicKey: b64(pair.publicKey), secretKey: b64(pair.secretKey),
77
+ };
78
+ await this.store.setItemAsync(this.storageKey, JSON.stringify(identity));
79
+ return identity;
80
+ }
81
+ async enrollmentInfo() {
82
+ const identity = await this.identity();
83
+ return { appId: this.options.appId, installationId: identity.installationId, registrationSlot: identity.registrationSlot, publicKey: identity.publicKey };
84
+ }
85
+ async enroll(platform = 'unknown') {
86
+ const identity = await this.identity();
87
+ const started = await responseJSON(await fetch(`${this.backendUrl}/dogfood/enroll/start`, {
88
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
89
+ body: JSON.stringify({ appId: this.options.appId, installationId: identity.installationId, registrationSlot: identity.registrationSlot, publicKey: identity.publicKey, platform, label: this.options.label }),
90
+ }));
91
+ if (started.status === 'active')
92
+ return { status: 'active', installationId: identity.installationId };
93
+ const signature = b64(tweetnacl_1.default.sign.detached(enrollmentMessage(this.options.appId, identity.installationId, started.challenge), fromB64(identity.secretKey)));
94
+ await responseJSON(await fetch(`${this.backendUrl}/dogfood/enroll/prove`, {
95
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
96
+ body: JSON.stringify({ appId: this.options.appId, installationId: identity.installationId, signature }),
97
+ }));
98
+ return { status: 'pending', installationId: identity.installationId };
99
+ }
100
+ async status() {
101
+ const identity = await this.identity();
102
+ const response = await fetch(`${this.backendUrl}/dogfood/enroll/status?appId=${encodeURIComponent(this.options.appId)}&installationId=${encodeURIComponent(identity.installationId)}`);
103
+ if (response.status === 404)
104
+ return 'unregistered';
105
+ return (await responseJSON(response)).status;
106
+ }
107
+ async session() {
108
+ const identity = await this.identity();
109
+ if (await this.status() !== 'active')
110
+ return null;
111
+ const challenge = await responseJSON(await fetch(`${this.backendUrl}/dogfood/session/challenge`, {
112
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
113
+ body: JSON.stringify({ appId: this.options.appId, installationId: identity.installationId }),
114
+ }));
115
+ const signature = b64(tweetnacl_1.default.sign.detached(sessionMessage(this.options.appId, identity.installationId, challenge.challenge), fromB64(identity.secretKey)));
116
+ const result = await responseJSON(await fetch(`${this.backendUrl}/dogfood/session`, {
117
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
118
+ body: JSON.stringify({ appId: this.options.appId, installationId: identity.installationId, signature }),
119
+ }));
120
+ return { active: true, appId: this.options.appId, installationId: identity.installationId, token: result.token, expiresAt: result.expiresAt, scopes: result.scopes || [], projectSlug: result.projectSlug, targetDeviceId: result.targetDeviceId };
121
+ }
122
+ /** Rotate the key but keep the logical slot. Approval supersedes the prior
123
+ * generation, making its already-issued sessions fail immediately. */
124
+ async reRegister(platform = 'unknown') {
125
+ const previous = await this.identity();
126
+ const seed = randomBytes(32);
127
+ const pair = tweetnacl_1.default.sign.keyPair.fromSeed(seed);
128
+ seed.fill(0);
129
+ const next = {
130
+ installationId: b64url(randomBytes(18)), registrationSlot: previous.registrationSlot,
131
+ publicKey: b64(pair.publicKey), secretKey: b64(pair.secretKey),
132
+ };
133
+ await this.store.setItemAsync(this.storageKey, JSON.stringify(next));
134
+ return this.enroll(platform);
135
+ }
136
+ }
137
+ exports.YaverDeviceDogfood = YaverDeviceDogfood;