yaver-feedback-react-native 0.9.7 → 0.9.9
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 +401 -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 +312 -12
- package/dist/__tests__/NativeDogfoodShortcut.test.js +14 -0
- package/dist/__tests__/YaverFeedback.test.js +173 -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 +461 -0
- package/src/DogfoodSessionUi.tsx +213 -0
- package/src/FeedbackModal.tsx +62 -34
- package/src/YaverFeedback.ts +343 -12
- package/src/__tests__/NativeDogfoodShortcut.test.ts +15 -0
- package/src/__tests__/YaverFeedback.test.ts +183 -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/src/YaverFeedback.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
getToken,
|
|
16
16
|
getSelectedDeviceId,
|
|
17
17
|
getDogfoodAccountAccess,
|
|
18
|
+
setDogfoodControlPreference,
|
|
18
19
|
listReachableDevices,
|
|
19
20
|
clearToken,
|
|
20
21
|
clearSelectedDeviceId,
|
|
@@ -25,6 +26,11 @@ import {
|
|
|
25
26
|
getQuickIconColorPreset,
|
|
26
27
|
setQuickIconDisabled,
|
|
27
28
|
setQuickIconColorPreset,
|
|
29
|
+
getDogfoodControlPresentation,
|
|
30
|
+
setDogfoodControlPresentation as cacheDogfoodControlPresentation,
|
|
31
|
+
getDogfoodControlOnboardingSeen,
|
|
32
|
+
setDogfoodControlOnboardingSeen,
|
|
33
|
+
type DogfoodControlPresentation,
|
|
28
34
|
QuickIconColorPreset,
|
|
29
35
|
} from './preferences';
|
|
30
36
|
import { resolveSDKDogfood, type SDKDogfoodStatus, type DogfoodAccessSnapshot } from './dogfoodPolicy';
|
|
@@ -44,6 +50,27 @@ export interface DogfoodFlowState {
|
|
|
44
50
|
error?: string;
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
export interface DogfoodControlTriggerState {
|
|
54
|
+
configured: boolean;
|
|
55
|
+
authorized: boolean;
|
|
56
|
+
appId?: string;
|
|
57
|
+
installationId?: string;
|
|
58
|
+
gestureSupported: boolean;
|
|
59
|
+
gestureEnabled: boolean;
|
|
60
|
+
fallbackVisible: boolean;
|
|
61
|
+
presentation: DogfoodControlPresentation;
|
|
62
|
+
onboardingSeen: boolean;
|
|
63
|
+
reason: string;
|
|
64
|
+
platform?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface DogfoodControlSyncOptions {
|
|
68
|
+
presentation?: DogfoodControlPresentation;
|
|
69
|
+
onboardingSeen?: boolean;
|
|
70
|
+
/** User-initiated changes fail visibly when Convex cannot persist them. */
|
|
71
|
+
requirePersistence?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
47
74
|
function unrefTimer(timer: ReturnType<typeof setTimeout>): void {
|
|
48
75
|
const maybeNodeTimer = timer as unknown as { unref?: () => void };
|
|
49
76
|
if (typeof maybeNodeTimer.unref === 'function') {
|
|
@@ -194,6 +221,11 @@ let dogfoodActivationSubscription: { remove: () => void } | null = null;
|
|
|
194
221
|
let dogfoodShortcutLaunchInFlight = false;
|
|
195
222
|
let lastDogfoodActivationUrl = '';
|
|
196
223
|
|
|
224
|
+
function dogfoodControlPreferenceScope(appId?: string, installationId?: string): string | undefined {
|
|
225
|
+
if (!appId || !installationId) return undefined;
|
|
226
|
+
return `${appId}:${installationId}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
197
229
|
function publishDogfoodFlow(state: DogfoodFlowState): void {
|
|
198
230
|
dogfoodFlowState = state;
|
|
199
231
|
try { config?.dogfood?.onStateChange?.(state); } catch { /* host callback */ }
|
|
@@ -490,19 +522,29 @@ export class YaverFeedback {
|
|
|
490
522
|
void YaverFeedback.hydrateSession();
|
|
491
523
|
}
|
|
492
524
|
|
|
493
|
-
if (config.dogfood?.appShortcut) {
|
|
494
|
-
|
|
495
|
-
|
|
525
|
+
if (config.dogfood?.appShortcut || config.dogfood?.controlGesture) {
|
|
526
|
+
if (config.dogfood?.appShortcut) {
|
|
527
|
+
void YaverFeedback.syncDogfoodAppShortcut();
|
|
528
|
+
void consumeNativeDogfoodShortcut();
|
|
529
|
+
}
|
|
530
|
+
if (config.dogfood?.controlGesture) {
|
|
531
|
+
void YaverFeedback.syncDogfoodControlGesture();
|
|
532
|
+
}
|
|
496
533
|
try {
|
|
497
534
|
const { AppState } = require('react-native');
|
|
498
535
|
dogfoodShortcutAppStateSubscription?.remove();
|
|
499
536
|
dogfoodShortcutAppStateSubscription = AppState.addEventListener('change', (state: string) => {
|
|
500
537
|
if (state === 'active') {
|
|
501
|
-
|
|
502
|
-
|
|
538
|
+
if (config?.dogfood?.appShortcut) {
|
|
539
|
+
void YaverFeedback.syncDogfoodAppShortcut();
|
|
540
|
+
void consumeNativeDogfoodShortcut();
|
|
541
|
+
}
|
|
542
|
+
if (config?.dogfood?.controlGesture) {
|
|
543
|
+
void YaverFeedback.syncDogfoodControlGesture();
|
|
544
|
+
}
|
|
503
545
|
}
|
|
504
546
|
});
|
|
505
|
-
} catch { /* native
|
|
547
|
+
} catch { /* native quick controls unavailable on web/test runtimes */ }
|
|
506
548
|
}
|
|
507
549
|
if (config.dogfood) {
|
|
508
550
|
try {
|
|
@@ -805,6 +847,7 @@ export class YaverFeedback {
|
|
|
805
847
|
YaverFeedback.scheduleBlackBoxAutoStart();
|
|
806
848
|
}
|
|
807
849
|
await YaverFeedback.syncDogfoodAppShortcut().catch(() => false);
|
|
850
|
+
await YaverFeedback.syncDogfoodControlGesture().catch(() => undefined);
|
|
808
851
|
}
|
|
809
852
|
|
|
810
853
|
/** Returns true once the SDK has a session token it can use. */
|
|
@@ -963,16 +1006,21 @@ export class YaverFeedback {
|
|
|
963
1006
|
const token = config?.authToken || await getToken();
|
|
964
1007
|
const account = token
|
|
965
1008
|
? await getDogfoodAccountAccess(appId, token, installationId)
|
|
966
|
-
: { authenticated: false, ownerAuthorized: false, installationAuthorized: false };
|
|
1009
|
+
: { authenticated: false, ownerAuthorized: false, accountAuthorized: false, installationAuthorized: false };
|
|
967
1010
|
const yaverAuthenticated = account.authenticated;
|
|
968
1011
|
const ownerAuthorized = account.ownerAuthorized;
|
|
969
1012
|
return {
|
|
970
1013
|
appId,
|
|
971
1014
|
yaverAuthenticated,
|
|
972
1015
|
ownerAuthorized,
|
|
1016
|
+
accountAuthorized: account.accountAuthorized,
|
|
973
1017
|
installationId,
|
|
974
1018
|
deviceState,
|
|
975
1019
|
authorized: yaverAuthenticated && account.installationAuthorized && deviceState === 'active',
|
|
1020
|
+
controlPresentation: account.controlPresentation,
|
|
1021
|
+
gestureSupported: account.gestureSupported,
|
|
1022
|
+
gestureCapabilityReason: account.gestureCapabilityReason,
|
|
1023
|
+
controlOnboardingSeen: account.controlOnboardingSeen,
|
|
976
1024
|
};
|
|
977
1025
|
}
|
|
978
1026
|
|
|
@@ -1002,6 +1050,254 @@ export class YaverFeedback {
|
|
|
1002
1050
|
return visible;
|
|
1003
1051
|
}
|
|
1004
1052
|
|
|
1053
|
+
/** Capability-gated in-app Dogfood controls. Every new exact installation
|
|
1054
|
+
* starts with Y until Convex records onboarding. A supported standalone app
|
|
1055
|
+
* can then select a passive three-finger hold and no persistent pixels;
|
|
1056
|
+
* missing native support or accessibility touch exploration keeps Y. Yaver's
|
|
1057
|
+
* own container suppresses both because its split preview/chat UI owns them. */
|
|
1058
|
+
static async syncDogfoodControlGesture(
|
|
1059
|
+
syncOptions: DogfoodControlSyncOptions = {},
|
|
1060
|
+
): Promise<DogfoodControlTriggerState> {
|
|
1061
|
+
const configured = config?.dogfood?.controlGesture;
|
|
1062
|
+
const native = (NativeModules as any)?.YaverDogfoodGesture;
|
|
1063
|
+
const base: DogfoodControlTriggerState = {
|
|
1064
|
+
configured: Boolean(configured),
|
|
1065
|
+
authorized: false,
|
|
1066
|
+
gestureSupported: false,
|
|
1067
|
+
gestureEnabled: false,
|
|
1068
|
+
fallbackVisible: false,
|
|
1069
|
+
presentation: syncOptions.presentation || 'auto',
|
|
1070
|
+
onboardingSeen: syncOptions.onboardingSeen === true,
|
|
1071
|
+
reason: configured ? 'native-module-unavailable' : 'not-configured',
|
|
1072
|
+
platform: Platform.OS,
|
|
1073
|
+
};
|
|
1074
|
+
if (!configured) {
|
|
1075
|
+
if (typeof native?.setEnabled === 'function') {
|
|
1076
|
+
await native.setEnabled(false, 900).catch(() => undefined);
|
|
1077
|
+
}
|
|
1078
|
+
return base;
|
|
1079
|
+
}
|
|
1080
|
+
// Backend approval makes the installation eligible; it does not mean the
|
|
1081
|
+
// tester is currently in Dogfood. Keep all in-app controls absent until
|
|
1082
|
+
// enableDeviceDogfood() has minted a live scoped session, and make Exit
|
|
1083
|
+
// Dogfood actually remove both the gesture and fallback Y.
|
|
1084
|
+
if (!YaverFeedback.getDogfoodStatus().active) {
|
|
1085
|
+
if (typeof native?.setEnabled === 'function') {
|
|
1086
|
+
await native.setEnabled(false, 900).catch(() => undefined);
|
|
1087
|
+
}
|
|
1088
|
+
return { ...base, reason: 'dogfood-session-inactive' };
|
|
1089
|
+
}
|
|
1090
|
+
if (IS_HOST_MODE || isRunningInsideYaverHost()) {
|
|
1091
|
+
if (typeof native?.setEnabled === 'function') {
|
|
1092
|
+
await native.setEnabled(false, 900).catch(() => undefined);
|
|
1093
|
+
}
|
|
1094
|
+
return { ...base, reason: 'yaver-host-owns-controls' };
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
const access = await YaverFeedback.getDogfoodAccess();
|
|
1098
|
+
let authorized = access.authorized;
|
|
1099
|
+
if (authorized && config?.dogfood?.canShow) {
|
|
1100
|
+
try {
|
|
1101
|
+
authorized = await config.dogfood.canShow(access);
|
|
1102
|
+
} catch {
|
|
1103
|
+
authorized = false;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
const options = typeof configured === 'object' ? configured : {};
|
|
1107
|
+
const durationMs = Math.min(2000, Math.max(650, options.durationMs ?? 900));
|
|
1108
|
+
const allowFallback = options.fallback !== 'none';
|
|
1109
|
+
if (!authorized) {
|
|
1110
|
+
if (typeof native?.setEnabled === 'function') {
|
|
1111
|
+
await native.setEnabled(false, durationMs).catch(() => undefined);
|
|
1112
|
+
}
|
|
1113
|
+
return { ...base, appId: access.appId, installationId: access.installationId, reason: 'installation-not-authorized' };
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const preferenceScope = dogfoodControlPreferenceScope(access.appId, access.installationId);
|
|
1117
|
+
const cachedPresentation = await getDogfoodControlPresentation(preferenceScope);
|
|
1118
|
+
const cachedOnboardingSeen = await getDogfoodControlOnboardingSeen(preferenceScope);
|
|
1119
|
+
const onboardingSeen = syncOptions.onboardingSeen
|
|
1120
|
+
?? access.controlOnboardingSeen
|
|
1121
|
+
?? cachedOnboardingSeen;
|
|
1122
|
+
// A newly authorized tester must never be left staring at an invisible
|
|
1123
|
+
// feature. Until the exact installation has completed onboarding in
|
|
1124
|
+
// Convex, the edge-docked Y wins even when the gesture is supported.
|
|
1125
|
+
const preferredPresentation = syncOptions.presentation
|
|
1126
|
+
|| access.controlPresentation
|
|
1127
|
+
|| cachedPresentation
|
|
1128
|
+
|| options.defaultPresentation
|
|
1129
|
+
|| 'auto';
|
|
1130
|
+
const presentation: DogfoodControlPresentation = onboardingSeen
|
|
1131
|
+
? preferredPresentation
|
|
1132
|
+
: 'minimized-y';
|
|
1133
|
+
|
|
1134
|
+
if (typeof native?.getCapability !== 'function' || typeof native?.setEnabled !== 'function') {
|
|
1135
|
+
const result: DogfoodControlTriggerState = {
|
|
1136
|
+
...base,
|
|
1137
|
+
authorized: true,
|
|
1138
|
+
appId: access.appId,
|
|
1139
|
+
installationId: access.installationId,
|
|
1140
|
+
presentation: 'minimized-y',
|
|
1141
|
+
onboardingSeen,
|
|
1142
|
+
fallbackVisible: allowFallback,
|
|
1143
|
+
};
|
|
1144
|
+
if (syncOptions.requirePersistence) {
|
|
1145
|
+
const token = config?.authToken || await getToken();
|
|
1146
|
+
if (!token || !access.installationId) {
|
|
1147
|
+
throw new Error('A full Yaver session is required to save Dogfood settings.');
|
|
1148
|
+
}
|
|
1149
|
+
const persisted = await setDogfoodControlPreference({
|
|
1150
|
+
appId: access.appId,
|
|
1151
|
+
installationId: access.installationId,
|
|
1152
|
+
token,
|
|
1153
|
+
presentation: 'minimized-y',
|
|
1154
|
+
gestureSupported: false,
|
|
1155
|
+
gestureCapabilityReason: 'native-module-unavailable',
|
|
1156
|
+
gesturePlatform: Platform.OS,
|
|
1157
|
+
controlOnboardingSeen: onboardingSeen,
|
|
1158
|
+
});
|
|
1159
|
+
if (!persisted) throw new Error('Could not save this Dogfood control preference to Yaver.');
|
|
1160
|
+
await cacheDogfoodControlPresentation('minimized-y', preferenceScope);
|
|
1161
|
+
if (onboardingSeen) await setDogfoodControlOnboardingSeen(true, preferenceScope);
|
|
1162
|
+
}
|
|
1163
|
+
return result;
|
|
1164
|
+
}
|
|
1165
|
+
try {
|
|
1166
|
+
const capability = await native.getCapability();
|
|
1167
|
+
const gestureSupported = capability?.supported === true;
|
|
1168
|
+
const shouldEnableGesture = gestureSupported && onboardingSeen && presentation === 'auto';
|
|
1169
|
+
const applied = await native.setEnabled(shouldEnableGesture, durationMs);
|
|
1170
|
+
const gestureEnabled = shouldEnableGesture && applied?.enabled === true;
|
|
1171
|
+
const gestureEnableFailed = shouldEnableGesture && !gestureEnabled;
|
|
1172
|
+
const result: DogfoodControlTriggerState = {
|
|
1173
|
+
configured: true,
|
|
1174
|
+
authorized: true,
|
|
1175
|
+
appId: access.appId,
|
|
1176
|
+
installationId: access.installationId,
|
|
1177
|
+
gestureSupported,
|
|
1178
|
+
gestureEnabled,
|
|
1179
|
+
fallbackVisible: allowFallback && (!gestureSupported || presentation === 'minimized-y' || gestureEnableFailed),
|
|
1180
|
+
presentation,
|
|
1181
|
+
onboardingSeen,
|
|
1182
|
+
reason: gestureEnableFailed
|
|
1183
|
+
? 'gesture-enable-failed'
|
|
1184
|
+
: String(applied?.reason || capability?.reason || (gestureSupported ? 'supported' : 'unsupported')),
|
|
1185
|
+
platform: String(applied?.platform || capability?.platform || Platform.OS),
|
|
1186
|
+
};
|
|
1187
|
+
const token = config?.authToken || await getToken();
|
|
1188
|
+
if (syncOptions.requirePersistence && (!token || !access.installationId)) {
|
|
1189
|
+
await native.setEnabled(false, durationMs).catch(() => undefined);
|
|
1190
|
+
throw new Error('A full Yaver session is required to save Dogfood settings.');
|
|
1191
|
+
}
|
|
1192
|
+
if (token && access.installationId) {
|
|
1193
|
+
const needsPersistence =
|
|
1194
|
+
access.controlPresentation !== presentation
|
|
1195
|
+
|| access.gestureSupported !== gestureSupported
|
|
1196
|
+
|| access.gestureCapabilityReason !== result.reason
|
|
1197
|
+
|| (syncOptions.onboardingSeen === true && access.controlOnboardingSeen !== true);
|
|
1198
|
+
if (needsPersistence) {
|
|
1199
|
+
const persisted = await setDogfoodControlPreference({
|
|
1200
|
+
appId: access.appId,
|
|
1201
|
+
installationId: access.installationId,
|
|
1202
|
+
token,
|
|
1203
|
+
presentation,
|
|
1204
|
+
gestureSupported,
|
|
1205
|
+
gestureCapabilityReason: result.reason,
|
|
1206
|
+
gesturePlatform: result.platform || Platform.OS,
|
|
1207
|
+
controlOnboardingSeen: onboardingSeen,
|
|
1208
|
+
});
|
|
1209
|
+
if (!persisted && syncOptions.requirePersistence) {
|
|
1210
|
+
await native.setEnabled(false, durationMs).catch(() => undefined);
|
|
1211
|
+
throw new Error('Could not save this Dogfood control preference to Yaver.');
|
|
1212
|
+
}
|
|
1213
|
+
if (persisted) {
|
|
1214
|
+
await cacheDogfoodControlPresentation(presentation, preferenceScope);
|
|
1215
|
+
if (onboardingSeen) await setDogfoodControlOnboardingSeen(true, preferenceScope);
|
|
1216
|
+
}
|
|
1217
|
+
} else {
|
|
1218
|
+
await cacheDogfoodControlPresentation(presentation, preferenceScope);
|
|
1219
|
+
if (onboardingSeen) await setDogfoodControlOnboardingSeen(true, preferenceScope);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
return result;
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
await native.setEnabled(false, durationMs).catch(() => undefined);
|
|
1225
|
+
if (syncOptions.requirePersistence) {
|
|
1226
|
+
throw error instanceof Error
|
|
1227
|
+
? error
|
|
1228
|
+
: new Error('Could not save this Dogfood control preference to Yaver.');
|
|
1229
|
+
}
|
|
1230
|
+
return {
|
|
1231
|
+
...base,
|
|
1232
|
+
authorized: true,
|
|
1233
|
+
appId: access.appId,
|
|
1234
|
+
installationId: access.installationId,
|
|
1235
|
+
presentation: 'minimized-y',
|
|
1236
|
+
onboardingSeen,
|
|
1237
|
+
fallbackVisible: allowFallback,
|
|
1238
|
+
reason: 'native-capability-check-failed',
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/** Complete first-run onboarding or change the later control preference.
|
|
1244
|
+
* Convex is authoritative and keyed by full Yaver user + app + installation;
|
|
1245
|
+
* the local value is only a cold-start cache. */
|
|
1246
|
+
static async setDogfoodControlPresentation(
|
|
1247
|
+
presentation: DogfoodControlPresentation,
|
|
1248
|
+
): Promise<DogfoodControlTriggerState> {
|
|
1249
|
+
if (presentation !== 'auto' && presentation !== 'minimized-y') {
|
|
1250
|
+
throw new Error('Invalid Dogfood control presentation.');
|
|
1251
|
+
}
|
|
1252
|
+
return YaverFeedback.syncDogfoodControlGesture({
|
|
1253
|
+
presentation,
|
|
1254
|
+
onboardingSeen: true,
|
|
1255
|
+
requirePersistence: true,
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/** One-tap fast reload for the compact Dogfood card. It preserves the same
|
|
1260
|
+
* selected render machine and bearer-authenticated P2P route as the full
|
|
1261
|
+
* Feedback modal, and names missing auth/machine state instead of no-oping. */
|
|
1262
|
+
static async requestDogfoodFastReload(): Promise<string> {
|
|
1263
|
+
await YaverFeedback.hydrateSession();
|
|
1264
|
+
if (!config?.authToken) {
|
|
1265
|
+
YaverFeedback.showLogin();
|
|
1266
|
+
throw new Error('Sign in to Yaver before requesting Fast Reload.');
|
|
1267
|
+
}
|
|
1268
|
+
if (!config.preferredDeviceId) {
|
|
1269
|
+
YaverFeedback.showMachinePicker();
|
|
1270
|
+
throw new Error('Choose a render machine before requesting Fast Reload.');
|
|
1271
|
+
}
|
|
1272
|
+
const selected = await YaverFeedback.getSelectedRemoteDevice();
|
|
1273
|
+
if (!selected) {
|
|
1274
|
+
YaverFeedback.showMachinePicker();
|
|
1275
|
+
throw new Error('The selected render machine is no longer available.');
|
|
1276
|
+
}
|
|
1277
|
+
if (selected.needsAuth) {
|
|
1278
|
+
YaverFeedback.showMachinePicker();
|
|
1279
|
+
throw new Error('The selected render machine needs pairing again.');
|
|
1280
|
+
}
|
|
1281
|
+
if (!selected.isOnline) {
|
|
1282
|
+
throw new Error('The selected render machine is offline.');
|
|
1283
|
+
}
|
|
1284
|
+
let client = YaverFeedback.getRenderP2PClient();
|
|
1285
|
+
if (!client && await YaverFeedback.reconnect()) {
|
|
1286
|
+
client = YaverFeedback.getRenderP2PClient();
|
|
1287
|
+
}
|
|
1288
|
+
if (!client) throw new Error('The render machine is not connected yet.');
|
|
1289
|
+
try {
|
|
1290
|
+
const ack = await client.reloadWithMode('fast');
|
|
1291
|
+
return ack.message;
|
|
1292
|
+
} catch (firstError) {
|
|
1293
|
+
if (await YaverFeedback.reconnect()) {
|
|
1294
|
+
const retry = YaverFeedback.getRenderP2PClient();
|
|
1295
|
+
if (retry) return (await retry.reloadWithMode('fast')).message;
|
|
1296
|
+
}
|
|
1297
|
+
throw firstError;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1005
1301
|
/** Backwards-compatible entry point for existing integrations. */
|
|
1006
1302
|
static async beginDogfoodOnboarding(options: DogfoodOnboardingOptions): Promise<DogfoodFlowState> {
|
|
1007
1303
|
YaverFeedback.configureDogfood(options);
|
|
@@ -1025,11 +1321,28 @@ export class YaverFeedback {
|
|
|
1025
1321
|
publishDogfoodFlow(state);
|
|
1026
1322
|
return state;
|
|
1027
1323
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
:
|
|
1324
|
+
let token: string | null;
|
|
1325
|
+
let account: Awaited<ReturnType<typeof getDogfoodAccountAccess>> | {
|
|
1326
|
+
authenticated: false;
|
|
1327
|
+
ownerAuthorized: false;
|
|
1328
|
+
accountAuthorized: false;
|
|
1329
|
+
installationAuthorized: false;
|
|
1330
|
+
};
|
|
1331
|
+
try {
|
|
1332
|
+
await YaverFeedback.hydrateSession();
|
|
1333
|
+
token = config?.authToken || await getToken();
|
|
1334
|
+
account = token
|
|
1335
|
+
? await getDogfoodAccountAccess(appId, token)
|
|
1336
|
+
: { authenticated: false, ownerAuthorized: false, accountAuthorized: false, installationAuthorized: false };
|
|
1337
|
+
} catch (cause) {
|
|
1338
|
+
const state: DogfoodFlowState = {
|
|
1339
|
+
phase: 'error',
|
|
1340
|
+
appId,
|
|
1341
|
+
error: cause instanceof Error ? cause.message : 'Yaver could not verify Dogfood access.',
|
|
1342
|
+
};
|
|
1343
|
+
publishDogfoodFlow(state);
|
|
1344
|
+
return state;
|
|
1345
|
+
}
|
|
1033
1346
|
// A device key is a second factor for this installation, never a
|
|
1034
1347
|
// replacement for a real Yaver account. Narrow installation sessions and
|
|
1035
1348
|
// stale/invalid cached tokens both return authenticated=false here.
|
|
@@ -1039,6 +1352,15 @@ export class YaverFeedback {
|
|
|
1039
1352
|
YaverFeedback.showLogin();
|
|
1040
1353
|
return state;
|
|
1041
1354
|
}
|
|
1355
|
+
if (!account.accountAuthorized) {
|
|
1356
|
+
const state: DogfoodFlowState = {
|
|
1357
|
+
phase: 'denied',
|
|
1358
|
+
appId,
|
|
1359
|
+
error: 'The app owner has not enabled Dogfood for this Yaver account.',
|
|
1360
|
+
};
|
|
1361
|
+
publishDogfoodFlow(state);
|
|
1362
|
+
return state;
|
|
1363
|
+
}
|
|
1042
1364
|
if (!config?.preferredDeviceId) {
|
|
1043
1365
|
const state: DogfoodFlowState = { phase: 'machine-required', appId };
|
|
1044
1366
|
publishDogfoodFlow(state);
|
|
@@ -1162,6 +1484,7 @@ export class YaverFeedback {
|
|
|
1162
1484
|
renderP2PClient = null;
|
|
1163
1485
|
p2pAuthToken = null;
|
|
1164
1486
|
await YaverFeedback.syncDogfoodAppShortcut().catch(() => false);
|
|
1487
|
+
await YaverFeedback.syncDogfoodControlGesture().catch(() => undefined);
|
|
1165
1488
|
}
|
|
1166
1489
|
|
|
1167
1490
|
/**
|
|
@@ -1461,6 +1784,7 @@ export class YaverFeedback {
|
|
|
1461
1784
|
DeviceEventEmitter.emit('yaverFeedback:dogfoodChanged', { active: !!session, status });
|
|
1462
1785
|
} catch { /* noop */ }
|
|
1463
1786
|
await YaverFeedback.syncDogfoodAppShortcut().catch(() => false);
|
|
1787
|
+
await YaverFeedback.syncDogfoodControlGesture().catch(() => undefined);
|
|
1464
1788
|
return { status, installationId: info.installationId, session };
|
|
1465
1789
|
}
|
|
1466
1790
|
|
|
@@ -1475,6 +1799,7 @@ export class YaverFeedback {
|
|
|
1475
1799
|
const { DeviceEventEmitter } = require('react-native');
|
|
1476
1800
|
DeviceEventEmitter.emit('yaverFeedback:dogfoodChanged', { active: false, exited: true });
|
|
1477
1801
|
} catch { /* noop */ }
|
|
1802
|
+
await YaverFeedback.syncDogfoodControlGesture().catch(() => undefined);
|
|
1478
1803
|
}
|
|
1479
1804
|
|
|
1480
1805
|
/** Returns the resolved relay password the SDK is currently using.
|
|
@@ -2019,6 +2344,12 @@ export class YaverFeedback {
|
|
|
2019
2344
|
dogfoodShortcutAppStateSubscription = null;
|
|
2020
2345
|
dogfoodActivationSubscription?.remove();
|
|
2021
2346
|
dogfoodActivationSubscription = null;
|
|
2347
|
+
try {
|
|
2348
|
+
const native = (NativeModules as any)?.YaverDogfoodGesture;
|
|
2349
|
+
if (typeof native?.setEnabled === 'function') {
|
|
2350
|
+
void native.setEnabled(false, 900).catch(() => undefined);
|
|
2351
|
+
}
|
|
2352
|
+
} catch { /* native trigger teardown is best-effort */ }
|
|
2022
2353
|
lastDogfoodActivationUrl = '';
|
|
2023
2354
|
// Before `enabled = false` / `config = null` below, so an in-flight retry
|
|
2024
2355
|
// can't fire against a torn-down config.
|
|
@@ -56,4 +56,19 @@ final class TalosSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
|
56
56
|
expect(android).toContain('addDynamicShortcuts');
|
|
57
57
|
expect(android).toContain('consumeDogfoodShortcut');
|
|
58
58
|
});
|
|
59
|
+
|
|
60
|
+
it('observes a three-finger hold without consuming host app touches', () => {
|
|
61
|
+
const ios = readFileSync(join(__dirname, '../../ios/YaverDogfoodGesture.swift'), 'utf8');
|
|
62
|
+
const android = readFileSync(join(__dirname, '../../android/src/main/java/io/yaver/feedback/YaverDogfoodGestureModule.java'), 'utf8');
|
|
63
|
+
const controls = readFileSync(join(__dirname, '../DogfoodQuickControls.tsx'), 'utf8');
|
|
64
|
+
expect(ios).toContain('numberOfTouchesRequired = 3');
|
|
65
|
+
expect(ios).toContain('cancelsTouchesInView = false');
|
|
66
|
+
expect(ios).toContain('isVoiceOverRunning');
|
|
67
|
+
expect(android).toContain('event.getPointerCount() >= 3');
|
|
68
|
+
expect(android).toContain('method.invoke(callback, args)');
|
|
69
|
+
expect(android).toContain('isTouchExplorationEnabled');
|
|
70
|
+
expect(controls).toContain('yaver-dogfood-fast-reload');
|
|
71
|
+
expect(controls).toContain('yaver-dogfood-chat');
|
|
72
|
+
expect(controls).toContain('yaver-dogfood-minimized-control');
|
|
73
|
+
});
|
|
59
74
|
});
|