刘光辉
3 小时以前 cf11e63b659afcc9792ab3e395f1b45d844431a8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
export const AUDIT_BIZ_SIGN_HEADER = 'X-Audit-Biz-Sign';
 
const EVENT_TIMEOUT_MS = 5 * 60 * 1000;
const SIGN_PATTERN = /^[\w-]{1,40}$/;
 
interface AuditBizSignEvent {
  bizSign: string;
  expiresAt: number;
  timeout: ReturnType<typeof setTimeout>;
  token: symbol;
}
 
let activeEvent: AuditBizSignEvent | undefined;
let eventQueue: Promise<void> = Promise.resolve();
 
export async function runWithAuditBizSignEvent<T>(bizSign: unknown, submit: () => Promise<T> | T): Promise<T> {
  const normalizedBizSign = normalizeBizSign(bizSign);
  if (!normalizedBizSign) throw new Error('电子签名认证结果无效');
 
  const previousEvent = eventQueue;
  let releaseEvent!: () => void;
  eventQueue = new Promise<void>((resolve) => {
    releaseEvent = resolve;
  });
  await previousEvent;
 
  const token = Symbol('audit-biz-sign-event');
  const timeout = setTimeout(() => clearEvent(token), EVENT_TIMEOUT_MS);
  activeEvent = {
    bizSign: normalizedBizSign,
    expiresAt: Date.now() + EVENT_TIMEOUT_MS,
    timeout,
    token,
  };
 
  try {
    return await submit();
  } finally {
    clearEvent(token);
    releaseEvent();
  }
}
 
export function getActiveAuditBizSign(): string {
  if (!activeEvent) return '';
  if (activeEvent.expiresAt <= Date.now()) {
    clearEvent(activeEvent.token);
    return '';
  }
  return activeEvent.bizSign;
}
 
export function applyActiveAuditBizSign(headers: Record<string, any>, url: string | undefined, method: string | undefined, apiBaseUrl = ''): void {
  const bizSign = getActiveAuditBizSign();
  if (!bizSign || hasHeader(headers, AUDIT_BIZ_SIGN_HEADER) || !shouldAttachAuditBizSign(url, method, apiBaseUrl)) return;
  headers[AUDIT_BIZ_SIGN_HEADER] = bizSign;
}
 
export function shouldAttachAuditBizSign(url: string | undefined, method: string | undefined, apiBaseUrl = ''): boolean {
  if (!['delete', 'patch', 'post', 'put'].includes(String(method || '').toLowerCase())) return false;
  if (!url || url.startsWith('//')) return false;
 
  try {
    const browserOrigin = globalThis.location?.origin || 'http://audit.local';
    const resolved = new URL(url, browserOrigin);
    const configuredApi = new URL(apiBaseUrl || '/', browserOrigin);
    if (resolved.origin !== configuredApi.origin) return false;
    const pathname = stripApiBasePath(resolved.pathname, configuredApi.pathname);
    if (!pathname.startsWith('/api/')) return false;
    return !isExcludedPath(pathname);
  } catch {
    return false;
  }
}
 
function hasHeader(headers: Record<string, any>, name: string): boolean {
  if (typeof headers?.has === 'function' && headers.has(name)) return true;
  return Object.keys(headers || {}).some((key) => key.toLowerCase() === name.toLowerCase());
}
 
function normalizeBizSign(value: unknown): string {
  const normalized = value == null ? '' : String(value).trim();
  return SIGN_PATTERN.test(normalized) ? normalized : '';
}
 
function stripApiBasePath(pathname: string, apiBasePath: string): string {
  const normalizedBasePath = apiBasePath.replace(/\/$/, '');
  if (!normalizedBasePath || normalizedBasePath === '/') return pathname;
  if (pathname === normalizedBasePath) return '/';
  return pathname.startsWith(`${normalizedBasePath}/`) ? pathname.slice(normalizedBasePath.length) : pathname;
}
 
function clearEvent(token: symbol) {
  if (activeEvent?.token !== token) return;
  clearTimeout(activeEvent.timeout);
  activeEvent = undefined;
}
 
function isExcludedPath(pathname: string): boolean {
  return (
    /\/sign\/(?:init-all|modify-password|verify|verify_user)\/?$/i.test(pathname) ||
    /^\/api\/oauth(?:\/|$)/i.test(pathname) ||
    /^\/api\/audit(?:\/|$)/i.test(pathname)
  );
}