This commit is contained in:
@@ -59,8 +59,15 @@ export const hasActiveOrPendingPackage = async (identifier) => {
|
||||
|
||||
export const assetApi = {
|
||||
getInfo(identifier) {
|
||||
identifier = typeof identifier === 'string' ? identifier.trim() : '';
|
||||
if (!identifier) {
|
||||
return Promise.reject({ msg: '缺少资产标识,请重新输入或扫码登录' });
|
||||
}
|
||||
const context = getSessionContext();
|
||||
const userStore = useUserStore();
|
||||
if (userStore.state.loginPending || !context.token) {
|
||||
return Promise.reject({ msg: '登录和微信配置尚未完成,请稍后重试' });
|
||||
}
|
||||
const phoneRevision = userStore.state.phoneRevision;
|
||||
return request({
|
||||
url: '/api/c/v1/asset/info',
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
|
||||
// 加载套餐流量数据
|
||||
const loadPackageData = async () => {
|
||||
if (userStore.state.loginPending || !userStore.state.token) return;
|
||||
const identifier = props.identifier || userStore.state.identifier;
|
||||
if (!identifier) return;
|
||||
|
||||
@@ -151,8 +152,8 @@
|
||||
};
|
||||
|
||||
// 监听 identifier 变化
|
||||
watch(() => props.identifier || userStore.state.identifier, (newVal) => {
|
||||
if (newVal) {
|
||||
watch(() => [props.identifier || userStore.state.identifier, userStore.state.token, userStore.state.loginPending], ([newVal, token, loginPending]) => {
|
||||
if (newVal && token && !loginPending) {
|
||||
loadPackageData();
|
||||
}
|
||||
}, {
|
||||
|
||||
@@ -305,8 +305,9 @@
|
||||
};
|
||||
|
||||
const loadAssetInfo = async () => {
|
||||
const identifier = userStore.state.identifier;
|
||||
const identifier = String(userStore.state.identifier || uni.getStorageSync('identifier') || '').trim();
|
||||
if (!identifier) return;
|
||||
if (userStore.state.identifier !== identifier) userStore.setIdentifier(identifier);
|
||||
const context = getSessionContext();
|
||||
|
||||
loading.value = true;
|
||||
@@ -401,11 +402,18 @@
|
||||
};
|
||||
|
||||
const handleIndexEntry = async () => {
|
||||
if (userStore.state.loginPending) return;
|
||||
const token = uni.getStorageSync('token');
|
||||
if (!token) {
|
||||
const identifier = String(userStore.state.identifier || uni.getStorageSync('identifier') || '').trim();
|
||||
if (!token || !identifier) {
|
||||
if (token && !identifier) {
|
||||
userStore.clearUser();
|
||||
uni.showToast({ title: '缺少资产标识,请重新输入或扫码登录', icon: 'none' });
|
||||
}
|
||||
uni.reLaunch({ url: '/pages/login/login' });
|
||||
return;
|
||||
}
|
||||
if (userStore.state.identifier !== identifier) userStore.setIdentifier(identifier);
|
||||
|
||||
if (indexEntryChecking.value) return;
|
||||
indexEntryChecking.value = true;
|
||||
@@ -1001,7 +1009,11 @@
|
||||
awaitingHomeReminders.value = true;
|
||||
notificationFallbackPending = false;
|
||||
notificationQueued = false;
|
||||
if (!getSessionContext().token) {
|
||||
if (userStore.state.loginPending) {
|
||||
awaitingHomeReminders.value = false;
|
||||
return;
|
||||
}
|
||||
if (!getSessionContext().token || !String(userStore.state.identifier || uni.getStorageSync('identifier') || '').trim()) {
|
||||
awaitingHomeReminders.value = false;
|
||||
handleIndexEntry();
|
||||
return;
|
||||
|
||||
@@ -71,7 +71,14 @@
|
||||
import { useUserStore } from '@/store/index.js';
|
||||
import { initWxConfig, wxScan, isInWechat } from '@/utils/wxsdk.js';
|
||||
|
||||
const WECHAT_LOGIN_CONTEXT_KEY = 'wechatLoginContext';
|
||||
|
||||
const userStore = useUserStore();
|
||||
// Close the asset-request gate synchronously, before cached homepage
|
||||
// components can react to the callback's old token or saved identifier.
|
||||
if (new URLSearchParams(window.location.search).get('code') && sessionStorage.getItem('assetToken')) {
|
||||
userStore.setLoginPending(true);
|
||||
}
|
||||
const identifier = ref(uni.getStorageSync('identifier') || '');
|
||||
const loading = ref(false);
|
||||
const agreed = ref(true);
|
||||
@@ -82,6 +89,24 @@
|
||||
const resetLoadingState = () => {
|
||||
loading.value = false;
|
||||
};
|
||||
const initializeLoginSdk = async () => {
|
||||
if (isInWechat()) await initWxConfig();
|
||||
};
|
||||
const resumeStoredSession = async (assetIdentifier) => {
|
||||
userStore.setLoginPending(true);
|
||||
loading.value = true;
|
||||
try {
|
||||
userStore.setIdentifier(assetIdentifier);
|
||||
await initializeLoginSdk();
|
||||
sessionStorage.removeItem('assetToken');
|
||||
sessionStorage.removeItem(WECHAT_LOGIN_CONTEXT_KEY);
|
||||
userStore.setLoginPending(false);
|
||||
uni.reLaunch({ url: '/pages/index/index' });
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e?.msg || e?.message || '微信初始化失败,请重新登录', icon: 'none' });
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// Remove the obsolete post-bind re-login notice from older client builds.
|
||||
@@ -89,13 +114,19 @@
|
||||
const urlIdentifier = getPathDeviceId();
|
||||
const token = uni.getStorageSync('token');
|
||||
const storedIdentifier = uni.getStorageSync('identifier') || '';
|
||||
// Complete OAuth first. An existing token or the callback's identifier
|
||||
// must not bypass the callback or start another authorization round.
|
||||
if (getCode() && sessionStorage.getItem('assetToken')) {
|
||||
await handleWechatCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
// 从外部链接进入时,链接中的资产标识优先于本地登录态。
|
||||
// 只有相同资产才能复用已登录的会话,避免将上一台设备的数据带到新链接中。
|
||||
if (urlIdentifier) {
|
||||
identifier.value = urlIdentifier;
|
||||
if (token && urlIdentifier === storedIdentifier) {
|
||||
uni.reLaunch({ url: '/pages/index/index' });
|
||||
await resumeStoredSession(urlIdentifier);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,16 +134,15 @@
|
||||
userStore.clearUser();
|
||||
}
|
||||
|
||||
doLogin();
|
||||
await doLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
if (token) {
|
||||
uni.reLaunch({ url: '/pages/index/index' });
|
||||
if (token && storedIdentifier.trim()) {
|
||||
await resumeStoredSession(storedIdentifier.trim());
|
||||
return;
|
||||
}
|
||||
|
||||
handleWechatCallback();
|
||||
if (token) userStore.clearUser();
|
||||
|
||||
// 初始化微信 SDK (仅在微信浏览器内执行)
|
||||
// #ifdef H5
|
||||
@@ -163,10 +193,13 @@
|
||||
return wechatAppId.value;
|
||||
};
|
||||
|
||||
const redirectToWxAuth = async (assetToken) => {
|
||||
const redirectToWxAuth = async (assetToken, assetIdentifier) => {
|
||||
sessionStorage.setItem('assetToken', assetToken);
|
||||
sessionStorage.setItem(WECHAT_LOGIN_CONTEXT_KEY, JSON.stringify({ assetToken, identifier: assetIdentifier }));
|
||||
const appId = await getWechatAppId();
|
||||
const redirectUri = encodeURIComponent(window.location.origin + window.location.pathname);
|
||||
const callbackUrl = new URL(window.location.pathname, window.location.origin);
|
||||
callbackUrl.searchParams.set('identifier', assetIdentifier);
|
||||
const redirectUri = encodeURIComponent(callbackUrl.toString());
|
||||
const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_userinfo&state=wechat_auth#wechat_redirect`;
|
||||
window.location.href = url;
|
||||
};
|
||||
@@ -290,19 +323,23 @@
|
||||
|
||||
const doLogin = async () => {
|
||||
loading.value = true;
|
||||
userStore.setLoginPending(true);
|
||||
// 丢弃上一轮未完成授权留下的临时凭证,避免校验失败时继续复用。
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.removeItem('assetToken');
|
||||
sessionStorage.removeItem(WECHAT_LOGIN_CONTEXT_KEY);
|
||||
}
|
||||
try {
|
||||
const enteredIdentifier = identifier.value;
|
||||
const enteredIdentifier = String(identifier.value || '').trim();
|
||||
if (!enteredIdentifier) throw { msg: '请输入有效的资产标识' };
|
||||
const { verifyData, verifiedIdentifier } = await verifyAssetWithIccidRetry(enteredIdentifier);
|
||||
|
||||
userStore.setAssetToken(verifyData.asset_token);
|
||||
userStore.setIdentifier(verifiedIdentifier);
|
||||
|
||||
await redirectToWxAuth(verifyData.asset_token);
|
||||
await redirectToWxAuth(verifyData.asset_token, verifiedIdentifier);
|
||||
} catch (e) {
|
||||
userStore.setLoginPending(false);
|
||||
console.error('登录失败', e);
|
||||
uni.showToast({
|
||||
title: e?.msg || e?.message || '资产校验失败,请稍后重试',
|
||||
@@ -318,15 +355,38 @@
|
||||
const assetToken = sessionStorage.getItem('assetToken');
|
||||
|
||||
if (!code || !assetToken) return;
|
||||
userStore.setLoginPending(true);
|
||||
let loginContext = null;
|
||||
try {
|
||||
loginContext = JSON.parse(sessionStorage.getItem(WECHAT_LOGIN_CONTEXT_KEY) || 'null');
|
||||
} catch (e) {
|
||||
// Old clients may only have the legacy assetToken and saved identifier.
|
||||
}
|
||||
const contextIdentifier = loginContext?.assetToken === assetToken && typeof loginContext.identifier === 'string'
|
||||
? loginContext.identifier.trim() : '';
|
||||
const loginIdentifier = contextIdentifier || getPathDeviceId() ||
|
||||
String(uni.getStorageSync('identifier') || userStore.state.identifier || identifier.value || '').trim();
|
||||
if (!loginIdentifier) {
|
||||
clearCode();
|
||||
sessionStorage.removeItem('assetToken');
|
||||
sessionStorage.removeItem(WECHAT_LOGIN_CONTEXT_KEY);
|
||||
userStore.clearUser();
|
||||
uni.showToast({ title: '缺少资产标识,请重新输入或扫码登录', icon: 'none' });
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
clearCode();
|
||||
|
||||
try {
|
||||
const loginData = await authApi.wechatLogin(assetToken, code);
|
||||
// Restore the verified identifier before publishing the login state or
|
||||
// mounting homepage components that immediately request asset/info.
|
||||
userStore.setIdentifier(loginIdentifier);
|
||||
userStore.setToken(loginData.token);
|
||||
userStore.setLoginPhoneState(loginData);
|
||||
uni.setStorageSync('identifier', userStore.state.identifier);
|
||||
identifier.value = loginIdentifier;
|
||||
|
||||
// #ifdef H5
|
||||
if (loginData.user_info) {
|
||||
@@ -337,6 +397,13 @@
|
||||
}
|
||||
// #endif
|
||||
|
||||
// Keep the gate closed while JSSDK config/ready is still pending.
|
||||
// Homepage and bind-page asset/info requests only start afterwards.
|
||||
await initializeLoginSdk();
|
||||
sessionStorage.removeItem('assetToken');
|
||||
sessionStorage.removeItem(WECHAT_LOGIN_CONTEXT_KEY);
|
||||
userStore.setLoginPending(false);
|
||||
|
||||
if (userStore.state.needBindPhone === true) {
|
||||
uni.redirectTo({ url: '/pages/bind/bind?fromLogin=true' });
|
||||
} else {
|
||||
|
||||
@@ -12,6 +12,8 @@ const emptyPhoneState = () => ({
|
||||
const savedPhoneState = uni.getStorageSync(PHONE_STATE_KEY);
|
||||
const token = uni.getStorageSync('token') || '';
|
||||
const identifier = uni.getStorageSync('identifier') || '';
|
||||
const isOAuthCallback = typeof window !== 'undefined' && typeof sessionStorage !== 'undefined' &&
|
||||
!!new URLSearchParams(window.location.search).get('code') && !!sessionStorage.getItem('assetToken');
|
||||
const phoneState = savedPhoneState?.token === token && savedPhoneState?.identifier === identifier && token
|
||||
? {
|
||||
customerPhone: typeof savedPhoneState.customerPhone === 'string' ? savedPhoneState.customerPhone : null,
|
||||
@@ -25,6 +27,7 @@ const state = reactive({
|
||||
token,
|
||||
assetToken: '',
|
||||
identifier,
|
||||
loginPending: isOAuthCallback,
|
||||
...phoneState,
|
||||
phoneRevision: 0,
|
||||
isDevice: false,
|
||||
@@ -36,6 +39,7 @@ const state = reactive({
|
||||
});
|
||||
|
||||
export const useUserStore = () => {
|
||||
const setLoginPending = (pending) => { state.loginPending = pending === true; };
|
||||
const persistPhoneState = () => {
|
||||
uni.setStorageSync(PHONE_STATE_KEY, {
|
||||
token: state.token, identifier: state.identifier,
|
||||
@@ -111,6 +115,7 @@ export const useUserStore = () => {
|
||||
const clearUser = () => {
|
||||
resetContext();
|
||||
state.token = '';
|
||||
state.loginPending = false;
|
||||
state.assetToken = '';
|
||||
state.identifier = '';
|
||||
state.isDevice = false;
|
||||
@@ -124,6 +129,7 @@ export const useUserStore = () => {
|
||||
return {
|
||||
state,
|
||||
setToken,
|
||||
setLoginPending,
|
||||
setAssetToken,
|
||||
setIdentifier,
|
||||
setIsDevice,
|
||||
|
||||
@@ -29,6 +29,8 @@ function runtime(initial = {}) {
|
||||
const timers = [];
|
||||
const sessionStorage = new Map();
|
||||
const api = {};
|
||||
let inWechat = false;
|
||||
let sdkInit = async () => {};
|
||||
let options = {};
|
||||
const uni = {
|
||||
getStorageSync: key => storage.get(key) ?? '',
|
||||
@@ -44,13 +46,13 @@ function runtime(initial = {}) {
|
||||
navigateBack: () => calls.push({ kind: 'navigateBack' })
|
||||
};
|
||||
const context = vm.createContext({
|
||||
uni, Intl, Date, URLSearchParams,
|
||||
uni, Intl, Date, URLSearchParams, URL,
|
||||
console: { log() {}, error() {}, warn() {} },
|
||||
setTimeout: callback => { timers.push(callback); return timers.length; },
|
||||
setInterval: () => 1, clearInterval() {},
|
||||
getCurrentPages: () => [{ options }],
|
||||
sessionStorage: { getItem: key => sessionStorage.get(key) || '', removeItem: key => sessionStorage.delete(key), setItem: (key, value) => sessionStorage.set(key, value) },
|
||||
window: { location: { search: '', pathname: '/', hash: '' }, history: { replaceState() {} } },
|
||||
window: { location: { search: '', pathname: '/', hash: '', origin: 'https://example.invalid' }, history: { replaceState() {} } },
|
||||
defineProps: () => ({}), defineEmits: () => () => {}
|
||||
});
|
||||
const cache = new Map();
|
||||
@@ -88,7 +90,7 @@ function runtime(initial = {}) {
|
||||
consumePendingPaymentRefresh: () => false, PAYMENT_REFRESH_TARGETS: { HOME: 'home' }
|
||||
});
|
||||
if (specifier === '@/utils/wxsdk.js') return synthetic('wxsdk', {
|
||||
initWxConfig: async () => {}, wxScan: async () => {}, isInWechat: () => false
|
||||
initWxConfig: () => sdkInit(), wxScan: async () => {}, isInWechat: () => inWechat
|
||||
});
|
||||
if (specifier === './env.js') return synthetic('env', { BASE_URL: 'https://example.invalid' });
|
||||
if (specifier.endsWith('.vue')) return synthetic(specifier, { default: {} });
|
||||
@@ -105,7 +107,8 @@ function runtime(initial = {}) {
|
||||
request.success({ statusCode, data: { code, msg, data } });
|
||||
};
|
||||
return { storage, sessionStorage, context, calls, requests, mounted, shown, hidden, unmounted, timers, api, load, reply,
|
||||
setOptions: value => { options = value; }, tick: () => vue.nextTick() };
|
||||
setOptions: value => { options = value; }, tick: () => vue.nextTick(),
|
||||
setWechatSdk: init => { inWechat = true; sdkInit = init; } };
|
||||
}
|
||||
|
||||
test('phone binding modes and success updates distinguish all four cases', async () => {
|
||||
@@ -231,6 +234,152 @@ test('login routing honors server guidance even with a phone bound to someone el
|
||||
}
|
||||
});
|
||||
|
||||
test('OAuth round trip carries the verified identifier without relying on localStorage', async () => {
|
||||
const r = runtime({ token: '', identifier: '' });
|
||||
r.api.authApi = { verifyAsset: async id => { assert.equal(id, 'B'); return { asset_token: 'asset-token-B' }; } };
|
||||
r.api.wechatApi = { getAppId: async () => ({ app_id: 'mock-app-id' }) };
|
||||
const page = await r.load('pages/login/login.vue', ['identifier', 'doLogin']);
|
||||
page.identifier.value = ' B ';
|
||||
await page.doLogin();
|
||||
assert.equal(r.storage.get('identifier'), 'B');
|
||||
assert.deepEqual(JSON.parse(r.sessionStorage.get('wechatLoginContext')), { assetToken: 'asset-token-B', identifier: 'B' });
|
||||
const authUrl = new URL(r.context.window.location.href);
|
||||
assert.equal(new URL(authUrl.searchParams.get('redirect_uri')).searchParams.get('identifier'), 'B');
|
||||
});
|
||||
|
||||
test('OAuth callback restores its own asset identifier before entering home', async () => {
|
||||
const r = runtime({ token: 'old-token', identifier: 'old-asset' });
|
||||
r.sessionStorage.set('assetToken', 'asset-token-B');
|
||||
r.sessionStorage.set('wechatLoginContext', JSON.stringify({ assetToken: 'asset-token-B', identifier: 'B' }));
|
||||
r.context.window.location.search = '?code=mock-code&identifier=B';
|
||||
r.api.authApi = { wechatLogin: async () => ({ token: 'new-token', need_bind_phone: false }) };
|
||||
r.api.wechatApi = {};
|
||||
await r.load('pages/login/login.vue');
|
||||
await r.mounted[0]();
|
||||
const user = (await r.load('store/user.js')).useUserStore();
|
||||
assert.equal(user.state.identifier, 'B');
|
||||
assert.equal(r.storage.get('identifier'), 'B');
|
||||
assert.equal(user.state.token, 'new-token');
|
||||
assert.equal(r.calls.find(call => call.kind === 'redirectTo').url, '/pages/index/index');
|
||||
assert.equal(r.sessionStorage.has('assetToken'), false);
|
||||
const { assetApi } = await r.load('api/modules/asset.js');
|
||||
const info = assetApi.getInfo(user.state.identifier);
|
||||
assert.deepEqual(plain(r.requests[0].data), { identifier: 'B' });
|
||||
r.reply(r.requests[0], { identifier: 'B' }); await info;
|
||||
});
|
||||
|
||||
test('callback can recover a verified identifier from session even if localStorage is empty', async () => {
|
||||
const r = runtime({ token: '', identifier: '' });
|
||||
r.sessionStorage.set('assetToken', 'asset-token-B');
|
||||
r.sessionStorage.set('wechatLoginContext', JSON.stringify({ assetToken: 'asset-token-B', identifier: 'B' }));
|
||||
r.context.window.location.search = '?code=mock-code';
|
||||
r.api.authApi = { wechatLogin: async () => ({ token: 'new-token', need_bind_phone: true }) };
|
||||
r.api.wechatApi = {};
|
||||
const page = await r.load('pages/login/login.vue', ['handleWechatCallback']);
|
||||
await page.handleWechatCallback();
|
||||
assert.equal(r.storage.get('identifier'), 'B');
|
||||
assert.equal(r.calls.find(call => call.kind === 'redirectTo').url, '/pages/bind/bind?fromLogin=true');
|
||||
});
|
||||
|
||||
test('callback falls back to URL/saved identifiers, but never enters home without one', async () => {
|
||||
for (const source of ['url', 'storage', 'missing']) {
|
||||
const r = runtime({ token: '', identifier: '' });
|
||||
r.sessionStorage.set('assetToken', 'mock-asset-token');
|
||||
r.context.window.location.search = source === 'url' ? '?code=mock-code&identifier=B' : '?code=mock-code';
|
||||
if (source === 'storage') r.storage.set('identifier', 'B');
|
||||
let logins = 0;
|
||||
r.api.wechatApi = {};
|
||||
r.api.authApi = { wechatLogin: async () => { logins++; return { token: 'new-token', need_bind_phone: false }; } };
|
||||
const page = await r.load('pages/login/login.vue', ['handleWechatCallback']);
|
||||
await page.handleWechatCallback();
|
||||
if (source === 'missing') {
|
||||
assert.equal(logins, 0);
|
||||
assert.equal(r.calls.some(call => call.kind === 'redirectTo'), false);
|
||||
assert.match(r.calls.find(call => call.kind === 'toast').title, /缺少资产标识/);
|
||||
} else {
|
||||
assert.equal(r.storage.get('identifier'), 'B');
|
||||
assert.equal(logins, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('asset-info API rejects empty identifiers without a network request', async () => {
|
||||
const r = runtime();
|
||||
const { assetApi } = await r.load('api/modules/asset.js');
|
||||
for (const value of ['', ' ', undefined, null]) {
|
||||
await assert.rejects(assetApi.getInfo(value));
|
||||
}
|
||||
assert.equal(r.requests.length, 0);
|
||||
const request = assetApi.getInfo(' A ');
|
||||
assert.deepEqual(plain(r.requests[0].data), { identifier: 'A' });
|
||||
r.reply(r.requests[0], {}); await request;
|
||||
});
|
||||
|
||||
test('asset info waits until both wechat-login and JSSDK readiness complete', async () => {
|
||||
const r = runtime({ token: 'old-token', identifier: 'old-asset' });
|
||||
r.sessionStorage.set('assetToken', 'asset-token-B');
|
||||
r.sessionStorage.set('wechatLoginContext', JSON.stringify({ assetToken: 'asset-token-B', identifier: 'B' }));
|
||||
r.context.window.location.search = '?code=mock-code';
|
||||
const login = deferred();
|
||||
const sdk = deferred();
|
||||
const events = [];
|
||||
r.api.authApi = { wechatLogin: () => { events.push('login'); return login.promise; } };
|
||||
r.api.wechatApi = {};
|
||||
r.setWechatSdk(() => { events.push('jssdk'); return sdk.promise; });
|
||||
await r.load('pages/login/login.vue');
|
||||
const { assetApi } = await r.load('api/modules/asset.js');
|
||||
const pending = r.mounted[0]();
|
||||
await assert.rejects(assetApi.getInfo('old-asset'));
|
||||
assert.equal(r.requests.length, 0);
|
||||
assert.deepEqual(events, ['login']);
|
||||
login.resolve({ token: 'new-token', need_bind_phone: false });
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
assert.deepEqual(events, ['login', 'jssdk']);
|
||||
assert.equal(r.calls.some(call => call.kind === 'redirectTo'), false);
|
||||
await assert.rejects(assetApi.getInfo('B'));
|
||||
assert.equal(r.requests.length, 0);
|
||||
sdk.resolve(); await pending;
|
||||
const user = (await r.load('store/user.js')).useUserStore();
|
||||
assert.equal(user.state.loginPending, false);
|
||||
assert.equal(r.calls.find(call => call.kind === 'redirectTo').url, '/pages/index/index');
|
||||
const info = assetApi.getInfo(user.state.identifier);
|
||||
assert.deepEqual(plain(r.requests[0].data), { identifier: 'B' });
|
||||
assert.equal(r.requests[0].header.Authorization, 'Bearer new-token');
|
||||
r.reply(r.requests[0], {}); await info;
|
||||
});
|
||||
|
||||
test('JSSDK failure does not enter the homepage or release asset requests', async () => {
|
||||
const r = runtime({ token: '', identifier: 'A' });
|
||||
r.sessionStorage.set('assetToken', 'asset-token-A');
|
||||
r.context.window.location.search = '?code=mock-code';
|
||||
r.api.authApi = { wechatLogin: async () => ({ token: 'new-token', need_bind_phone: false }) };
|
||||
r.api.wechatApi = {};
|
||||
r.setWechatSdk(async () => { throw new Error('JSSDK failed'); });
|
||||
const page = await r.load('pages/login/login.vue', ['handleWechatCallback']);
|
||||
await page.handleWechatCallback();
|
||||
assert.equal(r.calls.some(call => call.kind === 'redirectTo'), false);
|
||||
const { assetApi } = await r.load('api/modules/asset.js');
|
||||
await assert.rejects(assetApi.getInfo('A'));
|
||||
assert.equal(r.requests.length, 0);
|
||||
});
|
||||
|
||||
test('returning login session initializes JSSDK before re-entering homepage', async () => {
|
||||
const r = runtime();
|
||||
r.api.authApi = {}; r.api.wechatApi = {};
|
||||
const sdk = deferred();
|
||||
r.setWechatSdk(() => sdk.promise);
|
||||
await r.load('pages/login/login.vue');
|
||||
const pending = r.mounted[0]();
|
||||
const { assetApi } = await r.load('api/modules/asset.js');
|
||||
await assert.rejects(assetApi.getInfo('A'));
|
||||
assert.equal(r.calls.some(call => call.kind === 'reLaunch'), false);
|
||||
sdk.resolve(); await pending;
|
||||
assert.equal(r.calls.find(call => call.kind === 'reLaunch').url, '/pages/index/index');
|
||||
const info = assetApi.getInfo('A');
|
||||
assert.equal(r.requests.length, 1);
|
||||
r.reply(r.requests[0], {}); await info;
|
||||
});
|
||||
|
||||
test('expiry boundaries and Shanghai rendering preserve server values, including zero', async () => {
|
||||
const r = runtime();
|
||||
const { normalizeExpiryReminder, getShanghaiDay, formatShanghaiExpiry } = await r.load('utils/expiry-reminder.js');
|
||||
|
||||
Reference in New Issue
Block a user