yaver-feedback-react-native 0.5.5 → 0.6.1

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/auth.js CHANGED
@@ -2,26 +2,25 @@
2
2
  /**
3
3
  * Authentication + device/agent discovery API used by the Yaver Feedback SDK.
4
4
  *
5
- * This module is a trimmed SDK-local port of mobile/src/lib/auth.ts. It only
6
- * covers what the embedded login/machine-picker flow needs:
5
+ * Mirrors mobile/src/lib/auth.ts:
7
6
  *
8
- * - Device-code login (`POST /auth/device-code` + `GET /auth/device-code/poll`)
9
- * so users can sign in via any OAuth provider (apple/google/github/gitlab/
10
- * microsoft) on yaver.io without requiring deep-link wiring in the host app.
7
+ * - Native Apple Sign-In (`POST /auth/apple-native`) on iOS via
8
+ * `expo-apple-authentication`.
9
+ * - In-app browser OAuth for Google/Microsoft/GitHub/GitLab via
10
+ * `expo-web-browser`'s `openAuthSessionAsync` — same callback URL
11
+ * (`yaver://oauth-callback`) the Yaver mobile app uses.
11
12
  * - Email / password sign-up + login (no 2FA flow — for SDK simplicity).
12
- * - Token validation + refresh.
13
+ * - Token validation.
13
14
  * - `/devices/list` → owned + shared (guest) remote dev machines.
14
15
  *
15
- * All calls target the public Yaver Convex site URL by default; callers may
16
- * override via `init()` config to point at staging.
17
- *
18
- * Token persistence uses `@react-native-async-storage/async-storage` (already
19
- * a peer dep). SecureStore is intentionally avoided to keep the SDK portable
20
- * to any RN host app.
16
+ * Mobile-only. A web equivalent will ship as a separate `yaver-web-feedback`
17
+ * package; do not import this module from a browser bundle.
21
18
  */
22
19
  Object.defineProperty(exports, "__esModule", { value: true });
23
- exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = void 0;
20
+ exports.DEFAULT_OAUTH_REDIRECT = exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = void 0;
24
21
  exports.configureAuthEndpoints = configureAuthEndpoints;
22
+ exports.setStrictNativeAuth = setStrictNativeAuth;
23
+ exports.isStrictNativeAuth = isStrictNativeAuth;
25
24
  exports.getConvexSiteUrl = getConvexSiteUrl;
26
25
  exports.getWebBaseUrl = getWebBaseUrl;
27
26
  exports.getToken = getToken;
@@ -33,8 +32,8 @@ exports.getSelectedDeviceId = getSelectedDeviceId;
33
32
  exports.saveSelectedDeviceId = saveSelectedDeviceId;
34
33
  exports.clearSelectedDeviceId = clearSelectedDeviceId;
35
34
  exports.validateToken = validateToken;
36
- exports.startDeviceCode = startDeviceCode;
37
- exports.pollDeviceCode = pollDeviceCode;
35
+ exports.signInWithApple = signInWithApple;
36
+ exports.signInWithOAuth = signInWithOAuth;
38
37
  exports.signupWithEmail = signupWithEmail;
39
38
  exports.loginWithEmail = loginWithEmail;
40
39
  exports.listReachableDevices = listReachableDevices;
@@ -46,6 +45,21 @@ try {
46
45
  catch {
47
46
  // not installed — token persistence disabled, caller must pass authToken
48
47
  }
48
+ let WebBrowser = null;
49
+ try {
50
+ WebBrowser = require('expo-web-browser');
51
+ WebBrowser?.maybeCompleteAuthSession();
52
+ }
53
+ catch {
54
+ // optional
55
+ }
56
+ let AppleAuth = null;
57
+ try {
58
+ AppleAuth = require('expo-apple-authentication');
59
+ }
60
+ catch {
61
+ // optional — Apple sign-in falls back to in-app browser OAuth
62
+ }
49
63
  const TOKEN_KEY = 'yaver_feedback_auth_token';
50
64
  const USER_KEY = 'yaver_feedback_user';
51
65
  const DEVICE_KEY = 'yaver_feedback_selected_device';
@@ -53,6 +67,7 @@ exports.DEFAULT_CONVEX_SITE_URL = 'https://shocking-echidna-394.eu-west-1.convex
53
67
  exports.DEFAULT_WEB_BASE_URL = 'https://yaver.io';
54
68
  let convexSiteUrl = exports.DEFAULT_CONVEX_SITE_URL;
55
69
  let webBaseUrl = exports.DEFAULT_WEB_BASE_URL;
70
+ let strictNativeAuth = false;
56
71
  /** Override the Convex site URL + web base (staging vs prod). */
57
72
  function configureAuthEndpoints(opts) {
58
73
  if (opts.convexSiteUrl)
@@ -60,6 +75,17 @@ function configureAuthEndpoints(opts) {
60
75
  if (opts.webBaseUrl)
61
76
  webBaseUrl = opts.webBaseUrl;
62
77
  }
78
+ /**
79
+ * Enable strict native auth: refuse any fallback that would redirect the
80
+ * user to an external browser (Safari / Chrome) or show a device code.
81
+ * See FeedbackConfig.strictNativeAuth for rationale.
82
+ */
83
+ function setStrictNativeAuth(enabled) {
84
+ strictNativeAuth = enabled;
85
+ }
86
+ function isStrictNativeAuth() {
87
+ return strictNativeAuth;
88
+ }
63
89
  function getConvexSiteUrl() {
64
90
  return convexSiteUrl;
65
91
  }
@@ -178,54 +204,113 @@ async function validateToken(token) {
178
204
  return null;
179
205
  }
180
206
  }
207
+ // ─── Native Apple Sign-In ─────────────────────────────────────────────
181
208
  /**
182
- * Start a device-code flow. The user opens `verificationUrl`, signs in with
183
- * any OAuth provider on yaver.io, and the SDK polls `pollDeviceCode` until
184
- * a session token is issued.
209
+ * Sign in with Apple using the native ASAuthorization flow. Requires
210
+ * `expo-apple-authentication` installed and the host app's bundle to have
211
+ * the "Sign in with Apple" capability enabled. iOS only.
212
+ *
213
+ * Throws `cancelled` if the user dismisses the sheet.
185
214
  */
186
- async function startDeviceCode(opts) {
187
- const res = await fetch(`${convexSiteUrl}/auth/device-code`, {
215
+ async function signInWithApple() {
216
+ if (!AppleAuth) {
217
+ throw new Error('expo-apple-authentication is not installed. Add it as a peer dep to enable native Apple Sign-In.');
218
+ }
219
+ const available = await AppleAuth.isAvailableAsync();
220
+ if (!available) {
221
+ throw new Error('Apple Sign-In is not available on this device');
222
+ }
223
+ let credential;
224
+ try {
225
+ credential = await AppleAuth.signInAsync({
226
+ requestedScopes: [
227
+ AppleAuth.AppleAuthenticationScope.FULL_NAME,
228
+ AppleAuth.AppleAuthenticationScope.EMAIL,
229
+ ],
230
+ });
231
+ }
232
+ catch (err) {
233
+ if (err?.code === 'ERR_REQUEST_CANCELED') {
234
+ throw new Error('cancelled');
235
+ }
236
+ throw err;
237
+ }
238
+ if (!credential.identityToken) {
239
+ throw new Error('Apple did not return an identity token');
240
+ }
241
+ const fullName = [credential.fullName?.givenName, credential.fullName?.familyName]
242
+ .filter(Boolean)
243
+ .join(' ') || undefined;
244
+ const res = await fetch(`${convexSiteUrl}/auth/apple-native`, {
188
245
  method: 'POST',
189
246
  headers: { 'Content-Type': 'application/json' },
190
- body: JSON.stringify({
191
- machineName: opts?.machineName,
192
- platform: opts?.platform,
193
- preferredProvider: opts?.preferredProvider,
194
- environment: 'feedback-sdk',
195
- }),
247
+ body: JSON.stringify({ identityToken: credential.identityToken, fullName }),
196
248
  });
197
249
  if (!res.ok) {
198
- const data = await res.json().catch(() => ({}));
199
- throw new Error(data.error ?? 'Failed to start device-code');
250
+ const body = await res.text().catch(() => '');
251
+ throw new Error(body || 'Apple sign-in failed');
200
252
  }
201
253
  const data = await res.json();
202
- const params = new URLSearchParams({ code: data.userCode });
203
- if (opts?.preferredProvider) {
204
- params.set('preferredProvider', opts.preferredProvider);
205
- }
206
- return {
207
- userCode: data.userCode,
208
- deviceCode: data.deviceCode,
209
- expiresAt: data.expiresAt,
210
- verificationUrl: `${webBaseUrl}/auth/device?${params.toString()}`,
211
- };
254
+ return { token: data.token, userId: data.userId };
212
255
  }
213
- async function pollDeviceCode(deviceCode) {
256
+ // ─── In-app browser OAuth (Google / GitHub / GitLab / Microsoft) ──────
257
+ /**
258
+ * Default OAuth redirect — the same callback the Yaver mobile app uses
259
+ * (`yaver://oauth-callback`). `WebBrowser.openAuthSessionAsync` intercepts
260
+ * this redirect inside the auth session, so the host app does not need to
261
+ * register the scheme on iOS. On Android, add an `<intent-filter>` for
262
+ * `yaver://oauth-callback` in the host app's AndroidManifest.xml.
263
+ */
264
+ exports.DEFAULT_OAUTH_REDIRECT = 'yaver://oauth-callback';
265
+ /**
266
+ * Sign in through the in-app browser via yaver.io. Opens
267
+ * `https://yaver.io/api/auth/oauth/<provider>?client=mobile`, the user picks
268
+ * an OAuth provider, and the web callback redirects back to
269
+ * `yaver://oauth-callback?token=...` which `openAuthSessionAsync` captures
270
+ * inside the auth session. No deep-link wiring required on iOS.
271
+ *
272
+ * Throws `cancelled` if the user dismisses the browser.
273
+ */
274
+ async function signInWithOAuth(provider, opts) {
275
+ if (!WebBrowser) {
276
+ // In strictNativeAuth we hard-fail rather than letting the caller
277
+ // fall back to any homegrown `Linking.openURL(…)` flow that would
278
+ // leave the app for Safari. Without strict mode we still can't
279
+ // proceed (no browser module available) so the behavior is the same
280
+ // — just a clearer error message.
281
+ throw new Error('expo-web-browser is not installed. Add it as a peer dep to enable in-app OAuth sign-in.');
282
+ }
283
+ const redirectUrl = opts?.redirectUrl ?? exports.DEFAULT_OAUTH_REDIRECT;
284
+ const params = new URLSearchParams({ client: 'mobile' });
285
+ const authUrl = `${webBaseUrl}/api/auth/oauth/${provider}?${params.toString()}`;
286
+ // In strict mode force ephemeral session (ASWebAuthenticationSession
287
+ // with no shared cookie jar) so the OAuth dance is visibly native and
288
+ // can never hand off to the user's default browser.
289
+ const prefer = strictNativeAuth || opts?.preferEphemeralSession
290
+ ? true
291
+ : false;
292
+ const result = await WebBrowser.openAuthSessionAsync(authUrl, redirectUrl, {
293
+ preferEphemeralSession: prefer,
294
+ showInRecents: !strictNativeAuth,
295
+ });
296
+ if (result.type !== 'success' || !result.url) {
297
+ throw new Error('cancelled');
298
+ }
299
+ let token = null;
214
300
  try {
215
- const res = await fetch(`${convexSiteUrl}/auth/device-code/poll?device_code=${encodeURIComponent(deviceCode)}`);
216
- if (!res.ok)
217
- return { status: 'expired' };
218
- const data = await res.json();
219
- if (data.status === 'authorized' && typeof data.token === 'string') {
220
- return { status: 'authorized', token: data.token };
221
- }
222
- if (data.status === 'pending')
223
- return { status: 'pending' };
224
- return { status: 'expired' };
301
+ const parsed = new URL(result.url);
302
+ token = parsed.searchParams.get('token');
225
303
  }
226
304
  catch {
227
- return { status: 'pending' }; // network blip let caller keep polling
305
+ // best-effort fallback for odd schemes
306
+ const match = result.url.match(/[?&]token=([^&]+)/);
307
+ if (match)
308
+ token = decodeURIComponent(match[1]);
309
+ }
310
+ if (!token) {
311
+ throw new Error('OAuth callback did not include a token');
228
312
  }
313
+ return { token };
229
314
  }
230
315
  // ─── Email / password (no 2FA) ────────────────────────────────────────
231
316
  async function signupWithEmail(fullName, email, password) {
@@ -252,9 +337,9 @@ async function loginWithEmail(email, password) {
252
337
  }
253
338
  const data = await res.json();
254
339
  if (data?.requires2fa) {
255
- // SDK login surface does not handle 2FA — direct the user to complete
256
- // sign-in through the web flow (device-code) which supports it.
257
- throw new Error('2FA is enabled on this account. Sign in via the device-code flow instead.');
340
+ // SDK login surface does not handle 2FA — direct the user to sign in
341
+ // through one of the OAuth providers, which complete 2FA on the web.
342
+ throw new Error('2FA is enabled on this account. Sign in with Apple/Google/GitHub/GitLab/Microsoft instead.');
258
343
  }
259
344
  return { token: data.token, userId: data.userId };
260
345
  }
package/dist/index.d.ts CHANGED
@@ -38,8 +38,8 @@ export { ShakeDetector } from './ShakeDetector';
38
38
  export { FloatingButton } from './FloatingButton';
39
39
  export { FeedbackModal } from './FeedbackModal';
40
40
  export { FixReport } from './FixReport';
41
- export { configureAuthEndpoints, getConvexSiteUrl, getWebBaseUrl, getToken, saveToken, clearToken, getUser, saveUser, getSelectedDeviceId, saveSelectedDeviceId, clearSelectedDeviceId, validateToken, startDeviceCode, pollDeviceCode, signupWithEmail, loginWithEmail, listReachableDevices, DEFAULT_CONVEX_SITE_URL, DEFAULT_WEB_BASE_URL, } from './auth';
42
- export type { OAuthProvider, User, DeviceCodeStart, DeviceCodePoll, RemoteDevice, DeviceList, } from './auth';
41
+ export { configureAuthEndpoints, getConvexSiteUrl, getWebBaseUrl, getToken, saveToken, clearToken, getUser, saveUser, getSelectedDeviceId, saveSelectedDeviceId, clearSelectedDeviceId, validateToken, signInWithApple, signInWithOAuth, signupWithEmail, loginWithEmail, listReachableDevices, DEFAULT_CONVEX_SITE_URL, DEFAULT_WEB_BASE_URL, DEFAULT_OAUTH_REDIRECT, } from './auth';
42
+ export type { OAuthProvider, User, RemoteDevice, DeviceList, } from './auth';
43
43
  export { captureScreenshot, startAudioRecording, stopAudioRecording } from './capture';
44
44
  export { uploadFeedback } from './upload';
45
45
  export type { FeedbackConfig, FeedbackBundle, FeedbackMetadata, DeviceInfo, AppInfo, TimelineEvent, FeedbackReport, AgentCommentary, FeedbackStreamEvent, VoiceCapability, CapturedError, TestFix, TestSession, } from './types';
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@
23
23
  * ```
24
24
  */
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.uploadFeedback = exports.stopAudioRecording = exports.startAudioRecording = exports.captureScreenshot = exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = exports.listReachableDevices = exports.loginWithEmail = exports.signupWithEmail = exports.pollDeviceCode = exports.startDeviceCode = exports.validateToken = exports.clearSelectedDeviceId = exports.saveSelectedDeviceId = exports.getSelectedDeviceId = exports.saveUser = exports.getUser = exports.clearToken = exports.saveToken = exports.getToken = exports.getWebBaseUrl = exports.getConvexSiteUrl = exports.configureAuthEndpoints = exports.FixReport = exports.FeedbackModal = exports.FloatingButton = exports.ShakeDetector = exports.AuthOverlay = exports.YaverMachinePickerScreen = exports.YaverLoginScreen = exports.YaverConnectionScreen = exports.P2PClient = exports.YaverDiscovery = exports.initExpo = exports.YaverUpdates = exports.BlackBox = exports.YaverFeedback = void 0;
26
+ exports.uploadFeedback = exports.stopAudioRecording = exports.startAudioRecording = exports.captureScreenshot = exports.DEFAULT_OAUTH_REDIRECT = exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = exports.listReachableDevices = exports.loginWithEmail = exports.signupWithEmail = exports.signInWithOAuth = exports.signInWithApple = exports.validateToken = exports.clearSelectedDeviceId = exports.saveSelectedDeviceId = exports.getSelectedDeviceId = exports.saveUser = exports.getUser = exports.clearToken = exports.saveToken = exports.getToken = exports.getWebBaseUrl = exports.getConvexSiteUrl = exports.configureAuthEndpoints = exports.FixReport = exports.FeedbackModal = exports.FloatingButton = exports.ShakeDetector = exports.AuthOverlay = exports.YaverMachinePickerScreen = exports.YaverLoginScreen = exports.YaverConnectionScreen = exports.P2PClient = exports.YaverDiscovery = exports.initExpo = exports.YaverUpdates = exports.BlackBox = exports.YaverFeedback = void 0;
27
27
  var YaverFeedback_1 = require("./YaverFeedback");
28
28
  Object.defineProperty(exports, "YaverFeedback", { enumerable: true, get: function () { return YaverFeedback_1.YaverFeedback; } });
29
29
  var BlackBox_1 = require("./BlackBox");
@@ -65,13 +65,14 @@ Object.defineProperty(exports, "getSelectedDeviceId", { enumerable: true, get: f
65
65
  Object.defineProperty(exports, "saveSelectedDeviceId", { enumerable: true, get: function () { return auth_1.saveSelectedDeviceId; } });
66
66
  Object.defineProperty(exports, "clearSelectedDeviceId", { enumerable: true, get: function () { return auth_1.clearSelectedDeviceId; } });
67
67
  Object.defineProperty(exports, "validateToken", { enumerable: true, get: function () { return auth_1.validateToken; } });
68
- Object.defineProperty(exports, "startDeviceCode", { enumerable: true, get: function () { return auth_1.startDeviceCode; } });
69
- Object.defineProperty(exports, "pollDeviceCode", { enumerable: true, get: function () { return auth_1.pollDeviceCode; } });
68
+ Object.defineProperty(exports, "signInWithApple", { enumerable: true, get: function () { return auth_1.signInWithApple; } });
69
+ Object.defineProperty(exports, "signInWithOAuth", { enumerable: true, get: function () { return auth_1.signInWithOAuth; } });
70
70
  Object.defineProperty(exports, "signupWithEmail", { enumerable: true, get: function () { return auth_1.signupWithEmail; } });
71
71
  Object.defineProperty(exports, "loginWithEmail", { enumerable: true, get: function () { return auth_1.loginWithEmail; } });
72
72
  Object.defineProperty(exports, "listReachableDevices", { enumerable: true, get: function () { return auth_1.listReachableDevices; } });
73
73
  Object.defineProperty(exports, "DEFAULT_CONVEX_SITE_URL", { enumerable: true, get: function () { return auth_1.DEFAULT_CONVEX_SITE_URL; } });
74
74
  Object.defineProperty(exports, "DEFAULT_WEB_BASE_URL", { enumerable: true, get: function () { return auth_1.DEFAULT_WEB_BASE_URL; } });
75
+ Object.defineProperty(exports, "DEFAULT_OAUTH_REDIRECT", { enumerable: true, get: function () { return auth_1.DEFAULT_OAUTH_REDIRECT; } });
75
76
  var capture_1 = require("./capture");
76
77
  Object.defineProperty(exports, "captureScreenshot", { enumerable: true, get: function () { return capture_1.captureScreenshot; } });
77
78
  Object.defineProperty(exports, "startAudioRecording", { enumerable: true, get: function () { return capture_1.startAudioRecording; } });
package/dist/types.d.ts CHANGED
@@ -4,7 +4,8 @@ export interface FeedbackConfig {
4
4
  /**
5
5
  * Auth token for the Yaver agent. Optional in 0.5+: if omitted, the SDK
6
6
  * will hydrate one from AsyncStorage or show its in-app login screen
7
- * (device-code / email / OAuth) the first time the user triggers feedback.
7
+ * (Apple native / OAuth in-app browser / email) the first time the user
8
+ * triggers feedback.
8
9
  */
9
10
  authToken?: string;
10
11
  /**
@@ -156,6 +157,25 @@ export interface FeedbackConfig {
156
157
  * Default: false (HTTPS preferred when fingerprint available, HTTP fallback).
157
158
  */
158
159
  requireTLS?: boolean;
160
+ /**
161
+ * Compile-time lockdown of the auth flow. When true the SDK refuses to
162
+ * ever open the user's external browser (Safari / Chrome) or show a
163
+ * 6-char device code. Auth happens only via native Apple Sign-In
164
+ * (`expo-apple-authentication`), in-app OAuth (`expo-web-browser`'s
165
+ * `ASWebAuthenticationSession` with `preferEphemeralSession: true`), or
166
+ * the built-in email/password form. If a required peer dep is missing,
167
+ * `signInWithOAuth`/`signInWithApple` throw instead of silently falling
168
+ * back to a web redirect.
169
+ *
170
+ * Recommended for apps that already embed OAuth on the native side and
171
+ * never want their users to see a `yaver.io` landing page. This is the
172
+ * belt-and-suspenders version of what the SDK has done since 0.6; set
173
+ * it to guarantee future regressions can't quietly reintroduce a
174
+ * browser-hop fallback.
175
+ *
176
+ * Default: false (preserve historical behavior).
177
+ */
178
+ strictNativeAuth?: boolean;
159
179
  }
160
180
  export interface FeedbackBundle {
161
181
  metadata: FeedbackMetadata;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaver-feedback-react-native",
3
- "version": "0.5.5",
3
+ "version": "0.6.1",
4
4
  "description": "Visual feedback SDK for Yaver — bug reports, screen recording, voice annotations, and local-first developer workflows",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,7 +20,9 @@
20
20
  "peerDependencies": {
21
21
  "react": ">=18.0.0",
22
22
  "react-native": ">=0.70.0",
23
- "@react-native-async-storage/async-storage": ">=1.17.0"
23
+ "@react-native-async-storage/async-storage": ">=1.17.0",
24
+ "expo-web-browser": ">=12.0.0",
25
+ "expo-apple-authentication": ">=6.0.0"
24
26
  },
25
27
  "peerDependenciesMeta": {
26
28
  "@expo/config-plugins": {
@@ -28,6 +30,9 @@
28
30
  },
29
31
  "expo-constants": {
30
32
  "optional": true
33
+ },
34
+ "expo-apple-authentication": {
35
+ "optional": true
31
36
  }
32
37
  },
33
38
  "devDependencies": {