yaver-feedback-react-native 0.9.6 → 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 +101 -3
- 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 +32 -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 +38 -1
- 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/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,244 @@ 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
|
+
if (IS_HOST_MODE || isRunningInsideYaverHost()) {
|
|
1081
|
+
if (typeof native?.setEnabled === 'function') {
|
|
1082
|
+
await native.setEnabled(false, 900).catch(() => undefined);
|
|
1083
|
+
}
|
|
1084
|
+
return { ...base, reason: 'yaver-host-owns-controls' };
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
const access = await YaverFeedback.getDogfoodAccess();
|
|
1088
|
+
let authorized = access.authorized;
|
|
1089
|
+
if (authorized && config?.dogfood?.canShow) {
|
|
1090
|
+
try {
|
|
1091
|
+
authorized = await config.dogfood.canShow(access);
|
|
1092
|
+
} catch {
|
|
1093
|
+
authorized = false;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
const options = typeof configured === 'object' ? configured : {};
|
|
1097
|
+
const durationMs = Math.min(2000, Math.max(650, options.durationMs ?? 900));
|
|
1098
|
+
const allowFallback = options.fallback !== 'none';
|
|
1099
|
+
if (!authorized) {
|
|
1100
|
+
if (typeof native?.setEnabled === 'function') {
|
|
1101
|
+
await native.setEnabled(false, durationMs).catch(() => undefined);
|
|
1102
|
+
}
|
|
1103
|
+
return { ...base, appId: access.appId, installationId: access.installationId, reason: 'installation-not-authorized' };
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
const preferenceScope = dogfoodControlPreferenceScope(access.appId, access.installationId);
|
|
1107
|
+
const cachedPresentation = await getDogfoodControlPresentation(preferenceScope);
|
|
1108
|
+
const cachedOnboardingSeen = await getDogfoodControlOnboardingSeen(preferenceScope);
|
|
1109
|
+
const onboardingSeen = syncOptions.onboardingSeen
|
|
1110
|
+
?? access.controlOnboardingSeen
|
|
1111
|
+
?? cachedOnboardingSeen;
|
|
1112
|
+
// A newly authorized tester must never be left staring at an invisible
|
|
1113
|
+
// feature. Until the exact installation has completed onboarding in
|
|
1114
|
+
// Convex, the edge-docked Y wins even when the gesture is supported.
|
|
1115
|
+
const preferredPresentation = syncOptions.presentation
|
|
1116
|
+
|| access.controlPresentation
|
|
1117
|
+
|| cachedPresentation
|
|
1118
|
+
|| options.defaultPresentation
|
|
1119
|
+
|| 'auto';
|
|
1120
|
+
const presentation: DogfoodControlPresentation = onboardingSeen
|
|
1121
|
+
? preferredPresentation
|
|
1122
|
+
: 'minimized-y';
|
|
1123
|
+
|
|
1124
|
+
if (typeof native?.getCapability !== 'function' || typeof native?.setEnabled !== 'function') {
|
|
1125
|
+
const result: DogfoodControlTriggerState = {
|
|
1126
|
+
...base,
|
|
1127
|
+
authorized: true,
|
|
1128
|
+
appId: access.appId,
|
|
1129
|
+
installationId: access.installationId,
|
|
1130
|
+
presentation: 'minimized-y',
|
|
1131
|
+
onboardingSeen,
|
|
1132
|
+
fallbackVisible: allowFallback,
|
|
1133
|
+
};
|
|
1134
|
+
if (syncOptions.requirePersistence) {
|
|
1135
|
+
const token = config?.authToken || await getToken();
|
|
1136
|
+
if (!token || !access.installationId) {
|
|
1137
|
+
throw new Error('A full Yaver session is required to save Dogfood settings.');
|
|
1138
|
+
}
|
|
1139
|
+
const persisted = await setDogfoodControlPreference({
|
|
1140
|
+
appId: access.appId,
|
|
1141
|
+
installationId: access.installationId,
|
|
1142
|
+
token,
|
|
1143
|
+
presentation: 'minimized-y',
|
|
1144
|
+
gestureSupported: false,
|
|
1145
|
+
gestureCapabilityReason: 'native-module-unavailable',
|
|
1146
|
+
gesturePlatform: Platform.OS,
|
|
1147
|
+
controlOnboardingSeen: onboardingSeen,
|
|
1148
|
+
});
|
|
1149
|
+
if (!persisted) throw new Error('Could not save this Dogfood control preference to Yaver.');
|
|
1150
|
+
await cacheDogfoodControlPresentation('minimized-y', preferenceScope);
|
|
1151
|
+
if (onboardingSeen) await setDogfoodControlOnboardingSeen(true, preferenceScope);
|
|
1152
|
+
}
|
|
1153
|
+
return result;
|
|
1154
|
+
}
|
|
1155
|
+
try {
|
|
1156
|
+
const capability = await native.getCapability();
|
|
1157
|
+
const gestureSupported = capability?.supported === true;
|
|
1158
|
+
const shouldEnableGesture = gestureSupported && onboardingSeen && presentation === 'auto';
|
|
1159
|
+
const applied = await native.setEnabled(shouldEnableGesture, durationMs);
|
|
1160
|
+
const gestureEnabled = shouldEnableGesture && applied?.enabled === true;
|
|
1161
|
+
const gestureEnableFailed = shouldEnableGesture && !gestureEnabled;
|
|
1162
|
+
const result: DogfoodControlTriggerState = {
|
|
1163
|
+
configured: true,
|
|
1164
|
+
authorized: true,
|
|
1165
|
+
appId: access.appId,
|
|
1166
|
+
installationId: access.installationId,
|
|
1167
|
+
gestureSupported,
|
|
1168
|
+
gestureEnabled,
|
|
1169
|
+
fallbackVisible: allowFallback && (!gestureSupported || presentation === 'minimized-y' || gestureEnableFailed),
|
|
1170
|
+
presentation,
|
|
1171
|
+
onboardingSeen,
|
|
1172
|
+
reason: gestureEnableFailed
|
|
1173
|
+
? 'gesture-enable-failed'
|
|
1174
|
+
: String(applied?.reason || capability?.reason || (gestureSupported ? 'supported' : 'unsupported')),
|
|
1175
|
+
platform: String(applied?.platform || capability?.platform || Platform.OS),
|
|
1176
|
+
};
|
|
1177
|
+
const token = config?.authToken || await getToken();
|
|
1178
|
+
if (syncOptions.requirePersistence && (!token || !access.installationId)) {
|
|
1179
|
+
await native.setEnabled(false, durationMs).catch(() => undefined);
|
|
1180
|
+
throw new Error('A full Yaver session is required to save Dogfood settings.');
|
|
1181
|
+
}
|
|
1182
|
+
if (token && access.installationId) {
|
|
1183
|
+
const needsPersistence =
|
|
1184
|
+
access.controlPresentation !== presentation
|
|
1185
|
+
|| access.gestureSupported !== gestureSupported
|
|
1186
|
+
|| access.gestureCapabilityReason !== result.reason
|
|
1187
|
+
|| (syncOptions.onboardingSeen === true && access.controlOnboardingSeen !== true);
|
|
1188
|
+
if (needsPersistence) {
|
|
1189
|
+
const persisted = await setDogfoodControlPreference({
|
|
1190
|
+
appId: access.appId,
|
|
1191
|
+
installationId: access.installationId,
|
|
1192
|
+
token,
|
|
1193
|
+
presentation,
|
|
1194
|
+
gestureSupported,
|
|
1195
|
+
gestureCapabilityReason: result.reason,
|
|
1196
|
+
gesturePlatform: result.platform || Platform.OS,
|
|
1197
|
+
controlOnboardingSeen: onboardingSeen,
|
|
1198
|
+
});
|
|
1199
|
+
if (!persisted && syncOptions.requirePersistence) {
|
|
1200
|
+
await native.setEnabled(false, durationMs).catch(() => undefined);
|
|
1201
|
+
throw new Error('Could not save this Dogfood control preference to Yaver.');
|
|
1202
|
+
}
|
|
1203
|
+
if (persisted) {
|
|
1204
|
+
await cacheDogfoodControlPresentation(presentation, preferenceScope);
|
|
1205
|
+
if (onboardingSeen) await setDogfoodControlOnboardingSeen(true, preferenceScope);
|
|
1206
|
+
}
|
|
1207
|
+
} else {
|
|
1208
|
+
await cacheDogfoodControlPresentation(presentation, preferenceScope);
|
|
1209
|
+
if (onboardingSeen) await setDogfoodControlOnboardingSeen(true, preferenceScope);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
return result;
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
await native.setEnabled(false, durationMs).catch(() => undefined);
|
|
1215
|
+
if (syncOptions.requirePersistence) {
|
|
1216
|
+
throw error instanceof Error
|
|
1217
|
+
? error
|
|
1218
|
+
: new Error('Could not save this Dogfood control preference to Yaver.');
|
|
1219
|
+
}
|
|
1220
|
+
return {
|
|
1221
|
+
...base,
|
|
1222
|
+
authorized: true,
|
|
1223
|
+
appId: access.appId,
|
|
1224
|
+
installationId: access.installationId,
|
|
1225
|
+
presentation: 'minimized-y',
|
|
1226
|
+
onboardingSeen,
|
|
1227
|
+
fallbackVisible: allowFallback,
|
|
1228
|
+
reason: 'native-capability-check-failed',
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/** Complete first-run onboarding or change the later control preference.
|
|
1234
|
+
* Convex is authoritative and keyed by full Yaver user + app + installation;
|
|
1235
|
+
* the local value is only a cold-start cache. */
|
|
1236
|
+
static async setDogfoodControlPresentation(
|
|
1237
|
+
presentation: DogfoodControlPresentation,
|
|
1238
|
+
): Promise<DogfoodControlTriggerState> {
|
|
1239
|
+
if (presentation !== 'auto' && presentation !== 'minimized-y') {
|
|
1240
|
+
throw new Error('Invalid Dogfood control presentation.');
|
|
1241
|
+
}
|
|
1242
|
+
return YaverFeedback.syncDogfoodControlGesture({
|
|
1243
|
+
presentation,
|
|
1244
|
+
onboardingSeen: true,
|
|
1245
|
+
requirePersistence: true,
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/** One-tap fast reload for the compact Dogfood card. It preserves the same
|
|
1250
|
+
* selected render machine and bearer-authenticated P2P route as the full
|
|
1251
|
+
* Feedback modal, and names missing auth/machine state instead of no-oping. */
|
|
1252
|
+
static async requestDogfoodFastReload(): Promise<string> {
|
|
1253
|
+
await YaverFeedback.hydrateSession();
|
|
1254
|
+
if (!config?.authToken) {
|
|
1255
|
+
YaverFeedback.showLogin();
|
|
1256
|
+
throw new Error('Sign in to Yaver before requesting Fast Reload.');
|
|
1257
|
+
}
|
|
1258
|
+
if (!config.preferredDeviceId) {
|
|
1259
|
+
YaverFeedback.showMachinePicker();
|
|
1260
|
+
throw new Error('Choose a render machine before requesting Fast Reload.');
|
|
1261
|
+
}
|
|
1262
|
+
const selected = await YaverFeedback.getSelectedRemoteDevice();
|
|
1263
|
+
if (!selected) {
|
|
1264
|
+
YaverFeedback.showMachinePicker();
|
|
1265
|
+
throw new Error('The selected render machine is no longer available.');
|
|
1266
|
+
}
|
|
1267
|
+
if (selected.needsAuth) {
|
|
1268
|
+
YaverFeedback.showMachinePicker();
|
|
1269
|
+
throw new Error('The selected render machine needs pairing again.');
|
|
1270
|
+
}
|
|
1271
|
+
if (!selected.isOnline) {
|
|
1272
|
+
throw new Error('The selected render machine is offline.');
|
|
1273
|
+
}
|
|
1274
|
+
let client = YaverFeedback.getRenderP2PClient();
|
|
1275
|
+
if (!client && await YaverFeedback.reconnect()) {
|
|
1276
|
+
client = YaverFeedback.getRenderP2PClient();
|
|
1277
|
+
}
|
|
1278
|
+
if (!client) throw new Error('The render machine is not connected yet.');
|
|
1279
|
+
try {
|
|
1280
|
+
const ack = await client.reloadWithMode('fast');
|
|
1281
|
+
return ack.message;
|
|
1282
|
+
} catch (firstError) {
|
|
1283
|
+
if (await YaverFeedback.reconnect()) {
|
|
1284
|
+
const retry = YaverFeedback.getRenderP2PClient();
|
|
1285
|
+
if (retry) return (await retry.reloadWithMode('fast')).message;
|
|
1286
|
+
}
|
|
1287
|
+
throw firstError;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1005
1291
|
/** Backwards-compatible entry point for existing integrations. */
|
|
1006
1292
|
static async beginDogfoodOnboarding(options: DogfoodOnboardingOptions): Promise<DogfoodFlowState> {
|
|
1007
1293
|
YaverFeedback.configureDogfood(options);
|
|
@@ -1025,11 +1311,28 @@ export class YaverFeedback {
|
|
|
1025
1311
|
publishDogfoodFlow(state);
|
|
1026
1312
|
return state;
|
|
1027
1313
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
:
|
|
1314
|
+
let token: string | null;
|
|
1315
|
+
let account: Awaited<ReturnType<typeof getDogfoodAccountAccess>> | {
|
|
1316
|
+
authenticated: false;
|
|
1317
|
+
ownerAuthorized: false;
|
|
1318
|
+
accountAuthorized: false;
|
|
1319
|
+
installationAuthorized: false;
|
|
1320
|
+
};
|
|
1321
|
+
try {
|
|
1322
|
+
await YaverFeedback.hydrateSession();
|
|
1323
|
+
token = config?.authToken || await getToken();
|
|
1324
|
+
account = token
|
|
1325
|
+
? await getDogfoodAccountAccess(appId, token)
|
|
1326
|
+
: { authenticated: false, ownerAuthorized: false, accountAuthorized: false, installationAuthorized: false };
|
|
1327
|
+
} catch (cause) {
|
|
1328
|
+
const state: DogfoodFlowState = {
|
|
1329
|
+
phase: 'error',
|
|
1330
|
+
appId,
|
|
1331
|
+
error: cause instanceof Error ? cause.message : 'Yaver could not verify Dogfood access.',
|
|
1332
|
+
};
|
|
1333
|
+
publishDogfoodFlow(state);
|
|
1334
|
+
return state;
|
|
1335
|
+
}
|
|
1033
1336
|
// A device key is a second factor for this installation, never a
|
|
1034
1337
|
// replacement for a real Yaver account. Narrow installation sessions and
|
|
1035
1338
|
// stale/invalid cached tokens both return authenticated=false here.
|
|
@@ -1039,6 +1342,15 @@ export class YaverFeedback {
|
|
|
1039
1342
|
YaverFeedback.showLogin();
|
|
1040
1343
|
return state;
|
|
1041
1344
|
}
|
|
1345
|
+
if (!account.accountAuthorized) {
|
|
1346
|
+
const state: DogfoodFlowState = {
|
|
1347
|
+
phase: 'denied',
|
|
1348
|
+
appId,
|
|
1349
|
+
error: 'The app owner has not enabled Dogfood for this Yaver account.',
|
|
1350
|
+
};
|
|
1351
|
+
publishDogfoodFlow(state);
|
|
1352
|
+
return state;
|
|
1353
|
+
}
|
|
1042
1354
|
if (!config?.preferredDeviceId) {
|
|
1043
1355
|
const state: DogfoodFlowState = { phase: 'machine-required', appId };
|
|
1044
1356
|
publishDogfoodFlow(state);
|
|
@@ -1162,6 +1474,7 @@ export class YaverFeedback {
|
|
|
1162
1474
|
renderP2PClient = null;
|
|
1163
1475
|
p2pAuthToken = null;
|
|
1164
1476
|
await YaverFeedback.syncDogfoodAppShortcut().catch(() => false);
|
|
1477
|
+
await YaverFeedback.syncDogfoodControlGesture().catch(() => undefined);
|
|
1165
1478
|
}
|
|
1166
1479
|
|
|
1167
1480
|
/**
|
|
@@ -1461,6 +1774,7 @@ export class YaverFeedback {
|
|
|
1461
1774
|
DeviceEventEmitter.emit('yaverFeedback:dogfoodChanged', { active: !!session, status });
|
|
1462
1775
|
} catch { /* noop */ }
|
|
1463
1776
|
await YaverFeedback.syncDogfoodAppShortcut().catch(() => false);
|
|
1777
|
+
await YaverFeedback.syncDogfoodControlGesture().catch(() => undefined);
|
|
1464
1778
|
return { status, installationId: info.installationId, session };
|
|
1465
1779
|
}
|
|
1466
1780
|
|
|
@@ -1475,6 +1789,7 @@ export class YaverFeedback {
|
|
|
1475
1789
|
const { DeviceEventEmitter } = require('react-native');
|
|
1476
1790
|
DeviceEventEmitter.emit('yaverFeedback:dogfoodChanged', { active: false, exited: true });
|
|
1477
1791
|
} catch { /* noop */ }
|
|
1792
|
+
await YaverFeedback.syncDogfoodControlGesture().catch(() => undefined);
|
|
1478
1793
|
}
|
|
1479
1794
|
|
|
1480
1795
|
/** Returns the resolved relay password the SDK is currently using.
|
|
@@ -2019,6 +2334,12 @@ export class YaverFeedback {
|
|
|
2019
2334
|
dogfoodShortcutAppStateSubscription = null;
|
|
2020
2335
|
dogfoodActivationSubscription?.remove();
|
|
2021
2336
|
dogfoodActivationSubscription = null;
|
|
2337
|
+
try {
|
|
2338
|
+
const native = (NativeModules as any)?.YaverDogfoodGesture;
|
|
2339
|
+
if (typeof native?.setEnabled === 'function') {
|
|
2340
|
+
void native.setEnabled(false, 900).catch(() => undefined);
|
|
2341
|
+
}
|
|
2342
|
+
} catch { /* native trigger teardown is best-effort */ }
|
|
2022
2343
|
lastDogfoodActivationUrl = '';
|
|
2023
2344
|
// Before `enabled = false` / `config = null` below, so an in-flight retry
|
|
2024
2345
|
// can't fire against a torn-down config.
|
|
@@ -2,7 +2,10 @@ import { readFileSync } from 'fs';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
|
|
4
4
|
const plugin = require('../../app.plugin.js') as {
|
|
5
|
-
__test: {
|
|
5
|
+
__test: {
|
|
6
|
+
patchDogfoodAppShortcut(contents: string): string;
|
|
7
|
+
patchDogfoodSceneDelegate(contents: string): string;
|
|
8
|
+
};
|
|
6
9
|
};
|
|
7
10
|
|
|
8
11
|
describe('native Dogfood shortcut contract', () => {
|
|
@@ -25,6 +28,25 @@ public class AppDelegate: ExpoAppDelegate {
|
|
|
25
28
|
expect(plugin.__test.patchDogfoodAppShortcut(once)).toBe(once);
|
|
26
29
|
});
|
|
27
30
|
|
|
31
|
+
it('patches cold and warm scene-lifecycle delivery idempotently', () => {
|
|
32
|
+
const source = `
|
|
33
|
+
final class TalosSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
34
|
+
func scene(
|
|
35
|
+
_ scene: UIScene,
|
|
36
|
+
willConnectTo session: UISceneSession,
|
|
37
|
+
options connectionOptions: UIScene.ConnectionOptions
|
|
38
|
+
) {
|
|
39
|
+
guard scene is UIWindowScene else { return }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
const once = plugin.__test.patchDogfoodSceneDelegate(source);
|
|
44
|
+
expect(once).toContain('connectionOptions.shortcutItem');
|
|
45
|
+
expect(once).toContain('func windowScene(');
|
|
46
|
+
expect(once).toContain('markDogfoodShortcutPending()');
|
|
47
|
+
expect(plugin.__test.patchDogfoodSceneDelegate(once)).toBe(once);
|
|
48
|
+
});
|
|
49
|
+
|
|
28
50
|
it('uses dynamic native shortcuts on both platforms', () => {
|
|
29
51
|
const ios = readFileSync(join(__dirname, '../../ios/YaverHotReload.swift'), 'utf8');
|
|
30
52
|
const android = readFileSync(join(__dirname, '../../android/src/main/java/io/yaver/feedback/YaverHotReloadModule.java'), 'utf8');
|
|
@@ -34,4 +56,19 @@ public class AppDelegate: ExpoAppDelegate {
|
|
|
34
56
|
expect(android).toContain('addDynamicShortcuts');
|
|
35
57
|
expect(android).toContain('consumeDogfoodShortcut');
|
|
36
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
|
+
});
|
|
37
74
|
});
|