Files
Genarrative/miniprogram/pages/web-view/index.js

350 lines
9.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const {
API_BASE_URL,
MINI_PROGRAM_APP_ID,
MINI_PROGRAM_ENV,
WEB_VIEW_ENTRY_URL,
WEB_VIEW_SOURCE_QUERY,
} = require('../../config');
const MINI_PROGRAM_CLIENT_TYPE = 'mini_program';
const MINI_PROGRAM_CLIENT_RUNTIME = 'wechat_mini_program';
const CLIENT_INSTANCE_STORAGE_KEY = 'genarrative:mini-program-client-instance-id';
function isConfiguredEntryUrl(value) {
const trimmed = String(value || '').trim();
return /^https:\/\/[^/]+/i.test(trimmed);
}
function trimTrailingSlash(value) {
return String(value || '').trim().replace(/\/+$/u, '');
}
function isConfiguredApiBaseUrl(value) {
return /^https:\/\/[^/]+/i.test(String(value || '').trim());
}
function appendQuery(url, query) {
const pairs = Object.keys(query)
.filter((key) => query[key])
.map(
(key) =>
`${encodeURIComponent(key)}=${encodeURIComponent(String(query[key]))}`,
);
if (pairs.length === 0) {
return url;
}
return `${url}${url.includes('?') ? '&' : '?'}${pairs.join('&')}`;
}
function appendHashParams(url, params) {
const pairs = Object.keys(params)
.filter((key) => params[key])
.map(
(key) =>
`${encodeURIComponent(key)}=${encodeURIComponent(String(params[key]))}`,
);
if (pairs.length === 0) {
return url;
}
const hashIndex = url.indexOf('#');
const baseUrl = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
const rawHash = hashIndex >= 0 ? url.slice(hashIndex + 1) : '';
const separator = rawHash ? '&' : '';
return `${baseUrl}#${rawHash}${separator}${pairs.join('&')}`;
}
function resolveWebViewUrl(authResult) {
const entryUrl = String(WEB_VIEW_ENTRY_URL || '').trim();
if (!isConfiguredEntryUrl(entryUrl)) {
return '';
}
const sourcedUrl = appendQuery(entryUrl, WEB_VIEW_SOURCE_QUERY);
if (!authResult || !authResult.token) {
return sourcedUrl;
}
return appendHashParams(sourcedUrl, {
auth_provider: 'wechat',
auth_token: authResult.token,
auth_binding_status: authResult.bindingStatus,
});
}
function getClientInstanceId() {
const stored = wx.getStorageSync(CLIENT_INSTANCE_STORAGE_KEY);
if (stored) {
return String(stored);
}
const nextId = `wxmp_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
wx.setStorageSync(CLIENT_INSTANCE_STORAGE_KEY, nextId);
return nextId;
}
function resolveClientPlatform() {
const info = wx.getSystemInfoSync();
const platform = String(info.platform || '').toLowerCase();
if (platform === 'ios') {
return 'ios';
}
if (platform === 'android') {
return 'android';
}
return 'unknown';
}
function wxLogin() {
return new Promise((resolve, reject) => {
wx.login({
success(result) {
if (result.code) {
resolve(result.code);
return;
}
reject(new Error('微信登录未返回 code'));
},
fail(error) {
reject(new Error(error.errMsg || '微信登录失败'));
},
});
});
}
function requestMiniProgramLogin(code) {
return new Promise((resolve, reject) => {
const apiBaseUrl = trimTrailingSlash(API_BASE_URL);
if (!isConfiguredApiBaseUrl(apiBaseUrl)) {
reject(new Error('请先配置 API_BASE_URL'));
return;
}
wx.request({
url: `${apiBaseUrl}/api/auth/wechat/miniprogram-login`,
method: 'POST',
data: { code },
header: {
'content-type': 'application/json',
'x-client-type': MINI_PROGRAM_CLIENT_TYPE,
'x-client-runtime': MINI_PROGRAM_CLIENT_RUNTIME,
'x-client-platform': resolveClientPlatform(),
'x-client-instance-id': getClientInstanceId(),
'x-mini-program-app-id': MINI_PROGRAM_APP_ID,
'x-mini-program-env': MINI_PROGRAM_ENV,
},
success(response) {
if (response.statusCode >= 200 && response.statusCode < 300) {
resolve(response.data);
return;
}
const message =
response.data &&
response.data.error &&
response.data.error.message
? response.data.error.message
: `微信登录失败:${response.statusCode}`;
reject(new Error(message));
},
fail(error) {
reject(new Error(error.errMsg || '微信登录请求失败'));
},
});
});
}
function requestMiniProgramBindPhone(authToken, wechatPhoneCode) {
return new Promise((resolve, reject) => {
const apiBaseUrl = trimTrailingSlash(API_BASE_URL);
if (!isConfiguredApiBaseUrl(apiBaseUrl)) {
reject(new Error('请先配置 API_BASE_URL'));
return;
}
wx.request({
url: `${apiBaseUrl}/api/auth/wechat/bind-phone`,
method: 'POST',
data: { wechatPhoneCode },
header: {
authorization: `Bearer ${authToken}`,
'content-type': 'application/json',
'x-client-type': MINI_PROGRAM_CLIENT_TYPE,
'x-client-runtime': MINI_PROGRAM_CLIENT_RUNTIME,
'x-client-platform': resolveClientPlatform(),
'x-client-instance-id': getClientInstanceId(),
'x-mini-program-app-id': MINI_PROGRAM_APP_ID,
'x-mini-program-env': MINI_PROGRAM_ENV,
},
success(response) {
if (response.statusCode >= 200 && response.statusCode < 300) {
resolve(response.data);
return;
}
const message =
response.data &&
response.data.error &&
response.data.error.message
? response.data.error.message
: `绑定手机号失败:${response.statusCode}`;
reject(new Error(message));
},
fail(error) {
reject(new Error(error.errMsg || '绑定手机号请求失败'));
},
});
});
}
async function resolveAuthResult() {
const code = await wxLogin();
const response = await requestMiniProgramLogin(code);
if (!response || !response.token) {
throw new Error('服务器未返回登录态');
}
return {
token: response.token,
bindingStatus: response.bindingStatus || 'pending_bind_phone',
};
}
Page({
data: {
authResult: null,
bindingPhone: false,
errorMessage: '',
loading: true,
phoneBindingRequired: false,
webViewUrl: '',
},
async onLoad() {
// 中文注释web-view 只能打开已配置业务域名;未配置时展示本地提示,避免空白页误判。
if (!isConfiguredEntryUrl(WEB_VIEW_ENTRY_URL)) {
this.setData({
errorMessage: '请先在 miniprogram/config.js 填写 WEB_VIEW_ENTRY_URL。',
loading: false,
webViewUrl: '',
});
return;
}
if (!isConfiguredApiBaseUrl(API_BASE_URL)) {
this.setData({
errorMessage: '请先在 miniprogram/config.js 填写 API_BASE_URL。',
loading: false,
webViewUrl: '',
});
return;
}
try {
const authResult = await resolveAuthResult();
if (authResult.bindingStatus === 'pending_bind_phone') {
this.setData({
authResult,
errorMessage: '',
loading: false,
phoneBindingRequired: true,
webViewUrl: '',
});
return;
}
this.setData({
authResult,
errorMessage: '',
loading: false,
phoneBindingRequired: false,
webViewUrl: resolveWebViewUrl(authResult),
});
} catch (error) {
this.setData({
authResult: null,
errorMessage:
error && error.message
? error.message
: '微信登录失败,请稍后重试。',
loading: false,
phoneBindingRequired: false,
webViewUrl: '',
});
}
},
async handleGetPhoneNumber(event) {
if (!this.data.authResult || !this.data.authResult.token) {
this.handleRetryLogin();
return;
}
const detail = event.detail || {};
if (!detail.code) {
this.setData({
errorMessage: detail.errMsg || '需要授权手机号后才能完成绑定。',
});
return;
}
this.setData({
bindingPhone: true,
errorMessage: '',
});
try {
const response = await requestMiniProgramBindPhone(
this.data.authResult.token,
detail.code,
);
if (!response || !response.token) {
throw new Error('服务器未返回绑定后的登录态');
}
const nextAuthResult = {
token: response.token,
bindingStatus: 'active',
};
this.setData({
authResult: nextAuthResult,
bindingPhone: false,
errorMessage: '',
loading: false,
phoneBindingRequired: false,
webViewUrl: resolveWebViewUrl(nextAuthResult),
});
} catch (error) {
this.setData({
bindingPhone: false,
errorMessage:
error && error.message
? error.message
: '绑定手机号失败,请稍后重试。',
});
}
},
handleRetryLogin() {
this.setData({
authResult: null,
bindingPhone: false,
errorMessage: '',
loading: true,
phoneBindingRequired: false,
webViewUrl: '',
});
this.onLoad();
},
handleWebViewLoad(event) {
console.info('[web-view] loaded', event.detail);
},
handleWebViewError(event) {
console.error('[web-view] load failed', event.detail);
},
handleWebViewMessage(event) {
// 中文注释H5 如需和小程序壳通信,可通过 wx.miniProgram.postMessage 发送轻量消息。
console.info('[web-view] message', event.detail);
},
});