yaver-feedback-react-native 0.9.7 → 0.9.8
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 +39 -7
- package/android/src/main/java/io/yaver/feedback/YaverDogfoodGestureModule.java +241 -0
- package/android/src/main/java/io/yaver/feedback/YaverHotReloadPackage.java +1 -0
- package/app.plugin.js +12 -2
- package/dist/AuthOverlay.js +13 -2
- package/dist/DogfoodQuickControls.d.ts +8 -0
- package/dist/DogfoodQuickControls.js +394 -0
- package/dist/DogfoodSessionUi.d.ts +50 -0
- package/dist/DogfoodSessionUi.js +135 -0
- package/dist/FeedbackModal.js +50 -22
- package/dist/YaverFeedback.d.ts +35 -1
- package/dist/YaverFeedback.js +302 -12
- package/dist/__tests__/NativeDogfoodShortcut.test.js +14 -0
- package/dist/__tests__/YaverFeedback.test.js +148 -0
- package/dist/auth.d.ts +15 -0
- package/dist/auth.js +41 -2
- package/dist/dogfoodPolicy.d.ts +22 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +8 -2
- package/dist/preferences.d.ts +13 -0
- package/dist/preferences.js +84 -0
- package/ios/YaverDogfoodGesture.m +14 -0
- package/ios/YaverDogfoodGesture.swift +175 -0
- package/package.json +1 -1
- package/src/AuthOverlay.tsx +20 -3
- package/src/DogfoodQuickControls.tsx +454 -0
- package/src/DogfoodSessionUi.tsx +213 -0
- package/src/FeedbackModal.tsx +62 -34
- package/src/YaverFeedback.ts +333 -12
- package/src/__tests__/NativeDogfoodShortcut.test.ts +15 -0
- package/src/__tests__/YaverFeedback.test.ts +155 -0
- package/src/auth.ts +53 -2
- package/src/dogfoodPolicy.ts +22 -1
- package/src/index.ts +13 -1
- package/src/preferences.ts +89 -0
package/dist/FeedbackModal.js
CHANGED
|
@@ -44,12 +44,14 @@ const AuthOverlay_1 = require("./AuthOverlay");
|
|
|
44
44
|
const QuickActionIcon_1 = require("./QuickActionIcon");
|
|
45
45
|
const YaverModeBadge_1 = require("./YaverModeBadge");
|
|
46
46
|
const VibeChatScreen_1 = require("./VibeChatScreen");
|
|
47
|
+
const DogfoodQuickControls_1 = require("./DogfoodQuickControls");
|
|
47
48
|
const DeployPanel_1 = require("./DeployPanel");
|
|
48
49
|
const auth_1 = require("./auth");
|
|
49
50
|
const reloadActions_1 = require("./reloadActions");
|
|
50
51
|
const preferences_1 = require("./preferences");
|
|
51
52
|
const DogfoodRuntime_1 = require("./DogfoodRuntime");
|
|
52
53
|
const P2PDogfoodDriver_1 = require("./P2PDogfoodDriver");
|
|
54
|
+
const DogfoodSessionUi_1 = require("./DogfoodSessionUi");
|
|
53
55
|
const PRIMARY_RUNNER_IDS = ['claude', 'codex', 'opencode'];
|
|
54
56
|
function normalizeRunnerStatusRows(rows) {
|
|
55
57
|
const byId = new Map();
|
|
@@ -1023,7 +1025,42 @@ const FeedbackModal = () => {
|
|
|
1023
1025
|
&& (selectedDogfoodRunner.ready || selectedDogfoodRunner.authConfigured);
|
|
1024
1026
|
const dogfoodModelReady = !selectedDogfoodRunner?.models?.length
|
|
1025
1027
|
|| !!preferredModel && selectedDogfoodRunner.models.some((model) => model.id === preferredModel);
|
|
1026
|
-
const
|
|
1028
|
+
const dogfoodLaneChoices = (0, DogfoodRuntime_1.dogfoodLaneOptions)(dogfoodProject?.framework || YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.framework || 'expo', { nativeRuntimeAvailable: dogfoodNativeAvailable });
|
|
1029
|
+
const dogfoodLaneReady = dogfoodLaneChoices.some((option) => option.lane === dogfoodLane && option.supported);
|
|
1030
|
+
const dogfoodReadinessSteps = [
|
|
1031
|
+
{
|
|
1032
|
+
key: 'oauth', label: 'Yaver OAuth',
|
|
1033
|
+
detail: YaverFeedback_1.YaverFeedback.isAuthed() ? 'Signed in · session saved' : 'Sign in required',
|
|
1034
|
+
tone: YaverFeedback_1.YaverFeedback.isAuthed() ? 'ready' : 'attention',
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
key: 'machine', label: 'Remote PC',
|
|
1038
|
+
detail: machineCard.device ? machineCard.title : 'Choose a reachable development machine',
|
|
1039
|
+
tone: machineCard.device && machineCard.status === 'live' ? 'ready' : 'attention',
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
key: 'installation', label: 'This installation',
|
|
1043
|
+
detail: dogfoodEnrollment?.status === 'active' ? 'Device key approved' : dogfoodEnrollment?.status || 'Checking device key',
|
|
1044
|
+
tone: dogfoodEnrollment?.status === 'active' ? 'ready'
|
|
1045
|
+
: dogfoodEnrollment?.status === 'failed' ? 'blocked' : 'pending',
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
key: 'runner', label: 'Runner',
|
|
1049
|
+
detail: dogfoodRunnerReady ? selectedDogfoodRunner?.name || preferredRunner || 'Ready' : 'Choose or configure a coding runner',
|
|
1050
|
+
tone: dogfoodRunnerReady ? 'ready' : 'attention',
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
key: 'model', label: 'Model',
|
|
1054
|
+
detail: dogfoodModelReady ? preferredModel || 'Runner default' : 'Choose a model',
|
|
1055
|
+
tone: dogfoodModelReady ? 'ready' : 'attention',
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
key: 'lane', label: 'Runtime lane',
|
|
1059
|
+
detail: dogfoodLaneChoices.find((option) => option.lane === dogfoodLane)?.label || dogfoodLane,
|
|
1060
|
+
tone: dogfoodLaneReady ? 'ready' : 'blocked',
|
|
1061
|
+
},
|
|
1062
|
+
];
|
|
1063
|
+
const dogfoodStartBlocked = !dogfoodProject || !dogfoodRunnerReady || !dogfoodModelReady || !dogfoodLaneReady;
|
|
1027
1064
|
// Once the user fires off a vibe task, swap the entire modal body
|
|
1028
1065
|
// for the live chat screen. The chat manages its own SSE
|
|
1029
1066
|
// subscription, multi-turn follow-ups, and Reload button. Closing
|
|
@@ -1032,6 +1069,7 @@ const FeedbackModal = () => {
|
|
|
1032
1069
|
const client = YaverFeedback_1.YaverFeedback.getP2PClient();
|
|
1033
1070
|
return (<>
|
|
1034
1071
|
<AuthOverlay_1.AuthOverlay />
|
|
1072
|
+
<DogfoodQuickControls_1.DogfoodQuickControls />
|
|
1035
1073
|
<QuickActionIcon_1.QuickActionIcon />
|
|
1036
1074
|
<YaverModeBadgeGate />
|
|
1037
1075
|
<react_native_1.Modal visible={visible} animationType="slide" transparent onRequestClose={handleClose}>
|
|
@@ -1049,6 +1087,7 @@ const FeedbackModal = () => {
|
|
|
1049
1087
|
}
|
|
1050
1088
|
return (<>
|
|
1051
1089
|
<AuthOverlay_1.AuthOverlay />
|
|
1090
|
+
<DogfoodQuickControls_1.DogfoodQuickControls />
|
|
1052
1091
|
<QuickActionIcon_1.QuickActionIcon />
|
|
1053
1092
|
<YaverModeBadgeGate />
|
|
1054
1093
|
{visible && (<react_native_1.Modal visible={visible} animationType="slide" transparent onRequestClose={handleClose}>
|
|
@@ -1101,6 +1140,7 @@ const FeedbackModal = () => {
|
|
|
1101
1140
|
<react_native_1.Text style={styles.dogfoodWizardHint}>
|
|
1102
1141
|
OAuth ✓ · machine {machineCard.device ? '✓' : 'required'} · installation {dogfoodEnrollment?.status || 'checking'}
|
|
1103
1142
|
</react_native_1.Text>
|
|
1143
|
+
<DogfoodSessionUi_1.DogfoodStatusRail steps={dogfoodReadinessSteps}/>
|
|
1104
1144
|
{dogfoodEnrollment?.installationId ? (<react_native_1.Text selectable style={styles.dogfoodInstallationId}>
|
|
1105
1145
|
This device · {dogfoodEnrollment.installationId}
|
|
1106
1146
|
</react_native_1.Text>) : null}
|
|
@@ -1148,34 +1188,22 @@ const FeedbackModal = () => {
|
|
|
1148
1188
|
</react_native_1.ScrollView>
|
|
1149
1189
|
</>) : null}
|
|
1150
1190
|
<react_native_1.Text style={styles.dogfoodStepLabel}>Runtime lane</react_native_1.Text>
|
|
1151
|
-
<
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
if (appId)
|
|
1158
|
-
void (0, preferences_1.setPreferredDogfoodLane)(appId, option.lane);
|
|
1159
|
-
}} style={[
|
|
1160
|
-
styles.dogfoodChoice,
|
|
1161
|
-
dogfoodLane === option.lane && styles.dogfoodChoiceSelected,
|
|
1162
|
-
!option.supported && styles.actionBtnDisabled,
|
|
1163
|
-
]} accessibilityState={{ disabled: !option.supported, selected: dogfoodLane === option.lane }}>
|
|
1164
|
-
<react_native_1.Text style={[styles.dogfoodChoiceText, dogfoodLane === option.lane && styles.dogfoodChoiceTextSelected]}>{option.label}</react_native_1.Text>
|
|
1165
|
-
</react_native_1.Pressable>))}
|
|
1166
|
-
</react_native_1.View>
|
|
1191
|
+
<DogfoodSessionUi_1.DogfoodLanePicker options={dogfoodLaneChoices} selected={dogfoodLane} onSelect={(lane) => {
|
|
1192
|
+
setDogfoodLane(lane);
|
|
1193
|
+
const appId = YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.appId;
|
|
1194
|
+
if (appId)
|
|
1195
|
+
void (0, preferences_1.setPreferredDogfoodLane)(appId, lane);
|
|
1196
|
+
}}/>
|
|
1167
1197
|
<react_native_1.Text style={styles.dogfoodWizardHint}>
|
|
1168
1198
|
{[preferredRunner || 'Choose a coding agent', preferredModel].filter(Boolean).join(' · ')}
|
|
1169
1199
|
</react_native_1.Text>
|
|
1170
1200
|
<ActionRow label={dogfoodRuntime && !['idle', 'ready', 'failed', 'stopped'].includes(dogfoodRuntime.phase) ? dogfoodRuntime.message : 'Start Dogfood'} tint="#818cf8" onPress={() => void startDogfoodRuntime()} disabled={dogfoodStartBlocked || !!(dogfoodRuntime && !['idle', 'ready', 'failed', 'stopped'].includes(dogfoodRuntime.phase))} busy={!!dogfoodRuntime && !['idle', 'ready', 'failed', 'stopped'].includes(dogfoodRuntime.phase)}/>
|
|
1171
|
-
{dogfoodRuntime ? (
|
|
1172
|
-
<
|
|
1173
|
-
{dogfoodRuntime.logs.slice(-80).map((line, index) => (<react_native_1.Text key={`${line.at}-${index}`} selectable style={styles.dogfoodConsoleLine}>{line.text}</react_native_1.Text>))}
|
|
1174
|
-
{dogfoodRuntime.failure ? (<react_native_1.Text style={styles.dogfoodConsoleError}>{dogfoodRuntime.failure.error}{'\n'}{dogfoodRuntime.failure.remedy}</react_native_1.Text>) : null}
|
|
1201
|
+
{dogfoodRuntime ? (<>
|
|
1202
|
+
<DogfoodSessionUi_1.DogfoodLiveConsole lane={dogfoodRuntime.project.lane} phase={dogfoodRuntime.phase} message={dogfoodRuntime.message} logs={dogfoodRuntime.logs} failure={dogfoodRuntime.failure}/>
|
|
1175
1203
|
{dogfoodRuntime.result?.url ? (<react_native_1.Pressable onPress={() => void react_native_1.Linking.openURL(dogfoodRuntime.result.url)} style={styles.dogfoodOpenPreview}>
|
|
1176
1204
|
<react_native_1.Text style={styles.dogfoodOpenPreviewText}>Open dogfooded app</react_native_1.Text>
|
|
1177
1205
|
</react_native_1.Pressable>) : null}
|
|
1178
|
-
|
|
1206
|
+
</>) : null}
|
|
1179
1207
|
</>)}
|
|
1180
1208
|
</react_native_1.View>) : null}
|
|
1181
1209
|
<react_native_1.Pressable onPress={() => {
|
package/dist/YaverFeedback.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FeedbackConfig, CapturedError } from './types';
|
|
2
2
|
import { P2PClient } from './P2PClient';
|
|
3
3
|
import { CaptureStoreScreenshotsOptions, CaptureStoreScreenshotsResult } from './storeShots';
|
|
4
|
-
import { QuickIconColorPreset } from './preferences';
|
|
4
|
+
import { type DogfoodControlPresentation, QuickIconColorPreset } from './preferences';
|
|
5
5
|
import { type SDKDogfoodStatus, type DogfoodAccessSnapshot } from './dogfoodPolicy';
|
|
6
6
|
import { type DeviceDogfoodOptions, type DeviceDogfoodSession, type DeviceDogfoodState } from './deviceDogfood';
|
|
7
7
|
export interface DogfoodOnboardingOptions extends DeviceDogfoodOptions {
|
|
@@ -16,6 +16,25 @@ export interface DogfoodFlowState {
|
|
|
16
16
|
appId?: string;
|
|
17
17
|
error?: string;
|
|
18
18
|
}
|
|
19
|
+
export interface DogfoodControlTriggerState {
|
|
20
|
+
configured: boolean;
|
|
21
|
+
authorized: boolean;
|
|
22
|
+
appId?: string;
|
|
23
|
+
installationId?: string;
|
|
24
|
+
gestureSupported: boolean;
|
|
25
|
+
gestureEnabled: boolean;
|
|
26
|
+
fallbackVisible: boolean;
|
|
27
|
+
presentation: DogfoodControlPresentation;
|
|
28
|
+
onboardingSeen: boolean;
|
|
29
|
+
reason: string;
|
|
30
|
+
platform?: string;
|
|
31
|
+
}
|
|
32
|
+
interface DogfoodControlSyncOptions {
|
|
33
|
+
presentation?: DogfoodControlPresentation;
|
|
34
|
+
onboardingSeen?: boolean;
|
|
35
|
+
/** User-initiated changes fail visibly when Convex cannot persist them. */
|
|
36
|
+
requirePersistence?: boolean;
|
|
37
|
+
}
|
|
19
38
|
/**
|
|
20
39
|
* Main entry point for the Yaver Feedback SDK.
|
|
21
40
|
* Call `YaverFeedback.init()` once at app startup.
|
|
@@ -130,6 +149,20 @@ export declare class YaverFeedback {
|
|
|
130
149
|
* owner/device ACL state. Static plist shortcuts are intentionally avoided:
|
|
131
150
|
* an unauthorized install must never advertise a hidden developer action. */
|
|
132
151
|
static syncDogfoodAppShortcut(): Promise<boolean>;
|
|
152
|
+
/** Capability-gated in-app Dogfood controls. Every new exact installation
|
|
153
|
+
* starts with Y until Convex records onboarding. A supported standalone app
|
|
154
|
+
* can then select a passive three-finger hold and no persistent pixels;
|
|
155
|
+
* missing native support or accessibility touch exploration keeps Y. Yaver's
|
|
156
|
+
* own container suppresses both because its split preview/chat UI owns them. */
|
|
157
|
+
static syncDogfoodControlGesture(syncOptions?: DogfoodControlSyncOptions): Promise<DogfoodControlTriggerState>;
|
|
158
|
+
/** Complete first-run onboarding or change the later control preference.
|
|
159
|
+
* Convex is authoritative and keyed by full Yaver user + app + installation;
|
|
160
|
+
* the local value is only a cold-start cache. */
|
|
161
|
+
static setDogfoodControlPresentation(presentation: DogfoodControlPresentation): Promise<DogfoodControlTriggerState>;
|
|
162
|
+
/** One-tap fast reload for the compact Dogfood card. It preserves the same
|
|
163
|
+
* selected render machine and bearer-authenticated P2P route as the full
|
|
164
|
+
* Feedback modal, and names missing auth/machine state instead of no-oping. */
|
|
165
|
+
static requestDogfoodFastReload(): Promise<string>;
|
|
133
166
|
/** Backwards-compatible entry point for existing integrations. */
|
|
134
167
|
static beginDogfoodOnboarding(options: DogfoodOnboardingOptions): Promise<DogfoodFlowState>;
|
|
135
168
|
/** Continue after OAuth or machine selection. Public so custom host UI can
|
|
@@ -416,3 +449,4 @@ export declare class YaverFeedback {
|
|
|
416
449
|
/** Tear down the SDK (stop shake detector, clear state). */
|
|
417
450
|
static destroy(): void;
|
|
418
451
|
}
|
|
452
|
+
export {};
|
package/dist/YaverFeedback.js
CHANGED
|
@@ -194,6 +194,11 @@ let dogfoodShortcutAppStateSubscription = null;
|
|
|
194
194
|
let dogfoodActivationSubscription = null;
|
|
195
195
|
let dogfoodShortcutLaunchInFlight = false;
|
|
196
196
|
let lastDogfoodActivationUrl = '';
|
|
197
|
+
function dogfoodControlPreferenceScope(appId, installationId) {
|
|
198
|
+
if (!appId || !installationId)
|
|
199
|
+
return undefined;
|
|
200
|
+
return `${appId}:${installationId}`;
|
|
201
|
+
}
|
|
197
202
|
function publishDogfoodFlow(state) {
|
|
198
203
|
dogfoodFlowState = state;
|
|
199
204
|
try {
|
|
@@ -533,20 +538,30 @@ class YaverFeedback {
|
|
|
533
538
|
if (config.autoLogin !== false && enabled) {
|
|
534
539
|
void YaverFeedback.hydrateSession();
|
|
535
540
|
}
|
|
536
|
-
if (config.dogfood?.appShortcut) {
|
|
537
|
-
|
|
538
|
-
|
|
541
|
+
if (config.dogfood?.appShortcut || config.dogfood?.controlGesture) {
|
|
542
|
+
if (config.dogfood?.appShortcut) {
|
|
543
|
+
void YaverFeedback.syncDogfoodAppShortcut();
|
|
544
|
+
void consumeNativeDogfoodShortcut();
|
|
545
|
+
}
|
|
546
|
+
if (config.dogfood?.controlGesture) {
|
|
547
|
+
void YaverFeedback.syncDogfoodControlGesture();
|
|
548
|
+
}
|
|
539
549
|
try {
|
|
540
550
|
const { AppState } = require('react-native');
|
|
541
551
|
dogfoodShortcutAppStateSubscription?.remove();
|
|
542
552
|
dogfoodShortcutAppStateSubscription = AppState.addEventListener('change', (state) => {
|
|
543
553
|
if (state === 'active') {
|
|
544
|
-
|
|
545
|
-
|
|
554
|
+
if (config?.dogfood?.appShortcut) {
|
|
555
|
+
void YaverFeedback.syncDogfoodAppShortcut();
|
|
556
|
+
void consumeNativeDogfoodShortcut();
|
|
557
|
+
}
|
|
558
|
+
if (config?.dogfood?.controlGesture) {
|
|
559
|
+
void YaverFeedback.syncDogfoodControlGesture();
|
|
560
|
+
}
|
|
546
561
|
}
|
|
547
562
|
});
|
|
548
563
|
}
|
|
549
|
-
catch { /* native
|
|
564
|
+
catch { /* native quick controls unavailable on web/test runtimes */ }
|
|
550
565
|
}
|
|
551
566
|
if (config.dogfood) {
|
|
552
567
|
try {
|
|
@@ -855,6 +870,7 @@ class YaverFeedback {
|
|
|
855
870
|
YaverFeedback.scheduleBlackBoxAutoStart();
|
|
856
871
|
}
|
|
857
872
|
await YaverFeedback.syncDogfoodAppShortcut().catch(() => false);
|
|
873
|
+
await YaverFeedback.syncDogfoodControlGesture().catch(() => undefined);
|
|
858
874
|
}
|
|
859
875
|
/** Returns true once the SDK has a session token it can use. */
|
|
860
876
|
static isAuthed() {
|
|
@@ -1005,16 +1021,21 @@ class YaverFeedback {
|
|
|
1005
1021
|
const token = config?.authToken || await (0, auth_1.getToken)();
|
|
1006
1022
|
const account = token
|
|
1007
1023
|
? await (0, auth_1.getDogfoodAccountAccess)(appId, token, installationId)
|
|
1008
|
-
: { authenticated: false, ownerAuthorized: false, installationAuthorized: false };
|
|
1024
|
+
: { authenticated: false, ownerAuthorized: false, accountAuthorized: false, installationAuthorized: false };
|
|
1009
1025
|
const yaverAuthenticated = account.authenticated;
|
|
1010
1026
|
const ownerAuthorized = account.ownerAuthorized;
|
|
1011
1027
|
return {
|
|
1012
1028
|
appId,
|
|
1013
1029
|
yaverAuthenticated,
|
|
1014
1030
|
ownerAuthorized,
|
|
1031
|
+
accountAuthorized: account.accountAuthorized,
|
|
1015
1032
|
installationId,
|
|
1016
1033
|
deviceState,
|
|
1017
1034
|
authorized: yaverAuthenticated && account.installationAuthorized && deviceState === 'active',
|
|
1035
|
+
controlPresentation: account.controlPresentation,
|
|
1036
|
+
gestureSupported: account.gestureSupported,
|
|
1037
|
+
gestureCapabilityReason: account.gestureCapabilityReason,
|
|
1038
|
+
controlOnboardingSeen: account.controlOnboardingSeen,
|
|
1018
1039
|
};
|
|
1019
1040
|
}
|
|
1020
1041
|
/** Add/remove the platform Home Screen shortcut from backend-authoritative
|
|
@@ -1044,6 +1065,243 @@ class YaverFeedback {
|
|
|
1044
1065
|
await native.setDogfoodShortcut(visible, label || 'Dogfood');
|
|
1045
1066
|
return visible;
|
|
1046
1067
|
}
|
|
1068
|
+
/** Capability-gated in-app Dogfood controls. Every new exact installation
|
|
1069
|
+
* starts with Y until Convex records onboarding. A supported standalone app
|
|
1070
|
+
* can then select a passive three-finger hold and no persistent pixels;
|
|
1071
|
+
* missing native support or accessibility touch exploration keeps Y. Yaver's
|
|
1072
|
+
* own container suppresses both because its split preview/chat UI owns them. */
|
|
1073
|
+
static async syncDogfoodControlGesture(syncOptions = {}) {
|
|
1074
|
+
const configured = config?.dogfood?.controlGesture;
|
|
1075
|
+
const native = react_native_1.NativeModules?.YaverDogfoodGesture;
|
|
1076
|
+
const base = {
|
|
1077
|
+
configured: Boolean(configured),
|
|
1078
|
+
authorized: false,
|
|
1079
|
+
gestureSupported: false,
|
|
1080
|
+
gestureEnabled: false,
|
|
1081
|
+
fallbackVisible: false,
|
|
1082
|
+
presentation: syncOptions.presentation || 'auto',
|
|
1083
|
+
onboardingSeen: syncOptions.onboardingSeen === true,
|
|
1084
|
+
reason: configured ? 'native-module-unavailable' : 'not-configured',
|
|
1085
|
+
platform: react_native_1.Platform.OS,
|
|
1086
|
+
};
|
|
1087
|
+
if (!configured) {
|
|
1088
|
+
if (typeof native?.setEnabled === 'function') {
|
|
1089
|
+
await native.setEnabled(false, 900).catch(() => undefined);
|
|
1090
|
+
}
|
|
1091
|
+
return base;
|
|
1092
|
+
}
|
|
1093
|
+
if (IS_HOST_MODE || isRunningInsideYaverHost()) {
|
|
1094
|
+
if (typeof native?.setEnabled === 'function') {
|
|
1095
|
+
await native.setEnabled(false, 900).catch(() => undefined);
|
|
1096
|
+
}
|
|
1097
|
+
return { ...base, reason: 'yaver-host-owns-controls' };
|
|
1098
|
+
}
|
|
1099
|
+
const access = await YaverFeedback.getDogfoodAccess();
|
|
1100
|
+
let authorized = access.authorized;
|
|
1101
|
+
if (authorized && config?.dogfood?.canShow) {
|
|
1102
|
+
try {
|
|
1103
|
+
authorized = await config.dogfood.canShow(access);
|
|
1104
|
+
}
|
|
1105
|
+
catch {
|
|
1106
|
+
authorized = false;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
const options = typeof configured === 'object' ? configured : {};
|
|
1110
|
+
const durationMs = Math.min(2000, Math.max(650, options.durationMs ?? 900));
|
|
1111
|
+
const allowFallback = options.fallback !== 'none';
|
|
1112
|
+
if (!authorized) {
|
|
1113
|
+
if (typeof native?.setEnabled === 'function') {
|
|
1114
|
+
await native.setEnabled(false, durationMs).catch(() => undefined);
|
|
1115
|
+
}
|
|
1116
|
+
return { ...base, appId: access.appId, installationId: access.installationId, reason: 'installation-not-authorized' };
|
|
1117
|
+
}
|
|
1118
|
+
const preferenceScope = dogfoodControlPreferenceScope(access.appId, access.installationId);
|
|
1119
|
+
const cachedPresentation = await (0, preferences_1.getDogfoodControlPresentation)(preferenceScope);
|
|
1120
|
+
const cachedOnboardingSeen = await (0, preferences_1.getDogfoodControlOnboardingSeen)(preferenceScope);
|
|
1121
|
+
const onboardingSeen = syncOptions.onboardingSeen
|
|
1122
|
+
?? access.controlOnboardingSeen
|
|
1123
|
+
?? cachedOnboardingSeen;
|
|
1124
|
+
// A newly authorized tester must never be left staring at an invisible
|
|
1125
|
+
// feature. Until the exact installation has completed onboarding in
|
|
1126
|
+
// Convex, the edge-docked Y wins even when the gesture is supported.
|
|
1127
|
+
const preferredPresentation = syncOptions.presentation
|
|
1128
|
+
|| access.controlPresentation
|
|
1129
|
+
|| cachedPresentation
|
|
1130
|
+
|| options.defaultPresentation
|
|
1131
|
+
|| 'auto';
|
|
1132
|
+
const presentation = onboardingSeen
|
|
1133
|
+
? preferredPresentation
|
|
1134
|
+
: 'minimized-y';
|
|
1135
|
+
if (typeof native?.getCapability !== 'function' || typeof native?.setEnabled !== 'function') {
|
|
1136
|
+
const result = {
|
|
1137
|
+
...base,
|
|
1138
|
+
authorized: true,
|
|
1139
|
+
appId: access.appId,
|
|
1140
|
+
installationId: access.installationId,
|
|
1141
|
+
presentation: 'minimized-y',
|
|
1142
|
+
onboardingSeen,
|
|
1143
|
+
fallbackVisible: allowFallback,
|
|
1144
|
+
};
|
|
1145
|
+
if (syncOptions.requirePersistence) {
|
|
1146
|
+
const token = config?.authToken || await (0, auth_1.getToken)();
|
|
1147
|
+
if (!token || !access.installationId) {
|
|
1148
|
+
throw new Error('A full Yaver session is required to save Dogfood settings.');
|
|
1149
|
+
}
|
|
1150
|
+
const persisted = await (0, auth_1.setDogfoodControlPreference)({
|
|
1151
|
+
appId: access.appId,
|
|
1152
|
+
installationId: access.installationId,
|
|
1153
|
+
token,
|
|
1154
|
+
presentation: 'minimized-y',
|
|
1155
|
+
gestureSupported: false,
|
|
1156
|
+
gestureCapabilityReason: 'native-module-unavailable',
|
|
1157
|
+
gesturePlatform: react_native_1.Platform.OS,
|
|
1158
|
+
controlOnboardingSeen: onboardingSeen,
|
|
1159
|
+
});
|
|
1160
|
+
if (!persisted)
|
|
1161
|
+
throw new Error('Could not save this Dogfood control preference to Yaver.');
|
|
1162
|
+
await (0, preferences_1.setDogfoodControlPresentation)('minimized-y', preferenceScope);
|
|
1163
|
+
if (onboardingSeen)
|
|
1164
|
+
await (0, preferences_1.setDogfoodControlOnboardingSeen)(true, preferenceScope);
|
|
1165
|
+
}
|
|
1166
|
+
return result;
|
|
1167
|
+
}
|
|
1168
|
+
try {
|
|
1169
|
+
const capability = await native.getCapability();
|
|
1170
|
+
const gestureSupported = capability?.supported === true;
|
|
1171
|
+
const shouldEnableGesture = gestureSupported && onboardingSeen && presentation === 'auto';
|
|
1172
|
+
const applied = await native.setEnabled(shouldEnableGesture, durationMs);
|
|
1173
|
+
const gestureEnabled = shouldEnableGesture && applied?.enabled === true;
|
|
1174
|
+
const gestureEnableFailed = shouldEnableGesture && !gestureEnabled;
|
|
1175
|
+
const result = {
|
|
1176
|
+
configured: true,
|
|
1177
|
+
authorized: true,
|
|
1178
|
+
appId: access.appId,
|
|
1179
|
+
installationId: access.installationId,
|
|
1180
|
+
gestureSupported,
|
|
1181
|
+
gestureEnabled,
|
|
1182
|
+
fallbackVisible: allowFallback && (!gestureSupported || presentation === 'minimized-y' || gestureEnableFailed),
|
|
1183
|
+
presentation,
|
|
1184
|
+
onboardingSeen,
|
|
1185
|
+
reason: gestureEnableFailed
|
|
1186
|
+
? 'gesture-enable-failed'
|
|
1187
|
+
: String(applied?.reason || capability?.reason || (gestureSupported ? 'supported' : 'unsupported')),
|
|
1188
|
+
platform: String(applied?.platform || capability?.platform || react_native_1.Platform.OS),
|
|
1189
|
+
};
|
|
1190
|
+
const token = config?.authToken || await (0, auth_1.getToken)();
|
|
1191
|
+
if (syncOptions.requirePersistence && (!token || !access.installationId)) {
|
|
1192
|
+
await native.setEnabled(false, durationMs).catch(() => undefined);
|
|
1193
|
+
throw new Error('A full Yaver session is required to save Dogfood settings.');
|
|
1194
|
+
}
|
|
1195
|
+
if (token && access.installationId) {
|
|
1196
|
+
const needsPersistence = access.controlPresentation !== presentation
|
|
1197
|
+
|| access.gestureSupported !== gestureSupported
|
|
1198
|
+
|| access.gestureCapabilityReason !== result.reason
|
|
1199
|
+
|| (syncOptions.onboardingSeen === true && access.controlOnboardingSeen !== true);
|
|
1200
|
+
if (needsPersistence) {
|
|
1201
|
+
const persisted = await (0, auth_1.setDogfoodControlPreference)({
|
|
1202
|
+
appId: access.appId,
|
|
1203
|
+
installationId: access.installationId,
|
|
1204
|
+
token,
|
|
1205
|
+
presentation,
|
|
1206
|
+
gestureSupported,
|
|
1207
|
+
gestureCapabilityReason: result.reason,
|
|
1208
|
+
gesturePlatform: result.platform || react_native_1.Platform.OS,
|
|
1209
|
+
controlOnboardingSeen: onboardingSeen,
|
|
1210
|
+
});
|
|
1211
|
+
if (!persisted && syncOptions.requirePersistence) {
|
|
1212
|
+
await native.setEnabled(false, durationMs).catch(() => undefined);
|
|
1213
|
+
throw new Error('Could not save this Dogfood control preference to Yaver.');
|
|
1214
|
+
}
|
|
1215
|
+
if (persisted) {
|
|
1216
|
+
await (0, preferences_1.setDogfoodControlPresentation)(presentation, preferenceScope);
|
|
1217
|
+
if (onboardingSeen)
|
|
1218
|
+
await (0, preferences_1.setDogfoodControlOnboardingSeen)(true, preferenceScope);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
else {
|
|
1222
|
+
await (0, preferences_1.setDogfoodControlPresentation)(presentation, preferenceScope);
|
|
1223
|
+
if (onboardingSeen)
|
|
1224
|
+
await (0, preferences_1.setDogfoodControlOnboardingSeen)(true, preferenceScope);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
return result;
|
|
1228
|
+
}
|
|
1229
|
+
catch (error) {
|
|
1230
|
+
await native.setEnabled(false, durationMs).catch(() => undefined);
|
|
1231
|
+
if (syncOptions.requirePersistence) {
|
|
1232
|
+
throw error instanceof Error
|
|
1233
|
+
? error
|
|
1234
|
+
: new Error('Could not save this Dogfood control preference to Yaver.');
|
|
1235
|
+
}
|
|
1236
|
+
return {
|
|
1237
|
+
...base,
|
|
1238
|
+
authorized: true,
|
|
1239
|
+
appId: access.appId,
|
|
1240
|
+
installationId: access.installationId,
|
|
1241
|
+
presentation: 'minimized-y',
|
|
1242
|
+
onboardingSeen,
|
|
1243
|
+
fallbackVisible: allowFallback,
|
|
1244
|
+
reason: 'native-capability-check-failed',
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
/** Complete first-run onboarding or change the later control preference.
|
|
1249
|
+
* Convex is authoritative and keyed by full Yaver user + app + installation;
|
|
1250
|
+
* the local value is only a cold-start cache. */
|
|
1251
|
+
static async setDogfoodControlPresentation(presentation) {
|
|
1252
|
+
if (presentation !== 'auto' && presentation !== 'minimized-y') {
|
|
1253
|
+
throw new Error('Invalid Dogfood control presentation.');
|
|
1254
|
+
}
|
|
1255
|
+
return YaverFeedback.syncDogfoodControlGesture({
|
|
1256
|
+
presentation,
|
|
1257
|
+
onboardingSeen: true,
|
|
1258
|
+
requirePersistence: true,
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
/** One-tap fast reload for the compact Dogfood card. It preserves the same
|
|
1262
|
+
* selected render machine and bearer-authenticated P2P route as the full
|
|
1263
|
+
* Feedback modal, and names missing auth/machine state instead of no-oping. */
|
|
1264
|
+
static async requestDogfoodFastReload() {
|
|
1265
|
+
await YaverFeedback.hydrateSession();
|
|
1266
|
+
if (!config?.authToken) {
|
|
1267
|
+
YaverFeedback.showLogin();
|
|
1268
|
+
throw new Error('Sign in to Yaver before requesting Fast Reload.');
|
|
1269
|
+
}
|
|
1270
|
+
if (!config.preferredDeviceId) {
|
|
1271
|
+
YaverFeedback.showMachinePicker();
|
|
1272
|
+
throw new Error('Choose a render machine before requesting Fast Reload.');
|
|
1273
|
+
}
|
|
1274
|
+
const selected = await YaverFeedback.getSelectedRemoteDevice();
|
|
1275
|
+
if (!selected) {
|
|
1276
|
+
YaverFeedback.showMachinePicker();
|
|
1277
|
+
throw new Error('The selected render machine is no longer available.');
|
|
1278
|
+
}
|
|
1279
|
+
if (selected.needsAuth) {
|
|
1280
|
+
YaverFeedback.showMachinePicker();
|
|
1281
|
+
throw new Error('The selected render machine needs pairing again.');
|
|
1282
|
+
}
|
|
1283
|
+
if (!selected.isOnline) {
|
|
1284
|
+
throw new Error('The selected render machine is offline.');
|
|
1285
|
+
}
|
|
1286
|
+
let client = YaverFeedback.getRenderP2PClient();
|
|
1287
|
+
if (!client && await YaverFeedback.reconnect()) {
|
|
1288
|
+
client = YaverFeedback.getRenderP2PClient();
|
|
1289
|
+
}
|
|
1290
|
+
if (!client)
|
|
1291
|
+
throw new Error('The render machine is not connected yet.');
|
|
1292
|
+
try {
|
|
1293
|
+
const ack = await client.reloadWithMode('fast');
|
|
1294
|
+
return ack.message;
|
|
1295
|
+
}
|
|
1296
|
+
catch (firstError) {
|
|
1297
|
+
if (await YaverFeedback.reconnect()) {
|
|
1298
|
+
const retry = YaverFeedback.getRenderP2PClient();
|
|
1299
|
+
if (retry)
|
|
1300
|
+
return (await retry.reloadWithMode('fast')).message;
|
|
1301
|
+
}
|
|
1302
|
+
throw firstError;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1047
1305
|
/** Backwards-compatible entry point for existing integrations. */
|
|
1048
1306
|
static async beginDogfoodOnboarding(options) {
|
|
1049
1307
|
YaverFeedback.configureDogfood(options);
|
|
@@ -1066,11 +1324,24 @@ class YaverFeedback {
|
|
|
1066
1324
|
publishDogfoodFlow(state);
|
|
1067
1325
|
return state;
|
|
1068
1326
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1327
|
+
let token;
|
|
1328
|
+
let account;
|
|
1329
|
+
try {
|
|
1330
|
+
await YaverFeedback.hydrateSession();
|
|
1331
|
+
token = config?.authToken || await (0, auth_1.getToken)();
|
|
1332
|
+
account = token
|
|
1333
|
+
? await (0, auth_1.getDogfoodAccountAccess)(appId, token)
|
|
1334
|
+
: { authenticated: false, ownerAuthorized: false, accountAuthorized: false, installationAuthorized: false };
|
|
1335
|
+
}
|
|
1336
|
+
catch (cause) {
|
|
1337
|
+
const state = {
|
|
1338
|
+
phase: 'error',
|
|
1339
|
+
appId,
|
|
1340
|
+
error: cause instanceof Error ? cause.message : 'Yaver could not verify Dogfood access.',
|
|
1341
|
+
};
|
|
1342
|
+
publishDogfoodFlow(state);
|
|
1343
|
+
return state;
|
|
1344
|
+
}
|
|
1074
1345
|
// A device key is a second factor for this installation, never a
|
|
1075
1346
|
// replacement for a real Yaver account. Narrow installation sessions and
|
|
1076
1347
|
// stale/invalid cached tokens both return authenticated=false here.
|
|
@@ -1080,6 +1351,15 @@ class YaverFeedback {
|
|
|
1080
1351
|
YaverFeedback.showLogin();
|
|
1081
1352
|
return state;
|
|
1082
1353
|
}
|
|
1354
|
+
if (!account.accountAuthorized) {
|
|
1355
|
+
const state = {
|
|
1356
|
+
phase: 'denied',
|
|
1357
|
+
appId,
|
|
1358
|
+
error: 'The app owner has not enabled Dogfood for this Yaver account.',
|
|
1359
|
+
};
|
|
1360
|
+
publishDogfoodFlow(state);
|
|
1361
|
+
return state;
|
|
1362
|
+
}
|
|
1083
1363
|
if (!config?.preferredDeviceId) {
|
|
1084
1364
|
const state = { phase: 'machine-required', appId };
|
|
1085
1365
|
publishDogfoodFlow(state);
|
|
@@ -1191,6 +1471,7 @@ class YaverFeedback {
|
|
|
1191
1471
|
renderP2PClient = null;
|
|
1192
1472
|
p2pAuthToken = null;
|
|
1193
1473
|
await YaverFeedback.syncDogfoodAppShortcut().catch(() => false);
|
|
1474
|
+
await YaverFeedback.syncDogfoodControlGesture().catch(() => undefined);
|
|
1194
1475
|
}
|
|
1195
1476
|
/**
|
|
1196
1477
|
* Manually trigger the feedback collection flow.
|
|
@@ -1475,6 +1756,7 @@ class YaverFeedback {
|
|
|
1475
1756
|
}
|
|
1476
1757
|
catch { /* noop */ }
|
|
1477
1758
|
await YaverFeedback.syncDogfoodAppShortcut().catch(() => false);
|
|
1759
|
+
await YaverFeedback.syncDogfoodControlGesture().catch(() => undefined);
|
|
1478
1760
|
return { status, installationId: info.installationId, session };
|
|
1479
1761
|
}
|
|
1480
1762
|
/** Confirmed by the Y badge/UI before this is called. Reverts this SDK to
|
|
@@ -1490,6 +1772,7 @@ class YaverFeedback {
|
|
|
1490
1772
|
DeviceEventEmitter.emit('yaverFeedback:dogfoodChanged', { active: false, exited: true });
|
|
1491
1773
|
}
|
|
1492
1774
|
catch { /* noop */ }
|
|
1775
|
+
await YaverFeedback.syncDogfoodControlGesture().catch(() => undefined);
|
|
1493
1776
|
}
|
|
1494
1777
|
/** Returns the resolved relay password the SDK is currently using.
|
|
1495
1778
|
* Empty string when no relay routing is in play (direct LAN agent
|
|
@@ -1988,6 +2271,13 @@ class YaverFeedback {
|
|
|
1988
2271
|
dogfoodShortcutAppStateSubscription = null;
|
|
1989
2272
|
dogfoodActivationSubscription?.remove();
|
|
1990
2273
|
dogfoodActivationSubscription = null;
|
|
2274
|
+
try {
|
|
2275
|
+
const native = react_native_1.NativeModules?.YaverDogfoodGesture;
|
|
2276
|
+
if (typeof native?.setEnabled === 'function') {
|
|
2277
|
+
void native.setEnabled(false, 900).catch(() => undefined);
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
catch { /* native trigger teardown is best-effort */ }
|
|
1991
2281
|
lastDogfoodActivationUrl = '';
|
|
1992
2282
|
// Before `enabled = false` / `config = null` below, so an in-flight retry
|
|
1993
2283
|
// can't fire against a torn-down config.
|
|
@@ -49,4 +49,18 @@ final class TalosSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
|
49
49
|
expect(android).toContain('addDynamicShortcuts');
|
|
50
50
|
expect(android).toContain('consumeDogfoodShortcut');
|
|
51
51
|
});
|
|
52
|
+
it('observes a three-finger hold without consuming host app touches', () => {
|
|
53
|
+
const ios = (0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../../ios/YaverDogfoodGesture.swift'), 'utf8');
|
|
54
|
+
const android = (0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../../android/src/main/java/io/yaver/feedback/YaverDogfoodGestureModule.java'), 'utf8');
|
|
55
|
+
const controls = (0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../DogfoodQuickControls.tsx'), 'utf8');
|
|
56
|
+
expect(ios).toContain('numberOfTouchesRequired = 3');
|
|
57
|
+
expect(ios).toContain('cancelsTouchesInView = false');
|
|
58
|
+
expect(ios).toContain('isVoiceOverRunning');
|
|
59
|
+
expect(android).toContain('event.getPointerCount() >= 3');
|
|
60
|
+
expect(android).toContain('method.invoke(callback, args)');
|
|
61
|
+
expect(android).toContain('isTouchExplorationEnabled');
|
|
62
|
+
expect(controls).toContain('yaver-dogfood-fast-reload');
|
|
63
|
+
expect(controls).toContain('yaver-dogfood-chat');
|
|
64
|
+
expect(controls).toContain('yaver-dogfood-minimized-control');
|
|
65
|
+
});
|
|
52
66
|
});
|