ai-world-sdk 1.2.2 → 1.2.5
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/__tests__/login.test.d.ts +1 -0
- package/dist/__tests__/login.test.js +170 -0
- package/dist/config.d.ts +31 -3
- package/dist/config.js +202 -28
- package/dist/index.d.ts +1 -1
- package/dist/login.d.ts +33 -0
- package/dist/login.js +272 -0
- package/dist/vite-plugin.d.ts +14 -0
- package/dist/vite-plugin.js +83 -0
- package/package.json +11 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const http = __importStar(require("http"));
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const login_1 = require("../login");
|
|
40
|
+
const TEST_DIR = path.join(__dirname, "__login_test_tmp__");
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
if (!fs.existsSync(TEST_DIR)) {
|
|
43
|
+
fs.mkdirSync(TEST_DIR, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
if (fs.existsSync(TEST_DIR)) {
|
|
48
|
+
fs.rmSync(TEST_DIR, { recursive: true, force: true });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
describe("readEnvLocal", () => {
|
|
52
|
+
it("returns empty object when file does not exist", () => {
|
|
53
|
+
const result = (0, login_1.readEnvLocal)(TEST_DIR);
|
|
54
|
+
expect(result).toEqual({});
|
|
55
|
+
});
|
|
56
|
+
it("parses key=value pairs from .env.local", () => {
|
|
57
|
+
const envPath = path.join(TEST_DIR, ".env.local");
|
|
58
|
+
fs.writeFileSync(envPath, "DEBUG=true\nDEBUG_TOKEN=abc123\n# comment\nPLUGIN_ID=test\n");
|
|
59
|
+
const result = (0, login_1.readEnvLocal)(TEST_DIR);
|
|
60
|
+
expect(result).toEqual({
|
|
61
|
+
DEBUG: "true",
|
|
62
|
+
DEBUG_TOKEN: "abc123",
|
|
63
|
+
PLUGIN_ID: "test",
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
it("skips blank lines and comments", () => {
|
|
67
|
+
const envPath = path.join(TEST_DIR, ".env.local");
|
|
68
|
+
fs.writeFileSync(envPath, "\n# header\n\nKEY=value\n\n");
|
|
69
|
+
const result = (0, login_1.readEnvLocal)(TEST_DIR);
|
|
70
|
+
expect(result).toEqual({ KEY: "value" });
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe("writeEnvLocal", () => {
|
|
74
|
+
it("creates .env.local when it does not exist", () => {
|
|
75
|
+
(0, login_1.writeEnvLocal)(TEST_DIR, { DEBUG: "true", DEBUG_TOKEN: "token123" });
|
|
76
|
+
const envPath = path.join(TEST_DIR, ".env.local");
|
|
77
|
+
expect(fs.existsSync(envPath)).toBe(true);
|
|
78
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
79
|
+
expect(content).toContain("DEBUG=true");
|
|
80
|
+
expect(content).toContain("DEBUG_TOKEN=token123");
|
|
81
|
+
});
|
|
82
|
+
it("updates existing keys and preserves others", () => {
|
|
83
|
+
const envPath = path.join(TEST_DIR, ".env.local");
|
|
84
|
+
fs.writeFileSync(envPath, "# comment\nDEBUG=false\nPLUGIN_ID=old\n");
|
|
85
|
+
(0, login_1.writeEnvLocal)(TEST_DIR, { DEBUG: "true", DEBUG_TOKEN: "newtoken" });
|
|
86
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
87
|
+
expect(content).toContain("# comment");
|
|
88
|
+
expect(content).toContain("DEBUG=true");
|
|
89
|
+
expect(content).toContain("PLUGIN_ID=old");
|
|
90
|
+
expect(content).toContain("DEBUG_TOKEN=newtoken");
|
|
91
|
+
expect(content).not.toContain("DEBUG=false");
|
|
92
|
+
});
|
|
93
|
+
it("does not duplicate keys on repeated writes", () => {
|
|
94
|
+
(0, login_1.writeEnvLocal)(TEST_DIR, { DEBUG_TOKEN: "v1" });
|
|
95
|
+
(0, login_1.writeEnvLocal)(TEST_DIR, { DEBUG_TOKEN: "v2" });
|
|
96
|
+
const content = fs.readFileSync(path.join(TEST_DIR, ".env.local"), "utf-8");
|
|
97
|
+
const matches = content.match(/DEBUG_TOKEN/g);
|
|
98
|
+
expect(matches).toHaveLength(1);
|
|
99
|
+
expect(content).toContain("DEBUG_TOKEN=v2");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe("resolvePluginId", () => {
|
|
103
|
+
it("returns plugin_id from plugin.json when it exists", () => {
|
|
104
|
+
const pluginJson = path.join(TEST_DIR, "plugin.json");
|
|
105
|
+
fs.writeFileSync(pluginJson, JSON.stringify({ plugin_id: "my-plugin", name: "My Plugin" }));
|
|
106
|
+
expect((0, login_1.resolvePluginId)(TEST_DIR)).toBe("my-plugin");
|
|
107
|
+
});
|
|
108
|
+
it("falls back to package.json name when plugin.json does not exist", () => {
|
|
109
|
+
fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ name: "my-pkg" }));
|
|
110
|
+
expect((0, login_1.resolvePluginId)(TEST_DIR)).toBe("my-pkg");
|
|
111
|
+
});
|
|
112
|
+
it("falls back to package.json name when plugin.json has no plugin_id", () => {
|
|
113
|
+
fs.writeFileSync(path.join(TEST_DIR, "plugin.json"), JSON.stringify({ name: "No ID" }));
|
|
114
|
+
fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ name: "fallback-pkg" }));
|
|
115
|
+
expect((0, login_1.resolvePluginId)(TEST_DIR)).toBe("fallback-pkg");
|
|
116
|
+
});
|
|
117
|
+
it("falls back to directory name when neither plugin.json nor package.json exist", () => {
|
|
118
|
+
expect((0, login_1.resolvePluginId)(TEST_DIR)).toBe(path.basename(TEST_DIR));
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe("validateToken", () => {
|
|
122
|
+
let server = null;
|
|
123
|
+
let port;
|
|
124
|
+
afterEach((done) => {
|
|
125
|
+
if (server && server.listening) {
|
|
126
|
+
server.close(() => {
|
|
127
|
+
server = null;
|
|
128
|
+
done();
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
server = null;
|
|
133
|
+
done();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
it("returns true for valid token", async () => {
|
|
137
|
+
server = http.createServer((req, res) => {
|
|
138
|
+
if (req.headers.authorization === "Bearer valid-token") {
|
|
139
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
140
|
+
res.end(JSON.stringify({ id: 1, email: "test@test.com" }));
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
res.writeHead(401);
|
|
144
|
+
res.end();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
await new Promise((resolve) => {
|
|
148
|
+
server.listen(0, () => resolve());
|
|
149
|
+
});
|
|
150
|
+
port = server.address().port;
|
|
151
|
+
const result = await (0, login_1.validateToken)(`http://localhost:${port}`, "valid-token");
|
|
152
|
+
expect(result).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
it("returns false for invalid token", async () => {
|
|
155
|
+
server = http.createServer((_req, res) => {
|
|
156
|
+
res.writeHead(401);
|
|
157
|
+
res.end();
|
|
158
|
+
});
|
|
159
|
+
await new Promise((resolve) => {
|
|
160
|
+
server.listen(0, () => resolve());
|
|
161
|
+
});
|
|
162
|
+
port = server.address().port;
|
|
163
|
+
const result = await (0, login_1.validateToken)(`http://localhost:${port}`, "bad-token");
|
|
164
|
+
expect(result).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
it("returns false when server is unreachable", async () => {
|
|
167
|
+
const result = await (0, login_1.validateToken)("http://localhost:19999", "any-token");
|
|
168
|
+
expect(result).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
});
|
package/dist/config.d.ts
CHANGED
|
@@ -9,13 +9,20 @@
|
|
|
9
9
|
*
|
|
10
10
|
* 注意: {VERSION} 占位符会在构建时被替换为实际版本号
|
|
11
11
|
*/
|
|
12
|
-
export declare const SDK_SIGNATURE = "AI_WORLD_SDK_V:1.2.
|
|
12
|
+
export declare const SDK_SIGNATURE = "AI_WORLD_SDK_V:1.2.5";
|
|
13
13
|
/**
|
|
14
14
|
* 版本兼容性错误
|
|
15
15
|
*/
|
|
16
16
|
export declare class VersionCompatibilityError extends Error {
|
|
17
17
|
constructor(message: string);
|
|
18
18
|
}
|
|
19
|
+
export interface AuthenticatedUser {
|
|
20
|
+
id: number;
|
|
21
|
+
email?: string;
|
|
22
|
+
full_name?: string;
|
|
23
|
+
avatar_url?: string;
|
|
24
|
+
feishu_user_id?: string;
|
|
25
|
+
}
|
|
19
26
|
declare class SDKConfig {
|
|
20
27
|
private _baseUrl;
|
|
21
28
|
private _token;
|
|
@@ -24,8 +31,11 @@ declare class SDKConfig {
|
|
|
24
31
|
private _pluginId;
|
|
25
32
|
private _versionCompatible;
|
|
26
33
|
private _versionCheckPromise;
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
private _authenticated;
|
|
35
|
+
private _authCheckPromise;
|
|
36
|
+
private _currentUser;
|
|
37
|
+
readonly sdkSignature = "AI_WORLD_SDK_V:1.2.5";
|
|
38
|
+
readonly sdkVersion = "1.2.5";
|
|
29
39
|
constructor();
|
|
30
40
|
/**
|
|
31
41
|
* Set global base URL
|
|
@@ -77,6 +87,24 @@ declare class SDKConfig {
|
|
|
77
87
|
* 获取插件 ID(从 window.__INITIAL_DATA__ 自动获取或手动设置)
|
|
78
88
|
*/
|
|
79
89
|
getPluginId(): string | null;
|
|
90
|
+
/**
|
|
91
|
+
* 检查当前 token 是否有效,获取登录用户信息
|
|
92
|
+
*/
|
|
93
|
+
checkAuthentication(): Promise<boolean>;
|
|
94
|
+
private _performAuthCheck;
|
|
95
|
+
private _nodeAuthCheck;
|
|
96
|
+
/**
|
|
97
|
+
* 获取当前登录状态
|
|
98
|
+
*/
|
|
99
|
+
isAuthenticated(): boolean | null;
|
|
100
|
+
/**
|
|
101
|
+
* 获取当前登录用户信息(需先完成 checkAuthentication)
|
|
102
|
+
*/
|
|
103
|
+
getCurrentUser(): AuthenticatedUser | null;
|
|
104
|
+
/**
|
|
105
|
+
* 等待登录检查完成并确保已登录,未登录则抛出错误
|
|
106
|
+
*/
|
|
107
|
+
ensureAuthenticated(): Promise<AuthenticatedUser>;
|
|
80
108
|
/**
|
|
81
109
|
* Check version compatibility with backend
|
|
82
110
|
* 检查与后端的版本兼容性
|
package/dist/config.js
CHANGED
|
@@ -7,7 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.sdkConfig = exports.VersionCompatibilityError = exports.SDK_SIGNATURE = void 0;
|
|
8
8
|
// SDK 版本号(构建时自动从 package.json 更新)
|
|
9
9
|
// 此版本号会在运行 npm run build 时自动从 package.json 读取并更新
|
|
10
|
-
const SDK_VERSION = "1.2.
|
|
10
|
+
const SDK_VERSION = "1.2.5";
|
|
11
11
|
/**
|
|
12
12
|
* SDK 特征码 - 用于在构建后的 JS 文件中识别 SDK 版本
|
|
13
13
|
* 格式: AI_WORLD_SDK_V:版本号
|
|
@@ -15,7 +15,7 @@ const SDK_VERSION = "1.2.2";
|
|
|
15
15
|
*
|
|
16
16
|
* 注意: {VERSION} 占位符会在构建时被替换为实际版本号
|
|
17
17
|
*/
|
|
18
|
-
exports.SDK_SIGNATURE = "AI_WORLD_SDK_V:1.2.
|
|
18
|
+
exports.SDK_SIGNATURE = "AI_WORLD_SDK_V:1.2.5";
|
|
19
19
|
/**
|
|
20
20
|
* 版本兼容性错误
|
|
21
21
|
*/
|
|
@@ -33,48 +33,100 @@ class SDKConfig {
|
|
|
33
33
|
this._headers = {};
|
|
34
34
|
this._debug = false;
|
|
35
35
|
this._pluginId = null;
|
|
36
|
-
this._versionCompatible = null;
|
|
37
|
-
this._versionCheckPromise = null;
|
|
38
|
-
//
|
|
36
|
+
this._versionCompatible = null;
|
|
37
|
+
this._versionCheckPromise = null;
|
|
38
|
+
this._authenticated = null; // null = 未检查
|
|
39
|
+
this._authCheckPromise = null;
|
|
40
|
+
this._currentUser = null;
|
|
39
41
|
this.sdkSignature = exports.SDK_SIGNATURE;
|
|
40
42
|
this.sdkVersion = SDK_VERSION;
|
|
41
43
|
// 在全局对象上注册 SDK 版本信息(用于后端扫描检测)
|
|
42
44
|
if (typeof globalThis !== "undefined") {
|
|
43
45
|
globalThis.__AI_WORLD_SDK_VERSION__ = SDK_VERSION;
|
|
44
46
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
48
|
+
if (isBrowser) {
|
|
49
|
+
// 浏览器环境:从 cookie 获取 token
|
|
50
|
+
try {
|
|
51
|
+
const tokenRow = document.cookie.split('; ').find(row => row.startsWith('auth_token='));
|
|
52
|
+
if (tokenRow) {
|
|
53
|
+
const tokenValue = tokenRow.split('=')[1];
|
|
54
|
+
this._token = decodeURIComponent(tokenValue);
|
|
55
|
+
}
|
|
53
56
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (initialData.plugin_id) {
|
|
66
|
-
this._headers['X-Plugin-Id'] = this._pluginId = initialData.plugin_id;
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.warn('Failed to read token from cookie:', error);
|
|
59
|
+
}
|
|
60
|
+
this._baseUrl = window.location.origin;
|
|
61
|
+
// 从 window.__INITIAL_DATA__ 获取 plugin_id(SSR 插件环境)
|
|
62
|
+
try {
|
|
63
|
+
if (window.__INITIAL_DATA__) {
|
|
64
|
+
const initialData = window.__INITIAL_DATA__;
|
|
65
|
+
if (initialData.plugin_id) {
|
|
66
|
+
this._headers['X-Plugin-Id'] = this._pluginId = initialData.plugin_id;
|
|
67
|
+
}
|
|
67
68
|
}
|
|
68
69
|
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.warn('Failed to read plugin_id from __INITIAL_DATA__:', error);
|
|
72
|
+
}
|
|
73
|
+
// Vite dev 模式:从 aiWorldPlugin 注入的 window.__AI_WORLD_CONFIG__ 读取
|
|
74
|
+
try {
|
|
75
|
+
const aiConfig = window.__AI_WORLD_CONFIG__;
|
|
76
|
+
if (aiConfig) {
|
|
77
|
+
if (!this._token && aiConfig.token) {
|
|
78
|
+
this._token = aiConfig.token;
|
|
79
|
+
}
|
|
80
|
+
if (aiConfig.debug) {
|
|
81
|
+
this._debug = true;
|
|
82
|
+
}
|
|
83
|
+
if (!this._pluginId && aiConfig.pluginId) {
|
|
84
|
+
this._pluginId = aiConfig.pluginId;
|
|
85
|
+
this._headers['X-Plugin-Id'] = aiConfig.pluginId;
|
|
86
|
+
}
|
|
87
|
+
if (aiConfig.baseUrl) {
|
|
88
|
+
this._baseUrl = aiConfig.baseUrl;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// __AI_WORLD_CONFIG__ not available
|
|
94
|
+
}
|
|
69
95
|
}
|
|
70
|
-
|
|
71
|
-
|
|
96
|
+
else {
|
|
97
|
+
// 非浏览器环境(Node.js / Vite dev):从 process.env 读取
|
|
98
|
+
try {
|
|
99
|
+
const debugToken = typeof process !== 'undefined' && process.env?.DEBUG_TOKEN;
|
|
100
|
+
if (debugToken) {
|
|
101
|
+
this._token = debugToken;
|
|
102
|
+
}
|
|
103
|
+
const debugFlag = typeof process !== 'undefined' && process.env?.DEBUG;
|
|
104
|
+
if (debugFlag === 'true') {
|
|
105
|
+
this._debug = true;
|
|
106
|
+
}
|
|
107
|
+
const pluginId = typeof process !== 'undefined' && process.env?.PLUGIN_ID;
|
|
108
|
+
if (pluginId) {
|
|
109
|
+
this._pluginId = pluginId;
|
|
110
|
+
this._headers['X-Plugin-Id'] = pluginId;
|
|
111
|
+
}
|
|
112
|
+
const baseUrl = typeof process !== 'undefined' && process.env?.AI_WORLD_BASE_URL;
|
|
113
|
+
if (baseUrl) {
|
|
114
|
+
this._baseUrl = baseUrl.replace(/\/$/, "");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// process.env may not be available in some runtimes
|
|
119
|
+
}
|
|
72
120
|
}
|
|
73
121
|
this._headers['X-SDK-Version'] = SDK_VERSION;
|
|
74
122
|
// 自动检查版本兼容性(异步,不阻塞初始化)
|
|
75
123
|
this.checkVersionCompatibility().catch((error) => {
|
|
76
124
|
console.warn('版本兼容性检查失败:', error);
|
|
77
125
|
});
|
|
126
|
+
// 自动检查登录状态(异步,不阻塞初始化)
|
|
127
|
+
this.checkAuthentication().catch(() => {
|
|
128
|
+
// 静默处理,详细日志在 checkAuthentication 内部
|
|
129
|
+
});
|
|
78
130
|
}
|
|
79
131
|
/**
|
|
80
132
|
* Set global base URL
|
|
@@ -146,6 +198,128 @@ class SDKConfig {
|
|
|
146
198
|
getPluginId() {
|
|
147
199
|
return this._pluginId;
|
|
148
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* 检查当前 token 是否有效,获取登录用户信息
|
|
203
|
+
*/
|
|
204
|
+
async checkAuthentication() {
|
|
205
|
+
if (this._authenticated !== null) {
|
|
206
|
+
return this._authenticated;
|
|
207
|
+
}
|
|
208
|
+
if (this._authCheckPromise) {
|
|
209
|
+
return this._authCheckPromise;
|
|
210
|
+
}
|
|
211
|
+
this._authCheckPromise = this._performAuthCheck();
|
|
212
|
+
const result = await this._authCheckPromise;
|
|
213
|
+
this._authCheckPromise = null;
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
async _performAuthCheck() {
|
|
217
|
+
if (!this._token) {
|
|
218
|
+
this._authenticated = false;
|
|
219
|
+
console.warn('[ai-world-sdk] 未检测到登录 token,请先登录。Vite 项目请在 vite.config 中添加 aiWorldPlugin()');
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
const baseUrl = this._baseUrl || (typeof window !== 'undefined' ? window.location.origin : '');
|
|
223
|
+
if (!baseUrl) {
|
|
224
|
+
this._authenticated = false;
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
229
|
+
let ok = false;
|
|
230
|
+
let user = null;
|
|
231
|
+
if (isBrowser) {
|
|
232
|
+
const response = await fetch(`${baseUrl}/api/auth/me`, {
|
|
233
|
+
method: 'GET',
|
|
234
|
+
headers: {
|
|
235
|
+
'Authorization': `Bearer ${this._token}`,
|
|
236
|
+
'Content-Type': 'application/json',
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
if (response.ok) {
|
|
240
|
+
user = await response.json();
|
|
241
|
+
ok = true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
// Node.js 环境:使用 https/http 模块,支持自签名证书
|
|
246
|
+
const result = await this._nodeAuthCheck(baseUrl, this._token);
|
|
247
|
+
ok = result.ok;
|
|
248
|
+
user = result.user;
|
|
249
|
+
}
|
|
250
|
+
if (ok && user) {
|
|
251
|
+
this._authenticated = true;
|
|
252
|
+
this._currentUser = user;
|
|
253
|
+
if (this._debug) {
|
|
254
|
+
console.log(`[ai-world-sdk] 已登录: ${user.full_name || user.email || user.id}`);
|
|
255
|
+
}
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
this._authenticated = false;
|
|
259
|
+
this._token = null;
|
|
260
|
+
console.warn('[ai-world-sdk] Token 已过期或无效,请重新登录');
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
this._authenticated = false;
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
_nodeAuthCheck(baseUrl, token) {
|
|
269
|
+
return new Promise((resolve) => {
|
|
270
|
+
try {
|
|
271
|
+
const url = new URL(`${baseUrl}/api/auth/me`);
|
|
272
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
273
|
+
const mod = url.protocol === 'https:' ? require('https') : require('http');
|
|
274
|
+
const req = mod.get(url.href, {
|
|
275
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
276
|
+
rejectUnauthorized: false,
|
|
277
|
+
}, (res) => {
|
|
278
|
+
let body = '';
|
|
279
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
280
|
+
res.on('end', () => {
|
|
281
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
282
|
+
try {
|
|
283
|
+
resolve({ ok: true, user: JSON.parse(body) });
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
resolve({ ok: true, user: null });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
resolve({ ok: false, user: null });
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
req.on('error', () => resolve({ ok: false, user: null }));
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
resolve({ ok: false, user: null });
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* 获取当前登录状态
|
|
303
|
+
*/
|
|
304
|
+
isAuthenticated() {
|
|
305
|
+
return this._authenticated;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* 获取当前登录用户信息(需先完成 checkAuthentication)
|
|
309
|
+
*/
|
|
310
|
+
getCurrentUser() {
|
|
311
|
+
return this._currentUser;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* 等待登录检查完成并确保已登录,未登录则抛出错误
|
|
315
|
+
*/
|
|
316
|
+
async ensureAuthenticated() {
|
|
317
|
+
const ok = await this.checkAuthentication();
|
|
318
|
+
if (!ok || !this._currentUser) {
|
|
319
|
+
throw new Error('未登录,请先完成登录。Vite 项目请在 vite.config 中添加 aiWorldPlugin()');
|
|
320
|
+
}
|
|
321
|
+
return this._currentUser;
|
|
322
|
+
}
|
|
149
323
|
/**
|
|
150
324
|
* Check version compatibility with backend
|
|
151
325
|
* 检查与后端的版本兼容性
|
package/dist/index.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export { DownloadClient, type DownloadConfig, type DownloadOptions, type StreamD
|
|
|
30
30
|
export { MinioStorageClient, type MinioStorageConfig, type UploadResponse, type ObjectInfo, type PresignedUrlResponse, type UploadOptions, type ListOptions, } from "./minio";
|
|
31
31
|
export { ResourceClient, type ResourceClientConfig, type ResourceInfo, type AccessLevel, type UploadResourceOptions, type ListResourceOptions, } from "./resource";
|
|
32
32
|
export { AuthClient, getCurrentUserInfo, type AuthConfig, type UserInfo, };
|
|
33
|
-
export { sdkConfig, VersionCompatibilityError, SDK_SIGNATURE } from "./config";
|
|
33
|
+
export { sdkConfig, VersionCompatibilityError, SDK_SIGNATURE, type AuthenticatedUser } from "./config";
|
|
34
34
|
/**
|
|
35
35
|
* Create a chat model instance based on model name
|
|
36
36
|
*/
|
package/dist/login.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI World SDK - Login utilities (Node.js only)
|
|
3
|
+
* 登录工具函数,仅在 Node.js 环境使用(Vite Plugin / CLI)
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 从项目根目录推断 pluginId:plugin.json plugin_id → package.json name → 目录名
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolvePluginId(projectRoot: string): string;
|
|
9
|
+
export interface LoginOptions {
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
timeout?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface LoginResult {
|
|
14
|
+
token: string;
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 验证 token 是否有效
|
|
19
|
+
* 使用 https/http 模块替代 fetch,以支持自签名证书(rejectUnauthorized: false)
|
|
20
|
+
*/
|
|
21
|
+
export declare function validateToken(baseUrl: string, token: string): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* 读取 .env.local 中的环境变量
|
|
24
|
+
*/
|
|
25
|
+
export declare function readEnvLocal(projectRoot: string): Record<string, string>;
|
|
26
|
+
/**
|
|
27
|
+
* 写入/合并 .env.local,只更新指定的 key,保留其他内容
|
|
28
|
+
*/
|
|
29
|
+
export declare function writeEnvLocal(projectRoot: string, updates: Record<string, string>): void;
|
|
30
|
+
/**
|
|
31
|
+
* 执行登录流程:启动本地 HTTP 服务器 → 打开浏览器 → 等待回调
|
|
32
|
+
*/
|
|
33
|
+
export declare function performLogin(options?: LoginOptions): Promise<LoginResult>;
|
package/dist/login.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AI World SDK - Login utilities (Node.js only)
|
|
4
|
+
* 登录工具函数,仅在 Node.js 环境使用(Vite Plugin / CLI)
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.resolvePluginId = resolvePluginId;
|
|
41
|
+
exports.validateToken = validateToken;
|
|
42
|
+
exports.readEnvLocal = readEnvLocal;
|
|
43
|
+
exports.writeEnvLocal = writeEnvLocal;
|
|
44
|
+
exports.performLogin = performLogin;
|
|
45
|
+
const http = __importStar(require("http"));
|
|
46
|
+
const https = __importStar(require("https"));
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const child_process_1 = require("child_process");
|
|
50
|
+
/**
|
|
51
|
+
* 从项目根目录推断 pluginId:plugin.json plugin_id → package.json name → 目录名
|
|
52
|
+
*/
|
|
53
|
+
function resolvePluginId(projectRoot) {
|
|
54
|
+
try {
|
|
55
|
+
const pluginJsonPath = path.join(projectRoot, "plugin.json");
|
|
56
|
+
if (fs.existsSync(pluginJsonPath)) {
|
|
57
|
+
const data = JSON.parse(fs.readFileSync(pluginJsonPath, "utf-8"));
|
|
58
|
+
if (data.plugin_id)
|
|
59
|
+
return data.plugin_id;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// ignore parse errors
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const pkgJsonPath = path.join(projectRoot, "package.json");
|
|
67
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
68
|
+
const data = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
69
|
+
if (data.name)
|
|
70
|
+
return data.name;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// ignore parse errors
|
|
75
|
+
}
|
|
76
|
+
return path.basename(projectRoot);
|
|
77
|
+
}
|
|
78
|
+
const CALLBACK_PORT = 18000;
|
|
79
|
+
const LOGIN_TIMEOUT_MS = 60000;
|
|
80
|
+
const DEFAULT_BASE_URL = "https://aiworld.local:8000";
|
|
81
|
+
/**
|
|
82
|
+
* 验证 token 是否有效
|
|
83
|
+
* 使用 https/http 模块替代 fetch,以支持自签名证书(rejectUnauthorized: false)
|
|
84
|
+
*/
|
|
85
|
+
function validateToken(baseUrl, token) {
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
try {
|
|
88
|
+
const url = new URL(`${baseUrl}/api/auth/me`);
|
|
89
|
+
const mod = url.protocol === "https:" ? https : http;
|
|
90
|
+
const req = mod.get(url.href, {
|
|
91
|
+
headers: {
|
|
92
|
+
Authorization: `Bearer ${token}`,
|
|
93
|
+
"Content-Type": "application/json",
|
|
94
|
+
},
|
|
95
|
+
rejectUnauthorized: false,
|
|
96
|
+
}, (res) => {
|
|
97
|
+
res.resume();
|
|
98
|
+
resolve(res.statusCode !== undefined && res.statusCode >= 200 && res.statusCode < 300);
|
|
99
|
+
});
|
|
100
|
+
req.on("error", () => resolve(false));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
resolve(false);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 强制确保端口可用:如果被占用则 kill 占用进程
|
|
109
|
+
*/
|
|
110
|
+
function ensurePort(port) {
|
|
111
|
+
try {
|
|
112
|
+
const result = (0, child_process_1.execSync)(`lsof -ti:${port}`, { encoding: "utf-8" }).trim();
|
|
113
|
+
if (result) {
|
|
114
|
+
const pids = result.split("\n").filter(Boolean);
|
|
115
|
+
for (const pid of pids) {
|
|
116
|
+
try {
|
|
117
|
+
(0, child_process_1.execSync)(`kill -9 ${pid}`, { stdio: "ignore" });
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// pid may have already exited
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Brief wait for OS to release the port
|
|
124
|
+
(0, child_process_1.execSync)("sleep 0.3");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// lsof returns non-zero when no process found — port is free
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 打开浏览器
|
|
133
|
+
*/
|
|
134
|
+
function openBrowser(url) {
|
|
135
|
+
const platform = process.platform;
|
|
136
|
+
const cmd = platform === "darwin"
|
|
137
|
+
? "open"
|
|
138
|
+
: platform === "win32"
|
|
139
|
+
? "start"
|
|
140
|
+
: "xdg-open";
|
|
141
|
+
(0, child_process_1.exec)(`${cmd} "${url}"`, (err) => {
|
|
142
|
+
if (err) {
|
|
143
|
+
console.log(`[ai-world] 无法自动打开浏览器,请手动访问: ${url}`);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 读取 .env.local 中的环境变量
|
|
149
|
+
*/
|
|
150
|
+
function readEnvLocal(projectRoot) {
|
|
151
|
+
const envPath = path.join(projectRoot, ".env.local");
|
|
152
|
+
const vars = {};
|
|
153
|
+
if (!fs.existsSync(envPath))
|
|
154
|
+
return vars;
|
|
155
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
156
|
+
for (const line of content.split("\n")) {
|
|
157
|
+
const trimmed = line.trim();
|
|
158
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
159
|
+
continue;
|
|
160
|
+
const eqIdx = trimmed.indexOf("=");
|
|
161
|
+
if (eqIdx === -1)
|
|
162
|
+
continue;
|
|
163
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
164
|
+
const value = trimmed.slice(eqIdx + 1).trim();
|
|
165
|
+
vars[key] = value;
|
|
166
|
+
}
|
|
167
|
+
return vars;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 写入/合并 .env.local,只更新指定的 key,保留其他内容
|
|
171
|
+
*/
|
|
172
|
+
function writeEnvLocal(projectRoot, updates) {
|
|
173
|
+
const envPath = path.join(projectRoot, ".env.local");
|
|
174
|
+
let lines = [];
|
|
175
|
+
if (fs.existsSync(envPath)) {
|
|
176
|
+
lines = fs.readFileSync(envPath, "utf-8").split("\n");
|
|
177
|
+
}
|
|
178
|
+
const updatedKeys = new Set();
|
|
179
|
+
for (let i = 0; i < lines.length; i++) {
|
|
180
|
+
const trimmed = lines[i].trim();
|
|
181
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
182
|
+
continue;
|
|
183
|
+
const eqIdx = trimmed.indexOf("=");
|
|
184
|
+
if (eqIdx === -1)
|
|
185
|
+
continue;
|
|
186
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
187
|
+
if (key in updates) {
|
|
188
|
+
lines[i] = `${key}=${updates[key]}`;
|
|
189
|
+
updatedKeys.add(key);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
193
|
+
if (!updatedKeys.has(key)) {
|
|
194
|
+
lines.push(`${key}=${value}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Remove trailing empty lines then add single newline at end
|
|
198
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
|
|
199
|
+
lines.pop();
|
|
200
|
+
}
|
|
201
|
+
lines.push("");
|
|
202
|
+
fs.writeFileSync(envPath, lines.join("\n"), "utf-8");
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 执行登录流程:启动本地 HTTP 服务器 → 打开浏览器 → 等待回调
|
|
206
|
+
*/
|
|
207
|
+
function performLogin(options = {}) {
|
|
208
|
+
const baseUrl = options.baseUrl || DEFAULT_BASE_URL;
|
|
209
|
+
const timeout = options.timeout || LOGIN_TIMEOUT_MS;
|
|
210
|
+
return new Promise((resolve, reject) => {
|
|
211
|
+
ensurePort(CALLBACK_PORT);
|
|
212
|
+
let settled = false;
|
|
213
|
+
let timeoutId;
|
|
214
|
+
const server = http.createServer((req, res) => {
|
|
215
|
+
const url = new URL(req.url || "/", `http://localhost:${CALLBACK_PORT}`);
|
|
216
|
+
if (url.pathname === "/callback") {
|
|
217
|
+
const token = url.searchParams.get("token");
|
|
218
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
219
|
+
if (token) {
|
|
220
|
+
res.end(`
|
|
221
|
+
<!DOCTYPE html>
|
|
222
|
+
<html><head><meta charset="utf-8"><title>AI World 登录成功</title></head>
|
|
223
|
+
<body style="display:flex;justify-content:center;align-items:center;height:100vh;font-family:system-ui;background:#f5f5f5">
|
|
224
|
+
<div style="text-align:center;padding:40px;background:white;border-radius:12px;box-shadow:0 2px 12px rgba(0,0,0,.1)">
|
|
225
|
+
<h2 style="color:#52c41a">✓ 登录成功</h2>
|
|
226
|
+
<p style="color:#666">Token 已保存,可以关闭此页面</p>
|
|
227
|
+
</div></body></html>`);
|
|
228
|
+
if (!settled) {
|
|
229
|
+
settled = true;
|
|
230
|
+
clearTimeout(timeoutId);
|
|
231
|
+
server.close();
|
|
232
|
+
resolve({ token, baseUrl });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
res.end(`
|
|
237
|
+
<!DOCTYPE html>
|
|
238
|
+
<html><head><meta charset="utf-8"><title>AI World 登录失败</title></head>
|
|
239
|
+
<body style="display:flex;justify-content:center;align-items:center;height:100vh;font-family:system-ui;background:#f5f5f5">
|
|
240
|
+
<div style="text-align:center;padding:40px;background:white;border-radius:12px;box-shadow:0 2px 12px rgba(0,0,0,.1)">
|
|
241
|
+
<h2 style="color:#ff4d4f">✗ 登录失败</h2>
|
|
242
|
+
<p style="color:#666">未收到 token,请重试</p>
|
|
243
|
+
</div></body></html>`);
|
|
244
|
+
}
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
res.writeHead(404);
|
|
248
|
+
res.end("Not Found");
|
|
249
|
+
});
|
|
250
|
+
server.on("error", (err) => {
|
|
251
|
+
if (!settled) {
|
|
252
|
+
settled = true;
|
|
253
|
+
reject(new Error(`无法启动本地回调服务器: ${err.message}`));
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
server.listen(CALLBACK_PORT, () => {
|
|
257
|
+
const callbackUrl = `http://localhost:${CALLBACK_PORT}/callback`;
|
|
258
|
+
const loginUrl = `${baseUrl}/api/auth/login?redirect=${encodeURIComponent(callbackUrl)}`;
|
|
259
|
+
console.log(`[ai-world] 正在打开浏览器登录...`);
|
|
260
|
+
console.log(`[ai-world] 如果浏览器未自动打开,请手动访问:`);
|
|
261
|
+
console.log(`[ai-world] ${loginUrl}`);
|
|
262
|
+
openBrowser(loginUrl);
|
|
263
|
+
timeoutId = setTimeout(() => {
|
|
264
|
+
if (!settled) {
|
|
265
|
+
settled = true;
|
|
266
|
+
server.close();
|
|
267
|
+
reject(new Error(`登录超时(${timeout / 1000}s),请重新启动 dev server`));
|
|
268
|
+
}
|
|
269
|
+
}, timeout);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI World SDK - Vite Plugin
|
|
3
|
+
* 在 Vite dev server 启动时自动检测并完成登录
|
|
4
|
+
* 仅在 Node.js 环境运行(vite.config.ts),不会被打包到浏览器代码中
|
|
5
|
+
*
|
|
6
|
+
* 使用方式:
|
|
7
|
+
* import { aiWorldPlugin } from 'ai-world-sdk/vite'
|
|
8
|
+
* export default defineConfig({ plugins: [aiWorldPlugin()] })
|
|
9
|
+
*/
|
|
10
|
+
export interface AIWorldPluginOptions {
|
|
11
|
+
/** AI World 后端地址,默认 https://aiworld.local:8000 */
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function aiWorldPlugin(options?: AIWorldPluginOptions): any;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AI World SDK - Vite Plugin
|
|
4
|
+
* 在 Vite dev server 启动时自动检测并完成登录
|
|
5
|
+
* 仅在 Node.js 环境运行(vite.config.ts),不会被打包到浏览器代码中
|
|
6
|
+
*
|
|
7
|
+
* 使用方式:
|
|
8
|
+
* import { aiWorldPlugin } from 'ai-world-sdk/vite'
|
|
9
|
+
* export default defineConfig({ plugins: [aiWorldPlugin()] })
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.aiWorldPlugin = aiWorldPlugin;
|
|
13
|
+
const login_1 = require("./login");
|
|
14
|
+
const DEFAULT_BASE_URL = "https://aiworld.local:8000";
|
|
15
|
+
function aiWorldPlugin(options = {}) {
|
|
16
|
+
const baseUrl = options.baseUrl || DEFAULT_BASE_URL;
|
|
17
|
+
let hasLoggedIn = false;
|
|
18
|
+
let projectRoot = process.cwd();
|
|
19
|
+
return {
|
|
20
|
+
name: "ai-world-sdk",
|
|
21
|
+
apply: "serve",
|
|
22
|
+
// 将 .env.local 中的配置注入到浏览器页面的 window.__AI_WORLD_CONFIG__
|
|
23
|
+
// 使 SDK 在浏览器端能自动读取 token/pluginId/baseUrl
|
|
24
|
+
transformIndexHtml() {
|
|
25
|
+
const env = (0, login_1.readEnvLocal)(projectRoot);
|
|
26
|
+
const config = { baseUrl };
|
|
27
|
+
if (env["DEBUG_TOKEN"])
|
|
28
|
+
config.token = env["DEBUG_TOKEN"];
|
|
29
|
+
if (env["PLUGIN_ID"])
|
|
30
|
+
config.pluginId = env["PLUGIN_ID"];
|
|
31
|
+
if (env["DEBUG"] === "true")
|
|
32
|
+
config.debug = true;
|
|
33
|
+
return [{
|
|
34
|
+
tag: "script",
|
|
35
|
+
attrs: { type: "text/javascript" },
|
|
36
|
+
children: `window.__AI_WORLD_CONFIG__=${JSON.stringify(config)};`,
|
|
37
|
+
injectTo: "head-prepend",
|
|
38
|
+
}];
|
|
39
|
+
},
|
|
40
|
+
configureServer(server) {
|
|
41
|
+
projectRoot = server.config?.root || process.cwd();
|
|
42
|
+
server.httpServer?.once("listening", async () => {
|
|
43
|
+
if (hasLoggedIn)
|
|
44
|
+
return;
|
|
45
|
+
const env = (0, login_1.readEnvLocal)(projectRoot);
|
|
46
|
+
const existingToken = env["DEBUG_TOKEN"] || process.env.DEBUG_TOKEN;
|
|
47
|
+
// 确保 PLUGIN_ID 存在,不存在则从 plugin.json 或目录名推断
|
|
48
|
+
if (!env["PLUGIN_ID"] && !process.env.PLUGIN_ID) {
|
|
49
|
+
const pluginId = (0, login_1.resolvePluginId)(projectRoot);
|
|
50
|
+
(0, login_1.writeEnvLocal)(projectRoot, { PLUGIN_ID: pluginId });
|
|
51
|
+
console.log(`[ai-world] PLUGIN_ID 设置为 "${pluginId}"`);
|
|
52
|
+
}
|
|
53
|
+
if (existingToken) {
|
|
54
|
+
const valid = await (0, login_1.validateToken)(baseUrl, existingToken);
|
|
55
|
+
if (valid) {
|
|
56
|
+
console.log("[ai-world] Token 有效,跳过登录");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
console.log("[ai-world] Token 已过期或无效,需要重新登录");
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
console.log("[ai-world] 未检测到 DEBUG_TOKEN,需要登录");
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const result = await (0, login_1.performLogin)({ baseUrl });
|
|
66
|
+
hasLoggedIn = true;
|
|
67
|
+
const pluginId = env["PLUGIN_ID"] || process.env.PLUGIN_ID || (0, login_1.resolvePluginId)(projectRoot);
|
|
68
|
+
(0, login_1.writeEnvLocal)(projectRoot, {
|
|
69
|
+
DEBUG: "true",
|
|
70
|
+
DEBUG_TOKEN: result.token,
|
|
71
|
+
PLUGIN_ID: pluginId,
|
|
72
|
+
});
|
|
73
|
+
console.log("[ai-world] 登录成功,Token 已写入 .env.local");
|
|
74
|
+
console.log("[ai-world] 正在重启 dev server 以加载新的环境变量...");
|
|
75
|
+
await server.restart();
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
console.error(`[ai-world] 登录失败: ${err.message}`);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-world-sdk",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.5",
|
|
4
4
|
"description": "TypeScript SDK for AI World Platform - Chat Models, Image Generation, and Video Generation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./vite": {
|
|
13
|
+
"types": "./dist/vite-plugin.d.ts",
|
|
14
|
+
"default": "./dist/vite-plugin.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
7
17
|
"scripts": {
|
|
8
18
|
"update-version": "node scripts/update-version.js",
|
|
9
19
|
"build": "npm run update-version && rm -rf dist && tsc",
|