yaver-feedback-react-native 0.8.8 → 0.8.10
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/dist/FeedbackModal.js +69 -2
- package/dist/P2PClient.d.ts +6 -0
- package/dist/P2PClient.js +19 -0
- package/dist/YaverFeedback.d.ts +4 -0
- package/dist/YaverFeedback.js +140 -14
- package/package.json +1 -1
- package/src/FeedbackModal.tsx +93 -2
- package/src/P2PClient.ts +23 -0
- package/src/YaverFeedback.ts +141 -14
package/dist/FeedbackModal.js
CHANGED
|
@@ -1049,7 +1049,15 @@ const RunnerAuthNativeModal = ({ runner, onClose }) => {
|
|
|
1049
1049
|
const [session, setSession] = (0, react_1.useState)(null);
|
|
1050
1050
|
const [startError, setStartError] = (0, react_1.useState)(null);
|
|
1051
1051
|
const [copied, setCopied] = (0, react_1.useState)(false);
|
|
1052
|
+
const [pasteCode, setPasteCode] = (0, react_1.useState)('');
|
|
1053
|
+
const [submitting, setSubmitting] = (0, react_1.useState)(false);
|
|
1054
|
+
const [submitError, setSubmitError] = (0, react_1.useState)(null);
|
|
1052
1055
|
const startedRef = (0, react_1.useRef)(false);
|
|
1056
|
+
// Claude is the only runner that needs the user to paste a verifier
|
|
1057
|
+
// code back from platform.claude.com's callback page; Codex device-
|
|
1058
|
+
// auth and OpenCode (no OAuth at all) bypass this. Mirrors the
|
|
1059
|
+
// requiresPasteBack check in iOS YaverRunnerAuthFlowPane.swift.
|
|
1060
|
+
const needsPasteBack = runner === 'claude' || runner === 'claude-code';
|
|
1053
1061
|
(0, react_1.useEffect)(() => {
|
|
1054
1062
|
if (startedRef.current)
|
|
1055
1063
|
return;
|
|
@@ -1164,9 +1172,68 @@ const RunnerAuthNativeModal = ({ runner, onClose }) => {
|
|
|
1164
1172
|
</react_native_1.Text>
|
|
1165
1173
|
</react_native_1.Pressable>
|
|
1166
1174
|
</react_native_1.View>) : null}
|
|
1175
|
+
{needsPasteBack ? (<react_native_1.View style={{ marginTop: 14 }}>
|
|
1176
|
+
<react_native_1.Text style={runnerAuthModalStyles.codeLabel}>
|
|
1177
|
+
PASTE CODE FROM CLAUDE.COM
|
|
1178
|
+
</react_native_1.Text>
|
|
1179
|
+
<react_native_1.View style={{ flexDirection: 'row', gap: 8, marginTop: 6 }}>
|
|
1180
|
+
<react_native_1.View style={{
|
|
1181
|
+
flex: 1,
|
|
1182
|
+
backgroundColor: 'rgba(148,163,184,0.10)',
|
|
1183
|
+
borderRadius: 10,
|
|
1184
|
+
paddingHorizontal: 10,
|
|
1185
|
+
}}>
|
|
1186
|
+
{/* Lazy-import TextInput so the SDK doesn't pull
|
|
1187
|
+
extra surface from react-native at module load. */}
|
|
1188
|
+
{(() => {
|
|
1189
|
+
const { TextInput } = require('react-native');
|
|
1190
|
+
return (<TextInput value={pasteCode} onChangeText={(t) => {
|
|
1191
|
+
setPasteCode(t);
|
|
1192
|
+
setSubmitError(null);
|
|
1193
|
+
}} placeholder="paste code here" placeholderTextColor="#64748b" autoCapitalize="none" autoCorrect={false} spellCheck={false} style={{ color: '#f1f5f9', fontSize: 14, paddingVertical: 10 }}/>);
|
|
1194
|
+
})()}
|
|
1195
|
+
</react_native_1.View>
|
|
1196
|
+
<react_native_1.Pressable disabled={!pasteCode.trim() || submitting} onPress={async () => {
|
|
1197
|
+
if (!session || !pasteCode.trim())
|
|
1198
|
+
return;
|
|
1199
|
+
setSubmitting(true);
|
|
1200
|
+
setSubmitError(null);
|
|
1201
|
+
try {
|
|
1202
|
+
const next = await YaverFeedback_1.YaverFeedback.submitRunnerBrowserAuthCode(session.id, pasteCode.trim());
|
|
1203
|
+
setSession(next);
|
|
1204
|
+
setPasteCode('');
|
|
1205
|
+
}
|
|
1206
|
+
catch (err) {
|
|
1207
|
+
setSubmitError(err instanceof Error ? err.message : String(err));
|
|
1208
|
+
}
|
|
1209
|
+
finally {
|
|
1210
|
+
setSubmitting(false);
|
|
1211
|
+
}
|
|
1212
|
+
}} style={{
|
|
1213
|
+
paddingHorizontal: 14,
|
|
1214
|
+
justifyContent: 'center',
|
|
1215
|
+
backgroundColor: !pasteCode.trim() || submitting
|
|
1216
|
+
? 'rgba(124,58,237,0.4)'
|
|
1217
|
+
: '#7c3aed',
|
|
1218
|
+
borderRadius: 10,
|
|
1219
|
+
}}>
|
|
1220
|
+
<react_native_1.Text style={{ color: 'white', fontWeight: '600' }}>
|
|
1221
|
+
{submitting ? '…' : 'Submit'}
|
|
1222
|
+
</react_native_1.Text>
|
|
1223
|
+
</react_native_1.Pressable>
|
|
1224
|
+
</react_native_1.View>
|
|
1225
|
+
{submitError ? (<react_native_1.Text style={{
|
|
1226
|
+
marginTop: 6,
|
|
1227
|
+
color: '#fca5a5',
|
|
1228
|
+
fontSize: 12,
|
|
1229
|
+
}}>
|
|
1230
|
+
{submitError}
|
|
1231
|
+
</react_native_1.Text>) : null}
|
|
1232
|
+
</react_native_1.View>) : null}
|
|
1167
1233
|
<react_native_1.Text style={runnerAuthModalStyles.phishingHint}>
|
|
1168
|
-
|
|
1169
|
-
|
|
1234
|
+
{needsPasteBack
|
|
1235
|
+
? 'After authorising on platform.claude.com, copy the code from the callback page and paste it above. Never share this code.'
|
|
1236
|
+
: 'Device codes are a common phishing target. Never share this code. This dialog turns green automatically once sign-in completes.'}
|
|
1170
1237
|
</react_native_1.Text>
|
|
1171
1238
|
</react_native_1.View>)}
|
|
1172
1239
|
</react_native_1.View>
|
package/dist/P2PClient.d.ts
CHANGED
|
@@ -47,6 +47,12 @@ export declare class P2PClient {
|
|
|
47
47
|
startRunnerBrowserAuth(runner: string): Promise<RunnerBrowserAuthSession>;
|
|
48
48
|
getRunnerBrowserAuthStatus(sessionId: string): Promise<RunnerBrowserAuthSession>;
|
|
49
49
|
cancelRunnerBrowserAuth(sessionId: string): Promise<void>;
|
|
50
|
+
/** Submit the verifier code Anthropic shows on the callback page so
|
|
51
|
+
* the agent can finalise claude CLI's OAuth handshake. Codex doesn't
|
|
52
|
+
* use this — its device-auth flow auto-resolves via polling — but
|
|
53
|
+
* the SDK still exposes it for symmetry with mobile/src/components/
|
|
54
|
+
* RunnerAuthModal.tsx and the Swift YaverRunnerAuthFlowPane. */
|
|
55
|
+
submitRunnerBrowserAuthCode(sessionId: string, code: string): Promise<RunnerBrowserAuthSession>;
|
|
50
56
|
capabilitySnapshot(): Promise<CapabilitySnapshot | null>;
|
|
51
57
|
incidents(opts?: {
|
|
52
58
|
category?: string;
|
package/dist/P2PClient.js
CHANGED
|
@@ -155,6 +155,25 @@ class P2PClient {
|
|
|
155
155
|
}
|
|
156
156
|
catch { /* best-effort */ }
|
|
157
157
|
}
|
|
158
|
+
/** Submit the verifier code Anthropic shows on the callback page so
|
|
159
|
+
* the agent can finalise claude CLI's OAuth handshake. Codex doesn't
|
|
160
|
+
* use this — its device-auth flow auto-resolves via polling — but
|
|
161
|
+
* the SDK still exposes it for symmetry with mobile/src/components/
|
|
162
|
+
* RunnerAuthModal.tsx and the Swift YaverRunnerAuthFlowPane. */
|
|
163
|
+
async submitRunnerBrowserAuthCode(sessionId, code) {
|
|
164
|
+
const url = `${this.baseUrl}/runner-auth/browser/submit-code`;
|
|
165
|
+
const resp = await fetch(url, {
|
|
166
|
+
method: 'POST',
|
|
167
|
+
headers: { ...this.authHeaders(), 'Content-Type': 'application/json' },
|
|
168
|
+
body: JSON.stringify({ id: sessionId, code }),
|
|
169
|
+
});
|
|
170
|
+
if (!resp.ok) {
|
|
171
|
+
const text = await resp.text().catch(() => '');
|
|
172
|
+
throw new Error(`submitRunnerBrowserAuthCode HTTP ${resp.status}: ${text}`);
|
|
173
|
+
}
|
|
174
|
+
const data = await resp.json();
|
|
175
|
+
return data.session;
|
|
176
|
+
}
|
|
158
177
|
async capabilitySnapshot() {
|
|
159
178
|
try {
|
|
160
179
|
const resp = await fetch(`${this.baseUrl}/capabilities/snapshot`, { headers: this.authHeaders() });
|
package/dist/YaverFeedback.d.ts
CHANGED
|
@@ -78,6 +78,10 @@ export declare class YaverFeedback {
|
|
|
78
78
|
static startRunnerBrowserAuth(runner: string): Promise<import('./types').RunnerBrowserAuthSession>;
|
|
79
79
|
static getRunnerBrowserAuthStatus(sessionId: string): Promise<import('./types').RunnerBrowserAuthSession>;
|
|
80
80
|
static cancelRunnerBrowserAuth(sessionId: string): Promise<void>;
|
|
81
|
+
/** Submit the Claude paste-back verifier so the agent can finalise the
|
|
82
|
+
* OAuth handshake. RunnerAuthModal calls this after the user copies
|
|
83
|
+
* the code from platform.claude.com's callback page. */
|
|
84
|
+
static submitRunnerBrowserAuthCode(sessionId: string, code: string): Promise<import('./types').RunnerBrowserAuthSession>;
|
|
81
85
|
/**
|
|
82
86
|
* Sign out: clear cached token + device, tear down the P2P client. The
|
|
83
87
|
* SDK stays enabled; the next feedback trigger will re-prompt for login.
|
package/dist/YaverFeedback.js
CHANGED
|
@@ -13,10 +13,13 @@ const preferences_1 = require("./preferences");
|
|
|
13
13
|
// (mobile/ios/Yaver/YaverInfo.{swift,m} + Android counterpart); a
|
|
14
14
|
// standalone app bundled by its own developer has no such module.
|
|
15
15
|
// When the SDK is loaded through Yaver's Hermes-push guest runtime we
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// the
|
|
16
|
+
// run in HOST MODE: dormant by default (no shake detector, no auto
|
|
17
|
+
// BlackBox, no QuickActionIcon — Yaver's host shell owns those), but
|
|
18
|
+
// we DO register a DeviceEventEmitter listener so Yaver's overlay can
|
|
19
|
+
// flip the SDK live at runtime. When the user shakes inside the guest
|
|
20
|
+
// app and taps "Feedback" on the Yaver overlay, AppDelegate dispatches
|
|
21
|
+
// `yaverFeedback:startReport` into this bridge; the listener wakes the
|
|
22
|
+
// SDK and opens the modal in-place over the running guest UI.
|
|
20
23
|
function isRunningInsideYaverHost() {
|
|
21
24
|
try {
|
|
22
25
|
return !!react_native_1.NativeModules?.YaverInfo;
|
|
@@ -25,11 +28,108 @@ function isRunningInsideYaverHost() {
|
|
|
25
28
|
return false;
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// by
|
|
31
|
-
//
|
|
32
|
-
|
|
31
|
+
// Two distinct compile-time modes for a guest app like sfmg / talos:
|
|
32
|
+
//
|
|
33
|
+
// YAVER_HOST_MODE — bundled by Yaver's agent (/dev/build-native) for
|
|
34
|
+
// loading inside Yaver mobile. SDK code is in the
|
|
35
|
+
// bundle but boots PASSIVE: no shake detector, no
|
|
36
|
+
// auto-BlackBox, no container UI. Yaver's host
|
|
37
|
+
// overlay owns the shake gesture; when the user
|
|
38
|
+
// taps "Feedback" on Yaver's overlay, AppDelegate
|
|
39
|
+
// dispatches yaverFeedback:hostActivate into this
|
|
40
|
+
// bridge and the SDK runtime-flips active for a
|
|
41
|
+
// single feedback session.
|
|
42
|
+
//
|
|
43
|
+
// YAVER_SDK_MODE — sfmg's own standalone TestFlight / Play Store
|
|
44
|
+
// build, with the Yaver SDK embedded. SDK boots
|
|
45
|
+
// active: shake → modal directly, no Yaver host
|
|
46
|
+
// involved. Default for normal `expo build`.
|
|
47
|
+
//
|
|
48
|
+
// Both can be forced at build time via process.env. When neither is
|
|
49
|
+
// set, fall back to runtime detection: if the YaverInfo native module
|
|
50
|
+
// exists (we're inside Yaver), assume HOST_MODE; else SDK_MODE. This
|
|
51
|
+
// keeps older bundles (built before the agent learned to set the env)
|
|
52
|
+
// working unchanged.
|
|
53
|
+
const YAVER_HOST_MODE_BUILD = (() => {
|
|
54
|
+
try {
|
|
55
|
+
const v = process.env?.YAVER_HOST_MODE;
|
|
56
|
+
return v === 'true' || v === '1' || v === true || v === 1;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
62
|
+
const YAVER_SDK_MODE_BUILD = (() => {
|
|
63
|
+
try {
|
|
64
|
+
const v = process.env?.YAVER_SDK_MODE;
|
|
65
|
+
return v === 'true' || v === '1' || v === true || v === 1;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
// Effective mode after considering build flags AND runtime detection.
|
|
72
|
+
const IS_HOST_MODE = YAVER_HOST_MODE_BUILD ||
|
|
73
|
+
(!YAVER_SDK_MODE_BUILD && isRunningInsideYaverHost());
|
|
74
|
+
// Tracks whether we've been runtime-activated by the host (sfmg-in-Yaver
|
|
75
|
+
// case). Independent of `enabled` so we can tell "host turned us on for
|
|
76
|
+
// one shot" apart from "developer toggled enabled programmatically".
|
|
77
|
+
let hostActivated = false;
|
|
78
|
+
// Host-activation listener. Always registered when in HOST mode so a
|
|
79
|
+
// Yaver overlay tap can wake the SDK even before the guest's
|
|
80
|
+
// YaverFeedback.init() runs. AppDelegate (mobile/ios/Yaver/AppDelegate.
|
|
81
|
+
// swift::handleFeedbackTap) sends `yaverFeedback:startReport` into the
|
|
82
|
+
// guest bridge when the user picks Feedback on the shake overlay.
|
|
83
|
+
//
|
|
84
|
+
// Activation flow:
|
|
85
|
+
// 1. Try Yaver's existing bearer (NativeModules.YaverInfo.
|
|
86
|
+
// inheritedAuthToken, populated by Yaver mobile's auth.ts on
|
|
87
|
+
// sign-in). Validate against /auth/validate before trusting.
|
|
88
|
+
// 2. If valid: setAuthToken + open feedback modal in-place (the
|
|
89
|
+
// modal already supports hot reload, screenshot, vibing chat).
|
|
90
|
+
// 3. If missing or invalid: open the SDK's own login screen.
|
|
91
|
+
if (IS_HOST_MODE) {
|
|
92
|
+
try {
|
|
93
|
+
const { DeviceEventEmitter } = require('react-native');
|
|
94
|
+
DeviceEventEmitter.addListener('yaverFeedback:startReport', () => {
|
|
95
|
+
hostActivated = true;
|
|
96
|
+
enabled = true;
|
|
97
|
+
void (async () => {
|
|
98
|
+
const yi = react_native_1.NativeModules?.YaverInfo;
|
|
99
|
+
const inheritedToken = String(yi?.inheritedAuthToken || '').trim();
|
|
100
|
+
const inheritedAgent = String(yi?.inheritedAgentUrl || '').trim();
|
|
101
|
+
const inheritedDevice = String(yi?.inheritedDeviceId || '').trim();
|
|
102
|
+
if (inheritedToken) {
|
|
103
|
+
// Lazy-import auth.ts so module load doesn't drag the auth
|
|
104
|
+
// network code into the active set when the SDK is dormant.
|
|
105
|
+
const { validateToken } = require('./auth');
|
|
106
|
+
const user = await validateToken(inheritedToken).catch(() => null);
|
|
107
|
+
if (user) {
|
|
108
|
+
// Seed config + connect the SDK to the host's auth.
|
|
109
|
+
if (!config) {
|
|
110
|
+
YaverFeedback.init({
|
|
111
|
+
authToken: inheritedToken,
|
|
112
|
+
agentUrl: inheritedAgent || undefined,
|
|
113
|
+
preferredDeviceId: inheritedDevice || undefined,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
await YaverFeedback.setAuthToken(inheritedToken);
|
|
117
|
+
await YaverFeedback.startReport();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// No token, or token invalid — fall through to the SDK's own
|
|
122
|
+
// login screen. The user picks an OAuth provider; on success
|
|
123
|
+
// the modal continues with the new token.
|
|
124
|
+
if (!config) {
|
|
125
|
+
YaverFeedback.init({});
|
|
126
|
+
}
|
|
127
|
+
YaverFeedback.showLogin();
|
|
128
|
+
})();
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
catch { /* react-native unavailable in jsdom unit tests */ }
|
|
132
|
+
}
|
|
33
133
|
let config = null;
|
|
34
134
|
let enabled = false;
|
|
35
135
|
let p2pClient = null;
|
|
@@ -102,17 +202,34 @@ class YaverFeedback {
|
|
|
102
202
|
* via `YaverDiscovery` on the first `startReport()` call.
|
|
103
203
|
*/
|
|
104
204
|
static init(cfg) {
|
|
105
|
-
if (YAVER_HOST_SUPPRESS) {
|
|
106
|
-
// Running inside Yaver's super-host — yield to Yaver's native UX.
|
|
107
|
-
enabled = false;
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
205
|
config = {
|
|
111
206
|
trigger: 'shake',
|
|
112
207
|
maxRecordingDuration: 120,
|
|
113
208
|
autoLogin: true,
|
|
114
209
|
...cfg,
|
|
115
210
|
};
|
|
211
|
+
if (IS_HOST_MODE) {
|
|
212
|
+
// sfmg / talos / etc. running inside Yaver mobile (compile-time
|
|
213
|
+
// YAVER_HOST_MODE or runtime-detected). Store config so a later
|
|
214
|
+
// host activation (yaverFeedback:startReport from AppDelegate)
|
|
215
|
+
// can open the modal — but skip the side effects Yaver's host
|
|
216
|
+
// shell owns: shake detector, auto-BlackBox, QuickActionIcon.
|
|
217
|
+
enabled = false;
|
|
218
|
+
// Configure auth endpoints + strict-native-auth even in passive
|
|
219
|
+
// mode so a host-activated session uses the same login routing
|
|
220
|
+
// the standalone path would.
|
|
221
|
+
(0, auth_1.configureAuthEndpoints)({
|
|
222
|
+
convexSiteUrl: cfg.authConvexSiteUrl,
|
|
223
|
+
webBaseUrl: cfg.authWebBaseUrl,
|
|
224
|
+
});
|
|
225
|
+
(0, auth_1.setStrictNativeAuth)(cfg.strictNativeAuth === true);
|
|
226
|
+
if (!config.convexUrl) {
|
|
227
|
+
config.convexUrl = cfg.authConvexSiteUrl ?? auth_1.DEFAULT_CONVEX_SITE_URL;
|
|
228
|
+
}
|
|
229
|
+
maxErrors = cfg.maxCapturedErrors ?? 5;
|
|
230
|
+
errorBuffer = [];
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
116
233
|
if (config.disableShakeGesture && (!config.quickIcon || config.quickIcon === 'auto')) {
|
|
117
234
|
config.quickIcon = 'always';
|
|
118
235
|
}
|
|
@@ -454,6 +571,15 @@ class YaverFeedback {
|
|
|
454
571
|
return;
|
|
455
572
|
await p2pClient.cancelRunnerBrowserAuth(sessionId);
|
|
456
573
|
}
|
|
574
|
+
/** Submit the Claude paste-back verifier so the agent can finalise the
|
|
575
|
+
* OAuth handshake. RunnerAuthModal calls this after the user copies
|
|
576
|
+
* the code from platform.claude.com's callback page. */
|
|
577
|
+
static async submitRunnerBrowserAuthCode(sessionId, code) {
|
|
578
|
+
if (!p2pClient) {
|
|
579
|
+
throw new Error('Not connected to any agent.');
|
|
580
|
+
}
|
|
581
|
+
return p2pClient.submitRunnerBrowserAuthCode(sessionId, code);
|
|
582
|
+
}
|
|
457
583
|
/**
|
|
458
584
|
* Sign out: clear cached token + device, tear down the P2P client. The
|
|
459
585
|
* SDK stays enabled; the next feedback trigger will re-prompt for login.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yaver-feedback-react-native",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.10",
|
|
4
4
|
"description": "Visual feedback SDK for Yaver \u2014 bug reports, screen recording, voice annotations, and local-first developer workflows",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
package/src/FeedbackModal.tsx
CHANGED
|
@@ -1251,7 +1251,15 @@ const RunnerAuthNativeModal: React.FC<{
|
|
|
1251
1251
|
const [session, setSession] = useState<import('./types').RunnerBrowserAuthSession | null>(null);
|
|
1252
1252
|
const [startError, setStartError] = useState<string | null>(null);
|
|
1253
1253
|
const [copied, setCopied] = useState(false);
|
|
1254
|
+
const [pasteCode, setPasteCode] = useState('');
|
|
1255
|
+
const [submitting, setSubmitting] = useState(false);
|
|
1256
|
+
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
1254
1257
|
const startedRef = useRef(false);
|
|
1258
|
+
// Claude is the only runner that needs the user to paste a verifier
|
|
1259
|
+
// code back from platform.claude.com's callback page; Codex device-
|
|
1260
|
+
// auth and OpenCode (no OAuth at all) bypass this. Mirrors the
|
|
1261
|
+
// requiresPasteBack check in iOS YaverRunnerAuthFlowPane.swift.
|
|
1262
|
+
const needsPasteBack = runner === 'claude' || runner === 'claude-code';
|
|
1255
1263
|
|
|
1256
1264
|
useEffect(() => {
|
|
1257
1265
|
if (startedRef.current) return;
|
|
@@ -1380,9 +1388,92 @@ const RunnerAuthNativeModal: React.FC<{
|
|
|
1380
1388
|
</Pressable>
|
|
1381
1389
|
</View>
|
|
1382
1390
|
) : null}
|
|
1391
|
+
{needsPasteBack ? (
|
|
1392
|
+
<View style={{ marginTop: 14 }}>
|
|
1393
|
+
<Text style={runnerAuthModalStyles.codeLabel}>
|
|
1394
|
+
PASTE CODE FROM CLAUDE.COM
|
|
1395
|
+
</Text>
|
|
1396
|
+
<View style={{ flexDirection: 'row', gap: 8, marginTop: 6 }}>
|
|
1397
|
+
<View
|
|
1398
|
+
style={{
|
|
1399
|
+
flex: 1,
|
|
1400
|
+
backgroundColor: 'rgba(148,163,184,0.10)',
|
|
1401
|
+
borderRadius: 10,
|
|
1402
|
+
paddingHorizontal: 10,
|
|
1403
|
+
}}
|
|
1404
|
+
>
|
|
1405
|
+
{/* Lazy-import TextInput so the SDK doesn't pull
|
|
1406
|
+
extra surface from react-native at module load. */}
|
|
1407
|
+
{(() => {
|
|
1408
|
+
const { TextInput } = require('react-native');
|
|
1409
|
+
return (
|
|
1410
|
+
<TextInput
|
|
1411
|
+
value={pasteCode}
|
|
1412
|
+
onChangeText={(t: string) => {
|
|
1413
|
+
setPasteCode(t);
|
|
1414
|
+
setSubmitError(null);
|
|
1415
|
+
}}
|
|
1416
|
+
placeholder="paste code here"
|
|
1417
|
+
placeholderTextColor="#64748b"
|
|
1418
|
+
autoCapitalize="none"
|
|
1419
|
+
autoCorrect={false}
|
|
1420
|
+
spellCheck={false}
|
|
1421
|
+
style={{ color: '#f1f5f9', fontSize: 14, paddingVertical: 10 }}
|
|
1422
|
+
/>
|
|
1423
|
+
);
|
|
1424
|
+
})()}
|
|
1425
|
+
</View>
|
|
1426
|
+
<Pressable
|
|
1427
|
+
disabled={!pasteCode.trim() || submitting}
|
|
1428
|
+
onPress={async () => {
|
|
1429
|
+
if (!session || !pasteCode.trim()) return;
|
|
1430
|
+
setSubmitting(true);
|
|
1431
|
+
setSubmitError(null);
|
|
1432
|
+
try {
|
|
1433
|
+
const next = await YaverFeedback.submitRunnerBrowserAuthCode(
|
|
1434
|
+
session.id,
|
|
1435
|
+
pasteCode.trim(),
|
|
1436
|
+
);
|
|
1437
|
+
setSession(next);
|
|
1438
|
+
setPasteCode('');
|
|
1439
|
+
} catch (err) {
|
|
1440
|
+
setSubmitError(err instanceof Error ? err.message : String(err));
|
|
1441
|
+
} finally {
|
|
1442
|
+
setSubmitting(false);
|
|
1443
|
+
}
|
|
1444
|
+
}}
|
|
1445
|
+
style={{
|
|
1446
|
+
paddingHorizontal: 14,
|
|
1447
|
+
justifyContent: 'center',
|
|
1448
|
+
backgroundColor:
|
|
1449
|
+
!pasteCode.trim() || submitting
|
|
1450
|
+
? 'rgba(124,58,237,0.4)'
|
|
1451
|
+
: '#7c3aed',
|
|
1452
|
+
borderRadius: 10,
|
|
1453
|
+
}}
|
|
1454
|
+
>
|
|
1455
|
+
<Text style={{ color: 'white', fontWeight: '600' }}>
|
|
1456
|
+
{submitting ? '…' : 'Submit'}
|
|
1457
|
+
</Text>
|
|
1458
|
+
</Pressable>
|
|
1459
|
+
</View>
|
|
1460
|
+
{submitError ? (
|
|
1461
|
+
<Text
|
|
1462
|
+
style={{
|
|
1463
|
+
marginTop: 6,
|
|
1464
|
+
color: '#fca5a5',
|
|
1465
|
+
fontSize: 12,
|
|
1466
|
+
}}
|
|
1467
|
+
>
|
|
1468
|
+
{submitError}
|
|
1469
|
+
</Text>
|
|
1470
|
+
) : null}
|
|
1471
|
+
</View>
|
|
1472
|
+
) : null}
|
|
1383
1473
|
<Text style={runnerAuthModalStyles.phishingHint}>
|
|
1384
|
-
|
|
1385
|
-
|
|
1474
|
+
{needsPasteBack
|
|
1475
|
+
? 'After authorising on platform.claude.com, copy the code from the callback page and paste it above. Never share this code.'
|
|
1476
|
+
: 'Device codes are a common phishing target. Never share this code. This dialog turns green automatically once sign-in completes.'}
|
|
1386
1477
|
</Text>
|
|
1387
1478
|
</View>
|
|
1388
1479
|
)}
|
package/src/P2PClient.ts
CHANGED
|
@@ -199,6 +199,29 @@ export class P2PClient {
|
|
|
199
199
|
try { await fetch(url, { method: 'POST', headers: this.authHeaders() }); } catch { /* best-effort */ }
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
/** Submit the verifier code Anthropic shows on the callback page so
|
|
203
|
+
* the agent can finalise claude CLI's OAuth handshake. Codex doesn't
|
|
204
|
+
* use this — its device-auth flow auto-resolves via polling — but
|
|
205
|
+
* the SDK still exposes it for symmetry with mobile/src/components/
|
|
206
|
+
* RunnerAuthModal.tsx and the Swift YaverRunnerAuthFlowPane. */
|
|
207
|
+
async submitRunnerBrowserAuthCode(
|
|
208
|
+
sessionId: string,
|
|
209
|
+
code: string,
|
|
210
|
+
): Promise<RunnerBrowserAuthSession> {
|
|
211
|
+
const url = `${this.baseUrl}/runner-auth/browser/submit-code`;
|
|
212
|
+
const resp = await fetch(url, {
|
|
213
|
+
method: 'POST',
|
|
214
|
+
headers: { ...this.authHeaders(), 'Content-Type': 'application/json' },
|
|
215
|
+
body: JSON.stringify({ id: sessionId, code }),
|
|
216
|
+
});
|
|
217
|
+
if (!resp.ok) {
|
|
218
|
+
const text = await resp.text().catch(() => '');
|
|
219
|
+
throw new Error(`submitRunnerBrowserAuthCode HTTP ${resp.status}: ${text}`);
|
|
220
|
+
}
|
|
221
|
+
const data = await resp.json();
|
|
222
|
+
return data.session as RunnerBrowserAuthSession;
|
|
223
|
+
}
|
|
224
|
+
|
|
202
225
|
async capabilitySnapshot(): Promise<CapabilitySnapshot | null> {
|
|
203
226
|
try {
|
|
204
227
|
const resp = await fetch(`${this.baseUrl}/capabilities/snapshot`, { headers: this.authHeaders() });
|
package/src/YaverFeedback.ts
CHANGED
|
@@ -28,10 +28,13 @@ import {
|
|
|
28
28
|
// (mobile/ios/Yaver/YaverInfo.{swift,m} + Android counterpart); a
|
|
29
29
|
// standalone app bundled by its own developer has no such module.
|
|
30
30
|
// When the SDK is loaded through Yaver's Hermes-push guest runtime we
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
// the
|
|
31
|
+
// run in HOST MODE: dormant by default (no shake detector, no auto
|
|
32
|
+
// BlackBox, no QuickActionIcon — Yaver's host shell owns those), but
|
|
33
|
+
// we DO register a DeviceEventEmitter listener so Yaver's overlay can
|
|
34
|
+
// flip the SDK live at runtime. When the user shakes inside the guest
|
|
35
|
+
// app and taps "Feedback" on the Yaver overlay, AppDelegate dispatches
|
|
36
|
+
// `yaverFeedback:startReport` into this bridge; the listener wakes the
|
|
37
|
+
// SDK and opens the modal in-place over the running guest UI.
|
|
35
38
|
function isRunningInsideYaverHost(): boolean {
|
|
36
39
|
try {
|
|
37
40
|
return !!(NativeModules as any)?.YaverInfo;
|
|
@@ -40,11 +43,105 @@ function isRunningInsideYaverHost(): boolean {
|
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// by
|
|
46
|
-
//
|
|
47
|
-
|
|
46
|
+
// Two distinct compile-time modes for a guest app like sfmg / talos:
|
|
47
|
+
//
|
|
48
|
+
// YAVER_HOST_MODE — bundled by Yaver's agent (/dev/build-native) for
|
|
49
|
+
// loading inside Yaver mobile. SDK code is in the
|
|
50
|
+
// bundle but boots PASSIVE: no shake detector, no
|
|
51
|
+
// auto-BlackBox, no container UI. Yaver's host
|
|
52
|
+
// overlay owns the shake gesture; when the user
|
|
53
|
+
// taps "Feedback" on Yaver's overlay, AppDelegate
|
|
54
|
+
// dispatches yaverFeedback:hostActivate into this
|
|
55
|
+
// bridge and the SDK runtime-flips active for a
|
|
56
|
+
// single feedback session.
|
|
57
|
+
//
|
|
58
|
+
// YAVER_SDK_MODE — sfmg's own standalone TestFlight / Play Store
|
|
59
|
+
// build, with the Yaver SDK embedded. SDK boots
|
|
60
|
+
// active: shake → modal directly, no Yaver host
|
|
61
|
+
// involved. Default for normal `expo build`.
|
|
62
|
+
//
|
|
63
|
+
// Both can be forced at build time via process.env. When neither is
|
|
64
|
+
// set, fall back to runtime detection: if the YaverInfo native module
|
|
65
|
+
// exists (we're inside Yaver), assume HOST_MODE; else SDK_MODE. This
|
|
66
|
+
// keeps older bundles (built before the agent learned to set the env)
|
|
67
|
+
// working unchanged.
|
|
68
|
+
const YAVER_HOST_MODE_BUILD = (() => {
|
|
69
|
+
try {
|
|
70
|
+
const v = (process.env as any)?.YAVER_HOST_MODE;
|
|
71
|
+
return v === 'true' || v === '1' || v === true || v === 1;
|
|
72
|
+
} catch { return false; }
|
|
73
|
+
})();
|
|
74
|
+
const YAVER_SDK_MODE_BUILD = (() => {
|
|
75
|
+
try {
|
|
76
|
+
const v = (process.env as any)?.YAVER_SDK_MODE;
|
|
77
|
+
return v === 'true' || v === '1' || v === true || v === 1;
|
|
78
|
+
} catch { return false; }
|
|
79
|
+
})();
|
|
80
|
+
|
|
81
|
+
// Effective mode after considering build flags AND runtime detection.
|
|
82
|
+
const IS_HOST_MODE =
|
|
83
|
+
YAVER_HOST_MODE_BUILD ||
|
|
84
|
+
(!YAVER_SDK_MODE_BUILD && isRunningInsideYaverHost());
|
|
85
|
+
|
|
86
|
+
// Tracks whether we've been runtime-activated by the host (sfmg-in-Yaver
|
|
87
|
+
// case). Independent of `enabled` so we can tell "host turned us on for
|
|
88
|
+
// one shot" apart from "developer toggled enabled programmatically".
|
|
89
|
+
let hostActivated = false;
|
|
90
|
+
|
|
91
|
+
// Host-activation listener. Always registered when in HOST mode so a
|
|
92
|
+
// Yaver overlay tap can wake the SDK even before the guest's
|
|
93
|
+
// YaverFeedback.init() runs. AppDelegate (mobile/ios/Yaver/AppDelegate.
|
|
94
|
+
// swift::handleFeedbackTap) sends `yaverFeedback:startReport` into the
|
|
95
|
+
// guest bridge when the user picks Feedback on the shake overlay.
|
|
96
|
+
//
|
|
97
|
+
// Activation flow:
|
|
98
|
+
// 1. Try Yaver's existing bearer (NativeModules.YaverInfo.
|
|
99
|
+
// inheritedAuthToken, populated by Yaver mobile's auth.ts on
|
|
100
|
+
// sign-in). Validate against /auth/validate before trusting.
|
|
101
|
+
// 2. If valid: setAuthToken + open feedback modal in-place (the
|
|
102
|
+
// modal already supports hot reload, screenshot, vibing chat).
|
|
103
|
+
// 3. If missing or invalid: open the SDK's own login screen.
|
|
104
|
+
if (IS_HOST_MODE) {
|
|
105
|
+
try {
|
|
106
|
+
const { DeviceEventEmitter } = require('react-native');
|
|
107
|
+
DeviceEventEmitter.addListener('yaverFeedback:startReport', () => {
|
|
108
|
+
hostActivated = true;
|
|
109
|
+
enabled = true;
|
|
110
|
+
void (async () => {
|
|
111
|
+
const yi = (NativeModules as any)?.YaverInfo;
|
|
112
|
+
const inheritedToken = String(yi?.inheritedAuthToken || '').trim();
|
|
113
|
+
const inheritedAgent = String(yi?.inheritedAgentUrl || '').trim();
|
|
114
|
+
const inheritedDevice = String(yi?.inheritedDeviceId || '').trim();
|
|
115
|
+
if (inheritedToken) {
|
|
116
|
+
// Lazy-import auth.ts so module load doesn't drag the auth
|
|
117
|
+
// network code into the active set when the SDK is dormant.
|
|
118
|
+
const { validateToken } = require('./auth');
|
|
119
|
+
const user = await validateToken(inheritedToken).catch(() => null);
|
|
120
|
+
if (user) {
|
|
121
|
+
// Seed config + connect the SDK to the host's auth.
|
|
122
|
+
if (!config) {
|
|
123
|
+
YaverFeedback.init({
|
|
124
|
+
authToken: inheritedToken,
|
|
125
|
+
agentUrl: inheritedAgent || undefined,
|
|
126
|
+
preferredDeviceId: inheritedDevice || undefined,
|
|
127
|
+
} as FeedbackConfig);
|
|
128
|
+
}
|
|
129
|
+
await YaverFeedback.setAuthToken(inheritedToken);
|
|
130
|
+
await YaverFeedback.startReport();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// No token, or token invalid — fall through to the SDK's own
|
|
135
|
+
// login screen. The user picks an OAuth provider; on success
|
|
136
|
+
// the modal continues with the new token.
|
|
137
|
+
if (!config) {
|
|
138
|
+
YaverFeedback.init({} as FeedbackConfig);
|
|
139
|
+
}
|
|
140
|
+
YaverFeedback.showLogin();
|
|
141
|
+
})();
|
|
142
|
+
});
|
|
143
|
+
} catch { /* react-native unavailable in jsdom unit tests */ }
|
|
144
|
+
}
|
|
48
145
|
|
|
49
146
|
let config: FeedbackConfig | null = null;
|
|
50
147
|
let enabled = false;
|
|
@@ -126,17 +223,34 @@ export class YaverFeedback {
|
|
|
126
223
|
* via `YaverDiscovery` on the first `startReport()` call.
|
|
127
224
|
*/
|
|
128
225
|
static init(cfg: FeedbackConfig): void {
|
|
129
|
-
if (YAVER_HOST_SUPPRESS) {
|
|
130
|
-
// Running inside Yaver's super-host — yield to Yaver's native UX.
|
|
131
|
-
enabled = false;
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
226
|
config = {
|
|
135
227
|
trigger: 'shake',
|
|
136
228
|
maxRecordingDuration: 120,
|
|
137
229
|
autoLogin: true,
|
|
138
230
|
...cfg,
|
|
139
231
|
};
|
|
232
|
+
if (IS_HOST_MODE) {
|
|
233
|
+
// sfmg / talos / etc. running inside Yaver mobile (compile-time
|
|
234
|
+
// YAVER_HOST_MODE or runtime-detected). Store config so a later
|
|
235
|
+
// host activation (yaverFeedback:startReport from AppDelegate)
|
|
236
|
+
// can open the modal — but skip the side effects Yaver's host
|
|
237
|
+
// shell owns: shake detector, auto-BlackBox, QuickActionIcon.
|
|
238
|
+
enabled = false;
|
|
239
|
+
// Configure auth endpoints + strict-native-auth even in passive
|
|
240
|
+
// mode so a host-activated session uses the same login routing
|
|
241
|
+
// the standalone path would.
|
|
242
|
+
configureAuthEndpoints({
|
|
243
|
+
convexSiteUrl: cfg.authConvexSiteUrl,
|
|
244
|
+
webBaseUrl: cfg.authWebBaseUrl,
|
|
245
|
+
});
|
|
246
|
+
setStrictNativeAuth(cfg.strictNativeAuth === true);
|
|
247
|
+
if (!config.convexUrl) {
|
|
248
|
+
config.convexUrl = cfg.authConvexSiteUrl ?? DEFAULT_CONVEX_SITE_URL;
|
|
249
|
+
}
|
|
250
|
+
maxErrors = cfg.maxCapturedErrors ?? 5;
|
|
251
|
+
errorBuffer = [];
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
140
254
|
if (config.disableShakeGesture && (!config.quickIcon || config.quickIcon === 'auto')) {
|
|
141
255
|
config.quickIcon = 'always';
|
|
142
256
|
}
|
|
@@ -482,6 +596,19 @@ export class YaverFeedback {
|
|
|
482
596
|
await p2pClient.cancelRunnerBrowserAuth(sessionId);
|
|
483
597
|
}
|
|
484
598
|
|
|
599
|
+
/** Submit the Claude paste-back verifier so the agent can finalise the
|
|
600
|
+
* OAuth handshake. RunnerAuthModal calls this after the user copies
|
|
601
|
+
* the code from platform.claude.com's callback page. */
|
|
602
|
+
static async submitRunnerBrowserAuthCode(
|
|
603
|
+
sessionId: string,
|
|
604
|
+
code: string,
|
|
605
|
+
): Promise<import('./types').RunnerBrowserAuthSession> {
|
|
606
|
+
if (!p2pClient) {
|
|
607
|
+
throw new Error('Not connected to any agent.');
|
|
608
|
+
}
|
|
609
|
+
return p2pClient.submitRunnerBrowserAuthCode(sessionId, code);
|
|
610
|
+
}
|
|
611
|
+
|
|
485
612
|
/**
|
|
486
613
|
* Sign out: clear cached token + device, tear down the P2P client. The
|
|
487
614
|
* SDK stays enabled; the next feedback trigger will re-prompt for login.
|