@xfe-repo/cli-plugin-oauth 2.0.9 → 2.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @xfe-repo/cli-plugin-oauth
2
2
 
3
+ ## 2.0.10
4
+
5
+ ### Patch Changes
6
+
7
+ - 优化插件性能
8
+
3
9
  ## 2.0.9
4
10
 
5
11
  ### Patch Changes
package/dist/index.js CHANGED
@@ -59,11 +59,13 @@ export function oauthPlugin() {
59
59
  slot: ['footer'],
60
60
  value: (state) => {
61
61
  const currentSession = resolveOptionalOAuthState(state.xfe, state.oauth);
62
- return currentSession ? `Oauth ${currentSession.isAuthenticated ? '已登录' : '未登录'}` : undefined;
62
+ const isAuthenticated = currentSession && isOAuthAccessTokenValid(currentSession, Math.floor(Date.now() / MILLISECONDS_PER_SECOND));
63
+ return currentSession ? `Oauth ${isAuthenticated ? '已登录' : '未登录'}` : undefined;
63
64
  },
64
65
  color: (state) => {
65
66
  const currentSession = resolveOptionalOAuthState(state.xfe, state.oauth);
66
- return currentSession?.isAuthenticated ? 'cyan' : 'yellow';
67
+ const isAuthenticated = currentSession && isOAuthAccessTokenValid(currentSession, Math.floor(Date.now() / MILLISECONDS_PER_SECOND));
68
+ return isAuthenticated ? 'cyan' : 'yellow';
67
69
  },
68
70
  });
69
71
  hooks.registerCommand({
@@ -197,24 +199,34 @@ async function getUser(ctx, runtime) {
197
199
  }
198
200
  const user = normalizeOAuthUserInfo(await response.json());
199
201
  const latest = resolveOAuthStateForConfig(config, ctx.store.getState().oauth);
200
- saveOAuthSession(ctx, config, { ...toOAuthSessionState(latest, config.name), ...user });
202
+ saveOAuthSession(ctx, config, { ...latest, ...user });
201
203
  return user;
202
204
  }
203
205
  async function logout(ctx) {
204
206
  const config = parseOptionalOAuthConfig(ctx.store.getState().xfe);
205
207
  const current = config ? resolveOAuthStateForConfig(config, ctx.store.getState().oauth) : undefined;
206
208
  const name = config?.name || current?.name || DEFAULT_OAUTH_NAME;
207
- const storeState = cloneOAuthState(ctx.store.getState().oauth);
208
- if (config)
209
- delete storeState[resolveOAuthConfigKey(config)];
210
- ctx.store.setState({ oauth: storeState });
209
+ const storeState = ctx.store.getState().oauth ?? oauthStoreInitial;
210
+ const currentConfigKey = config ? resolveOAuthConfigKey(config) : undefined;
211
+ const nextStoreState = currentConfigKey
212
+ ? Object.fromEntries(Object.entries(storeState).filter(([configKey]) => configKey !== currentConfigKey))
213
+ : storeState;
214
+ ctx.store.setState({ oauth: nextStoreState });
211
215
  ctx.logger.success(`${name} 已退出登录`);
212
216
  }
213
217
  // ─── Config And State ──────────────────────────────────────
214
218
  function resolveOAuthStateForConfig(config, state) {
215
219
  const configKey = resolveOAuthConfigKey(config);
216
220
  const session = state?.[configKey];
217
- return session ? toOAuthSessionState(session, config.name) : createEmptyOAuthSession(config.name);
221
+ return {
222
+ name: session?.name || config.name,
223
+ accessToken: session?.accessToken || '',
224
+ refreshToken: session?.refreshToken || '',
225
+ tokenType: session?.tokenType || DEFAULT_TOKEN_TYPE,
226
+ expiresAt: session?.expiresAt ?? 0,
227
+ userId: session?.userId ?? 0,
228
+ userName: session?.userName || '',
229
+ };
218
230
  }
219
231
  function parseOAuthConfig(xfeConfig) {
220
232
  const config = parseOptionalOAuthConfig(xfeConfig);
@@ -235,8 +247,8 @@ function parseOptionalOAuthConfig(xfeConfig) {
235
247
  }
236
248
  function saveOAuthSession(ctx, config, session) {
237
249
  const configKey = resolveOAuthConfigKey(config);
238
- const storeState = cloneOAuthState(ctx.store.getState().oauth);
239
- storeState[configKey] = toOAuthSessionState(session, config.name);
250
+ const storeState = { ...(ctx.store.getState().oauth ?? oauthStoreInitial) };
251
+ storeState[configKey] = session;
240
252
  ctx.store.setState({ oauth: storeState });
241
253
  }
242
254
  function resolveOptionalOAuthState(xfeConfig, state) {
@@ -260,38 +272,8 @@ function resolveOAuthConfigKey(config) {
260
272
  const digest = createHash(OAUTH_CONFIG_HASH_ALGORITHM).update(payload).digest(OAUTH_CONFIG_HASH_ENCODING);
261
273
  return `${OAUTH_CONFIG_KEY_PREFIX}:${digest}`;
262
274
  }
263
- function cloneOAuthState(stateMap) {
264
- if (!stateMap)
265
- return {};
266
- return Object.fromEntries(Object.entries(stateMap)
267
- .filter((entry) => isOAuthSessionState(entry[1]))
268
- .map(([configKey, session]) => [configKey, toOAuthSessionState(session, session.name)]));
269
- }
270
- function isOAuthSessionState(value) {
271
- if (typeof value !== 'object' || value === null || Array.isArray(value))
272
- return false;
273
- const record = value;
274
- return typeof record.name === 'string';
275
- }
276
- function createEmptyOAuthSession(name) {
277
- return toOAuthSessionState(undefined, name);
278
- }
279
- function toOAuthSessionState(state, name) {
280
- return {
281
- name: state?.name || name,
282
- accessToken: state?.accessToken || '',
283
- refreshToken: state?.refreshToken || '',
284
- idToken: state?.idToken || '',
285
- tokenType: state?.tokenType || DEFAULT_TOKEN_TYPE,
286
- scope: state?.scope || '',
287
- expiresAt: state?.expiresAt ?? 0,
288
- isAuthenticated: state?.isAuthenticated ?? false,
289
- userId: state?.userId ?? 0,
290
- userName: state?.userName || '',
291
- };
292
- }
293
275
  function isOAuthAccessTokenValid(state, now) {
294
- return state.isAuthenticated && !!state.accessToken && (!state.expiresAt || state.expiresAt - TOKEN_EXPIRY_SKEW_SECONDS > now);
276
+ return !!state.accessToken && (!state.expiresAt || state.expiresAt - TOKEN_EXPIRY_SKEW_SECONDS > now);
295
277
  }
296
278
  function createOAuthSessionFromToken(config, tokenResponse, now, fallbackRefreshToken = '') {
297
279
  const expiresIn = tokenResponse.expiresIn();
@@ -299,11 +281,8 @@ function createOAuthSessionFromToken(config, tokenResponse, now, fallbackRefresh
299
281
  name: config.name,
300
282
  accessToken: tokenResponse.access_token ?? '',
301
283
  refreshToken: tokenResponse.refresh_token || fallbackRefreshToken,
302
- idToken: tokenResponse.id_token || '',
303
284
  tokenType: tokenResponse.token_type || DEFAULT_TOKEN_TYPE,
304
- scope: tokenResponse.scope || '',
305
285
  expiresAt: expiresIn ? now + expiresIn : 0,
306
- isAuthenticated: true,
307
286
  userId: 0,
308
287
  userName: '',
309
288
  };
@@ -317,31 +296,16 @@ function normalizeOAuthUserInfo(input) {
317
296
  const userRecord = nestedRecord ?? rootRecord;
318
297
  const userIdKeys = ['userId', 'user_id', 'UserId', 'id', 'uid'];
319
298
  const userNameKeys = ['name', 'userName', 'user_name', 'UserName', 'username', 'nickName', 'nikeName', 'nickname'];
320
- let userId = 0;
321
- let userName = '';
322
- for (const key of userIdKeys) {
323
- const value = userRecord[key];
324
- if (typeof value === 'number' && Number.isInteger(value)) {
325
- userId = value;
326
- break;
327
- }
328
- if (typeof value === 'string' && value.trim()) {
329
- const parsed = Number.parseInt(value, 10);
330
- if (!Number.isNaN(parsed)) {
331
- userId = parsed;
332
- break;
333
- }
334
- }
335
- }
299
+ const userId = userIdKeys
300
+ .map((key) => userRecord[key])
301
+ .map((value) => (typeof value === 'number' ? value : typeof value === 'string' ? Number.parseInt(value, 10) : Number.NaN))
302
+ .find(Number.isInteger) ?? 0;
336
303
  if (!userId)
337
304
  throw new Error('OAuth 用户信息缺少 userId');
338
- for (const key of userNameKeys) {
339
- const value = userRecord[key];
340
- if (typeof value === 'string' && value.trim()) {
341
- userName = value.trim();
342
- break;
343
- }
344
- }
305
+ const userName = userNameKeys
306
+ .map((key) => userRecord[key])
307
+ .find((value) => typeof value === 'string' && !!value.trim())
308
+ ?.trim();
345
309
  if (!userName)
346
310
  throw new Error('OAuth 用户信息缺少 userName');
347
311
  return { userId, userName };
package/dist/types.d.ts CHANGED
@@ -24,11 +24,8 @@ export interface OAuthSessionState {
24
24
  name: string;
25
25
  accessToken: string;
26
26
  refreshToken: string;
27
- idToken: string;
28
27
  tokenType: string;
29
- scope: string;
30
28
  expiresAt: number;
31
- isAuthenticated: boolean;
32
29
  userId: number;
33
30
  userName: string;
34
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfe-repo/cli-plugin-oauth",
3
- "version": "2.0.9",
3
+ "version": "2.0.10",
4
4
  "description": "XFE OAuth plugin - login through OIDC/OAuth and expose token state",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,8 +13,7 @@
13
13
  },
14
14
  "devDependencies": {
15
15
  "@types/node": "^24.3.0",
16
- "typescript": "^5.9.2",
17
- "vitest": "^3.1.0"
16
+ "typescript": "^5.9.2"
18
17
  },
19
18
  "files": [
20
19
  "dist",
@@ -24,8 +23,7 @@
24
23
  "license": "ISC",
25
24
  "scripts": {
26
25
  "dev": "tsc -p tsconfig.json -w",
27
- "build": "tsc -p tsconfig.json",
28
- "test": "vitest run",
26
+ "build": "pnpm clean && tsc -p tsconfig.json",
29
27
  "clean": "rm -rf dist"
30
28
  }
31
29
  }
@@ -1,8 +0,0 @@
1
- /**
2
- * @xfe-repo/cli-plugin-oauth - OAuth 回调子进程
3
- *
4
- * 在独立 Node 进程中等待本地 OAuth 回调,让 CLI session 的 Ctrl+C
5
- * 可以通过 ctx.exec 终止等待中的回调服务。
6
- */
7
- export {};
8
- //# sourceMappingURL=callback-runner.d.ts.map
@@ -1,26 +0,0 @@
1
- /**
2
- * @xfe-repo/cli-plugin-oauth - OAuth 回调子进程
3
- *
4
- * 在独立 Node 进程中等待本地 OAuth 回调,让 CLI session 的 Ctrl+C
5
- * 可以通过 ctx.exec 终止等待中的回调服务。
6
- */
7
- import { OAUTH_CALLBACK_URL_PREFIX, startOAuthCallbackServer } from './callback-server.js';
8
- async function main() {
9
- const redirectUri = process.argv[2];
10
- if (!redirectUri)
11
- throw new Error('缺少 OAuth redirectUri 参数');
12
- const callbackServer = await startOAuthCallbackServer(redirectUri);
13
- try {
14
- const callbackUrl = await callbackServer.waitForCallback();
15
- process.stdout.write(`${OAUTH_CALLBACK_URL_PREFIX}${callbackUrl.href}\n`);
16
- }
17
- finally {
18
- await callbackServer.close();
19
- }
20
- }
21
- main().catch((error) => {
22
- const message = error instanceof Error ? error.message : String(error);
23
- process.stderr.write(`${message}\n`);
24
- process.exitCode = 1;
25
- });
26
- //# sourceMappingURL=callback-runner.js.map