门店端微信号自动登录
This commit is contained in:
@@ -28,8 +28,18 @@ const ACCESS_TOKEN = 'accessToken';
|
||||
const REFRESH_TOKEN = 'refreshToken';
|
||||
const LAST_PHONE = 'shopLastPhone';
|
||||
const STORE_PROFILE = 'shopStoreProfile';
|
||||
const SESSION_EXPIRES_AT = 'shopSessionExpiresAt';
|
||||
export const SHOP_WX_BOUND = 'shopWxBound';
|
||||
|
||||
const AUTH_RECOVERY_EXEMPT_PATHS = ['/shop/auth/token/refresh', '/shop/auth/sms/send', '/shop/auth/login/sms'];
|
||||
/** 微信验证通过后的免登录时长 */
|
||||
export const SHOP_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
const AUTH_RECOVERY_EXEMPT_PATHS = [
|
||||
'/shop/auth/token/refresh',
|
||||
'/shop/auth/sms/send',
|
||||
'/shop/auth/login/sms',
|
||||
'/shop/auth/login/wechat',
|
||||
];
|
||||
|
||||
export function getLastPhone() {
|
||||
return localStorage.getItem(LAST_PHONE) ?? '';
|
||||
@@ -44,6 +54,21 @@ export function getStoreProfile(): StoreSessionStore | null {
|
||||
}
|
||||
}
|
||||
|
||||
export function hasShopWxSession() {
|
||||
return localStorage.getItem(SHOP_WX_BOUND) === '1';
|
||||
}
|
||||
|
||||
export function isShopSessionExpired() {
|
||||
const raw = localStorage.getItem(SESSION_EXPIRES_AT);
|
||||
if (!raw) return false;
|
||||
return Date.now() > Number(raw);
|
||||
}
|
||||
|
||||
export function touchShopSession() {
|
||||
if (!hasShopWxSession()) return;
|
||||
localStorage.setItem(SESSION_EXPIRES_AT, String(Date.now() + SHOP_SESSION_TTL_MS));
|
||||
}
|
||||
|
||||
export function saveAuth(data: ShopSessionPayload) {
|
||||
localStorage.setItem(ACCESS_TOKEN, data.accessToken);
|
||||
if (data.refreshToken) localStorage.setItem(REFRESH_TOKEN, data.refreshToken);
|
||||
@@ -53,10 +78,22 @@ export function saveAuth(data: ShopSessionPayload) {
|
||||
}
|
||||
}
|
||||
|
||||
export function clearAuth() {
|
||||
/** 微信登录/绑定成功后写入 7 天免登录 session */
|
||||
export function saveWechatSession(data: ShopSessionPayload) {
|
||||
saveAuth(data);
|
||||
localStorage.setItem(SHOP_WX_BOUND, '1');
|
||||
localStorage.setItem(SESSION_EXPIRES_AT, String(Date.now() + SHOP_SESSION_TTL_MS));
|
||||
}
|
||||
|
||||
export function clearAuth(options?: { keepProfile?: boolean }) {
|
||||
localStorage.removeItem(ACCESS_TOKEN);
|
||||
localStorage.removeItem(REFRESH_TOKEN);
|
||||
localStorage.removeItem(STORE_PROFILE);
|
||||
localStorage.removeItem(SESSION_EXPIRES_AT);
|
||||
localStorage.removeItem(SHOP_WX_BOUND);
|
||||
if (!options?.keepProfile) {
|
||||
localStorage.removeItem(STORE_PROFILE);
|
||||
localStorage.removeItem(LAST_PHONE);
|
||||
}
|
||||
}
|
||||
|
||||
export function isLoggedIn() {
|
||||
@@ -109,6 +146,7 @@ async function refreshSession(): Promise<ShopSessionPayload | null> {
|
||||
null,
|
||||
);
|
||||
saveAuth(data);
|
||||
touchShopSession();
|
||||
return data;
|
||||
} catch {
|
||||
return null;
|
||||
@@ -147,6 +185,10 @@ export async function ensureSession(): Promise<{ authenticated: boolean; store:
|
||||
if (!isLoggedIn()) {
|
||||
return { authenticated: false, store: null };
|
||||
}
|
||||
if (isShopSessionExpired()) {
|
||||
clearAuth({ keepProfile: true });
|
||||
return { authenticated: false, store: getStoreProfile() };
|
||||
}
|
||||
try {
|
||||
const me = await rawRequest<StoreProfile>('/shop/auth/me');
|
||||
const store = profileFromMe(me);
|
||||
@@ -155,6 +197,7 @@ export async function ensureSession(): Promise<{ authenticated: boolean; store:
|
||||
refreshToken: localStorage.getItem(REFRESH_TOKEN) ?? '',
|
||||
store,
|
||||
});
|
||||
touchShopSession();
|
||||
return { authenticated: true, store };
|
||||
} catch (e) {
|
||||
const err = e as Error & { status?: number };
|
||||
@@ -163,8 +206,8 @@ export async function ensureSession(): Promise<{ authenticated: boolean; store:
|
||||
if (refreshed?.store) {
|
||||
return { authenticated: true, store: refreshed.store };
|
||||
}
|
||||
clearAuth();
|
||||
return { authenticated: false, store: null };
|
||||
clearAuth({ keepProfile: true });
|
||||
return { authenticated: false, store: getStoreProfile() };
|
||||
}
|
||||
const cached = getStoreProfile();
|
||||
if (cached) return { authenticated: true, store: cached };
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { ClientRuntimeConfig, WechatLoginResult } from '@dukang/shared-types';
|
||||
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
|
||||
import { WECHAT_INAPP_REQUIRED_MSG } from '@dukang/weixin-sdk';
|
||||
import { isWechatEnv, weixinSdk } from './weixin';
|
||||
import { request, saveAuth, type ShopSessionPayload } from './api';
|
||||
import { request, saveWechatSession, type ShopSessionPayload } from './api';
|
||||
|
||||
export type ShopAccountProfile = {
|
||||
id: string;
|
||||
@@ -51,20 +52,17 @@ export function sessionFromWechatLogin(result: WechatLoginResult): ShopSessionPa
|
||||
};
|
||||
}
|
||||
|
||||
export function saveShopWechatAuth(result: WechatLoginResult): boolean {
|
||||
/** 处理微信登录/绑定结果,写入 7 天免登录 session */
|
||||
export function handleShopWechatLoginResult(result: WechatLoginResult): ShopSessionPayload | null {
|
||||
const session = sessionFromWechatLogin(result);
|
||||
if (!session) return false;
|
||||
saveAuth(session);
|
||||
return true;
|
||||
if (!session) return null;
|
||||
saveWechatSession(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function authorizeShopWechat(): Promise<WechatLoginResult | void> {
|
||||
const config = await fetchClientConfig();
|
||||
if (!isWxAuthorizeEnabled(config)) return;
|
||||
if (!isWechatEnv()) {
|
||||
throw new Error('请在微信内打开以完成授权');
|
||||
}
|
||||
return weixinSdk.login();
|
||||
/** @deprecated 使用 handleShopWechatLoginResult */
|
||||
export function saveShopWechatAuth(result: WechatLoginResult): boolean {
|
||||
return !!handleShopWechatLoginResult(result);
|
||||
}
|
||||
|
||||
export async function handleShopWechatCallback(): Promise<WechatLoginResult | null> {
|
||||
@@ -73,3 +71,34 @@ export async function handleShopWechatCallback(): Promise<WechatLoginResult | nu
|
||||
if (!isWxAuthorizeEnabled(config)) return null;
|
||||
return weixinSdk.handleOAuthCallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信一键登录(已绑定微信的门店账号免验证码)。
|
||||
* 返回 session = 已登录;void = 已跳转授权页等待回调。
|
||||
*/
|
||||
export async function loginShopWithWechat(): Promise<ShopSessionPayload | null | void> {
|
||||
const config = await fetchClientConfig();
|
||||
if (!isWxAuthorizeEnabled(config)) return null;
|
||||
if (!isWechatEnv()) {
|
||||
throw new Error(WECHAT_INAPP_REQUIRED_MSG);
|
||||
}
|
||||
const result = await weixinSdk.login();
|
||||
if (result) return handleShopWechatLoginResult(result);
|
||||
}
|
||||
|
||||
/** 短信登录成功后于微信内自动发起 OAuth,绑定 openId 便于后续免登 */
|
||||
export async function bindShopWechatAfterSmsLogin(): Promise<void> {
|
||||
const config = await fetchClientConfig();
|
||||
if (!isWxAuthorizeEnabled(config)) return;
|
||||
if (!isWechatEnv()) return;
|
||||
await weixinSdk.login();
|
||||
}
|
||||
|
||||
export async function authorizeShopWechat(): Promise<WechatLoginResult | void> {
|
||||
const config = await fetchClientConfig();
|
||||
if (!isWxAuthorizeEnabled(config)) return;
|
||||
if (!isWechatEnv()) {
|
||||
throw new Error(WECHAT_INAPP_REQUIRED_MSG);
|
||||
}
|
||||
return weixinSdk.login();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user