JavaScript SDK

JavaScript/TypeScript SDK 集成指南,支持浏览器和 Node.js。

安装

npm install @yunjii/um-sdk
# 或
pnpm add @yunjii/um-sdk

浏览器端

初始化

import { UM } from '@yunjii/um-sdk';

const um = new UM({
  appid: 'your_appid',
  appkey: 'your_appkey',
  callback: 'https://your-app.com/login/callback',
  apiUrl: 'https://um.yunjii.cn/php',
});

浏览器端 appkey 会暴露在前端代码中。生产环境务必在 BFF 层(如 Next.js API Routes)调用需要签名的接口,前端只调无需签名的接口。

生成登录跳转

const state = crypto.randomUUID();
sessionStorage.setItem('oauth_state', state);

const loginUrl = um.login('wx', state);
window.location.href = loginUrl;

回调处理

const url = new URL(window.location.href);
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');

if (state !== sessionStorage.getItem('oauth_state')) {
  throw new Error('State mismatch');
}

// 推荐:通过 BFF 后端换取用户信息(避免 appkey 暴露)
const res = await fetch('/api/auth/callback', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ code }),
});
const user = await res.json();

Node.js / Next.js BFF

初始化

import { UM } from '@yunjii/um-sdk/server';

const um = new UM({
  appid: process.env.UM_APPID!,
  appkey: process.env.UM_APPKEY!,
  callback: 'https://your-app.com/api/auth/callback',
  apiUrl: 'https://um.yunjii.cn/php',
});

Next.js API Route 示例

// app/api/auth/callback/route.ts
import { NextResponse } from 'next/server';
import { UM } from '@yunjii/um-sdk/server';
import { cookies } from 'next/headers';

const um = new UM({ /* config */ });

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const code = searchParams.get('code')!;

  const user = await um.callback(code);

  // 设置 httpOnly cookie
  cookies().set('um_token', user.token, {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    maxAge: 60 * 60 * 24 * 7, // 7 天
  });

  return NextResponse.redirect(new URL('/dashboard', request.url));
}

Token 验证中间件

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { UM } from '@yunjii/um-sdk/server';

const um = new UM({ /* config */ });

export async function middleware(request: NextRequest) {
  const token = request.cookies.get('um_token')?.value;
  if (!token) return NextResponse.redirect(new URL('/login', request.url));

  const result = await um.checkToken(token);
  if (result.code !== 1) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // 注入用户信息到请求头
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-user-id', String(result.data.user_id));
  return NextResponse.next({ request: { headers: requestHeaders } });
}

错误处理

import { UMError } from '@yunjii/um-sdk';

try {
  const user = await um.callback(code);
} catch (err) {
  if (err instanceof UMError) {
    console.error(err.errCode); // 40163
    console.error(err.message); // code 已被消费
  }
}