yaver-feedback-react-native 0.9.2 → 0.9.3
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 +102 -1
- package/dist/AuthOverlay.d.ts +1 -14
- package/dist/AuthOverlay.js +9 -62
- package/dist/Discovery.js +0 -6
- package/dist/DogfoodRuntime.d.ts +124 -0
- package/dist/DogfoodRuntime.js +273 -0
- package/dist/FeedbackModal.js +347 -61
- package/dist/LoginScreen.d.ts +1 -5
- package/dist/LoginScreen.js +2 -6
- package/dist/MachinePickerScreen.d.ts +1 -3
- package/dist/MachinePickerScreen.js +6 -18
- package/dist/P2PClient.d.ts +116 -3
- package/dist/P2PClient.js +273 -3
- package/dist/P2PDogfoodDriver.d.ts +12 -0
- package/dist/P2PDogfoodDriver.js +118 -0
- package/dist/PairDeviceModal.d.ts +2 -3
- package/dist/VibeChatScreen.d.ts +11 -1
- package/dist/VibeChatScreen.js +200 -41
- package/dist/YaverFeedback.d.ts +34 -0
- package/dist/YaverFeedback.js +124 -19
- package/dist/YaverModeBadge.d.ts +22 -0
- package/dist/YaverModeBadge.js +219 -0
- package/dist/__tests__/AuthDevices.test.js +1 -48
- package/dist/__tests__/DogfoodRuntime.test.d.ts +1 -0
- package/dist/__tests__/DogfoodRuntime.test.js +116 -0
- package/dist/__tests__/P2PDogfoodDriver.test.d.ts +1 -0
- package/dist/__tests__/P2PDogfoodDriver.test.js +64 -0
- package/dist/__tests__/ReportIdentity.test.d.ts +25 -1
- package/dist/__tests__/ReportIdentity.test.js +34 -22
- package/dist/__tests__/YaverFeedback.test.js +13 -3
- package/dist/__tests__/deviceDogfood.test.d.ts +1 -0
- package/dist/__tests__/deviceDogfood.test.js +86 -0
- package/dist/__tests__/dogfoodPolicy.test.d.ts +1 -0
- package/dist/__tests__/dogfoodPolicy.test.js +33 -0
- package/dist/_core/ansi.d.ts +117 -0
- package/dist/_core/ansi.js +468 -0
- package/dist/_core/ansi.test.d.ts +1 -0
- package/dist/_core/ansi.test.js +225 -0
- package/dist/_core/buildFeedbackPrompt.d.ts +4 -9
- package/dist/_core/buildFeedbackPrompt.js +18 -72
- package/dist/_core/constants.d.ts +19 -6
- package/dist/_core/constants.js +20 -7
- package/dist/_core/device.d.ts +9 -23
- package/dist/_core/device.js +13 -28
- package/dist/_core/endpoints.d.ts +0 -7
- package/dist/_core/endpoints.js +0 -7
- package/dist/_core/index.d.ts +4 -0
- package/dist/_core/index.js +4 -0
- package/dist/_core/remoteless.d.ts +44 -0
- package/dist/_core/remoteless.js +75 -0
- package/dist/_core/trace.d.ts +47 -0
- package/dist/_core/trace.js +38 -0
- package/dist/_core/trace.test.d.ts +1 -0
- package/dist/_core/trace.test.js +60 -0
- package/dist/auth.d.ts +3 -60
- package/dist/auth.js +6 -88
- package/dist/deviceDogfood.d.ts +53 -0
- package/dist/deviceDogfood.js +137 -0
- package/dist/dogfoodPolicy.d.ts +25 -0
- package/dist/dogfoodPolicy.js +24 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.js +24 -8
- package/dist/reloadActions.js +2 -2
- package/dist/types.d.ts +50 -7
- package/package.json +12 -3
- package/src/AuthOverlay.tsx +20 -106
- package/src/Discovery.ts +0 -6
- package/src/DogfoodRuntime.ts +373 -0
- package/src/FeedbackModal.tsx +445 -67
- package/src/LoginScreen.tsx +2 -22
- package/src/MachinePickerScreen.tsx +6 -21
- package/src/P2PClient.ts +347 -4
- package/src/P2PDogfoodDriver.ts +132 -0
- package/src/PairDeviceModal.tsx +2 -3
- package/src/VibeChatScreen.tsx +232 -42
- package/src/YaverFeedback.ts +133 -22
- package/src/YaverModeBadge.tsx +234 -0
- package/src/__tests__/AuthDevices.test.ts +1 -52
- package/src/__tests__/DogfoodRuntime.test.ts +135 -0
- package/src/__tests__/P2PDogfoodDriver.test.ts +80 -0
- package/src/__tests__/ReportIdentity.test.ts +36 -27
- package/src/__tests__/YaverFeedback.test.ts +15 -3
- package/src/__tests__/deviceDogfood.test.ts +77 -0
- package/src/__tests__/dogfoodPolicy.test.ts +34 -0
- package/src/_core/ansi.test.ts +250 -0
- package/src/_core/ansi.ts +475 -0
- package/src/_core/buildFeedbackPrompt.ts +18 -95
- package/src/_core/constants.ts +20 -6
- package/src/_core/device.ts +13 -30
- package/src/_core/endpoints.ts +0 -7
- package/src/_core/index.ts +4 -0
- package/src/_core/remoteless.ts +110 -0
- package/src/_core/trace.test.ts +58 -0
- package/src/_core/trace.ts +75 -0
- package/src/auth.ts +7 -156
- package/src/deviceDogfood.ts +171 -0
- package/src/dogfoodPolicy.ts +40 -0
- package/src/index.ts +40 -11
- package/src/reloadActions.ts +2 -2
- package/src/types.ts +45 -7
- package/dist/GuestOnboardingScreen.d.ts +0 -8
- package/dist/GuestOnboardingScreen.js +0 -282
- package/src/GuestOnboardingScreen.tsx +0 -307
package/dist/YaverFeedback.js
CHANGED
|
@@ -9,6 +9,8 @@ const ShakeDetector_1 = require("./ShakeDetector");
|
|
|
9
9
|
const storeShots_1 = require("./storeShots");
|
|
10
10
|
const auth_1 = require("./auth");
|
|
11
11
|
const preferences_1 = require("./preferences");
|
|
12
|
+
const dogfoodPolicy_1 = require("./dogfoodPolicy");
|
|
13
|
+
const deviceDogfood_1 = require("./deviceDogfood");
|
|
12
14
|
function unrefTimer(timer) {
|
|
13
15
|
const maybeNodeTimer = timer;
|
|
14
16
|
if (typeof maybeNodeTimer.unref === 'function') {
|
|
@@ -140,6 +142,7 @@ if (IS_HOST_MODE) {
|
|
|
140
142
|
let config = null;
|
|
141
143
|
let enabled = false;
|
|
142
144
|
let p2pClient = null;
|
|
145
|
+
let renderP2PClient = null;
|
|
143
146
|
let shakeDetector = null;
|
|
144
147
|
/** Unsubscribe for the BlackBox command handler registered by init(). Held so
|
|
145
148
|
* a re-init (or destroy) can drop the old one instead of stacking. */
|
|
@@ -151,6 +154,7 @@ let p2pRelayPassword = '';
|
|
|
151
154
|
let autoStartTimer = null;
|
|
152
155
|
let reportLaunchInFlight = false;
|
|
153
156
|
let crashReportInFlight = false;
|
|
157
|
+
let dogfoodOnboarding = null;
|
|
154
158
|
/** Resolve the user's relay password by validating their auth token
|
|
155
159
|
* against Convex. Used whenever we (re)build the P2PClient so a
|
|
156
160
|
* relay-routed agentUrl carries a valid X-Relay-Password — without
|
|
@@ -218,40 +222,37 @@ const flagCache = new Map();
|
|
|
218
222
|
*/
|
|
219
223
|
class YaverFeedback {
|
|
220
224
|
static async resolveP2PAuthToken() {
|
|
221
|
-
|
|
222
|
-
return null;
|
|
223
|
-
if (!config.preferredDeviceId)
|
|
224
|
-
return config.authToken;
|
|
225
|
-
const devices = await (0, auth_1.listReachableDevices)(config.authToken);
|
|
226
|
-
const all = [...devices.owned, ...devices.shared];
|
|
227
|
-
const selected = all.find((device) => device.deviceId === config?.preferredDeviceId);
|
|
228
|
-
if (!selected || !selected.isGuest || selected.accessScope !== 'shared-scoped') {
|
|
229
|
-
return config.authToken;
|
|
230
|
-
}
|
|
231
|
-
if (!selected.hostUserId) {
|
|
232
|
-
return config.authToken;
|
|
233
|
-
}
|
|
234
|
-
const delegated = await (0, auth_1.mintGuestSdkToken)(config.authToken, selected.hostUserId, selected.deviceId);
|
|
235
|
-
return delegated.token;
|
|
225
|
+
return config?.authToken ?? null;
|
|
236
226
|
}
|
|
237
227
|
static async rebuildP2PClient(agentUrl) {
|
|
238
|
-
|
|
228
|
+
const currentConfig = config;
|
|
229
|
+
if (!currentConfig)
|
|
239
230
|
return;
|
|
240
|
-
const effectiveUrl = agentUrl ??
|
|
231
|
+
const effectiveUrl = agentUrl ?? currentConfig.agentUrl;
|
|
241
232
|
if (!effectiveUrl) {
|
|
242
233
|
p2pClient = null;
|
|
234
|
+
renderP2PClient = null;
|
|
243
235
|
p2pAuthToken = null;
|
|
244
236
|
return;
|
|
245
237
|
}
|
|
246
238
|
const token = await YaverFeedback.resolveP2PAuthToken();
|
|
239
|
+
// reset() can clear config while relay-password resolution is awaiting.
|
|
240
|
+
// Do not resurrect a client from that stale init generation.
|
|
241
|
+
if (config !== currentConfig)
|
|
242
|
+
return;
|
|
247
243
|
if (!token) {
|
|
248
244
|
p2pClient = null;
|
|
245
|
+
renderP2PClient = null;
|
|
249
246
|
p2pAuthToken = null;
|
|
250
247
|
return;
|
|
251
248
|
}
|
|
252
249
|
p2pAuthToken = token;
|
|
253
250
|
const rp = await resolveRelayPassword(token);
|
|
251
|
+
if (config !== currentConfig)
|
|
252
|
+
return;
|
|
254
253
|
p2pClient = new P2PClient_1.P2PClient(effectiveUrl, token, rp);
|
|
254
|
+
const renderUrl = currentConfig.renderAgentUrl || effectiveUrl;
|
|
255
|
+
renderP2PClient = renderUrl === effectiveUrl ? p2pClient : new P2PClient_1.P2PClient(renderUrl, token, rp);
|
|
255
256
|
}
|
|
256
257
|
/**
|
|
257
258
|
* Initialize the feedback SDK with the given configuration.
|
|
@@ -378,6 +379,9 @@ class YaverFeedback {
|
|
|
378
379
|
maxRecordingDuration: 120,
|
|
379
380
|
autoLogin: true,
|
|
380
381
|
...cfg,
|
|
382
|
+
agentUrl: cfg.codingAgentUrl || cfg.agentUrl,
|
|
383
|
+
codingDeviceId: cfg.codingDeviceId || cfg.preferredDeviceId,
|
|
384
|
+
renderDeviceId: cfg.renderDeviceId || cfg.codingDeviceId || cfg.preferredDeviceId,
|
|
381
385
|
};
|
|
382
386
|
if (IS_HOST_MODE) {
|
|
383
387
|
// sfmg / talos / etc. running inside Yaver mobile (compile-time
|
|
@@ -451,12 +455,17 @@ class YaverFeedback {
|
|
|
451
455
|
// is set, so set a placeholder header here that won't 401 a
|
|
452
456
|
// direct-LAN url and will be overwritten before any relay hop.
|
|
453
457
|
p2pClient = new P2PClient_1.P2PClient(config.agentUrl, config.authToken ?? '', p2pRelayPassword);
|
|
458
|
+
const renderUrl = config.renderAgentUrl || config.agentUrl;
|
|
459
|
+
renderP2PClient = renderUrl === config.agentUrl
|
|
460
|
+
? p2pClient
|
|
461
|
+
: new P2PClient_1.P2PClient(renderUrl, config.authToken ?? '', p2pRelayPassword);
|
|
454
462
|
if (config.authToken) {
|
|
455
463
|
void YaverFeedback.rebuildP2PClient(config.agentUrl);
|
|
456
464
|
}
|
|
457
465
|
}
|
|
458
466
|
else {
|
|
459
467
|
p2pClient = null;
|
|
468
|
+
renderP2PClient = null;
|
|
460
469
|
// Auto-discover agent in the background when convexUrl or LAN is available
|
|
461
470
|
if (enabled && (config.authToken || config.preferredDeviceId)) {
|
|
462
471
|
YaverFeedback.discoverAgent();
|
|
@@ -803,6 +812,25 @@ class YaverFeedback {
|
|
|
803
812
|
const { DeviceEventEmitter } = require('react-native');
|
|
804
813
|
DeviceEventEmitter.emit('yaverFeedback:startMachinePicker');
|
|
805
814
|
}
|
|
815
|
+
/** Begin the reusable host-app Dogfood wizard. Owner OAuth and machine
|
|
816
|
+
* selection intentionally happen before installation enrollment/runtime
|
|
817
|
+
* controls because starting builds or runners is owner-level authority. */
|
|
818
|
+
static beginDogfoodOnboarding(options) {
|
|
819
|
+
dogfoodOnboarding = options;
|
|
820
|
+
if (!config)
|
|
821
|
+
YaverFeedback.init({ autoLogin: true });
|
|
822
|
+
if (!YaverFeedback.isAuthed()) {
|
|
823
|
+
YaverFeedback.showLogin();
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
YaverFeedback.showMachinePicker();
|
|
827
|
+
}
|
|
828
|
+
static getDogfoodOnboarding() {
|
|
829
|
+
return dogfoodOnboarding;
|
|
830
|
+
}
|
|
831
|
+
static clearDogfoodOnboarding() {
|
|
832
|
+
dogfoodOnboarding = null;
|
|
833
|
+
}
|
|
806
834
|
/**
|
|
807
835
|
* Update the selected remote device. Resets the cached agent URL so the
|
|
808
836
|
* next `startReport()` (or FloatingButton press) rediscovers against the
|
|
@@ -811,9 +839,19 @@ class YaverFeedback {
|
|
|
811
839
|
static async setPreferredDevice(deviceId) {
|
|
812
840
|
if (!config)
|
|
813
841
|
return;
|
|
842
|
+
const wasUnified = !config.renderDeviceId
|
|
843
|
+
|| config.renderDeviceId === config.codingDeviceId
|
|
844
|
+
|| config.renderDeviceId === config.preferredDeviceId;
|
|
814
845
|
config.preferredDeviceId = deviceId;
|
|
846
|
+
config.codingDeviceId = deviceId;
|
|
847
|
+
if (wasUnified)
|
|
848
|
+
config.renderDeviceId = deviceId;
|
|
815
849
|
config.agentUrl = undefined;
|
|
850
|
+
config.codingAgentUrl = undefined;
|
|
851
|
+
if (wasUnified)
|
|
852
|
+
config.renderAgentUrl = undefined;
|
|
816
853
|
p2pClient = null;
|
|
854
|
+
renderP2PClient = null;
|
|
817
855
|
p2pAuthToken = null;
|
|
818
856
|
await YaverFeedback.discoverAgent();
|
|
819
857
|
}
|
|
@@ -823,8 +861,7 @@ class YaverFeedback {
|
|
|
823
861
|
return null;
|
|
824
862
|
const preferredDeviceId = config.preferredDeviceId;
|
|
825
863
|
const devices = await (0, auth_1.listReachableDevices)(config.authToken);
|
|
826
|
-
|
|
827
|
-
return all.find((device) => device.deviceId === preferredDeviceId) ?? null;
|
|
864
|
+
return devices.owned.find((device) => device.deviceId === preferredDeviceId) ?? null;
|
|
828
865
|
}
|
|
829
866
|
/**
|
|
830
867
|
* Trigger remote device-auth for a CLI runner on the selected agent
|
|
@@ -874,6 +911,7 @@ class YaverFeedback {
|
|
|
874
911
|
config.agentUrl = undefined;
|
|
875
912
|
}
|
|
876
913
|
p2pClient = null;
|
|
914
|
+
renderP2PClient = null;
|
|
877
915
|
p2pAuthToken = null;
|
|
878
916
|
}
|
|
879
917
|
/**
|
|
@@ -1119,6 +1157,61 @@ class YaverFeedback {
|
|
|
1119
1157
|
static getConfig() {
|
|
1120
1158
|
return config;
|
|
1121
1159
|
}
|
|
1160
|
+
/** Current third-party SDK mode. Fails closed unless enabled + account match. */
|
|
1161
|
+
static getDogfoodStatus() {
|
|
1162
|
+
return (0, dogfoodPolicy_1.resolveSDKDogfood)(config?.dogfood);
|
|
1163
|
+
}
|
|
1164
|
+
/** One-call, account-free Dogfood bootstrap for third-party apps. On first
|
|
1165
|
+
* launch it creates/proves the installation key and returns pending; after
|
|
1166
|
+
* owner approval the same call obtains a short-lived scoped Yaver session
|
|
1167
|
+
* and enables Dogfood UX without the host app implementing OAuth. */
|
|
1168
|
+
static async enableDeviceDogfood(options) {
|
|
1169
|
+
const client = new deviceDogfood_1.YaverDeviceDogfood(options);
|
|
1170
|
+
let status = await client.status();
|
|
1171
|
+
if (status === 'unregistered' || status === 'pending' || status === 'cancelled' || status === 'revoked' || status === 'superseded') {
|
|
1172
|
+
const enrolled = status === 'unregistered' || status === 'pending'
|
|
1173
|
+
? await client.enroll(react_native_1.Platform.OS)
|
|
1174
|
+
: await client.reRegister(react_native_1.Platform.OS);
|
|
1175
|
+
status = enrolled.status;
|
|
1176
|
+
}
|
|
1177
|
+
const info = await client.enrollmentInfo();
|
|
1178
|
+
const session = status === 'active' ? await client.session() : null;
|
|
1179
|
+
if (!config)
|
|
1180
|
+
YaverFeedback.init({ autoLogin: false });
|
|
1181
|
+
if (config) {
|
|
1182
|
+
config.dogfood = {
|
|
1183
|
+
enabled: status === 'active' && !!session,
|
|
1184
|
+
appId: options.appId,
|
|
1185
|
+
installationId: info.installationId,
|
|
1186
|
+
installationStatus: status === 'active' && session ? 'active' : status === 'unregistered' ? 'pending' : status,
|
|
1187
|
+
label: options.label,
|
|
1188
|
+
};
|
|
1189
|
+
// A normal Yaver OAuth session remains authoritative for the runtime
|
|
1190
|
+
// wizard. Only account-free hosts adopt the narrow installation token.
|
|
1191
|
+
if (session && !config.authToken)
|
|
1192
|
+
await YaverFeedback.setAuthToken(session.token);
|
|
1193
|
+
}
|
|
1194
|
+
try {
|
|
1195
|
+
const { DeviceEventEmitter } = require('react-native');
|
|
1196
|
+
DeviceEventEmitter.emit('yaverFeedback:dogfoodChanged', { active: !!session, status });
|
|
1197
|
+
}
|
|
1198
|
+
catch { /* noop */ }
|
|
1199
|
+
return { status, installationId: info.installationId, session };
|
|
1200
|
+
}
|
|
1201
|
+
/** Confirmed by the Y badge/UI before this is called. Reverts this SDK to
|
|
1202
|
+
* normal Feedback mode for the remainder of the app run. */
|
|
1203
|
+
static async exitDogfoodMode() {
|
|
1204
|
+
const cfg = config?.dogfood;
|
|
1205
|
+
if (!cfg)
|
|
1206
|
+
return;
|
|
1207
|
+
cfg.enabled = false;
|
|
1208
|
+
await cfg.onExit?.();
|
|
1209
|
+
try {
|
|
1210
|
+
const { DeviceEventEmitter } = require('react-native');
|
|
1211
|
+
DeviceEventEmitter.emit('yaverFeedback:dogfoodChanged', { active: false, exited: true });
|
|
1212
|
+
}
|
|
1213
|
+
catch { /* noop */ }
|
|
1214
|
+
}
|
|
1122
1215
|
/** Returns the resolved relay password the SDK is currently using.
|
|
1123
1216
|
* Empty string when no relay routing is in play (direct LAN agent
|
|
1124
1217
|
* URLs need no password). Callers attaching it to relay-routed
|
|
@@ -1286,6 +1379,16 @@ class YaverFeedback {
|
|
|
1286
1379
|
static getP2PClient() {
|
|
1287
1380
|
return p2pClient;
|
|
1288
1381
|
}
|
|
1382
|
+
/** Renderer/reload route; intentionally distinct from the coding client. */
|
|
1383
|
+
static getRenderP2PClient() {
|
|
1384
|
+
return renderP2PClient ?? p2pClient;
|
|
1385
|
+
}
|
|
1386
|
+
static getMachineRouting() {
|
|
1387
|
+
return {
|
|
1388
|
+
codingDeviceId: config?.codingDeviceId || config?.preferredDeviceId,
|
|
1389
|
+
renderDeviceId: config?.renderDeviceId || config?.codingDeviceId || config?.preferredDeviceId,
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1289
1392
|
// ─── One-stop SaaS replacement methods ─────────────────────────
|
|
1290
1393
|
//
|
|
1291
1394
|
// These are the three solo-dev SaaS-replacement entry points
|
|
@@ -1396,6 +1499,7 @@ class YaverFeedback {
|
|
|
1396
1499
|
config.agentUrl = result.url;
|
|
1397
1500
|
const rp = await resolveRelayPassword(config.authToken ?? '');
|
|
1398
1501
|
p2pClient = new P2PClient_1.P2PClient(result.url, config.authToken ?? '', rp);
|
|
1502
|
+
renderP2PClient = p2pClient;
|
|
1399
1503
|
}
|
|
1400
1504
|
}
|
|
1401
1505
|
catch { }
|
|
@@ -1610,6 +1714,7 @@ class YaverFeedback {
|
|
|
1610
1714
|
enabled = false;
|
|
1611
1715
|
config = null;
|
|
1612
1716
|
p2pClient = null;
|
|
1717
|
+
renderP2PClient = null;
|
|
1613
1718
|
errorBuffer = [];
|
|
1614
1719
|
}
|
|
1615
1720
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/** Hide the mark for the rest of this run. Exposed on YaverFeedback so a host
|
|
3
|
+
* can wire its own "hide" control, and used by the sheet's own button. */
|
|
4
|
+
export declare function hideYaverModeBadge(): void;
|
|
5
|
+
/** Bring it back — e.g. when a NEW guest bundle loads and the context changed. */
|
|
6
|
+
export declare function showYaverModeBadge(): void;
|
|
7
|
+
export declare function isYaverModeBadgeHidden(): boolean;
|
|
8
|
+
export interface YaverModeBadgeProps {
|
|
9
|
+
/** Corner to sit in. Default bottom-left: bottom-right is where most apps
|
|
10
|
+
* put a FAB, and the badge must never compete with the app's own action. */
|
|
11
|
+
position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
|
|
12
|
+
/** Render even outside Yaver. Development aid only — the badge is meaningless
|
|
13
|
+
* in a standalone build and would be wallpaper. */
|
|
14
|
+
force?: boolean;
|
|
15
|
+
/** Account-scoped third-party Dogfood mode. Keeps the same Yaver Y mark but
|
|
16
|
+
* changes the explanation and offers a confirmed exit to normal Feedback. */
|
|
17
|
+
dogfood?: boolean;
|
|
18
|
+
dogfoodLabel?: string;
|
|
19
|
+
onExitDogfood?: () => void | Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare function YaverModeBadge({ position, force, dogfood, dogfoodLabel, onExitDogfood, }: YaverModeBadgeProps): React.JSX.Element | null;
|
|
22
|
+
export default YaverModeBadge;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// YaverModeBadge.tsx — the polite "you're running inside Yaver" mark, for
|
|
3
|
+
// THIRD-PARTY apps.
|
|
4
|
+
//
|
|
5
|
+
// ── Why a third-party app needs this ────────────────────────────────────────
|
|
6
|
+
//
|
|
7
|
+
// When your app is pushed into Yaver's container as a Hermes bundle, it looks
|
|
8
|
+
// exactly like your installed build. Same icon, same launch, same everything —
|
|
9
|
+
// except it is a development bundle from someone's branch. Testers have chased
|
|
10
|
+
// "bugs" that were only unbuilt work, and have been unable to find their way
|
|
11
|
+
// back to the real app because nothing on screen said there was anywhere to go.
|
|
12
|
+
//
|
|
13
|
+
// So the SDK shows one small Y, low contrast, in a corner. Tapping it says what
|
|
14
|
+
// you're in and how to leave. Nothing else.
|
|
15
|
+
//
|
|
16
|
+
// ── Defaults, and why ───────────────────────────────────────────────────────
|
|
17
|
+
//
|
|
18
|
+
// DEFAULT ENABLED (`modeBadge: true`), because the failure it prevents is
|
|
19
|
+
// silent and lands on a tester rather than the developer — exactly the class
|
|
20
|
+
// that should not be opt-in. Turn it off with `modeBadge: false` when your app
|
|
21
|
+
// has its own in-container indicator, or when a screenshot-perfect surface
|
|
22
|
+
// genuinely cannot afford 22 points in a corner.
|
|
23
|
+
//
|
|
24
|
+
// ── What it is NOT ──────────────────────────────────────────────────────────
|
|
25
|
+
//
|
|
26
|
+
// It is NOT the escape. Yaver's container owns that: shake opens Yaver's own
|
|
27
|
+
// overlay with "Back to Yaver". This badge only tells you the gesture exists.
|
|
28
|
+
// Making it the exit would put the way out inside the previewed app, which is
|
|
29
|
+
// the trap the container's escape-ownership rules exist to prevent — the guest
|
|
30
|
+
// could style over it, or unmount it, and the tester would be stuck.
|
|
31
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
32
|
+
if (k2 === undefined) k2 = k;
|
|
33
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
34
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
35
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
36
|
+
}
|
|
37
|
+
Object.defineProperty(o, k2, desc);
|
|
38
|
+
}) : (function(o, m, k, k2) {
|
|
39
|
+
if (k2 === undefined) k2 = k;
|
|
40
|
+
o[k2] = m[k];
|
|
41
|
+
}));
|
|
42
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
43
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
44
|
+
}) : function(o, v) {
|
|
45
|
+
o["default"] = v;
|
|
46
|
+
});
|
|
47
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
48
|
+
var ownKeys = function(o) {
|
|
49
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
50
|
+
var ar = [];
|
|
51
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
52
|
+
return ar;
|
|
53
|
+
};
|
|
54
|
+
return ownKeys(o);
|
|
55
|
+
};
|
|
56
|
+
return function (mod) {
|
|
57
|
+
if (mod && mod.__esModule) return mod;
|
|
58
|
+
var result = {};
|
|
59
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
60
|
+
__setModuleDefault(result, mod);
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
})();
|
|
64
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
65
|
+
exports.hideYaverModeBadge = hideYaverModeBadge;
|
|
66
|
+
exports.showYaverModeBadge = showYaverModeBadge;
|
|
67
|
+
exports.isYaverModeBadgeHidden = isYaverModeBadgeHidden;
|
|
68
|
+
exports.YaverModeBadge = YaverModeBadge;
|
|
69
|
+
const react_1 = __importStar(require("react"));
|
|
70
|
+
const react_native_1 = require("react-native");
|
|
71
|
+
/** True only inside Yaver's container: the YaverInfo native module is
|
|
72
|
+
* registered by Yaver's app and by nothing else. Same probe ShakeDetector
|
|
73
|
+
* and P2PClient use — one answer, not three. */
|
|
74
|
+
function isInsideYaver() {
|
|
75
|
+
try {
|
|
76
|
+
return !!react_native_1.NativeModules?.YaverInfo;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Per-RUN dismissal, shared by every mounted badge.
|
|
84
|
+
*
|
|
85
|
+
* Deliberately in memory, not storage. A permanently hidden badge recreates
|
|
86
|
+
* exactly the problem the badge exists to prevent: a tester who cannot tell an
|
|
87
|
+
* unbuilt branch from the installed app, and cannot find the way back. Polite
|
|
88
|
+
* means not nagging within a session — it does not mean permanent amnesia
|
|
89
|
+
* about which build you are looking at. A relaunch is a new context.
|
|
90
|
+
*
|
|
91
|
+
* An APP that wants it gone for good passes `modeBadge: false` at init. That is
|
|
92
|
+
* a developer making an informed choice, which is a different thing from a
|
|
93
|
+
* tester clearing their screen for a minute.
|
|
94
|
+
*/
|
|
95
|
+
let hiddenThisRun = false;
|
|
96
|
+
const hideListeners = new Set();
|
|
97
|
+
/** Hide the mark for the rest of this run. Exposed on YaverFeedback so a host
|
|
98
|
+
* can wire its own "hide" control, and used by the sheet's own button. */
|
|
99
|
+
function hideYaverModeBadge() {
|
|
100
|
+
hiddenThisRun = true;
|
|
101
|
+
hideListeners.forEach((l) => {
|
|
102
|
+
try {
|
|
103
|
+
l();
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
/* one bad listener mustn't block the others */
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** Bring it back — e.g. when a NEW guest bundle loads and the context changed. */
|
|
111
|
+
function showYaverModeBadge() {
|
|
112
|
+
hiddenThisRun = false;
|
|
113
|
+
hideListeners.forEach((l) => {
|
|
114
|
+
try {
|
|
115
|
+
l();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* noop */
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function isYaverModeBadgeHidden() {
|
|
123
|
+
return hiddenThisRun;
|
|
124
|
+
}
|
|
125
|
+
function YaverModeBadge({ position = 'bottom-left', force = false, dogfood = false, dogfoodLabel = 'Dogfood', onExitDogfood, }) {
|
|
126
|
+
const [open, setOpen] = (0, react_1.useState)(false);
|
|
127
|
+
const [, bump] = (0, react_1.useState)(0);
|
|
128
|
+
(0, react_1.useEffect)(() => {
|
|
129
|
+
const listener = () => bump((n) => n + 1);
|
|
130
|
+
hideListeners.add(listener);
|
|
131
|
+
return () => {
|
|
132
|
+
hideListeners.delete(listener);
|
|
133
|
+
};
|
|
134
|
+
}, []);
|
|
135
|
+
// Standalone builds render nothing at all — zero cost, zero pixels.
|
|
136
|
+
if (!force && !dogfood && !isInsideYaver())
|
|
137
|
+
return null;
|
|
138
|
+
if (hiddenThisRun)
|
|
139
|
+
return null;
|
|
140
|
+
const vertical = position.startsWith('top') ? { top: 44 } : { bottom: 28 };
|
|
141
|
+
const horizontal = position.endsWith('left') ? { left: 12 } : { right: 12 };
|
|
142
|
+
return (<>
|
|
143
|
+
<react_native_1.View
|
|
144
|
+
// The wrapper never intercepts touches; only the 22pt mark does. A
|
|
145
|
+
// full-screen overlay that swallowed taps would break the app it is
|
|
146
|
+
// supposed to be politely annotating.
|
|
147
|
+
pointerEvents="box-none" style={{ position: 'absolute', ...vertical, ...horizontal, zIndex: 9998 }}>
|
|
148
|
+
<react_native_1.Pressable onPress={() => setOpen(true)} accessibilityRole="button" accessibilityLabel={dogfood ? `Exit ${dogfoodLabel} Dogfood mode` : 'Running inside Yaver'} hitSlop={10} style={({ pressed }) => ({
|
|
149
|
+
width: 22,
|
|
150
|
+
height: 22,
|
|
151
|
+
borderRadius: 11,
|
|
152
|
+
alignItems: 'center',
|
|
153
|
+
justifyContent: 'center',
|
|
154
|
+
borderWidth: 1,
|
|
155
|
+
borderColor: 'rgba(124,92,255,0.45)',
|
|
156
|
+
backgroundColor: 'rgba(124,92,255,0.14)',
|
|
157
|
+
opacity: pressed ? 0.6 : 0.9,
|
|
158
|
+
})}>
|
|
159
|
+
<react_native_1.Text style={{ color: '#7C5CFF', fontSize: 12, fontWeight: '700', lineHeight: 14 }}>Y</react_native_1.Text>
|
|
160
|
+
</react_native_1.Pressable>
|
|
161
|
+
</react_native_1.View>
|
|
162
|
+
|
|
163
|
+
<react_native_1.Modal visible={open} transparent animationType="fade" onRequestClose={() => setOpen(false)}>
|
|
164
|
+
<react_native_1.Pressable style={{ flex: 1, backgroundColor: '#0008' }} onPress={() => setOpen(false)}>
|
|
165
|
+
<react_native_1.Pressable onPress={(e) => e.stopPropagation()} style={{
|
|
166
|
+
margin: 24,
|
|
167
|
+
marginTop: 'auto',
|
|
168
|
+
marginBottom: 48,
|
|
169
|
+
borderRadius: 16,
|
|
170
|
+
padding: 18,
|
|
171
|
+
backgroundColor: '#151519',
|
|
172
|
+
borderWidth: 1,
|
|
173
|
+
borderColor: '#2a2a32',
|
|
174
|
+
gap: 10,
|
|
175
|
+
}}>
|
|
176
|
+
<react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
177
|
+
<react_native_1.Text style={{ color: '#7C5CFF', fontSize: 15, fontWeight: '800' }}>Y</react_native_1.Text>
|
|
178
|
+
<react_native_1.Text style={{ color: '#fff', fontSize: 15, fontWeight: '700' }}>
|
|
179
|
+
{dogfood ? `${dogfoodLabel} Dogfood mode` : 'Running inside Yaver'}
|
|
180
|
+
</react_native_1.Text>
|
|
181
|
+
</react_native_1.View>
|
|
182
|
+
<react_native_1.Text style={{ color: '#b6b6c0', fontSize: 13, lineHeight: 19 }}>
|
|
183
|
+
{dogfood
|
|
184
|
+
? 'This approved account is improving the app directly through Yaver. Tasks and re-render actions target this app instead of creating a feedback report.'
|
|
185
|
+
: 'This is a development build loaded into Yaver — not the version installed on this device. Anything unfinished here is work in progress, not a released bug.'}
|
|
186
|
+
</react_native_1.Text>
|
|
187
|
+
{dogfood ? (<react_native_1.Pressable onPress={() => react_native_1.Alert.alert('Exit Dogfood mode?', 'Return this SDK to normal Feedback mode. Your coding tasks and source changes are untouched.', [
|
|
188
|
+
{ text: 'Keep Dogfooding', style: 'cancel' },
|
|
189
|
+
{ text: 'Exit Dogfood', style: 'destructive', onPress: () => {
|
|
190
|
+
void onExitDogfood?.();
|
|
191
|
+
setOpen(false);
|
|
192
|
+
} },
|
|
193
|
+
])} style={{ paddingVertical: 10, alignItems: 'center' }}>
|
|
194
|
+
<react_native_1.Text style={{ color: '#fca5a5', fontSize: 13, fontWeight: '700' }}>Exit Dogfood mode</react_native_1.Text>
|
|
195
|
+
</react_native_1.Pressable>) : (<react_native_1.Text style={{ color: '#b6b6c0', fontSize: 13, lineHeight: 19 }}>
|
|
196
|
+
Shake the device to open Yaver's overlay, where "Back to Yaver" returns you to the installed app.
|
|
197
|
+
</react_native_1.Text>)}
|
|
198
|
+
{/* Polite means closeable. "for now", never "don't show again":
|
|
199
|
+
the badge returns next launch so nobody forgets which build
|
|
200
|
+
they're testing. */}
|
|
201
|
+
<react_native_1.Pressable onPress={() => {
|
|
202
|
+
hideYaverModeBadge();
|
|
203
|
+
setOpen(false);
|
|
204
|
+
}} style={{ paddingVertical: 10, alignItems: 'center' }}>
|
|
205
|
+
<react_native_1.Text style={{ color: '#8a8a96', fontSize: 13 }}>Hide for now</react_native_1.Text>
|
|
206
|
+
</react_native_1.Pressable>
|
|
207
|
+
<react_native_1.Text style={{ color: '#6b6b76', fontSize: 11, textAlign: 'center', lineHeight: 15 }}>
|
|
208
|
+
Hidden until you next launch this build.
|
|
209
|
+
</react_native_1.Text>
|
|
210
|
+
|
|
211
|
+
<react_native_1.Pressable onPress={() => setOpen(false)} style={{ paddingVertical: 10, alignItems: 'center' }}>
|
|
212
|
+
<react_native_1.Text style={{ color: '#8a8a96', fontSize: 13 }}>Close</react_native_1.Text>
|
|
213
|
+
</react_native_1.Pressable>
|
|
214
|
+
</react_native_1.Pressable>
|
|
215
|
+
</react_native_1.Pressable>
|
|
216
|
+
</react_native_1.Modal>
|
|
217
|
+
</>);
|
|
218
|
+
}
|
|
219
|
+
exports.default = YaverModeBadge;
|
|
@@ -8,7 +8,7 @@ describe('auth device listing', () => {
|
|
|
8
8
|
jest.clearAllMocks();
|
|
9
9
|
mockFetch.mockReset();
|
|
10
10
|
});
|
|
11
|
-
it('
|
|
11
|
+
it('returns the owner devices emitted by /devices/list', async () => {
|
|
12
12
|
mockFetch.mockResolvedValue({
|
|
13
13
|
ok: true,
|
|
14
14
|
json: () => Promise.resolve({
|
|
@@ -18,24 +18,10 @@ describe('auth device listing', () => {
|
|
|
18
18
|
name: 'My Mac',
|
|
19
19
|
platform: 'macos',
|
|
20
20
|
isOnline: true,
|
|
21
|
-
isGuest: false,
|
|
22
21
|
quicHost: '10.0.0.10',
|
|
23
22
|
quicPort: 18080,
|
|
24
23
|
lastHeartbeat: 123,
|
|
25
24
|
},
|
|
26
|
-
{
|
|
27
|
-
deviceId: 'guest-1',
|
|
28
|
-
name: 'yaver-test-ephemeral',
|
|
29
|
-
platform: 'linux',
|
|
30
|
-
isOnline: true,
|
|
31
|
-
isGuest: true,
|
|
32
|
-
hostName: 'Host User',
|
|
33
|
-
hostEmail: 'host@example.com',
|
|
34
|
-
accessScope: 'shared-scoped',
|
|
35
|
-
quicHost: '198.51.100.20',
|
|
36
|
-
quicPort: 18080,
|
|
37
|
-
lastHeartbeat: 456,
|
|
38
|
-
},
|
|
39
25
|
],
|
|
40
26
|
}),
|
|
41
27
|
});
|
|
@@ -44,39 +30,6 @@ describe('auth device listing', () => {
|
|
|
44
30
|
headers: { Authorization: 'Bearer sdk-user-token' },
|
|
45
31
|
}));
|
|
46
32
|
expect(result.owned).toHaveLength(1);
|
|
47
|
-
expect(result.shared).toHaveLength(1);
|
|
48
33
|
expect(result.owned[0].deviceId).toBe('own-1');
|
|
49
|
-
expect(result.shared[0]).toMatchObject({
|
|
50
|
-
deviceId: 'guest-1',
|
|
51
|
-
isGuest: true,
|
|
52
|
-
hostEmail: 'host@example.com',
|
|
53
|
-
accessScope: 'shared-scoped',
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
it('shows shared devices even when the guest owns no machines', async () => {
|
|
57
|
-
mockFetch.mockResolvedValue({
|
|
58
|
-
ok: true,
|
|
59
|
-
json: () => Promise.resolve({
|
|
60
|
-
devices: [
|
|
61
|
-
{
|
|
62
|
-
deviceId: 'guest-only',
|
|
63
|
-
name: 'yaver-test-ephemeral',
|
|
64
|
-
platform: 'linux',
|
|
65
|
-
isOnline: true,
|
|
66
|
-
isGuest: true,
|
|
67
|
-
hostName: 'Host User',
|
|
68
|
-
hostEmail: 'host@example.com',
|
|
69
|
-
accessScope: 'shared-scoped',
|
|
70
|
-
quicHost: '198.51.100.20',
|
|
71
|
-
quicPort: 18080,
|
|
72
|
-
lastHeartbeat: 789,
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
}),
|
|
76
|
-
});
|
|
77
|
-
const result = await (0, auth_1.listReachableDevices)('guest-only-token');
|
|
78
|
-
expect(result.owned).toEqual([]);
|
|
79
|
-
expect(result.shared).toHaveLength(1);
|
|
80
|
-
expect(result.shared[0].deviceId).toBe('guest-only');
|
|
81
34
|
});
|
|
82
35
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|