ai-world-sdk 1.2.1 → 1.2.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.
@@ -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
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ResourceClient 集成测试
3
+ * 使用真实 SDK 调用后端 /api/resources 接口
4
+ * 需要后端运行 + MinIO 服务 + 数据库可用
5
+ *
6
+ * 运行: npx jest --testPathPattern=resource.test.ts
7
+ */
8
+ export {};
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ /**
3
+ * ResourceClient 集成测试
4
+ * 使用真实 SDK 调用后端 /api/resources 接口
5
+ * 需要后端运行 + MinIO 服务 + 数据库可用
6
+ *
7
+ * 运行: npx jest --testPathPattern=resource.test.ts
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ const dotenv = __importStar(require("dotenv"));
44
+ const index_1 = require("../index");
45
+ dotenv.config();
46
+ index_1.sdkConfig.setBaseUrl("http://localhost:8000");
47
+ index_1.sdkConfig.setToken(process.env.AUTH_TOKEN || process.env.TOKEN || "");
48
+ index_1.sdkConfig.setDebug(true);
49
+ const PLUGIN_ID = "test-plugin";
50
+ describe("ResourceClient 测试", () => {
51
+ let client;
52
+ let uploadedResourceId;
53
+ beforeAll(() => {
54
+ index_1.sdkConfig.setPluginId(PLUGIN_ID);
55
+ client = new index_1.ResourceClient();
56
+ });
57
+ // ==================== 上传 ====================
58
+ test("上传资源 - 默认 private", async () => {
59
+ const content = "Resource test content: " + Date.now();
60
+ const blob = new Blob([content], { type: "text/plain" });
61
+ const file = new File([blob], "resource-test.txt", { type: "text/plain" });
62
+ const result = await client.upload("test/resource-test.txt", file);
63
+ expect(result).toBeDefined();
64
+ expect(result.id).toBeDefined();
65
+ expect(result.path).toBe("test/resource-test.txt");
66
+ expect(result.access_level).toBe("private");
67
+ expect(result.owner_id).toBeDefined();
68
+ expect(result.plugin_id).toBe(PLUGIN_ID);
69
+ expect(result.size).toBeGreaterThan(0);
70
+ uploadedResourceId = result.id;
71
+ console.log("上传成功:", result);
72
+ }, 30000);
73
+ test("上传资源 - public 带描述", async () => {
74
+ const blob = new Blob(["public content"], { type: "text/plain" });
75
+ const file = new File([blob], "public-file.txt", { type: "text/plain" });
76
+ const result = await client.upload("test/public-file.txt", file, {
77
+ accessLevel: "public",
78
+ description: "公开测试文件",
79
+ });
80
+ expect(result).toBeDefined();
81
+ expect(result.access_level).toBe("public");
82
+ expect(result.description).toBe("公开测试文件");
83
+ console.log("公开资源上传成功:", result);
84
+ }, 30000);
85
+ // ==================== 列表 ====================
86
+ test("列出我的资源", async () => {
87
+ const resources = await client.listMy();
88
+ expect(resources).toBeDefined();
89
+ expect(Array.isArray(resources)).toBe(true);
90
+ expect(resources.length).toBeGreaterThan(0);
91
+ const testFile = resources.find((r) => r.path === "test/resource-test.txt");
92
+ expect(testFile).toBeDefined();
93
+ console.log(`我的资源 (${resources.length} 个):`, resources.map((r) => r.path));
94
+ }, 30000);
95
+ test("列出公开资源", async () => {
96
+ const resources = await client.listPublic();
97
+ expect(resources).toBeDefined();
98
+ expect(Array.isArray(resources)).toBe(true);
99
+ const publicFile = resources.find((r) => r.path === "test/public-file.txt");
100
+ expect(publicFile).toBeDefined();
101
+ console.log(`公开资源 (${resources.length} 个):`, resources.map((r) => r.path));
102
+ }, 30000);
103
+ // ==================== 获取详情 ====================
104
+ test("获取资源详情", async () => {
105
+ expect(uploadedResourceId).toBeDefined();
106
+ const info = await client.getInfo(uploadedResourceId);
107
+ expect(info).toBeDefined();
108
+ expect(info.id).toBe(uploadedResourceId);
109
+ expect(info.path).toBe("test/resource-test.txt");
110
+ expect(info.filename).toBe("resource-test.txt");
111
+ console.log("资源详情:", info);
112
+ }, 30000);
113
+ // ==================== 修改权限 ====================
114
+ test("修改访问级别为 public", async () => {
115
+ expect(uploadedResourceId).toBeDefined();
116
+ const updated = await client.updateAccess(uploadedResourceId, "public");
117
+ expect(updated).toBeDefined();
118
+ expect(updated.access_level).toBe("public");
119
+ console.log("访问级别已更新:", updated.access_level);
120
+ }, 30000);
121
+ test("修改访问级别为 specific_users", async () => {
122
+ expect(uploadedResourceId).toBeDefined();
123
+ const updated = await client.updateAccess(uploadedResourceId, "specific_users");
124
+ expect(updated).toBeDefined();
125
+ expect(updated.access_level).toBe("specific_users");
126
+ console.log("访问级别已更新:", updated.access_level);
127
+ }, 30000);
128
+ // ==================== 批量分享 ====================
129
+ test("批量添加分享 - 单个用户", async () => {
130
+ expect(uploadedResourceId).toBeDefined();
131
+ const result = await client.addShare(uploadedResourceId, 99);
132
+ expect(result).toBeDefined();
133
+ expect(result.resource_id).toBe(uploadedResourceId);
134
+ expect(result.total_requested).toBe(1);
135
+ console.log("批量添加分享(单个):", result);
136
+ }, 30000);
137
+ test("批量添加分享 - 多个用户", async () => {
138
+ expect(uploadedResourceId).toBeDefined();
139
+ const result = await client.addShare(uploadedResourceId, [100, 101]);
140
+ expect(result).toBeDefined();
141
+ expect(result.total_requested).toBe(2);
142
+ console.log("批量添加分享(多个):", result);
143
+ }, 30000);
144
+ test("批量移除分享 - 多个用户", async () => {
145
+ expect(uploadedResourceId).toBeDefined();
146
+ const result = await client.removeShare(uploadedResourceId, [99, 100, 101]);
147
+ expect(result).toBeDefined();
148
+ expect(result.total_requested).toBe(3);
149
+ console.log("批量移除分享:", result);
150
+ }, 30000);
151
+ // ==================== 下载 ====================
152
+ test("通过资源 ID 下载", async () => {
153
+ expect(uploadedResourceId).toBeDefined();
154
+ // 先改回 private 验证 owner 仍可下载
155
+ await client.updateAccess(uploadedResourceId, "private");
156
+ const blob = await client.download(uploadedResourceId);
157
+ expect(blob).toBeDefined();
158
+ expect(blob.size).toBeGreaterThan(0);
159
+ console.log("下载成功, 大小:", blob.size);
160
+ }, 30000);
161
+ // ==================== 列出被分享的 ====================
162
+ test("列出被分享给我的资源", async () => {
163
+ const resources = await client.listSharedWithMe();
164
+ expect(resources).toBeDefined();
165
+ expect(Array.isArray(resources)).toBe(true);
166
+ console.log(`被分享资源 (${resources.length} 个)`);
167
+ }, 30000);
168
+ // ==================== 跨插件 ====================
169
+ test("列出我的资源 - 跨插件", async () => {
170
+ const resources = await client.listMy({ crossPlugin: true });
171
+ expect(resources).toBeDefined();
172
+ expect(Array.isArray(resources)).toBe(true);
173
+ console.log(`跨插件资源 (${resources.length} 个)`);
174
+ }, 30000);
175
+ // ==================== 删除 ====================
176
+ test("删除资源", async () => {
177
+ expect(uploadedResourceId).toBeDefined();
178
+ await client.delete(uploadedResourceId);
179
+ console.log("资源已删除:", uploadedResourceId);
180
+ // 验证已删除
181
+ try {
182
+ await client.getInfo(uploadedResourceId);
183
+ fail("应该抛出错误");
184
+ }
185
+ catch (e) {
186
+ expect(e.message).toContain("404");
187
+ }
188
+ }, 30000);
189
+ // 清理 public-file
190
+ test("清理测试资源", async () => {
191
+ const resources = await client.listMy();
192
+ const publicFile = resources.find((r) => r.path === "test/public-file.txt");
193
+ if (publicFile) {
194
+ await client.delete(publicFile.id);
195
+ console.log("清理 public-file:", publicFile.id);
196
+ }
197
+ }, 30000);
198
+ // ==================== 插件 ID 验证 ====================
199
+ test("缺少 pluginId 时抛出错误", () => {
200
+ const originalPluginId = index_1.sdkConfig.getPluginId();
201
+ index_1.sdkConfig.setPluginId("");
202
+ const badClient = new index_1.ResourceClient();
203
+ expect(() => {
204
+ badClient.ensurePluginId();
205
+ }).toThrow("X-Plugin-Id is required");
206
+ index_1.sdkConfig.setPluginId(originalPluginId || PLUGIN_ID);
207
+ });
208
+ });
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.1";
12
+ export declare const SDK_SIGNATURE = "AI_WORLD_SDK_V:1.2.3";
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
- readonly sdkSignature = "AI_WORLD_SDK_V:1.2.1";
28
- readonly sdkVersion = "1.2.1";
34
+ private _authenticated;
35
+ private _authCheckPromise;
36
+ private _currentUser;
37
+ readonly sdkSignature = "AI_WORLD_SDK_V:1.2.3";
38
+ readonly sdkVersion = "1.2.3";
29
39
  constructor();
30
40
  /**
31
41
  * Set global base URL
@@ -77,6 +87,23 @@ 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
+ /**
96
+ * 获取当前登录状态
97
+ */
98
+ isAuthenticated(): boolean | null;
99
+ /**
100
+ * 获取当前登录用户信息(需先完成 checkAuthentication)
101
+ */
102
+ getCurrentUser(): AuthenticatedUser | null;
103
+ /**
104
+ * 等待登录检查完成并确保已登录,未登录则抛出错误
105
+ */
106
+ ensureAuthenticated(): Promise<AuthenticatedUser>;
80
107
  /**
81
108
  * Check version compatibility with backend
82
109
  * 检查与后端的版本兼容性
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.1";
10
+ const SDK_VERSION = "1.2.3";
11
11
  /**
12
12
  * SDK 特征码 - 用于在构建后的 JS 文件中识别 SDK 版本
13
13
  * 格式: AI_WORLD_SDK_V:版本号
@@ -15,7 +15,7 @@ const SDK_VERSION = "1.2.1";
15
15
  *
16
16
  * 注意: {VERSION} 占位符会在构建时被替换为实际版本号
17
17
  */
18
- exports.SDK_SIGNATURE = "AI_WORLD_SDK_V:1.2.1";
18
+ exports.SDK_SIGNATURE = "AI_WORLD_SDK_V:1.2.3";
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; // null 表示未检查,true/false 表示检查结果
37
- this._versionCheckPromise = null; // 版本检查的 Promise,避免重复请求
38
- // SDK 版本标识(用于后端扫描检测)
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
- // 通过cookie获取token
46
- try {
47
- const cookieString = typeof document !== 'undefined' ? document.cookie : '';
48
- const tokenRow = cookieString.split('; ').find(row => row.startsWith('auth_token='));
49
- if (tokenRow) {
50
- const tokenValue = tokenRow.split('=')[1];
51
- // 处理 URL 编码的 token(如果有)
52
- this._token = decodeURIComponent(tokenValue);
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
- catch (error) {
56
- console.warn('Failed to read token from cookie:', error);
57
- }
58
- // 通过cookie获取baseUrl
59
- const baseUrl = typeof window !== 'undefined' ? window.location.origin : '';
60
- this._baseUrl = baseUrl;
61
- // window.__INITIAL_DATA__ 获取 plugin_id(SSR 插件环境)
62
- try {
63
- if (typeof window !== 'undefined' && 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;
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
+ }
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
+ }
67
90
  }
68
91
  }
92
+ catch {
93
+ // __AI_WORLD_CONFIG__ not available
94
+ }
69
95
  }
70
- catch (error) {
71
- console.warn('Failed to read plugin_id from __INITIAL_DATA__:', error);
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,81 @@ 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 response = await fetch(`${baseUrl}/api/auth/me`, {
229
+ method: 'GET',
230
+ headers: {
231
+ 'Authorization': `Bearer ${this._token}`,
232
+ 'Content-Type': 'application/json',
233
+ },
234
+ });
235
+ if (response.ok) {
236
+ const user = await response.json();
237
+ this._authenticated = true;
238
+ this._currentUser = user;
239
+ if (this._debug) {
240
+ console.log(`[ai-world-sdk] 已登录: ${user.full_name || user.email || user.id}`);
241
+ }
242
+ return true;
243
+ }
244
+ this._authenticated = false;
245
+ this._token = null;
246
+ console.warn('[ai-world-sdk] Token 已过期或无效,请重新登录');
247
+ return false;
248
+ }
249
+ catch {
250
+ this._authenticated = false;
251
+ return false;
252
+ }
253
+ }
254
+ /**
255
+ * 获取当前登录状态
256
+ */
257
+ isAuthenticated() {
258
+ return this._authenticated;
259
+ }
260
+ /**
261
+ * 获取当前登录用户信息(需先完成 checkAuthentication)
262
+ */
263
+ getCurrentUser() {
264
+ return this._currentUser;
265
+ }
266
+ /**
267
+ * 等待登录检查完成并确保已登录,未登录则抛出错误
268
+ */
269
+ async ensureAuthenticated() {
270
+ const ok = await this.checkAuthentication();
271
+ if (!ok || !this._currentUser) {
272
+ throw new Error('未登录,请先完成登录。Vite 项目请在 vite.config 中添加 aiWorldPlugin()');
273
+ }
274
+ return this._currentUser;
275
+ }
149
276
  /**
150
277
  * Check version compatibility with backend
151
278
  * 检查与后端的版本兼容性