linke-sdufe 0.1.0

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.
package/src/env.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * 运行环境能力注入(宿主适配层)。
3
+ *
4
+ * 适配器核心不直接触碰 Node 专有 API;宿主通过 env 提供四件能力:
5
+ *
6
+ * env.fetch(url, { method, headers, body, redirect, timeoutMs, expect })
7
+ * → Promise<Response-like>
8
+ * Response-like: { status:number, ok:boolean,
9
+ * headers:{ getSetCookie():string[] },
10
+ * text():Promise<string>, arrayBuffer():Promise<ArrayBuffer> }
11
+ * expect='buffer' 表示适配器将调用 arrayBuffer()(uni.request 一次只能
12
+ * 一种 responseType,垫片据此选择;标准 fetch 宿主可忽略 expect)。
13
+ * 超时由 timeoutMs 表达(毫秒),宿主负责实现(Node 用 AbortSignal.timeout)。
14
+ * 请求失败(网络层)应 reject——适配器统一转 networkError。
15
+ *
16
+ * env.toBase64(bytes:Uint8Array) → string 验证码图片上行(云端识别入参)
17
+ * env.bytesToText(bytes:Uint8Array) → string 前 200 字节嗅探(判定教务返回
18
+ * 了 HTML 错误页而非图片),宽松实现即可(ASCII 特征判定)
19
+ * env.progress(msg:string) → void 进度输出(Node 走 stderr;
20
+ * App 端注入 console/log 回调,缺省为 no-op)
21
+ */
22
+
23
+ /** Node 宿主默认 env(Node >= 18.14:全局 fetch + getSetCookie)。全惰性,模块顶层零 Node 触碰 */
24
+ export function nodeEnv() {
25
+ return {
26
+ async fetch(url, { method, headers, body, redirect, timeoutMs } = {}) {
27
+ return fetch(url, {
28
+ method,
29
+ redirect,
30
+ headers,
31
+ body,
32
+ signal: AbortSignal.timeout(timeoutMs || 20000),
33
+ })
34
+ },
35
+ toBase64(bytes) {
36
+ return Buffer.from(bytes).toString('base64')
37
+ },
38
+ bytesToText(bytes) {
39
+ return Buffer.from(bytes).toString('utf8')
40
+ },
41
+ progress(msg) {
42
+ process.stderr.write(`[linke] ${msg}\n`)
43
+ },
44
+ }
45
+ }
46
+
47
+ const REQUIRED = ['fetch', 'toBase64', 'bytesToText']
48
+
49
+ /** 校验宿主 env 缺件(progress 可缺省,其余三件必须) */
50
+ export function validateEnv(env) {
51
+ if (!env || typeof env !== 'object') {
52
+ throw new Error('createSdufeAdapter 需要 env 对象(见 linke-sdufe README 环境契约)')
53
+ }
54
+ for (const key of REQUIRED) {
55
+ if (typeof env[key] !== 'function') {
56
+ throw new Error(`env.${key} 缺失或不是函数(linke-sdufe 环境契约四件之一)`)
57
+ }
58
+ }
59
+ return env
60
+ }
package/src/errors.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * 适配器层统一错误分类。exit code 契约同时写进了 linke-cli 的
3
+ * skill 说明书(skills/linke/SKILL.md),改动这里必须同步改说明书。
4
+ * linke-cli 的 src/errors.js re-export 本文件并补充 CLI 专属错误
5
+ * (configMissing)——单一实现,双端同源。
6
+ */
7
+ export class LinkeError extends Error {
8
+ /**
9
+ * @param {string} code 机器可读错误码
10
+ * @param {string} message 人类可读信息(不得包含密码等凭据)
11
+ * @param {object} options
12
+ * @param {number} options.exitCode
13
+ * @param {string} options.hint 自救提示(给 agent / 用户看)
14
+ * @param {boolean} options.isLoginExpired 内部标记:教务 session 失效,可重登重试
15
+ */
16
+ constructor(code, message, options = {}) {
17
+ super(message)
18
+ this.name = 'LinkeError'
19
+ this.code = code
20
+ this.exitCode = options.exitCode ?? 1
21
+ this.hint = options.hint ?? ''
22
+ this.isLoginExpired = options.isLoginExpired ?? false
23
+ this.cause = options.cause
24
+ }
25
+ }
26
+
27
+ export const EXIT = {
28
+ OK: 0,
29
+ GENERAL: 1,
30
+ CREDENTIAL_INVALID: 2,
31
+ LOGIN_RETRY_EXHAUSTED: 3,
32
+ NETWORK: 4,
33
+ PARSE: 5,
34
+ NOT_CONFIGURED: 6,
35
+ }
36
+
37
+ export function credentialInvalid(detail = '') {
38
+ return new LinkeError('CREDENTIAL_INVALID', `教务账号或密码错误${detail ? `(${detail})` : ''}`, {
39
+ exitCode: EXIT.CREDENTIAL_INVALID,
40
+ hint: '请重新运行: linke config 录入正确凭据',
41
+ })
42
+ }
43
+
44
+ export function loginRetryExhausted(attempts) {
45
+ return new LinkeError('LOGIN_RETRY_EXHAUSTED', `验证码自动识别连续 ${attempts} 次未通过,登录未完成`, {
46
+ exitCode: EXIT.LOGIN_RETRY_EXHAUSTED,
47
+ hint: '稍后重试同一命令即可(会自动重登);若持续失败,可能是云端识别服务或教务系统异常,运行 linke status 检查',
48
+ })
49
+ }
50
+
51
+ export function networkError(action, cause) {
52
+ return new LinkeError('NETWORK', `${action}失败:网络不可达或服务异常`, {
53
+ exitCode: EXIT.NETWORK,
54
+ hint: '检查本机网络(教务系统为校内可达的 http://jw.sdufe.edu.cn,云端识别为 https://api.linketeam.com)后重试',
55
+ cause,
56
+ })
57
+ }
58
+
59
+ export function parseError(what) {
60
+ return new LinkeError('PARSE', `解析${what}失败:教务页面结构可能已变化`, {
61
+ exitCode: EXIT.PARSE,
62
+ hint: '这是适配器正则与教务页面脱节,请到 linke-cli 仓库提 issue 注明发生时间',
63
+ })
64
+ }
package/src/index.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * linke-sdufe:山财强智教务适配器共享包。
3
+ * linke-cli(Node)与 Linke App(uni-app,经 uni.request 垫片)同源引用。
4
+ */
5
+ export { createSdufeAdapter, COURSE_TYPE_MAP } from './adapter.js'
6
+ export { nodeEnv, validateEnv } from './env.js'
7
+ export { computeEncoded } from './encoding.js'
8
+ export * as parsers from './parsers.js'
9
+ export { stripSpaces, isJwLoginExpired, extractCookieHeader } from './util.js'
10
+ export { LinkeError, EXIT, networkError, credentialInvalid, loginRetryExhausted, parseError } from './errors.js'
11
+ export { getAdapter, registerAdapter, listAdapters, initSdufe } from './registry.js'