merge(dev): release v3.4.10
This commit is contained in:
@@ -9,10 +9,9 @@ const isDevMode =
|
|||||||
process.argv.includes('development');
|
process.argv.includes('development');
|
||||||
|
|
||||||
/** 小程序/H5 请求的后端 origin(不含 /api);本地默认本机,生产构建默认远程 */
|
/** 小程序/H5 请求的后端 origin(不含 /api);本地默认本机,生产构建默认远程 */
|
||||||
// TODO(prod-release): 发生产前改回 https://api.dukanghaoke.com
|
|
||||||
const API_ORIGIN =
|
const API_ORIGIN =
|
||||||
process.env.VITE_API_TARGET ??
|
process.env.VITE_API_TARGET ??
|
||||||
(isDevMode ? 'http://localhost:3000' : 'https://api-test.dukanghaoke.com');
|
(isDevMode ? 'http://localhost:3000' : 'https://api.dukanghaoke.com');
|
||||||
|
|
||||||
const requireFromApp = createRequire(path.resolve(__dirname, '../package.json'));
|
const requireFromApp = createRequire(path.resolve(__dirname, '../package.json'));
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,25 @@
|
|||||||
|
import Taro from '@tarojs/taro';
|
||||||
import { createUserTracker, getSessionId } from '@dukang/client-logging';
|
import { createUserTracker, getSessionId } from '@dukang/client-logging';
|
||||||
import { API_BASE, CLIENT_APP, getToken } from './api';
|
import { API_BASE, CLIENT_APP, getToken } from './api';
|
||||||
|
|
||||||
|
function currentPagePath(): string | undefined {
|
||||||
|
try {
|
||||||
|
const pages = Taro.getCurrentPages();
|
||||||
|
const cur = pages[pages.length - 1] as { route?: string; $taroPath?: string } | undefined;
|
||||||
|
return cur?.$taroPath || cur?.route || undefined;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const tracker = createUserTracker({
|
const tracker = createUserTracker({
|
||||||
apiBase: API_BASE,
|
apiBase: API_BASE,
|
||||||
clientApp: CLIENT_APP,
|
clientApp: CLIENT_APP,
|
||||||
getToken,
|
getToken,
|
||||||
|
getPagePath: currentPagePath,
|
||||||
|
postJson: ({ url, headers, body }) => {
|
||||||
|
void Taro.request({ url, method: 'POST', header: headers, data: body }).catch(() => {});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export { getSessionId };
|
export { getSessionId };
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { postJson, type PostJson } from './http';
|
||||||
import { getSessionId } from './session';
|
import { getSessionId } from './session';
|
||||||
|
|
||||||
export type TrackParams = Record<string, unknown>;
|
export type TrackParams = Record<string, unknown>;
|
||||||
@@ -6,10 +7,20 @@ export type UserTrackerOptions = {
|
|||||||
apiBase: string;
|
apiBase: string;
|
||||||
clientApp: string;
|
clientApp: string;
|
||||||
getToken?: () => string | null;
|
getToken?: () => string | null;
|
||||||
|
postJson?: PostJson;
|
||||||
|
getPagePath?: () => string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function resolvePagePath(getPagePath?: () => string | undefined): string | undefined {
|
||||||
|
if (getPagePath) return getPagePath();
|
||||||
|
if (typeof window !== 'undefined') return window.location.pathname;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function createUserTracker(opts: UserTrackerOptions) {
|
export function createUserTracker(opts: UserTrackerOptions) {
|
||||||
const getToken = opts.getToken ?? (() => localStorage.getItem('accessToken'));
|
const send = opts.postJson ?? postJson;
|
||||||
|
const getToken = opts.getToken ?? (() =>
|
||||||
|
typeof localStorage !== 'undefined' ? localStorage.getItem('accessToken') : null);
|
||||||
|
|
||||||
function track(eventName: string, params?: TrackParams) {
|
function track(eventName: string, params?: TrackParams) {
|
||||||
const sessionId = getSessionId();
|
const sessionId = getSessionId();
|
||||||
@@ -20,27 +31,27 @@ export function createUserTracker(opts: UserTrackerOptions) {
|
|||||||
};
|
};
|
||||||
if (token) headers.Authorization = `Bearer ${token}`;
|
if (token) headers.Authorization = `Bearer ${token}`;
|
||||||
|
|
||||||
void fetch(`${opts.apiBase}/analytics/events`, {
|
send({
|
||||||
method: 'POST',
|
url: `${opts.apiBase}/analytics/events`,
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify({
|
body: {
|
||||||
sessionId,
|
sessionId,
|
||||||
clientApp: opts.clientApp,
|
clientApp: opts.clientApp,
|
||||||
events: [{ eventName, params: { sessionId, ...params } }],
|
events: [{ eventName, params: { sessionId, ...params } }],
|
||||||
}),
|
},
|
||||||
}).catch(() => {});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function trackPageView(eventName: string, params?: TrackParams) {
|
function trackPageView(eventName: string, params?: TrackParams) {
|
||||||
track(eventName, {
|
track(eventName, {
|
||||||
pagePath: typeof window !== 'undefined' ? window.location.pathname : undefined,
|
pagePath: resolvePagePath(opts.getPagePath),
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function trackSessionStart() {
|
function trackSessionStart() {
|
||||||
track('session_start', {
|
track('session_start', {
|
||||||
pagePath: typeof window !== 'undefined' ? window.location.pathname : undefined,
|
pagePath: resolvePagePath(opts.getPagePath),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,32 +63,36 @@ export type StoreTrackerOptions = {
|
|||||||
clientApp: string;
|
clientApp: string;
|
||||||
getToken: () => string | null;
|
getToken: () => string | null;
|
||||||
getStoreId: () => string | null;
|
getStoreId: () => string | null;
|
||||||
|
postJson?: PostJson;
|
||||||
|
getPagePath?: () => string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createStoreTracker(opts: StoreTrackerOptions) {
|
export function createStoreTracker(opts: StoreTrackerOptions) {
|
||||||
|
const send = opts.postJson ?? postJson;
|
||||||
|
|
||||||
function track(eventName: string, params?: TrackParams) {
|
function track(eventName: string, params?: TrackParams) {
|
||||||
const token = opts.getToken();
|
const token = opts.getToken();
|
||||||
const storeId = opts.getStoreId();
|
const storeId = opts.getStoreId();
|
||||||
if (!token || !storeId) return;
|
if (!token || !storeId) return;
|
||||||
|
|
||||||
void fetch(`${opts.apiBase}/analytics/store-events`, {
|
send({
|
||||||
method: 'POST',
|
url: `${opts.apiBase}/analytics/store-events`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
'X-Client-App': opts.clientApp,
|
'X-Client-App': opts.clientApp,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: {
|
||||||
sessionId: getSessionId(),
|
sessionId: getSessionId(),
|
||||||
storeId,
|
storeId,
|
||||||
events: [{ eventName, params: { sessionId: getSessionId(), storeId, ...params } }],
|
events: [{ eventName, params: { sessionId: getSessionId(), storeId, ...params } }],
|
||||||
}),
|
},
|
||||||
}).catch(() => {});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function trackPageView(eventName: string, params?: TrackParams) {
|
function trackPageView(eventName: string, params?: TrackParams) {
|
||||||
track(eventName, {
|
track(eventName, {
|
||||||
pagePath: typeof window !== 'undefined' ? window.location.pathname : undefined,
|
pagePath: resolvePagePath(opts.getPagePath),
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -89,30 +104,34 @@ export type PartnerTrackerOptions = {
|
|||||||
apiBase: string;
|
apiBase: string;
|
||||||
clientApp: string;
|
clientApp: string;
|
||||||
getToken: () => string | null;
|
getToken: () => string | null;
|
||||||
|
postJson?: PostJson;
|
||||||
|
getPagePath?: () => string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createPartnerTracker(opts: PartnerTrackerOptions) {
|
export function createPartnerTracker(opts: PartnerTrackerOptions) {
|
||||||
|
const send = opts.postJson ?? postJson;
|
||||||
|
|
||||||
function track(eventName: string, params?: TrackParams) {
|
function track(eventName: string, params?: TrackParams) {
|
||||||
const token = opts.getToken();
|
const token = opts.getToken();
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
|
|
||||||
void fetch(`${opts.apiBase}/analytics/partner-events`, {
|
send({
|
||||||
method: 'POST',
|
url: `${opts.apiBase}/analytics/partner-events`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
'X-Client-App': opts.clientApp,
|
'X-Client-App': opts.clientApp,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: {
|
||||||
sessionId: getSessionId(),
|
sessionId: getSessionId(),
|
||||||
events: [{ eventName, params: { sessionId: getSessionId(), ...params } }],
|
events: [{ eventName, params: { sessionId: getSessionId(), ...params } }],
|
||||||
}),
|
},
|
||||||
}).catch(() => {});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function trackPageView(eventName: string, params?: TrackParams) {
|
function trackPageView(eventName: string, params?: TrackParams) {
|
||||||
track(eventName, {
|
track(eventName, {
|
||||||
pagePath: typeof window !== 'undefined' ? window.location.pathname : undefined,
|
pagePath: resolvePagePath(opts.getPagePath),
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { postJson } from './http';
|
||||||
|
|
||||||
export type ClientErrorLevel = 'fatal' | 'error' | 'warn' | 'info';
|
export type ClientErrorLevel = 'fatal' | 'error' | 'warn' | 'info';
|
||||||
export type ClientErrorCategory =
|
export type ClientErrorCategory =
|
||||||
| 'js_error'
|
| 'js_error'
|
||||||
@@ -31,10 +33,10 @@ export function installClientErrorReporting(opts: ClientErrorReporterOptions) {
|
|||||||
};
|
};
|
||||||
if (token) headers.Authorization = `Bearer ${token}`;
|
if (token) headers.Authorization = `Bearer ${token}`;
|
||||||
|
|
||||||
void fetch(`${opts.apiBase}/common/client-errors`, {
|
postJson({
|
||||||
method: 'POST',
|
url: `${opts.apiBase}/common/client-errors`,
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify({
|
body: {
|
||||||
level: payload.level,
|
level: payload.level,
|
||||||
category: payload.category,
|
category: payload.category,
|
||||||
message: payload.message,
|
message: payload.message,
|
||||||
@@ -42,8 +44,8 @@ export function installClientErrorReporting(opts: ClientErrorReporterOptions) {
|
|||||||
pagePath: window.location.pathname,
|
pagePath: window.location.pathname,
|
||||||
clientApp: opts.clientApp,
|
clientApp: opts.clientApp,
|
||||||
extra: payload.extra,
|
extra: payload.extra,
|
||||||
}),
|
},
|
||||||
}).catch(() => {});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('error', (ev) => {
|
window.addEventListener('error', (ev) => {
|
||||||
@@ -78,16 +80,16 @@ export function reportApiError(
|
|||||||
};
|
};
|
||||||
if (token) headers.Authorization = `Bearer ${token}`;
|
if (token) headers.Authorization = `Bearer ${token}`;
|
||||||
|
|
||||||
void fetch(`${opts.apiBase}/common/client-errors`, {
|
postJson({
|
||||||
method: 'POST',
|
url: `${opts.apiBase}/common/client-errors`,
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify({
|
body: {
|
||||||
level: 'warn',
|
level: 'warn',
|
||||||
category: input.category ?? 'api_error',
|
category: input.category ?? 'api_error',
|
||||||
message: input.message.slice(0, 1000),
|
message: input.message.slice(0, 1000),
|
||||||
pagePath: window.location.pathname,
|
pagePath: window.location.pathname,
|
||||||
clientApp: opts.clientApp,
|
clientApp: opts.clientApp,
|
||||||
extra: { status: input.status, url: input.url },
|
extra: { status: input.status, url: input.url },
|
||||||
}),
|
},
|
||||||
}).catch(() => {});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
export type PostJsonInput = {
|
||||||
|
url: string;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
body: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PostJson = (input: PostJsonInput) => void;
|
||||||
|
|
||||||
|
type WxLike = {
|
||||||
|
request?: (opts: {
|
||||||
|
url: string;
|
||||||
|
method?: string;
|
||||||
|
header?: Record<string, string>;
|
||||||
|
data?: unknown;
|
||||||
|
fail?: () => void;
|
||||||
|
}) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getWx(): WxLike | undefined {
|
||||||
|
return (globalThis as { wx?: WxLike }).wx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** fire-and-forget POST JSON;优先 fetch,小程序回退 wx.request */
|
||||||
|
export function postJson(input: PostJsonInput): void {
|
||||||
|
const headers = input.headers ?? { 'Content-Type': 'application/json' };
|
||||||
|
|
||||||
|
if (typeof fetch === 'function') {
|
||||||
|
void fetch(input.url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(input.body),
|
||||||
|
}).catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wxApi = getWx();
|
||||||
|
if (wxApi?.request) {
|
||||||
|
wxApi.request({
|
||||||
|
url: input.url,
|
||||||
|
method: 'POST',
|
||||||
|
header: headers,
|
||||||
|
data: input.body,
|
||||||
|
fail: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ export {
|
|||||||
type StoreTrackerOptions,
|
type StoreTrackerOptions,
|
||||||
type PartnerTrackerOptions,
|
type PartnerTrackerOptions,
|
||||||
} from './analytics';
|
} from './analytics';
|
||||||
|
export { postJson, type PostJson, type PostJsonInput } from './http';
|
||||||
export {
|
export {
|
||||||
installClientErrorReporting,
|
installClientErrorReporting,
|
||||||
reportApiError,
|
reportApiError,
|
||||||
|
|||||||
@@ -1,14 +1,50 @@
|
|||||||
const SESSION_KEY = 'dukang_session_id';
|
const SESSION_KEY = 'dukang_session_id';
|
||||||
|
|
||||||
|
type WxStorage = {
|
||||||
|
getStorageSync?: (key: string) => unknown;
|
||||||
|
setStorageSync?: (key: string, value: unknown) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getWxStorage(): WxStorage | undefined {
|
||||||
|
return (globalThis as { wx?: WxStorage }).wx;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readStorage(key: string): string | null {
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
return localStorage.getItem(key);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const v = getWxStorage()?.getStorageSync?.(key);
|
||||||
|
return v != null && v !== '' ? String(v) : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeStorage(key: string, value: string): void {
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
localStorage.setItem(key, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
getWxStorage()?.setStorageSync?.(key, value);
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function newSessionId(): string {
|
||||||
|
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
|
||||||
|
return crypto.randomUUID();
|
||||||
|
}
|
||||||
|
return `s_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function getSessionId(): string {
|
export function getSessionId(): string {
|
||||||
if (typeof localStorage === 'undefined') return 'ssr';
|
let id = readStorage(SESSION_KEY);
|
||||||
let id = localStorage.getItem(SESSION_KEY);
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
id =
|
id = newSessionId();
|
||||||
typeof crypto !== 'undefined' && 'randomUUID' in crypto
|
writeStorage(SESSION_KEY, id);
|
||||||
? crypto.randomUUID()
|
|
||||||
: `s_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
||||||
localStorage.setItem(SESSION_KEY, id);
|
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user