yaver-feedback-react-native 0.9.4 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -16
- package/android/src/main/java/io/yaver/feedback/YaverHotReloadModule.java +59 -0
- package/app.plugin.js +200 -3
- package/dist/AuthOverlay.js +13 -2
- package/dist/FeedbackModal.js +70 -7
- package/dist/YaverFeedback.d.ts +34 -9
- package/dist/YaverFeedback.js +304 -20
- package/dist/__tests__/NativeDogfoodShortcut.test.d.ts +1 -0
- package/dist/__tests__/NativeDogfoodShortcut.test.js +52 -0
- package/dist/__tests__/ReportIdentity.test.js +6 -3
- package/dist/__tests__/YaverFeedback.test.js +97 -4
- package/dist/__tests__/deviceDogfood.test.js +2 -2
- package/dist/__tests__/dogfoodPolicy.test.js +1 -1
- package/dist/auth.d.ts +7 -0
- package/dist/auth.js +28 -0
- package/dist/deviceDogfood.d.ts +6 -2
- package/dist/deviceDogfood.js +6 -3
- package/dist/dogfoodPolicy.d.ts +32 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +5 -2
- package/dist/preferences.d.ts +2 -0
- package/dist/preferences.js +24 -0
- package/ios/YaverHotReload.m +8 -0
- package/ios/YaverHotReload.swift +37 -0
- package/package.json +2 -2
- package/src/AuthOverlay.tsx +11 -2
- package/src/FeedbackModal.tsx +98 -6
- package/src/YaverFeedback.ts +271 -19
- package/src/__tests__/NativeDogfoodShortcut.test.ts +59 -0
- package/src/__tests__/ReportIdentity.test.ts +6 -4
- package/src/__tests__/YaverFeedback.test.ts +104 -5
- package/src/__tests__/deviceDogfood.test.ts +2 -2
- package/src/__tests__/dogfoodPolicy.test.ts +1 -1
- package/src/auth.ts +29 -0
- package/src/deviceDogfood.ts +8 -3
- package/src/dogfoodPolicy.ts +32 -1
- package/src/index.ts +5 -2
- package/src/preferences.ts +20 -0
package/dist/auth.d.ts
CHANGED
|
@@ -47,6 +47,13 @@ export declare function saveUser(user: User): Promise<void>;
|
|
|
47
47
|
export declare function getSelectedDeviceId(): Promise<string | null>;
|
|
48
48
|
export declare function saveSelectedDeviceId(deviceId: string): Promise<void>;
|
|
49
49
|
export declare function clearSelectedDeviceId(): Promise<void>;
|
|
50
|
+
/** Verify that this full Yaver session owns the exact registered Dogfood app.
|
|
51
|
+
* Any login is not enough, and installation-scoped SDK tokens return false. */
|
|
52
|
+
export declare function getDogfoodAccountAccess(appId: string, token: string, installationId?: string): Promise<{
|
|
53
|
+
authenticated: boolean;
|
|
54
|
+
ownerAuthorized: boolean;
|
|
55
|
+
installationAuthorized: boolean;
|
|
56
|
+
}>;
|
|
50
57
|
export declare function validateToken(token: string): Promise<User | null>;
|
|
51
58
|
/**
|
|
52
59
|
* Sign in with Apple using the native ASAuthorization flow. Requires
|
package/dist/auth.js
CHANGED
|
@@ -31,6 +31,7 @@ exports.saveUser = saveUser;
|
|
|
31
31
|
exports.getSelectedDeviceId = getSelectedDeviceId;
|
|
32
32
|
exports.saveSelectedDeviceId = saveSelectedDeviceId;
|
|
33
33
|
exports.clearSelectedDeviceId = clearSelectedDeviceId;
|
|
34
|
+
exports.getDogfoodAccountAccess = getDogfoodAccountAccess;
|
|
34
35
|
exports.validateToken = validateToken;
|
|
35
36
|
exports.signInWithApple = signInWithApple;
|
|
36
37
|
exports.signInWithOAuth = signInWithOAuth;
|
|
@@ -186,6 +187,33 @@ async function clearSelectedDeviceId() {
|
|
|
186
187
|
// best effort
|
|
187
188
|
}
|
|
188
189
|
}
|
|
190
|
+
/** Verify that this full Yaver session owns the exact registered Dogfood app.
|
|
191
|
+
* Any login is not enough, and installation-scoped SDK tokens return false. */
|
|
192
|
+
async function getDogfoodAccountAccess(appId, token, installationId) {
|
|
193
|
+
try {
|
|
194
|
+
const controller = new AbortController();
|
|
195
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
196
|
+
const query = new URLSearchParams({ appId });
|
|
197
|
+
if (installationId)
|
|
198
|
+
query.set('installationId', installationId);
|
|
199
|
+
const response = await fetch(`${convexSiteUrl}/dogfood/access?${query.toString()}`, {
|
|
200
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
201
|
+
signal: controller.signal,
|
|
202
|
+
});
|
|
203
|
+
clearTimeout(timeout);
|
|
204
|
+
if (!response.ok)
|
|
205
|
+
return { authenticated: false, ownerAuthorized: false, installationAuthorized: false };
|
|
206
|
+
const result = await response.json();
|
|
207
|
+
return {
|
|
208
|
+
authenticated: result?.authenticated === true,
|
|
209
|
+
ownerAuthorized: result?.ownerAuthorized === true,
|
|
210
|
+
installationAuthorized: result?.installationAuthorized === true,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return { authenticated: false, ownerAuthorized: false, installationAuthorized: false };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
189
217
|
// ─── Token validation ──────────────────────────────────────────────────
|
|
190
218
|
async function validateToken(token) {
|
|
191
219
|
try {
|
package/dist/deviceDogfood.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ export interface DeviceDogfoodOptions {
|
|
|
9
9
|
appId: string;
|
|
10
10
|
label?: string;
|
|
11
11
|
backendUrl?: string;
|
|
12
|
+
/** Full Yaver OAuth token. Required for enrollment; never persisted with the
|
|
13
|
+
* installation private key. The backend binds its user to this key. */
|
|
14
|
+
authToken?: string;
|
|
12
15
|
/** Advanced bare-RN integration. Expo apps use SecureStore automatically. */
|
|
13
16
|
secureStore?: SecureStoreLike;
|
|
14
17
|
}
|
|
@@ -22,8 +25,9 @@ export interface DeviceDogfoodSession {
|
|
|
22
25
|
projectSlug?: string;
|
|
23
26
|
targetDeviceId?: string;
|
|
24
27
|
}
|
|
25
|
-
/** Account-
|
|
26
|
-
* handle; possession is proven by the private key retained in
|
|
28
|
+
/** Account-bound third-party Dogfood enrollment. The installation ID is a
|
|
29
|
+
* public lookup handle; possession is proven by the private key retained in
|
|
30
|
+
* this app, while enrollment is bound to the signed-in Yaver account. */
|
|
27
31
|
export declare class YaverDeviceDogfood {
|
|
28
32
|
private readonly options;
|
|
29
33
|
private readonly backendUrl;
|
package/dist/deviceDogfood.js
CHANGED
|
@@ -47,8 +47,9 @@ function enrollmentMessage(appId, installationId, challenge) {
|
|
|
47
47
|
function sessionMessage(appId, installationId, challenge) {
|
|
48
48
|
return new TextEncoder().encode(`yaver-dogfood-session-v1\n${appId}\n${installationId}\n${challenge}`);
|
|
49
49
|
}
|
|
50
|
-
/** Account-
|
|
51
|
-
* handle; possession is proven by the private key retained in
|
|
50
|
+
/** Account-bound third-party Dogfood enrollment. The installation ID is a
|
|
51
|
+
* public lookup handle; possession is proven by the private key retained in
|
|
52
|
+
* this app, while enrollment is bound to the signed-in Yaver account. */
|
|
52
53
|
class YaverDeviceDogfood {
|
|
53
54
|
constructor(options) {
|
|
54
55
|
this.options = options;
|
|
@@ -83,9 +84,11 @@ class YaverDeviceDogfood {
|
|
|
83
84
|
return { appId: this.options.appId, installationId: identity.installationId, registrationSlot: identity.registrationSlot, publicKey: identity.publicKey };
|
|
84
85
|
}
|
|
85
86
|
async enroll(platform = 'unknown') {
|
|
87
|
+
if (!this.options.authToken)
|
|
88
|
+
throw new Error('Sign in to Yaver before registering this device for Dogfood.');
|
|
86
89
|
const identity = await this.identity();
|
|
87
90
|
const started = await responseJSON(await fetch(`${this.backendUrl}/dogfood/enroll/start`, {
|
|
88
|
-
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
91
|
+
method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.options.authToken}` },
|
|
89
92
|
body: JSON.stringify({ appId: this.options.appId, installationId: identity.installationId, registrationSlot: identity.registrationSlot, publicKey: identity.publicKey, platform, label: this.options.label }),
|
|
90
93
|
}));
|
|
91
94
|
if (started.status === 'active')
|
package/dist/dogfoodPolicy.d.ts
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
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
|
+
export interface DogfoodFlowSnapshot {
|
|
13
|
+
phase: 'idle' | 'denied' | 'auth-required' | 'machine-required' | 'opening' | 'error';
|
|
14
|
+
appId?: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
1
17
|
export interface SDKDogfoodConfig {
|
|
2
18
|
/** Explicit opt-in. Omitted/false keeps the normal Feedback SDK. */
|
|
3
19
|
enabled?: boolean;
|
|
@@ -5,7 +21,7 @@ export interface SDKDogfoodConfig {
|
|
|
5
21
|
accountIds?: string[];
|
|
6
22
|
/** The third-party app's currently authenticated account ID. */
|
|
7
23
|
currentAccountId?: string;
|
|
8
|
-
/** Account-
|
|
24
|
+
/** Account-bound installation identity resolved by YaverDeviceDogfood. */
|
|
9
25
|
appId?: string;
|
|
10
26
|
installationId?: string;
|
|
11
27
|
installationStatus?: 'pending' | 'active' | 'cancelled' | 'revoked' | 'superseded';
|
|
@@ -13,6 +29,21 @@ export interface SDKDogfoodConfig {
|
|
|
13
29
|
onExit?: () => void | Promise<void>;
|
|
14
30
|
/** Optional app label shown beside Dogfood mode. */
|
|
15
31
|
label?: string;
|
|
32
|
+
/** Project/framework hints used by the zero-orchestration onboarding UI. */
|
|
33
|
+
projectName?: string;
|
|
34
|
+
framework?: string;
|
|
35
|
+
/** Advanced override for staging/self-hosted enrollment. */
|
|
36
|
+
backendUrl?: string;
|
|
37
|
+
/** Presentation-only ACL hook, e.g. `() => user.isAdmin`. Server-side OAuth
|
|
38
|
+
* or device-signature verification remains the authority boundary. */
|
|
39
|
+
canShow?: (access: DogfoodAccessSnapshot) => boolean | Promise<boolean>;
|
|
40
|
+
/** Optional callback alternative to onDogfoodFlowState(). */
|
|
41
|
+
onStateChange?: (state: DogfoodFlowSnapshot) => void;
|
|
42
|
+
/** ACL-backed Home Screen/App Shortcut. Dynamic and absent until Yaver
|
|
43
|
+
* owner auth or this installation's approved key authorizes it. */
|
|
44
|
+
appShortcut?: boolean | {
|
|
45
|
+
label?: string;
|
|
46
|
+
};
|
|
16
47
|
}
|
|
17
48
|
export interface SDKDogfoodStatus {
|
|
18
49
|
active: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
30
|
export { YaverFeedback } from './YaverFeedback';
|
|
31
|
-
export type { DogfoodOnboardingOptions } from './YaverFeedback';
|
|
31
|
+
export type { DogfoodOnboardingOptions, DogfoodFlowPhase, DogfoodFlowState } from './YaverFeedback';
|
|
32
32
|
export { captureStoreScreenshots } from './storeShots';
|
|
33
33
|
export type { CaptureStoreScreenshotsOptions, CaptureStoreScreenshotsResult, StoreShotFrame, } from './storeShots';
|
|
34
34
|
export { BlackBox } from './BlackBox';
|
|
@@ -54,7 +54,7 @@ export { FloatingButton } from './FloatingButton';
|
|
|
54
54
|
export { YaverModeBadge, hideYaverModeBadge, showYaverModeBadge, isYaverModeBadgeHidden } from './YaverModeBadge';
|
|
55
55
|
export type { YaverModeBadgeProps } from './YaverModeBadge';
|
|
56
56
|
export { resolveSDKDogfood } from './dogfoodPolicy';
|
|
57
|
-
export type { SDKDogfoodConfig, SDKDogfoodStatus } from './dogfoodPolicy';
|
|
57
|
+
export type { DogfoodAccessSnapshot, DogfoodFlowSnapshot, SDKDogfoodConfig, SDKDogfoodStatus } from './dogfoodPolicy';
|
|
58
58
|
export { YaverDeviceDogfood } from './deviceDogfood';
|
|
59
59
|
export type { DeviceDogfoodOptions, DeviceDogfoodSession, DeviceDogfoodState } from './deviceDogfood';
|
|
60
60
|
export { DogfoodController, DogfoodRuntimeError, defaultDogfoodLane, dogfoodLaneOptions, dogfoodLogLinesFromDevEvent, runtimeLogLinesFromDevEvent, validateDogfoodProject, } from './DogfoodRuntime';
|
|
@@ -63,8 +63,8 @@ export { FeedbackModal } from './FeedbackModal';
|
|
|
63
63
|
export { QuickActionIcon } from './QuickActionIcon';
|
|
64
64
|
export type { QuickActionIconProps } from './QuickActionIcon';
|
|
65
65
|
export { FixReport } from './FixReport';
|
|
66
|
-
export { getQuickIconDisabled, setQuickIconDisabled, clearQuickIconDisabled, } from './preferences';
|
|
67
|
-
export { configureAuthEndpoints, getConvexSiteUrl, getWebBaseUrl, getToken, saveToken, clearToken, getUser, saveUser, getSelectedDeviceId, saveSelectedDeviceId, clearSelectedDeviceId, validateToken, signInWithApple, signInWithOAuth, signupWithEmail, loginWithEmail, listReachableDevices, DEFAULT_CONVEX_SITE_URL, DEFAULT_WEB_BASE_URL, DEFAULT_OAUTH_REDIRECT, } from './auth';
|
|
66
|
+
export { getQuickIconDisabled, setQuickIconDisabled, clearQuickIconDisabled, getPreferredDogfoodLane, setPreferredDogfoodLane, } from './preferences';
|
|
67
|
+
export { configureAuthEndpoints, getConvexSiteUrl, getWebBaseUrl, getToken, saveToken, clearToken, getUser, saveUser, getSelectedDeviceId, getDogfoodAccountAccess, saveSelectedDeviceId, clearSelectedDeviceId, validateToken, signInWithApple, signInWithOAuth, signupWithEmail, loginWithEmail, listReachableDevices, DEFAULT_CONVEX_SITE_URL, DEFAULT_WEB_BASE_URL, DEFAULT_OAUTH_REDIRECT, } from './auth';
|
|
68
68
|
export type { OAuthProvider, User, RemoteDevice, DeviceList, } from './auth';
|
|
69
69
|
export { captureScreenshot, pickFeedbackFile, startVideoRecording, stopVideoRecording, isVideoRecording, } from './capture';
|
|
70
70
|
export { uploadFeedback } from './upload';
|
package/dist/index.js
CHANGED
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
-
exports.
|
|
33
|
-
exports.uploadFeedback = exports.isVideoRecording = exports.stopVideoRecording = exports.startVideoRecording = exports.pickFeedbackFile = exports.captureScreenshot = exports.DEFAULT_OAUTH_REDIRECT = exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = exports.listReachableDevices = exports.loginWithEmail = exports.signupWithEmail = exports.signInWithOAuth = exports.signInWithApple = exports.validateToken = exports.clearSelectedDeviceId = void 0;
|
|
32
|
+
exports.saveUser = exports.getUser = exports.clearToken = exports.saveToken = exports.getToken = exports.getWebBaseUrl = exports.getConvexSiteUrl = exports.configureAuthEndpoints = exports.setPreferredDogfoodLane = exports.getPreferredDogfoodLane = exports.clearQuickIconDisabled = exports.setQuickIconDisabled = exports.getQuickIconDisabled = exports.FixReport = exports.QuickActionIcon = exports.FeedbackModal = exports.validateDogfoodProject = exports.runtimeLogLinesFromDevEvent = exports.dogfoodLogLinesFromDevEvent = exports.dogfoodLaneOptions = exports.defaultDogfoodLane = exports.DogfoodRuntimeError = exports.DogfoodController = exports.YaverDeviceDogfood = exports.resolveSDKDogfood = exports.isYaverModeBadgeHidden = exports.showYaverModeBadge = exports.hideYaverModeBadge = exports.YaverModeBadge = exports.FloatingButton = exports.ShakeDetector = exports.AuthOverlay = exports.PairDeviceModal = exports.YaverMachinePickerScreen = exports.YaverLoginScreen = exports.YaverConnectionScreen = exports.RELOAD_APP_PATH = exports.RELOAD_PATH = exports.describeReloadFailure = exports.reloadFrameworkFamily = exports.reloadRequest = exports.reloadActions = exports.createP2PDogfoodDriver = exports.P2PClient = exports.YaverDiscovery = exports.initExpo = exports.YaverUpdates = exports.BlackBox = exports.captureStoreScreenshots = exports.YaverFeedback = void 0;
|
|
33
|
+
exports.uploadFeedback = exports.isVideoRecording = exports.stopVideoRecording = exports.startVideoRecording = exports.pickFeedbackFile = exports.captureScreenshot = exports.DEFAULT_OAUTH_REDIRECT = exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = exports.listReachableDevices = exports.loginWithEmail = exports.signupWithEmail = exports.signInWithOAuth = exports.signInWithApple = exports.validateToken = exports.clearSelectedDeviceId = exports.saveSelectedDeviceId = exports.getDogfoodAccountAccess = exports.getSelectedDeviceId = void 0;
|
|
34
34
|
var YaverFeedback_1 = require("./YaverFeedback");
|
|
35
35
|
Object.defineProperty(exports, "YaverFeedback", { enumerable: true, get: function () { return YaverFeedback_1.YaverFeedback; } });
|
|
36
36
|
var storeShots_1 = require("./storeShots");
|
|
@@ -98,6 +98,8 @@ var preferences_1 = require("./preferences");
|
|
|
98
98
|
Object.defineProperty(exports, "getQuickIconDisabled", { enumerable: true, get: function () { return preferences_1.getQuickIconDisabled; } });
|
|
99
99
|
Object.defineProperty(exports, "setQuickIconDisabled", { enumerable: true, get: function () { return preferences_1.setQuickIconDisabled; } });
|
|
100
100
|
Object.defineProperty(exports, "clearQuickIconDisabled", { enumerable: true, get: function () { return preferences_1.clearQuickIconDisabled; } });
|
|
101
|
+
Object.defineProperty(exports, "getPreferredDogfoodLane", { enumerable: true, get: function () { return preferences_1.getPreferredDogfoodLane; } });
|
|
102
|
+
Object.defineProperty(exports, "setPreferredDogfoodLane", { enumerable: true, get: function () { return preferences_1.setPreferredDogfoodLane; } });
|
|
101
103
|
var auth_1 = require("./auth");
|
|
102
104
|
Object.defineProperty(exports, "configureAuthEndpoints", { enumerable: true, get: function () { return auth_1.configureAuthEndpoints; } });
|
|
103
105
|
Object.defineProperty(exports, "getConvexSiteUrl", { enumerable: true, get: function () { return auth_1.getConvexSiteUrl; } });
|
|
@@ -108,6 +110,7 @@ Object.defineProperty(exports, "clearToken", { enumerable: true, get: function (
|
|
|
108
110
|
Object.defineProperty(exports, "getUser", { enumerable: true, get: function () { return auth_1.getUser; } });
|
|
109
111
|
Object.defineProperty(exports, "saveUser", { enumerable: true, get: function () { return auth_1.saveUser; } });
|
|
110
112
|
Object.defineProperty(exports, "getSelectedDeviceId", { enumerable: true, get: function () { return auth_1.getSelectedDeviceId; } });
|
|
113
|
+
Object.defineProperty(exports, "getDogfoodAccountAccess", { enumerable: true, get: function () { return auth_1.getDogfoodAccountAccess; } });
|
|
111
114
|
Object.defineProperty(exports, "saveSelectedDeviceId", { enumerable: true, get: function () { return auth_1.saveSelectedDeviceId; } });
|
|
112
115
|
Object.defineProperty(exports, "clearSelectedDeviceId", { enumerable: true, get: function () { return auth_1.clearSelectedDeviceId; } });
|
|
113
116
|
Object.defineProperty(exports, "validateToken", { enumerable: true, get: function () { return auth_1.validateToken; } });
|
package/dist/preferences.d.ts
CHANGED
|
@@ -31,3 +31,5 @@ export declare function getPreferredRunner(): Promise<string | null>;
|
|
|
31
31
|
export declare function setPreferredRunner(runner: string | null): Promise<void>;
|
|
32
32
|
export declare function getPreferredModel(): Promise<string | null>;
|
|
33
33
|
export declare function setPreferredModel(model: string | null): Promise<void>;
|
|
34
|
+
export declare function getPreferredDogfoodLane(appId: string): Promise<'browser' | 'hermes' | 'webrtc' | null>;
|
|
35
|
+
export declare function setPreferredDogfoodLane(appId: string, lane: 'browser' | 'hermes' | 'webrtc'): Promise<void>;
|
package/dist/preferences.js
CHANGED
|
@@ -25,6 +25,8 @@ exports.getPreferredRunner = getPreferredRunner;
|
|
|
25
25
|
exports.setPreferredRunner = setPreferredRunner;
|
|
26
26
|
exports.getPreferredModel = getPreferredModel;
|
|
27
27
|
exports.setPreferredModel = setPreferredModel;
|
|
28
|
+
exports.getPreferredDogfoodLane = getPreferredDogfoodLane;
|
|
29
|
+
exports.setPreferredDogfoodLane = setPreferredDogfoodLane;
|
|
28
30
|
let AsyncStorage = null;
|
|
29
31
|
try {
|
|
30
32
|
AsyncStorage = require('@react-native-async-storage/async-storage').default;
|
|
@@ -149,6 +151,7 @@ async function clearQuickIconColorPreset() {
|
|
|
149
151
|
// run picks whatever's signed-in via getRunnerStatus().)
|
|
150
152
|
const PREFERRED_RUNNER_KEY = 'yaver_feedback_preferred_runner';
|
|
151
153
|
const PREFERRED_MODEL_KEY = 'yaver_feedback_preferred_model';
|
|
154
|
+
const PREFERRED_DOGFOOD_LANE_PREFIX = 'yaver_feedback_dogfood_lane_';
|
|
152
155
|
async function getPreferredRunner() {
|
|
153
156
|
if (!AsyncStorage)
|
|
154
157
|
return null;
|
|
@@ -199,3 +202,24 @@ async function setPreferredModel(model) {
|
|
|
199
202
|
/* best-effort */
|
|
200
203
|
}
|
|
201
204
|
}
|
|
205
|
+
async function getPreferredDogfoodLane(appId) {
|
|
206
|
+
if (!AsyncStorage || !appId)
|
|
207
|
+
return null;
|
|
208
|
+
try {
|
|
209
|
+
const value = await AsyncStorage.getItem(`${PREFERRED_DOGFOOD_LANE_PREFIX}${appId}`);
|
|
210
|
+
return value === 'browser' || value === 'hermes' || value === 'webrtc' ? value : null;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function setPreferredDogfoodLane(appId, lane) {
|
|
217
|
+
if (!AsyncStorage || !appId)
|
|
218
|
+
return;
|
|
219
|
+
try {
|
|
220
|
+
await AsyncStorage.setItem(`${PREFERRED_DOGFOOD_LANE_PREFIX}${appId}`, lane);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
/* best-effort */
|
|
224
|
+
}
|
|
225
|
+
}
|
package/ios/YaverHotReload.m
CHANGED
|
@@ -13,4 +13,12 @@ RCT_EXTERN_METHOD(hasBundle:(RCTPromiseResolveBlock)resolve
|
|
|
13
13
|
RCT_EXTERN_METHOD(clearBundle:(RCTPromiseResolveBlock)resolve
|
|
14
14
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
15
15
|
|
|
16
|
+
RCT_EXTERN_METHOD(setDogfoodShortcut:(BOOL)enabled
|
|
17
|
+
label:(NSString *)label
|
|
18
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
19
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
20
|
+
|
|
21
|
+
RCT_EXTERN_METHOD(consumeDogfoodShortcut:(RCTPromiseResolveBlock)resolve
|
|
22
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
23
|
+
|
|
16
24
|
@end
|
package/ios/YaverHotReload.swift
CHANGED
|
@@ -18,6 +18,8 @@ class YaverHotReload: NSObject {
|
|
|
18
18
|
static let bundleDir = "yaver-hot-reload"
|
|
19
19
|
static let bundleFile = "main.jsbundle"
|
|
20
20
|
static let reloadNotification = Notification.Name("YaverHotReloadBundle")
|
|
21
|
+
static let dogfoodShortcutType = "io.yaver.feedback.dogfood"
|
|
22
|
+
static let dogfoodShortcutPendingKey = "yaverDogfoodShortcutPending"
|
|
21
23
|
|
|
22
24
|
// `requiresMainQueueSetup` is an RCTBridgeModule protocol method,
|
|
23
25
|
// not an NSObject method — so it must not be marked `override`.
|
|
@@ -101,6 +103,41 @@ class YaverHotReload: NSObject {
|
|
|
101
103
|
resolve(true)
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
/** Dynamic (ACL-backed) Home Screen quick action. Never put this in
|
|
107
|
+
* Info.plist: static shortcuts are visible before account authorization. */
|
|
108
|
+
@objc func setDogfoodShortcut(_ enabled: Bool,
|
|
109
|
+
label: String,
|
|
110
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
111
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
112
|
+
DispatchQueue.main.async {
|
|
113
|
+
var items = UIApplication.shared.shortcutItems ?? []
|
|
114
|
+
items.removeAll { $0.type == YaverHotReload.dogfoodShortcutType }
|
|
115
|
+
if enabled {
|
|
116
|
+
items.append(UIApplicationShortcutItem(
|
|
117
|
+
type: YaverHotReload.dogfoodShortcutType,
|
|
118
|
+
localizedTitle: label.isEmpty ? "Dogfood" : label,
|
|
119
|
+
localizedSubtitle: nil,
|
|
120
|
+
icon: UIApplicationShortcutIcon(type: .play),
|
|
121
|
+
userInfo: nil
|
|
122
|
+
))
|
|
123
|
+
}
|
|
124
|
+
UIApplication.shared.shortcutItems = items
|
|
125
|
+
resolve(enabled)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@objc func consumeDogfoodShortcut(_ resolve: RCTPromiseResolveBlock,
|
|
130
|
+
rejecter reject: RCTPromiseRejectBlock) {
|
|
131
|
+
let defaults = UserDefaults.standard
|
|
132
|
+
let pending = defaults.bool(forKey: YaverHotReload.dogfoodShortcutPendingKey)
|
|
133
|
+
if pending { defaults.removeObject(forKey: YaverHotReload.dogfoodShortcutPendingKey) }
|
|
134
|
+
resolve(pending)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@objc static func markDogfoodShortcutPending() {
|
|
138
|
+
UserDefaults.standard.set(true, forKey: dogfoodShortcutPendingKey)
|
|
139
|
+
}
|
|
140
|
+
|
|
104
141
|
// MARK: - Static helpers
|
|
105
142
|
|
|
106
143
|
static func savedBundlePath() -> URL {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yaver-feedback-react-native",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
4
4
|
"description": "Visual feedback SDK for Yaver — bug reports, screen recording, voice vibe coding, and local-first developer workflows",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"typescript": "^5.0.0"
|
|
80
80
|
},
|
|
81
81
|
"scripts": {
|
|
82
|
-
"build": "rm -rf dist &&
|
|
82
|
+
"build": "rm -rf dist && tsc -p tsconfig.json && test -f dist/index.js",
|
|
83
83
|
"prepublishOnly": "npm run build",
|
|
84
84
|
"test": "jest --runInBand",
|
|
85
85
|
"test:ci": "npm run build && npm test"
|
package/src/AuthOverlay.tsx
CHANGED
|
@@ -56,13 +56,22 @@ export const AuthOverlay: React.FC = () => {
|
|
|
56
56
|
const handleLoggedIn = async (newToken: string) => {
|
|
57
57
|
setToken(newToken);
|
|
58
58
|
await YaverFeedback.setAuthToken(newToken);
|
|
59
|
-
|
|
59
|
+
if (YaverFeedback.getDogfoodOnboarding()) {
|
|
60
|
+
closeAll();
|
|
61
|
+
await YaverFeedback.continueDogfoodOnboarding();
|
|
62
|
+
} else {
|
|
63
|
+
openPicker();
|
|
64
|
+
}
|
|
60
65
|
};
|
|
61
66
|
|
|
62
67
|
const handleDevicePicked = async (device: RemoteDevice) => {
|
|
63
68
|
await YaverFeedback.setPreferredDevice(device.deviceId);
|
|
64
69
|
closeAll();
|
|
65
|
-
|
|
70
|
+
if (YaverFeedback.getDogfoodOnboarding()) {
|
|
71
|
+
await YaverFeedback.continueDogfoodOnboarding();
|
|
72
|
+
} else {
|
|
73
|
+
DeviceEventEmitter.emit('yaverFeedback:startReport');
|
|
74
|
+
}
|
|
66
75
|
};
|
|
67
76
|
|
|
68
77
|
return (
|
package/src/FeedbackModal.tsx
CHANGED
|
@@ -47,8 +47,10 @@ import {
|
|
|
47
47
|
QuickIconColorPreset,
|
|
48
48
|
getPreferredModel,
|
|
49
49
|
getPreferredRunner,
|
|
50
|
+
getPreferredDogfoodLane,
|
|
50
51
|
setPreferredModel,
|
|
51
52
|
setPreferredRunner,
|
|
53
|
+
setPreferredDogfoodLane,
|
|
52
54
|
} from './preferences';
|
|
53
55
|
import {
|
|
54
56
|
DogfoodController,
|
|
@@ -323,7 +325,16 @@ export const FeedbackModal: React.FC = () => {
|
|
|
323
325
|
|| projects[0]
|
|
324
326
|
|| null;
|
|
325
327
|
setDogfoodProject((current) => current && projects.some((item) => item.path === current.path) ? current : preferred);
|
|
326
|
-
if (preferred)
|
|
328
|
+
if (preferred) {
|
|
329
|
+
const framework = preferred.framework || onboarding.framework || 'expo';
|
|
330
|
+
const capabilities = await client.getDogfoodRemoteRuntimeCapabilities(preferred.path, framework).catch(() => null);
|
|
331
|
+
const nativeRuntimeAvailable = !!capabilities?.targets.some((target) => target.enabled && target.id !== 'browser-window');
|
|
332
|
+
if (mountedRef.current) setDogfoodNativeAvailable(nativeRuntimeAvailable);
|
|
333
|
+
const savedLane = await getPreferredDogfoodLane(onboarding.appId);
|
|
334
|
+
const savedSupported = dogfoodLaneOptions(framework, { nativeRuntimeAvailable })
|
|
335
|
+
.some((option) => option.lane === savedLane && option.supported);
|
|
336
|
+
setDogfoodLane(savedLane && savedSupported ? savedLane : defaultDogfoodLane(framework));
|
|
337
|
+
}
|
|
327
338
|
} catch (cause) {
|
|
328
339
|
if (mountedRef.current) setDogfoodEnrollment({ status: 'failed', error: cause instanceof Error ? cause.message : String(cause) });
|
|
329
340
|
} finally {
|
|
@@ -551,8 +562,12 @@ export const FeedbackModal: React.FC = () => {
|
|
|
551
562
|
useEffect(() => {
|
|
552
563
|
mountedRef.current = true;
|
|
553
564
|
const sub = DeviceEventEmitter.addListener('yaverFeedback:startReport', () => {
|
|
554
|
-
|
|
555
|
-
|
|
565
|
+
const onboarding = YaverFeedback.getDogfoodOnboarding();
|
|
566
|
+
// Explicit Dogfood remains usable when passive capture/shake is off.
|
|
567
|
+
// The host still owns visibility; server OAuth/device signatures own
|
|
568
|
+
// authority. Keeping this event path independent avoids toggling the
|
|
569
|
+
// user's feedback preference merely to open Developer Mode.
|
|
570
|
+
if (YaverFeedback.isEnabled() || onboarding) {
|
|
556
571
|
const directDogfood = YaverFeedback.getDogfoodStatus().active;
|
|
557
572
|
setDogfoodActive(directDogfood);
|
|
558
573
|
setVisible(true);
|
|
@@ -1127,6 +1142,14 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1127
1142
|
const needsAuthRunnerCount = runnerCards.filter(
|
|
1128
1143
|
(row) => row.installed && !row.authConfigured && !row.ready,
|
|
1129
1144
|
).length;
|
|
1145
|
+
const selectedDogfoodRunner = preferredRunner
|
|
1146
|
+
? runnerCards.find((row) => row.id === preferredRunner) ?? null
|
|
1147
|
+
: null;
|
|
1148
|
+
const dogfoodRunnerReady = !!selectedDogfoodRunner
|
|
1149
|
+
&& (selectedDogfoodRunner.ready || selectedDogfoodRunner.authConfigured);
|
|
1150
|
+
const dogfoodModelReady = !selectedDogfoodRunner?.models?.length
|
|
1151
|
+
|| !!preferredModel && selectedDogfoodRunner.models.some((model) => model.id === preferredModel);
|
|
1152
|
+
const dogfoodStartBlocked = !dogfoodProject || !dogfoodRunnerReady || !dogfoodModelReady;
|
|
1130
1153
|
|
|
1131
1154
|
// Once the user fires off a vibe task, swap the entire modal body
|
|
1132
1155
|
// for the live chat screen. The chat manages its own SSE
|
|
@@ -1302,6 +1325,52 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1302
1325
|
</Pressable>
|
|
1303
1326
|
))}
|
|
1304
1327
|
</ScrollView>
|
|
1328
|
+
<Text style={styles.dogfoodStepLabel}>Coding agent</Text>
|
|
1329
|
+
<View style={styles.dogfoodChoiceRow}>
|
|
1330
|
+
{runnerCards.filter((row) => row.ready || row.authConfigured).map((row) => (
|
|
1331
|
+
<Pressable
|
|
1332
|
+
key={row.id}
|
|
1333
|
+
onPress={() => {
|
|
1334
|
+
const nextModel = row.models?.find((model) => model.isDefault)?.id || row.models?.[0]?.id || '';
|
|
1335
|
+
setPreferredRunnerState(row.id);
|
|
1336
|
+
setPreferredModelState(nextModel);
|
|
1337
|
+
void setPreferredRunner(row.id);
|
|
1338
|
+
void setPreferredModel(nextModel || null);
|
|
1339
|
+
}}
|
|
1340
|
+
style={[styles.dogfoodChoice, preferredRunner === row.id && styles.dogfoodChoiceSelected]}
|
|
1341
|
+
accessibilityRole="button"
|
|
1342
|
+
accessibilityState={{ selected: preferredRunner === row.id }}
|
|
1343
|
+
accessibilityLabel={`Use ${row.name} for Dogfood`}
|
|
1344
|
+
>
|
|
1345
|
+
<Text style={[styles.dogfoodChoiceText, preferredRunner === row.id && styles.dogfoodChoiceTextSelected]}>{row.name}</Text>
|
|
1346
|
+
</Pressable>
|
|
1347
|
+
))}
|
|
1348
|
+
</View>
|
|
1349
|
+
{readyRunnerCount === 0 ? (
|
|
1350
|
+
<Text style={styles.dogfoodWizardHint}>Sign in or configure a coding agent under Coding Agents below.</Text>
|
|
1351
|
+
) : null}
|
|
1352
|
+
{selectedDogfoodRunner?.models?.length ? (
|
|
1353
|
+
<>
|
|
1354
|
+
<Text style={styles.dogfoodStepLabel}>Model</Text>
|
|
1355
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.dogfoodChoiceRow}>
|
|
1356
|
+
{selectedDogfoodRunner.models.map((model) => (
|
|
1357
|
+
<Pressable
|
|
1358
|
+
key={model.id}
|
|
1359
|
+
onPress={() => {
|
|
1360
|
+
setPreferredModelState(model.id);
|
|
1361
|
+
void setPreferredModel(model.id);
|
|
1362
|
+
}}
|
|
1363
|
+
style={[styles.dogfoodChoice, preferredModel === model.id && styles.dogfoodChoiceSelected]}
|
|
1364
|
+
accessibilityRole="button"
|
|
1365
|
+
accessibilityState={{ selected: preferredModel === model.id }}
|
|
1366
|
+
accessibilityLabel={`Use ${model.name || model.id} model for Dogfood`}
|
|
1367
|
+
>
|
|
1368
|
+
<Text style={[styles.dogfoodChoiceText, preferredModel === model.id && styles.dogfoodChoiceTextSelected]}>{model.name || model.id}</Text>
|
|
1369
|
+
</Pressable>
|
|
1370
|
+
))}
|
|
1371
|
+
</ScrollView>
|
|
1372
|
+
</>
|
|
1373
|
+
) : null}
|
|
1305
1374
|
<Text style={styles.dogfoodStepLabel}>Runtime lane</Text>
|
|
1306
1375
|
<View style={styles.dogfoodChoiceRow}>
|
|
1307
1376
|
{dogfoodLaneOptions(
|
|
@@ -1310,7 +1379,12 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1310
1379
|
).map((option) => (
|
|
1311
1380
|
<Pressable
|
|
1312
1381
|
key={option.lane}
|
|
1313
|
-
onPress={() =>
|
|
1382
|
+
onPress={() => {
|
|
1383
|
+
if (!option.supported) return;
|
|
1384
|
+
setDogfoodLane(option.lane);
|
|
1385
|
+
const appId = YaverFeedback.getDogfoodOnboarding()?.appId;
|
|
1386
|
+
if (appId) void setPreferredDogfoodLane(appId, option.lane);
|
|
1387
|
+
}}
|
|
1314
1388
|
style={[
|
|
1315
1389
|
styles.dogfoodChoice,
|
|
1316
1390
|
dogfoodLane === option.lane && styles.dogfoodChoiceSelected,
|
|
@@ -1323,13 +1397,13 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1323
1397
|
))}
|
|
1324
1398
|
</View>
|
|
1325
1399
|
<Text style={styles.dogfoodWizardHint}>
|
|
1326
|
-
|
|
1400
|
+
{[preferredRunner || 'Choose a coding agent', preferredModel].filter(Boolean).join(' · ')}
|
|
1327
1401
|
</Text>
|
|
1328
1402
|
<ActionRow
|
|
1329
1403
|
label={dogfoodRuntime && !['idle', 'ready', 'failed', 'stopped'].includes(dogfoodRuntime.phase) ? dogfoodRuntime.message : 'Start Dogfood'}
|
|
1330
1404
|
tint="#818cf8"
|
|
1331
1405
|
onPress={() => void startDogfoodRuntime()}
|
|
1332
|
-
disabled={
|
|
1406
|
+
disabled={dogfoodStartBlocked || !!(dogfoodRuntime && !['idle', 'ready', 'failed', 'stopped'].includes(dogfoodRuntime.phase))}
|
|
1333
1407
|
busy={!!dogfoodRuntime && !['idle', 'ready', 'failed', 'stopped'].includes(dogfoodRuntime.phase)}
|
|
1334
1408
|
/>
|
|
1335
1409
|
{dogfoodRuntime ? (
|
|
@@ -1534,6 +1608,22 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1534
1608
|
</View>
|
|
1535
1609
|
)}
|
|
1536
1610
|
|
|
1611
|
+
{YaverFeedback.isAuthed() ? (
|
|
1612
|
+
<Pressable
|
|
1613
|
+
onPress={() => {
|
|
1614
|
+
void YaverFeedback.signOut().then(() => {
|
|
1615
|
+
handleClose();
|
|
1616
|
+
YaverFeedback.showLogin();
|
|
1617
|
+
});
|
|
1618
|
+
}}
|
|
1619
|
+
style={({ pressed }) => [styles.yaverSignOutBtn, pressed && styles.buttonPressed]}
|
|
1620
|
+
accessibilityRole="button"
|
|
1621
|
+
accessibilityLabel="Sign out of Yaver"
|
|
1622
|
+
>
|
|
1623
|
+
<Text style={styles.yaverSignOutText}>Sign out of Yaver</Text>
|
|
1624
|
+
</Pressable>
|
|
1625
|
+
) : null}
|
|
1626
|
+
|
|
1537
1627
|
<View style={styles.iconSelector}>
|
|
1538
1628
|
<Text style={styles.iconSelectorTitle}>Quick Icon Color</Text>
|
|
1539
1629
|
<Text style={styles.iconSelectorText}>
|
|
@@ -1803,6 +1893,8 @@ const styles = StyleSheet.create({
|
|
|
1803
1893
|
dogfoodConsoleError: { color: '#fca5a5', fontSize: 11, lineHeight: 16, marginTop: 5 },
|
|
1804
1894
|
dogfoodOpenPreview: { alignSelf: 'flex-start', borderRadius: 9, paddingHorizontal: 11, paddingVertical: 8, marginTop: 6, backgroundColor: '#6555df' },
|
|
1805
1895
|
dogfoodOpenPreviewText: { color: '#fff', fontSize: 12, fontWeight: '800' },
|
|
1896
|
+
yaverSignOutBtn: { alignSelf: 'flex-start', paddingHorizontal: 4, paddingVertical: 8 },
|
|
1897
|
+
yaverSignOutText: { color: '#b42318', fontSize: 13, fontWeight: '700' },
|
|
1806
1898
|
reloadRow: {
|
|
1807
1899
|
gap: 4,
|
|
1808
1900
|
},
|