Skip to content

feat(mobile): report paired device identity#8295

Open
bbingz wants to merge 3 commits into
stablyai:mainfrom
bbingz:bbingz/mobile-device-identity
Open

feat(mobile): report paired device identity#8295
bbingz wants to merge 3 commits into
stablyai:mainfrom
bbingz:bbingz/mobile-device-identity

Conversation

@bbingz

@bbingz bbingz commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • collect a bounded mobile device model/name in the React client context
  • inject it into transport connect options instead of importing React Native from the low-level RPC client
  • send the optional device name only inside the existing encrypted e2ee_auth payload
  • validate and sanitize the name on the runtime before updating the already-authenticated device record
  • cap client and runtime values by Unicode code point so a 64-character boundary cannot split an emoji surrogate pair

This is the device-identity replacement extracted from #7949. It intentionally excludes host editing and iOS scene-lifecycle work.

Refs #7949

Screenshots

No visual redesign. The existing paired-device surface displays the additional sanitized name.

Testing

  • mobile Vitest: 6 files / 45 passed / 2 skipped
  • desktop Vitest: 3 files / 62 passed
  • mobile TypeScript 6 and targeted oxlint
  • root Node typecheck and targeted oxlint
  • oxfmt --check and max-lines ratchet
  • git diff --check
  • RED-to-GREEN Unicode boundary coverage on both client and runtime
  • rebased on current origin/main; retained the latest host-client open registry and connection logging
  • full repository test/lint aggregate
  • mobile simulator/device
  • Electron/E2E/GUI

AI Review Report

Independent review ended spec PASS / quality APPROVED. It verified the latest-main conflict resolution, additive protocol compatibility, encrypted transport, token-to-device binding, secure persistence, and matching client/runtime Unicode sanitizers.

Review also found and fixed a functional boundary bug: UTF-16 String.slice() could leave a lone surrogate when an emoji crossed the 64-character limit. Both sanitizers now truncate an Array.from() code-point array and have regression tests.

Security Audit

  • device name is optional display metadata and does not participate in pairing or authorization
  • it is carried inside the existing encrypted E2EE authentication message
  • the runtime validates the token before updating that token's device record
  • both client and runtime remove C0/DEL controls, normalize whitespace, and cap the value at 64 Unicode code points
  • the value is not logged and contains model/platform fallback metadata, not hostname, serial number, or account data
  • this adds the direct expo-device dependency needed for the iOS marketing model; it adds no credential, permission, command, or IPC method

Compatibility

The field is additive and optional, so older mobile clients continue without it and older runtimes ignore it. Desktop, SSH, keyboard, filesystem, and Git-provider paths are unchanged.

Notes

Validation used the repository-declared Node 24 toolchain. Full Electron/E2E and physical mobile-device verification were intentionally not run in this review cycle.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7488b532-d1cb-4486-8205-4491a3b4be65

📥 Commits

Reviewing files that changed from the base of the PR and between 7c09b48 and 9d93eba.

⛔ Files ignored due to path filters (1)
  • mobile/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • mobile/package.json
  • mobile/src/transport/client-context.test.ts
  • mobile/src/transport/client-context.tsx
  • mobile/src/transport/device-identity.test.ts
  • mobile/src/transport/device-identity.ts
  • mobile/src/transport/rpc-client.test.ts
  • mobile/src/transport/rpc-client.ts
  • src/main/runtime/device-registry-name.test.ts
  • src/main/runtime/device-registry.ts
  • src/main/runtime/rpc/e2ee-channel.test.ts
  • src/main/runtime/rpc/e2ee-channel.ts
  • src/main/runtime/runtime-rpc.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • mobile/package.json
  • mobile/src/transport/client-context.test.ts
  • mobile/src/transport/client-context.tsx
  • src/main/runtime/device-registry.ts
  • src/main/runtime/runtime-rpc.ts
  • mobile/src/transport/device-identity.test.ts
  • mobile/src/transport/rpc-client.test.ts
  • mobile/src/transport/device-identity.ts
  • src/main/runtime/device-registry-name.test.ts
  • src/main/runtime/rpc/e2ee-channel.test.ts
  • mobile/src/transport/rpc-client.ts
  • src/main/runtime/rpc/e2ee-channel.ts

📝 Walkthrough

Walkthrough

Mobile clients now resolve platform-specific device names, sanitize them, and include them in encrypted authentication. The runtime sanitizes and records the reported name during authentication, then updates and persists the corresponding device registry entry. Tests cover device identity resolution, handshake payloads, authentication state, registry validation, and persistence.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is mostly complete, but the AI Review Report lacks the required explicit macOS/Linux/Windows compatibility check details. Add an AI Review Report sentence confirming cross-platform review on macOS, Linux, and Windows, including shortcuts, labels, paths, shell behavior, and Electron-specific differences.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main change: reporting paired mobile device identity.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
mobile/src/transport/device-identity.ts (1)

48-57: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Client sanitization uses split('') (UTF-16 code units) while the server uses Array.from() (Unicode code points).

raw.split('') splits surrogate pairs into separate code units. Both halves pass the charCodeAt(0) > 0x1f filter, so the join('') reconstructs them — but slice(0, MAX_DEVICE_NAME_LENGTH) counts code units, not code points. A device name with emoji near the 64-char boundary could be sliced mid-surrogate, producing a lone surrogate that the server's Array.from()-based slice wouldn't. The server re-sanitizes, so this won't corrupt persisted data, but the client and server could compute different lengths for the same input.

♻️ Use Array.from for code-point-accurate filtering and slicing
   const cleaned = raw
-    .split('')
+    .split('')  // or Array.from(raw) for full code-point alignment with server
     .filter((character) => {
       const code = character.charCodeAt(0)
       return code > 0x1f && code !== 0x7f
     })
     .join('')
     .replace(/\s+/g, ' ')
     .trim()
-    .slice(0, MAX_DEVICE_NAME_LENGTH)
+    .slice(0, MAX_DEVICE_NAME_LENGTH)  // consider Array.from(raw).filter(...).join('') to match server

If full parity with the server is desired:

-  const cleaned = raw
-    .split('')
-    .filter((character) => {
-      const code = character.charCodeAt(0)
-      return code > 0x1f && code !== 0x7f
-    })
-    .join('')
-    .replace(/\s+/g, ' ')
-    .trim()
-    .slice(0, MAX_DEVICE_NAME_LENGTH)
+  const cleaned = Array.from(raw)
+    .filter((character) => {
+      const codePoint = character.codePointAt(0)
+      return codePoint !== undefined && codePoint > 0x1f && codePoint !== 0x7f
+    })
+    .join('')
+    .replace(/\s+/g, ' ')
+    .trim()
+    .slice(0, MAX_DEVICE_NAME_LENGTH)
mobile/src/transport/device-identity.test.ts (1)

51-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add tests for Android model-only and brand-only fallback paths.

Lines 34-39 of device-identity.ts handle cases where only model or only brand is available on Android, but no test exercises these branches. Consider adding cases for { Model: 'Pixel 8' } (no Brand) and { Brand: 'Google' } (no Model).

♻️ Suggested additional test cases
  it('uses Android model only when brand is absent', () => {
    platformMock.OS = 'android'
    platformMock.constants = { Model: 'Pixel 8' }
    expect(resolveMobileDeviceDisplayName()).toBe('Pixel 8')
  })

  it('uses Android brand only when model is absent', () => {
    platformMock.OS = 'android'
    platformMock.constants = { Brand: 'Google' }
    expect(resolveMobileDeviceDisplayName()).toBe('Google')
  })
src/main/runtime/device-registry-name.test.ts (1)

22-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider using createRegistry() in the first test or returning dir from it.

The first test manually duplicates the temp-dir creation logic (lines 23-24) instead of reusing the createRegistry() helper. This works because the reload assertion needs the dir path, but the helper could return both to avoid the duplication.

♻️ Optional: return dir from createRegistry
-  function createRegistry(): DeviceRegistry {
+  function createRegistry(): { registry: DeviceRegistry; dir: string } {
     const dir = mkdtempSync(join(tmpdir(), 'orca-device-registry-'))
     dirs.push(dir)
-    return new DeviceRegistry(dir)
+    return { registry: new DeviceRegistry(dir), dir }
   }

   it('renames a device and persists across reload', () => {
-    const dir = mkdtempSync(join(tmpdir(), 'orca-device-registry-'))
-    dirs.push(dir)
-    const registry = new DeviceRegistry(dir)
+    const { registry, dir } = createRegistry()
     const device = registry.addDevice('Mobile 7/3/2026', 'mobile')

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0343004b-705f-447e-ad3b-029536a28d31

📥 Commits

Reviewing files that changed from the base of the PR and between ff8192a and 8e78fdf.

⛔ Files ignored due to path filters (1)
  • mobile/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • mobile/package.json
  • mobile/src/transport/client-context.test.ts
  • mobile/src/transport/client-context.tsx
  • mobile/src/transport/device-identity.test.ts
  • mobile/src/transport/device-identity.ts
  • mobile/src/transport/rpc-client.test.ts
  • mobile/src/transport/rpc-client.ts
  • src/main/runtime/device-registry-name.test.ts
  • src/main/runtime/device-registry.ts
  • src/main/runtime/rpc/e2ee-channel.test.ts
  • src/main/runtime/rpc/e2ee-channel.ts
  • src/main/runtime/runtime-rpc.ts

@AmethystLiang AmethystLiang self-assigned this Jul 11, 2026
@AmethystLiang AmethystLiang self-requested a review July 11, 2026 16:47
@bbingz bbingz force-pushed the bbingz/mobile-device-identity branch from 8e78fdf to 7c09b48 Compare July 12, 2026 01:34
@bbingz

bbingz commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the functional Unicode nitpick in 9d93ebad6: both the mobile sender and runtime sanitizer now cap by Unicode code point, with RED-to-GREEN emoji-boundary tests on each side.

I did not add the two optional Android single-field tests: the existing suite already covers brand+model, brand-prefix deduplication, platform fallback, sanitization, and transport injection; the model-only and brand-only branches are direct returns through the same sanitizer, so adding those cases would not protect a distinct contract in this focused PR.

I also left the registry test fixture unchanged. The one test that constructs its directory explicitly needs that path for the reload assertion; changing the helper to return a tuple/object would be test-only churn with no behavioral coverage gain.

@bbingz bbingz force-pushed the bbingz/mobile-device-identity branch from 7c09b48 to 9d93eba Compare July 12, 2026 01:46
@bbingz

bbingz commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants