@tacoreai/web-sdk 1.22.0 → 1.22.1-beta.1

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/README.md CHANGED
@@ -17,52 +17,63 @@ npm install @tacoreai/web-sdk
17
17
  ### AppsClient (Data & AI)
18
18
  示例略(保持原有章节)
19
19
 
20
+ ### AppsClient (Events)
21
+
22
+ 应用自定义埋点应使用事件日志接口,不要把高频事件写入通用数据模型。
23
+ 应用代码不要直接请求 `/plat` 平台接口,也不要读取平台登录 token;写入和读取都应通过 `AppsClient.invoke(...)` 走 `/apps/events/*`。事件日志不新增 WebSDK 专用方法,apiName 直接和后端 path 对齐。
24
+
25
+ ```js
26
+ const tracked = await client.invoke('events/track', {
27
+ eventName: 'signup_click',
28
+ properties: { plan: 'pro' },
29
+ visitorId: localStorage.getItem('visitor_id'),
30
+ sessionId: sessionStorage.getItem('session_id'),
31
+ path: window.location.pathname,
32
+ });
33
+
34
+ // 以下读取接口仅适用于已登录且具备 app owner/admin 角色的应用管理界面。
35
+ const event = await client.invoke('events/read', {
36
+ eventId: tracked?.data?.eventId,
37
+ });
38
+
39
+ const page = await client.invoke('events/query', {
40
+ day: '2026-05-14',
41
+ eventName: 'signup_click',
42
+ limit: 50,
43
+ });
44
+ ```
45
+
46
+ 可用接口包括 `events/track`、`events/batch-track`、`events/read`、`events/batch-read`、`events/query`。写入接口由后端校验应用部署域名或自定义域名来源;所有读取接口都要求 Apps 登录态且当前用户为应用 `owner/admin`。平台通用查看/搜索由平台工作视图承接。
47
+
20
48
  ---
21
49
 
22
- ## 文件存储 API(CloudBase 通道)
50
+ ## 文件存储 API(R2 / GCS)
23
51
 
24
52
  先确保已通过 AppsAuthManager 登录,并使用 AppsClient 初始化时提供 appId。以下接口均作用于 /apps 路由,需携带 Authorization。
25
53
 
26
- 1) 上传文件(<=20MB)
54
+ 1) 上传文件到 R2(公开访问 / 前端展示)
27
55
 
28
56
  import { AppsClient } from '@tacoreai/web-sdk'
29
57
  const client = new AppsClient('YOUR_APP_ID')
30
58
 
31
59
  const input = document.querySelector('#fileInput')
32
60
  const file = input.files[0]
33
- const result = await client.uploadFile(file, { expires: 3600 })
34
- // result.data => { fileID, url, path, mimeType, size, expiresIn }
61
+ const result = await client.uploadFile(file)
62
+ // result => { success: true, url, key }
35
63
 
36
- 2) 获取临时下载 URL
64
+ 2) 下载 R2 文件
37
65
 
38
- const { data } = await client.getFileUrl('<fileID>', 1800)
39
- // data.url 为可直接访问的签名地址(有效期30分钟)
66
+ const response = await client.downloadFromR2('<key>')
67
+ const blob = await response.blob()
40
68
 
41
- 3) 批量删除文件
69
+ 3) 删除 R2 文件
42
70
 
43
- await client.deleteFiles(['<fileID1>', '<fileID2>'])
71
+ await client.deleteFromR2('<key>')
44
72
 
45
- 4) 下载文件为 Blob(用于预览或保存)
73
+ 4) 上传文件到 GCS(Gemini / 多模态分析输入)
46
74
 
47
- const blob = await client.downloadFile('<fileID>')
48
- // 例如:保存为本地文件
49
- const a = document.createElement('a')
50
- a.href = URL.createObjectURL(blob)
51
- a.download = 'download.bin'
52
- a.click()
53
- URL.revokeObjectURL(a.href)
54
-
55
- ---
56
-
57
- ## [新增] 文件上传与分析
58
-
59
- 此接口用于上传文件(如 PDF、图片)以供 AI 进行深度分析或作为图片编辑的输入。
60
-
61
- ### `uploadFileForAnalysis(file)`
62
- - **功能**: 将文件上传至 Google 的专用存储,并返回一个可供 AI 模型引用的 `fileUri`。
63
- - **参数**:
64
- - `file` (File): 从文件输入框获取的 File 对象。
65
- - **返回值**: `Promise<{fileUri: string, mimeType: string, ...}>`
75
+ const gcsResult = await client.uploadToGCS(file)
76
+ // gcsResult.fileUri => gs://... ,供 Gemini 等模型使用
66
77
 
67
78
  ---
68
79
 
@@ -111,12 +122,12 @@ const imageUri = result.data.uri;
111
122
  - **返回**: `Promise<Object>` (后端响应对象,包含 `data.uri` 或 `data.images`)
112
123
 
113
124
  ### 核心流程
114
- 1. **上传图片**: 所有源图片(包括底图、叠加图、掩膜图)都**必须**先通过 `uploadFileForAnalysis(file)` 上传,获取 `fileUri` `mimeType`。
125
+ 1. **上传图片**: Google / Gemini 链路下,所有源图片(包括底图、叠加图、掩膜图)都应先通过 `uploadToGCS(file)` 获取 `gs://` 形式的 `fileUri`。
115
126
  2. **调用编辑**: 使用获取到的 `fileUri` 和 `mimeType`,通过 `editImage` 方法并采用**简化的 `prompt + inputs` 模式**进行调用。
116
127
 
117
128
  ### 前置要求
118
129
  - **必须先登录** (AppsAuthManager)
119
- - `uploadFileForAnalysis`
130
+ - `uploadToGCS`
120
131
 
121
132
  ### 1. 简化调用 (prompt + inputs 模式,推荐)
122
133
  SDK 会自动将 `prompt` 和 `inputs` 数组组合成标准的多模态 `messages`。
@@ -125,13 +136,13 @@ import { AppsClient } from '@tacoreai/web-sdk'
125
136
  const client = new AppsClient('YOUR_APP_ID')
126
137
 
127
138
  // 1. 上传源文件
128
- const baseFile = await client.uploadFileForAnalysis(file);
139
+ const baseFile = await client.uploadToGCS(file);
129
140
 
130
141
  // 2. 调用编辑接口
131
142
  const result = await client.editImage({
132
143
  prompt: '仅移除图中叠加的文字,保持其他区域不变,输出高清。',
133
144
  inputs: [
134
- { type: 'uploaded_file', fileUri: baseFile.fileUri, mimeType: baseFile.mimeType }
145
+ { type: 'uploaded_file', fileUri: baseFile.fileUri, mimeType: file.type }
135
146
  ]
136
147
  });
137
148
  // 获取图片 URI (兼容不同后端返回结构)
@@ -142,41 +153,41 @@ const editedBase64 = result.data.uri || (result.data.images && result.data.image
142
153
 
143
154
  - **场景 A:单图编辑(去字/去水印/局部替换)**
144
155
 
145
- const base = await client.uploadFileForAnalysis(file);
156
+ const base = await client.uploadToGCS(file);
146
157
  const result = await client.editImage({
147
158
  prompt: '移除叠加文字,其他区域完全保持不变。',
148
159
  inputs: [
149
- { type: 'uploaded_file', fileUri: base.fileUri, mimeType: base.mimeType }
160
+ { type: 'uploaded_file', fileUri: base.fileUri, mimeType: file.type }
150
161
  ]
151
162
  });
152
163
  const base64 = result.data.uri || result.data.images?.[0]?.uri;
153
164
 
154
165
  - **场景 B:双图融合(将服装穿到模特身上)**
155
166
 
156
- const garment = await client.uploadFileForAnalysis(garmentFile);
157
- const modelPhoto = await client.uploadFileForAnalysis(modelFile);
167
+ const garment = await client.uploadToGCS(garmentFile);
168
+ const modelPhoto = await client.uploadToGCS(modelFile);
158
169
 
159
170
  const result = await client.editImage({
160
171
  prompt: '将服装自然替换到模特身上,保持姿态与光照;去除服装图背景;保持背景不变;不改变面部和发型。',
161
172
  inputs: [
162
173
  // 建议顺序:底图(模特)放前,叠加/融合图(服装)放后
163
- { type: 'uploaded_file', fileUri: modelPhoto.fileUri, mimeType: modelPhoto.mimeType },
164
- { type: 'uploaded_file', fileUri: garment.fileUri, mimeType: garment.mimeType }
174
+ { type: 'uploaded_file', fileUri: modelPhoto.fileUri, mimeType: modelFile.type },
175
+ { type: 'uploaded_file', fileUri: garment.fileUri, mimeType: garmentFile.type }
165
176
  ]
166
177
  });
167
178
  const base64 = result.data.uri || result.data.images?.[0]?.uri;
168
179
 
169
180
  - **场景 C:掩膜修复/扩展(Inpainting / Outpainting)**
170
181
 
171
- const base = await client.uploadFileForAnalysis(baseFile);
172
- const mask = await client.uploadFileForAnalysis(maskFile); // 掩膜语义按后端约定
182
+ const base = await client.uploadToGCS(baseFile);
183
+ const mask = await client.uploadToGCS(maskFile); // 掩膜语义按后端约定
173
184
 
174
185
  const result = await client.editImage({
175
186
  prompt: '在掩膜区域进行修复,移除水印并保持纹理一致;不要改变未掩膜区域。',
176
187
  inputs: [
177
188
  // 建议顺序:底图 -> 掩膜
178
- { type: 'uploaded_file', fileUri: base.fileUri, mimeType: base.mimeType },
179
- { type: 'uploaded_file', fileUri: mask.fileUri, mimeType: mask.mimeType }
189
+ { type: 'uploaded_file', fileUri: base.fileUri, mimeType: baseFile.type },
190
+ { type: 'uploaded_file', fileUri: mask.fileUri, mimeType: maskFile.type }
180
191
  ]
181
192
  });
182
193
  const base64 = result.data.uri || result.data.images?.[0]?.uri;
@@ -184,7 +195,7 @@ const base64 = result.data.uri || result.data.images?.[0]?.uri;
184
195
  ### 3. 高级模式 (messages 透传)
185
196
  用于完全控制请求结构,不推荐常规使用。
186
197
 
187
- const base = await client.uploadFileForAnalysis(file)
198
+ const base = await client.uploadToGCS(file)
188
199
 
189
200
  const result = await client.editImage({
190
201
  model: 'gemini-2.5-flash-image-preview',
@@ -193,7 +204,7 @@ const result = await client.editImage({
193
204
  role: 'user',
194
205
  content: [
195
206
  { type: 'text', text: '移除图中叠加的文字...' },
196
- { type: 'uploaded_file', uploaded_file: { fileUri: base.fileUri, mimeType: base.mimeType } }
207
+ { type: 'uploaded_file', uploaded_file: { fileUri: base.fileUri, mimeType: file.type } }
197
208
  ]
198
209
  }
199
210
  ]
@@ -31,17 +31,36 @@ const getRequestSource = (isPlatformProxyRequest, isSchedulerRequest) => {
31
31
  };
32
32
 
33
33
  const createRequestScopedAppsClient = (env, options = {}) => {
34
- const { accessToken = null, previewAppServerApiKey = null } = options;
34
+ const {
35
+ accessToken = null,
36
+ appServerApiKey = null,
37
+ clientAppId = env.dataSourceId || env.appId,
38
+ previewAppServerApiKey = null,
39
+ enableAppServerServiceAuth = false,
40
+ } = options;
35
41
  const config = {
36
42
  ...env,
37
43
  accessToken: accessToken || null,
44
+ enableAppServerServiceAuth,
38
45
  };
39
46
 
47
+ if (enableAppServerServiceAuth) {
48
+ config.appServerOwnerAppId = env.appId;
49
+ config.tacoreAppServerServiceToken =
50
+ env.tacoreAppServerServiceToken ||
51
+ process.env.TACORE_APP_SERVER_SERVICE_TOKEN ||
52
+ null;
53
+ }
54
+
55
+ if (appServerApiKey) {
56
+ config.appServerApiKey = appServerApiKey;
57
+ }
58
+
40
59
  if (previewAppServerApiKey) {
41
60
  config.previewAppServerApiKey = previewAppServerApiKey;
42
61
  }
43
62
 
44
- return new AppsClient(env.dataSourceId || env.appId, config);
63
+ return new AppsClient(clientAppId, config);
45
64
  };
46
65
 
47
66
  const isPreviewApiKeyShapeValid = (apiKey) => {
@@ -273,7 +292,9 @@ export const createAppServerRuntime = async (options = {}) => {
273
292
  process.env.CLOUDFLARE_WORKER_INTEROP_APP_SERVER_TOKEN
274
293
  );
275
294
  const requestSource = getRequestSource(isPlatformProxyRequest, isSchedulerRequest);
295
+ let appServerApiKey = null;
276
296
  let previewAppServerApiKey = null;
297
+ let isExternalApiKeyRequest = false;
277
298
 
278
299
  console.log(`[AppServer] invoke api="${apiName}" source="${requestSource}"`);
279
300
 
@@ -299,6 +320,7 @@ export const createAppServerRuntime = async (options = {}) => {
299
320
  try {
300
321
  const authClient = createRequestScopedAppsClient(env, {
301
322
  accessToken: requestAccessToken || null,
323
+ clientAppId: env.appId,
302
324
  });
303
325
  isValidApiKey = await validateExternalApiKey(authClient, env.appId, requestApiKey);
304
326
  } catch (error) {
@@ -318,6 +340,9 @@ export const createAppServerRuntime = async (options = {}) => {
318
340
  });
319
341
  }
320
342
 
343
+ isExternalApiKeyRequest = true;
344
+ appServerApiKey = requestApiKey;
345
+
321
346
  if (isPreviewMode() && !requestAccessToken) {
322
347
  previewAppServerApiKey = requestApiKey;
323
348
  }
@@ -330,17 +355,24 @@ export const createAppServerRuntime = async (options = {}) => {
330
355
  hasRequestApiKey: Boolean(requestApiKey),
331
356
  hasPlatformInteropHeader: Boolean(platformInteropHeader),
332
357
  hasSchedulerInteropHeader: Boolean(schedulerInteropHeader),
358
+ isExternalApiKeyRequest,
359
+ willForwardAppServerApiKey: Boolean(appServerApiKey && !requestAccessToken),
333
360
  willForwardPreviewAppServerApiKey: Boolean(previewAppServerApiKey),
334
361
  });
335
362
 
336
363
  const appsClient = createRequestScopedAppsClient(env, {
337
364
  accessToken: requestAccessToken || null,
365
+ appServerApiKey: requestAccessToken ? null : appServerApiKey,
338
366
  previewAppServerApiKey,
367
+ enableAppServerServiceAuth: Boolean((isSchedulerRequest || isExternalApiKeyRequest) && !requestAccessToken),
339
368
  });
340
369
  console.log(`[AppServer Debug] scoped AppsClient api="${apiName}"`, {
341
370
  hasAccessToken: Boolean(appsClient.getAccessToken()),
371
+ hasAppServerApiKey: Boolean(appsClient.config.appServerApiKey),
342
372
  hasPreviewAppServerApiKey: Boolean(appsClient.config.previewAppServerApiKey),
343
373
  hasInteropKey: Boolean(appsClient.config.tacoreServerInteropAppServerApiKey),
374
+ serviceAuthEnabled: appsClient.config.enableAppServerServiceAuth === true,
375
+ hasServiceToken: Boolean(appsClient.config.tacoreAppServerServiceToken),
344
376
  });
345
377
  const data = await appsClient.invokeAppServerAPI(apiName, payload, {
346
378
  appsClient,
@@ -80,22 +80,11 @@ export const appsClientDataMethods = {
80
80
  modelName: modelName,
81
81
  };
82
82
 
83
- if (typeof query === "string") {
84
- params.wyID = query;
85
- } else if (query && typeof query === "object") {
86
- const { page = 1, pageSize = 20, fields, filter } = query;
87
- params.page = Math.max(1, parseInt(page));
88
- params.pageSize = parseInt(pageSize) || 1000;
89
- console.log(params);
90
- if (fields && Array.isArray(fields)) {
91
- params.fields = fields;
92
- }
93
-
94
- if (filter && typeof filter === "object") {
95
- params.filter = filter;
96
- }
83
+ if (typeof query !== "string" || !query) {
84
+ throw new Error("readData only supports single-record reads by wyID. Use invoke('data/list', ...) or invoke('data/query', ...) for list/query reads.");
97
85
  }
98
86
 
87
+ params.wyID = query;
99
88
  const result = await this._get("/apps/data/read", params);
100
89
 
101
90
  if (result.data === null && params.wyID) {
@@ -152,7 +141,13 @@ export const appsClientDataMethods = {
152
141
  async hasData(modelName) {
153
142
  try {
154
143
  if (!modelName) throw new Error("modelName is required for hasData");
155
- const result = await this.readData(modelName, { page: 1, pageSize: 1 });
144
+ const result = await this.invoke("data/list", {
145
+ modelName,
146
+ query: {
147
+ page: 1,
148
+ pageSize: 1,
149
+ },
150
+ });
156
151
  return result && result.data && result.data.length > 0;
157
152
  } catch (error) {
158
153
  console.error(`检查应用数据失败 (model: ${modelName}):`, error);
@@ -0,0 +1,77 @@
1
+ export const appsClientIamMethods = {
2
+ async getIamContext(params = {}) {
3
+ return this._post("/apps/iam/me", params);
4
+ },
5
+
6
+ async bootstrapIam(params = {}) {
7
+ return this._post("/apps/iam/bootstrap", params);
8
+ },
9
+
10
+ async listIamOrgUnits(params = {}) {
11
+ return this._post("/apps/iam/org-units/list", params);
12
+ },
13
+
14
+ async createIamOrgUnit(params = {}) {
15
+ return this._post("/apps/iam/org-units", params);
16
+ },
17
+
18
+ async updateIamOrgUnit(params = {}) {
19
+ return this._post("/apps/iam/org-units/update", params);
20
+ },
21
+
22
+ async deleteIamOrgUnit(params = {}) {
23
+ return this._post("/apps/iam/org-units/delete", params);
24
+ },
25
+
26
+ async listIamRoles(params = {}) {
27
+ return this._post("/apps/iam/roles/list", params);
28
+ },
29
+
30
+ async createIamRole(params = {}) {
31
+ return this._post("/apps/iam/roles", params);
32
+ },
33
+
34
+ async updateIamRole(params = {}) {
35
+ return this._post("/apps/iam/roles/update", params);
36
+ },
37
+
38
+ async deleteIamRole(params = {}) {
39
+ return this._post("/apps/iam/roles/delete", params);
40
+ },
41
+
42
+ async listIamStaff(params = {}) {
43
+ return this._post("/apps/iam/staff/list", params);
44
+ },
45
+
46
+ async createIamStaff(params = {}) {
47
+ return this._post("/apps/iam/staff", params);
48
+ },
49
+
50
+ async updateIamStaff(params = {}) {
51
+ return this._post("/apps/iam/staff/update", params);
52
+ },
53
+
54
+ async deleteIamStaff(params = {}) {
55
+ return this._post("/apps/iam/staff/delete", params);
56
+ },
57
+
58
+ async listModelPolicies(params = {}) {
59
+ return this._post("/apps/data/model-policies/list", params);
60
+ },
61
+
62
+ async declareModelPolicy(params = {}) {
63
+ return this._post("/apps/data/model-policies/declare", params);
64
+ },
65
+
66
+ async writeAuditLog(params = {}) {
67
+ return this._post("/apps/audit/write", params);
68
+ },
69
+
70
+ async queryAuditLogs(params = {}) {
71
+ return this._post("/apps/audit/query", params);
72
+ },
73
+
74
+ async authorizeFileDownload(params = {}) {
75
+ return this._post("/apps/files/authorize-download", params);
76
+ },
77
+ };
@@ -62,4 +62,34 @@ export const appsClientUserMethods = {
62
62
  password,
63
63
  });
64
64
  },
65
+
66
+ /**
67
+ * 从当前应用移除用户访问权限,保留 Auth 账号。
68
+ * @param {Object} params { userId: string }
69
+ * @returns {Promise<{success: boolean, data: {userId: string, appId: string}}>}
70
+ */
71
+ async removeAppUser({ userId }) {
72
+ if (!userId) {
73
+ throw new Error("userId is required.");
74
+ }
75
+ return this._post("/apps/users/remove", {
76
+ appId: this.appId,
77
+ targetUserId: userId,
78
+ });
79
+ },
80
+
81
+ /**
82
+ * 彻底删除当前应用中的用户账号,使该邮箱可重新注册。
83
+ * @param {Object} params { userId: string }
84
+ * @returns {Promise<{success: boolean, data: {userId: string, appId: string, deletionMode: string, authDeleted: boolean}}>}
85
+ */
86
+ async deleteAppUser({ userId }) {
87
+ if (!userId) {
88
+ throw new Error("userId is required.");
89
+ }
90
+ return this._post("/apps/users/delete", {
91
+ appId: this.appId,
92
+ targetUserId: userId,
93
+ });
94
+ },
65
95
  };
@@ -18,6 +18,7 @@ import { appsClientToolsMethods } from "./AppsClient.Tools.js";
18
18
  import { appsClientDebugMethods } from "./AppsClient.Debug.js";
19
19
  import { appsClientCrawlerMethods } from "./AppsClient.Crawler.js";
20
20
  import { appsClientWechatMethods } from "./AppsClient.Wechat.js";
21
+ import { appsClientIamMethods } from "./AppsClient.IAM.js";
21
22
 
22
23
  const instanceMap = new Map();
23
24
  /**
@@ -79,8 +80,7 @@ class AppsClient extends BaseAppsClient {
79
80
  return this.readData(modelName, rest.wyID);
80
81
  }
81
82
 
82
- // 兼容旧版平铺参数
83
- return this.readData(modelName, rest);
83
+ throw new Error("readData only supports single-record reads by wyID. Use invoke('data/list', ...) or invoke('data/query', ...) for list/query reads.");
84
84
  },
85
85
 
86
86
  "deleteData": ({ modelName, wyID, options }) =>
@@ -103,8 +103,31 @@ class AppsClient extends BaseAppsClient {
103
103
  "createRole": params => this.createRole(params),
104
104
  "updateUserRole": params => this.updateUserRole(params),
105
105
  "users/update-password": params => this.updateUserPassword(params),
106
+ "users/remove": params => this.removeAppUser(params),
107
+ "users/delete": params => this.deleteAppUser(params),
106
108
  "listAppUsers": params => this.listAppUsers(params),
107
109
 
110
+ // === 企业 IAM / 审计 / 文件鉴权 ===
111
+ "iam/me": params => this.getIamContext(params),
112
+ "iam/bootstrap": params => this.bootstrapIam(params),
113
+ "iam/org-units/list": params => this.listIamOrgUnits(params),
114
+ "iam/org-units": params => this.createIamOrgUnit(params),
115
+ "iam/org-units/update": params => this.updateIamOrgUnit(params),
116
+ "iam/org-units/delete": params => this.deleteIamOrgUnit(params),
117
+ "iam/roles/list": params => this.listIamRoles(params),
118
+ "iam/roles": params => this.createIamRole(params),
119
+ "iam/roles/update": params => this.updateIamRole(params),
120
+ "iam/roles/delete": params => this.deleteIamRole(params),
121
+ "iam/staff/list": params => this.listIamStaff(params),
122
+ "iam/staff": params => this.createIamStaff(params),
123
+ "iam/staff/update": params => this.updateIamStaff(params),
124
+ "iam/staff/delete": params => this.deleteIamStaff(params),
125
+ "data/model-policies/list": params => this.listModelPolicies(params),
126
+ "data/model-policies/declare": params => this.declareModelPolicy(params),
127
+ "audit/write": params => this.writeAuditLog(params),
128
+ "audit/query": params => this.queryAuditLogs(params),
129
+ "files/authorize-download": params => this.authorizeFileDownload(params),
130
+
108
131
  // === 文件存储 ===
109
132
  "uploadFile": ({ file }) => this.uploadFile(file),
110
133
  "uploadToR2": ({ file }) => this.uploadToR2(file),
@@ -213,7 +236,8 @@ Object.assign(
213
236
  appsClientToolsMethods,
214
237
  appsClientDebugMethods,
215
238
  appsClientCrawlerMethods,
216
- appsClientWechatMethods
239
+ appsClientWechatMethods,
240
+ appsClientIamMethods
217
241
  );
218
242
 
219
243
  export { AppsClient };
@@ -4,8 +4,10 @@ import { BaseAppsClient } from "./BaseAppsClient.js";
4
4
  * AI应用公共数据客户端 - 无需认证
5
5
  *
6
6
  * 核心理念:
7
- * - 用于匿名读取被 `writeData` 设为公共的数据。
7
+ * - 通过继承的通用 invoke(path, params) 调用匿名公开接口。
8
+ * - 用于匿名读取模型策略 `publicRead` 明确开放的 CMS 数据。
8
9
  * - 用于匿名提交数据到指定的、允许公开写入的模型。
10
+ * - 保留 `readConfiguredPublicData` 兼容旧 publicConfig 公开读。
9
11
  * - 此客户端的所有操作都无需用户登录。
10
12
  */
11
13
  export class AppsPublicClient extends BaseAppsClient {
@@ -2,8 +2,11 @@ import { getAppsApiBaseUrl, isBrowser, isBackend, isSSR, getGlobalOptions } from
2
2
 
3
3
  const ACCESS_TOKEN_STORAGE_KEY = "tacoreai_apps_access_token";
4
4
  const PREVIEW_APPSERVER_API_KEY_HEADER = "x-tacore-preview-app-server-api-key";
5
- const INTEROP_APP_SERVER_API_KEY_REQUIRED_ERROR =
6
- "tacoreServerInteropAppServerApiKey is required for backend environment. Provide it in config or via TACORE_SERVER_INTEROP_APP_SERVER_API_KEY env var.";
5
+ const APP_SERVER_API_KEY_HEADER = "x-tacore-app-server-api-key";
6
+ const APP_SERVER_SERVICE_TOKEN_HEADER = "x-tacore-app-server-service-token";
7
+ const APP_SERVER_OWNER_APP_ID_HEADER = "x-tacore-app-server-owner-app-id";
8
+ const APP_SERVER_SERVICE_CREDENTIAL_REQUIRED_ERROR =
9
+ "AppServer service credential is required for backend service auth. Provide TACORE_SERVER_INTEROP_APP_SERVER_API_KEY + TACORE_APP_SERVER_SERVICE_TOKEN, or forward a validated appServerApiKey.";
7
10
 
8
11
  const normalizeAccessToken = (value) => {
9
12
  if (typeof value !== "string") {
@@ -48,7 +51,17 @@ const writePersistedAccessToken = (token) => {
48
51
  };
49
52
 
50
53
  const isBackendPreviewMode = () => isBackend && process.env.TACORE_APPSERVER_PREVIEW_MODE === "true";
51
- const shouldRequireInteropAppServerApiKey = () => isBackend && !isSSR && !isBackendPreviewMode();
54
+ const hasAppServerServiceCredential = (config = {}) => {
55
+ if (config.tacoreServerInteropAppServerApiKey && config.tacoreAppServerServiceToken) {
56
+ return true;
57
+ }
58
+ if (config.appServerApiKey) {
59
+ return true;
60
+ }
61
+ return isBackendPreviewMode() && Boolean(config.previewAppServerApiKey);
62
+ };
63
+ const shouldRequireAppServerServiceCredential = (config = {}) =>
64
+ isBackend && !isSSR && config.enableAppServerServiceAuth === true;
52
65
 
53
66
  /**
54
67
  * Apps SDK 基类
@@ -85,9 +98,17 @@ export class BaseAppsClient {
85
98
  if (isBackend && !isSSR && !this.config.tacoreServerInteropAppServerApiKey) {
86
99
  this.config.tacoreServerInteropAppServerApiKey = process.env.TACORE_SERVER_INTEROP_APP_SERVER_API_KEY;
87
100
  }
101
+ if (
102
+ isBackend &&
103
+ !isSSR &&
104
+ this.config.enableAppServerServiceAuth === true &&
105
+ !this.config.tacoreAppServerServiceToken
106
+ ) {
107
+ this.config.tacoreAppServerServiceToken = process.env.TACORE_APP_SERVER_SERVICE_TOKEN;
108
+ }
88
109
 
89
- if (shouldRequireInteropAppServerApiKey() && !this.config.tacoreServerInteropAppServerApiKey) {
90
- throw new Error(INTEROP_APP_SERVER_API_KEY_REQUIRED_ERROR);
110
+ if (shouldRequireAppServerServiceCredential(this.config) && !hasAppServerServiceCredential(this.config)) {
111
+ throw new Error(APP_SERVER_SERVICE_CREDENTIAL_REQUIRED_ERROR);
91
112
  }
92
113
  }
93
114
 
@@ -157,10 +178,6 @@ export class BaseAppsClient {
157
178
  ...additionalHeaders,
158
179
  };
159
180
 
160
- if (this.config.datasource) {
161
- headers["x-datasource"] = this.config.datasource;
162
- }
163
-
164
181
  if (this.config.supabaseBaseUrl) {
165
182
  headers["x-supabase-base-url"] = this.config.supabaseBaseUrl;
166
183
  }
@@ -186,8 +203,20 @@ export class BaseAppsClient {
186
203
  headers["Authorization"] = `Bearer ${token}`;
187
204
  }
188
205
 
189
- if (isBackend && !isSSR && this.config.tacoreServerInteropAppServerApiKey) {
190
- headers["x-tacore-server-interop-app-server-api-key"] = this.config.tacoreServerInteropAppServerApiKey;
206
+ if (isBackend && !isSSR && !token && this.config.enableAppServerServiceAuth === true) {
207
+ if (this.config.tacoreServerInteropAppServerApiKey && this.config.tacoreAppServerServiceToken) {
208
+ headers["x-tacore-server-interop-app-server-api-key"] = this.config.tacoreServerInteropAppServerApiKey;
209
+ headers[APP_SERVER_SERVICE_TOKEN_HEADER] = this.config.tacoreAppServerServiceToken;
210
+ headers[APP_SERVER_OWNER_APP_ID_HEADER] = this.config.appServerOwnerAppId || this.config.appId || process.env.APP_ID || this.appId;
211
+ } else if (this.config.appServerApiKey && !headers[APP_SERVER_API_KEY_HEADER]) {
212
+ headers[APP_SERVER_API_KEY_HEADER] = this.config.appServerApiKey;
213
+ } else if (
214
+ isBackendPreviewMode() &&
215
+ this.config.previewAppServerApiKey &&
216
+ !headers[PREVIEW_APPSERVER_API_KEY_HEADER]
217
+ ) {
218
+ headers[PREVIEW_APPSERVER_API_KEY_HEADER] = this.config.previewAppServerApiKey;
219
+ }
191
220
  } else if (
192
221
  isBackendPreviewMode() &&
193
222
  !token &&
@@ -304,6 +333,7 @@ export class BaseAppsClient {
304
333
  getConfig() {
305
334
  const config = { ...this.config };
306
335
  delete config.accessToken;
336
+ delete config.appServerApiKey;
307
337
  delete config.previewAppServerApiKey;
308
338
  return config;
309
339
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tacoreai/web-sdk",
3
3
  "description": "This file is for app server package, not the real npm package",
4
- "version": "1.22.0",
4
+ "version": "1.22.1-beta.1",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public",
@@ -13,6 +13,7 @@
13
13
  "./app-server-runtime": "./database/core/apps/AppServerRuntime.js"
14
14
  },
15
15
  "scripts": {
16
- "release": "npm version minor && git commit -am \"web-sdk update version\" && npm publish"
16
+ "release": "npm version minor && git commit -am \"web-sdk update version\" && git push && npm publish",
17
+ "release:beta": "npm version prerelease --preid beta --no-git-tag-version && git commit -am \"web-sdk update beta version\" && npm publish --tag beta"
17
18
  }
18
19
  }
package/utils/index.js CHANGED
@@ -54,11 +54,8 @@ export function getAppsApiBaseUrl() {
54
54
  }
55
55
  // 后端环境逻辑
56
56
  if (isBackend) {
57
- // stone 手动: tacore server 内网地址
58
- const baseUrl = process.env.TACORE_SERVER_INTRANET_BASE_URL;
59
- if (!baseUrl) {
60
- throw new Error("TACORE_SERVER_INTRANET_BASE_URL environment variable is not set in the backend environment.");
61
- }
57
+ // stone 手动:tacore server 地址,优先使用内网地址,兜底走公网(比如平台外本地部署)
58
+ const baseUrl = process.env.TACORE_SERVER_INTRANET_BASE_URL || 'https://api.tacore.chat';
62
59
  return baseUrl;
63
60
  }
64
61