hiwork-core 0.0.2 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,82 @@
1
+ /**
2
+ * 客户端版本策略(spec §6):`GET /api/client/policy` 的免登录只读拉取。
3
+ *
4
+ * 网关返回的是**最终判定**(`loginMode` 等),插件只做投影不做策略逻辑,因此管理端
5
+ * 调整 `client.login_mode` 后重启插件即生效,无需发版(spec §6.1)。
6
+ *
7
+ * 本模块只负责「拉 + 严格解析」,投影与 fail-open 缺省由 `src/index.ts` 的
8
+ * `projectLoginMode` 完成:**非 200 / 非 JSON / 枚举非法 / 超时 / 网络异常一律返回
9
+ * undefined**,调用方据此回退 `DEFAULT_LOGIN_MODE`,绝不把用户锁在门外(spec §5.4 / §6.4)。
10
+ */
11
+ import { PKG_VERSION } from "./version.js";
12
+ /** fail-open 缺省值:策略缺失 / 失败 / 非法时一律按此投影(spec §5.4)。 */
13
+ export const DEFAULT_LOGIN_MODE = 'both';
14
+ /** 策略端点:域名一律来自 baseURL,不硬编码。 */
15
+ export const CLIENT_POLICY_PATH = '/api/client/policy';
16
+ /** 策略拉取默认超时:免登录只读接口,超过 5s 视同不可达(fail-open)。 */
17
+ export const CLIENT_POLICY_TIMEOUT_MS = 5_000;
18
+ /**
19
+ * 拼策略 URL:`{baseURL}/api/client/policy?platform=&arch=&version=&device_id=`。
20
+ * 参数固定顺序、逐个 `encodeURIComponent`(device_id 可能是任意 ASCII,含 `+/=` 也必须安全);
21
+ * 空值参数直接省略(不产生 `version=` 这样的空参数);baseURL 尾斜杠归一。
22
+ * @returns 完整 URL;缺 baseURL 时 undefined(调用方据此跳过请求)。
23
+ */
24
+ export function buildClientPolicyURL(opts) {
25
+ const baseURL = typeof opts.baseURL === 'string' ? opts.baseURL.trim().replace(/\/+$/, '') : '';
26
+ if (baseURL === '')
27
+ return undefined;
28
+ const params = [
29
+ ['platform', opts.platform],
30
+ ['arch', opts.arch],
31
+ ['version', opts.version],
32
+ ['device_id', opts.deviceId],
33
+ ];
34
+ const query = params
35
+ .filter(([, value]) => typeof value === 'string' && value !== '')
36
+ .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
37
+ .join('&');
38
+ return query === '' ? `${baseURL}${CLIENT_POLICY_PATH}` : `${baseURL}${CLIENT_POLICY_PATH}?${query}`;
39
+ }
40
+ /**
41
+ * 严格解析响应体:只认 `loginMode` 恰好是三个枚举值之一的对象;
42
+ * 其余(非对象 / 字段缺失 / 非字符串 / 非法枚举值)一律 undefined。
43
+ */
44
+ function parseLoginMode(payload) {
45
+ if (typeof payload !== 'object' || payload === null)
46
+ return undefined;
47
+ const value = payload.loginMode;
48
+ return value === 'browser' || value === 'form' || value === 'both' ? value : undefined;
49
+ }
50
+ /**
51
+ * 拉取并解析客户端策略。**永不抛异常**:任何失败都返回 undefined(fail-open)。
52
+ *
53
+ * 不记录响应原文——策略接口当前无敏感字段,但保持与其他网关调用一致的日志纪律。
54
+ */
55
+ export async function fetchClientPolicy(opts) {
56
+ const url = buildClientPolicyURL(opts);
57
+ if (url === undefined)
58
+ return undefined;
59
+ try {
60
+ const res = await fetch(url, {
61
+ method: 'GET',
62
+ headers: { 'user-agent': `hiwork-core/${PKG_VERSION}`, accept: 'application/json' },
63
+ signal: AbortSignal.timeout(opts.timeoutMs ?? CLIENT_POLICY_TIMEOUT_MS),
64
+ });
65
+ // 严格 200:304 / 204 等一律视为不可用(本插件不维护 ETag 缓存,宁可按缺省放行)。
66
+ if (res.status !== 200)
67
+ return undefined;
68
+ let payload;
69
+ try {
70
+ payload = await res.json();
71
+ }
72
+ catch {
73
+ return undefined;
74
+ }
75
+ const loginMode = parseLoginMode(payload);
76
+ return loginMode === undefined ? undefined : { loginMode };
77
+ }
78
+ catch {
79
+ // 网络异常 / 超时(AbortSignal.timeout 抛 TimeoutError)/ 非法 URL:一律 fail-open。
80
+ return undefined;
81
+ }
82
+ }