Skip to content

Commit 512342d

Browse files
committed
perf(devframe): parse user-agent server-side
The only client-side use of ua-parser-modern was formatting navigator.userAgent into a short device label before sending it in the anonymous:devframe:auth(:exchange) handshake - the parsed shape never crossed the wire, only the resulting string did. Send the raw navigator.userAgent instead and parse+format it at the server ingress (node/auth/state.ts, where it's stored), keeping the persisted label format and the ua: string wire shape identical while moving ua-parser-modern out of the ~90 KB client bundle every embedded page loads.
1 parent 8a8ec85 commit 512342d

2 files changed

Lines changed: 29 additions & 16 deletions

File tree

packages/devframe/src/client/rpc-live.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type { ConnectionMeta, DevframeRpcClientFunctions, DevframeRpcServerFunct
33
import type { DevframeConnectionStatus } from './connection'
44
import type { DevframeClientRpcHost, DevframeRpcClientMode, DevframeRpcClientOptions, RpcClientEvents } from './rpc'
55
import { createRpcClient } from 'devframe/rpc/client'
6-
import { parseUA } from 'ua-parser-modern'
76
import { promiseWithResolver } from '../utils/promise'
87
import { DevframeConnectionError } from './connection'
98

@@ -184,24 +183,15 @@ export function createLiveRpcClientMode(
184183

185184
let currentAuthToken: string | undefined = authToken
186185

187-
function describeUA(): string {
188-
const info = parseUA(navigator.userAgent)
189-
return [
190-
info.browser.name,
191-
info.browser.version,
192-
'|',
193-
info.os.name,
194-
info.os.version,
195-
info.device.type,
196-
].filter(i => i).join(' ')
197-
}
198-
199186
async function requestTrustWithToken(token: string) {
200187
currentAuthToken = token
201188

202189
const result = await serverRpc.$call('anonymous:devframe:auth', {
203190
authToken: token,
204-
ua: describeUA(),
191+
// Sent raw; the server parses it into a display label (see
192+
// `describeUA` in `node/auth/state.ts`) so `ua-parser-modern` stays
193+
// out of the browser bundle.
194+
ua: navigator.userAgent,
205195
origin: location.origin,
206196
})
207197

@@ -228,7 +218,7 @@ export function createLiveRpcClientMode(
228218
async function requestTrustWithCode(code: string): Promise<string | null> {
229219
const result = await serverRpc.$call('anonymous:devframe:auth:exchange', {
230220
code,
231-
ua: describeUA(),
221+
ua: navigator.userAgent,
232222
origin: location.origin,
233223
})
234224

packages/devframe/src/node/auth/state.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,29 @@ import type { SharedState } from 'devframe/utils/shared-state'
33
import type { InternalAnonymousAuthStorage } from '../hub-internals/context'
44
import { DEVFRAME_OTP_URL_PARAM } from 'devframe/constants'
55
import { randomDigits, randomToken, timingSafeEqual } from 'devframe/utils/crypto-token'
6+
import { parseUA } from 'ua-parser-modern'
7+
8+
/**
9+
* Format a raw `navigator.userAgent` string into the short display label
10+
* shown for a trusted device (e.g. "Chrome 120 | macOS 14 desktop").
11+
*
12+
* The client used to parse+format this itself, but that pulled
13+
* `ua-parser-modern` into the browser bundle for a label nothing else on
14+
* the client needs — the client now sends the raw string and parsing
15+
* happens here, at the server ingress, keeping the persisted label format
16+
* identical.
17+
*/
18+
function describeUA(userAgent: string): string {
19+
const info = parseUA(userAgent)
20+
return [
21+
info.browser.name,
22+
info.browser.version,
23+
'|',
24+
info.os.name,
25+
info.os.version,
26+
info.device.type,
27+
].filter(i => i).join(' ')
28+
}
629

730
/** Number of decimal digits in a human-typed one-time authentication code. */
831
const TEMP_AUTH_CODE_LENGTH = 6
@@ -121,7 +144,7 @@ export function exchangeTempAuthCode(
121144
storage.mutate((state) => {
122145
state.trusted[authToken] = {
123146
authToken,
124-
ua: info.ua,
147+
ua: describeUA(info.ua),
125148
origin: info.origin,
126149
timestamp: Date.now(),
127150
}

0 commit comments

Comments
 (0)