@uzqw/shared 0.1.1 → 0.1.3
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/dist/index.d.ts +75 -16
- package/dist/index.js +181 -14
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,29 +1,60 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
+
/** 环境:local 本地测试 / staging 线上测试 / prod 线上正式 */
|
|
4
|
+
type RecolxEnv = "local" | "staging" | "prod";
|
|
3
5
|
interface RecolxConfig {
|
|
4
6
|
/** OAuth client id(developer 客户端注册名) */
|
|
5
7
|
clientId?: string;
|
|
6
|
-
/** OAuth 回调地址(本地
|
|
8
|
+
/** OAuth 回调地址(本地 8199) */
|
|
7
9
|
redirectUri?: string;
|
|
8
|
-
/** token
|
|
10
|
+
/** token 落盘文件名;缺省 tokens-mcp-{env}.json(每环境独立登录态) */
|
|
9
11
|
tokenFile?: string;
|
|
10
|
-
/** token
|
|
12
|
+
/** token 落盘目录;缺省 ~/.recolx */
|
|
11
13
|
tokenDir?: string;
|
|
12
|
-
/**
|
|
14
|
+
/** 环境:local | staging | prod;缺省按 配置文件 > RECOLX_ENV > prod */
|
|
15
|
+
env?: RecolxEnv;
|
|
16
|
+
/** 后端地址覆盖(自定义部署逃生舱);缺省按环境预设 */
|
|
13
17
|
apiBase?: string;
|
|
14
|
-
/**
|
|
18
|
+
/** 授权页地址(developer 模式);缺省 = `${apiBase}/open/oauth/authorize` */
|
|
15
19
|
authorizationUrl?: string;
|
|
16
|
-
/** token/refresh
|
|
20
|
+
/** token/refresh 端点(developer 模式);缺省 = `${apiBase}/open/oauth/token` */
|
|
17
21
|
tokenUrl?: string;
|
|
22
|
+
/** Logto 端点(logto 模式),默认 https://test-auth.recolx.ai */
|
|
23
|
+
logtoEndpoint?: string;
|
|
24
|
+
/** Logto application id(logto 模式;需在 Logto 控制台注册 redirect_uri=http://localhost:8199/auth/callback) */
|
|
25
|
+
logtoClientId?: string;
|
|
18
26
|
/** 附加请求头 */
|
|
19
27
|
extraHeaders?: Record<string, string>;
|
|
20
28
|
/** fetch 实现(测试注入用),默认全局 fetch */
|
|
21
29
|
fetch?: typeof fetch;
|
|
22
30
|
}
|
|
23
|
-
declare const
|
|
31
|
+
declare const DEFAULT_ENV: RecolxEnv;
|
|
24
32
|
declare const DEFAULT_CLIENT_ID: string;
|
|
25
33
|
declare const DEFAULT_REDIRECT_URI = "http://localhost:8199/auth/callback";
|
|
26
|
-
declare
|
|
34
|
+
declare const DEFAULT_LOGTO_ENDPOINT: string;
|
|
35
|
+
declare const DEFAULT_LOGTO_CLIENT_ID: string;
|
|
36
|
+
/** 默认后端地址(prod 预设;RECOLX_API_BASE 可覆盖) */
|
|
37
|
+
declare const DEFAULT_API_BASE = "https://api.recolx.ai/api";
|
|
38
|
+
/** 环境预设:后端地址 + 鉴权模式 + Logto 配置(logto 模式用)。authMode 由环境推导,不再猜 IP。
|
|
39
|
+
* prod 的 Logto 为独立实例,地址/应用 id 未知——留空,logto 模式登录时明确报错,绝不静默连到测试实例。 */
|
|
40
|
+
declare const ENV_PRESETS: Record<RecolxEnv, {
|
|
41
|
+
apiBase: string;
|
|
42
|
+
authMode: "developer" | "logto";
|
|
43
|
+
logtoEndpoint: string;
|
|
44
|
+
logtoClientId: string;
|
|
45
|
+
}>;
|
|
46
|
+
/** 环境配置文件路径(AI set_env / install --env 共用);RECOLX_ENV_CONFIG 为测试钩子。 */
|
|
47
|
+
declare function envConfigPath(): string;
|
|
48
|
+
/** 读取环境配置文件中的 env;无文件/非法值返回 null */
|
|
49
|
+
declare function readEnvConfig(): RecolxEnv | null;
|
|
50
|
+
/** 写入环境配置文件(AI set_env / install --env 共用) */
|
|
51
|
+
declare function writeEnvConfig(env: RecolxEnv): void;
|
|
52
|
+
/** 解析最终环境:实例配置 > 配置文件 > RECOLX_ENV > prod */
|
|
53
|
+
declare function resolveEnv(env?: RecolxEnv): RecolxEnv;
|
|
54
|
+
declare function resolveConfig(config: RecolxConfig): Required<Pick<RecolxConfig, "clientId" | "redirectUri" | "tokenFile" | "apiBase" | "authorizationUrl" | "tokenUrl" | "logtoEndpoint" | "logtoClientId">> & {
|
|
55
|
+
env: RecolxEnv;
|
|
56
|
+
authMode: "developer" | "logto";
|
|
57
|
+
} & Pick<RecolxConfig, "extraHeaders" | "fetch" | "tokenDir">;
|
|
27
58
|
|
|
28
59
|
interface TokenSet {
|
|
29
60
|
access_token: string;
|
|
@@ -57,13 +88,21 @@ declare class OAuth {
|
|
|
57
88
|
readonly tokenStore: TokenStore;
|
|
58
89
|
readonly authorizationUrl: string;
|
|
59
90
|
readonly tokenUrl: string;
|
|
91
|
+
/** developer | logto(resolveConfig 已解析) */
|
|
92
|
+
readonly mode: "developer" | "logto";
|
|
60
93
|
private readonly fetchImpl;
|
|
61
94
|
constructor(config: RecolxConfig);
|
|
62
|
-
/**
|
|
95
|
+
/** logto 模式 client id 校验:缺省抛错(authorize / exchangeCode 共用) */
|
|
96
|
+
private logtoClientId;
|
|
97
|
+
/** logto 模式端点校验:缺省抛错(prod 独立 Logto 未配置时明确报错,不静默连测试实例) */
|
|
98
|
+
private logtoEndpoint;
|
|
99
|
+
/** 构造授权请求(PKCE S256)。extraParams 用于本地联调注入 user_id(developer 模式,env=local 才接受)。 */
|
|
63
100
|
createAuthorizationRequest(extraParams?: Record<string, string>): AuthorizationRequest;
|
|
64
|
-
/**
|
|
101
|
+
/** 授权码换凭证并落盘;失败抛 Error(含状态码与响应体)。
|
|
102
|
+
* developer 模式:直接换 developerOAuth JWT;
|
|
103
|
+
* logto 模式:先换 Logto access token,再 POST /open/oauth/apikey 换 sk- 长期 key(落盘的是 sk- key)。 */
|
|
65
104
|
exchangeCode(code: string, codeVerifier: string): Promise<TokenSet>;
|
|
66
|
-
/** 用 refresh_token 换新 token
|
|
105
|
+
/** 用 refresh_token 换新 token(后端滚动轮换);logto 模式无 refresh(sk- key 长期有效),恒返回 null。 */
|
|
67
106
|
refresh(refreshToken: string): Promise<TokenSet | null>;
|
|
68
107
|
/** 取可用 access token:未过期直接用;过期则滚动刷新;失败返回 null。 */
|
|
69
108
|
getAccessToken(): Promise<string | null>;
|
|
@@ -73,15 +112,27 @@ declare class OAuth {
|
|
|
73
112
|
logout(): Promise<void>;
|
|
74
113
|
}
|
|
75
114
|
|
|
115
|
+
interface EnvState {
|
|
116
|
+
env: RecolxEnv;
|
|
117
|
+
api_base: string;
|
|
118
|
+
auth_mode: "developer" | "logto";
|
|
119
|
+
logged_in: boolean;
|
|
120
|
+
}
|
|
76
121
|
declare class RecolxClient {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
122
|
+
oauth: OAuth;
|
|
123
|
+
apiBase: string;
|
|
124
|
+
env: RecolxEnv;
|
|
125
|
+
extraHeaders: Record<string, string>;
|
|
80
126
|
/** 测试/联调用:设置后不再走 OAuth token 获取 */
|
|
81
127
|
staticToken?: string;
|
|
82
|
-
private
|
|
128
|
+
private fetchImpl;
|
|
83
129
|
constructor(config: RecolxConfig);
|
|
130
|
+
private applyConfig;
|
|
84
131
|
get auth(): OAuth;
|
|
132
|
+
/** 切换环境:写 ~/.recolx/config.json 并即时重建 OAuth/API base;返回新状态 */
|
|
133
|
+
setEnv(env: RecolxEnv): Promise<EnvState>;
|
|
134
|
+
/** 当前环境状态(含登录态) */
|
|
135
|
+
getEnvState(): Promise<EnvState>;
|
|
85
136
|
request(path: string, init?: RequestInit & {
|
|
86
137
|
token?: string;
|
|
87
138
|
}): Promise<unknown>;
|
|
@@ -617,6 +668,14 @@ type RecolxFileDetail = z.infer<typeof FileDetailSchema>;
|
|
|
617
668
|
declare const LoginInput: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
|
|
618
669
|
declare const LogoutInput: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
|
|
619
670
|
declare const GetCurrentUserInput: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
|
|
671
|
+
declare const SetEnvInput: z.ZodObject<{
|
|
672
|
+
env: z.ZodEnum<["local", "staging", "prod"]>;
|
|
673
|
+
}, "strip", z.ZodTypeAny, {
|
|
674
|
+
env: "local" | "staging" | "prod";
|
|
675
|
+
}, {
|
|
676
|
+
env: "local" | "staging" | "prod";
|
|
677
|
+
}>;
|
|
678
|
+
declare const GetEnvInput: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
|
|
620
679
|
declare const ListFilesInput: z.ZodObject<{
|
|
621
680
|
page: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
622
681
|
page_size: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
@@ -705,4 +764,4 @@ interface RunOAuthCallbackOpts {
|
|
|
705
764
|
}
|
|
706
765
|
declare function runOAuthCallback(opts: RunOAuthCallbackOpts): Promise<OAuthCallbackResult>;
|
|
707
766
|
|
|
708
|
-
export { type AuthorizationRequest, CurrentUserSchema, DEFAULT_API_BASE, DEFAULT_CLIENT_ID, DEFAULT_REDIRECT_URI, DeviceSchema, FileDetailSchema, FileListSchema, FileSchema, GetCurrentUserInput, GetFileInput, GetNoteInput, GetTranscriptInput, ListFilesInput, LoginInput, LogoutInput, OAuth, type OAuthCallbackResult, RecolxClient, type RecolxConfig, type RecolxCurrentUser, type RecolxDevice, type RecolxErrorType, type RecolxFile, type RecolxFileDetail, type RecolxFileList, type RecolxSegment, type RecolxSubscription, type RecolxSummary, type RecolxTranscript, type RunOAuthCallbackOpts, SegmentSchema, SubscriptionSchema, SummarySchema, TOKENS_MCP_PATH, type TokenSet, TokenStore, TranscriptSchema, classifyError, errorTypeFromStatus, generateCodeChallenge, generateCodeVerifier, generateState, localDayEnd, localDayStart, parseApiTimestamp, resolveConfig, runOAuthCallback };
|
|
767
|
+
export { type AuthorizationRequest, CurrentUserSchema, DEFAULT_API_BASE, DEFAULT_CLIENT_ID, DEFAULT_ENV, DEFAULT_LOGTO_CLIENT_ID, DEFAULT_LOGTO_ENDPOINT, DEFAULT_REDIRECT_URI, DeviceSchema, ENV_PRESETS, type EnvState, FileDetailSchema, FileListSchema, FileSchema, GetCurrentUserInput, GetEnvInput, GetFileInput, GetNoteInput, GetTranscriptInput, ListFilesInput, LoginInput, LogoutInput, OAuth, type OAuthCallbackResult, RecolxClient, type RecolxConfig, type RecolxCurrentUser, type RecolxDevice, type RecolxEnv, type RecolxErrorType, type RecolxFile, type RecolxFileDetail, type RecolxFileList, type RecolxSegment, type RecolxSubscription, type RecolxSummary, type RecolxTranscript, type RunOAuthCallbackOpts, SegmentSchema, SetEnvInput, SubscriptionSchema, SummarySchema, TOKENS_MCP_PATH, type TokenSet, TokenStore, TranscriptSchema, classifyError, envConfigPath, errorTypeFromStatus, generateCodeChallenge, generateCodeVerifier, generateState, localDayEnd, localDayStart, parseApiTimestamp, readEnvConfig, resolveConfig, resolveEnv, runOAuthCallback, writeEnvConfig };
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,75 @@
|
|
|
1
1
|
// src/config.ts
|
|
2
|
-
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
var DEFAULT_ENV = "prod";
|
|
3
6
|
var DEFAULT_CLIENT_ID = process.env.RECOLX_MCP_CLIENT_ID ?? "recolx-mcp";
|
|
4
7
|
var DEFAULT_REDIRECT_URI = "http://localhost:8199/auth/callback";
|
|
8
|
+
var DEFAULT_LOGTO_ENDPOINT = process.env.RECOLX_LOGTO_ENDPOINT ?? "https://test-auth.recolx.ai";
|
|
9
|
+
var DEFAULT_LOGTO_CLIENT_ID = process.env.RECOLX_LOGTO_CLIENT_ID ?? "7gxhhlqavsei898azq1e9";
|
|
10
|
+
var DEFAULT_API_BASE = "https://api.recolx.ai/api";
|
|
11
|
+
var LEGACY_BAKED_API_BASE = "http://192.168.3.194:8000";
|
|
12
|
+
var ENV_PRESETS = {
|
|
13
|
+
// local 与 staging 共用同一套 Logto(test-auth.recolx.ai):本地测试环境不再走 developer OAuth 单独体系。
|
|
14
|
+
// 前提:本地后端(192.168.3.194:8000)已接真实 Logto,且用户先用 App/Web 登录过(本地库有该 Logto sub)。
|
|
15
|
+
local: {
|
|
16
|
+
apiBase: "http://192.168.3.194:8000",
|
|
17
|
+
authMode: "logto",
|
|
18
|
+
logtoEndpoint: DEFAULT_LOGTO_ENDPOINT,
|
|
19
|
+
logtoClientId: DEFAULT_LOGTO_CLIENT_ID
|
|
20
|
+
},
|
|
21
|
+
staging: {
|
|
22
|
+
apiBase: "https://test-202412-api.recolx.cn/api",
|
|
23
|
+
authMode: "logto",
|
|
24
|
+
logtoEndpoint: DEFAULT_LOGTO_ENDPOINT,
|
|
25
|
+
logtoClientId: DEFAULT_LOGTO_CLIENT_ID
|
|
26
|
+
},
|
|
27
|
+
prod: { apiBase: DEFAULT_API_BASE, authMode: "logto", logtoEndpoint: "", logtoClientId: "" }
|
|
28
|
+
};
|
|
29
|
+
function envConfigPath() {
|
|
30
|
+
return process.env.RECOLX_ENV_CONFIG ?? join(homedir(), ".recolx", "config.json");
|
|
31
|
+
}
|
|
32
|
+
function isEnv(v) {
|
|
33
|
+
return v === "local" || v === "staging" || v === "prod";
|
|
34
|
+
}
|
|
35
|
+
function readEnvConfig() {
|
|
36
|
+
try {
|
|
37
|
+
const raw = JSON.parse(readFileSync(envConfigPath(), "utf8"));
|
|
38
|
+
return isEnv(raw.env) ? raw.env : null;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function writeEnvConfig(env) {
|
|
44
|
+
mkdirSync(join(homedir(), ".recolx"), { recursive: true });
|
|
45
|
+
writeFileSync(envConfigPath(), JSON.stringify({ env }, null, 2) + "\n", "utf8");
|
|
46
|
+
}
|
|
47
|
+
function resolveEnv(env) {
|
|
48
|
+
if (isEnv(env)) return env;
|
|
49
|
+
const fromFile = readEnvConfig();
|
|
50
|
+
if (fromFile) return fromFile;
|
|
51
|
+
const fromVar = process.env.RECOLX_ENV;
|
|
52
|
+
if (isEnv(fromVar)) return fromVar;
|
|
53
|
+
return DEFAULT_ENV;
|
|
54
|
+
}
|
|
5
55
|
function resolveConfig(config) {
|
|
6
|
-
const
|
|
56
|
+
const env = resolveEnv(config.env);
|
|
57
|
+
const preset = ENV_PRESETS[env];
|
|
58
|
+
const envApiBase = process.env.RECOLX_API_BASE?.replace(/\/+$/, "");
|
|
59
|
+
const legacyOverride = env !== "local" && envApiBase === LEGACY_BAKED_API_BASE;
|
|
60
|
+
const apiBase = (config.apiBase ?? (legacyOverride ? preset.apiBase : envApiBase ?? preset.apiBase)).replace(/\/+$/, "");
|
|
61
|
+
const authMode = process.env.RECOLX_AUTH_MODE === "developer" || process.env.RECOLX_AUTH_MODE === "logto" ? process.env.RECOLX_AUTH_MODE : preset.authMode;
|
|
7
62
|
return {
|
|
8
63
|
clientId: config.clientId ?? DEFAULT_CLIENT_ID,
|
|
9
64
|
redirectUri: config.redirectUri ?? DEFAULT_REDIRECT_URI,
|
|
10
|
-
tokenFile: config.tokenFile ??
|
|
65
|
+
tokenFile: config.tokenFile ?? `tokens-mcp-${env}.json`,
|
|
11
66
|
apiBase,
|
|
12
67
|
authorizationUrl: config.authorizationUrl ?? `${apiBase}/open/oauth/authorize`,
|
|
13
68
|
tokenUrl: config.tokenUrl ?? `${apiBase}/open/oauth/token`,
|
|
69
|
+
env,
|
|
70
|
+
authMode,
|
|
71
|
+
logtoEndpoint: (config.logtoEndpoint ?? process.env.RECOLX_LOGTO_ENDPOINT ?? preset.logtoEndpoint).replace(/\/+$/, ""),
|
|
72
|
+
logtoClientId: config.logtoClientId ?? process.env.RECOLX_LOGTO_CLIENT_ID ?? preset.logtoClientId,
|
|
14
73
|
extraHeaders: config.extraHeaders ?? {},
|
|
15
74
|
fetch: config.fetch,
|
|
16
75
|
tokenDir: config.tokenDir
|
|
@@ -19,14 +78,14 @@ function resolveConfig(config) {
|
|
|
19
78
|
|
|
20
79
|
// src/token-store.ts
|
|
21
80
|
import { readFile, writeFile, mkdir, rm } from "fs/promises";
|
|
22
|
-
import { join } from "path";
|
|
23
|
-
import { homedir } from "os";
|
|
81
|
+
import { join as join2 } from "path";
|
|
82
|
+
import { homedir as homedir2 } from "os";
|
|
24
83
|
var TokenStore = class {
|
|
25
84
|
dir;
|
|
26
85
|
path;
|
|
27
|
-
constructor(filename = "tokens-mcp.json", dir =
|
|
86
|
+
constructor(filename = "tokens-mcp.json", dir = join2(homedir2(), ".recolx")) {
|
|
28
87
|
this.dir = dir;
|
|
29
|
-
this.path =
|
|
88
|
+
this.path = join2(dir, filename);
|
|
30
89
|
}
|
|
31
90
|
async save(tokenSet) {
|
|
32
91
|
await mkdir(this.dir, { recursive: true });
|
|
@@ -46,7 +105,7 @@ var TokenStore = class {
|
|
|
46
105
|
}
|
|
47
106
|
}
|
|
48
107
|
};
|
|
49
|
-
var TOKENS_MCP_PATH =
|
|
108
|
+
var TOKENS_MCP_PATH = join2(homedir2(), ".recolx", "tokens-mcp.json");
|
|
50
109
|
|
|
51
110
|
// src/oauth.ts
|
|
52
111
|
import { createHash, randomBytes } from "crypto";
|
|
@@ -73,17 +132,52 @@ var OAuth = class {
|
|
|
73
132
|
tokenStore;
|
|
74
133
|
authorizationUrl;
|
|
75
134
|
tokenUrl;
|
|
135
|
+
/** developer | logto(resolveConfig 已解析) */
|
|
136
|
+
mode;
|
|
76
137
|
fetchImpl;
|
|
77
138
|
constructor(config) {
|
|
78
139
|
this.config = resolveConfig(config);
|
|
79
140
|
this.tokenStore = new TokenStore(this.config.tokenFile, this.config.tokenDir);
|
|
80
141
|
this.authorizationUrl = this.config.authorizationUrl;
|
|
81
142
|
this.tokenUrl = this.config.tokenUrl;
|
|
143
|
+
this.mode = this.config.authMode;
|
|
82
144
|
this.fetchImpl = this.config.fetch ?? fetch;
|
|
83
145
|
}
|
|
84
|
-
/**
|
|
146
|
+
/** logto 模式 client id 校验:缺省抛错(authorize / exchangeCode 共用) */
|
|
147
|
+
logtoClientId() {
|
|
148
|
+
const id = this.config.logtoClientId;
|
|
149
|
+
if (!id) {
|
|
150
|
+
throw new Error("logto \u6A21\u5F0F\u7F3A\u5C11 client id\uFF1A\u8BF7\u8BBE\u7F6E RECOLX_LOGTO_CLIENT_ID\uFF08Logto \u63A7\u5236\u53F0\u6CE8\u518C\u7684 application id\uFF09");
|
|
151
|
+
}
|
|
152
|
+
return id;
|
|
153
|
+
}
|
|
154
|
+
/** logto 模式端点校验:缺省抛错(prod 独立 Logto 未配置时明确报错,不静默连测试实例) */
|
|
155
|
+
logtoEndpoint() {
|
|
156
|
+
const ep = this.config.logtoEndpoint;
|
|
157
|
+
if (!ep) {
|
|
158
|
+
throw new Error("logto \u6A21\u5F0F\u7F3A\u5C11 Logto \u7AEF\u70B9\uFF1A\u5F53\u524D\u73AF\u5883\u7684 Logto \u5C1A\u672A\u914D\u7F6E\uFF08prod \u9700\u8BBE\u7F6E RECOLX_LOGTO_ENDPOINT\uFF09");
|
|
159
|
+
}
|
|
160
|
+
return ep;
|
|
161
|
+
}
|
|
162
|
+
/** 构造授权请求(PKCE S256)。extraParams 用于本地联调注入 user_id(developer 模式,env=local 才接受)。 */
|
|
85
163
|
createAuthorizationRequest(extraParams = {}) {
|
|
86
164
|
const codeVerifier = generateCodeVerifier();
|
|
165
|
+
const state = generateState();
|
|
166
|
+
if (this.mode === "logto") {
|
|
167
|
+
const { redirectUri } = this.config;
|
|
168
|
+
const logtoClientId = this.logtoClientId();
|
|
169
|
+
const logtoEndpoint = this.logtoEndpoint();
|
|
170
|
+
const params2 = new URLSearchParams({
|
|
171
|
+
client_id: logtoClientId,
|
|
172
|
+
redirect_uri: redirectUri,
|
|
173
|
+
response_type: "code",
|
|
174
|
+
scope: "openid profile email",
|
|
175
|
+
code_challenge: generateCodeChallenge(codeVerifier),
|
|
176
|
+
code_challenge_method: "S256",
|
|
177
|
+
state
|
|
178
|
+
});
|
|
179
|
+
return { url: `${logtoEndpoint}/oidc/auth?${params2.toString()}`, codeVerifier, state };
|
|
180
|
+
}
|
|
87
181
|
const params = new URLSearchParams({
|
|
88
182
|
client_id: this.config.clientId,
|
|
89
183
|
redirect_uri: this.config.redirectUri,
|
|
@@ -91,13 +185,55 @@ var OAuth = class {
|
|
|
91
185
|
scope: "read",
|
|
92
186
|
code_challenge: generateCodeChallenge(codeVerifier),
|
|
93
187
|
code_challenge_method: "S256",
|
|
94
|
-
state
|
|
188
|
+
state,
|
|
95
189
|
...extraParams
|
|
96
190
|
});
|
|
97
|
-
return { url: `${this.authorizationUrl}?${params.toString()}`, codeVerifier, state
|
|
191
|
+
return { url: `${this.authorizationUrl}?${params.toString()}`, codeVerifier, state };
|
|
98
192
|
}
|
|
99
|
-
/**
|
|
193
|
+
/** 授权码换凭证并落盘;失败抛 Error(含状态码与响应体)。
|
|
194
|
+
* developer 模式:直接换 developerOAuth JWT;
|
|
195
|
+
* logto 模式:先换 Logto access token,再 POST /open/oauth/apikey 换 sk- 长期 key(落盘的是 sk- key)。 */
|
|
100
196
|
async exchangeCode(code, codeVerifier) {
|
|
197
|
+
if (this.mode === "logto") {
|
|
198
|
+
const { redirectUri } = this.config;
|
|
199
|
+
const logtoClientId = this.logtoClientId();
|
|
200
|
+
const logtoEndpoint = this.logtoEndpoint();
|
|
201
|
+
const tokenRes = await this.fetchImpl(`${logtoEndpoint}/oidc/token`, {
|
|
202
|
+
method: "POST",
|
|
203
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
204
|
+
body: new URLSearchParams({
|
|
205
|
+
grant_type: "authorization_code",
|
|
206
|
+
code,
|
|
207
|
+
code_verifier: codeVerifier,
|
|
208
|
+
client_id: logtoClientId,
|
|
209
|
+
redirect_uri: redirectUri
|
|
210
|
+
})
|
|
211
|
+
});
|
|
212
|
+
if (!tokenRes.ok) {
|
|
213
|
+
throw new Error(`Logto token exchange failed: ${tokenRes.status} ${await tokenRes.text()}`);
|
|
214
|
+
}
|
|
215
|
+
const tokenBody = await tokenRes.json();
|
|
216
|
+
const logtoToken = tokenBody.access_token;
|
|
217
|
+
if (typeof logtoToken !== "string" || logtoToken.length === 0) {
|
|
218
|
+
throw new Error("Logto token exchange: missing access_token");
|
|
219
|
+
}
|
|
220
|
+
const keyRes = await this.fetchImpl(`${this.config.apiBase}/open/oauth/apikey`, {
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
223
|
+
body: new URLSearchParams({ logto_token: logtoToken })
|
|
224
|
+
});
|
|
225
|
+
if (!keyRes.ok) {
|
|
226
|
+
throw new Error(`Issue API key failed: ${keyRes.status} ${await keyRes.text()}`);
|
|
227
|
+
}
|
|
228
|
+
const body = await keyRes.json();
|
|
229
|
+
const apiKey = typeof body.api_key === "string" ? body.api_key : "";
|
|
230
|
+
if (!apiKey) {
|
|
231
|
+
throw new Error("Issue API key: missing api_key in response");
|
|
232
|
+
}
|
|
233
|
+
const tokenSet2 = { access_token: apiKey, token_type: "Bearer" };
|
|
234
|
+
await this.tokenStore.save(tokenSet2);
|
|
235
|
+
return tokenSet2;
|
|
236
|
+
}
|
|
101
237
|
const res = await this.fetchImpl(this.tokenUrl, {
|
|
102
238
|
method: "POST",
|
|
103
239
|
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
@@ -110,8 +246,9 @@ var OAuth = class {
|
|
|
110
246
|
await this.tokenStore.save(tokenSet);
|
|
111
247
|
return tokenSet;
|
|
112
248
|
}
|
|
113
|
-
/** 用 refresh_token 换新 token
|
|
249
|
+
/** 用 refresh_token 换新 token(后端滚动轮换);logto 模式无 refresh(sk- key 长期有效),恒返回 null。 */
|
|
114
250
|
async refresh(refreshToken) {
|
|
251
|
+
if (this.mode === "logto") return null;
|
|
115
252
|
const res = await this.fetchImpl(this.tokenUrl, {
|
|
116
253
|
method: "POST",
|
|
117
254
|
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
@@ -159,20 +296,36 @@ var OAuth = class {
|
|
|
159
296
|
var RecolxClient = class {
|
|
160
297
|
oauth;
|
|
161
298
|
apiBase;
|
|
299
|
+
env;
|
|
162
300
|
extraHeaders;
|
|
163
301
|
/** 测试/联调用:设置后不再走 OAuth token 获取 */
|
|
164
302
|
staticToken;
|
|
165
303
|
fetchImpl;
|
|
166
304
|
constructor(config) {
|
|
305
|
+
this.applyConfig(config);
|
|
306
|
+
}
|
|
307
|
+
applyConfig(config) {
|
|
167
308
|
const resolved = resolveConfig(config);
|
|
168
309
|
this.oauth = new OAuth(resolved);
|
|
169
310
|
this.apiBase = resolved.apiBase;
|
|
311
|
+
this.env = resolved.env;
|
|
170
312
|
this.extraHeaders = resolved.extraHeaders ?? {};
|
|
171
313
|
this.fetchImpl = resolved.fetch ?? fetch;
|
|
172
314
|
}
|
|
173
315
|
get auth() {
|
|
174
316
|
return this.oauth;
|
|
175
317
|
}
|
|
318
|
+
/** 切换环境:写 ~/.recolx/config.json 并即时重建 OAuth/API base;返回新状态 */
|
|
319
|
+
async setEnv(env) {
|
|
320
|
+
writeEnvConfig(env);
|
|
321
|
+
this.applyConfig({});
|
|
322
|
+
return this.getEnvState();
|
|
323
|
+
}
|
|
324
|
+
/** 当前环境状态(含登录态) */
|
|
325
|
+
async getEnvState() {
|
|
326
|
+
const token = await this.oauth.getAccessToken();
|
|
327
|
+
return { env: this.env, api_base: this.apiBase, auth_mode: this.oauth.mode, logged_in: token !== null };
|
|
328
|
+
}
|
|
176
329
|
async request(path, init) {
|
|
177
330
|
const token = init?.token ?? this.staticToken ?? await this.oauth.getAccessToken();
|
|
178
331
|
if (!token) {
|
|
@@ -308,6 +461,10 @@ var FileDetailSchema = FileSchema.extend({
|
|
|
308
461
|
var LoginInput = z.object({}).optional();
|
|
309
462
|
var LogoutInput = z.object({}).optional();
|
|
310
463
|
var GetCurrentUserInput = z.object({}).optional();
|
|
464
|
+
var SetEnvInput = z.object({
|
|
465
|
+
env: z.enum(["local", "staging", "prod"]).describe("\u76EE\u6807\u73AF\u5883\uFF1Alocal \u672C\u5730\u6D4B\u8BD5 / staging \u7EBF\u4E0A\u6D4B\u8BD5 / prod \u7EBF\u4E0A\u6B63\u5F0F")
|
|
466
|
+
});
|
|
467
|
+
var GetEnvInput = z.object({}).optional();
|
|
311
468
|
var ListFilesInput = z.object({
|
|
312
469
|
page: z.number().int().min(1).optional().default(1),
|
|
313
470
|
page_size: z.number().int().min(1).max(100).optional().default(20),
|
|
@@ -460,12 +617,17 @@ export {
|
|
|
460
617
|
CurrentUserSchema,
|
|
461
618
|
DEFAULT_API_BASE,
|
|
462
619
|
DEFAULT_CLIENT_ID,
|
|
620
|
+
DEFAULT_ENV,
|
|
621
|
+
DEFAULT_LOGTO_CLIENT_ID,
|
|
622
|
+
DEFAULT_LOGTO_ENDPOINT,
|
|
463
623
|
DEFAULT_REDIRECT_URI,
|
|
464
624
|
DeviceSchema,
|
|
625
|
+
ENV_PRESETS,
|
|
465
626
|
FileDetailSchema,
|
|
466
627
|
FileListSchema,
|
|
467
628
|
FileSchema,
|
|
468
629
|
GetCurrentUserInput,
|
|
630
|
+
GetEnvInput,
|
|
469
631
|
GetFileInput,
|
|
470
632
|
GetNoteInput,
|
|
471
633
|
GetTranscriptInput,
|
|
@@ -475,12 +637,14 @@ export {
|
|
|
475
637
|
OAuth,
|
|
476
638
|
RecolxClient,
|
|
477
639
|
SegmentSchema,
|
|
640
|
+
SetEnvInput,
|
|
478
641
|
SubscriptionSchema,
|
|
479
642
|
SummarySchema,
|
|
480
643
|
TOKENS_MCP_PATH,
|
|
481
644
|
TokenStore,
|
|
482
645
|
TranscriptSchema,
|
|
483
646
|
classifyError,
|
|
647
|
+
envConfigPath,
|
|
484
648
|
errorTypeFromStatus,
|
|
485
649
|
generateCodeChallenge,
|
|
486
650
|
generateCodeVerifier,
|
|
@@ -488,6 +652,9 @@ export {
|
|
|
488
652
|
localDayEnd,
|
|
489
653
|
localDayStart,
|
|
490
654
|
parseApiTimestamp,
|
|
655
|
+
readEnvConfig,
|
|
491
656
|
resolveConfig,
|
|
492
|
-
|
|
657
|
+
resolveEnv,
|
|
658
|
+
runOAuthCallback,
|
|
659
|
+
writeEnvConfig
|
|
493
660
|
};
|