yaver-feedback-react-native 0.9.3 → 0.9.6

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 (43) hide show
  1. package/README.md +31 -16
  2. package/android/src/main/java/io/yaver/feedback/YaverHotReloadModule.java +59 -0
  3. package/app.plugin.js +112 -3
  4. package/dist/AuthOverlay.js +13 -2
  5. package/dist/FeedbackModal.js +70 -7
  6. package/dist/ShakeDetector.js +31 -17
  7. package/dist/YaverFeedback.d.ts +34 -9
  8. package/dist/YaverFeedback.js +304 -20
  9. package/dist/__tests__/NativeDogfoodShortcut.test.d.ts +1 -0
  10. package/dist/__tests__/NativeDogfoodShortcut.test.js +34 -0
  11. package/dist/__tests__/ReportIdentity.test.js +6 -3
  12. package/dist/__tests__/ShakeDetectorWeb.test.d.ts +1 -0
  13. package/dist/__tests__/ShakeDetectorWeb.test.js +27 -0
  14. package/dist/__tests__/YaverFeedback.test.js +97 -4
  15. package/dist/__tests__/deviceDogfood.test.js +2 -2
  16. package/dist/__tests__/dogfoodPolicy.test.js +1 -1
  17. package/dist/auth.d.ts +7 -0
  18. package/dist/auth.js +28 -0
  19. package/dist/deviceDogfood.d.ts +6 -2
  20. package/dist/deviceDogfood.js +6 -3
  21. package/dist/dogfoodPolicy.d.ts +32 -1
  22. package/dist/index.d.ts +4 -4
  23. package/dist/index.js +5 -2
  24. package/dist/preferences.d.ts +2 -0
  25. package/dist/preferences.js +24 -0
  26. package/ios/YaverHotReload.m +8 -0
  27. package/ios/YaverHotReload.swift +37 -0
  28. package/package.json +2 -2
  29. package/src/AuthOverlay.tsx +11 -2
  30. package/src/FeedbackModal.tsx +98 -6
  31. package/src/ShakeDetector.ts +31 -18
  32. package/src/YaverFeedback.ts +271 -19
  33. package/src/__tests__/NativeDogfoodShortcut.test.ts +37 -0
  34. package/src/__tests__/ReportIdentity.test.ts +6 -4
  35. package/src/__tests__/ShakeDetectorWeb.test.ts +30 -0
  36. package/src/__tests__/YaverFeedback.test.ts +104 -5
  37. package/src/__tests__/deviceDogfood.test.ts +2 -2
  38. package/src/__tests__/dogfoodPolicy.test.ts +1 -1
  39. package/src/auth.ts +29 -0
  40. package/src/deviceDogfood.ts +8 -3
  41. package/src/dogfoodPolicy.ts +32 -1
  42. package/src/index.ts +5 -2
  43. package/src/preferences.ts +20 -0
@@ -20,7 +20,7 @@ describe('resolveSDKDogfood', () => {
20
20
  });
21
21
  });
22
22
 
23
- it('supports a key-enrolled installation without an app account', () => {
23
+ it('supports an account-bound key-enrolled installation without an app backend', () => {
24
24
  expect(resolveSDKDogfood({ enabled: true, appId: 'io.example', installationStatus: 'active' }).code)
25
25
  .toBe('SDK_DOGFOOD_INSTALLATION_REQUIRED');
26
26
  expect(resolveSDKDogfood({ enabled: true, appId: 'io.example', installationId: 'install-1', installationStatus: 'pending' }).code)
package/src/auth.ts CHANGED
@@ -202,6 +202,35 @@ export async function clearSelectedDeviceId(): Promise<void> {
202
202
  }
203
203
  }
204
204
 
205
+ /** Verify that this full Yaver session owns the exact registered Dogfood app.
206
+ * Any login is not enough, and installation-scoped SDK tokens return false. */
207
+ export async function getDogfoodAccountAccess(appId: string, token: string, installationId?: string): Promise<{
208
+ authenticated: boolean;
209
+ ownerAuthorized: boolean;
210
+ installationAuthorized: boolean;
211
+ }> {
212
+ try {
213
+ const controller = new AbortController();
214
+ const timeout = setTimeout(() => controller.abort(), 5_000);
215
+ const query = new URLSearchParams({ appId });
216
+ if (installationId) query.set('installationId', installationId);
217
+ const response = await fetch(`${convexSiteUrl}/dogfood/access?${query.toString()}`, {
218
+ headers: { Authorization: `Bearer ${token}` },
219
+ signal: controller.signal,
220
+ });
221
+ clearTimeout(timeout);
222
+ if (!response.ok) return { authenticated: false, ownerAuthorized: false, installationAuthorized: false };
223
+ const result = await response.json();
224
+ return {
225
+ authenticated: result?.authenticated === true,
226
+ ownerAuthorized: result?.ownerAuthorized === true,
227
+ installationAuthorized: result?.installationAuthorized === true,
228
+ };
229
+ } catch {
230
+ return { authenticated: false, ownerAuthorized: false, installationAuthorized: false };
231
+ }
232
+ }
233
+
205
234
  // ─── Token validation ──────────────────────────────────────────────────
206
235
 
207
236
  export async function validateToken(token: string): Promise<User | null> {
@@ -15,6 +15,9 @@ export interface DeviceDogfoodOptions {
15
15
  appId: string;
16
16
  label?: string;
17
17
  backendUrl?: string;
18
+ /** Full Yaver OAuth token. Required for enrollment; never persisted with the
19
+ * installation private key. The backend binds its user to this key. */
20
+ authToken?: string;
18
21
  /** Advanced bare-RN integration. Expo apps use SecureStore automatically. */
19
22
  secureStore?: SecureStoreLike;
20
23
  }
@@ -79,8 +82,9 @@ function sessionMessage(appId: string, installationId: string, challenge: string
79
82
  return new TextEncoder().encode(`yaver-dogfood-session-v1\n${appId}\n${installationId}\n${challenge}`);
80
83
  }
81
84
 
82
- /** Account-free third-party Dogfood enrollment. The UUID is a public lookup
83
- * handle; possession is proven by the private key retained in this app. */
85
+ /** Account-bound third-party Dogfood enrollment. The installation ID is a
86
+ * public lookup handle; possession is proven by the private key retained in
87
+ * this app, while enrollment is bound to the signed-in Yaver account. */
84
88
  export class YaverDeviceDogfood {
85
89
  private readonly backendUrl: string;
86
90
  private readonly store: SecureStoreLike;
@@ -118,9 +122,10 @@ export class YaverDeviceDogfood {
118
122
  }
119
123
 
120
124
  async enroll(platform = 'unknown'): Promise<{ status: DeviceDogfoodState; installationId: string }> {
125
+ if (!this.options.authToken) throw new Error('Sign in to Yaver before registering this device for Dogfood.');
121
126
  const identity = await this.identity();
122
127
  const started = await responseJSON(await fetch(`${this.backendUrl}/dogfood/enroll/start`, {
123
- method: 'POST', headers: { 'Content-Type': 'application/json' },
128
+ method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.options.authToken}` },
124
129
  body: JSON.stringify({ appId: this.options.appId, installationId: identity.installationId, registrationSlot: identity.registrationSlot, publicKey: identity.publicKey, platform, label: this.options.label }),
125
130
  }));
126
131
  if (started.status === 'active') return { status: 'active', installationId: identity.installationId };
@@ -1,3 +1,21 @@
1
+ export interface DogfoodAccessSnapshot {
2
+ appId: string;
3
+ /** A Yaver session exists locally; not itself an authorization decision. */
4
+ yaverAuthenticated: boolean;
5
+ /** Backend-confirmed owner/maintainer of this exact appId. */
6
+ ownerAuthorized: boolean;
7
+ installationId?: string;
8
+ deviceState: 'unknown' | 'unregistered' | 'pending' | 'active' | 'cancelled' | 'revoked' | 'superseded';
9
+ /** True only for a backend-approved device key or an authenticated owner. */
10
+ authorized: boolean;
11
+ }
12
+
13
+ export interface DogfoodFlowSnapshot {
14
+ phase: 'idle' | 'denied' | 'auth-required' | 'machine-required' | 'opening' | 'error';
15
+ appId?: string;
16
+ error?: string;
17
+ }
18
+
1
19
  export interface SDKDogfoodConfig {
2
20
  /** Explicit opt-in. Omitted/false keeps the normal Feedback SDK. */
3
21
  enabled?: boolean;
@@ -5,7 +23,7 @@ export interface SDKDogfoodConfig {
5
23
  accountIds?: string[];
6
24
  /** The third-party app's currently authenticated account ID. */
7
25
  currentAccountId?: string;
8
- /** Account-free installation identity resolved by YaverDeviceDogfood. */
26
+ /** Account-bound installation identity resolved by YaverDeviceDogfood. */
9
27
  appId?: string;
10
28
  installationId?: string;
11
29
  installationStatus?: 'pending' | 'active' | 'cancelled' | 'revoked' | 'superseded';
@@ -13,6 +31,19 @@ export interface SDKDogfoodConfig {
13
31
  onExit?: () => void | Promise<void>;
14
32
  /** Optional app label shown beside Dogfood mode. */
15
33
  label?: string;
34
+ /** Project/framework hints used by the zero-orchestration onboarding UI. */
35
+ projectName?: string;
36
+ framework?: string;
37
+ /** Advanced override for staging/self-hosted enrollment. */
38
+ backendUrl?: string;
39
+ /** Presentation-only ACL hook, e.g. `() => user.isAdmin`. Server-side OAuth
40
+ * or device-signature verification remains the authority boundary. */
41
+ canShow?: (access: DogfoodAccessSnapshot) => boolean | Promise<boolean>;
42
+ /** Optional callback alternative to onDogfoodFlowState(). */
43
+ onStateChange?: (state: DogfoodFlowSnapshot) => void;
44
+ /** ACL-backed Home Screen/App Shortcut. Dynamic and absent until Yaver
45
+ * owner auth or this installation's approved key authorizes it. */
46
+ appShortcut?: boolean | { label?: string };
16
47
  }
17
48
 
18
49
  export interface SDKDogfoodStatus {
package/src/index.ts CHANGED
@@ -29,7 +29,7 @@
29
29
  */
30
30
 
31
31
  export { YaverFeedback } from './YaverFeedback';
32
- export type { DogfoodOnboardingOptions } from './YaverFeedback';
32
+ export type { DogfoodOnboardingOptions, DogfoodFlowPhase, DogfoodFlowState } from './YaverFeedback';
33
33
  export { captureStoreScreenshots } from './storeShots';
34
34
  export type {
35
35
  CaptureStoreScreenshotsOptions,
@@ -81,7 +81,7 @@ export { FloatingButton } from './FloatingButton';
81
81
  export { YaverModeBadge, hideYaverModeBadge, showYaverModeBadge, isYaverModeBadgeHidden } from './YaverModeBadge';
82
82
  export type { YaverModeBadgeProps } from './YaverModeBadge';
83
83
  export { resolveSDKDogfood } from './dogfoodPolicy';
84
- export type { SDKDogfoodConfig, SDKDogfoodStatus } from './dogfoodPolicy';
84
+ export type { DogfoodAccessSnapshot, DogfoodFlowSnapshot, SDKDogfoodConfig, SDKDogfoodStatus } from './dogfoodPolicy';
85
85
  export { YaverDeviceDogfood } from './deviceDogfood';
86
86
  export type { DeviceDogfoodOptions, DeviceDogfoodSession, DeviceDogfoodState } from './deviceDogfood';
87
87
  export {
@@ -114,6 +114,8 @@ export {
114
114
  getQuickIconDisabled,
115
115
  setQuickIconDisabled,
116
116
  clearQuickIconDisabled,
117
+ getPreferredDogfoodLane,
118
+ setPreferredDogfoodLane,
117
119
  } from './preferences';
118
120
  export {
119
121
  configureAuthEndpoints,
@@ -125,6 +127,7 @@ export {
125
127
  getUser,
126
128
  saveUser,
127
129
  getSelectedDeviceId,
130
+ getDogfoodAccountAccess,
128
131
  saveSelectedDeviceId,
129
132
  clearSelectedDeviceId,
130
133
  validateToken,
@@ -159,6 +159,7 @@ export async function clearQuickIconColorPreset(): Promise<void> {
159
159
 
160
160
  const PREFERRED_RUNNER_KEY = 'yaver_feedback_preferred_runner';
161
161
  const PREFERRED_MODEL_KEY = 'yaver_feedback_preferred_model';
162
+ const PREFERRED_DOGFOOD_LANE_PREFIX = 'yaver_feedback_dogfood_lane_';
162
163
 
163
164
  export async function getPreferredRunner(): Promise<string | null> {
164
165
  if (!AsyncStorage) return null;
@@ -205,3 +206,22 @@ export async function setPreferredModel(model: string | null): Promise<void> {
205
206
  /* best-effort */
206
207
  }
207
208
  }
209
+
210
+ export async function getPreferredDogfoodLane(appId: string): Promise<'browser' | 'hermes' | 'webrtc' | null> {
211
+ if (!AsyncStorage || !appId) return null;
212
+ try {
213
+ const value = await AsyncStorage.getItem(`${PREFERRED_DOGFOOD_LANE_PREFIX}${appId}`);
214
+ return value === 'browser' || value === 'hermes' || value === 'webrtc' ? value : null;
215
+ } catch {
216
+ return null;
217
+ }
218
+ }
219
+
220
+ export async function setPreferredDogfoodLane(appId: string, lane: 'browser' | 'hermes' | 'webrtc'): Promise<void> {
221
+ if (!AsyncStorage || !appId) return;
222
+ try {
223
+ await AsyncStorage.setItem(`${PREFERRED_DOGFOOD_LANE_PREFIX}${appId}`, lane);
224
+ } catch {
225
+ /* best-effort */
226
+ }
227
+ }